/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.cdm;


import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.gui.GLIButton;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataSetManager;
import org.greenstone.gatherer.util.CheckList;
import org.greenstone.gatherer.util.CheckListEntry;
import org.greenstone.gatherer.util.JarTools;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;

/** This class manages the language commands, remembering both a list of languages to build indexes in, plus the default language.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public class LanguageManager
    extends DOMProxyListModel {

    static public Document LANGUAGES_DOCUMENT = XMLTools.parseXMLFile("xml/languages.xml", true);

    static final private Dimension COMPONENT_SIZE = new Dimension(125,25);

    /** The visual controls for this manager. */
    private Control controls = null;
    /** A reference to this class as a model, for the inner controls class. */
    private DOMProxyListModel model = null;
    /** A hashtable of code->name mappings of known languages. */
    private LinkedHashMap known_languages = null;
    /** The default language object. */
    private Language default_language = null;
    /** The language metadata element - specifies which metadata the 
	language should be read from to determine the partitons */
    private Element language_metadata = null;
   /** Constructor. */
    public LanguageManager(Element languages_element) {
	super(languages_element, StaticStrings.LANGUAGE_ELEMENT, new Language());
	
	DebugStream.println("LanguageManager: " + getSize() + " languages parsed.");

	this.model = this;
	// Retrieve the default language
	NodeList default_language_elements = CollectionConfiguration.getElementsByTagName(StaticStrings.LANGUAGE_DEFAULT_ELEMENT);
	if(default_language_elements.getLength() > 0) {
	    default_language = new Language((Element)default_language_elements.item(0));
	}

	// Retrieve the language metadata
	language_metadata = CollectionDesignManager.collect_config.getLanguageMetadata();
	// Load a series of code->language mappings into known_languages, by reading from the 'languages.xml' file, which is essentially a subset of the ISO 639 Standard.
	known_languages = new LinkedHashMap();
	NodeList language_elements = LANGUAGES_DOCUMENT.getDocumentElement().getElementsByTagName(StaticStrings.LANGUAGE_ELEMENT);
	for(int i = 0; i < language_elements.getLength(); i++) {
	    Element language_element = (Element) language_elements.item(i);
	    String code = language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE);
	    String name = language_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
	    known_languages.put(code.toLowerCase(), name);
	    name = null;
	    code = null;
	    language_element = null;
	}
    }

    /** Method to add a new language.
     * @param language The <strong>Language</strong> to add.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.CollectionManager
     */
    private void addLanguage(Language language) {
	if(!contains(language)) {
	    // need to add a pseudo metadata
	    CollectionMeta metadatum = new CollectionMeta(StaticStrings.STOP_CHARACTER + language.getCode());
	    metadatum.setValue(language.getName());
	    CollectionDesignManager.collectionmeta_manager.addMetadatum(metadatum);
	    add(getSize(), language);
	}
    }

    public void destroy() {
	if(controls != null) {
	    controls.destroy();
	    controls = null;
	}
	known_languages.clear();
	known_languages = null;
	default_language = null;
    }

    /** Method to retrieve the control for this manager.
     * @return the Control for editing the language partitions
     */
    public Control getControls() {
	if(controls == null) {
	    // Build controls
	    controls = new LanguageControl();
	}
	return controls;
    }
    
    /** Method to retrieve a certain language object by its code.
     * @param code The two letter code of a language, as a <strong>String</strong>.
     * @return The <strong>Language</strong> that matches the given code, or <i>null</i> if no such language exists.
     */
    public Language getLanguage(String code) {
	int size = getSize();
	for(int i = 0; i < size; i++) {
	    Language language = (Language) getElementAt(i);
	    if(language.getCode().equals(code)) {
		return language;
	    }
	}
	return null;
    }

    public ArrayList getLanguages() {
	return children();
    }

    /** Method to return a list of the known language codes.
     * @return an ArrayList containing the series of known language codes as per the languages.dat file
     */
    public ArrayList getLanguageCodes() {
	return new ArrayList(known_languages.keySet());
    }

    public String getLanguageName(String code) {
	return (String) known_languages.get(code);
    }
    
    /** Called when the detail mode has changed which in turn may cause several design elements to be available/hidden
     * @param mode the new mode as an int
     */
    public void modeChanged(int mode) {
	
    }

   private int moveLanguage(Language lang, boolean move_up)
    {
	// Determine the current position of the language
	int position = indexOf(lang);
	int new_position;

	// Attempt to move the language up
	if (move_up) {
	    // Check it's not already at the top
	    if (position == 0) {
		return position;
	    }

	    // This automatically removes the language first, as an Element can only exist once in a particular document
	    new_position = position - 1;
	    addBefore(lang, (Language) getElementAt(new_position));
	}

	// Attempt to move the language down
	else {
	    // Check it's not already at the bottom
	    if (position == (getSize()) - 1) {
		return position;
	    }

	    // This automatically removes the language first, as an Element can only exist once in a particular document
	    new_position = position + 1;
	    addAfter(lang, (Language) getElementAt(new_position));
	}

	return new_position;
    }

    /** Method to remove a certain language.
     * @param language The <strong>Language</strong> to remove.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.CollectionManager
     */
    private void removeLanguage(Language language) {
	remove(language);
	// Remove any collection metadata for this language
	CollectionDesignManager.collectionmeta_manager.removeMetadata(StaticStrings.STOP_CHARACTER + language.getCode());
	if(default_language != null && default_language.equals(language)) {
	    setDefault(null);
	}
    }

    private void replaceLanguage(Language old_language, Language new_language) {
	// Remove old lang collection meta
	CollectionDesignManager.collectionmeta_manager.removeMetadata(StaticStrings.STOP_CHARACTER + old_language.getCode());
	// Add new one
	CollectionMeta metadatum = new CollectionMeta(StaticStrings.STOP_CHARACTER + new_language.getCode());
	metadatum.setValue(new_language.getName());
	CollectionDesignManager.collectionmeta_manager.addMetadatum(metadatum);
	if(default_language != null && default_language.equals(old_language)) {
	    setDefault(new_language);
	}

	// get the position of the old one
	int position = indexOf(old_language);
	remove(old_language);
	add(position, new_language);
    }


    /** Method to set the default language.
     * @param language The <strong>Language</strong> to use as a default, or <i>null</i> for no default.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.collection.CollectionManager
     */
    public void setDefault(Language language) {
	if(language != null) {
	    if(default_language == null) {
		// Create the default index element, and place immediately after indexes element.
		Element default_language_element = root.getOwnerDocument().createElement(StaticStrings.LANGUAGE_DEFAULT_ELEMENT);
		default_language = new Language(default_language_element);
		Node target_node = CollectionConfiguration.findInsertionPoint(default_language_element);
		if(target_node != null) {
		    root.getOwnerDocument().getDocumentElement().insertBefore(default_language_element, target_node);
		}
		else {
		    root.getOwnerDocument().getDocumentElement().appendChild(default_language_element);
		}
	    }
	    default_language.setAssigned(true);
	    default_language.setCode(language.getCode());
	}
	else {
	    if(default_language != null) {
		default_language.setAssigned(false);
	    }
	}
    }


    /** This class represents the visual component of the Language Manager. */
    private class LanguageControl
	extends JPanel 
	implements Control {
	/** The list of available languages */
	private CheckList language_list = null;
	/** The button to add a new language support. */
	private JButton add_button = null;
	/** The button to replace a language support. */
	private JButton replace_button = null;
	/** The button to remove a supported language. */
	private JButton remove_button = null;
	/** button to move a language up in the list */
	private JButton move_down_button;
	/** button to move a language down in the list */
	private JButton move_up_button;
	/** The button to set the current language as the default one. */
	private JButton set_default_button = null;
	/** A list of currently supported languages. */
	private JList selected_languages_list = null;
	/** A list of metadata elements that may hold the language metadata */
	private JComboBox language_metadata_combo = null;
	
	/** Constructor.
	 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.AddListener
	 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.ClearDefaultListener
	 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.ListListener
	 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.RemoveListener
	 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.SelectorListener
	 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.SetDefaultListener
	 */
	public LanguageControl() {
	    super();
	    // Creation.
	    JPanel center_panel = new JPanel();

	    JLabel selected_languages_list_label = new JLabel(Dictionary.get("CDM.LanguageManager.Assigned_Languages"));
	    selected_languages_list = new JList(model);
	    selected_languages_list.setCellRenderer(new MyLanguageListCellRenderer());
	    selected_languages_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	    selected_languages_list.setVisibleRowCount(5);
	    
	    JPanel control_panel = new JPanel();
	    JLabel selector_label = new JLabel(Dictionary.get("CDM.LanguageManager.Selector"));
	    
	    language_metadata_combo = new JComboBox(MetadataSetManager.getEveryMetadataSetElement().toArray());
	    language_metadata_combo.setOpaque(false);
	    language_metadata_combo.setToolTipText(Dictionary.get("CDM.LanguageManager.LanguageMetadata_Tooltip"));
	    JLabel language_metadata_label = new JLabel(Dictionary.get("CDM.LanguageManager.LanguageMetadata"));
	    String current_value = "ex.Language";
	    if (language_metadata.getAttribute(StaticStrings.ASSIGNED_ATTRIBUTE).equals(StaticStrings.TRUE_STR) && !language_metadata.getAttribute(StaticStrings.NAME_ATTRIBUTE).equals("")) {
		current_value = language_metadata.getAttribute(StaticStrings.NAME_ATTRIBUTE);
	    }
	    ArgumentControl.selectValue(language_metadata_combo, current_value);
	    language_list = new CheckList(false);
	    language_list.setListData(getLanguageCodes());
	    language_list.setToolTipText(Dictionary.get("CDM.LanguageManager.Selector_Tooltip"));
	    language_list.setCellRenderer(new LanguageCheckListCellRenderer());
	    	    
	    JPanel movement_pane = new JPanel();
	    move_up_button = new GLIButton(Dictionary.get("CDM.Move.Move_Up"), JarTools.getImage("arrow-up.gif"), Dictionary.get("CDM.Move.Move_Up_Tooltip"));
	    move_up_button.setEnabled(false);
	    
	    move_down_button = new GLIButton(Dictionary.get("CDM.Move.Move_Down"), JarTools.getImage("arrow-down.gif"), Dictionary.get("CDM.Move.Move_Down_Tooltip"));
	    move_down_button.setEnabled(false);
	    
	    set_default_button = new GLIButton(Dictionary.get("CDM.LanguageManager.Set_Default"), Dictionary.get("CDM.LanguageManager.Set_Default_Tooltip"));
	    set_default_button.setEnabled(false);
	    
	    JPanel button_panel = new JPanel();

	    add_button = new GLIButton(Dictionary.get("CDM.SubcollectionIndexManager.Add_Subindex"), Dictionary.get("CDM.LanguageManager.Add_Tooltip"));
	    add_button.setEnabled(false);

	    replace_button = new GLIButton(Dictionary.get("CDM.SubcollectionIndexManager.Replace_Subindex"), Dictionary.get("CDM.LanguageManager.Replace_Tooltip"));
	    replace_button.setEnabled(false);
	    
	    remove_button = new GLIButton(Dictionary.get("CDM.SubcollectionIndexManager.Remove_Subindex"), Dictionary.get("CDM.LanguageManager.Remove_Tooltip"));
	    remove_button.setEnabled(false);
	    
	    // Set up and connect listeners.
	    add_button.addActionListener(new AddListener());
	    add_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
	    move_down_button.addActionListener(new MoveListener(false));
	    move_down_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
	    move_up_button.addActionListener(new MoveListener(true));
	    move_up_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
	    remove_button.addActionListener(new RemoveListener());
	    remove_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
	    replace_button.addActionListener(new ReplaceListener());
	    replace_button.addActionListener(CollectionDesignManager.buildcol_change_listener);

	    language_list.addListSelectionListener(new LanguageListListener());
	    language_metadata_combo.addActionListener(new ActionListener() {
		    public void actionPerformed(ActionEvent event) {
			String value = ((MetadataElement)language_metadata_combo.getSelectedItem()).getFullName();
			language_metadata.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, StaticStrings.TRUE_STR);
			language_metadata.setAttribute(StaticStrings.NAME_ATTRIBUTE, value);
		    }
		});
	    set_default_button.addActionListener(new SetDefaultListener());
	    set_default_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
	    selected_languages_list.addListSelectionListener(new AssignedListListener());

	    // Layout components
	    button_panel.setLayout(new GridLayout(1,3));
	    button_panel.add(add_button);
	    button_panel.add(replace_button);
	    button_panel.add(remove_button);

	    JPanel metadata_panel = new JPanel();
	    metadata_panel.setLayout(new BorderLayout());
	    JPanel inner_panel = new JPanel();
	    inner_panel.setLayout(new BorderLayout(10,10));
	    inner_panel.add(language_metadata_label, BorderLayout.WEST);
	    inner_panel.add(language_metadata_combo, BorderLayout.CENTER);
	    metadata_panel.add(new JPanel(), BorderLayout.CENTER);
	    metadata_panel.add(inner_panel, BorderLayout.EAST);

	    movement_pane.setBorder(BorderFactory.createEmptyBorder(0,2,0,0));
	    movement_pane.setLayout(new GridLayout(3,1));
	    movement_pane.add(move_up_button);
	    movement_pane.add(move_down_button);
	    movement_pane.add(set_default_button);

	    control_panel.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
	    control_panel.setLayout(new BorderLayout());
	    control_panel.add(selector_label, BorderLayout.WEST);
	    control_panel.add(new JScrollPane(language_list), BorderLayout.CENTER);
	    control_panel.add(button_panel, BorderLayout.SOUTH);

	    center_panel.setLayout(new BorderLayout());
	    center_panel.add(selected_languages_list_label, BorderLayout.NORTH);
	    center_panel.add(new JScrollPane(selected_languages_list), BorderLayout.CENTER);
	    center_panel.add(movement_pane, BorderLayout.EAST);

	    JPanel top_panel = new JPanel();
	    top_panel.setLayout(new BorderLayout());
	    top_panel.add(metadata_panel, BorderLayout.NORTH);
	    top_panel.add(center_panel, BorderLayout.SOUTH);
	    
	    setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	    setLayout(new BorderLayout());
	    add(top_panel, BorderLayout.NORTH);
	    add(control_panel, BorderLayout.CENTER);
	}
	
	/** Destructor.	*/
	public void destroy() {
	}

	public void gainFocus() {
	}

	public void loseFocus() {
	}

	
	private void clearControls() {
	    selected_languages_list.clearSelection();
	    language_list.clearTicked();
	    add_button.setEnabled(false);
	    remove_button.setEnabled(false);
	    replace_button.setEnabled(false);
	    set_default_button.setEnabled(false);
	    move_down_button.setEnabled(false);
	    move_up_button.setEnabled(false);

	}
	
	private void updateControlsWithSelectedLanguage()
	{
	    Language selected_lang = (Language) selected_languages_list.getSelectedValue();
	    if (selected_lang == null) {
		clearControls();
		return;
	    }
	    
	    // Display the selected subcollection index's sources
	    language_list.clearTicked();
	    language_list.setTickedObjects(selected_lang.getCode().split(","));
	    
	}

	private void validateButtons() {
	    boolean add_enabled = false;
	    boolean replace_enabled = false;

	    if (!language_list.isNothingTicked()) {
		// Create a dummy Langauge and see if its in the collection
		ArrayList langs = language_list.getTicked();
		StringBuffer code_str = new StringBuffer();
		boolean first = true;
		for (int i=0; i<langs.size(); i++) {
		    if (!first) {
			code_str.append(",");
		    } else {
			first = false;
		    }
		    code_str.append(langs.get(i));
		}
		String lang_str = code_str.toString();
		
		if (!model.contains(lang_str)) {
		    add_enabled = true;
		    if (!selected_languages_list.isSelectionEmpty()) {
			replace_enabled = true;
		    }
		}

	    }
	    add_button.setEnabled(add_enabled);
	    replace_button.setEnabled(replace_enabled);
	}
	
	/** Listens for actions apon the 'add' button in the LanguageManager controls, and if detected calls the add method of the manager with a newly created language. */
	private class AddListener
	    implements ActionListener {
	    /** Add a new language support.
	     * @param event an ActionEvent
	     * @see org.greenstone.gatherer.cdm.Language
	     */
	    public void actionPerformed(ActionEvent event) {
		if (!language_list.isNothingTicked()) {
		    addLanguage(new Language(language_list.getTicked()));
		    clearControls();
		}
	    }
	}

	/** Listens for actions apon the 'remove' button in the LanguageManager controls, and if detected calls the remove method of the manager with the language selected for removal. */
	private class RemoveListener
	    implements ActionListener {
	    /** Remove the currently selected language, if any.
	     * @param event An <strong>ActionEvent</strong>.
	     * @see org.greenstone.gatherer.cdm.Language
	     */
	    public void actionPerformed(ActionEvent event) {
		Language delete_me = (Language)selected_languages_list.getSelectedValue();
		if(delete_me != null) {
		    removeLanguage(delete_me);
		}
	    }
	}

	private class ReplaceListener
	    implements ActionListener {
	    
	    public void actionPerformed(ActionEvent event) {
		if (selected_languages_list.isSelectionEmpty() || language_list.isNothingTicked()) {
		    // This should never happen, but just in case...
		    replace_button.setEnabled(false);
		    return;
		}
		Language old_language = (Language) selected_languages_list.getSelectedValue();
		Language new_language = new Language(language_list.getTicked());
		replaceLanguage(old_language, new_language);
		
	    }
	}
	
	private class LanguageListListener
	    implements ListSelectionListener {

	    public void valueChanged(ListSelectionEvent event) {
		if (event.getValueIsAdjusting()) {
		    return;
		}
		validateButtons();
	    }
	}
	
	/** Listens for actions apon the 'set default' button in the LanguageManager controls, and if detected calls the <i>setDefault()</i> method of the manager with the language selected for default. */
	private class SetDefaultListener
	    implements ActionListener {
	    /** Set the default index to the one currently selected, if any.
	     * @param event An <strong>ActionEvent</strong>.
	     * @see org.greenstone.gatherer.cdm.Language
	     */
	    public void actionPerformed(ActionEvent event) {
		Language selected_language = (Language) selected_languages_list.getSelectedValue();
		if(selected_language != null) {
		    setDefault(selected_language);
		    // This should cause a repaint of just the desired row
		    selected_languages_list.setSelectedValue(selected_language, true);
		}
		set_default_button.setEnabled(false);
	    }
	}

	private class MoveListener
	    implements ActionListener
	{
	    private boolean move_up;

	    public MoveListener(boolean move_up)
	    {
		this.move_up = move_up;
	    }

	    public void actionPerformed(ActionEvent event)
	    {
		// Retrieve the selected language
		Language language = (Language) selected_languages_list.getSelectedValue();
		if (language != null) {
		    int new_position = moveLanguage(language, move_up);
		    // Ensure the language that moved is still selected
		    selected_languages_list.setSelectedIndex(new_position);
		}
	    }
	}


	/** Listens for selections within the list on the LanguageManager controls, and if a change is detected enables, or disables, controls appropriately. */
	private class AssignedListListener
	    implements ListSelectionListener {
	    /** Enable or disable controls depending on the current list selection.
	     * @param event A <strong>ListSelectionEvent</strong>.
	     */
	    public void valueChanged(ListSelectionEvent event) {
		if (event.getValueIsAdjusting()) {
		    return;
		}
		if(selected_languages_list.isSelectionEmpty()) {
		    clearControls();
		    return;
		}
		
		int i = selected_languages_list.getSelectedIndex();
 		int size = selected_languages_list.getModel().getSize();
		Language selected_lang = (Language)selected_languages_list.getSelectedValue();
		remove_button.setEnabled(true);
		replace_button.setEnabled(false);
		add_button.setEnabled(false);
		set_default_button.setEnabled(default_language == null || !default_language.equals(selected_lang));
		
		if (i > 0) {
 		    move_up_button.setEnabled(true);
 		}
 		else {
 		    move_up_button.setEnabled(false);
 		}
 		if (i < size-1){
 		    move_down_button.setEnabled(true);
 		}
 		else {
 		    move_down_button.setEnabled(false);
 		}
		updateControlsWithSelectedLanguage();
	    }
	}

	private class MyLanguageListCellRenderer
	    extends DefaultListCellRenderer
	{
	    /** Return a component that has been configured to display the specified value. */
	    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
		JLabel component = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
		if (default_language != null && default_language.equals(value)) {
		    component.setText(component.getText() + " [" + Dictionary.get("CDM.LanguageManager.Default_Language")+"]");
		}
		return component;
	    }
	}
    }
    
    /** A custom list cell renderer for producing rows which contain clickable check boxes. */
    private class LanguageCheckListCellRenderer 
	implements ListCellRenderer
    {
	/** Return a component that has been configured to display the specified value. That component's paint method is then called to "render" the cell. If it is necessary to compute the dimensions of a list because the list cells do not have a fixed size, this method is called to generate a component on which getPreferredSize  can be invoked.
	 * @param list The </strong>JList</strong> we're painting.
	 * @param value The value returned by list.getModel().getElementAt(index), as an <strong>Object</strong>.
	 * @param index The cells index as an <i>int</i>.
	 * @param is_selected <i>true</i> if the specified cell was selected, <i>false</i> otherwise.
	 * @param cell_has_focus <i>true</i> if and only if the specified cell has the focus.
	 * @return A <strong>Component</strong> whose paint() method will render the specified value.
	 */
	public Component getListCellRendererComponent(JList list, Object value, int index, boolean is_selected, boolean cell_has_focus) {
	    JCheckBox checkbox = (JCheckBox) value;
	    checkbox.setBackground(list.getBackground());
	    checkbox.setForeground(list.getForeground());
	    checkbox.setBorderPainted(false);
	    checkbox.setEnabled(list.isEnabled());
	    checkbox.setFont(list.getFont());
	    checkbox.setFocusPainted(false);
	    checkbox.setBorder((is_selected) ? UIManager.getBorder("List.focusCellHighlightBorder") : new EmptyBorder(1, 1, 1, 1));
	    
	    String code = (String)((CheckListEntry)list.getModel().getElementAt(index)).getObject();
	    checkbox.setText((String)known_languages.get(code));

	    return checkbox;
	}
    }

}
