// global object remembering the state of the widgets on the page (except for
// the page size)
var gPageState = {};
// time before doing a search when the user types something in the input field
var browseBoxIndexWait = 1000;
// don't try to launch the double click popup if the currently selected element
// is one of these
var dblclickForbiddenClasses = ['GEN','IPA','N','HOM','PUN','LS','REF','S-PRO', 'folding_sign'];
/* the key is the id of a data panel, the value is 'true' if the data panel is in browse mode
    (ie, if the quick search panel is hidden and the browse panel shown) */
var gBrowseMode = {};
/* number of items displayed in the browse list of the dictionaries */
var gDictBrowseListSize = 25;
/* number of items displayed in the browse list of the verbs */
var gVerbBrowseListSize = 25;
/* number of items displayed in the browse list of the language resources */
var gLrBrowseListSize = 18;
/* max number of chars for an item of a standard browse list - set to 0 to disable trimming */
var gBrowseListMaxChars = 15;
/* max number of chars for an item of the language resources - set to 0 to disable trimming  */
var gLrMaxChars = 30;
/******************************************************
 ******************************************************
 ** Init functions - launched by $(document).ready * *
 ******************************************************
 ******************************************************/

/*******************************************************
 * Cookies, login box, language box
 *******************************************************/

/** Load page state (form closed/unclosed, etc) from cookie into a single 
 * object for the whole page */
function initPageState() {

    var sValues = $.cookie('page_state');
    if (sValues)
        gPageState = eval("(" + sValues+ ")");
    // for verb, etc lookup string save
    if (gPageState.lookup == null)
        gPageState.lookup = {};
    // remember what dictionary/verb conjugator/ language resource was saved
    // last
    if (gPageState.selectedDictionaries == null)
        gPageState.selectedDictionaries = {};
    // remember whether the advanced search was folded
    if (gPageState.advSearchFolded == null)
        gPageState.advSearchFolded = true;
    // remember the values for the advanced search
    if (gPageState.advSearchValues == null)
        gPageState.advSearchValues = {};
    // remember what data panels where closed
    if (gPageState.dataPanelsFolded == null)
        gPageState.dataPanelsFolded = {};
    // remember what view options(quick search, etc) are folded
    if (gPageState.viewOptionsFolded == null)
        gPageState.viewOptionsFolded = {};
    // remember at what index we are in the browse lists
    if (gPageState.browseListIndexes == null)
        gPageState.browseListIndexes = {};
    // remember what language resource has been selected
    if (gPageState.lrFs == null)
        gPageState.lrFs = {};
    // save cookie when leaving the page
    // save it at submit time (to retrieve the current dictionary in Java)
    // save it also at unload when clicking on a link
    $("#searchentry_form").submit(savePageState);
    $(window).unload(savePageState);
}

/**
 * Saves the page state in a cookie
 */
function savePageState() {
    var sValues = JSON.serialize(gPageState);
    $.cookie('page_state', sValues,{ path: '/' });
}

/** Initialize the actions on the login box on the public home page */
function initLoginBox() {
    // login when pressing enter on the username or password field
    $("#login_form *[@name='j_username'],#login_form *[@name='j_password']").keypress(function(e) {
       if (e.which == 13) {
          var login = $(this).parents("#login_form").find("*[@name='j_username']").attr("value");
          var password = $(this).parents("#login_form").find("*[@name='j_password']").attr("value");
          // check that login and password are not null
          if (login == null || login == "" || password == null || password == "")
            return;
          // avoids problem with login/password autocompletion in Firefox
          e.preventDefault();
          // check that login is a valid email
          if (login.match(/^[A-Z][\w\.\+\-]+[A-Z0-9]@[A-Z0-9][\w\.-]*[A-Z0-9]\.[A-Z][A-Z\.]*[A-Z]$/i))
              $(this).parents("#login_form").submit();
       }
    });
}

/** Initialize the locale switching buttons */
function initLangBox() {
    $("#locale_form").find(".langbox").click(function () {
       var locale = $(this).attr("locale");
       $(this).parents("form").find("input[@name='locale']").attr("value", locale);
       $(this).parents("form").submit();
    });
}

/*************************************************************
 * Functions specific to the result list
 *************************************************************/
/** Initialize the drop down list which lets one change the number of results per page */
function initPageSizeBox() {
    //change the number of results by page
    $("#results_by_page").change(function() {
         setResultsByPage($(this));
    });
    
    //init the list "results by page"
    $("#results_by_page option[@value='" + $.cookie('pageSize') + "']").attr("selected","selected");
}

/** Sets the search results number to display */
function setResultsByPage(poSelectObj) {
    var i;
    var oResult;
    var sParamKey, sParamValue;
    var sNewUrl      = "";
    var sLocation    = window.location.toString();
    var sSearch      = window.location.search;
    var aParams      = (sSearch.length > 1 ? sSearch.substr(1).split("&") : null);
    var oRegExp      = new RegExp("([^=]*)=([^=]*)");
    var aParamkeys   = new Array();
    var aParamValues = new Array();
    var iPageSize    = poSelectObj.val();
    var bPageSize    = false;

    $.cookie('pageSize', iPageSize,{ path: '/' });
    
    //Polulate the hash with params
    if (aParams) {
        for (i=0; i<aParams.length; i++) {
            oResult = aParams[i].match(oRegExp)
            sParamKey = RegExp.$1;
            
            switch (sParamKey) {
                case 'page':
                    sParamValue = 0;
                    break;
                case 'pageSize':
                    sParamValue = iPageSize;
                    bPageSize = true;
                    break;
                default:
                    sParamValue = RegExp.$2;
            }
            
            aParamkeys.push(sParamKey);
            aParamValues.push(sParamValue);
        }
    }
    
    //Add the param 'pageSize' to the hash (if not defined in the location)
    if (!bPageSize) {
            aParamkeys.push("pageSize");
            aParamValues.push(iPageSize);
    }
    
    //Build the new location
    for (i=0; i<aParamkeys.length; i++)
        sNewUrl += (sNewUrl.length ? "&" : "?") + aParamkeys[i] + "=" + aParamValues[i];
    sNewUrl = sLocation.replace(/\?.*$/, "") + sNewUrl;
    window.location.href = sNewUrl;
}

/*************************************************************
 * Functions specific to the entry
 *************************************************************/
/** Initializes the fold/unfold widgets on entries */
function initEntry(fold) {
    if (fold == null)
        fold = true;
     //add double click handler
    $("#entryContent")
        .dblclick(
            function(e) {
                e.preventDefault();
                var dblclickPage = $("#entryContent").attr("dblclickPage"); 
                var entryLookup = getTextSelection();
                if (entryLookup != null) 
                    window.open(dblclickPage + "&entryLookup=" + entryLookup, null, "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=no,copyhistory=no,width=200,height=200,top=300,left=300");
                }
            );
    
    if (fold) {
        // make elements with FOLD attribute foldable
        $("#entryContent")
            .find(".folding_sign")
            .toggle(
                function() {
                    $(this).parent().find(".folded_section").find(".first_sign").click();
                    $(this).parent().addClass("folded_section");
                    $(this).parent().find(".first_sign").removeClass("first_sign");
                    $(this).addClass("first_sign");
                },
                function() {
                    $(this).removeClass("first_sign");
                    $(this).parent().removeClass("folded_section");
                }
             );
    }
}

/* Popup for double click */
function initEntryPopup() {
    $(".ok_button_popup").click(function() { 
        var entryUrl =  $("#searchentrypopup_form").attr("searchEntryUrl"); 
        var code = $("#searchentrypopup_form").find("*[@name='code']:checked").attr("value");
        window.opener.location.replace(entryUrl + "&code=" + code);
        window.close();
     });
}

/* Opens the double-click popup */
function openPopup(psUrl, psTitle, psWidth, psHeight) {

    if( psWidth == null )
        psWidth = 700;
    if( psHeight == null )
        psHeight = 520;
    if( psTitle == null )
        psTitle = "POPUP";

    var lsOptions = "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width="+(psWidth)+",height="+(psHeight)+",top=50,left=150";
    var name = psTitle;
    var popWin = window.open(psUrl, name, lsOptions);
    popWin.focus();   
}

/** Action for the 'back to results' element */
function backToResults() {
	var entryLookup = getURLParam("entryLookup");
	if(entryLookup != null && entryLookup != "")
		$("#entryLookup").attr("value", entryLookup);
    $("#searchentry_form").get(0).submit();
}

function getURLParam(strParamName){
	var strReturn = "";
	var strHref = window.location.href;
	if ( strHref.indexOf("?") > -1 ){
	var strQueryString = strHref.substr(strHref.indexOf("?")).toLowerCase();
	var aQueryString = strQueryString.split("&");
	    for ( var iParam = 0; iParam < aQueryString.length; iParam++ ){
	      if (
	aQueryString[iParam].indexOf(strParamName.toLowerCase() + "=") > -1 ){
	var aParam = aQueryString[iParam].split("=");
	        strReturn = aParam[1];
	        break;
	      }
	    }
	}
	return unescape(strReturn);
} 

/*******************************************************
 * Data panel
 *******************************************************/
/** Init code for all data panels (dictionary box, verb box and language resources) */
function initDataPanels() {
    // for the lookup string input field
    // lookup search string in the associated browse list 
    $boxes = $("#right_column,#left_column").find(".box");
    $boxes.find(".form_lookup_input").
        keyup(function() {
        	var $searchIndex = $(this).parents("form").eq(0).find(".browse_box").eq(0);
        	if (!$searchIndex.is(":visible"))
        		return;
            var entryId = $(this).attr("id");
            var dataPanelId = getDataPanelId($(this));

            gPageState.lookup[dataPanelId] = $(this).val();
            setTimeout(function() {
                    browseListLookup(entryId)
                },
                browseBoxIndexWait);
        }).
        map(function() {
            var dataPanelId = getDataPanelId($(this));

            var lookup = gPageState.lookup[dataPanelId];
            if (lookup != null)
                 $(this).val(lookup);
        });
    // add fold-unfold arrows to already folded elements

    $boxes.not("[noarrow]").find(".header")
         .click(function () {
             var dataPanel = getDataPanel(this);
             var isHidden = dataPanel.hasClass("folded");
             setDataPanelState(this, !isHidden, true);
         })
         .map(function(position) {
                // did we remember the class of this box when saving the cookie
                // last time ?
                // otherwise, fold it if it's not the first element
                var panelId = getDataPanelId($(this));
                var wasFolded = gPageState.dataPanelsFolded[panelId];
                if ((wasFolded == true) ||
                    (wasFolded == null && position > 0)) {
                    $(this).triggerHandler("click");
                }
            });

    // submit form when clicking the 'ok' button in dictionary search, verb conjugator
    $boxes.find(".ok_button").click(function() { $(this).parents("form").submit(); });
    // switch to associated dictionary
    $boxes.find(".switch_button").click(function() {
        var oSelect = $(this).parents("form").find("select:first");
        var linkCode = oSelect.find("option[@selected]").attr("associated_dict");
        
        if (linkCode != "") {
            oSelect.val(linkCode);
            oSelect.triggerHandler("change");
        }
    });
    
    // add event handler to the list of dictionaries
    $boxes.find("select.form_input").map(function() {
        var savedDictCode = gPageState.selectedDictionaries[getDataPanelId($(this))];
        if (savedDictCode != null) {
            $(this).val(savedDictCode);
        }
    });
    $boxes.find("select.form_input").change(function() {
        // preserve state
        gPageState.selectedDictionaries[getDataPanelId($(this))] = getDictCode($(this));
        if ($(this).attr("no_submit") != "true")
            $(this).parents("form").submit();
    });
    initViewOptions();
    // in browse mode, when submitting the form, go to the first selected entry
    $boxes.find("form:has(.browse_box)").submit(function(e) {
        var dataPanelId = getDataPanelId($(this));
        if (gBrowseMode[dataPanelId] == true) {
            // scroll to index
            browseListLookup($(this).find(".form_lookup_input").attr("id"));
            // simulate a click on the first displayed element
            var newUrl = $(this).find(".browse_box:first div.item:first a").attr("href");
            window.location = newUrl;

            return false;
        }
    });
    
}

/** Folds/unfolds a data panel */
function setDataPanelState(widget, hidden, propagate) {
    // grab parent data panel (.box)
    var dataPanel = getDataPanel(widget);
    if (hidden) {
        dataPanel.addClass("folded").children(":not(.header)").hide();
    }
    else {
        dataPanel.removeClass("folded").children(":not(.header)").show();
    }   
    // close elements in the 'close' attribute if we are unfolded
    var elemsToClose = dataPanel.attr("close");
    gPageState.dataPanelsFolded[dataPanel.attr("id")] = hidden;
    if (propagate && elemsToClose != null) {
        var splitElems = elemsToClose.split(",");
        for (var i = 0; i < splitElems.length ; i++) {
            // find elements with this id and close them
            $("#" + splitElems[i]).map(function() {
                setDataPanelState(this, !hidden, false);
            });
       }
   }
   
}

/*-------------------------------------------------
 * View options (quick search/browse index)
 --------------------------------------------------*/
/** Adds buttons and actions to the view options */
function initViewOptions() {
    // show/hide actions for quick search/index
    $("#right_column,#left_column").find(".view_option").
        // add arrows
        prepend("<span class=\"opt_arrow\">&#x25BC;</span> ").
        click(function() {
            var isHidden = $(this).hasClass("disabled");
            setViewOptionState(this, !isHidden, true);
        }).
        map(function(position) {
            // did we remember it as folded in the cookie?
            var wasFolded = gPageState.viewOptionsFolded[$(this).attr("id")];
            if ((wasFolded == true) || 
                (wasFolded == null && position > 0)) {
                $(this).triggerHandler("click");
            }
        });
}

/** Folds/unfolds a given view option */
function setViewOptionState(widget, hidden, propagate) {
    var toggleId = $(widget).attr("toggles");
    var browseMode = null;
    var $elementToShow;
    if (toggleId) {
        $elementToShow = $("#" + toggleId);
        if ($elementToShow.hasClass("browse_box"))
                browseMode = !hidden;
    }
    
    if (hidden) {
        $(widget).addClass("disabled");
        if ($elementToShow)
            $elementToShow.hide();
    }
    else {
        $(widget).removeClass("disabled");
        if ($elementToShow) {
            $elementToShow.show();
        }
    }
    if (browseMode != null) {
        gBrowseMode[getDataPanelId($(widget))] = browseMode;
        var dataPanel = getDataPanel(widget);
        if (browseMode)
            dataPanel.find(".form_lookup_input").attr("autocomplete", "off");
        else
            dataPanel.find(".form_lookup_input").removeAttr("autocomplete");
    }
    var newContent = $(widget).hasClass("disabled") ? "&#x25B6;" : "&#x25BC;";
    $(widget).find(".opt_arrow").html(newContent);
    // remember status
    gPageState.viewOptionsFolded[$(widget).attr("id")] = hidden;
    if (propagate) {
        $(widget).siblings(".view_option").map(function() {
            setViewOptionState(this, !hidden, false);
        });
    }
}

/*---------------------------------------------------------------
 * Browse list (the alphabetical indexes)
 ----------------------------------------------------------------*/
/**
 * Loads one or more browse lists (pass the id of the browse list to reload if need be)
 */
function initBrowseList(browseListId, pageSize, renderCallback) {
    $("#" + browseListId).each(
        function() {
            var dictionary =  $("#" + $(this).attr("dictionary_list")).val();
            var index = $(this).attr("index") != null ? $(this).attr("index") : "alpha_index";
            var showPage = $(this).attr("show_page");           
            
            if (renderCallback == null)
                renderCallback = function(item) {
                    var url = showPage + "?code=" + dictionary + "&id=" + item.idmId;
                    if (item.contextId != null)
                        url += "#" + item.contextId;
                    return "<a href=\"" + url + "\">" + trimItem(item.display, gBrowseListMaxChars) + "</a>";
                };
            
            
            
            // grab the size of the data
            var ajaxResult = $.ajax({
                url: gsBrowseListSizeUrl,
                data: {
                    "code" : dictionary, 
                    "index" : index
                    },
                async: false
                }).responseText;
            var parsedResult = eval("(" + ajaxResult + ")");
            var size = parsedResult["size"];            
            // start at previous position
            var startPosition = gPageState.browseListIndexes[getDataPanelId($(this))] || 0; 
            $(this).browseList(
                // AJAX callback to retrieve data
                function(start, end) {
                    var sPage = $.ajax({
                        url: gsBrowseListPageUrl,
                        data: {
                            code : dictionary,
                            index : index,
                            startPosition : start,
                            endPosition : end
                            },
                        async: false
                        }).responseText;
                    return eval(sPage);
                    },
                    size,
                // settings
                {
                    // renders an element of the list
                    render: renderCallback,
                    startPosition: startPosition,
                    pageSize: pageSize
                }
            ).scroll(function() {
                // save index upon scrolling
                gPageState.browseListIndexes[getDataPanelId($(this))] = 
                    $(this).idx();
            });
        });
}

/**
 * Looks up a specific string in the index
 */
function browseListLookup(entryId) {
    var lastValue = $("#" + entryId).data("lastValue");
    var criterion = $("#" + entryId).val();
    $("#" + entryId).data("lastValue",criterion); 
    if(lastValue != criterion) {
        var dictionary = $("#" + entryId).parents("form").eq(0).find("select:first").val();
        var index = $("#" + entryId).parents("form").eq(0).find(".browse_box").attr("index") != null 
            ? $("#" + entryId).parents("form").eq(0).find(".browse_box").attr("index")
            : "alpha_index";
        var ajaxResult = $.ajax({
                    url: gsBrowseListIndexLookupUrl ,
                    data: {
                        "code" : dictionary,
                        "index" : index,
                        "criterion":criterion
                        },
                    async: false
                    }).responseText;
        var parsedResult = eval("(" + ajaxResult + ")");
        var index = parsedResult["index"];
        $("#" + entryId).parents("form").eq(0).find(".browse_box").
            scrollTo(index).
            map(function() {
                    // save the position
                    gPageState.browseListIndexes[getDataPanelId($(this))] = 
                        $(this).idx();
                });
    }
}

/**
 * Trims an item of a browse list like this:
 * aaaaaaa
 * aa...
 * Also adds a tooltip if it's too long
 */
function trimItem(display, maxChars) {
    display = display.replace(/^<(span [^>]+)>(.+?)<\/(span)>$/g,
        function(m, openingSpanContent, content, closingSpanContent) {
            var tooltip = "";
            var newContent = content;
            // strip the tags inside the content
            var cleanContent = content.replace(/<[^>]+>/g, "");
            if (maxChars > 0 && cleanContent.length > maxChars) { 
//                newContent = content.substring(0, maxChars - 3) + "...";
                tooltip = " title=\"" + cleanContent + "\" alt=\"" + cleanContent + "\"";
            }
            return "<" + openingSpanContent + tooltip + ">" + content + "</" + closingSpanContent + ">";
        });
    return display;
}
/*****************************************************************
 * Functions specific to the dictionary box (first box on the left)
 ******************************************************************/
function initDictionaryBox() {
    $("#searchoption-btn").toggle(function () {
            setAdvSearchFormState(true);
        },
        function() {
            setAdvSearchFormState(false);
        }).
        map(function() {
            // folded per default
            if (gPageState.advSearchFolded == true)
                $(this).triggerHandler("click");
        });
    // load advanced search drop down lists
    initAdvancedSearch();
    initBrowseList("search_index", gDictBrowseListSize);
    
}

/**
 * Creates the advanced search box
 */
function initAdvancedSearch() {
    var $advSearchBox = $("#advsearch-box");
    var currentDictionary = getDictCode($advSearchBox);
    var sFilterGroups = $.ajax({
        url: gsFilterGroupsAjaxUrl,
        data: "code=" + currentDictionary,
        async: false
    }).responseText;

    var aFilterObj;
    var oDiv, oText, oSelect;
    var aFilterGroups = eval("(" + sFilterGroups + ")");
    $advSearchBox.prepend("<div class=\"container\"/>");
    var oContainer = $advSearchBox.children(":first");
    var i, j;
    //remove previous menulists
    oContainer.empty();
    var savedValues = gPageState.advSearchValues[currentDictionary];
    var code;
    var isSelected = false;
    //create new menulists
    var html = "";
    var sLabel, sSelect;
    for (i=0; i < aFilterGroups.length; i++) {
        aFilterObj = aFilterGroups[i].filters;
        sLabel = "<div class\"advSearchLabel\">" + aFilterGroups[i].label + "</div>";
        sSelect = "<select name=\"advancedFilterCodes\">";
        // add default option
        sSelect += "<option value=\"-1\"></option>";
       
        // add the other options
        for (j=0; j<aFilterObj.length; j++) {
            code = aFilterObj[j].code;
            // was the current option the selected one?
            isSelected = (savedValues != null && savedValues[i] == code);

            sSelect += "<option value=\"" + code + "\"" + 
                (isSelected ? " selected=\"selected\"" : "") + ">" + 
                aFilterObj[j].label + "</option>";
        }
        sSelect += "</select>";
        html += sLabel + sSelect;

    }
    oContainer.append(html);
    // add an action on the select elements to
    // remember selected items
    oContainer.children("select").change(function() {
    // create array for the current dictionary if it doesn't exist
    if (gPageState.advSearchValues[currentDictionary] == null) {
       gPageState.advSearchValues[currentDictionary] = new Array(
           $(this).parent().children(
               "select[@name='advancedFilterCodes']").length);
            }
            // save value for the list
            var position = $(this).prevAll("select[@name='advancedFilterCodes']").length;
            gPageState.advSearchValues[currentDictionary][position] = $(this).val();
       });
}


/**
 * Folds/unfolds the advanced search box
 */
function setAdvSearchFormState(hidden) {
    var $advSearchBox = $("#advsearch-box");
    var sBtnLabel;
    if (hidden) {
        $advSearchBox.addClass("folded");
        sBtnLabel = gsAdvSearchBtnMoreLabel + " >>";
    }
    else {
        $advSearchBox.removeClass("folded");
        sBtnLabel = gsAdvSearchBtnLessLabel + " <<";
    }

    $("#searchoption-btn").text(sBtnLabel);
    //hidden field used to switch to the advanced search
    $("#advancedSearch").attr("value", !hidden);
    // remember if the advanced search is folded
    gPageState.advSearchFolded = hidden;
}

/*****************************************************************
 * Functions specific to the verb conjugator box (second box on the left)
 ******************************************************************/
function initVerbConjugatorBox() {
    initBrowseList("verb_index", gVerbBrowseListSize);
}
/*****************************************************************
 * Functions specific to the language resource box (first box on the right)
 ******************************************************************/
function initLanguageResourceBox() {
    // language resources browse list
    var $langIndex = $("#lang_index");
    if (!$langIndex)
        return;
    // each browse list has a dictionary_list attribute with the id of the widget holding
    // the dictionary code associated, in this case a hidden input field
    var dictCodeId = $langIndex.attr("dictionary_list");
    var dictionaryCode = $("#" + dictCodeId).val();
    var savedFsName = gPageState.lrFs[dictionaryCode];    
    var $langList = $("select#lang_list");
    if (savedFsName != null) {
        $langList.find("option[@fs='" + savedFsName + "']").attr("selected", "true");
    }
    $("select#lang_list").change(function() {
        // preserve selected language resource
        var fsName = $langList.find("option[@selected]").attr("fs");
        gPageState.lrFs[dictionaryCode] = fsName;
        initLRBrowseList();
        });
    initLRBrowseList();
    
}

function initLRBrowseList() {
    var $langList = $("select#lang_list");
    var fsName = $langList.find("option[@selected]").attr("fs");
    var index = $langList.val();
    // alphabetical index object to use - need to specify it 
    // since we have multiple alpha indexes in a single dictionary object
    // (while the other dictionaries can get away with using alpha_index)
    var $langIndex = $("#lang_index");
    $langIndex.attr("index", index);
    var showPage = $langIndex.attr("show_page");
    var dictionaryCode = $("#" + $("#lang_index").attr("dictionary_list")).val();
    initBrowseList("lang_index", gLrBrowseListSize, function(item){
        // create display form of a single item of the browse list
        var url = showPage + "?code=" + dictionaryCode + "&fs=" + fsName + "&id=" + item.idmId;
        if (item.contextId)
            url+= "#" + item.contextId;
        var code = $("#dictionary_list");
        var display = item.display;
        return "<a href=\"" + url + "\">" + trimItem(display, gLrMaxChars) + "</a>";
    });
}
/*******************************************************
 *******************************************************
 ** Global init functions - call several init functions
 ********************************************************
 ********************************************************/

/** Initializes the public pages */
function initPublicPage() {
    initLoginBox();
    initLangBox();
}

/** Initializes the private pages */
function initPrivatePage() {
    initPageState();
    initLangBox();
    initDataPanels();
    initDictionaryBox();
    initVerbConjugatorBox();
    initLanguageResourceBox();
    initPageSizeBox();
}

/*******************************************************************
 *******************************************************************
 ** Helper functions
 ******************************************************************
 ******************************************************************/
/*
 * Cross-browser function to get selected text
 */
function getTextSelection() {
    var sSelection = null;
    var range = null;
    var parentNode = null;
    
    if (window.getSelection) {
        sSelection = window.getSelection();
        range = getRangeObject(sSelection);
    } else if (document.getSelection) {
        sSelection = document.getSelection();
        range = getRangeObject(sSelection);
    } else if (document.selection) {
        // IE
        range = document.selection.createRange();
        sSelection = range.text;
    }
    if (range == "" || range == null)
        return null;
    parentNode = range.commonAncestorContainer != null ? range.commonAncestorContainer : range.parentElement();
    for (var i = 0 ; i < dblclickForbiddenClasses.length ; i++) {
        className = dblclickForbiddenClasses[i];
        if ($(parentNode).hasClass(className))
            return null;
    }        
    return sSelection;
}

function getRangeObject(selectionObject) {
    if (selectionObject.getRangeAt)
        return selectionObject.getRangeAt(0);
    else { // Safari!
        var range = document.createRange();
        range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
        range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);
        return range;
    }
}
/** Returns the data panel containing this widget */
function getDataPanel(widget) {
    // test if jquery object
    if (!$.isFunction(widget.parents))
        widget = $(widget);
    var dataPanel = widget.hasClass("box") ? widget : widget.parents(".box").eq(0);
    if (dataPanel == null)
        throw "No data panel for this widget";
    return dataPanel;
}

/** Returns the id of the data panel containing this widget */
function getDataPanelId(widget) {
    return getDataPanel(widget).attr("id");
}

/** Returns the code of the data source associated with this widget (for any
 * widget in a data panel (ie, <div class="box"></div> ) */
function getDictCode(widget) {
    return getDataPanel(widget).find(".form_input").val();
}
