WME Context Menu

A right-click popup menu for editing segments. Currently integrates with WME Speedhelper and Road Selector to help make it even easier and faster to edit the map.

Från och med 2016-04-05. Se den senaste versionen.

// ==UserScript==
// @name            WME Context Menu
// @namespace       https://greasyfork.org/users/11629-TheLastTaterTot
// @version         0.3.5
// @description     A right-click popup menu for editing segments. Currently integrates with WME Speedhelper and Road Selector to help make it even easier and faster to edit the map.
// @author          TheLastTaterTot
// @include         https://editor-beta.waze.com/*editor/*
// @include         https://www.waze.com/*editor/*
// @exclude         https://www.waze.com/*user/editor/*
// @grant           none
// @run-at          document-end
// ==/UserScript==
/* jshint -W097 */

//--------------- DEBUG ----------------
var DEBUG = false;
function cmlog() {
    if (DEBUG) {
	    var debugStyle, debugCode,
	    	args = arguments,
	        argArray = Object.keys(args).map(function(key) {
	            return args[key];
	        });
	        if (argArray[0].constructor === Array) {
	        	debugStyle = argArray[0][1];
	        	debugCode = parseInt(argArray[0][0]);
	        }

	    if (debugCode === DEBUG) {
	    	argArray = argArray.splice(1);

	    	switch (debugStyle) {
	    		case 1: // examine functions
	    			logCss = 'background: #444; color: #6FF';
	    			break;
	    		case 2: // examine eventListeners
	    			logCss = 'background: #CCC; color: #048';
	    			break;
	    		default:
	    			logCss = 'background: #EEE; color: #000; font-weight: bold;';
	    	}

	    	console.debug('%cWMECM: %s', logCss, argArray.join(' '));
	    }
	}
}
//---------------------------------------
var roadTypes = {
        1: "Street",
        2: "Primary Street",
        3: "Freeway",
        4: "Ramp",
        5: "Walking Trail",
        6: "Major Highway",
        7: "Minor Highway",
        8: "Dirt road / 4X4 Trail",
        10: "Pedestrian Boardwalk",
        15: "Ferry",
        16: "Stairway",
        17: "Private Road",
        18: "Railroad",
        19: "Runway/Taxiway",
        20: "Parking Lot Road"
    },
    menuResetEvent_RSel = false,
    menuResetEvent_SL = true,
    contextMenuSettings,
    slSavedMaxElementTotal = 0,
    slSavedMenuElementFlags = false,
    speedhelperHeight = null;
    speedhelperHTML = false,
    SL_imperial = {
        countries: ['UK', 'US', 'MU', 'AR', 'IC', 'JE', 'GQ', 'CJ', 'BM', 'LI'],
        mph2kph: 1.609344,
        kph2mph: 0.621371192,
        convertUnits: null
    },
    changeEvent = new Event('change', { 'bubbles': true }),
    isFirefox = !!~navigator.userAgent.indexOf('irefox'),
    minVersion = '0.3';

try {
	if (isFirefox && localStorage.WME_ContextMenuFF) {
		window.alert('WME Context Menu has been updated to be fully functional in FireFox.\n\nThank you for your patience!');
	    localStorage.removeItem('WME_ContextMenuFF');
	}
} catch(err) {}

//-----------------------------------------------
if (localStorage.WME_ContextMenu) {
    contextMenuSettings = JSON.parse(localStorage.WME_ContextMenu);
    if (!contextMenuSettings.hidden) contextMenuSettings.hidden = {};
} else {
    contextMenuSettings = {
        clipboard: 0,
        position: 0,
        pin: false,
        countries: [],
        version: 0,
        hidden: {}
    };
    localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);
}
//-----------------------------------------------

CMenuVersion = {
    currentVersion: GM_info.script.version,
    lastVersionString: function(){return contextMenuSettings.version;},
    convertToNumericVersion: function(versionString) {
        var vMult = [8000, 400, 20, 1],
            versionNumeric = 0;

        if (versionString) {
            if (versionString.constructor === Array) {
                if (versionString.length === 1) {
                    versionString = versionString[0];
                } else {
                    console.error('WMECM:', 'versionString is an array with more than 1 element.');
                }
            }
            return versionString.match(/(\d)+/g).map(function(d, i) {
                versionNumeric += d * vMult[i];
            }), versionNumeric;
        } else {
            return null;
        }
    },
    getLastVersionValue: function() {
        return this.convertToNumericVersion(this.lastVersionString());
    },
    updateVersionString: function() {
    	contextMenuSettings.version = this.currentVersion;
        localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);
    },
    isUpToDate: function(minimumVersionString) {
        var minVersionVal = this.convertToNumericVersion(minimumVersionString),
            lastVersionVal = this.getLastVersionValue();
        return (lastVersionVal >= minVersionVal) ? true : false;
    }
};
////////////////////////////////////////////////////////////////////////////////////////////////
var getUnique = function (objArray) {
    var isNotDuplicate = function (comparisonList, checkThisName) {
        var isNotDup = true;
        try {
            for (var c = 0, cLength = comparisonList.length; c < cLength; c++) {
                if (comparisonList[c] === checkThisName) isNotDup = false;
            }
        } catch (err) {
            console.error(err);
        }
        return isNotDup;
    };

    try {
        var uniqObjs = [];
        for (var r = objArray.length; r--;) {
            if (isNotDuplicate(uniqObjs, objArray[r])) uniqObjs.push(objArray[r]); // && objArray[r] !== ''
        }
        return uniqObjs;
    } catch (err) {
        console.error(err);
    }
};

//----------------------------------------------------------------------
var addPasteItems = function(attrName) {
    if (/primaryStreet|primaryCity/.test(attrName)) {
        clipboard = document.getElementById('input_' + attrName);
        if (clipboard) {
            pasteOption = document.createElement('dd');
            pasteOption.className = 'cm-paste';
            pasteOption.innerHTML = clipboard.value;
            pasteOption.name = clipboard.value;
            //$('#'+attrName+'>dt').after('<dd class="cm-paste" name="' + clipboard.value + '">' + clipboard.value +'</dd>');

            $('#' + attrName + ' .cm-paste').remove();

            if (document.querySelector('#' + attrName + '>dd')){
                document.getElementById(attrName).insertBefore(pasteOption, document.querySelector('#' + attrName + '>dd'));
            } else {
                document.getElementById(attrName).appendChild(pasteOption);
            }
            pasteOption.addEventListener('click', function(e) { pasteTo(e, this.parentNode.id, this.name); }, false);
        }
    }
};

var pasteTo = function (e, opt, val) {
    try { // (temporary) really dumb way via DOM nodes:
        switch (opt) {
            case 'cm_primaryStreet':
                $('.address-edit-icon').click();
                $('#emptyStreet').prop('checked',false).change();
                $('.form-control.streetName').val(val).change();
                if (!$('.form-control[name=cityName]').val().length) $('#emptyCity').prop('checked',true).change();
                if ($('.form-control[name=stateID]').length) $('.form-control[name=stateID]').val(W.model.states.top.id);
                if ($('.form-control[name=countryID]').length) $('.form-control[name=countryID]').val(W.model.countries.top.id);
                $('.action-buttons>.save-button').click();
                break;
            case 'cm_primaryCity':
                $('.address-edit-icon').click();
                $('#emptyCity').prop('checked',false).change();
                $('.form-control[name=cityName]').val(val).change();
                if (!$('.form-control.streetName').val().length) $('#emptyStreet').prop('checked',true).change();
                if ($('.form-control[name=stateID]').length) $('.form-control[name=stateID]').val(W.model.states.top.id);
                if ($('.form-control[name=countryID]').length) $('.form-control[name=countryID]').val(W.model.countries.top.id);
                $('.action-buttons>.save-button').click();
                break;
            /*case 'cm_state':
                var $selState = $('.form-control[name=stateID]>option');
                $selState.map(function(i,opt){ console.info(opt.text); if (opt.text === val) opt.prop('selected','') });
                break;*/
        }
    } catch(err) { console.error(err); }
};

var getTopCityName = function() {
	return W.model.cities.objects[W.model.segments.topCityID].name;
};

//----------------------------------------------------------------------
var addStreetAndCityToRSel = function (segIds, caseSelection) {
    var opAndOr = parseInt(document.getElementById('cmOpAddOr').value),
        opNot = document.getElementById('cmOpNot').classList.contains('active'),
        addConjunction = !!document.getElementById('outRSExpr').innerHTML.length, s;

    switch (caseSelection ^ 0) {
        case 'primaryStreet':
        case 0:
            if (segIds.primaryStreet.length !== 0) {
                for (s = segIds.primaryStreet.length; s--;) {
                    if (addConjunction) {
                        if (opAndOr) {
                            document.getElementById('btnRSOr').click();
                        } else {
                            document.getElementById('btnRSAnd').click();
                        }
                    }
                    if (opNot) document.getElementById('btnRSNot').click();
                    //set to primary
                    document.getElementById('selRSAlttStreet').value = 0;
                    document.getElementById('selRSAltCity').value = 0;

                    document.getElementById('btnRSLBkt').click();
                    document.getElementById('inRSStreet').value = Waze.model.streets.objects[segIds.primaryStreet[s]].name;
                    document.getElementById('btnRSAddStreet').click();
                    //document.getElementById('btnRSAnd').click();
                    document.getElementById('inRSCity').value = Waze.model.cities.objects[Waze.model.streets.objects[segIds.primaryStreet[s]].cityID].name;
                    document.getElementById('btnRSAddCity').click();
                    document.getElementById('btnRSRBkt').click();
                }
            }
            break;
        case 'altStreets':
        case 1:
            if (segIds.altStreets.length !== 0) {
                for (s = segIds.primaryStreet.length; s--;) {
                    if (addConjunction) {
                        if (opAndOr) {
                            document.getElementById('btnRSOr').click();
                        } else {
                            document.getElementById('btnRSAnd').click();
                        }
                    }

                    if (opNot) document.getElementById('btnRSNot').click();
                    //set to alt
                    document.getElementById('selRSAlttStreet').value = 1;
                    document.getElementById('selRSAltCity').value = 1;

                    document.getElementById('btnRSLBkt').click();
                    document.getElementById('inRSStreet').value = Waze.model.streets.objects[segIds.altStreets[s]].name;
                    document.getElementById('btnRSAddStreet').click();
                    //document.getElementById('btnRSAnd').click();
                    document.getElementById('inRSCity').value = Waze.model.cities.objects[Waze.model.streets.objects[segIds.altStreets[s]].cityID].name;
                    document.getElementById('btnRSAddCity').click();
                    document.getElementById('btnRSRBkt').click();
                }
            }
            break;
        case 'anyStreet':
        case 2:
            addStreetAndCityToRSel(segIds, 0); //primary
            addStreetAndCityToRSel(segIds, 1); //alt
            break;
    }
};

var addStreetNameToRSel = function (segNames, inputFieldId, altSelId, altVal, addBtnId) {
    var opAndOr = parseInt(document.getElementById('cmOpAddOr').value),
        opNot = document.getElementById('cmOpNot').classList.contains('active'),
        addConjunction = !!document.getElementById('outRSExpr').innerHTML.length;

    for (var n = 0, nLength = segNames.length; n < nLength; n++) {
        if (addConjunction) {
            if (opAndOr) {
                document.getElementById('btnRSOr').click();
            } else {
                document.getElementById('btnRSAnd').click();
            }
        }
        if (opNot) document.getElementById('btnRSNot').click();

        document.getElementById(inputFieldId).value = segNames[n];
        document.getElementById(altSelId).value = altVal ^ 0;
        document.getElementById(addBtnId).click();
        addConjunction = true;
    }
};

var addRoadTypeToRSel = function (ids, inputFieldId, addBtnId) {
    var opAndOr = parseInt(document.getElementById('cmOpAddOr').value),
        opNot = document.getElementById('cmOpNot').classList.contains('active'),
        addConjunction = !!document.getElementById('outRSExpr').innerHTML.length;

    document.getElementById('btnRSLBkt').click();
    for (var n = 0, nLength = ids.length; n < nLength; n++) {
        if (addConjunction) {
            if (opAndOr) {
                document.getElementById('btnRSOr').click();
            } else {
                document.getElementById('btnRSAnd').click();
            }
        }
        if (opNot) document.getElementById('btnRSNot').click();

        document.getElementById(inputFieldId).value = ids[n];
        document.getElementById(addBtnId).click();
        addConjunction = true;
    }
    document.getElementById('btnRSRBkt').click();
};

var copyToClipboard = function(id,str) {
    var $hiddenText;
    if (!document.getElementById('input_' + id)) {
        $hiddenText = $('<input>');
        $hiddenText.prop('id','input_' + id);
        $hiddenText.css('position','fixed');
        $hiddenText.css('top','-100px');
        $hiddenText.css('left','-1000px');
        $hiddenText.css('opacity',0);
        $("body").append($hiddenText);
    } else {
        $hiddenText = $('#input_' + id);
    }
    $hiddenText.val(str).select();
    document.execCommand("copy");
    //$temp.remove();
};

var copyTo = function (e, opt, val) {
    if (document.getElementById('cmPinMenu').value) e.stopPropagation();

    if (document.getElementById('cmClipboard').value) {
        copyToClipboard(opt,val);
        if (document.getElementById('cmPinMenu').value) addPasteItems(opt);
    } else {
        try {
            switch (opt) {
                case 'cm_priSC':
                    if (document.getElementById('cmRoadType').classList.contains('active')) {
                        addRoadTypeToRSel(getUnique(val.roadType), 'selRSRoadType', 'btnRSAddRoadType');
                        document.getElementById('btnRSAnd').click();
                        document.getElementById('btnRSLBkt').click();
                        addStreetAndCityToRSel(val, 0);
                        document.getElementById('btnRSRBkt').click();
                    } else {
                        addStreetAndCityToRSel(val, 0);
                    }
                    break;
                case 'cm_altSC':
                    if (document.getElementById('cmRoadType').classList.contains('active')) {
                        addRoadTypeToRSel(getUnique(val.roadType), 'selRSRoadType', 'btnRSAddRoadType');
                        document.getElementById('btnRSAnd').click();
                        document.getElementById('btnRSLBkt').click();
                        addStreetAndCityToRSel(val, 1);
                        document.getElementById('btnRSRBkt').click();
                    } else {
                        addStreetAndCityToRSel(val, 1);
                    }
                    break;
                case 'cm_anySC':
                    if (document.getElementById('cmRoadType').classList.contains('active')) {
                        addRoadTypeToRSel(getUnique(val.roadType), 'selRSRoadType', 'btnRSAddRoadType');
                        document.getElementById('btnRSAnd').click();
                        document.getElementById('btnRSLBkt').click();
                        addStreetAndCityToRSel(val, 2);
                        document.getElementById('btnRSRBkt').click();
                    } else {
                        addStreetAndCityToRSel(val, 2);
                    }
                    break;
                case 'cm_priS':
                    if (document.getElementById('cmRoadType').classList.contains('active')) {
                        addRoadTypeToRSel(getUnique(val.roadType), 'selRSRoadType', 'btnRSAddRoadType');
                        document.getElementById('btnRSAnd').click();
                        document.getElementById('btnRSLBkt').click();
                        addStreetNameToRSel(getUnique(val.primaryStreet), 'inRSStreet', 'selRSAlttStreet', 0, 'btnRSAddStreet');
                        document.getElementById('btnRSRBkt').click();
                    } else {
                        addStreetNameToRSel(getUnique(val.primaryStreet), 'inRSStreet', 'selRSAlttStreet', 0, 'btnRSAddStreet');
                    }
                    break;
                case 'cm_altS':
                    if (document.getElementById('cmRoadType').classList.contains('active')) {
                        addRoadTypeToRSel(getUnique(val.roadType), 'selRSRoadType', 'btnRSAddRoadType');
                        document.getElementById('btnRSAnd').click();
                        document.getElementById('btnRSLBkt').click();
                        addStreetNameToRSel(getUnique(val.altStreets), 'inRSStreet', 'selRSAlttStreet', 1, 'btnRSAddStreet');
                        document.getElementById('btnRSRBkt').click();
                    } else {
                        addStreetNameToRSel(getUnique(val.altStreets), 'inRSStreet', 'selRSAlttStreet', 1, 'btnRSAddStreet');
                    }
                    break;
                case 'cm_anyS':
                    if (document.getElementById('cmRoadType').classList.contains('active')) {
                        addRoadTypeToRSel(getUnique(val.roadType), 'selRSRoadType', 'btnRSAddRoadType');
                        document.getElementById('btnRSAnd').click();
                        document.getElementById('btnRSLBkt').click();
                        addStreetNameToRSel(getUnique(val.primaryStreet), 'inRSStreet', 'selRSAlttStreet', 0, 'btnRSAddStreet');
                        addStreetNameToRSel(getUnique(val.altStreets), 'inRSStreet', 'selRSAlttStreet', 1, 'btnRSAddStreet');
                        document.getElementById('btnRSRBkt').click();
                    } else {
                        addStreetNameToRSel(getUnique(val.primaryStreet), 'inRSStreet', 'selRSAlttStreet', 0, 'btnRSAddStreet');
                        addStreetNameToRSel(getUnique(val.altStreets), 'inRSStreet', 'selRSAlttStreet', 1, 'btnRSAddStreet');
                    }
                    break;
                case 'cm_ids':
                    document.getElementById('inRSSegId').value = val;
                    document.getElementById('btnRSAddSegId').classList.add('btn-info');
                    break;
                case 'cm_primaryStreet':
                    document.getElementById('inRSStreet').value = val;
                    document.getElementById('selRSAlttStreet').value = 0;
                    document.getElementById('btnRSAddStreet').classList.add('btn-info');
                    break;
                case 'cm_altStreets':
                    document.getElementById('inRSStreet').value = val;
                    document.getElementById('selRSAlttStreet').value = 1;
                    document.getElementById('btnRSAddStreet').classList.add('btn-info');
                    break;
                case 'cm_altStreetsAND':
                    document.getElementById('inRSStreet').value = '';
                    break;
                case 'cm_altStreetsOR':
                    document.getElementById('inRSStreet').value = '';
                    break;
                case 'cm_primaryCity':
                    document.getElementById('inRSCity').value = val;
                    document.getElementById('selRSAltCity').value = 0;
                    document.getElementById('btnRSAddCity').classList.add('btn-info');
                    break;
                case 'cm_altCities':
                    document.getElementById('inRSCity').value = val;
                    document.getElementById('selRSAltCity').value = 1;
                    document.getElementById('btnRSAddCity').classList.add('btn-info');
                    break;
                case 'cm_state':
                    document.getElementById('inRSState').value = val;
                    document.getElementById('btnRSAddState').classList.add('btn-info');
                    break;
                case 'cm_roadType':
                    document.getElementById('selRSRoadType').value = val;
                    document.getElementById('btnRSAddRoadType').classList.add('btn-info');
                    break;
                case 'cm_updatedBy':
                    document.getElementById('inRSUpdtd').value = val;
                    document.getElementById('btnRSAddUpdtd').classList.add('btn-info');
                    break;
                case 'cm_createdBy':
                    document.getElementById('inRSCrtd').value = val;
                    document.getElementById('btnRSAddCrtd').classList.add('btn-info');
                    break;
                case 'cm_toConnections':
                    break;
            }
        } catch (err) {
            console.error(err);
        }

        // swap panel from selection panel
        try {
            document.getElementById('user-info').style.display = 'block';
            document.getElementById('edit-panel').style.display = 'none';
        } catch (err) {
            console.error(err);
        }

        // switch active tab-content
        try {
            document.querySelector('.tab-content>.active').classList.remove('active');
            document.getElementById('sidepanel-roadselector').classList.add('active');
        } catch (err) {}

        // switch active nav-tab
        try {
            document.querySelector('#user-tabs li.active').classList.remove('active');
            document.getElementById('tabRSel').classList.add('active');
        } catch (err) {}

        // switch to RSel editor tab
        try {
            document.getElementById('roadselector-tabs').children[1].classList.remove('active');
            document.getElementById('roadselector-tabs').children[0].classList.add('active');
            document.getElementById('roadselector-tab-content').children[1].classList.remove('active');
            document.getElementById('roadselector-tab-content').children[0].classList.add('active');
        } catch (err) { /* */ }
    }
};

var getAutoAddToRSelCase = function (addType) {
    var nameClass = ((document.getElementById('cm_pri').checked * document.getElementById('cm_pri').value) + (document.getElementById('cm_alt').checked * document.getElementById('cm_alt').value)) - 1;

    switch (addType) {
        case 'cm_S':
            switch (nameClass) {
                case 0: //primary
                    return 'cm_priS';
                case 1: //alt
                    return 'cm_altS';
                case 2: //any
                    return 'cm_anyS';
            }
            break;
        case 'cm_SC': // cm_SC street and city
            switch (nameClass) {
                case 0: //primary
                    return 'cm_priSC';
                case 1: //alt
                    return 'cm_altSC';
                case 2: //any
                    return 'cm_anySC';
            }
            break;
    }
};

var getSegmentProperties = function (selectedStuff) {
	cmlog([1,1], 'getSegmentProperties()');
    try {
        /*var roadTypeOpts = document.querySelector('#segment-edit-general select[name="roadType"]'),
            numRoadTypes = roadTypeOpts.options.length, r, roadTypes = {};
        for (r=0; r < numRoadTypes; r++) {
            roadTypes[roadTypeOpts[r].value] = roadTypeOpts[r].text;
        }*/
        var s, segments = selectedStuff.segments,
            s_altStObjKeys, s_altSt, s_toConnObjKeys, a, numAlts, k, numKeys;

        var s_ids = {
            ids: {},
            primaryStreet: {},
            altStreets: {},
            altCities: {},
            roadType: {},
            createdBy: {},
            updatedBy: {},
            toConnections: {},
            primaryCity: {},
            state: {},
            country: {}
        };

        for (s = segments.length; s--;) {
            if (segments[s].model.attributes.id)
                s_ids.ids[segments[s].model.attributes.id] = null;
            if (segments[s].model.attributes.primaryStreetID) {
                s_ids.primaryStreet[segments[s].model.attributes.primaryStreetID] = null;
                if (Waze.model.streets.objects[segments[s].model.attributes.primaryStreetID].cityID) {
                    s_ids.primaryCity[Waze.model.streets.objects[segments[s].model.attributes.primaryStreetID].cityID] = null;
                    if (Waze.model.cities.objects[Waze.model.streets.objects[segments[s].model.attributes.primaryStreetID].cityID].stateID)
                        s_ids.state[Waze.model.cities.objects[Waze.model.streets.objects[segments[s].model.attributes.primaryStreetID].cityID].stateID] = null;

                    s_ids.country[Waze.model.cities.objects[Waze.model.streets.objects[segments[s].model.attributes.primaryStreetID].cityID].countryID] = null;
                }
            }
            s_ids.roadType[segments[s].model.attributes.roadType] = null;
            s_ids.createdBy[segments[s].model.attributes.createdBy] = null;
            if (segments[s].model.attributes.updatedBy)
                s_ids.updatedBy[segments[s].model.attributes.updatedBy] = null;


            s_altSt = segments[s].model.attributes.streetIDs;
            numAlts = s_altSt.length;
            for (a = 0;  a < numAlts; a++) {
                try {
                    s_ids.altStreets[s_altSt[a]] = null;
                    s_ids.altCities[Waze.model.streets.objects[s_altSt[a]].cityID] = null;
                } catch(err) {}
            }

            s_toConnObjKeys = Object.keys(segments[s].model.attributes.toConnections);
            numKeys = s_toConnObjKeys.length;
            for (k = 0; k < numKeys; k++) {
                try {
                    if (s_toConnObjKeys[k] !== '') {
                        s_ids.toConnections[s_toConnObjKeys[k]] = segments[s].model.attributes.toConnections[s_toConnObjKeys[k]];
                    }
                } catch(err) {}
            }
        }

        var seg_ids = {},
            seg_names = {
                ids: [],
                primaryStreet: [],
                altStreets: [],
                altCities: [],
                roadType: [],
                createdBy: [],
                updatedBy: [],
                toConnections: [],
                primaryCity: [],
                state: [],
                country: []
            };

        for (var idKey in s_ids) {
            seg_ids[idKey] = Object.keys(s_ids[idKey]);
            numKeys = seg_ids[idKey].length;

            for (k = 0; k < numKeys; k++) {
                try {
                    if (seg_ids[idKey][k] !== '') {
                        switch (idKey) {
                            case 'primaryStreet':
                                seg_names[idKey][k] = Waze.model.streets.objects[seg_ids[idKey][k]].name;
                                break;
                            case 'primaryCity':
                                seg_names[idKey][k] = Waze.model.cities.objects[seg_ids[idKey][k]].name;
                                break;
                            case 'altStreets':
                                seg_names[idKey][k] = Waze.model.streets.objects[seg_ids[idKey][k]].name;
                                break;
                            case 'altCities':
                                seg_names[idKey][k] = Waze.model.cities.objects[seg_ids[idKey][k]].name;
                                break;
                            case 'state':
                                seg_names[idKey][k] = Waze.model.states.objects[seg_ids[idKey][k]].name;
                                break;
                            case 'roadType':
                                seg_names[idKey][k] = roadTypes[String(seg_ids[idKey][k])];
                                break;
                            case 'createdBy':
                                seg_names[idKey][k] = Waze.model.users.objects[seg_ids[idKey][k]].userName;
                                break;
                            case 'updatedBy':
                                seg_names[idKey][k] = Waze.model.users.objects[seg_ids[idKey][k]].userName;
                                break;
                        }
                    }
                } catch(err) {}
            }
        }
        return {
            ids: seg_ids,
            names: seg_names
        };
    } catch (err) {
        console.error(err);
    }
};

var addHotkeyListener = function() {
	cmlog([1,1], 'addHotkeyListener()');
    window.addEventListener('keydown', menuShortcutKeys, true);
    cmlog([2,2],'Adding global hotkey listener due to mouseenter');
};

var removeHotkeyListener = function() {
	cmlog([1,1], 'removeHotkeyListener()');
    window.removeEventListener('keydown', menuShortcutKeys, true);
    cmlog([2,2],'Removing global hotkey listener due to mouseleave');
};

var closeContextMenu = function () {
	cmlog([1,1], 'closeContextMenu()');
    try {
    	// remove unnecessary hotkey listeners
        window.removeEventListener('keydown', menuShortcutKeys, true);
		document.getElementById('cmContextMenu').removeEventListener('mouseenter', addHotkeyListener, false);
		document.getElementById('cmContextMenu').removeEventListener('mouseleave', removeHotkeyListener, false);
		cmlog([2,2],'Closing menu, so removing all hotkey listeners');
		// remove unnecessary close contextmenu listeners
        window.removeEventListener('click', closeContextMenu, false);
        document.getElementById('toolbar').removeEventListener('mouseenter', closeContextMenu, false);

    	Waze.selectionManager.events.unregister("selectionchanged", null, setupSegmentContextMenu);

        // close the menu
        document.getElementById('cmContextMenu').style.display = 'none';
        menuResetEvent_SL = true;
    } catch (err) {}
};

var addSpecialMenuListeners = function () {
	cmlog([1,1], 'addSpecialMenuListeners()');
    if (document.getElementById('cmPinMenu').value) {
        window.removeEventListener('click', closeContextMenu, false);
        document.getElementById('toolbar').removeEventListener('mouseenter', closeContextMenu, false);
    } else {
        window.addEventListener('keydown', menuShortcutKeys, true);
        cmlog([2,2],'Adding just global hotkey listener via addSpecialMenuListeners()');
        window.addEventListener('click', closeContextMenu, false);
        document.getElementById('toolbar').addEventListener('mouseenter', closeContextMenu, false);
    }
};

var adjustContextMenubar = function(pos) {
	if (pos === 1) {
       	document.getElementById('cmFooterCaret').classList.add('fa-angle-down');
        document.getElementById('cmFooterCaret').classList.remove('fa-angle-up');
        document.getElementById('cmFooter').classList.add('cm-top');
        document.getElementById('cmFooter').classList.remove('cm-bottom');
        document.getElementById('cmFooter').style.marginBottom = '-1px';
        document.getElementById('cmContextMenu').insertBefore(document.getElementById('cmFooter'), document.getElementById('cmContextMenu').children[0]);
	} else {
        document.getElementById('cmFooterCaret').classList.add('fa-angle-up');
        document.getElementById('cmFooterCaret').classList.remove('fa-angle-down');
        document.getElementById('cmFooter').classList.remove('cm-top');
        document.getElementById('cmFooter').classList.add('cm-bottom');
        document.getElementById('cmFooter').style.marginBottom = '0';
        document.getElementById('cmContextMenu').appendChild(document.getElementById('cmFooter'));
	}
};

var hideMenuSection = function(evt, sectionName) {
    var that;
    if (evt) that = evt.target;
    else if (sectionName) that = document.querySelector('.cm-menu-section[name=' + sectionName + ']>.cm-hide');

    that.classList.toggle('fa-caret-down');
    that.classList.toggle('fa-caret-up');

    if (that.classList.contains('fa-caret-up')) { //hidden section
        that.parentNode.classList.add('cm-hidden');
        contextMenuSettings.hidden[that.parentNode.getAttribute('name')] = true;
    } else { //revealed section
        that.parentNode.classList.remove('cm-hidden');
        contextMenuSettings.hidden[that.parentNode.getAttribute('name')] = false;
    }
    localStorage.contextMenuSettings = JSON.stringify(contextMenuSettings);
};

var resetContextMenu = function (contextMenuSettings) {
	cmlog([1,1], 'resetContextMenu()');

    var menuHTML;

    if (menuResetEvent_RSel) document.getElementById('btnRSClear').click();
    menuResetEvent_RSel = false;

    try {
        document.querySelector('#user-tabs a[href="#sidepanel-roadselector"]').parentNode.id = 'tabRSel';
    } catch (err) {}
    // <label for="cm_any" class="btn cm-rsel-options badge cm-badge-right active"><input id="cm_any" type="radio" name="cmOptions" value=2 style="opacity: 0" checked>any</label> //dl - background-color: rgba(177, 210, 220, 0.75);

    if (contextMenuSettings.clipboard === 1) { //RSel
	    document.getElementById('cmContainer').innerHTML = `
	    <div id="cmRSelAutoAdd" class="cm-menu-section cm-rsel">
	        <dl><dt>RSel Auto-Add Names</dt>
	            <dd><div class="btn-group pull-left" data-toggle="buttons">
	                    <label for="cm_pri" class="btn cm-rsel-options cm-badge-left active"><input id="cm_pri" type="checkbox" value=1 checked>&nbsp;Primary</label>
	                    <label for="cm_alt" class="btn cm-rsel-options cm-badge-right active"><input id="cm_alt" type="checkbox" value=2 checked>Alternate&nbsp;</label>
	                </div>
	                <div class="pull-left btn-group" style="margin: 0px 2px">
	                    <button type="button" id="cmRoadType" class="btn cm-rsel-options active">Type</button>
	                </div>
	                <div class="btn-group pull-right">
	                    <button type="button" id="cmOpAddOr" class="btn cm-rsel-options cm-badge-left and" value="0" style="padding-right: 4px;">and</button>
	                    <button type="button" id="cmOpNot" class="btn cm-rsel-options cm-badge-right" style="padding-left: 4px;">!</button>
	            </div></dd>
	            <dd id="cm_SC">All <span id="cm_textSC">Primary/Alt. Street, City</span></dd>
	            <dd id="cm_S">All <span id="cm_textS">Primary/Alt. Street</span></dd>
	        </dl>
	    </div>`;
	} else {
		document.getElementById('cmContainer').innerHTML = '';
	}

    menuHTML = `
    <div class="cm-menu-header">
        <dl><dt id="cmMenuHeaderTitle">Copy To Clipboard</dt></dl>
    </div>
    <div id="cmMenuNoContent" class="cm-menu-section" style="display: none;"><dd>No valid segment(s) selected</dd></div>
    <div id="cmMenuContent">
        <div id="cm_street" name="street" class="cm-menu-section">
            <span class="fa fa-caret-down cm-hide"></span><span class="fa fa-caret-up cm-hide"></span>
            <dl id="cm_primaryStreet">
                <dt>Street</dt>
            </dl>
            <dl id="cm_altStreets">
                <dt>Alt street</dt>
            </dl>
        </div>
        <div id="cm_city" name="city" class="cm-menu-section">
            <span class="fa fa-caret-down cm-hide"></span><span class="fa fa-caret-up cm-hide"></span>
            <dl id="cm_primaryCity">
                <dt>City</dt>
            </dl>
            <dl id="cm_altCities">
                <dt>Alt city</dt>
            </dl>
        </div>
        <div name="state" class="cm-menu-section">
            <span class="fa fa-caret-down cm-hide"></span><span class="fa fa-caret-up cm-hide"></span>
            <dl id="cm_state">
                <dt>State</dt>
            </dl>
        </div>
        <div name="roadtype" class="cm-menu-section">
            <span class="fa fa-caret-down cm-hide"></span><span class="fa fa-caret-up cm-hide"></span>
            <dl id="cm_roadType">
                <dt>Road type</dt>
            </dl>
        </div>
        <div name="by" class="cm-menu-section">
            <span class="fa fa-caret-down cm-hide"></span><span class="fa fa-caret-up cm-hide"></span>
            <dl id="cm_updatedBy">
                <dt>Updated by</dt>
            </dl>
            <dl id="cm_createdBy">
                <dt>Created by</dt>
            </dl>
        </div>
        <div name="id" class="cm-menu-section">
            <dl>
                <dd id="cm_ids">Segment IDs</dd>
            </dl>
    </div></div>`;



    for (var h in contextMenuSettings.hidden) {
        if (contextMenuSettings.hidden[h]) //hideMenuSection(null, h);
            menuHTML = menuHTML.replace(new RegExp('(name="' + h + '" class=")','m'),'$1cm-hidden ');
    }

    document.getElementById('cmContainer').innerHTML += menuHTML;

    $('.cm-hide').click(hideMenuSection);

    // if menu is not pinned
    if (!document.getElementById('cmPinMenu').value) {
        setTimeout(addSpecialMenuListeners, 250);
    }

    adjustContextMenubar(contextMenuSettings.position);
};

var hidePasteMenu = function(bit) {
    if (bit === undefined) bit = !document.getElementById('cmClipboard').value;

    if ($('.cm-paste').length) {
        $('.cm-paste').each(function(i, node) {
            if (bit) node.style.display = 'none';
            else node.style.display = '';
        });
    }
};

var populateCopyMenu = function (segInfo, contextMenuSettings) {
	cmlog([1,1], 'populateCopyMenu()');

    document.getElementById('cmMenuNoContent').style.display = 'none';
    document.getElementById('cmMenuContent').style.display = 'block';

    try {
        resetContextMenu(contextMenuSettings);
        var updateNames = Object.keys(segInfo.ids),
            numNames = updateNames.length,
            n, selOption, emptyArr, s_names = {},
            s_ids = {}, clipboard, pasteOption, sectionElement;

        for (n = numNames; n--;) {
            sectionElement = document.getElementById('cm_' + updateNames[n]);
            if (sectionElement &&
                segInfo.ids[updateNames[n]] &&
                segInfo.ids[updateNames[n]].length) {

                if (updateNames[n] !== 'ids') {
                    emptyArr = 0;
                    s_names[updateNames[n]] = getUnique(segInfo.names[updateNames[n]])
                    s_ids[updateNames[n]] = getUnique(segInfo.ids[updateNames[n]])

                    for (var a = 0, aLength = s_ids[updateNames[n]].length; a < aLength; a++) {
                    	try {
	                    	selOption = document.createElement('dd');
	                        if (s_names[updateNames[n]][a]) {
	                            if (s_names[updateNames[n]][a] === '') { //no city or no street
	                                if (/city/i.test(updateNames[n])) {
	                                    selOption.innerHTML = 'No City';
	                                } else if (/street/i.test(updateNames[n])) {
	                                    selOption.innerHTML = 'No Street';
	                                }
	                                //selOption.name = '';
	                            } else { //add the property value to the menu
	                                selOption.innerHTML = s_names[updateNames[n]][a];
	                                //selOption.name = s_names[updateNames[n]][a];
	                            }
	                            sectionElement.appendChild(selOption);

	                            switch (updateNames[n]) {
	                                case 'roadType':
	                                    selOption.name = s_ids[updateNames[n]][a];
	                                    if (document.getElementById('cmClipboard').value) selOption.name = s_names[updateNames[n]][a];
	                                    selOption.onclick = function (e) {
	                                        //if (document.getElementById('cmPinMenu').value) e.stopPropagation();
	                                        copyTo(e, this.parentNode.id, this.name);
	                                    };
	                                    break;
	                                default:
	                                    selOption.name = s_names[updateNames[n]][a];
	                                    selOption.onclick = function (e) {
	                                        //if (document.getElementById('cmPinMenu').value) e.stopPropagation();
	                                        copyTo(e, this.parentNode.id, this.name);
	                                    };
	                            }
	                        } else {
	                            emptyArr++;
	                        }
	                    } catch(err) { console.error(err); }
                    }

                    // Check if something has been copied into clipboard for pasting...
                    if (document.getElementById('cmClipboard').value) addPasteItems('cm_' + updateNames[n]);

                    // Hide section if nothing to copy
                    if (emptyArr === aLength && !$('#input_cm_' + updateNames[n]).length) {
                        sectionElement.style.display = "none";
                        //sectionElement.parentNode.children[0].style.display = "none";
		            } else if ($('#input_cm_' + updateNames[n]).length) { //show if something to paste
		            	selOption = document.createElement('dd');
		            	selOption.innerHTML = '&nbsp;';
		            	sectionElement.appendChild(selOption);
		            }
                } else { // Segment IDs
                    selOption = document.getElementById('cm_ids');
                    selOption.name = segInfo.ids[updateNames[n]].join(',');
                    selOption.onclick = function (e) {
                        //if (document.getElementById('cmPinMenu').value) e.stopPropagation();
                        copyTo(e, this.id, this.name);
                    };
                }
                //--------------------------------------------
            } else if (sectionElement && !$('#input_cm_' + updateNames[n]).length) { // Hide section if nothing to copy
                //sectionElement.parentNode.style.display = "none";
                sectionElement.style.display = "none";
                sectionElement.parentNode.children[0].style.display = "none"; //caret-down
                sectionElement.parentNode.children[1].style.display = "none"; //caret-up
            } else if (sectionElement && $('#input_cm_' + updateNames[n]).length) { //show if something to paste
            	selOption = document.createElement('dd');
                if (/city/i.test(updateNames[n])) {
                    selOption.innerHTML = 'No City';
                } else if (/street/i.test(updateNames[n])) {
                    selOption.innerHTML = 'No Street';
                } /*else {
                	selOption.innerHTML = '&nbsp;';
                }*/
                if (selOption.innerHTML.length) {
	                selOption.name = '';
	                sectionElement.appendChild(selOption);
	                selOption.onclick = function (e) {
	                    //if (document.getElementById('cmPinMenu').value) e.stopPropagation();
	                    copyTo(e, this.parentNode.id, this.name);
	                };
	            	sectionElement.appendChild(selOption);
	            }
            }
        }
        // Hide sections if nothing to copy
        if (!s_names.primaryStreet.length && !s_names.altStreets.length && !$('#input_cm_primaryStreet').length) {
            document.getElementById('cm_primaryStreet').parentNode.style.display = 'none';
        }
        if (!s_names.primaryCity.length && !s_names.altCities.length && !$('#input_cm_primaryCity').length) {
            document.getElementById('cm_primaryCity').parentNode.style.display = 'none';
        }
        if (!s_names.state.length) document.getElementById('cm_state').parentNode.style.display = 'none';

        var numSegments = Waze.selectionManager.selectionCountByType.segment;
        if (!numSegments) numSegments = '0 Segment IDs';
        else if (numSegments === 1) numSegments = '1 Segment ID';
        else numSegments = numSegments + ' Segment IDs';
        document.getElementById('cm_ids').innerHTML = numSegments;

        //=================================================================================
        // RSEL-specific menu items
        //=================================================================================
        if (document.getElementById('cmRSel').value) {
            document.getElementById('cmMenuHeaderTitle').innerHTML = 'Send To Road Selector';

            var copyToRSelAndSelect = document.createElement('dd');
            copyToRSelAndSelect.id = 'cm_SCgo';
            copyToRSelAndSelect.zIndex = 1;
            copyToRSelAndSelect.className = 'fa fa-fast-forward pull-right cm-rsel-goselect';
            document.getElementById('cm_SC').parentNode.insertBefore(copyToRSelAndSelect, document.getElementById('cm_SC'));

            var copyToRSelAndSelect2 = copyToRSelAndSelect.cloneNode();
            copyToRSelAndSelect2.id = 'cm_Sgo';
            copyToRSelAndSelect.zIndex = 1;
            document.getElementById('cm_S').parentNode.insertBefore(copyToRSelAndSelect2, document.getElementById('cm_S'));

            document.getElementById('cmRSelAutoAdd').style.display = 'block';

            //name options
            document.getElementById('cm_pri').parentNode.addEventListener('click', function (e) {
                e.stopPropagation();
                var numBtnDown = this.parentNode.getElementsByClassName('active').length;
                if (this.children[0].checked) { //currently checked... decide whether to uncheck
                    this.classList.remove('active');
                    this.children[0].checked = false;
                    if (numBtnDown === 0) {
                        document.getElementById('cm_alt').parentNode.classList.add('active')
                        document.getElementById('cm_alt').checked = true;
                    }
                    document.getElementById('cm_textSC').innerHTML = 'Alt. Street, City';
                    document.getElementById('cm_textS').innerHTML = 'Alt. Street';
                } else { //unchecked
                    this.classList.add('active');
                    this.children[0].checked = true;
                    document.getElementById('cm_textSC').innerHTML = 'Primary/Alt. Street, City';
                    document.getElementById('cm_textS').innerHTML = 'Primary/Alt. Street';
                }
            }, false);
            document.getElementById('cm_alt').parentNode.addEventListener('click', function (e) {
                e.stopPropagation();
                var numBtnDown = this.parentNode.getElementsByClassName('active').length;
                if (this.children[0].checked) { //currently checked... decide whether to uncheck
                    this.classList.remove('active');
                    this.children[0].checked = false;
                    if (numBtnDown === 0) {
                        document.getElementById('cm_pri').parentNode.classList.add('active');
                        document.getElementById('cm_pri').checked = true;
                    }
                    document.getElementById('cm_textSC').innerHTML = 'Primary Street, City';
                    document.getElementById('cm_textS').innerHTML = 'Primary Street';
                } else { //unchecked
                    this.classList.add('active');
                    this.children[0].checked = true;
                    document.getElementById('cm_textSC').innerHTML = 'Primary/Alt. Street, City';
                    document.getElementById('cm_textS').innerHTML = 'Primary/Alt. Street';
                }
            }, false);

            //road type
            document.getElementById('cmRoadType').addEventListener('click', function (e) {
                e.stopPropagation();
                if (document.getElementById('cmRoadType').classList.contains('active')) {
                    document.getElementById('cmRoadType').classList.remove('active')
                } else {
                    document.getElementById('cmRoadType').classList.add('active')
                }
            }, false);

            //operations
            document.getElementById('cmOpAddOr').addEventListener('click', function (e) {
                e.stopPropagation();
                var opToggleLabel = ['and', 'or'],
                    newValue = this.value ^ 1;
                this.innerHTML = opToggleLabel[newValue];
                this.value = newValue;
                //console.info(this.value);
            }, false);

            document.getElementById('cmOpNot').addEventListener('click', function (e) {
                e.stopPropagation();
                if (document.getElementById('cmOpNot').classList.contains('active')) {
                    document.getElementById('cmOpNot').classList.remove('active')
                } else {
                    document.getElementById('cmOpNot').classList.add('active')
                }
            }, false);

            //menu selections
            document.getElementById('cm_SC').addEventListener('click', function (e) {
                copyTo(e, getAutoAddToRSelCase('cm_SC'), segInfo.ids);
            }, false);
            document.getElementById('cm_S').addEventListener('click', function (e) {
                copyTo(e, getAutoAddToRSelCase('cm_S'), segInfo.names);
            }, false);

            document.getElementById('cm_SCgo').addEventListener('click', function (e) {
                menuResetEvent_RSel = true;
                copyTo(e, getAutoAddToRSelCase('cm_SC'), segInfo.ids);
                document.getElementById('btnRSSelect').click();
            }, false);
            document.getElementById('cm_Sgo').addEventListener('click', function (e) {
                menuResetEvent_RSel = true;
                copyTo(e, getAutoAddToRSelCase('cm_S'), segInfo.names);
                document.getElementById('btnRSSelect').click();
            }, false);

        }
    } catch (err) {
    	cmlog([1,1], err);
        //console.error(err);
    };
};

//===============================================================================================

var SL_checkCountry = function () {
	cmlog([1,1], 'SL_checkCountry()');
    // returns a boolean value for menuResetEvent_SL and implies whether to run
    // reduceSpeedhelperOverhead() again

    //for (i=1; i<17; i++) {kph=Math.round(i*5*SL_imperial.mph2kph); mph=Math.round(kph*SL_imperial.kph2mph); console.info('kph:',kph,'---','mph:',mph)}
    if (!contextMenuSettings.countries) contextMenuSettings.countries = [];

    var savedNumCountries = contextMenuSettings.countries.length,
        currentNumCountries, currentCountries, convertUnits = false, isImperialCountry = false;

    try {
        currentCountries = [Waze.model.countries.top.abbr];
        currentNumCountries = 1;
    } catch (err) {
        console.warning('WMECM:', 'Could not find W.model.countries.top. Trying to use W.model.countries.objects instead.');
        try {
            currentCountries = Object.keys(Waze.model.countries.objects);
            currentNumCountries = currentCountries.length;
        } catch (err) {
            console.warning('WMECM:', 'WME objects might have been changed by Waze. This could be a problem and should be examined.')
            currentCountries = false;
            currentNumCountries = 0;
        }
    }

    var matchCount = 0;
    for (var c = 0; c < currentNumCountries; c++) {
        for (var s = 0; s < savedNumCountries; s++) {
            if (currentCountries[c] === contextMenuSettings.countries[s]) matchCount++;
        }
    }

    for (var ic = 0, icLength = SL_imperial.countries.length; ic < icLength; ic++) {
        if (currentCountries[0] === SL_imperial.countries[ic]) {
            isImperialCountry = true;
            break;
        }
    }

    if (isImperialCountry  && !Waze.prefs.attributes.isImperial) convertUnits = 1; //convert metric --> imperial
    else if (!isImperialCountry  && Waze.prefs.attributes.isImperial) convertUnits = 2; //convert imperial --> metric

    if (matchCount !== currentNumCountries) {
        contextMenuSettings.countries = currentCountries;
        localStorage.contextMenuSettings = JSON.stringify(contextMenuSettings);
        return {
            menuResetEvent_SL: true, //saved for later for implementing a check without having to refresh browser
            convertUnits: convertUnits
        };
    } else {
        return {
            menuResetEvent_SL: false, //saved for later for implementing a check without having to refresh browser
            convertUnits: convertUnits
        };
    }
};

var highlightSpeedSigns = function () {
	cmlog([1,1], 'highlightSpeedSigns()');
    //$('#signsholder_cm>div').removeAttr('style'); //reset styles
    $('#signsholder_cm>div:not(#btnCMclearSLs)').removeAttr('class'); //reset classes

    if (SL_imperial.convertUnits === null) {
        var countryResults = SL_checkCountry();
        SL_imperial.convertUnits = countryResults.convertUnits;
        SL = SL_imperial //debug
    }

    // Highlight the current speed limit
    var fwdSL = document.querySelector('input[name="fwdMaxSpeed"]'), fwdSpeedVal,
        revSL = document.querySelector('input[name="revMaxSpeed"]'), revSpeedVal,
        numSegments = Waze.selectionManager.selectionCountByType.segment,
        fwdSLMenu = document.querySelector('input[name="fwdMaxSpeed_cm'),
		revSLMenu = document.querySelector('input[name="revMaxSpeed_cm'),
		unverFwdChkBox = document.getElementById('fwdMaxSpeedUnverifiedCheckbox'),
		unverRevChkBox = document.getElementById('revMaxSpeedUnverifiedCheckbox'),
		signFwd, signRev;

	if (fwdSL !== null) fwdSpeedVal = fwdSL.valueAsNumber;
	if (revSL !== null) revSpeedVal = revSL.valueAsNumber;

    if ((fwdSpeedVal+revSpeedVal) === 0) {
    	return;
    } else if ((revSLMenu && revSLMenu.parentNode.style.display === 'none') || revSpeedVal===0) {
		revSpeedVal = false;
	} else if ((fwdSLMenu && fwdSLMenu.parentNode.style.display === 'none') || fwdSpeedVal===0) {
		fwdSpeedVal = false;
	}

    if (SL_imperial.convertUnits === 1) {
        if (fwdSpeedVal) fwdSpeedVal = Math.round(fwdSpeedVal * SL_imperial.kph2mph);
        if (revSpeedVal) revSpeedVal = Math.round(revSpeedVal * SL_imperial.kph2mph);
    } else if (SL_imperial.convertUnits === 2) {
        if (fwdSpeedVal) fwdSpeedVal = Math.round(fwdSpeedVal * SL_imperial.mph2kph);
        if (revSpeedVal) revSpeedVal = Math.round(revSpeedVal * SL_imperial.mph2kph);
	}

	signFwd = document.querySelector('#signsholder_cm>div[id="sign' + fwdSpeedVal + '"]');
	signRev = document.querySelector('#signsholder_cm>div[id="sign' + revSpeedVal + '"]');

    if ((fwdSpeedVal && revSpeedVal) && (fwdSpeedVal === revSpeedVal)) {
        if ((unverFwdChkBox === null && unverRevChkBox === null) || // if both have been verified
            (unverFwdChkBox && unverRevChkBox && unverFwdChkBox.checked === true && unverRevChkBox.checked === true)) {
            if (signFwd) signFwd.className ='cm-sl-verified cm-both';
        } else if ((unverFwdChkBox === null || unverFwdChkBox.checked === true) ||
        	(unverRevChkBox === null || unverRevChkBox.checked === true)) { // only one has been verified
            if (signFwd) signFwd.className ='cm-sl-unverified cm-one';
        } else if (unverFwdChkBox.checked === false && unverRevChkBox.checked === false) { // neither have been verified
            if (signFwd) signFwd.className ='cm-sl-unverified cm-both';
        }
    } else if ((fwdSpeedVal && revSpeedVal) && (fwdSpeedVal !== revSpeedVal)) {
        if ((unverFwdChkBox === null && unverRevChkBox === null) || // if both have been verified -- no checkboxes
            (unverFwdChkBox && unverRevChkBox && unverFwdChkBox.checked === true && unverRevChkBox.checked === true)) { // or both checked
            if (signFwd) signFwd.className ='cm-sl-verified cm-a';
            signRev.className ='cm-sl-verified cm-b';
        } else if (unverFwdChkBox === null || unverFwdChkBox.checked === true) { //fwdSpeedVal checked or no checkbox
            if (signFwd) signFwd.className ='cm-sl-verified cm-a';
            if (signRev)signRev.className ='cm-sl-unverified cm-b';
        } else if (unverRevChkBox === null || unverRevChkBox.checked === true) { //revSpeedVal checked or no checkbox
            if (signFwd) signFwd.className ='cm-sl-unverified cm-a';
            if (signRev)signRev.className ='cm-sl-verified cm-b';
        } else if (unverFwdChkBox.checked === false && unverRevChkBox.checked === false) { //both unchecked
            if (signFwd) signFwd.className ='cm-sl-unverified cm-a';
            if (signRev)signRev.className ='cm-sl-unverified cm-b';
        }
    } else if (fwdSpeedVal && !revSpeedVal) { // no reverse speed has been inputted
        if (document.querySelector('input[name="revMaxSpeed"]') === null) { // if revSpeedVal input does not exist bc this is a oneway road
            if (unverFwdChkBox === null || unverFwdChkBox.checked === true) { // if fwdSpeedVal has been verified
                if (signFwd) signFwd.className ='cm-sl-verified';
            } else if (unverFwdChkBox.checked === false) { //unverified speed on oneway road
                if (signFwd) signFwd.className ='cm-sl-unverified';
            }
        } else { //revSpeedVal input box does exist but is empty
            if (unverFwdChkBox === null || unverFwdChkBox.checked === true) { // if fwdSpeedVal has been verified, then draw green box
                if (signFwd) signFwd.className ='cm-sl-verified cm-a';
            } else if (unverFwdChkBox.checked === false) { //unverified speed on oneway road
                if (signFwd) signFwd.className ='cm-sl-unverified cm-a';
            }
        }
    } else if (revSpeedVal && !fwdSpeedVal) {
        if (document.querySelector('input[name="fwdMaxSpeed"]') === null) { // if fwdSpeedVal input does not exist bc this is a oneway road
            if (unverRevChkBox === null || unverRevChkBox.checked === true) { //revSpeedVal has been verified
                if (signRev)signRev.className ='cm-sl-verified';
            } else if (unverRevChkBox.checked === false) { //unverified speed on oneway road
                if (signRev)signRev.className ='cm-sl-unverified';
            }
        } else { //fwdSpeedVal does exist, but has not been inputted
            if (unverRevChkBox === null || unverRevChkBox.checked === true) { //revSpeedVal has been verified
                signRev.className ='cm-sl-verified cm-b';
            } else if (unverRevChkBox.checked === false) { //unverified speed on oneway road
                if (signRev) signRev.className ='cm-sl-unverified cm-b';
            }
        }
    }

    if (numSegments > 1) $('#signsholder_cm>div.cm-sl-verified').addClass('cm-sl-multisegs');
};

//------------------------------------------------------------------------------
var reduceSpeedhelperOverhead = function () {
	cmlog([1,1], 'reduceSpeedhelperOverhead()');
    // returns the innerHTML for slimmed down cmMenuContent, to be assigned to speedhelperHTML
    if (document.getElementById('cmSpeedhelperCSS')) document.getElementById('cmSpeedhelperCSS').remove();

    var speedhelperCSSEl = document.createElement('style');
    speedhelperCSSEl.type = 'text/css';
    speedhelperCSSEl.id = 'cmSpeedhelperCSS';
    speedhelperCSSEl.innerHTML = '#signsholder_cm>div[id^="sign"], #btnCMclearSLs {margin-bottom: 1px; ' + document.getElementById('signsholder').children[0].getAttribute('style') + '}\n' +
        '#signsholder_cm>div[id^="sign"]>* {' + document.querySelector('#signsholder>div[id^="sign"]>*').getAttribute('style') + '}\n';

    document.body.appendChild(speedhelperCSSEl);

    $('#signsholder_cm>div[id^="sign"]:not([id="signsError"]').removeAttr('style');
    $('#signsholder_cm>div[id^="sign"]>div').removeAttr('style');

    return document.getElementById('cmMenuContent').innerHTML;
};
//===============================================================================
var addABSpeedHelper = function(speedVal) {
	var fwdSLMenu = document.querySelector('input[name="fwdMaxSpeed_cm'),
		fwdSL = document.querySelector('input[name="fwdMaxSpeed"]'),
		fwdChkBox = document.getElementById('fwdMaxSpeedUnverifiedCheckbox'),
		prevFwdSpeedVal = fwdSLMenu.value,
		numSegsSelected = Waze.selectionManager.selectionCountByType.segment,
		pauseTime = (numSegsSelected > 10) ? (50+numSegsSelected) : 0;

    cmlog([5,3],speedVal);
    if (fwdSL.disabled === false) {
    	document.getElementById('cmWaitCover').style.display = 'block';
		requestAnimationFrame(function(){
			var fwdChkBoxMenu = document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm');
	        if (fwdChkBoxMenu !== null) fwdChkBoxMenu.checked = true;
			fwdSLMenu.value = speedVal;
		});

    	setTimeout(function(){
	    	if (fwdChkBox !== null && speedVal === prevFwdSpeedVal) {
				fwdChkBox.checked = true;
				fwdChkBox.dispatchEvent(changeEvent);
			} else if (fwdSL !== null) {
	    		fwdSL.value = speedVal;
	    		fwdSL.dispatchEvent(changeEvent);
	    		//$(fwdSL).val(speedVal).change();
	    		if (fwdChkBox !== null) setTimeout(function(){fwdChkBox.checked = true;},20);
			}

            if (!document.getElementById('cmPinMenu').value) setTimeout(closeContextMenu, 150);
            else setTimeout(highlightSpeedSigns,50);

			document.getElementById('cmWaitCover').style.display = 'none';
		},pauseTime);
    }
};
var addBASpeedHelper = function(speedVal) {
	var revSLMenu = document.querySelector('input[name="revMaxSpeed_cm'),
		revSL = document.querySelector('input[name="revMaxSpeed"]'),
		revChkBox = document.getElementById('revMaxSpeedUnverifiedCheckbox'),
		prevRevSpeedVal = revSLMenu.value,
		numSegsSelected = Waze.selectionManager.selectionCountByType.segment,
		pauseTime = (numSegsSelected > 10) ? (50+numSegsSelected) : 0;

	cmlog([5,3],speedVal);
    if (revSL.disabled === false) {
    	document.getElementById('cmWaitCover').style.display = 'block';
		requestAnimationFrame(function(){
			var revChkBoxMenu = document.getElementById('revMaxSpeedUnverifiedCheckbox_cm');
			if (revChkBoxMenu !== null) revChkBoxMenu.checked = true;
			revSLMenu.value = speedVal;
		});

    	setTimeout(function(){
	    	if (revChkBox !== null && speedVal === prevRevSpeedVal) {
	    		revChkBox.checked = true;
	    		revChkBox.dispatchEvent(changeEvent);
	    	} else if (revSL !== null) {
	    		revSL.value = speedVal;
	    		revSL.dispatchEvent(changeEvent);
	    		//$(revSL).val(speedVal).change();
	    		if (revChkBox !== null) setTimeout(function(){revChkBox.checked = true;},20);
	    	}

            if (!document.getElementById('cmPinMenu').value) setTimeout(closeContextMenu, 150);
            else setTimeout(highlightSpeedSigns,50);

			document.getElementById('cmWaitCover').style.display = 'none';
		},pauseTime);
    }
};
var addBothSpeedHelper = function(speedVal) {
	var fwdSLMenu = document.querySelector('input[name="fwdMaxSpeed_cm'),
		revSLMenu = document.querySelector('input[name="revMaxSpeed_cm'),
		fwdSL = document.querySelector('input[name="fwdMaxSpeed"]'),
		revSL = document.querySelector('input[name="revMaxSpeed"]'),
		fwdChkBox = document.getElementById('fwdMaxSpeedUnverifiedCheckbox'),
		revChkBox = document.getElementById('revMaxSpeedUnverifiedCheckbox'),
		prevFwdSpeedVal, prevRevSpeedVal,
		numSegsSelected = Waze.selectionManager.selectionCountByType.segment,
		pauseTime = (numSegsSelected > 10) ? 60 : 0;

	cmlog([5,3],speedVal);
	if ((revSLMenu && revSLMenu.parentNode.style.display === 'none') || revSLMenu === null) {
		addABSpeedHelper(speedVal);
	} else if ((fwdSLMenu && fwdSLMenu.parentNode.style.display === 'none') || fwdSLMenu === null) {
		addBASpeedHelper(speedVal);
	} else if (fwdSL.disabled === false && revSL.disabled === false) {
    	document.getElementById('cmWaitCover').style.display = 'block';
		prevFwdSpeedVal = fwdSLMenu.value;
		prevRevSpeedVal = revSLMenu.value;

		requestAnimationFrame(function(){
			var fwdChkBoxMenu = document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm'),
				revChkBoxMenu = document.getElementById('revMaxSpeedUnverifiedCheckbox_cm');
	        if (fwdChkBoxMenu !== null) fwdChkBoxMenu.checked = true;
			if (revChkBoxMenu !== null) revChkBoxMenu.checked = true;
	    	fwdSLMenu.value = speedVal; //$('#cmSpeedLimit input[type="number"]').val(speedVal);
	    	revSLMenu.value = speedVal;
		});

    	setTimeout(function(){
	    	if (fwdChkBox !== null && speedVal === prevFwdSpeedVal) {
				fwdChkBox.checked = true;
				fwdChkBox.dispatchEvent(changeEvent);
				//$('#fwdMaxSpeedUnverifiedCheckbox').prop('checked', true).change();
			} else if (fwdSL !== null) {
	    		fwdSL.value = speedVal;
	    		fwdSL.dispatchEvent(changeEvent);
				//$(fwdSL).val(speedVal).change();
			}
			//setTimeout(function(){
		    	if (revChkBox !== null && speedVal === prevRevSpeedVal) {
		    		revChkBox.checked = true;
		    		revChkBox.dispatchEvent(changeEvent);
	    			//$('#revMaxSpeedUnverifiedCheckbox').prop('checked', true).change();
		    	} else if (revSL !== null) {
	    			//revSL.value = speedVal;
	    			//revSL.dispatchEvent(changeEvent);
		    		$('input[name="revMaxSpeed"]').val(speedVal).change();
		    		if (fwdChkBox !== null) setTimeout(function(){fwdChkBox.checked = true;},20);
		    		if (revChkBox !== null) setTimeout(function(){revChkBox.checked = true;},40);
		    	}
			//}, 50);

            if (!document.getElementById('cmPinMenu').value) setTimeout(closeContextMenu, 150);
        	else setTimeout(highlightSpeedSigns,50);

			/*setTimeout(function(){
				try { //do it again just in case... sometimes necessary...
	        		$('input[name="revMaxSpeed"]').val(speedVal).change();
				} catch(err) {}
			}, 150);*/
			document.getElementById('cmWaitCover').style.display = 'none';
		}, pauseTime);
    }
};

var checkUnits = function(speedVal) {
    if (SL_imperial.convertUnits === 1) {
        return Math.round(speedVal * SL_imperial.mph2kph);
    } else if (SL_imperial.convertUnits === 2) {
        return Math.round(speedVal * SL_imperial.kph2mph);
    } else {
        return speedVal*1;
    }
};

var populateSpeedMenu = function (contextMenuSettings, nodeLabel) {
	cmlog([1,1], 'populateSpeedMenu(contextMenuSettings, ' + nodeLabel + ')');
    document.getElementById('cmMenuNoContent').style.display = 'none';
    document.getElementById('cmMenuContent').style.display = 'block';

    var slCurrentElementStatus = [!!document.getElementById('fwdMaxSpeedUnverifiedCheckbox'),
        !!document.getElementById('revMaxSpeedUnverifiedCheckbox'),
        !!document.getElementsByName('fwdMaxSpeed').length,
        !!document.getElementsByName('revMaxSpeed').length],
        newMenu = false,
        slMenuElementFlags = [!!document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm')+1,
        !!document.getElementById('revMaxSpeedUnverifiedCheckbox_cm')+1,
        document.getElementsByName('fwdMaxSpeed_cm').length+1,
        document.getElementsByName('revMaxSpeed_cm').length+1];

    if (slSavedMenuElementFlags === false) slSavedMenuElementFlags = slMenuElementFlags;


    for (var cc = 4, slNumSavedElementsMatched = 0, slNumElementsMatched = 0, slCurrentElementTotal=0; cc--;) {
        slNumElementsMatched += (((slMenuElementFlags[cc]*slCurrentElementStatus[cc]-1)>0)===slCurrentElementStatus[cc]);
        slNumSavedElementsMatched += (((slSavedMenuElementFlags[cc]*slCurrentElementStatus[cc]-1)>0)===slCurrentElementStatus[cc]);
        slCurrentElementTotal += slCurrentElementStatus[cc];
    }
    cmlog([4,3], 'slSavedMaxElementTotal', slSavedMaxElementTotal, '| slCurrentElementTotal', slCurrentElementTotal);
    cmlog([4,3], 'slNumSavedElementsMatched', slNumSavedElementsMatched, '| slNumElementsMatched', slNumElementsMatched);
    cmlog([4,3], 'slSavedMenuElementFlags', slSavedMenuElementFlags, '| slMenuElementFlags', slMenuElementFlags);

    if ( !speedhelperHTML || slNumElementsMatched !== 4 ||
    	(menuResetEvent_SL === true && slNumElementsMatched !== 4) ||
    	(slCurrentElementTotal === 4 && slSavedMaxElementTotal !== 4) ) {

        resetContextMenu(contextMenuSettings);
        document.getElementById('cmMenuHeaderTitle').innerHTML = 'Edit Speed Limits';

        if (speedhelperHTML && slNumSavedElementsMatched === 4) {
        	cmlog([4,1],'Replace - Copy Saved and Rebuild');
        	document.getElementById('cmMenuContent').innerHTML = speedhelperHTML;
        	menuResetEvent_SL = false;
        	newMenu = false;

        } else { //TODO: I think slNumElementsMatched !== 4 is redundant with slNumSavedElementsMatched
        	/*(document.getElementsByName('fwdMaxSpeed').length && !document.getElementsByName('fwdMaxSpeed_cm').length) ||
 			(document.getElementsByName('revMaxSpeed').length && !document.getElementsByName('revMaxSpeed_cm').length)) {*/
 			menuResetEvent_SL = true;
        	newMenu = true;
            try {
                /*var speedLimit = document.querySelector('#segment-edit-general div.speed-limit').cloneNode(true);
                if (speedLimit) {
	    	        cmlog([4,2],'Replace - Clone Source and Rebuild');
	                speedLimit.id = 'cmSpeedLimit';
	                speedLimit.className = 'cm-speed-limit cm-menu-section';
	                speedLimit.innerHTML = '<div id="signsContainer" style="min-height: ' + ((speedhelperHeight) ? (speedhelperHeight + 'px; ') : 'auto; ') + 'margin-bottom: 15px; opacity: 0.9;"></div><div class="form-inline">' + speedLimit.innerHTML + '</div>';
	                document.getElementById('cmMenuContent').innerHTML = '';
	                document.getElementById('cmMenuContent').appendChild(speedLimit);

	                document.querySelector('#cmSpeedLimit #signsholder').id = 'signsholder_cm';
	    	        document.getElementById('signsContainer').appendChild(document.getElementById('signsholder_cm')); // Move the signs to its own container
	    	    } else {*/
	    	    	cmlog([4,1],'Replace - Overwrite with Source and Rebuild');
	    	    	document.getElementById('cmMenuContent').innerHTML = document.querySelector('#segment-edit-general div.speed-limit').outerHTML;
	    	    	speedLimit = document.getElementById('cmMenuContent').children[0];
	                speedLimit.innerHTML = '<div id="signsContainer" style="min-height: ' + ((speedhelperHeight) ? (speedhelperHeight + 'px; ') : 'auto; ') + 'margin-bottom: 15px; opacity: 0.9;"></div><div class="form-inline">' + speedLimit.innerHTML + '</div>';
	                speedLimit.id = 'cmSpeedLimit';
	                speedLimit.className = 'cm-speed-limit cm-menu-section';

	                document.querySelector('#cmSpeedLimit #signsholder').id = 'signsholder_cm';
	    	        document.getElementById('signsContainer').appendChild(document.getElementById('signsholder_cm')); // Move the signs to its own container
	    	    //}
            } catch (err) {
            	cmlog([4,2],'Replace - Caught. No SpeedHelper or SpeedHelper is not ready.', err);
            }
        }

        //--------------------------------------------------------------------------
        try {
            var wazeVerifyChkBoxLabel = document.querySelectorAll('#segment-edit-general div.speed-limit input[type="checkbox"]+label'),
                wazeChkbox, wazeChkboxSelector;
            // Add event listeners for verified SL checkbox in menu
            for (var cb = 0; cb < wazeVerifyChkBoxLabel.length; cb++) {
                wazeChkbox = wazeVerifyChkBoxLabel[cb].parentNode.children[0];
                cmChkboxSelector = '#cmSpeedLimit ' + '#' + wazeChkbox.id;

                if (menuResetEvent_SL) document.querySelector(cmChkboxSelector).id = wazeChkbox.id + '_cm';

                document.querySelector(cmChkboxSelector + '_cm').addEventListener('click', function(e) {
                    e.stopPropagation();
                    this.parentNode.children[0].checked = !document.getElementById(this.parentNode.children[0].id.slice(0, -3)).checked;
                }, false);
            }
        } catch (err) {cmlog([4,2], 'wazeVerifyChkBoxLabel', err);}

        //--------------------------------------------------------------------------
        try {
            var wazeSpeedInput = document.querySelectorAll('#segment-edit-general div.speed-limit input[type="number"]'),
                wazeSpeedInputLength = wazeSpeedInput.length,
                cmSpeedInput, cmSpeedInputSelector_orig, cmSpeedInputSelector, nm;

            for (nm = 0; nm < wazeSpeedInputLength; nm++) {
            	cmSpeedInputSelector_orig = '#cmSpeedLimit input[name="' + wazeSpeedInput[nm].name + '"]';
            	cmSpeedInputSelector = '#cmSpeedLimit input[name="' + wazeSpeedInput[nm].name + '_cm' + '"]';

                if (menuResetEvent_SL) {
                	document.querySelector(cmSpeedInputSelector_orig).setAttribute('name', wazeSpeedInput[nm].name + '_cm');
                }

                cmSpeedInput = document.querySelector(cmSpeedInputSelector);
                cmSpeedInput.addEventListener('click', function (e) {
                    e.stopPropagation();
                    this.select();
					document.getElementById('cmContextMenu').removeEventListener('mouseenter', addHotkeyListener, false);
                    window.removeEventListener('keydown', menuShortcutKeys, true);
					cmlog([2,2],'Editing speed value, so removing global hotkey listener');
                }, false);

                cmSpeedInput.addEventListener('blur', function (e) {
					document.getElementById('cmContextMenu').addEventListener('mouseenter', addHotkeyListener, false);
                    window.addEventListener('keydown', menuShortcutKeys, true);
                    cmlog([2,2],'Removed focus from speed input field, so adding global hotkey listener again');
                    $('input[name="' + this.name.slice(0, -3) + '"]').val(this.value).change();
                }, false);

                cmSpeedInput.addEventListener('change', function (e) {
                    //e.stopPropagation();
                    $('input[name="' + this.name.slice(0, -3) + '"]').val(this.value).change();
                }, false);
            }
        } catch (err) {cmlog([4,2], 'cmSpeedInput', err);}
		//--------------------------------------------------------------------------
        //--------------------------------------------------------------------------
        try { // Try adding some event listeners for closing the menu if not pinned:
            if (!document.getElementById('cmPinMenu').value) {
                document.getElementById('cmSpeedLimit').addEventListener(
                    'mouseleave',
                    function () {
                        cmlog([2,2], 'Adding event listners to allow menu to close');
                        addSpecialMenuListeners();
                        //$('input[name="fwdMaxSpeed_cm"]').val($('input[name="fwdMaxSpeed"]').val());
                        //$('input[name="revMaxSpeed_cm"]').val($('input[name="revMaxSpeed"]').val());
                    }, false);

                document.getElementById('cmSpeedLimit').addEventListener(
                    'mouseenter',
                    function () { //prevent menu from closing
                    	cmlog([2,2],'Preventing menu from closing by removing event listeners');
                        window.removeEventListener('click', closeContextMenu, false);
                        document.getElementById('toolbar').removeEventListener('mouseenter', closeContextMenu, false);
                    }, false);
            }
        } catch (err) { cmlog([4,2], 'addSpecialMenuListeners', err); }

        //--------------------------------------------------------------------------
        // Add event listener for the checkbox
        if (document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm') !== null) {
            document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm').parentNode.addEventListener('click', function(e) {
                this.children[0].checked = !this.children[0].checked;
            }, false);
        }
        if (document.getElementById('revMaxSpeedUnverifiedCheckbox_cm') !== null) {
            document.getElementById('revMaxSpeedUnverifiedCheckbox_cm').parentNode.addEventListener('click', function(e) {
                this.children[0].checked = !this.children[0].checked;
            }, false);
        }

        var fwdSpeedEl = document.querySelector('input[name="fwdMaxSpeed_cm"]');
        if (fwdSpeedEl) {
        	if (fwdSpeedEl.parentNode.querySelector('.fa-ban') === null) {
	            var clearSignFwd = document.createElement('span');
	            clearSignFwd.className = 'fa fa-ban';
            	fwdSpeedEl.parentNode.insertBefore(clearSignFwd, fwdSpeedEl.parentNode.children[0]);
            	clearSignFwd.addEventListener('click', function() {
                    requestAnimationFrame(function(){
		                addABSpeedHelper(0);
		            });
	            }, false);
            }
        }

       	var revSpeedEl = document.querySelector('input[name="revMaxSpeed_cm"]');
        if (revSpeedEl) {
        	if (revSpeedEl.parentNode.querySelector('.fa-ban') === null) {
	            var clearSignRev = document.createElement('span');
	            clearSignRev.className = 'fa fa-ban';
            	revSpeedEl.parentNode.insertBefore(clearSignRev, revSpeedEl.parentNode.children[0]);
            	clearSignRev.addEventListener('click', function() {
                    requestAnimationFrame(function(){
		                addBASpeedHelper(0);
		            });
	            }, false);
			}
        }
        //--------------------------------------------------------------------------
        var addListenersToSigns = function () {
	        // Add clear SL button:
	        if (document.getElementById('btnCMclearSLs') === null) {
				document.getElementById('signsholder_cm').innerHTML = '<div id="btnCMclearSLs" class="fa fa-ban"><div id="spd_0">0</div></div>' +
					document.getElementById('signsholder_cm').innerHTML;
	        }

            var cmSpeedSigns = document.getElementById('signsholder_cm').children;
            for (var ss = cmSpeedSigns.length; ss--;) {
                //--------------------------------------------------------------
                cmSpeedSigns[ss].addEventListener('click', function (e) {
                    e.preventDefault();
                    if (!this.classList.length || !this.classList.contains('cm-sl-verified')) {
                        var speedVal = this.firstElementChild.innerHTML;
                        speedVal = checkUnits(speedVal);
                    } else {
                        speedVal = 0;
                    }

                    if (e.shiftKey) { //AB - fwd
	                    requestAnimationFrame(function(){
	                        addABSpeedHelper(speedVal);
	                    });
                    } else if (e.ctrlKey || e.altKey || e.metaKey ) { //BA - rev
                        requestAnimationFrame(function(){
                        	addBASpeedHelper(speedVal);
                        });
                    } else {
                        requestAnimationFrame(function(){
                        	addBothSpeedHelper(speedVal);
                        });
                    }
                }, false);
                //--------------------------------------------------------------
                cmSpeedSigns[ss].addEventListener('mousedown', function (ev) {
                    //console.info(this);
                    this.draggable = true;
                    if (document.getElementById('cmPinMenu').value) {
                        document.getElementById('cmContextMenu').removeEventListener('dragstart', allowMenuDrag, false);
                        document.getElementById('cmContextMenu').draggable = false;
                        window.addEventListener('mouseup', resetDrag, false);
                    }
                }, false);

                cmSpeedSigns[ss].addEventListener('dragstart', function (ev) {
                    //console.info(this);
                    this.style.opacity = 0.3;
                    this.children[0].opacity = 0.7;
                    ev.dataTransfer.setData('text', this.children[0].innerHTML);
                    ev.dataTransfer.setDragImage(this, ev.offsetX, ev.offsetY);
                }, false);

                cmSpeedSigns[ss].addEventListener('dragend', function (ev) {
                    this.style.opacity = '';
                    this.children[0].opacity = '';
                    this.draggable = false;
                    resetDrag();
                    window.removeEventListener('mouseup', resetDrag, false);
                }, false);
            }

            // Add event listener for the checkbox
            if (document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm') !== null) {
                document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm').parentNode.addEventListener('click', function(e) {
                    this.children[0].checked = !this.children[0].checked;
                }, false);
            }
            if (document.getElementById('revMaxSpeedUnverifiedCheckbox_cm') !== null) {
                document.getElementById('revMaxSpeedUnverifiedCheckbox_cm').parentNode.addEventListener('click', function(e) {
                    this.children[0].checked = !this.children[0].checked;
                }, false);
            }

            var fwdSpeedEl = document.querySelector('input[name="fwdMaxSpeed_cm"]');
            if (fwdSpeedEl) {
                // Allow drop event
                fwdSpeedEl.addEventListener('dragover', function(ev) {
                    if (ev.preventDefault()) ev.preventDefault();
                    this.style.border = '1px solid cyan';
                    return false;
                }, false);
                fwdSpeedEl.addEventListener('dragleave', function(ev) {
                    this.style.border = '';
                }, false);
                // Drop event
                fwdSpeedEl.addEventListener('drop', function(ev) {
                    if (ev.preventDefault()) ev.preventDefault();
                    var speedVal = ev.dataTransfer.getData('text');
                    requestAnimationFrame(function(){
	                    addABSpeedHelper(speedVal);
	                });
                    this.style.border = '';
                }, false);
            }

           	var revSpeedEl = document.querySelector('input[name="revMaxSpeed_cm"]');
            if (revSpeedEl) {
                // Allow drop event
                revSpeedEl.addEventListener('dragover', function(ev) {
                    if (ev.preventDefault()) ev.preventDefault();
                    this.style.border = '1px solid cyan';
                    return false;
                }, false);
                revSpeedEl.addEventListener('dragleave', function(ev) {
                    this.style.border = '';
                }, false);
                // Drop event
                revSpeedEl.addEventListener('drop', function(ev) {
                    if (ev.preventDefault()) ev.preventDefault();
                    var speedVal = ev.dataTransfer.getData('text');
                    requestAnimationFrame(function(){
                    	addBASpeedHelper(speedVal);
                    });
                    this.style.border = '';
                }, false);
            }
            //-----------------------------------------------

		    slCurrentElementStatus = [!!document.getElementById('fwdMaxSpeedUnverifiedCheckbox'),
		        !!document.getElementById('revMaxSpeedUnverifiedCheckbox'),
		        !!document.getElementsByName('fwdMaxSpeed').length,
		        !!document.getElementsByName('revMaxSpeed').length],
		    slMenuElementFlags = [!!document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm')+1,
		        !!document.getElementById('revMaxSpeedUnverifiedCheckbox_cm')+1,
		        document.getElementsByName('fwdMaxSpeed_cm').length+1,
		        document.getElementsByName('revMaxSpeed_cm').length+1];

		    for (var cc = 4, slNumElementsMatched = 0, slCurrentElementTotal=0; cc--;) {
		        slCurrentElementTotal += slCurrentElementStatus[cc];
		    }

            if (slCurrentElementTotal >= slSavedMaxElementTotal) {
                speedhelperHTML = reduceSpeedhelperOverhead();
                slSavedMaxElementTotal = slCurrentElementTotal;
                slSavedMenuElementFlags = slMenuElementFlags;
                cmlog([4,4],'*** speedhelperHTML replaced ***')
            } else if (newMenu) {
            	reduceSpeedhelperOverhead();
            }
            highlightSpeedSigns(); // this is necessarily at the end to play catchup bc of the slower initialization times of Speedhelper elements
        };

        //--------------------------------------------------------------------------
        var waitCount = 0,
            maxWait = 30;

        var waitForSpeedhelper = function () {
            var signsHolder = document.querySelector('#segment-edit-general div.speed-limit #signsholder');
            if (signsHolder !== null && !document.getElementById('signsContainer').children.length) {
                menuResetEvent_SL = false;
                document.getElementById('signsContainer').innerHTML = signsHolder.outerHTML;
                document.querySelector('#signsContainer>#signsholder').id = 'signsholder_cm';
                speedhelperHeight = $('#signsholder_cm').height();

                addListenersToSigns();
            } else if (waitCount++ < maxWait) {
                setTimeout(waitForSpeedhelper, 30*waitCount);
            /*} else if ((slCurrentElementTotal > slSavedMaxElementTotal) && !speedhelperHTML) { //for speedhelperHTML...
                slSavedMaxElementTotal = slCurrentElementTotal;*/
            } else {
            	speedhelperHeight = false;
            }
            return;
        };
        //------------------------------------------------------
        setTimeout(function () {
            if (document.getElementById('signsContainer') && document.getElementById('signsContainer').children.length) {
                addListenersToSigns();
            } else if (speedhelperHeight !== false) {
                setTimeout(waitForSpeedhelper, 30);
            }
        }, 30); //timeout may be needed for countries with many speedlimit signs loaded by Speedhelper
        //------------------------------------------------------

    } // menuResetEvent_SL === true

    var $fwdSL = $('input[name="fwdMaxSpeed"]'), hasFwdSL = $fwdSL.length,
    	$revSL = $('input[name="revMaxSpeed"]'), hasRevSL = $revSL.length,
    	hasOnlyOneSL = !(hasFwdSL && hasRevSL),
    	fwdSLMenu = document.querySelector('input[name="fwdMaxSpeed_cm"]'),
    	revSLMenu = document.querySelector('input[name="revMaxSpeed_cm"]'),
    	fwdChkBox, revChkBox, fwdMenuChkBox, revMenuChkBox;

    //cmlog([5,3], 'hasFwdSL, hasRevSL, hasOnlyOneSL, nodeLabel');
    //cmlog([5,3], hasFwdSL, hasRevSL, hasOnlyOneSL, nodeLabel);
	if (hasFwdSL || hasRevSL) {
	    fwdMenuChkBox = document.getElementById('fwdMaxSpeedUnverifiedCheckbox_cm');
    	fwdChkBox = document.getElementById('fwdMaxSpeedUnverifiedCheckbox');
	    revMenuChkBox = document.getElementById('revMaxSpeedUnverifiedCheckbox_cm');
	    revChkBox = document.getElementById('revMaxSpeedUnverifiedCheckbox');

	    // Update values of the context menu
	    // Hide input fields that aren't relevant for selected segment(s)
	    if (fwdSLMenu !== null) {
			if (hasOnlyOneSL || nodeLabel !== 'B') {
			    if (hasFwdSL) {
					fwdSLMenu.value = $fwdSL.val();
			    	fwdSLMenu.parentNode.style.display = 'inline-block';

				    if (fwdMenuChkBox !== null) {
					    if (fwdChkBox !== null) {
					    	fwdMenuChkBox.checked = fwdChkBox.checked;
					    	fwdMenuChkBox.parentNode.style.visibility = 'visible';
					    	fwdMenuChkBox.parentNode.style.display = 'inline-block';
					    } else {
					    	fwdMenuChkBox.parentNode.style.visibility = 'hidden';
					    	fwdMenuChkBox.parentNode.style.display = 'inline-block';
					    }
					}
			    } else {
			    	fwdSLMenu.parentNode.style.display = 'none';
			    	if (fwdMenuChkBox !== null) fwdMenuChkBox.parentNode.style.display = 'none';
			    }
			} else if (fwdMenuChkBox !== null) {
				fwdSLMenu.parentNode.style.display = 'none';
		    	fwdMenuChkBox.parentNode.style.display = 'none';
		    }
		}

		if (revSLMenu !== null) {
		    if (hasOnlyOneSL || nodeLabel !== 'A') {
			    if (hasRevSL) {
			    	revSLMenu.value = $revSL.val();
			    	revSLMenu.parentNode.style.display = 'inline-block';

				    if (revMenuChkBox !== null) {
					    if (revChkBox !== null) {
					    	revMenuChkBox.checked = revChkBox.checked;
					    	revMenuChkBox.parentNode.style.visibility = 'visible';
					    	revMenuChkBox.parentNode.style.display = 'inline-block';
					    } else {
					    	revMenuChkBox.parentNode.style.visibility = 'hidden';
					    	revMenuChkBox.parentNode.style.display = 'inline-block';
					    }
				    }
			    } else {
			    	revSLMenu.parentNode.style.display = 'none';
			    	if (revMenuChkBox !== null) revMenuChkBox.parentNode.style.display = 'none';
			    }
			} else if (revMenuChkBox !== null) {
				revSLMenu.parentNode.style.display = 'none';
		    	revMenuChkBox.parentNode.style.display = 'none';
		    }
		}

	    highlightSpeedSigns();

    } else {
    	cmlog([4,2],'No SL. Hiding...');
        document.getElementById('cmMenuNoContent').style.display = 'block';
        document.getElementById('cmMenuContent').style.display = 'none';
	}
    //--------------------------------------------------------------------------

    if (menuResetEvent_SL) menuResetEvent_SL = false; //redundant, but just to make sure :)
};

//==============================================================================================
var selectedItemsIsSegment = function () {
    var sel = Waze.selectionManager.selectedItems, //returns empty array if nothing
        selLength = sel.length,
        s, segments = [];

	cmlog([1,1], 'selectedItemsIsSegment()');
    for (s = 0; s < selLength; s++) {
        if (sel[s].model.type === 'segment') segments.push(sel[s]);
    }

    return (segments.length) ? {nodeLabel: false, segments: segments} : false;
};
//----------------------------------------------------------------------------------------------
var selectionIsSegment = function (e) {
	cmlog([1,1], 'selectionIsSegment(e)')
   var sel, selLength, s, segments = [],
        numSelected, eventFeatures, evTarget, nodeLabel = false;

    // First check for segments under cursor (hover/mouseover)
    if (e && e.target) {
    	evTarget = $(e.target).get(0); //normalization by jQuery is necessary for FF compatibility
    } else {
    	evTarget = false;
    }

	//cmlog([1], evTarget._featureId, evTarget._geometryClass)
	if 	(evTarget && evTarget._featureId &&
    	(evTarget._geometryClass === "OpenLayers.Geometry.LineString" ||
         evTarget._geometryClass === "OpenLayers.Geometry.Point")) {

		e.preventDefault();

		if (evTarget._geometryClass === "OpenLayers.Geometry.Point" && evTarget._style) {
			if (evTarget._style.label === 'A') {
				nodeLabel = 'A';
			} else if (evTarget._style.label === 'B') {
				nodeLabel = 'B';
			}
		}

        sel = Waze.selectionManager.selectedItems; //returns empty array if nothing
        selLength = sel.length;

        eventFeatures = Waze.map.segmentLayer.getFeatureById(evTarget._featureId); //segment layer -- returns null if nothing
    	//cmlog([1,0], 'eventFeatures =')
    	//console.debug(eventFeatures);

        if (eventFeatures && eventFeatures.model.type === 'segment') {
            segments[0] = eventFeatures; //return result from W.map.segmentLayer.getFeatureById(._featureId) is the same as individual objects within the array returned by W.selectionManager.selectedItems
            try {
            	//cmlog([1,0],'SegID:',eventFeatures.model.attributes.id);
            	Waze.selectionManager.select([eventFeatures.model]); // [eventFeatures.model] is the same as the return result for one seg from W.model.segments.getByIds([id])
        	} catch(err) { cmlog([1,0], '<tantrum>'); console.error(err); }
        }

        //Now check for any selected segments... any duplicates of the hovered
        //segment will be dealt with in the next steps using object literals
        try {
	        for (s = 0; s < selLength; s++) {
	            if (sel[s].model.type === 'segment') segments.push(sel[s]);
	        }
	    } catch(err) {
			if (e.type === 'selectionchanged') {
		        sel = e.selected;
		        selLength = sel.length;

		        for (s = 0; s < selLength; s++) {
		            if (sel[s] && sel[s].model.type === 'segment') segments.push(sel[s]);
		        }
		    }
	    }
        //console.info(segments);

        document.getElementById('cmMenuNoContent').style.display = 'none';
        document.getElementById('cmMenuContent').style.display = 'block';

        return (segments.length) ? {nodeLabel: nodeLabel, segments: segments} : false; //no segments near cursor

    } else if (e.type === 'selectionchanged') {
        sel = e.selected;
        selLength = sel.length;

        for (s = 0; s < selLength; s++) {
            if (sel[s] && sel[s].model.type === 'segment') segments.push(sel[s]);
        }

        return (segments.length) ? {nodeLabel: false, segments: segments} : false; //no segments near cursor

    } else {
        return false;
    }
};

//----------------------------------------------------------------------------------------------

var setupSegmentContextMenu = function (e) {
	cmlog([1,0],'------------------------------------------------------------')
    cmlog([1,1], 'setupSegmentContextMenu()');
	if (document.getElementById('cmContextMenu') && document.getElementById('cmContextMenu').style.display !== 'none') {
		var selectedStuff = selectionIsSegment(e);

	    if (selectedStuff) {
	    	if (document.getElementById('cmRSel').value && document.getElementById('cmRSelAutoAdd'))
	    	    document.getElementById('cmRSelAutoAdd').style.display = 'block';

	        switch (contextMenuSettings.clipboard) {
	            case 2:
	                populateSpeedMenu(contextMenuSettings, selectedStuff.nodeLabel);
	                break;
	            case 1:
	            case 0:
	                var segInfo = getSegmentProperties(selectedStuff);
	                populateCopyMenu(segInfo, contextMenuSettings);
	                break;
	        }
	    } else if (document.getElementById('cmPinMenu').value) {
			if (document.getElementById('cmRSelAutoAdd'))
			    document.getElementById('cmRSelAutoAdd').style.display = 'none';
	        document.getElementById('cmMenuNoContent').style.display = 'block';
	        document.getElementById('cmMenuContent').style.display = 'none';
	        return false;
	    } else {
	    	cmlog([1,1],'No segment detected.')
	        return false;
	    }
	} else {
		return false;
	}
};

//=======================================================================================
//var pressedKeys = [];
var menuShortcutKeys = function (e) {
	cmlog([1,1], 'menuShortcutKeys()');
    switch (e.which) {
        case 49: //81: //q
            e.preventDefault();
            e.stopPropagation();
            document.getElementById('cmClipboard').click();
            break;
        case 50: //87: //w
            e.preventDefault();
            e.stopPropagation();
            document.getElementById('cmRSel').click();
            break;
        case 51: //69: //e
            e.preventDefault();
            e.stopPropagation();
            document.getElementById('cmSpeed').click();
            break;
        default:
            return false;
    }
};

//--------------------------------------------------------------------------------
var cursorOffsetX, cursorOffsetY;

var moveMenu = function (evt) {
    //cmlog([1],'moveMenu()');
	try {
	    evt.preventDefault();
	    evt.stopPropagation();
	    //evt.dataTransfer.effectAllowed = 'move';
	    requestAnimationFrame( function() {
		    document.getElementById('cmContextMenu').classList.add('cm-drag');
		    document.getElementById('cmContextMenu').style.top = evt.clientY - cursorOffsetY + 'px';
		    document.getElementById('cmContextMenu').style.left = evt.clientX - cursorOffsetX  + 'px';
		});
	} catch (err) { console.error(err); }
};

var placeMenu = function (endevt) {
	cmlog([1,1], 'placeMenu()');
    endevt.preventDefault();
    //endevt.dataTransfer.dropEffect = 'move';
	try {
	    //document.getElementById('cmContextMenu').style.display = 'block';
	    setTimeout(function(){document.getElementById('cmContextMenu').classList.remove('cm-drag')},50);
	    if (!isFirefox) {
	    	document.getElementById('cmContextMenu').style.display = 'block';
	    	document.getElementById('cmContextMenu').removeEventListener('drag', moveMenu, false);
	    } else {
	    	window.removeEventListener('mousemove', moveMenu, true);
			window.removeEventListener('mouseup', placeMenu, true);
	    }
	} catch (err) { console.error(err); }
};

var allowMenuDrag = function (startevt) {
	cmlog([1,1], 'allowMenuDrag()');
	//console.info(startevt);
	try {
	    if (document.getElementById('cmContextMenu').draggable) {
	    	document.getElementById('cmContextMenu').classList.add('cm-drag');
	    	if (!isFirefox) {
	    		setTimeout(function(){document.getElementById('cmContextMenu').style.display = 'none';},20);
	    	    cursorOffsetX = startevt.offsetX;
		        cursorOffsetY = startevt.offsetY;
		        document.getElementById('cmContextMenu').addEventListener('drag', moveMenu, false);
			} else {
				startevt.preventDefault();
				startevt.stopPropagation();
		        cursorOffsetX = startevt.layerX;
		        cursorOffsetY = startevt.layerY;
	            window.addEventListener('mousemove', moveMenu, true);
	            window.addEventListener('mouseup', placeMenu, true);
		    }
	    }
	} catch (err) { console.error(err); }
};

// Setup dragging for when menu is pinned to page
var dragMenuSetup = function(pinState) {
	if (pinState === undefined) pinState = document.getElementById('cmPinMenu').value;

	var contextMenu = document.getElementById('cmContextMenu'),
		mapDiv = document.getElementById('map');

    if (pinState && contextMenu.draggable === false) {
        contextMenu.draggable = true;
        contextMenu.addEventListener('dragstart', allowMenuDrag, false);

        if (!isFirefox) {
	        mapDiv.addEventListener('drop', placeMenu, false);
	        mapDiv.ondragover = function (e) {
	            e.preventDefault();
	            e.dataTransfer.effectAllowed = 'move';
	            e.dataTransfer.dropEffect = 'move';
	        };
        }
    } else if (!pinState && contextMenu.draggable === true) {
        contextMenu.draggable = false;
    	contextMenu.removeEventListener('dragstart', allowMenuDrag);
        if (!isFirefox) {
	        mapDiv.removeEventListener('drop', placeMenu);
	    }

    }
};

//for preventing conflicts with SL sign drags
var resetDrag = function() {
    window.removeEventListener('mouseup', resetDrag, false);
    dragMenuSetup();
};

//=======================================================================================
var showPopupPanel = function(updateVersion, updateText, forumURL) {
	var popPanelWidth = 600,
	 	popPanelCSS = document.createElement('style'),
	 	popPanelHTML = document.createElement('div');

	popPanelCSS.type = 'text/css';
	popPanelCSS.id = 'cssCMupdate';
 	popPanelCSS.innerHTML =
		'.cm-panel { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);' +
			' width: ' + popPanelWidth + 'px; padding: 10px 25px; margin: 0; overflow-y: auto; overflow-x: auto; word-wrap: break-word;' +
			' background-color: white; box-shadow: 0px 5px 20px #555555; border: 1px solid #858585; border-radius: 10px; }\n' +
		'.cm-panel .fa-exclamation-circle { margin: -5px 16px 10px 8px; line-height: .9; font-size: 56px;}\n' +
		'.cm-panel-inner { padding: 0px 5px; }\n' +
		'.cm-panel-section {display: block; font-size: 14px; margin-bottom: 10px; text-align: left; padding: 0px; }\n' +
		'.cm-panel h2  { margin-top: 15px; margin-left: 80px; font-size: 32px; font-weight: bold; text-align: left; color: #C0C0C0 }\n' +
		'.cm-panel-hr  { display: block; border: 0; height: 0; border-top: 1px solid rgba(0, 0, 0, 0.1);' +
			' border-bottom: 1px solid rgba(255, 255, 255, 0.3); margin-top: 8px; margin-bottom: 15px; }\n' +
		'.cm-panel .cm-btn-container { position: relative; display: table; margin: 0px auto 8px; vertical-align: middle; padding: 0}\n' +
		'.cm-panel .btn { margin: 0px 5px; padding: 0px 15px; display: inline-block; height: 32px; }\n';

	document.body.appendChild(popPanelCSS);

	popPanelHTML.id = 'divCMupdate';
	popPanelHTML.style.backgroundColor = 'rgba(0,0,0,0.5)';
	popPanelHTML.style.position = 'fixed';
	popPanelHTML.style.top = 0;
	popPanelHTML.style.right = 0;
	popPanelHTML.style.bottom = 0;
	popPanelHTML.style.left = 0;
	popPanelHTML.style.zIndex = 5001;
	popPanelHTML.innerHTML = '<div class="cm-panel">' +
        '<div margin-bottom: 20px; margin-top: 20px;>' +
        '<i class="fa fa-exclamation-circle fa-pull-left"></i>' +
    	'<h2>WMECM Update Notes</h2>' +
        '<hr class="cm-panel-hr"></div>' +
        '<div class="cm-panel-inner"><div class="cm-panel-section">' +
        updateText +
        '</div>' +
        '<div style="margin-top: 10px; font-size: 10.1pt">' +
        'For more details on the new features or to report a bug, please visit the forum post: <a href="' + forumURL + '" target="_blank"><i class="fa fa-external-link"></i></a>' +
        '</div>' +
        '<div style="margin-top: 10px; font-style: italic; font-size: 10px;"> ' +
        'WME Context Menu update for min. version ' + updateVersion +
        '</div></div>' +
        '<hr class="cm-panel-hr">' +
        '<div class="cm-btn-container">' +
        '<button id="btnCMokay" class="btn btn-default">OK</button>' +
        '</div></div>';

    document.body.appendChild(popPanelHTML);

    document.getElementById('btnCMokay').onclick = function() {
        document.getElementById('divCMupdate').remove();
    	document.getElementById('cmUpdateNote').classList.remove('cm-unread');
    	document.getElementById('cssCMupdate').remove();
    	requestAnimationFrame(function(){CMenuVersion.updateVersionString(minVersion)});
    };
};

//=======================================================================================
var switchPanelTo = function(panelName) {
    switch (panelName) {
        case 'clipboard':
            document.getElementById('cmClipboard').value = true;
            document.getElementById('cmClipboard').classList.remove('toggle-off');
            document.getElementById('cmRSel').value = false;
            document.getElementById('cmRSel').classList.add('toggle-off');
            document.getElementById('cmSpeed').value = false;
            document.getElementById('cmSpeed').parentNode.style.opacity = 0.4;
            document.getElementById('cmContextMenu').style.width = '210px';
            break;
        case 'rsel':
            document.getElementById('cmClipboard').value = false;
            document.getElementById('cmClipboard').classList.add('toggle-off');
            document.getElementById('cmRSel').value = true;
            document.getElementById('cmRSel').classList.remove('toggle-off');
            document.getElementById('cmSpeed').value = false;
            document.getElementById('cmSpeed').parentNode.style.opacity = 0.4;
            document.getElementById('cmContextMenu').style.width = '210px';
            break;
        case 'speed':
            document.getElementById('cmClipboard').value = false;
            document.getElementById('cmClipboard').classList.add('toggle-off');
            document.getElementById('cmRSel').value = false;
            document.getElementById('cmRSel').classList.add('toggle-off');
            document.getElementById('cmSpeed').value = true;
            document.getElementById('cmSpeed').parentNode.style.opacity = 0.84;
            document.getElementById('cmContextMenu').style.width = '220px';
            speedhelperHeight = null;
            break;
    }
};

var showEmptyPanel = function(panelName) {
    switch (panelName) {
        case 'clipboard':
            document.getElementById('cmMenuHeaderTitle').innerHTML = 'Copy To Clipboard';
            document.getElementById('cmMenuNoContent').style.display = 'block';
            document.getElementById('cmMenuContent').style.display = 'none';
            break;
        case 'rsel':
            if (document.getElementById('cmRSelAutoAdd')) document.getElementById('cmRSelAutoAdd').style.display = 'none';
            document.getElementById('cmMenuHeaderTitle').innerHTML = 'Send To Road Selector';
            document.getElementById('cmMenuNoContent').style.display = 'block';
            document.getElementById('cmMenuContent').style.display = 'none';
            break;
        case 'speed':
            document.getElementById('cmMenuHeaderTitle').innerHTML = 'Edit Speed Limits';
            document.getElementById('cmMenuNoContent').style.display = 'block';
            document.getElementById('cmMenuContent').style.display = 'none';
            break;
    }
};

//=======================================================================================
var initContextMenu = function () {
	cmlog([1,1], 'initContextMenu()');
    var contextMenuActiveArea = document.createElement('div'),
        contextMenu = document.createElement('div'),
        contextMenuCSS = document.createElement('style'),
        menuCSS;

    try {//opacity: 0.5;
        contextMenuCSS.type = 'text/css';
        menuCSS = `
        #cmContextMenu { display: block; opacity: 1; }
        #cmContextMenu.cm-drag { cursor: move; opacity: 0.75 !important; transition: opacity .1s linear 0s; }
        .cm-top { border-top-right-radius: 3px; border-top-left-radius: 3px; }
        .cm-bottom { border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; }
        #cmFooter+#cmContainer .cm-menu-header>dl, #cmFooter+#cmContainer .cm-menu-header>dl>dt, #cmFooter+#cmContainer .cm-rsel>dl, #cmFooter+#cmContainer .cm-rsel>dl>dt, .cm-rsel+.cm-menu-header>dl, .cm-rsel+.cm-menu-header>dl>dt { border-radius: 0; }
        #cmFooter+#cmContainer #cmMenuContent > div:last-child { border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; }
        .cm-footer-icns { font-size: 13px; margin: 0px 4px 0px 2px; cursor: pointer; display: inline-table; line-height: 1; }
        .cm-footer-icns:active, .cm-footer-icns:focus, .cm-footer-icns.toggle-off:active, .cm-footer-icns.toggle-off:focus { color: #64D8EA; }
        .cm-footer-icns.toggle-off, .cm-footer-text.toggle-off { color: #8CBBCC; }
        .cm-update-note { z-index: 3; cursor: pointer; color: #59899E; margin-top: -15px; position: relative; float: right; right: 5px; bottom: 2px; opacity: .8; line-height: 1; font-size: 14px; }
        .cm-update-note.cm-unread { color: crimson; }
        div.cm-menu-header { padding: 0px; border: 0; height: 20px; }
        .cm-menu-header dl { background-color: rgba(147, 196, 211, 0.92); padding: 0; border-top-right-radius: 4px; border-top-left-radius: 4px; }
        .cm-menu-header dt { padding: 4px 11px; text-transform: uppercase; line-height: 1.3; background-color: rgba(111, 167, 185, 0.7); color: #D8E9EF; font-size: 10px; border-top-right-radius: 3px; border-top-left-radius: 3px; height: 22px; margin-top: 1px; box-shadow: 0px -1px 0px #9ACCDC; }
        div.cm-menu-section { z-index: 2; border-bottom: 1px solid #416B7C; padding: 2px 0px 3px; background-color: rgba(147, 196, 211, 0.92); }
        .cm-menu-section dl { margin: 0; padding: 0; display: block; }
        .cm-menu-section dt { font-size: 9px; padding: 0px 6px 0px 20px; margin-top: 2px; text-transform: uppercase; line-height: 1.2; }
        .cm-menu-section dd { display: inherit; padding: 0px 14px 0px 20px; font-size: 11px; color: #234350; font-weight: 600; line-height: 1.3; word-break: break-all;}
        .cm-menu-section dd:hover, .cm-menu-section dd:active, .cm-menu-section dd:focus { cursor: default; background-color: #BEDCE5; color: #416B7C; }
        .cm-menu-section dd:active { color: #64D8EA; }`;
        menuCSS += `
        .cm-menu-section .cm-hide.fa-caret-down, .cm-menu-section .cm-hide.fa-caret-up { position: absolute; left: 8px; width: 90%; cursor: pointer; }
        .cm-menu-section .cm-hide.fa-caret-down { display: block; }
        .cm-menu-section .cm-hide.fa-caret-up { display: none; }
        .cm-menu-section.cm-hidden .cm-hide.fa-caret-down { display: none; }
        .cm-menu-section.cm-hidden .cm-hide.fa-caret-up { display: block; }
        .cm-hidden { height: 19px; }
        .cm-hidden dd { display: none; }
        .cm-hidden dt, .cm-hidden dl {float: left; margin-right: -12px; color: rgba(255, 255, 255, 0.7); }
        .cm-hidden dl:nth-of-type(2)>dt:before { content: "/ "; }
        dd.cm-paste { font-style: italic; text-align: right; max-width: 50%; float: right; padding: 0px 10px 0px 5px;}
        .cm-paste:before { content: ""; font-style: normal; font-weight: 400; color: black; font-size: 11px; margin-right: 2px; }`;
        menuCSS += `
        dd.cm-rsel-goselect { padding-right: 12px; padding-left: 12px; cursor: pointer; }
        .cm-menu-section .cm-rsel-goselect:hover { background-color: transparent; color: #d4e7ed; }
        .cm-rsel { display: none; border: 0; background-color: rgba(154, 204, 220, 0.9); border-top-right-radius: 3px; border-top-left-radius: 3px; margin-bottom: -1px; }
        .cm-rsel>dl { padding-top: 1px; border-top-right-radius: 4px; border-top-left-radius: 4px; }
        .cm-rsel>dl>dt { background-color: rgba(111, 167, 185, 0.7); height: 20px; margin-top: -2px; border-top-right-radius: 3px; border-top-left-radius: 3px; padding: 4px 10px; color: #d8e9ef; font-size: 10px; }
        .cm-rsel>dl>dt+dd { height: 24px; margin: 0px 0px 3px; padding: 5px 10px 5px 9px; background-color: rgba(228, 248, 255, 0.3); box-shadow: 0px 1px 0px rgba(0,0,0,0.1); }
        .cm-rsel-options, cm-rsel-options:focus:hover, cm-rsel-options+input:not(checked) { z-index: 2; background-color: rgba(89, 137, 158, 0.60); color: #D4E7ED; font-weight: bold !important; height: 14px !important; line-height: 1; padding: 3px 6px; font-size: 8px !important; border-radius: 15px; }
        .cm-rsel-options:hover { background-color: rgba(89, 137, 158, 0.7); color: white; }
        .cm-rsel-options.active:hover { background-color: #274B5A }
        .cm-rsel-options.active, .cm-rsel-options:active, .cm-rsel-options:active:focus { color: white; background-color: #3F6271; }\
        .cm-rsel-options:focus { color: white; }
        .cm-rsel-options.and { background-color: #EEE; color: #59899E; border-right: 1px solid #AAA;}
        .cm-rsel-options.cm-badge-right { border-left: 1px solid #59899E; }`;
        menuCSS += `
        div#cmSpeedLimit { z-index: 3; width: 100%; font-size: 11px; text-align: center; display: inline-block; padding: 10px; }
        div#cmSpeedLimit input[type="number"] { height: 28px; width: 44px; font-size: 12px; padding: 4px 5px; line-height: 1; margin: 0px 2px; }
        div#cmSpeedLimit input[type="checkbox"]+label { color: transparent; width: 10px; margin-left: 5px; vertical-align: middle; }
        div#cmSpeedLimit .controls-container { margin-left: 5px; }
        div#cmSpeedLimit div { border-radius: 3px; display: inline-block; }
		#signsholder_cm>div#btnCMclearSLs { float: left; display: inline-block; cursor: pointer; background-image: none; color: #134C65; font-size: 28px; }
		#signsholder_cm>div#btnCMclearSLs>div {display: none;}
		#signsholder_cm>div#btnCMclearSLs:before { vertical-align: middle; }
		.cm-speed-limit>.form-inline .fa-ban { cursor: pointer; color: #1F576E; padding-right: 8px; font-size: 1.5em; font-weight: bold; vertical-align: middle; margin-bottom: 2px; }
		#btnCMclearSLs:hover, .cm-speed-limit>.form-inline .fa-ban:hover { color: #00ECE3; }
        div#signsholder_cm div:not(#btnCMclearSLs):before { opacity: .9; position: absolute; background-color: white; color: black; border: 3px solid #00ECE3; font-weight: 400; border-radius: 50%; font-size: 10px; width: 20px; height: 20px; line-height: 14px; text-align: center; margin-left: -10px; margin-top: -3px; box-shadow: 0px 1px 1px gray; }
        div#signsholder_cm .cm-a:before { content: "A"; }
        div#signsholder_cm .cm-b:before { content: "B"; }
        .cm-sl-verified { box-shadow: inset 0px 0px 0px 2px lime; }
        .cm-sl-unverified { box-shadow: inset 0px 0px 0px 2px gold; }
        .cm-sl-multisegs { box-shadow: inset 0px 0px 0px 3px #333 !important; }
        .cm-sl-verified.cm-both { box-shadow: 0px 0px 0px 1px lime, inset 0px 0px 0px 2px greenyellow; }
        .cm-sl-unverified.cm-one { box-shadow: 0px 0px 0px 1px lime, inset 0px 0px 0px 2px rgba(255, 235, 59, 1);  }
        .cm-sl-unverified.cm-both { box-shadow: 0px 0px 0px 1px orange, inset 0px 0px 0px 2px rgba(255, 235, 59, 1); }
        #signsholder_cm>#signsError { float: initial; width: 100%; height: initial; padding: 5px 5px 5px 45px !important; text-align: left; }`;
        contextMenuCSS.innerHTML = menuCSS;
        document.head.appendChild(contextMenuCSS);

        // reminder to self: no need to declare CSS here since this is put into DOM once and does not get destroyed
        contextMenu.id = 'cmContextMenu';
        contextMenu.style.position = 'fixed';
        contextMenu.style.top = '0px';
        contextMenu.style.left = '0px';
        contextMenu.style.width = '210px';
        contextMenu.style.margin = '0px';
        contextMenu.style.padding = '0px';
        contextMenu.style.borderRadius = '4px';
        contextMenu.style.boxShadow = '0px 5px 12px rgba(0, 0, 0, 0.5)';
        contextMenu.style.border = '1px solid rgb(89, 137, 150)';
        contextMenu.style.borderBottom = '1px solid #2D505F';
        contextMenu.style.color = 'white';
        contextMenu.style.zIndex = '5000';
        contextMenu.style.display = 'none';
        contextMenu.style.opacity = 0;
        contextMenu.innerHTML = '<div id="cmContainer" style="z-index: 2; color: white; padding:0; margin:0; position: relative; width: 100%; display: block;"></div>' +
        	'<div id="cmUpdateNote" class="fa fa-exclamation-circle cm-update-note"></div>' +
            '<div id="cmFooter" class="cm-bottom" style="color: #DDEDF3; height: 24px; background-color: rgba(75, 125, 148, 0.85); ' +
            'z-index: 3; padding: 1px 7px 1px; margin: 0; position: relative; width: 100%; display: block;"></div>';


        document.getElementById('map').appendChild(contextMenu);

        document.getElementById('cmFooter').innerHTML = `
        <div style="position: relative; float: left; vertical-align: middle;">
        <i id="cmPinMenu" class="fa fa-thumb-tack cm-footer-icns toggle-off" style="margin: 0px -3px 0px -3px; padding: 3px 6px;" value=true title="Pin menu" data-toggle="tooltips"></i>
        <span id="cmPinClose" class="cm-footer-text toggle-off" style="cursor: pointer; font-weight: 600; border-left: 1px solid #D4E7ED; padding-left: 8px; text-transform: uppercase; font-size: 10px; letter-spacing: .7px;">Close</span>
        </div>
        <div id="cmFooterCaret" class="fa fa-angle-up cm-footer-icns pull-right" style="position: relative; top: 3px; margin: auto 0px auto 7px; font-weight: bold;"></div>
        <div style="position: relative; height: 21px; border-radius: 20px; border: 1px solid #6EA1B7;  padding: 0px 1px 0px 5px;" class="btn-group pull-right">
          <div style="position: relative; display: inline-block; height: 100%; border-right: 1px solid #6EA1B7; padding-left: 2px; width: 22px;" class="">
            <i id="cmClipboard" value="false" style="font-size: 13px;" class="fa fa-clipboard cm-footer-icns toggle-off" title="Copy to Clipboard" data-toggle="tooltips"></i>
          </div>
          <div style="position: relative; display: inline-block; height: 100%; border-right: 1px solid #6EA1B7;">
            <i id="cmRSel" style="font-size: 14px;" value="false" class="fa fa-road cm-footer-icns toggle-off" title="Copy to WME Road Selector" data-toggle="tooltips"></i>
          </div>
          <div style="position: relative; display: inline-block; width: 20px;  margin: 0px 0px 0px -2px; opacity: 0.84;">
            <span id="cmSpeed" style="height: 16px" value="false" title="Edit Speed Limits" class="fa fa-stack">
              <i class="fa fa-circle cm-footer-icns" style="font-size: 15px; width: 15px; color: #EEE; line-height: 14px; position: absolute; left: 0; text-align: center;"></i>
              <i class="fa fa-circle-o cm-footer-icns" style="font-size: 15px; width: 15px; font-weight: 500; color: crimson; line-height: 14px; position: absolute; left: 0; text-align: center;"></i>
              <i class="fa cm-footer-icns" style="font-size: 8px; font-style: normal; width: 15px; color: black; line-height: 14px; position: absolute; left: 0; text-align: center;">S</i>
            </span>
          </div>
        </div>`;

        resetContextMenu(contextMenuSettings);
        hidePasteMenu();

        setTimeout(function () {
            if (contextMenuSettings.clipboard === 0 || (contextMenuSettings.clipboard === 1 && document.getElementById('tabRSel') === null))
                switchPanelTo('clipboard');
        }, 2000);

        if (contextMenuSettings.clipboard === 1) switchPanelTo('rsel');
        else if (contextMenuSettings.clipboard === 2) switchPanelTo('speed');

        if (contextMenuSettings.pin) {
            document.getElementById('cmPinMenu').value = true;
            document.getElementById('cmPinMenu').classList.remove('toggle-off');
            document.getElementById('cmPinClose').classList.remove('toggle-off');
        } else {
            document.getElementById('cmPinMenu').value = false;
            document.getElementById('cmPinMenu').classList.add('toggle-off');
            document.getElementById('cmPinClose').classList.add('toggle-off');
        }

        // VERSION CHECK
		if (!CMenuVersion.isUpToDate(minVersion)) {
			document.getElementById('cmUpdateNote').classList.add('cm-unread');
		}

		var forumURL = 'https://www.waze.com/forum/viewtopic.php?f=819&t=178371';
			updateNotes_0_2_2 = 'Hello there! Thanks for trying out WME Context Menu. Given the big push by Waze to add speed limits (SLs) to the map, this most recent update provides a few enhancements that I hope you\'ll find helpful in getting the job done:' +
			'</div><div class="cm-panel-section"><ul>' +
			'<li>Right-clicking directly on an unselected segment will now open the menu.</li>' +
			'<li>The menu can be dragged anywhere on the map when in pinned mode.</li>' +
			'<li>Only when pinned, numeric hotkeys (1-3) for switching panels of the context menu have been restricted to work only when your cursor is within the menu area. This is to prevent undesired capturing of keypresses.' +
			'<li>Verified Speedhelper signs are now highlighted with a green border. Unverified SLs are highlighted yellow.</li>' +
			'<li>Two-way roads with differing SL values for each direction are indicated by a start-node A or B badge on top of its sign.</li>' +
			'<li>You can now selectively assign SLs to either A-B or B-A direction: Use <b>shift+click for A-B</b> or <b>Alt+click for B-A</b>.</li>' +
			'<li>Speedhelper signs will automatically adjust the units to those used by the country</li>' +
			'</ul>' +
			'As a reminder, these enhanced features currenly apply to only the Speedhelper panel within WME Context Menu.';

	    document.getElementById('cmUpdateNote').onclick = function(e){
	    	e.stopPropagation();
	    	showPopupPanel(minVersion, updateNotes_0_2_2, forumURL);
	    };


        document.getElementById('cmClipboard').onclick = function (e) {
            try {
                e.stopPropagation();
                switchPanelTo('clipboard');
                contextMenuSettings.clipboard = 0;
                localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);

                var selectedStuff = selectedItemsIsSegment();
                if (selectedStuff) {
                    var segInfo = getSegmentProperties(selectedStuff);
                    populateCopyMenu(segInfo, contextMenuSettings);
                } else {
                    showEmptyPanel('clipboard');
                }
                hidePasteMenu(false);
            } catch (err) {
                console.error(err);
            };
        };

        document.getElementById('cmRSel').onclick = function (e) {
            try {
                e.stopPropagation();
                if (document.getElementById('tabRSel') !== null) {
                    switchPanelTo('rsel');
                    contextMenuSettings.clipboard = 1;
                    localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);

                    var selectedStuff = selectedItemsIsSegment();
                    if (selectedStuff) {
                        var segInfo = getSegmentProperties(selectedStuff);
                        if (document.getElementById('cmRSelAutoAdd')) document.getElementById('cmRSelAutoAdd').style.display = 'block';
                        populateCopyMenu(segInfo, contextMenuSettings);
                    } else {
                        showEmptyPanel('rsel');
                    }
                    hidePasteMenu(true);
                }
            } catch (err) {
                console.error(err);
            }
        };

        document.getElementById('cmSpeed').onclick = function (e) {
            try {
                e.stopPropagation();
                switchPanelTo('speed');
                contextMenuSettings.clipboard = 2;
                localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);

                var selectedStuff = selectedItemsIsSegment();
                if (selectedStuff) {
                    menuResetEvent_SL = true;
                    populateSpeedMenu(contextMenuSettings);
                    window.removeEventListener('click', closeContextMenu, false);
                    document.getElementById('toolbar').removeEventListener('mouseenter', closeContextMenu, false);
                } else {
                    showEmptyPanel('speed');
                }
            } catch (err) {
                console.error(err);
            }
        };

        document.getElementById('map').addEventListener(
            'contextmenu',
            function (e) {
				cmlog([1,0],'------------------------------------------------------------')
            	cmlog([1,1], 'contextmenu');
            	//console.info(e);
                var selectedStuff = selectionIsSegment(e),
                	contextMenu = document.getElementById('cmContextMenu');

                if (selectedStuff) {
                    try {
                        e.stopPropagation();
                        contextMenu.style.display = 'block';
                        contextMenu.style.top = e.clientY - 10 + 'px';
                        contextMenu.style.left = e.clientX + 'px';
                        contextMenu.classList.remove('cm-drag');

                        menuResetEvent_SL = true;
                        setupSegmentContextMenu(e);
                        contextMenu.style.opacity = 1;

                        window.addEventListener('keydown', menuShortcutKeys, true);
                        //console.info('WMECM:','Added initial global hotkey listener upon menu open');
                        if (document.getElementById('cmPinMenu').value) {
		                    // use a more selective hotkey listener
		                    //console.info('WMECM:','Menu is pinned, so adding selective hotkey listeners too');
							contextMenu.addEventListener('mouseenter', addHotkeyListener, false);
							contextMenu.addEventListener('mouseleave', removeHotkeyListener, false);

					        Waze.selectionManager.events.register("selectionchanged", null, setupSegmentContextMenu);
	                    }
                    } catch (err) { console.error(err); }
                } else {
                	// No segment detected... Decide whether to keep the menu open.
                    if (document.getElementById('cmPinMenu').value) {
                        return true;
                    } else {
                        contextMenu.style.display = 'none';
                        return false;
                    }
                }
            }, true);

        document.getElementById('cmPinMenu').onclick = function (e) {
            try {
                e.stopPropagation();
                if (this.value) { // no pinning
                    this.value = false;
                    this.classList.add('toggle-off');
                    document.getElementById('cmPinClose').classList.add('toggle-off');

                    // remove drag menu listeners
                    dragMenuSetup(false);

                    // closing context menu will remove hotkey listeners & closing contextmenu listeners
                    // they will be reinstated if appropriate upon reopening the menu
                    closeContextMenu();
                } else { // pin menu
                    this.value = true;
                    this.classList.remove('toggle-off');
                    document.getElementById('cmPinClose').classList.remove('toggle-off');

                    // remove listeners that close the menu without clicking close
                    window.removeEventListener('click', closeContextMenu, false);
                    document.getElementById('toolbar').removeEventListener('mouseenter', closeContextMenu, false);

                    // Add dragging menu listeners
                    dragMenuSetup(true);

                    // use a more selective hotkey listener
                    //console.info('WMECM:','Switch to menu pinning, so adding selective hotkey listener');
					document.getElementById('cmContextMenu').addEventListener('mouseenter', addHotkeyListener, false);
					document.getElementById('cmContextMenu').addEventListener('mouseleave', removeHotkeyListener, false);
                }
                contextMenuSettings.pin = !contextMenuSettings.pin;
                localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);
            } catch (err) {
                console.error(err);
            }
        };

        document.getElementById('cmPinClose').onclick = closeContextMenu;

        document.getElementById('cmFooterCaret').addEventListener('click', function (e) {
            e.stopPropagation();
            if (this.classList.contains('fa-angle-up')) { //switch to top menubar
                contextMenuSettings.position = 1;
                localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);
                adjustContextMenubar(1);
            } else if (this.classList.contains('fa-angle-down')) { //switch to bottom menubar
                contextMenuSettings.position = 0;
                localStorage.WME_ContextMenu = JSON.stringify(contextMenuSettings);
                adjustContextMenubar(0);
            }
        }, false);

    } catch (err) {
        console.error(err);
    }

    dragMenuSetup();

    var cmWaitCover = document.createElement('div');
    cmWaitCover.id = 'cmWaitCover';
	cmWaitCover.style.display = 'none';
	cmWaitCover.style.position = 'fixed';
	cmWaitCover.style.top = 0; cmWaitCover.style.bottom = '25px'; cmWaitCover.style.left = 0; cmWaitCover.style.right = 0;
	cmWaitCover.style.background = 'transparent';
	cmWaitCover.style.cursor = 'wait';
	cmWaitCover.style.zIndex = '5001';
	document.body.appendChild(cmWaitCover);

    setTimeout(function () {
        try {
            var rselBtnEls = document.querySelectorAll('#RSconditions button');
            for (var b = rselBtnEls.length; b--;) {
                rselBtnEls[b].addEventListener('click', function () {
                    this.classList.remove('btn-info')
                }, false);
            }
        } catch (err) {
            console.error(err)
        }
    }, 1200)

};

var waitCount = 0,
    maxWaitCount = 50;
var waitForWaze = function () {
    try {
        if (document.getElementById('cmContextMenu')) {
            return true;
        } else if (typeof(Waze) !== "undefined" && Waze.model && Waze.selectionManager &&
        	Waze.selectionManager.selectedItems &&
            Waze.model.segments && Waze.model.cities &&
            Waze.map && Waze.map.layers) {

            //cmlog([1],'starting...');
            setTimeout(initContextMenu, 1000);
            //console.info('WMECM:', 'Something may have went wrong... not sure.');
        } else if (waitCount++ < maxWaitCount) {
            //console.info('waiting...');
            setTimeout(waitForWaze, 1000);
        } else {
            console.error('WMECM:', 'Failed to start');
        }
    } catch (err) {
        console.error('WMECM:', 'Whoa. Major fail. Please let TheLastTaterTot know about this...');
        console.error(err);
    }
};

waitForWaze();