/**
*
* Copyright 2013 Radek Henys
* 
* This file is part of Spelling Alphabet Trainer.
*
* Spelling Alphabet Trainer 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 3 of the License, or
* (at your option) any later version.
*
* Spelling Alphabet Trainer 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 Spelling Alphabet Trainer.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package com.mouseviator.spelling.alphabet.trainer;

import com.mouseviator.utils.internatialization.LanguageFile;
import com.mouseviator.utils.internatialization.Translator;
import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.*;
import java.io.*;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.border.TitledBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.TableColumn;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.MaskFormatter;

/**
 * Spell Table Trainer is program to train spelling of spell tables.
 *
 * @author Murdock
 */
public class SpellingAlphabetTrainer extends javax.swing.JFrame {
    /*
     * My variables
     */

    private List<ListItem> spell_tables = new ArrayList<>();
    private Properties activeSpellTable = new Properties(); //active spell table
    private List<String> usedKeys = new ArrayList<>();
    private List<String> keysToChooseFrom = new ArrayList<>();
    private ListItem lastSpellTable;
    private Properties appSettings = new Properties();
    private Random randomGenerator = new Random();
    private boolean answerCorrect = false;
    private Timer answerResultTimer;
    private int correct_answers = 0;
    private int incorrect_answers = 0;
    private int table_rotations = 0;
    private static final String SPELL_TABLES_FOLDER = "." + File.separator + "tables" + File.separator;
    private static final String SPELL_TABLES_PATTERN = "^(.*?)(.properties)$";
    private static final String LANG_FOLDER = "." + File.separator + "lang" + File.separator;
    private static final String LANG_KEY_PATTERN = "^(.*?)(_)([\\w]{2})(_)([\\w]{2})$";
    private static final String FILE_NAME_PATTERN = "^(.*)(\\.)(.*)$";
    private static final byte UPDATE_VARIABLE_TO_FORM = 1;
    private static final byte UPDATE_FORM_TO_VARIABLE = 2;
    private static final String SETTINGS_FILE = "settings.properties";
    private static final String DOCS_FOLDER = "docs" + File.separator;
    private static final String LICENSE_FILE = "license.pdf";
    private static final String HELP_FILE = "help.pdf";

    private void TEDeleteRow() {
        int[] selected_rows = tblSpellTable.getSelectedRows();
        SpellTableModel model = (SpellTableModel) tblSpellTable.getModel();
        model.deleteRows(selected_rows);
    }

    private void TEAddRow() {
        SpellTableModel model = (SpellTableModel) tblSpellTable.getModel();
        model.addRow(new String[]{"", "new"});
        tblSpellTable.setRowSelectionInterval(tblSpellTable.getRowCount() - 1, tblSpellTable.getRowCount() - 1);
    }

    private void TESaveTable() {
        SpellTableModel model = (SpellTableModel) tblSpellTable.getModel();

        //iterate through items and look if there are some empty
        int invalid_rows = 0;
        int num_rows = model.getRowCount();

        //If there are no rows, there is no reason to save
        if (num_rows == 0) {
            JOptionPane.showMessageDialog(this, Translator.getString("error.editor.save.empty_table_msg"), Translator.getString("error.editor.save.empty_table_dt"), JOptionPane.ERROR_MESSAGE);
            return;
        }

        //iterate rows and check if they are valid
        for (String[] row : model.getData()) {
            if (row[0].trim().isEmpty() || row[1].trim().isEmpty()) {
                invalid_rows++;
            }
        }

        //warn user
        if (invalid_rows == num_rows) {
            JOptionPane.showMessageDialog(this, Translator.getString("error.editor.save.no_valid_rows_msg"), Translator.getString("error.editor.save.no_valid_rows_dt"), JOptionPane.ERROR_MESSAGE);
            return;
        } else if (invalid_rows > 0) {
            int result = JOptionPane.showConfirmDialog(this, Translator.getString("error.editor.save.invalid_rows_msg"), Translator.getString("error.editor.save.invalid_rows_dt"), JOptionPane.YES_NO_OPTION);
            if (result == JOptionPane.NO_OPTION) {
                return;
            }
        }

        //at least some rows are ok, save it
        //remeber what table was edited
        ListItem edited_table = (ListItem) lstTESpellTable.getSelectedItem();
        if (model.savePropertiesFile(model.getPropertiesFile())) {
            //Take a look if there is such a table in test list, if not, this one is new, so add it
            boolean isPresent = false;
            for (int i = 0; i < lstActiveSpellTable.getItemCount(); i++) {
                ListItem loaded_item = lstActiveSpellTable.getItemAt(i);
                if (loaded_item.equals(edited_table)) {
                    isPresent = true;
                }
            }
            //if not found, add it to the list
            if (!isPresent) {
                lstActiveSpellTable.addItem(edited_table);
                spell_tables.add(edited_table);
            }
            //if successfully saved, look if edited table is the one that is tested, if so, reload it
            ListItem tested_table = (ListItem) lstActiveSpellTable.getSelectedItem();
            if (((String) edited_table.getItemData()).equals((String) tested_table.getItemData())) {
                loadSelectedSpellTable();
            }
            JOptionPane.showMessageDialog(frmTableEditor, Translator.getString("table_editor.save.success"), Translator.getString("table_editor.save.DT"), JOptionPane.INFORMATION_MESSAGE);
        } else {
            JOptionPane.showMessageDialog(frmTableEditor, Translator.getString("table_editor.save.failed"), Translator.getString("table_editor.save.DT"), JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * Editor to edit spell table characters
     */
    protected class CharacterEditor extends DefaultCellEditor {

        JFormattedTextField ftf;
        MaskFormatter mf;
        private boolean DEBUG = false;

        /**
         * Default constructor
         */
        public CharacterEditor() {
            super(new JFormattedTextField());
            try {
                ftf = (JFormattedTextField) getComponent();

                //Setup MaskFormatter
                mf = new MaskFormatter("**");
                //mf.setValidCharacters("");

                ftf.setFormatterFactory(new DefaultFormatterFactory(mf));
                ftf.setHorizontalAlignment(JTextField.LEADING);
                ftf.setFocusLostBehavior(JFormattedTextField.PERSIST);

                //React when the user presses Enter while the editor is
                //active.  (Tab is handled as specified by
                //JFormattedTextField's focusLostBehavior property.)
                ftf.getInputMap().put(KeyStroke.getKeyStroke(
                        KeyEvent.VK_ENTER, 0),
                        "check");
                ftf.getActionMap().put("check", new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (!ftf.isEditValid()) { //The text is invalid.
                            if (userSaysRevert()) { //reverted
                                ftf.postActionEvent(); //inform the editor
                            }
                        } else {
                            try {              //The text is valid,
                                ftf.setText(ftf.getText().trim().toUpperCase());
                                ftf.commitEdit();     //so use it.
                                ftf.postActionEvent(); //stop editing
                            } catch (java.text.ParseException exc) {
                            }
                        }
                    }
                });
            } catch (ParseException ex) {
                Logger.getLogger(CharacterEditor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        //Override to invoke setValue on the formatted text field.
        /**
         *
         * @param table
         * @param value
         * @param isSelected
         * @param row
         * @param column
         * @return
         */
        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected,
                int row, int column) {
            JFormattedTextField ftf =
                    (JFormattedTextField) super.getTableCellEditorComponent(
                    table, value, isSelected, row, column);
            ftf.setValue(value);
            return ftf;
        }

        //Override to ensure that the value remains an String.
        /**
         *
         * @return
         */
        @Override
        public Object getCellEditorValue() {
            JFormattedTextField ftf = (JFormattedTextField) getComponent();
            Object o = ftf.getValue();
            if (o instanceof String) {
                return o;
            } else {
                if (DEBUG) {
                    System.out.println("getCellEditorValue: o isn't a String");
                }
                return String.valueOf(o);
            }
        }

        //Override to check whether the edit is valid,
        //setting the value if it is and complaining if
        //it isn't.  If it's OK for the editor to go
        //away, we need to invoke the superclass's version 
        //of this method so that everything gets cleaned up.
        /**
         *
         * @return
         */
        @Override
        public boolean stopCellEditing() {
            JFormattedTextField ftf = (JFormattedTextField) getComponent();
            if (ftf.isEditValid()) {
                try {
                    ftf.commitEdit();
                } catch (java.text.ParseException exc) {
                }

            } else { //text is invalid
                if (!userSaysRevert()) { //user wants to edit
                    return false; //don't let the editor go away
                }
            }
            return super.stopCellEditing();
        }

        /**
         * Lets the user know that the text they entered is bad. Returns true if
         * the user elects to revert to the last good value. Otherwise, returns
         * false, indicating that the user wants to continue editing.
         *
         * @return Returns true if user selects to keep old value in case of
         * input error
         */
        protected boolean userSaysRevert() {
            Toolkit.getDefaultToolkit().beep();
            ftf.selectAll();
            Object[] options = {Translator.getString("table_editor.char_edit_opt_edit"),
                Translator.getString("table_editor.char_edit_opt_revert")};
            int answer = JOptionPane.showOptionDialog(
                    SwingUtilities.getWindowAncestor(ftf),
                    Translator.getString("table_editor.invalid_text_character_msg"),
                    Translator.getString("table_editor.invalid_text_character_dt"),
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.ERROR_MESSAGE,
                    null,
                    options,
                    options[1]);

            if (answer == 1) { //Revert!
                ftf.setValue(ftf.getValue());
                return true;
            }
            return false;
        }
    }

    /**
     * Creates new form SpellTableTrainer
     */
    public SpellingAlphabetTrainer() {
        initComponents();

        //Ukoncovani
        WindowListener wl = new WindowListener() {
            @Override
            public void windowOpened(WindowEvent e) {
            }

            @Override
            public void windowClosing(WindowEvent e) {
                close();
            }

            @Override
            public void windowClosed(WindowEvent e) {
            }

            @Override
            public void windowIconified(WindowEvent e) {
            }

            @Override
            public void windowDeiconified(WindowEvent e) {
            }

            @Override
            public void windowActivated(WindowEvent e) {
            }

            @Override
            public void windowDeactivated(WindowEvent e) {
            }
        };
        this.addWindowListener(wl);

        //Set program icons
        Image image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("resources/SAT_icon_64.png"));
        this.setIconImage(image);
        frmAbout.setIconImage(image);
        frmSettings.setIconImage(image);
        frmTableEditor.setIconImage(image);

        //Set txtCharacter document listener
        txtSpelledCharacter.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                //text was changed
                enableCheckButton(true);
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                //text was removed
                enableCheckButton(true);
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                //text was inserted
                enableCheckButton(true);
            }
        });
        
        //this should center the program window on screen
        this.setLocationRelativeTo(null);
        frmAbout.setLocationRelativeTo(null);
        frmSettings.setLocationRelativeTo(null);
        frmTableEditor.setLocationRelativeTo(null);
        
        appInit();
    }

    //Init spell table combo box
    private void appInit() {
        try {
            /*
             * Load available languages and init current language
             */
            Translator.setLanguageFolder(LANG_FOLDER);
        } catch (IOException ex) {
            Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
        }
        //Check if there are any languages, if not, display warning
        if (Translator.getLanguages().isEmpty()) {
            JOptionPane.showMessageDialog(this, "Nebyl nalazen žádný jazykový soubor pro překlad uživatelského rozhraní! Bude použit výchozí jazyk!\nNo language file for translating GUI has been found! Default language will be used!",
                    "Chyba\\Error", JOptionPane.ERROR_MESSAGE);
            lstLanguage.setEnabled(false);
        } else {
            //add available languages into language chooser
            Map<String, LanguageFile> languages = Translator.getLanguages();
            for (Iterator<String> it = languages.keySet().iterator(); it.hasNext();) {
                String lang_key = it.next();
                LanguageFile lf = languages.get(lang_key);
                Locale lang_loc = lf.getLocale();
                ListItem<String> lang_item = new ListItem<>(lang_loc.getDisplayName(), lang_key);
                lstLanguage.addItem(lang_item);
            }
        }

        /*
         * Than load settings
         */
        File f = new File(SETTINGS_FILE);
        FileInputStream fis = null;
        InputStreamReader reader = null;
        if (f.exists()) {
            try {
                fis = new FileInputStream(f);
                reader = new InputStreamReader(fis, Charset.forName("UTF-8"));
                appSettings.load(reader);
                updateSettings(UPDATE_VARIABLE_TO_FORM);
            } catch (Exception ex) {
                Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
                updateSettings(UPDATE_FORM_TO_VARIABLE);
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException ex) {
                        Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException ex) {
                        Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        } else {
            //find if we have default language
            setDefaultLanguage();
            updateSettings(UPDATE_FORM_TO_VARIABLE);
        }

        //Now we should know what language to load, so do it
        loadSelectedLanguage();

        //setup the answer timer
        setupAnswerTimer();

        //load available spell tables
        loadSpellTables();
        //Check if there are any spell tables, if not, quit
        if (spell_tables.isEmpty()) {
            try {
                JOptionPane.showMessageDialog(this, Translator.getString("error.no_spell_tables_msg"), Translator.getString("error.no_spell_tables_dt"), JOptionPane.ERROR_MESSAGE);
            } catch (Exception ex) {
                Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                this.dispose();
                System.exit(0);
            }
        }

        for (ListItem item : spell_tables) {
            lstActiveSpellTable.addItem(item);
            lstTESpellTable.addItem(item);
        }

        //Load active spell table
        loadSelectedSpellTable();
        //set first question
        setQuestion();
    }

    private void setupAnswerTimer() {
        int time = ((SpinnerNumberModel) sprHighlightTime.getModel()).getNumber().intValue();
        answerResultTimer = new Timer(time, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                lblCheckResult.setText("");
                lblCheckResult.setBackground(null);
                lblCheckResult.setForeground(Color.black);
                txtSpelledCharacter.setBackground(Color.white);
                txtSpelledCharacter.setForeground(Color.black);
                txtSpelledCharacter.setText("");
                updateStatistics();
                setQuestion();
            }
        });
        answerResultTimer.setRepeats(false);
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        frmSettings = new javax.swing.JDialog();
        lblLanguage = new javax.swing.JLabel();
        lstLanguage = new javax.swing.JComboBox<ListItem>();
        lblCorrectAnswerHighlightDesc = new javax.swing.JLabel();
        lblIncorrectAnswerHighlightDesc = new javax.swing.JLabel();
        lblCorrectAnswerHighlight = new javax.swing.JLabel();
        lblIncorrectAnswerHighlight = new javax.swing.JLabel();
        lblHighlightTime = new javax.swing.JLabel();
        sprHighlightTime = new javax.swing.JSpinner();
        cmdSettingsOk = new javax.swing.JButton();
        chkShowWarningBeforeSTDelete = new javax.swing.JCheckBox();
        frmTableEditor = new javax.swing.JDialog();
        cmdTEClose = new javax.swing.JButton();
        cmdTESave = new javax.swing.JButton();
        pnlTableSelection = new javax.swing.JPanel();
        lblTESpellTable = new javax.swing.JLabel();
        cmdTELoad = new javax.swing.JButton();
        lstTESpellTable = new javax.swing.JComboBox<ListItem>();
        cmdTENew = new javax.swing.JButton();
        cmdTEDelete = new javax.swing.JButton();
        pnlTableEdit = new javax.swing.JPanel();
        jScrollPane1 = new javax.swing.JScrollPane();
        tblSpellTable = new javax.swing.JTable();
        cmdTEAddRow = new javax.swing.JButton();
        cmdTEDeleteRow = new javax.swing.JButton();
        frmAbout = new javax.swing.JDialog();
        lblAppTitle = new javax.swing.JLabel();
        lblAuthorDesc = new javax.swing.JLabel();
        lblVersionDesc = new javax.swing.JLabel();
        lblWebsiteDesc = new javax.swing.JLabel();
        lblAuthor = new javax.swing.JLabel();
        lblVersion = new javax.swing.JLabel();
        lblWebsite = new javax.swing.JLabel();
        cmdAboutOk = new javax.swing.JButton();
        lblNotesDesc = new javax.swing.JLabel();
        lblNotes = new javax.swing.JLabel();
        lblLicensed = new javax.swing.JLabel();
        lblGNUGPLv3Image = new javax.swing.JLabel();
        lblSATImage = new javax.swing.JLabel();
        lblActiveSpellTable = new javax.swing.JLabel();
        lstActiveSpellTable = new javax.swing.JComboBox<ListItem>();
        lblCurrentCharDesc = new javax.swing.JLabel();
        lblCurrentChar = new javax.swing.JLabel();
        txtSpelledCharacter = new javax.swing.JTextField();
        cmdCheck = new javax.swing.JButton();
        lblCheckResult = new javax.swing.JLabel();
        lblStatistics = new javax.swing.JLabel();
        cmdClose = new javax.swing.JButton();
        mainMenuBar = new javax.swing.JMenuBar();
        mainMenu = new javax.swing.JMenu();
        menuSettings = new javax.swing.JMenuItem();
        menuTableEditor = new javax.swing.JMenuItem();
        menuClose = new javax.swing.JMenuItem();
        helpMenu = new javax.swing.JMenu();
        menuAbout = new javax.swing.JMenuItem();
        menuLicense = new javax.swing.JMenuItem();
        menuHelp = new javax.swing.JMenuItem();

        frmSettings.setTitle("Settings");
        frmSettings.setModal(true);

        lblLanguage.setText("Language:");

        lstLanguage.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                lstLanguageItemStateChanged(evt);
            }
        });
        lstLanguage.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                lstLanguageActionPerformed(evt);
            }
        });

        lblCorrectAnswerHighlightDesc.setText("Correct answer highlight:");

        lblIncorrectAnswerHighlightDesc.setText("Incorrect answer highlight:");

        lblCorrectAnswerHighlight.setBackground(new java.awt.Color(0, 255, 0));
        lblCorrectAnswerHighlight.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblCorrectAnswerHighlight.setText("Correct");
        lblCorrectAnswerHighlight.setToolTipText("Left click to set background color, right click to set foreground color.");
        lblCorrectAnswerHighlight.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
        lblCorrectAnswerHighlight.setOpaque(true);
        lblCorrectAnswerHighlight.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                lblCorrectAnswerHighlightMouseClicked(evt);
            }
        });

        lblIncorrectAnswerHighlight.setBackground(new java.awt.Color(255, 0, 0));
        lblIncorrectAnswerHighlight.setForeground(new java.awt.Color(255, 255, 255));
        lblIncorrectAnswerHighlight.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblIncorrectAnswerHighlight.setText("Incorrect");
        lblIncorrectAnswerHighlight.setToolTipText("Left click to set background color, right click to set foreground color.");
        lblIncorrectAnswerHighlight.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
        lblIncorrectAnswerHighlight.setOpaque(true);
        lblIncorrectAnswerHighlight.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                lblIncorrectAnswerHighlightMouseClicked(evt);
            }
        });

        lblHighlightTime.setText("Highlight time (Miliseconds):");

        sprHighlightTime.setModel(new javax.swing.SpinnerNumberModel(1250, 250, 10000, 10));
        sprHighlightTime.setToolTipText("This sets how long the correct/incorrect result will be highlighted.");

        cmdSettingsOk.setText("Ok");
        cmdSettingsOk.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdSettingsOkActionPerformed(evt);
            }
        });

        chkShowWarningBeforeSTDelete.setSelected(true);
        chkShowWarningBeforeSTDelete.setText("Show waring before deleting spell table:");

        javax.swing.GroupLayout frmSettingsLayout = new javax.swing.GroupLayout(frmSettings.getContentPane());
        frmSettings.getContentPane().setLayout(frmSettingsLayout);
        frmSettingsLayout.setHorizontalGroup(
            frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(frmSettingsLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(frmSettingsLayout.createSequentialGroup()
                        .addComponent(lblLanguage, javax.swing.GroupLayout.PREFERRED_SIZE, 96, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(lstLanguage, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .addGroup(frmSettingsLayout.createSequentialGroup()
                        .addGroup(frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                            .addComponent(lblIncorrectAnswerHighlightDesc, javax.swing.GroupLayout.DEFAULT_SIZE, 169, Short.MAX_VALUE)
                            .addComponent(lblCorrectAnswerHighlightDesc, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(lblCorrectAnswerHighlight, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .addComponent(lblIncorrectAnswerHighlight, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, frmSettingsLayout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(cmdSettingsOk))
                    .addGroup(frmSettingsLayout.createSequentialGroup()
                        .addComponent(lblHighlightTime)
                        .addGap(18, 18, 18)
                        .addComponent(sprHighlightTime, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(0, 95, Short.MAX_VALUE))
                    .addComponent(chkShowWarningBeforeSTDelete, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        frmSettingsLayout.setVerticalGroup(
            frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(frmSettingsLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblLanguage)
                    .addComponent(lstLanguage, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblCorrectAnswerHighlightDesc)
                    .addComponent(lblCorrectAnswerHighlight))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblIncorrectAnswerHighlightDesc)
                    .addComponent(lblIncorrectAnswerHighlight))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(lblHighlightTime)
                    .addComponent(sprHighlightTime, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(chkShowWarningBeforeSTDelete)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 15, Short.MAX_VALUE)
                .addComponent(cmdSettingsOk)
                .addContainerGap())
        );

        frmTableEditor.setTitle("Spell Table Editor");
        frmTableEditor.setModal(true);

        cmdTEClose.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Cancel_16x16.png"))); // NOI18N
        cmdTEClose.setText("Close");
        cmdTEClose.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdTECloseActionPerformed(evt);
            }
        });

        cmdTESave.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/table-save-icon.png"))); // NOI18N
        cmdTESave.setText("Save");
        cmdTESave.setEnabled(false);
        cmdTESave.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdTESaveActionPerformed(evt);
            }
        });

        pnlTableSelection.setBorder(javax.swing.BorderFactory.createTitledBorder("Select table:"));

        lblTESpellTable.setText("Spell table:");

        cmdTELoad.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Button-Load-icon16.png"))); // NOI18N
        cmdTELoad.setText("Load");
        cmdTELoad.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdTELoadActionPerformed(evt);
            }
        });

        cmdTENew.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/table-add-icon.png"))); // NOI18N
        cmdTENew.setText("New");
        cmdTENew.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdTENewActionPerformed(evt);
            }
        });

        cmdTEDelete.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/table-delete-icon.png"))); // NOI18N
        cmdTEDelete.setText("Delete");
        cmdTEDelete.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdTEDeleteActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout pnlTableSelectionLayout = new javax.swing.GroupLayout(pnlTableSelection);
        pnlTableSelection.setLayout(pnlTableSelectionLayout);
        pnlTableSelectionLayout.setHorizontalGroup(
            pnlTableSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnlTableSelectionLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(lblTESpellTable, javax.swing.GroupLayout.DEFAULT_SIZE, 107, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(pnlTableSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlTableSelectionLayout.createSequentialGroup()
                        .addComponent(cmdTENew, javax.swing.GroupLayout.PREFERRED_SIZE, 74, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cmdTEDelete))
                    .addGroup(pnlTableSelectionLayout.createSequentialGroup()
                        .addComponent(lstTESpellTable, javax.swing.GroupLayout.PREFERRED_SIZE, 220, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cmdTELoad, javax.swing.GroupLayout.PREFERRED_SIZE, 74, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap())
        );

        pnlTableSelectionLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cmdTEDelete, cmdTELoad, cmdTENew});

        pnlTableSelectionLayout.setVerticalGroup(
            pnlTableSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlTableSelectionLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlTableSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblTESpellTable)
                    .addComponent(lstTESpellTable, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(cmdTELoad))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(pnlTableSelectionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(cmdTENew)
                    .addComponent(cmdTEDelete))
                .addContainerGap())
        );

        pnlTableSelectionLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {cmdTEDelete, cmdTELoad, cmdTENew});

        pnlTableEdit.setBorder(javax.swing.BorderFactory.createTitledBorder("Edit table:"));

        tblSpellTable.setModel(new SpellTableModel());
        tblSpellTable.setCellSelectionEnabled(true);
        tblSpellTable.setRowHeight(20);
        tblSpellTable.getColumnModel().getColumn(0).setCellEditor(new CharacterEditor());
        jScrollPane1.setViewportView(tblSpellTable);

        cmdTEAddRow.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/table-row-insert-icon.png"))); // NOI18N
        cmdTEAddRow.setText("Add");
        cmdTEAddRow.setEnabled(false);
        cmdTEAddRow.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdTEAddRowActionPerformed(evt);
            }
        });

        cmdTEDeleteRow.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/table-row-delete-icon.png"))); // NOI18N
        cmdTEDeleteRow.setText("Delete");
        cmdTEDeleteRow.setEnabled(false);
        cmdTEDeleteRow.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdTEDeleteRowActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout pnlTableEditLayout = new javax.swing.GroupLayout(pnlTableEdit);
        pnlTableEdit.setLayout(pnlTableEditLayout);
        pnlTableEditLayout.setHorizontalGroup(
            pnlTableEditLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlTableEditLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(pnlTableEditLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(cmdTEAddRow, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 69, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(cmdTEDeleteRow, javax.swing.GroupLayout.Alignment.TRAILING))
                .addContainerGap())
        );

        pnlTableEditLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cmdTEAddRow, cmdTEDeleteRow});

        pnlTableEditLayout.setVerticalGroup(
            pnlTableEditLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlTableEditLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlTableEditLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlTableEditLayout.createSequentialGroup()
                        .addComponent(cmdTEAddRow)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cmdTEDeleteRow))
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 275, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pnlTableEditLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {cmdTEAddRow, cmdTEDeleteRow});

        javax.swing.GroupLayout frmTableEditorLayout = new javax.swing.GroupLayout(frmTableEditor.getContentPane());
        frmTableEditor.getContentPane().setLayout(frmTableEditorLayout);
        frmTableEditorLayout.setHorizontalGroup(
            frmTableEditorLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(frmTableEditorLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(frmTableEditorLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(frmTableEditorLayout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(cmdTESave)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cmdTEClose))
                    .addComponent(pnlTableEdit, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(pnlTableSelection, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );

        frmTableEditorLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cmdTEClose, cmdTESave});

        frmTableEditorLayout.setVerticalGroup(
            frmTableEditorLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, frmTableEditorLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(pnlTableSelection, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pnlTableEdit, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmTableEditorLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(cmdTESave)
                    .addComponent(cmdTEClose))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        frmTableEditorLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {cmdTEClose, cmdTESave});

        frmAbout.setTitle("About");
        frmAbout.setModal(true);

        lblAppTitle.setFont(new java.awt.Font("Tahoma", 1, 20)); // NOI18N
        lblAppTitle.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblAppTitle.setText("Spelling Alphabet Trainer");

        lblAuthorDesc.setText("Author:");

        lblVersionDesc.setText("Version:");

        lblWebsiteDesc.setText("Website:");

        lblAuthor.setFont(new java.awt.Font("Tahoma", 1, 13)); // NOI18N
        lblAuthor.setText("Radek Henys");

        lblVersion.setFont(new java.awt.Font("Tahoma", 1, 13)); // NOI18N
        lblVersion.setText("1.0");

        lblWebsite.setFont(new java.awt.Font("Tahoma", 1, 13)); // NOI18N
        lblWebsite.setForeground(new java.awt.Color(51, 51, 255));
        lblWebsite.setText("web");

        cmdAboutOk.setText("OK");
        cmdAboutOk.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdAboutOkActionPerformed(evt);
            }
        });

        lblNotesDesc.setText("Notes:");

        lblNotes.setFont(new java.awt.Font("Tahoma", 2, 13)); // NOI18N
        lblNotes.setText("web");
        lblNotes.setVerticalAlignment(javax.swing.SwingConstants.TOP);

        lblLicensed.setText("Licensed under GNU GPL v3");

        lblGNUGPLv3Image.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/gplv3-127x51.png"))); // NOI18N

        lblSATImage.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/SAT_icon_128.png"))); // NOI18N

        javax.swing.GroupLayout frmAboutLayout = new javax.swing.GroupLayout(frmAbout.getContentPane());
        frmAbout.getContentPane().setLayout(frmAboutLayout);
        frmAboutLayout.setHorizontalGroup(
            frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, frmAboutLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                        .addComponent(lblNotesDesc, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(lblAuthorDesc, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(lblVersionDesc, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(lblWebsiteDesc, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(lblGNUGPLv3Image, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .addComponent(lblSATImage))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(frmAboutLayout.createSequentialGroup()
                        .addComponent(lblLicensed, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addGap(18, 18, 18)
                        .addComponent(cmdAboutOk))
                    .addComponent(lblVersion, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(lblWebsite, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(lblNotes, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(lblAuthor, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(lblAppTitle, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE))
                .addContainerGap())
        );
        frmAboutLayout.setVerticalGroup(
            frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(frmAboutLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(lblSATImage, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(lblAppTitle, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblAuthorDesc)
                    .addComponent(lblAuthor))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblVersionDesc)
                    .addComponent(lblVersion))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblWebsiteDesc)
                    .addComponent(lblWebsite))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(frmAboutLayout.createSequentialGroup()
                        .addComponent(lblNotes, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(frmAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(cmdAboutOk)
                            .addComponent(lblLicensed)))
                    .addGroup(frmAboutLayout.createSequentialGroup()
                        .addComponent(lblNotesDesc)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(lblGNUGPLv3Image, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                .addContainerGap())
        );

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        setTitle("Spelling Alphabet Trainer");

        lblActiveSpellTable.setText("Active spell table:");

        lstActiveSpellTable.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                lstActiveSpellTableActionPerformed(evt);
            }
        });

        lblCurrentCharDesc.setText("How do you spell this character:");

        lblCurrentChar.setFont(new java.awt.Font("Tahoma", 1, 36)); // NOI18N
        lblCurrentChar.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblCurrentChar.setText("A");

        txtSpelledCharacter.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                txtSpelledCharacterKeyPressed(evt);
            }
        });

        cmdCheck.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Check_16x16.png"))); // NOI18N
        cmdCheck.setText("Check");
        cmdCheck.setEnabled(false);
        cmdCheck.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdCheckActionPerformed(evt);
            }
        });

        lblCheckResult.setText(" ");
        lblCheckResult.setOpaque(true);

        lblStatistics.setText(" ");

        cmdClose.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Shutdown_button_T16x16.png"))); // NOI18N
        cmdClose.setText("Close");
        cmdClose.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdCloseActionPerformed(evt);
            }
        });

        mainMenu.setText("Menu");

        menuSettings.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
        menuSettings.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Settings_16x16.png"))); // NOI18N
        menuSettings.setText("Settings...");
        menuSettings.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                menuSettingsActionPerformed(evt);
            }
        });
        mainMenu.add(menuSettings);

        menuTableEditor.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_E, java.awt.event.InputEvent.CTRL_MASK));
        menuTableEditor.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/table-edit-icon.png"))); // NOI18N
        menuTableEditor.setText("Table Editor...");
        menuTableEditor.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                menuTableEditorActionPerformed(evt);
            }
        });
        mainMenu.add(menuTableEditor);

        menuClose.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F4, java.awt.event.InputEvent.ALT_MASK));
        menuClose.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Shutdown_button_T16x16.png"))); // NOI18N
        menuClose.setText("Close");
        menuClose.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                menuCloseActionPerformed(evt);
            }
        });
        mainMenu.add(menuClose);

        mainMenuBar.add(mainMenu);

        helpMenu.setText("Help");

        menuAbout.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F1, java.awt.event.InputEvent.CTRL_MASK));
        menuAbout.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Information_16x16.png"))); // NOI18N
        menuAbout.setText("About...");
        menuAbout.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                menuAboutActionPerformed(evt);
            }
        });
        helpMenu.add(menuAbout);

        menuLicense.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F2, java.awt.event.InputEvent.CTRL_MASK));
        menuLicense.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Text Document_16x16.png"))); // NOI18N
        menuLicense.setText("License...");
        menuLicense.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                menuLicenseActionPerformed(evt);
            }
        });
        helpMenu.add(menuLicense);

        menuHelp.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F1, 0));
        menuHelp.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/mouseviator/spelling/alphabet/trainer/resources/Actions-help-contents-icon.png"))); // NOI18N
        menuHelp.setText("Help...");
        menuHelp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                menuHelpActionPerformed(evt);
            }
        });
        helpMenu.add(menuHelp);

        mainMenuBar.add(helpMenu);

        setJMenuBar(mainMenuBar);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(lblActiveSpellTable)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(lstActiveSpellTable, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .addComponent(lblCheckResult, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(lblCurrentCharDesc)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(lblCurrentChar, javax.swing.GroupLayout.PREFERRED_SIZE, 103, javax.swing.GroupLayout.PREFERRED_SIZE))
                            .addComponent(txtSpelledCharacter))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cmdCheck))
                    .addComponent(lblStatistics, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(cmdClose)))
                .addContainerGap())
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cmdCheck, cmdClose});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(lblActiveSpellTable)
                    .addComponent(lstActiveSpellTable, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(lblCurrentChar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(lblCurrentCharDesc, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(txtSpelledCharacter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(cmdCheck))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(lblCheckResult)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(lblStatistics, javax.swing.GroupLayout.DEFAULT_SIZE, 43, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(cmdClose)
                .addGap(7, 7, 7))
        );

        layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {cmdCheck, cmdClose});

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void cmdCloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdCloseActionPerformed
        close();
    }//GEN-LAST:event_cmdCloseActionPerformed

    private void lstActiveSpellTableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_lstActiveSpellTableActionPerformed
        loadSelectedSpellTable();
        setQuestion();
    }//GEN-LAST:event_lstActiveSpellTableActionPerformed

    private void cmdCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdCheckActionPerformed
        checkAnswer();
    }//GEN-LAST:event_cmdCheckActionPerformed

    private void txtSpelledCharacterKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtSpelledCharacterKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_ENTER && cmdCheck.isEnabled()) {
            checkAnswer();
        }
    }//GEN-LAST:event_txtSpelledCharacterKeyPressed

    private void menuCloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuCloseActionPerformed
        close();
    }//GEN-LAST:event_menuCloseActionPerformed

    private void cmdSettingsOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdSettingsOkActionPerformed
        setupAnswerTimer();
        frmSettings.setVisible(false);
    }//GEN-LAST:event_cmdSettingsOkActionPerformed

    private void menuSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuSettingsActionPerformed
        frmSettings.pack();
        frmSettings.revalidate();
        frmSettings.setLocationRelativeTo(this);
        frmSettings.setVisible(true);
        frmSettings.toFront();
    }//GEN-LAST:event_menuSettingsActionPerformed

    private void lblCorrectAnswerHighlightMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_lblCorrectAnswerHighlightMouseClicked
        if (evt.getButton() == MouseEvent.BUTTON1) {
            Color newColor = JColorChooser.showDialog(this, Translator.getString("lblCorrectAnswerHighlight.ch.bg.title"), lblCorrectAnswerHighlight.getBackground());
            if (newColor != null) {
                lblCorrectAnswerHighlight.setBackground(newColor);
            }
        } else if (evt.getButton() == MouseEvent.BUTTON3) {
            Color newColor = JColorChooser.showDialog(this, Translator.getString("lblCorrectAnswerHighlight.ch.fg.title"), lblCorrectAnswerHighlight.getForeground());
            if (newColor != null) {
                lblCorrectAnswerHighlight.setForeground(newColor);
            }
        }
    }//GEN-LAST:event_lblCorrectAnswerHighlightMouseClicked

    private void lblIncorrectAnswerHighlightMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_lblIncorrectAnswerHighlightMouseClicked
        if (evt.getButton() == MouseEvent.BUTTON1) {
            Color newColor = JColorChooser.showDialog(this, Translator.getString("lblIncorrectAnswerHighlight.ch.bg.title"), lblIncorrectAnswerHighlight.getBackground());
            if (newColor != null) {
                lblIncorrectAnswerHighlight.setBackground(newColor);
            }
        } else if (evt.getButton() == MouseEvent.BUTTON3) {
            Color newColor = JColorChooser.showDialog(this, Translator.getString("lblIncorrectAnswerHighlight.ch.fg.title"), lblIncorrectAnswerHighlight.getForeground());
            if (newColor != null) {
                lblIncorrectAnswerHighlight.setForeground(newColor);
            }
        }
    }//GEN-LAST:event_lblIncorrectAnswerHighlightMouseClicked

    private void lstLanguageItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_lstLanguageItemStateChanged
        loadSelectedLanguage();
    }//GEN-LAST:event_lstLanguageItemStateChanged

    private void cmdTECloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdTECloseActionPerformed
        frmTableEditor.setVisible(false);
    }//GEN-LAST:event_cmdTECloseActionPerformed

    private void menuTableEditorActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuTableEditorActionPerformed
        frmTableEditor.pack();
        frmTableEditor.revalidate();
        frmTableEditor.setLocationRelativeTo(this);
        frmTableEditor.setVisible(true);
        frmTableEditor.toFront();
    }//GEN-LAST:event_menuTableEditorActionPerformed

    private void cmdTELoadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdTELoadActionPerformed
        enableControls(false);
        loadSpellTableIntoEditor();
        enableControls(true);
    }//GEN-LAST:event_cmdTELoadActionPerformed

    private void cmdTEAddRowActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdTEAddRowActionPerformed
        enableControls(false);
        TEAddRow();
        enableControls(true);
    }//GEN-LAST:event_cmdTEAddRowActionPerformed

    private void cmdTEDeleteRowActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdTEDeleteRowActionPerformed
        enableControls(false);
        TEDeleteRow();
        enableControls(true);
    }//GEN-LAST:event_cmdTEDeleteRowActionPerformed

    private void cmdTESaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdTESaveActionPerformed
        enableControls(false);
        TESaveTable();
        enableControls(true);
    }//GEN-LAST:event_cmdTESaveActionPerformed

    private void cmdAboutOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdAboutOkActionPerformed
        frmAbout.setVisible(false);
    }//GEN-LAST:event_cmdAboutOkActionPerformed

    private void menuAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuAboutActionPerformed
        frmAbout.setTitle(Translator.getString("frmAbout.title"));
        frmAbout.pack();
        frmAbout.revalidate();
        frmAbout.setLocationRelativeTo(this);
        frmAbout.setVisible(true);
        frmAbout.toFront();
    }//GEN-LAST:event_menuAboutActionPerformed

    private void cmdTENewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdTENewActionPerformed
        enableControls(false);
        try {
            boolean validInput;
            String tableName = "";
            //loop while we have not valid input
            do {
                validInput = true;
                tableName = JOptionPane.showInputDialog(frmTableEditor, Translator.getString("table_editor.new.id.text"));
                //if null than Cancel was clicked
                if (tableName == null) {
                    return;
                }
                //check if there is such a name
                for (ListItem item : spell_tables) {
                    if (item.getTitle().equals(tableName)) {
                        validInput = false;
                        break;
                    }
                }
                if (!validInput) {
                    JOptionPane.showMessageDialog(frmTableEditor, MessageFormat.format(Translator.getString("table_editor.new.name.error_msg"), tableName),
                            Translator.getString("table_editor.new.name.error_dt"), JOptionPane.INFORMATION_MESSAGE);
                }
            } while (!validInput);
            //add the new table into list of edited tables and select it
            ListItem<String> new_table = new ListItem<>(tableName, SpellingAlphabetTrainer.SPELL_TABLES_FOLDER + File.separator + tableName + ".properties");
            lstTESpellTable.addItem(new_table);
            lstTESpellTable.setSelectedItem(new_table);

            //set the file name, so the table could be saved later
            SpellTableModel model = (SpellTableModel) tblSpellTable.getModel();
            model.setPropertiesFile((String) new_table.getItemData());

            TEAddRow();
        } finally {
            enableControls(true);
        }
    }//GEN-LAST:event_cmdTENewActionPerformed

    private void cmdTEDeleteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmdTEDeleteActionPerformed
        enableControls(false);
        ListItem selected_table = (ListItem) lstTESpellTable.getSelectedItem();
        //test whether to not delete
        if (chkShowWarningBeforeSTDelete.isSelected()
                && (JOptionPane.showConfirmDialog(frmTableEditor, MessageFormat.format(Translator.getString("table_editor.del_tbl.confirm_msg"), selected_table.getTitle(), selected_table.getItemData()),
                Translator.getString("table_editor.del_tbl.confirm_dt"), JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION)) {
            enableControls(true);
            return;
        }
        //now delete the selected_table from active spell tables
        ListItem active_table = (ListItem) lstActiveSpellTable.getSelectedItem();
        lstActiveSpellTable.removeItem(selected_table);
        //check if removed table was selected in active spell tables, if so, select another one
        if (selected_table.equals(active_table) && lstActiveSpellTable.getItemCount() > 0) {
            //select first table
            lstActiveSpellTable.setSelectedIndex(0);
        }
        //remove from spell tables
        spell_tables.remove(selected_table);
        lstTESpellTable.removeItem(selected_table);
        //if we have some tables left, select one and load it
        if (lstTESpellTable.getItemCount() > 0) {
            lstTESpellTable.setSelectedIndex(0);
            loadSpellTableIntoEditor();
        } else {
            ((SpellTableModel) tblSpellTable.getModel()).deleteAll();
        }
        //delete the file
        try {
            File f = new File((String) selected_table.getItemData());
            f.delete();
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(frmTableEditor, MessageFormat.format(Translator.getString("table_editor.del_tbl.failed_msg"), selected_table.getTitle()),
                    Translator.getString("table_editor.del_tbl.failed_dt"), JOptionPane.ERROR_MESSAGE);
            Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
        }

        enableControls(true);
    }//GEN-LAST:event_cmdTEDeleteActionPerformed

    private void lstLanguageActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_lstLanguageActionPerformed
    }//GEN-LAST:event_lstLanguageActionPerformed

    private void menuLicenseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuLicenseActionPerformed
        String file_name = DOCS_FOLDER + LICENSE_FILE;
        String lang_key = (String) ((ListItem) lstLanguage.getSelectedItem()).getItemData();
        openFile(getLocalizedFile(file_name, lang_key, true), true);
    }//GEN-LAST:event_menuLicenseActionPerformed

    private void menuHelpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menuHelpActionPerformed
        String file_name = DOCS_FOLDER + HELP_FILE;
        String lang_key = (String) ((ListItem) lstLanguage.getSelectedItem()).getItemData();
        openFile(getLocalizedFile(file_name, lang_key, true), true);
    }//GEN-LAST:event_menuHelpActionPerformed

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /*
         * Set the Nimbus look and feel
         */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /*
         * If Nimbus (introduced in Java SE 6) is not available, stay with the
         * default look and feel. For details see
         * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /*
         * Create and display the form
         */
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new SpellingAlphabetTrainer().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox chkShowWarningBeforeSTDelete;
    private javax.swing.JButton cmdAboutOk;
    private javax.swing.JButton cmdCheck;
    private javax.swing.JButton cmdClose;
    private javax.swing.JButton cmdSettingsOk;
    private javax.swing.JButton cmdTEAddRow;
    private javax.swing.JButton cmdTEClose;
    private javax.swing.JButton cmdTEDelete;
    private javax.swing.JButton cmdTEDeleteRow;
    private javax.swing.JButton cmdTELoad;
    private javax.swing.JButton cmdTENew;
    private javax.swing.JButton cmdTESave;
    private javax.swing.JDialog frmAbout;
    private javax.swing.JDialog frmSettings;
    private javax.swing.JDialog frmTableEditor;
    private javax.swing.JMenu helpMenu;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JLabel lblActiveSpellTable;
    private javax.swing.JLabel lblAppTitle;
    private javax.swing.JLabel lblAuthor;
    private javax.swing.JLabel lblAuthorDesc;
    private javax.swing.JLabel lblCheckResult;
    private javax.swing.JLabel lblCorrectAnswerHighlight;
    private javax.swing.JLabel lblCorrectAnswerHighlightDesc;
    private javax.swing.JLabel lblCurrentChar;
    private javax.swing.JLabel lblCurrentCharDesc;
    private javax.swing.JLabel lblGNUGPLv3Image;
    private javax.swing.JLabel lblHighlightTime;
    private javax.swing.JLabel lblIncorrectAnswerHighlight;
    private javax.swing.JLabel lblIncorrectAnswerHighlightDesc;
    private javax.swing.JLabel lblLanguage;
    private javax.swing.JLabel lblLicensed;
    private javax.swing.JLabel lblNotes;
    private javax.swing.JLabel lblNotesDesc;
    private javax.swing.JLabel lblSATImage;
    private javax.swing.JLabel lblStatistics;
    private javax.swing.JLabel lblTESpellTable;
    private javax.swing.JLabel lblVersion;
    private javax.swing.JLabel lblVersionDesc;
    private javax.swing.JLabel lblWebsite;
    private javax.swing.JLabel lblWebsiteDesc;
    private javax.swing.JComboBox<ListItem> lstActiveSpellTable;
    private javax.swing.JComboBox<ListItem> lstLanguage;
    private javax.swing.JComboBox<ListItem> lstTESpellTable;
    private javax.swing.JMenu mainMenu;
    private javax.swing.JMenuBar mainMenuBar;
    private javax.swing.JMenuItem menuAbout;
    private javax.swing.JMenuItem menuClose;
    private javax.swing.JMenuItem menuHelp;
    private javax.swing.JMenuItem menuLicense;
    private javax.swing.JMenuItem menuSettings;
    private javax.swing.JMenuItem menuTableEditor;
    private javax.swing.JPanel pnlTableEdit;
    private javax.swing.JPanel pnlTableSelection;
    private javax.swing.JSpinner sprHighlightTime;
    private javax.swing.JTable tblSpellTable;
    private javax.swing.JTextField txtSpelledCharacter;
    // End of variables declaration//GEN-END:variables

    private void loadSpellTables() {
        File folder = new File(SPELL_TABLES_FOLDER);
        final Pattern pattern = Pattern.compile(SPELL_TABLES_PATTERN, Pattern.CASE_INSENSITIVE);

        File[] files = folder.listFiles(new java.io.FileFilter() {
            @Override
            public boolean accept(File pathname) {
                Matcher matcher = pattern.matcher(pathname.getPath());
                return matcher.matches();
            }
        });

        if (files != null && files.length > 0) {
            for (File f : files) {
                Matcher matcher = pattern.matcher(f.getName());
                matcher.matches();
                //We had to have match, since it was matched against this same regex in the filter
                String fileName = matcher.group(1);
                ListItem<String> item = new ListItem<>(fileName, f.getPath());
                spell_tables.add(item);
            }
        }
    }

    private boolean loadSelectedSpellTable() {
        ListItem item = (ListItem) (lstActiveSpellTable.getSelectedItem());
        FileInputStream fis = null;
        InputStreamReader reader = null;
        try {
            Properties props = new Properties();
            fis = new FileInputStream((String) item.getItemData());
            reader = new InputStreamReader(fis, Charset.forName("UTF-8"));
            props.load(reader);
            if (props.size() > 0) {
                activeSpellTable.clear();
                activeSpellTable = props;
                //clear keys arrays, load all keys to  keysArrayA and set keysToChooseFrom
                keysToChooseFrom.clear();
                usedKeys.clear();
                for (Enumeration e = activeSpellTable.keys(); e.hasMoreElements();) {
                    String key = (String) e.nextElement();
                    keysToChooseFrom.add(key);
                }
                //set window title
                this.setTitle(MessageFormat.format(Translator.getString("app.stl.title"), Translator.getString("app.title"), item.getTitle()));
                resetStatistics();
                lastSpellTable = item;
                System.gc();
            } else {
                JOptionPane.showMessageDialog(this, MessageFormat.format(Translator.getString("error.empty_table_msg"), item.getTitle()), Translator.getString("error.empty_table_dt"), JOptionPane.ERROR_MESSAGE);
                if (lastSpellTable != null) {
                    lstActiveSpellTable.setSelectedItem(lastSpellTable);
                }
            }
        } catch (FileNotFoundException ex) {
            Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
            JOptionPane.showMessageDialog(this, MessageFormat.format(Translator.getString("error.table_fnf_msg"), item.getTitle(), item.getItemData()), Translator.getString("error.table_fnf_dt"), JOptionPane.ERROR_MESSAGE);
            if (lastSpellTable != null) {
                lstActiveSpellTable.setSelectedItem(lastSpellTable);
            }
            return false;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ex) {
                    Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException ex) {
                    Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            return true;
        }
    }

    private void setQuestion() {
        //swap arrays if had test all keys
        if (keysToChooseFrom.isEmpty()) {
            List<String> tmp_arr = keysToChooseFrom;
            keysToChooseFrom = usedKeys;
            usedKeys = tmp_arr;
            table_rotations++;
        }
        //Get some random key from keysToChooseFRom
        String key = keysToChooseFrom.get(randomGenerator.nextInt(keysToChooseFrom.size()));
        lblCurrentChar.setText(key);
    }

    private void checkAnswer() {
        //Check if chck button is enabled. If it is not, answer is currently beeing checked
        if (cmdCheck.isEnabled()) {
            enableCheckButton(false);
            if (txtSpelledCharacter.getText().isEmpty()) {
                JOptionPane.showMessageDialog(this, MessageFormat.format(Translator.getString("msg.type_answer"), lblCurrentChar.getText()));
                enableCheckButton(true);
                return;
            }
            String correct_answer = activeSpellTable.getProperty(lblCurrentChar.getText());
            answerCorrect = txtSpelledCharacter.getText().trim().toLowerCase().equals(correct_answer.toLowerCase());
            if (answerCorrect) {
                lblCheckResult.setText(Translator.getString("answer.correct"));
                lblCheckResult.setBackground(lblCorrectAnswerHighlight.getBackground());
                lblCheckResult.setForeground(lblCorrectAnswerHighlight.getForeground());
                txtSpelledCharacter.setBackground(lblCorrectAnswerHighlight.getBackground());
                txtSpelledCharacter.setForeground(lblCorrectAnswerHighlight.getForeground());
                //remove correct answer from list of available keys to ask
                keysToChooseFrom.remove(lblCurrentChar.getText());
                //and add it to usedKeys
                usedKeys.add(lblCurrentChar.getText());
                correct_answers++;
            } else {
                lblCheckResult.setText(MessageFormat.format(Translator.getString("answer.incorrect"), lblCurrentChar.getText(), correct_answer));
                lblCheckResult.setBackground(lblIncorrectAnswerHighlight.getBackground());
                lblCheckResult.setForeground(lblIncorrectAnswerHighlight.getForeground());
                txtSpelledCharacter.setBackground(lblIncorrectAnswerHighlight.getBackground());
                txtSpelledCharacter.setForeground(lblIncorrectAnswerHighlight.getForeground());
                incorrect_answers++;
            }
            answerResultTimer.restart();
        }
    }

    private void selectLanguage(String lang_key) {
        Map<String, LanguageFile> languages = Translator.getLanguages();
        LanguageFile lf = languages.get(lang_key);
        Locale lang_loc = lf.getLocale();
        ListItem<String> lang_item = new ListItem<>(lang_loc.getDisplayName(), lang_key);
        lstLanguage.setSelectedItem(lang_item);
    }

    private void resetStatistics() {
        correct_answers = 0;
        incorrect_answers = 0;
        table_rotations = 0;
    }

    private void updateStatistics() {
        float percent = 1.0f / (correct_answers + incorrect_answers) * correct_answers;
        lblStatistics.setText(MessageFormat.format(Translator.getString("statistics"), correct_answers, incorrect_answers, percent, table_rotations));
    }

    private void close() {
        saveSettings();
        this.dispose();
        System.exit(0);
    }

    private void updateSettings(byte direction) {
        if (direction == UPDATE_FORM_TO_VARIABLE) {
            if (lstLanguage.getItemCount() > 0) {
                ListItem item = (ListItem) lstLanguage.getSelectedItem();
                String lang_key = (String) item.getItemData();
                appSettings.setProperty("language", lang_key);
            }

            int iValue = ((SpinnerNumberModel) (sprHighlightTime.getModel())).getNumber().intValue();
            appSettings.setProperty("highlight.time", String.valueOf(iValue));

            //barvy radku tabulky
            iValue = lblCorrectAnswerHighlight.getBackground().getRGB();
            appSettings.setProperty("correct.highlight.background", String.valueOf(iValue));
            iValue = lblIncorrectAnswerHighlight.getBackground().getRGB();
            appSettings.setProperty("incorrect.highlight.background", String.valueOf(iValue));

            iValue = lblCorrectAnswerHighlight.getForeground().getRGB();
            appSettings.setProperty("correct.highlight.foreground", String.valueOf(iValue));
            iValue = lblIncorrectAnswerHighlight.getForeground().getRGB();
            appSettings.setProperty("incorrect.highlight.foreground", String.valueOf(iValue));

            iValue = (chkShowWarningBeforeSTDelete.isSelected()) ? 1 : 0;
            appSettings.setProperty("editor.tbl.delete.warning", String.valueOf(iValue));
        } else if (direction == UPDATE_VARIABLE_TO_FORM) {
            if (lstLanguage.getItemCount() > 0) {
                String sValue = appSettings.getProperty("language").trim();
                selectLanguage(sValue);
            }

            int iValue = Integer.valueOf(appSettings.getProperty("highlight.time")).intValue();
            if (((SpinnerNumberModel) sprHighlightTime.getModel()).getMinimum().compareTo(iValue) <= 0
                    && ((SpinnerNumberModel) sprHighlightTime.getModel()).getMaximum().compareTo(iValue) >= 0) {
                sprHighlightTime.setValue(iValue);
            }

            //barvy radku
            iValue = Integer.valueOf(appSettings.getProperty("correct.highlight.background")).intValue();
            lblCorrectAnswerHighlight.setBackground(new Color(iValue));
            iValue = Integer.valueOf(appSettings.getProperty("incorrect.highlight.background")).intValue();
            lblIncorrectAnswerHighlight.setBackground(new Color(iValue));

            iValue = Integer.valueOf(appSettings.getProperty("correct.highlight.foreground")).intValue();
            lblCorrectAnswerHighlight.setForeground(new Color(iValue));
            iValue = Integer.valueOf(appSettings.getProperty("incorrect.highlight.foreground")).intValue();
            lblIncorrectAnswerHighlight.setForeground(new Color(iValue));

            iValue = Integer.valueOf(appSettings.getProperty("editor.tbl.delete.warning")).intValue();
            chkShowWarningBeforeSTDelete.setSelected((iValue == 1) ? true : false);
        }
    }

    /**
     * This function saves settings to file.
     */
    private void saveSettings() {
        FileOutputStream fos = null;
        OutputStreamWriter writer = null;
        try {
            updateSettings(UPDATE_FORM_TO_VARIABLE);
            //check if we can write to the file
            File f = new File(SETTINGS_FILE);
            if (!f.canWrite()) {
                f.setWritable(true);
            }
            fos = new FileOutputStream(f);
            writer = new OutputStreamWriter(fos, Charset.forName("UTF-8"));
            appSettings.store(writer, "Spell Table Trainer Settings");
            //make it read only after saving
            f.setReadOnly();
        } catch (IOException ioe) {
            Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ioe);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException ex) {
                    Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException ex) {
                    Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    private void setDefaultLanguage() {
        Map<String, LanguageFile> languages = Translator.getLanguages();
        if (!(languages.size() > 0 && lstLanguage.getItemCount() > 0)) {
            return;
        }

        //get default locale
        Locale loc = Locale.getDefault();
        String english_key = "";
        String local_key = "";

        //now we need to cycle thought loaded languages and look if we had match
        for (Iterator<String> it = languages.keySet().iterator(); it.hasNext();) {
            String key = it.next();
            //read the data for that ley
            LanguageFile lf = languages.get(key);
            if (loc.equals(lf.getLocale())) {
                local_key = key;
                break;
            }
            if (loc.equals(new Locale("en", "US"))) {
                english_key = key;
            }
        }

        if (!local_key.isEmpty()) {
            selectLanguage(local_key);
        } else if (!english_key.isEmpty()) {
            selectLanguage(english_key);
        } else {
            lstLanguage.setSelectedIndex(0);
        }
    }

    private void loadSelectedLanguage() {
        ListItem lang_item = (ListItem) lstLanguage.getSelectedItem();

        //load the file
        if (lang_item != null) {
            Translator.setLanguage((String) lang_item.getItemData());
        }
        //com.rh.spell.table.trainer.resources
        Translator.setBundleName("com.mouseviator.spelling.alphabet.trainer.resources.Translation");
        //Translator.setBundleName(this.getClass().getPackage().getName() + ".Translation");

        //now we can change all text

        cmdCheck.setText(Translator.getString("cmdCheck.text"));
        cmdCheck.setToolTipText(Translator.getString("cmdCheck.tooltip"));
        cmdClose.setText(Translator.getString("cmdClose.text"));
        cmdClose.setToolTipText(Translator.getString("cmdClose.tooltip"));
        cmdSettingsOk.setText(Translator.getString("cmdSettingsOk.text"));
        frmSettings.setTitle(Translator.getString("frmSettings.title"));
        lblActiveSpellTable.setText(Translator.getString("lblActiveSpellTable.text"));
        lstActiveSpellTable.setToolTipText(Translator.getString("lstActiveSpellTable.tooltip"));
        txtSpelledCharacter.setToolTipText(Translator.getString("txtSpelledCharacter.tooltip"));
        lblCheckResult.setText(Translator.getString("lblCheckResult.text"));
        lblCorrectAnswerHighlight.setText(Translator.getString("lblCorrectAnswerHighlight.text"));
        lblCorrectAnswerHighlight.setToolTipText(Translator.getString("lblCorrectAnswerHighlight.tooltip"));
        lblCorrectAnswerHighlightDesc.setText(Translator.getString("lblCorrectAnswerHighlightDesc.text"));
        lblCurrentCharDesc.setText(Translator.getString("lblCurrentCharDesc.text"));
        lblHighlightTime.setText(Translator.getString("lblHighlightTime.text"));
        lblIncorrectAnswerHighlight.setText(Translator.getString("lblIncorrectAnswerHighlight.text"));
        lblIncorrectAnswerHighlight.setToolTipText(Translator.getString("lblIncorrectAnswerHighlight.tooltip"));
        lblIncorrectAnswerHighlightDesc.setText(Translator.getString("lblIncorrectAnswerHighlightDesc.text"));
        lblLanguage.setText(Translator.getString("lblLanguage.text"));
        lblStatistics.setText(Translator.getString("lblStatistics.text"));
        mainMenu.setText(Translator.getString("mainMenu.text"));
        menuClose.setText(Translator.getString("menuClose.text"));
        menuSettings.setText(Translator.getString("menuSettings.text"));
        menuTableEditor.setText(Translator.getString("menuTableEditor.text"));
        ((TitledBorder) pnlTableSelection.getBorder()).setTitle(Translator.getString("pnlTableSelection.title"));
        ((TitledBorder) pnlTableEdit.getBorder()).setTitle(Translator.getString("pnlTableEdit.title"));
        lblTESpellTable.setText(Translator.getString("lblTESpellTable.text"));
        cmdTELoad.setText(Translator.getString("cmdTELoad.text"));
        cmdTEAddRow.setText(Translator.getString("cmdTEAddRow.text"));
        cmdTEDeleteRow.setText(Translator.getString("cmdTEDeleteRow.text"));
        cmdTESave.setText(Translator.getString("cmdTESave.text"));
        cmdTEClose.setText(Translator.getString("cmdTEClose.text"));
        cmdTENew.setText(Translator.getString("cmdTENew.text"));
        cmdTEDelete.setText(Translator.getString("cmdTEDelete.text"));
        frmTableEditor.setTitle(Translator.getString("frmTableEditor.title"));
        int column_index = tableColumnIndexToViewIndex(tblSpellTable, 0);
        tblSpellTable.getColumnModel().getColumn(column_index).setHeaderValue(Translator.getString("table_editor.column0"));
        column_index = tableColumnIndexToViewIndex(tblSpellTable, 1);
        tblSpellTable.getColumnModel().getColumn(column_index).setHeaderValue(Translator.getString("table_editor.column1"));
        sprHighlightTime.setToolTipText(Translator.getString("sprHighlightTime.tooltip"));
        lblAuthorDesc.setText(Translator.getString("lblAuthorDesc.text"));
        lblVersionDesc.setText(Translator.getString("lblVersionDesc.text"));
        lblWebsiteDesc.setText(Translator.getString("lblWebsiteDesc.text"));
        lblWebsite.setText(Translator.getString("lblWebsite.text"));
        cmdAboutOk.setText(Translator.getString("cmdAboutOk.text"));
        helpMenu.setText(Translator.getString("helpMenu.text"));
        menuAbout.setText(Translator.getString("menuAbout.text"));
        menuLicense.setText(Translator.getString("menuLicense.text"));
        menuHelp.setText(Translator.getString("menuHelp.text"));
        lblAppTitle.setText(Translator.getString("app.title"));
        chkShowWarningBeforeSTDelete.setText(Translator.getString("chkShowWarningBeforeSTDelete.text"));
        lblNotesDesc.setText(Translator.getString("lblNotesDesc.text"));
        lblNotes.setText(Translator.getString("lblNotes.text"));
        lblLicensed.setText(Translator.getString("lblLicensed.text"));

        ListItem item = (ListItem) lstActiveSpellTable.getSelectedItem();
        if (item != null) {
            this.setTitle(MessageFormat.format(Translator.getString("app.stl.title"), Translator.getString("app.title"), item.getTitle()));
        }
    }

    private void loadSpellTableIntoEditor() {
        SpellTableModel model = (SpellTableModel) tblSpellTable.getModel();
        ListItem item = (ListItem) lstTESpellTable.getSelectedItem();
        model.loadPropertiesFile((String) item.getItemData());
    }

    private void enableControls(boolean bEnable) {
        boolean hasItems = ((SpellTableModel) tblSpellTable.getModel()).getRowCount() > 0;
        boolean isTables = lstTESpellTable.getItemCount() > 0;
        cmdTEAddRow.setEnabled(bEnable && isTables);
        cmdTEDeleteRow.setEnabled(bEnable && hasItems && isTables);
        lstTESpellTable.setEnabled(bEnable && isTables);
        cmdTELoad.setEnabled(bEnable && isTables);
        cmdTESave.setEnabled(bEnable && hasItems && isTables);
        cmdTEClose.setEnabled(bEnable);
        cmdTENew.setEnabled(bEnable);
        cmdTEDelete.setEnabled(bEnable && isTables);

        //main window
        lstActiveSpellTable.setEnabled(bEnable && isTables);
        txtSpelledCharacter.setEnabled(bEnable && isTables);
        enableCheckButton(bEnable && isTables);
    }
    
    private void enableCheckButton(boolean enable) {
        boolean isEmpty = ("".equals(txtSpelledCharacter.getText().trim()));
        cmdCheck.setEnabled(enable && !isEmpty);
    }

    private int tableColumnIndexToViewIndex(JTable table, int mColIndex) {
        for (int c = 0; c < table.getColumnCount(); c++) {
            TableColumn col = table.getColumnModel().getColumn(c);
            if (col.getModelIndex() == mColIndex) {
                return c;
            }
        }
        return -1;
    }

    private boolean openFile(File file) {
        return openFile(file, false);
    }

    /**
     * This function will open specified file with system default program. If
     * second parameter is true, it will ask user if he wants to open the folder
     * where the file should be, if the file does not exists.
     *
     * @param file File to open
     * @param askOpenContainingFolder Set true an the function will ask to open
     * folder that contains the file if that does not exists
     * @return True if no error was raised, otherwise false. Errors are reported
     * in log and message box are displayed.
     */
    private boolean openFile(File file, boolean askOpenContainingFolder) {
        boolean bRet = false;
        boolean bDesktopSupported = true;
        Desktop desktop = null;

        if (file == null) {
            return bRet;
        }

        //check if the file exists
        if (!file.exists()) {
            //get the name of containing folder
            String file_name = file.getPath();
            String folder_name = file.getAbsolutePath();
            int last_pos = folder_name.lastIndexOf(File.separator);
            if (last_pos != -1) {
                folder_name = folder_name.substring(0, last_pos);
            }

            //check if folder name exists
            file = new File(folder_name);
            if (file.exists() && askOpenContainingFolder) {
                //file does not exists, but the containing folder does, ask if user wants to open that folder
                int result = JOptionPane.showConfirmDialog(this, MessageFormat.format(Translator.getString("error.file_not_exist_folder_open_msg"), file_name),
                        Translator.getString("error.file_not_exist_folder_open_dt"), JOptionPane.YES_NO_OPTION);
                if (result == JOptionPane.NO_OPTION) {
                    return bRet;
                }
            } else {
                //neither the file and containing folder exists
                JOptionPane.showMessageDialog(this, MessageFormat.format(Translator.getString("error.file_not_exist_msg"), file_name),
                        Translator.getString("error.file_not_exist_dt"), JOptionPane.ERROR_MESSAGE);
                return bRet;
            }
        }

        //check if desktop api and open action are supported
        if (bDesktopSupported = Desktop.isDesktopSupported()) {
            desktop = Desktop.getDesktop();
            bDesktopSupported = desktop.isSupported(Desktop.Action.OPEN);
        }

        //if they are, try to open the file
        if (bDesktopSupported) {
            try {
                desktop.open(file);
                bRet = true;
            } catch (Exception ex) {
                Logger.getLogger(SpellingAlphabetTrainer.class.getName()).log(Level.SEVERE, "Failed top open folder/file: " + file.getPath() + " using Desktop class.", ex);
                JOptionPane.showMessageDialog(this, MessageFormat.format(Translator.getString("error.file_open_failed_msg"), file.getPath()),
                        Translator.getString("error.file_open_failed_dt"), JOptionPane.ERROR_MESSAGE);
            }
        } else {
            JOptionPane.showMessageDialog(this, MessageFormat.format(Translator.getString("error.desktop_api_notsupported"), file.getPath()),
                    Translator.getString("error.desktop_api_notsupported_dt"), JOptionPane.ERROR_MESSAGE);
            return bRet;
        }

        return bRet;
    }

    /**
     * This function will construct localized file name for given file name
     * based on key of selected language. It will then check if the new file
     * exists and return it, if it does. If it does not, null is returned, or
     * original file is returned (if ret_original is true).
     *
     * @param file_name File name to get localized name for.
     * @param lang_key language key for selected language. This is used to get
     * locale info.
     * @param ret_original If true and localized file does not exists, function
     * will return original file instead of null.
     * @return Localized file, original file or null.
     */
    private File getLocalizedFile(String file_name, String lang_key, boolean ret_original) {
        File file = null;

        //Try to get parts of language key to extract locale information, which we will use
        //to construct new file name
        final Pattern loc_pattern = Pattern.compile(LANG_KEY_PATTERN, Pattern.CASE_INSENSITIVE);
        Matcher loc_matcher = loc_pattern.matcher(lang_key);
        if (loc_matcher.matches()) {
            final String locale = loc_matcher.group(2) + loc_matcher.group(3) + loc_matcher.group(4) + loc_matcher.group(5);

            //Construct localized file name
            final Pattern fn_pattern = Pattern.compile(FILE_NAME_PATTERN, Pattern.CASE_INSENSITIVE);
            Matcher fn_matcher = fn_pattern.matcher(file_name);
            if (fn_matcher.matches()) {
                String new_file_name = fn_matcher.group(1) + locale + fn_matcher.group(2) + fn_matcher.group(3);
                File new_file = new File(new_file_name);
                if (new_file.exists()) {
                    file = new_file;
                }
            }
        }

        //failed to construct localized file name, set result to original file if desired
        if (file == null && ret_original) {
            file = new File(file_name);
        }

        return file;
    }
}
