com.thetruthbeyond.botmaker.gui.tabs.TeachTab.java Source code

Java tutorial

Introduction

Here is the source code for com.thetruthbeyond.botmaker.gui.tabs.TeachTab.java

Source

/*
 * Copyleft (C) 2015 Piotr Siatkowski find me on Facebook;
 * This file is part of BotMaker. BotMaker is free software; you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version. BotMaker 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 BotMaker (look at the
 * Documents directory); if not, either write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA, or visit
 * (http://www.gnu.org/licenses/gpl.txt).
 */

/*
 * BotMaker - file created and updated by Piotr Siatkowski (2015).
 */

package com.thetruthbeyond.botmaker.gui.tabs;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.Set;

import com.thetruthbeyond.chatterbean.utility.annotations.UnhandledMethod;
import com.thetruthbeyond.chatterbean.AliceBot;
import com.thetruthbeyond.chatterbean.Context;
import com.thetruthbeyond.chatterbean.aiml.*;
import com.thetruthbeyond.chatterbean.graph.Graphmaster;
import com.thetruthbeyond.chatterbean.parser.api.AliceBotExplorerException;
import com.thetruthbeyond.chatterbean.text.structures.Sentence;
import com.thetruthbeyond.chatterbean.text.structures.Transformations;

import com.thetruthbeyond.chatterbean.utility.logging.Logger;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

import com.thetruthbeyond.bot.CurrentBot;
import com.thetruthbeyond.botmaker.BotMaker;
import com.thetruthbeyond.botmaker.gui.objects.buttons.TabButton;
import com.thetruthbeyond.gui.action.EventEmitter;
import com.thetruthbeyond.gui.action.emitters.*;
import com.thetruthbeyond.gui.configuration.Coding;
import com.thetruthbeyond.gui.configuration.Consts;
import com.thetruthbeyond.gui.enums.FontType;
import com.thetruthbeyond.gui.enums.ValidationFailureCause;
import com.thetruthbeyond.gui.interfaces.AIMLGenerator;
import com.thetruthbeyond.gui.interfaces.AIMLGeneratorArgument;
import com.thetruthbeyond.gui.interfaces.FileManager;
import com.thetruthbeyond.gui.objects.Clickable;
import com.thetruthbeyond.gui.objects.buttons.Button;
import com.thetruthbeyond.gui.objects.buttons.simple.Submit;
import com.thetruthbeyond.botmaker.gui.objects.buttons.tabs.*;
import com.thetruthbeyond.gui.objects.controllers.scrollpanel.ScrollPanel;
import com.thetruthbeyond.gui.objects.controllers.scrollpanel.ScrollPanelConfiguration;
import com.thetruthbeyond.gui.objects.controllers.textfield.TextField;
import com.thetruthbeyond.gui.objects.controllers.textfield.TextFieldConfiguration;
import com.thetruthbeyond.gui.objects.controllers.textfield.decorators.*;
import com.thetruthbeyond.gui.objects.tabs.Tab;
import com.thetruthbeyond.gui.objects.tabs.overtabs.wildcards.FullWildcardsTab;
import com.thetruthbeyond.gui.objects.tabs.overtabs.OperationTab;
import com.thetruthbeyond.gui.objects.tabs.overtabs.wildcards.StarWildcardsTab;
import com.thetruthbeyond.gui.structures.Area;
import com.thetruthbeyond.gui.structures.Position;
import com.thetruthbeyond.gui.structures.wildcard.*;
import com.thetruthbeyond.gui.utility.drawing.SmartSpriteBatch;
import com.thetruthbeyond.gui.utility.drawing.SmartTexture;

import com.thetruthbeyond.botmaker.gui.tabs.teachtab.*;
import com.thetruthbeyond.botmaker.logic.BotExplorer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import static com.thetruthbeyond.gui.configuration.Consts.RESOLUTION;
import static com.thetruthbeyond.gui.configuration.Consts.RES_1366x768;

@SuppressWarnings("FieldCanBeLocal")
public class TeachTab extends Tab implements AIMLGenerator {

    // Top panel. ///////////////////////////////////////////////////////////////////
    private final ScrollPanel topPanel;

    private static final float TOP_PANEL_X_PARAMETER = 0.0400f;
    private static final float TOP_PANEL_Y_PARAMETER = 0.0800f;
    private static final float TOP_PANEL_W_PARAMETER = RESOLUTION == RES_1366x768 ? 0.921f : 0.920f;
    private static final float TOP_PANEL_H_PARAMETER = 0.2630f;
    private static final float TOP_PANEL_P_PARAMETER = 0.0000f;
    private static final float TOP_PANEL_GAP_H_PARAMETER = 0.0500f;
    private static final float TOP_PANEL_GAP_W_PARAMETER = 0.0105f;

    private static final int TOP_PANEL_COLUMNS = 1;
    private static final int TOP_PANEL_ROWS = 2;
    /////////////////////////////////////////////////////////////////////////////////

    // Question text field. /////////////////////////////////////////////////////////
    private final WildcardTextField questionWTF;
    private final AutofillTextField questionATF;
    private final DefaultMessageTextField questionDMTF;
    /////////////////////////////////////////////////////////////////////////////////

    // My answer text field. ////////////////////////////////////////////////////////
    private final ContextMenuTextField answerCMTF;
    private final AutofillTextField answerATF;
    private final DefaultMessageTextField answerDMTF;
    private final OperationTextField answerOTF;
    /////////////////////////////////////////////////////////////////////////////////

    // Bottom panel. ////////////////////////////////////////////////////////////////
    private final ScrollPanel botPanel;

    private static final float PANEL_X_PARAMETER = 0.04f;
    private static final float PANEL_Y_PARAMETER = 0.46f;
    private static final float PANEL_W_PARAMETER = RESOLUTION == RES_1366x768 ? 0.921f : 0.920f;
    private static final float PANEL_H_PARAMETER = 0.36f;
    /////////////////////////////////////////////////////////////////////////////////

    // Buttons. /////////////////////////////////////////////////////////////////////
    private final List<TabButton> buttons = new ArrayList<>(6);

    private static final float BUTTONS_X_PARAMETER = 0.045f;
    private static final float BUTTONS_Y_PARAMETER = 0.36f;
    private static final float BUTTONS_PRESSED_Y_PARAMETER = 0.38f;
    private static final float PRESS_TIME = 0.2f;

    private static final int BUTTON_X_OFFSET = -3;

    private final int BUTTON_START_Y;
    private final int BUTTON_DIFF_Y;

    private final float[] elapsedPressTime;
    /////////////////////////////////////////////////////////////////////////////////

    // Tabs. ////////////////////////////////////////////////////////////////////////
    private final Map<Class<? extends TeachSubTab>, TeachSubTab> tabs = new HashMap<>();
    private final Map<Class<? extends AIMLGenerator>, AIMLGenerator> generators = new HashMap<>();

    private TeachSubTab currentTab;

    private int currentTabIndex = 0;
    /////////////////////////////////////////////////////////////////////////////////

    // Topic field. /////////////////////////////////////////////////////////////////
    private final AutofillTextField topicATF;
    private final DefaultMessageTextField topicDMTF;
    private final Set<String> topics = new TreeSet<>();

    private static final float TOPIC_TEXT_FIELD_X_PARAMETER = 0.560f;
    private static final float TOPIC_TEXT_FIELD_Y_PARAMETER = 0.820f;
    private static final float TOPIC_TEXT_FIELD_W_PARAMETER = RESOLUTION == RES_1366x768 ? 0.401f : 0.400f;
    private static final float TOPIC_TEXT_FIELD_H_PARAMETER = 0.100f;
    /////////////////////////////////////////////////////////////////////////////////

    // Submit. //////////////////////////////////////////////////////////////////////
    private final Button submit;

    private static final float SUBMIT_X_PARAMETER = 0.040f;
    private static final float SUBMIT_Y_PARAMETER = 0.823f;
    /////////////////////////////////////////////////////////////////////////////////

    // ContextTab menu. /////////////////////////////////////////////////////////////
    private final StarWildcardsTab starcardsTab;

    private static final float VARIABLE_PANEL_X_PARAMETER = 0.00f;
    private static final float VARIABLE_PANEL_Y_PARAMETER = 0.00f;
    private static final float VARIABLE_PANEL_W_PARAMETER = 0.34f;
    private static final float VARIABLE_PANEL_H_PARAMETER = 0.18f;
    /////////////////////////////////////////////////////////////////////////////////

    // ContextTab menu. /////////////////////////////////////////////////////////////
    private final FullWildcardsTab wildcardsTab;

    private static final float CONTEXT_PANEL_X_PARAMETER = 0.00f;
    private static final float CONTEXT_PANEL_Y_PARAMETER = 0.00f;
    private static final float CONTEXT_PANEL_W_PARAMETER = 0.34f;
    private static final float CONTEXT_PANEL_H_PARAMETER = 0.54f;
    /////////////////////////////////////////////////////////////////////////////////

    // OperationTab. ////////////////////////////////////////////////////////////////
    private final OperationTab operationTab;

    private static final float OPERATION_TAB_X_PARAMETER = 0.20f;
    private static final float OPERATION_TAB_Y_PARAMETER = 0.20f;
    private static final float OPERATION_TAB_W_PARAMETER = 0.60f;
    private static final float OPERATION_TAB_H_PARAMETER = 0.60f;
    /////////////////////////////////////////////////////////////////////////////////

    // Emitters. ////////////////////////////////////////////////////////////////////
    private final OnTabSwitch onSwitch;
    private final OnValidateFailure onValidationFailure;
    /////////////////////////////////////////////////////////////////////////////////

    private AliceBot bot;

    private final SmartTexture background;
    private final TextureRegion mask;

    private BotExplorer explorer;

    public TeachTab(Area area, FileManager loader, Clickable parent) {
        super(area, parent);

        background = loader.getTexture("WoodBackground2");

        onSwitch = new OnTabSwitch(this);
        addEmitter(onSwitch);

        onValidationFailure = new OnValidateFailure(this);
        addEmitter(onValidationFailure);

        Area contextArea = new Area();
        contextArea.setX(area.getX() + area.getW() * CONTEXT_PANEL_X_PARAMETER);
        contextArea.setY(area.getY() + area.getH() * CONTEXT_PANEL_Y_PARAMETER);
        contextArea.setW(area.getW() * CONTEXT_PANEL_W_PARAMETER);
        contextArea.setH(area.getH() * CONTEXT_PANEL_H_PARAMETER);

        wildcardsTab = new FullWildcardsTab(contextArea, loader, BotMaker.darkness, this);

        contextArea.setX(area.getX() + area.getW() * VARIABLE_PANEL_X_PARAMETER);
        contextArea.setY(area.getY() + area.getH() * VARIABLE_PANEL_Y_PARAMETER);
        contextArea.setW(area.getW() * VARIABLE_PANEL_W_PARAMETER);
        contextArea.setH(area.getH() * VARIABLE_PANEL_H_PARAMETER);

        starcardsTab = new StarWildcardsTab(contextArea, loader, BotMaker.darkness, this);

        ScrollPanelConfiguration topPanelConfiguration = configureTopPanel();
        topPanel = new ScrollPanel(topPanelConfiguration, loader, this);

        // Question text field initialization. //////////////////////////////////////////////////////
        TextField questionNTF = topPanel.addObjectImmediately(TextField.class, configureTextField());

        questionATF = topPanel.substituteObject(questionNTF, new AutofillTextField(questionNTF));
        questionWTF = topPanel.substituteObject(questionATF, new WildcardTextField(questionATF));
        questionDMTF = topPanel.substituteObject(questionWTF, new DefaultMessageTextField(questionWTF));
        questionDMTF.setDefaultMessage("Type here user question.");

        PromptTextFieldConfiguration configurationPTFquestion = new PromptTextFieldConfiguration();
        configurationPTFquestion.alpha = 0.4f;
        configurationPTFquestion.blinktime = 0.8f;
        configurationPTFquestion.promptoffset = 0;
        TextField questionPTF = topPanel.substituteObject(questionDMTF,
                new PromptTextField(questionDMTF, loader, configurationPTFquestion));

        OneSentenceTextField questionOSTF = new OneSentenceTextField(questionPTF);

        Set<String> splitters = obtainSplitters();
        questionOSTF.setSentenceSplitters(splitters);

        getObserver().observeEmitter(questionOSTF.getEmitter(OnValidateFailure.Id));
        topPanel.substituteObject(questionPTF, questionOSTF);
        //////////////////////////////////////////////////////////////////////////////////////////////

        // Answer text field initialization. /////////////////////////////////////////////////////////
        TextField answerNTF = topPanel.addObjectImmediately(TextField.class, configureTextField());

        answerATF = topPanel.substituteObject(answerNTF, new AutofillTextField(answerNTF));
        answerCMTF = topPanel.substituteObject(answerATF, new ContextMenuTextField(wildcardsTab, answerATF));
        answerDMTF = topPanel.substituteObject(answerCMTF, new DefaultMessageTextField(answerCMTF));
        answerDMTF.setDefaultMessage("Type here chatbot's answer.");

        Area operationTabArea = new Area();
        operationTabArea.setX(Math.round(getX() + getW() * OPERATION_TAB_X_PARAMETER));
        operationTabArea.setY(Math.round(getY() + getH() * OPERATION_TAB_Y_PARAMETER));
        operationTabArea.setW(Math.round(getW() * OPERATION_TAB_W_PARAMETER));
        operationTabArea.setH(Math.round(getH() * OPERATION_TAB_H_PARAMETER));

        operationTab = new OperationTab(operationTabArea, loader, this);

        answerOTF = topPanel.substituteObject(answerDMTF, new OperationTextField(operationTab, loader, answerDMTF));

        PromptTextFieldConfiguration configurationPTFanswer = new PromptTextFieldConfiguration();
        configurationPTFanswer.alpha = 0.4f;
        configurationPTFanswer.blinktime = 0.8f;
        configurationPTFanswer.promptoffset = 0;
        answerNTF = topPanel.substituteObject(answerOTF,
                new PromptTextField(answerOTF, loader, configurationPTFanswer));

        getObserver().observeEmitter(answerNTF.getEmitter(OnValidateFailure.Id));
        //////////////////////////////////////////////////////////////////////////////////////////////

        ScrollPanelConfiguration botPanelConfiguration = configureBotPanel();
        botPanel = new ScrollPanel(botPanelConfiguration, loader, this);

        TextFieldConfiguration topicConfiguration = configureTopicTextField();
        TextField topicInput = new TextField(topicConfiguration, loader, this);

        topicInput = new CapitalCaseTextField(topicInput);
        topicATF = new AutofillTextField(topicInput);
        topicDMTF = new DefaultMessageTextField(topicATF).setDefaultMessage("Type topic for that rule.");

        buttons.add(new ContexButton(loader, this));
        buttons.add(new VariabButton(loader, this));
        buttons.add(new ClauseButton(loader, this));
        buttons.add(new RandomButton(loader, this));
        buttons.add(new RecursButton(loader, this));

        elapsedPressTime = new float[buttons.size()];

        int buttonX = (int) (area.getX() + area.getW() * BUTTONS_X_PARAMETER);
        int buttonY = (int) (area.getY() + area.getH() * BUTTONS_Y_PARAMETER);

        BUTTON_START_Y = buttonY;
        BUTTON_DIFF_Y = (int) (area.getY() + area.getH() * BUTTONS_PRESSED_Y_PARAMETER) - BUTTON_START_Y;

        boolean isFirstButton = true;
        for (TabButton button : buttons) {
            if (isFirstButton) {
                Position position = new Position(buttonX + button.getW() / 2,
                        BUTTON_START_Y + button.getH() / 2 + BUTTON_DIFF_Y);
                button.setPosition(position);

                // Setting press action.
                button.setPressed();

                // Make button already hovered.
                button.update(10);

                elapsedPressTime[0] = PRESS_TIME;
                isFirstButton = false;
            } else
                button.setPosition(new Position(buttonX + button.getW() / 2, BUTTON_START_Y + button.getH() / 2));
            buttonX = buttonX + button.getW() + BUTTON_X_OFFSET;
        }

        submit = new Submit(loader, this);
        buttonX = (int) (area.getX() + area.getW() * SUBMIT_X_PARAMETER);
        buttonY = (int) (area.getY() + area.getH() * SUBMIT_Y_PARAMETER);
        submit.setPosition(new Position(buttonX + submit.getW() / 2, buttonY + submit.getH() / 2));

        mask = new TextureRegion(background.getTextureRegion(), (int) (area.getW() * PANEL_X_PARAMETER),
                (int) (area.getH() * PANEL_Y_PARAMETER), botPanel.getW(), botPanel.getH());

        Area tabArea = new Area(botPanel.getArea()).cutArea(Consts.BORDER_SIZE);

        tabs.put(ContexTab.class, new ContexTab(tabArea, loader, this, starcardsTab, operationTab));
        tabs.put(VariabTab.class, new VariabTab(tabArea, loader, this, wildcardsTab, operationTab));
        tabs.put(ClauseTab.class, new ClauseTab(tabArea, loader, this, wildcardsTab, operationTab));
        tabs.put(RandomTab.class, new RandomTab(tabArea, loader, this, wildcardsTab, operationTab));
        tabs.put(RecursTab.class, new RecursTab(tabArea, loader, this, wildcardsTab, operationTab));

        currentTab = tabs.get(ContexTab.class);

        generators.put(ContexTab.class, (AIMLGenerator) tabs.get(ContexTab.class));
        generators.put(ClauseTab.class, (AIMLGenerator) tabs.get(ClauseTab.class));
        generators.put(RandomTab.class, (AIMLGenerator) tabs.get(RandomTab.class));
        generators.put(RecursTab.class, (AIMLGenerator) tabs.get(RecursTab.class));
        generators.put(OperationTextField.class, answerOTF);
    }

    private ScrollPanelConfiguration configureTopPanel() {
        ScrollPanelConfiguration configuration = new ScrollPanelConfiguration();
        configuration.area.setX(area.getX() + area.getW() * TOP_PANEL_X_PARAMETER);
        configuration.area.setY(area.getY() + area.getH() * TOP_PANEL_Y_PARAMETER);
        configuration.area.setW(area.getW() * TOP_PANEL_W_PARAMETER);
        configuration.area.setH(area.getH() * TOP_PANEL_H_PARAMETER);

        configuration.border = Consts.BORDER_SIZE;
        configuration.backgroundAlpha = Consts.BACKGROUND_ALPHA;

        configuration.columnsNumber = TOP_PANEL_COLUMNS;
        configuration.rowsNumber = TOP_PANEL_ROWS;

        configuration.relativeGapW = TOP_PANEL_GAP_W_PARAMETER;
        configuration.relativeGapH = TOP_PANEL_GAP_H_PARAMETER;
        configuration.relativePadding = TOP_PANEL_P_PARAMETER;

        return configuration;
    }

    private TextFieldConfiguration configureTextField() {
        TextFieldConfiguration configuration = new TextFieldConfiguration();

        configuration.fontname = FontType.CHAT_FONT;
        configuration.fontcolor = Consts.MAIN_FONT_COLOR;
        configuration.characters = Coding.signs + "()[]";

        configuration.relativeTextPadding = Consts.RELATIVE_TEXT_PADDING;
        configuration.border = Consts.BORDER_SIZE;

        return configuration;
    }

    private ScrollPanelConfiguration configureBotPanel() {
        ScrollPanelConfiguration configuration = new ScrollPanelConfiguration();
        configuration.area.setX(area.getX() + area.getW() * PANEL_X_PARAMETER);
        configuration.area.setY(area.getY() + area.getH() * PANEL_Y_PARAMETER);
        configuration.area.setW(area.getW() * PANEL_W_PARAMETER);
        configuration.area.setH(area.getH() * PANEL_H_PARAMETER);

        configuration.border = Consts.BORDER_SIZE;
        configuration.backgroundAlpha = Consts.BACKGROUND_ALPHA;

        configuration.columnsNumber = 1;
        configuration.rowsNumber = 1;

        return configuration;
    }

    private TextFieldConfiguration configureTopicTextField() {
        TextFieldConfiguration configuration = new TextFieldConfiguration();
        configuration.area.setX(area.getX() + area.getW() * TOPIC_TEXT_FIELD_X_PARAMETER);
        configuration.area.setY(area.getY() + area.getH() * TOPIC_TEXT_FIELD_Y_PARAMETER);
        configuration.area.setW(area.getW() * TOPIC_TEXT_FIELD_W_PARAMETER);
        configuration.area.setW(
                Math.min(configuration.area.getW(), botPanel.getX() + botPanel.getW() - configuration.area.getX()));
        configuration.area.setH(area.getH() * TOPIC_TEXT_FIELD_H_PARAMETER);

        configuration.fontname = FontType.CHAT_FONT;
        configuration.fontcolor = new Color(0.4f, 0.5f, 0.2f, 1.0f);
        configuration.characters = Coding.signs;

        configuration.relativeTextPadding = Consts.RELATIVE_TEXT_PADDING;
        configuration.border = Consts.BORDER_SIZE;

        return configuration;
    }

    @Override
    public void onTabEnter() {

        bot = CurrentBot.instance;
        if (bot != null) {
            Context context = bot.getContext();
            explorer = new BotExplorer(context.getProperty("name"));

            setAnswerAutofillSet();

            try {
                List<File> aimlFiles = explorer.getAIMLFiles();

                topics.clear();

                for (File file : aimlFiles) {
                    String fullname = file.getName();
                    topics.add(fullname.substring(0, fullname.indexOf('.')));
                }

                topicATF.setAutofillSet(topics);
            } catch (AliceBotExplorerException exception) {
                BotMaker.messages.showMessage("Topic set cannot be established. " + exception.getMessage());
            }
        }
    }

    private Set<String> obtainSplitters() {

        Set<String> set = new HashSet<>();

        bot = CurrentBot.instance;
        if (bot != null) {
            Context context = bot.getContext();
            explorer = new BotExplorer(context.getProperty("name"));

            try {
                InputStream stream = explorer.getSplitters();

                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse(stream);

                NodeList list = document.getElementsByTagName("splitter");
                for (int i = 0, n = list.getLength(); i != n; ++i) {
                    Node node = list.item(i);
                    if (node instanceof Element) {
                        Element element = (Element) node;
                        if (element.getAttribute("type").equals("sentence"))
                            set.add(element.getAttribute("value"));
                    }
                }
            } catch (AliceBotExplorerException | ParserConfigurationException | IOException
                    | SAXException exception) {
                Logger logger = new Logger();
                logger.writeMessage("Warning", "Splitters file could not be properly parsed or read");
                logger.writeError(exception);

                BotMaker.messages.showMessage("Splitters file could not be properly parsed or read.");
            }
        }

        return set;
    }

    private void setAnswerAutofillSet() {

        bot = CurrentBot.instance;

        if (bot != null) {
            Context context = bot.getContext();
            explorer = new BotExplorer(context.getProperty("name"));

            Set<String> variables = new TreeSet<>();
            for (String variable : context.getPropertiesNames()) {
                if (variable.equals("id"))
                    continue;
                String bracketVariable = "[" + variable + "]";
                variables.add(bracketVariable);
                answerCMTF.markWildcard(new VariableCard(bracketVariable, Color.MAROON).setReadOnly(true));
            }

            for (String variable : context.getPredicatesNames()) {
                String bracketVariable = "[" + variable + "]";
                variables.add(bracketVariable);
                answerCMTF.markWildcard(new VariableCard(bracketVariable, Color.MAROON).setReadOnly(false));
            }

            answerCMTF.markRegularExpression("\\[.+\\]");

            answerATF.setAutofillSet(variables);
        }
    }

    @Override
    public void reactToEmittedSignal(EventEmitter object, int emitterID) {
        if (emitterID == OnValidateFailure.Id) {
            OnValidateFailure onValidateFailure = object.getEmitter(emitterID);
            if (onValidateFailure.getCause() == ValidationFailureCause.MoreThanOneSentence)
                BotMaker.messages.showMessage("You can enter only one valid sentence.");
            else

            if (onValidateFailure.getCause() == ValidationFailureCause.WildcardAlreadyPresent)
                BotMaker.messages.showMessage(
                        "There can be only one operation wildcard of any kind present in a text field.");
        } else

        if (emitterID == OnPress.Id) {
            Wildcard wildcard = wildcardsTab.getWildcard();

            // Validation.
            if (!answerCMTF.isFocused() && !currentTab.verifyWildcardAcceptance(wildcard)) {
                BotMaker.messages
                        .showMessage("Token " + wildcard.getName() + " cannot be added to an active input field.");
                onValidationFailure.signalValidationFailure(ValidationFailureCause.RecursionAttempt);
            }

            if (wildcard instanceof StarCard) {
                // Variable wildcard validation.
                StarCard starcard = (StarCard) wildcard;
                if (!questionWTF.isFocused() && !questionWTF.hasWildcard(starcard)) {
                    BotMaker.messages.showMessage(
                            "The variable wildcard must appear in the rule's pattern in the first place.");
                    onValidationFailure.signalValidationFailure(ValidationFailureCause.MissingWildcard);
                } else

                if (questionWTF.isFocused() && questionWTF.hasWildcard(starcard)) {
                    BotMaker.messages
                            .showMessage("You are restricted to only one variable wildcard of each color.");
                    onValidationFailure.signalValidationFailure(ValidationFailureCause.WildcardAlreadyPresent);
                } else
                    assignWildcard(starcard);
            }

            wildcardsTab.hide();
        }
    }

    private void assignWildcard(StarCard starcard) {
        // Assign index number to the new starcard.
        for (int i = 0, index = 1, n = questionWTF.getWords().size(); i != n; ++i) {
            Wildcard card = questionWTF.getWordAsWildcard(i);
            if (card != null) {
                if (card.equals(starcard)) {
                    starcard.setIndex(index);
                    break;
                } else

                if (card instanceof StarCard)
                    index++;
            }
        }
    }

    @Override
    @UnhandledMethod
    public boolean contains(Position position) {
        return false;
    }

    @Override
    public boolean executeMouseHover(Position position) {
        if (wildcardsTab.executeMouseHover(position))
            return false;
        if (starcardsTab.executeMouseHover(position))
            return false;

        if (operationTab.isVisible()) {
            operationTab.executeMouseHover(position);
        } else {
            answerOTF.executeMouseHover(position);

            if (!wildcardsTab.contains(position)) {
                currentTab.executeMouseHover(position);
                submit.executeMouseHover(position);
            }
        }

        return false;
    }

    @Override
    public boolean executeMouseDrag(Position position) {
        if (wildcardsTab.executeMouseDrag(position))
            return false;
        if (starcardsTab.executeMouseDrag(position))
            return false;

        if (operationTab.isVisible()) {
            operationTab.executeMouseDrag(position);
        } else {
            if (!wildcardsTab.contains(position)) {
                currentTab.executeMouseDrag(position);
                submit.executeMouseDrag(position);
            }
        }

        return false;
    }

    @Override
    public boolean executeMousePress(Position position) {

        if (wildcardsTab.executeMousePress(position))
            return false;

        if (starcardsTab.executeMousePress(position))
            return false;

        if (operationTab.isVisible()) {
            operationTab.executeMousePress(position);
        } else {
            boolean breakFocus = true;

            // Tab switching.
            for (int i = 0; i != buttons.size(); i++) {

                TabButton button = buttons.get(i);
                if (currentTabIndex != i && button.contains(position)) {
                    for (int j = 0; j != buttons.size(); j++)
                        buttons.get(j).executeMouseRelease(Position.NullPosition);

                    button.executeMousePress(position);
                    button.executeMouseHover(position);

                    if (currentTab.isUpdateNeeded()) {
                        currentTab.performUpdate();
                        if (currentTab instanceof VariabTab)
                            setAnswerAutofillSet();
                    }

                    currentTab.onTabLeave();

                    currentTab = tabs.get(button.getAssociatedTeachTabType());
                    currentTabIndex = i;

                    onSwitch.signalSwitch(button.getAssociatedTeachTabType());
                    currentTab.onTabEnter();

                    breakFocus = false;
                    break;
                }
            }

            // Tab press action.
            if (currentTab.executeMousePress(position)) {
                Wildcard wildcard = currentTab.getWildcard();
                if (wildcard != null) {
                    if (wildcard instanceof StarCard) {
                        // Variable wildcard validation.
                        StarCard starcard = (StarCard) wildcard;
                        if (answerCMTF.isFocused()) {
                            if (questionWTF.hasWildcard(starcard))
                                assignWildcard(starcard);
                            else
                                BotMaker.messages.showMessage(
                                        "The variable wildcard must appear in the rule's pattern in the first place.");
                        } else {
                            if (!questionWTF.hasWildcard(starcard))
                                questionWTF.addWildcard(starcard);
                            else
                                BotMaker.messages.showMessage(
                                        "You are restricted to only one variable wildcard of each color.");
                        }
                    } else
                        answerCMTF.addWildcard(wildcard);
                    breakFocus = false;
                }
            }

            if (breakFocus) {
                questionWTF.executeMousePress(position);

                if (answerOTF.executeMousePress(position)) {
                    if (currentTab.isUpdateNeeded()) {
                        currentTab.performUpdate();
                        if (currentTab instanceof VariabTab)
                            setAnswerAutofillSet();
                    }
                }
            }

            topicDMTF.executeMousePress(position);

            if (submit.contains(position)) {
                if (teachBot())
                    BotMaker.messages.showMessage("The rule was successfully added.");
                else
                    BotMaker.messages
                            .showMessage("Provide valid question and expected answer in order to proceed.");
            }
        }

        return true;
    }

    @Override
    public boolean executeMousePressRight(Position position) {
        if (operationTab.isVisible())
            operationTab.executeMousePressRight(position);
        else

        if (starcardsTab.isVisible())
            starcardsTab.executeMousePressRight(position);
        else

        if (wildcardsTab.isVisible())
            wildcardsTab.executeMousePressRight(position);
        else

            answerCMTF.executeMousePressRight(position);
        currentTab.executeMousePressRight(position);
        return false;
    }

    @Override
    public boolean executeMouseRelease(Position position) {
        if (operationTab.isVisible())
            operationTab.executeMouseRelease(position);
        else {
            topPanel.executeMouseRelease(position);

            currentTab.executeMouseRelease(position);

            topicDMTF.executeMouseRelease(position);
            submit.executeMouseRelease(position);
        }

        wildcardsTab.executeMouseRelease(position);
        starcardsTab.executeMouseRelease(position);
        return false;
    }

    private boolean teachBot() {
        // Creating category.
        Category category = new Category(makePattern(), makeTemplate());

        // Rule will not going to come into existence. Report this to the user.
        if (category.getPattern().isEmpty() || !category.getTemplate().hasChildren())
            return false;

        // Add new phrase to topic set if any were typed.
        String topicName = topicDMTF.getInput();
        if (!topics.contains(topicName)) {
            topics.add(topicName);
            topicATF.addAutofillElement(topicName);
        }

        Transformations transformations = bot.getTransformations();
        Graphmaster graph = bot.getGraphmaster();

        // Create that element and normalize it.
        List<AIMLElement> elements = generators.get(ContexTab.class).getAimlElements(null, null);
        for (AIMLElement element : elements)
            if (element instanceof NormalizedTag)
                ((NormalizedTag) element).normalizeContent(transformations);
        category.appendChildren(elements);

        // Create topic element and normalize it.
        Sentence topic = transformations.makeSentence(topicName);
        if (topicName.isEmpty())
            topic = Sentence.ASTERISK;
        category.appendChild(new Topic(topic.getNormalized()));

        // Check if category exists.
        boolean hasCategory = graph.hasCategory(category.getMatchPath());
        if (hasCategory) {
            graph.append(category);
            explorer.updateAIML(topicName, category);
        } else {
            graph.append(category);
            explorer.addToAIML(topicName, category);
        }

        return true;
    }

    private Pattern makePattern() {
        List<String> words = questionWTF.getWords();
        StringBuilder input = new StringBuilder(words.size() * 10);

        for (int index = 0; index != words.size(); index++) {
            Wildcard wildcard = questionWTF.getWordAsWildcard(index);
            if (wildcard != null) {
                if (wildcard instanceof StarCard) {
                    // Insert whitespace to make sure that wildcard will be properly added.
                    if (((StarCard) wildcard).isHighPriority())
                        input.append(" _ ");
                    else
                        input.append(" * ");
                }
            } else
                input.append(words.get(index));
        }

        Transformations transformations = bot.getTransformations();

        Sentence sentence = transformations.makeSentence(input.toString());
        return new Pattern(sentence.getNormalized().trim());
    }

    private Template makeTemplate() {
        List<String> words = answerCMTF.getWords();
        List<AIMLElement> elements = new LinkedList<>();

        StringBuilder freeText = new StringBuilder(words.size() * 5);
        for (int index = 0; index != words.size(); index++) {

            Wildcard wildcard = answerCMTF.getWordAsWildcard(index);
            if (wildcard != null) {

                // Adding free text.
                if (freeText.length() != 0) {
                    elements.add(new Text(freeText.toString()));
                    freeText.delete(0, freeText.length());
                }

                wildcard.fillTemplate(elements, this);
            } else
                freeText.append(words.get(index));
        }

        // Adding free text.
        if (freeText.length() != 0)
            elements.add(new Text(freeText.toString().trim()));

        Template template = new Template();
        template.appendChildren(elements);

        return template;
    }

    @Override
    public boolean hasAimlElement(AIMLGeneratorArgument argument) {
        boolean hasAimlElement = false;
        for (AIMLGenerator generator : generators.values())
            hasAimlElement = generator.hasAimlElement(argument);
        return hasAimlElement;
    }

    @Override
    public List<AIMLElement> getAimlElements(AIMLGeneratorArgument argument, Wildcard wildcard) {
        switch (wildcard.getName()) {

        case "(condition)": {
            return generators.get(ClauseTab.class).getAimlElements(argument, wildcard);
        }

        case "(random)": {
            return generators.get(RandomTab.class).getAimlElements(argument, wildcard);
        }

        case "(recursion)": {
            return generators.get(RecursTab.class).getAimlElements(argument, wildcard);
        }

        case "{set}":
        case "{exe}": {
            return generators.get(OperationTextField.class).getAimlElements(argument, wildcard);
        }
        }

        return null;
    }

    @Override
    public void update(float delta) {
        if (operationTab.isVisible())
            operationTab.update(delta);
        else {
            topPanel.update(delta);

            for (int i = 0; i != buttons.size(); i++) {
                Button button = buttons.get(i);
                if (currentTabIndex == i) {
                    if (elapsedPressTime[i] < PRESS_TIME) {
                        elapsedPressTime[i] = Math.min(elapsedPressTime[i] + delta, PRESS_TIME);
                        button.changeY(BUTTON_START_Y + (elapsedPressTime[i] / PRESS_TIME) * BUTTON_DIFF_Y);
                    }
                } else {
                    if (elapsedPressTime[i] > 0) {
                        elapsedPressTime[i] = Math.max(elapsedPressTime[i] - delta, 0);
                        button.changeY(BUTTON_START_Y + (elapsedPressTime[i] / PRESS_TIME) * BUTTON_DIFF_Y);
                    }
                }

                button.update(delta);
            }

            currentTab.update(delta);
            topicDMTF.update(delta);

            submit.update(delta);

            operationTab.update(delta);
        }

        // Right click context menu updating.
        wildcardsTab.update(delta);
        starcardsTab.update(delta);

        BotMaker.messages.update(delta);
    }

    @Override
    public void onTabLeave() {
        currentTab.onTabLeave();
    }

    @Override
    public void draw(SmartSpriteBatch batch) {
        batch.draw(background, area.getX(), area.getY(), area.getW(), area.getH());

        // draw top panel with question and answer text fields.
        topPanel.draw(batch);

        for (Button button : buttons)
            button.draw(batch);

        batch.draw(mask, botPanel.getX(), botPanel.getY(), botPanel.getW(), botPanel.getH());

        // Draw bottom background.
        botPanel.draw(batch);

        // Draw current sub tab on background.
        currentTab.draw(batch);

        // Draw topic text field beneath.
        topicDMTF.draw(batch);

        // Draw submit button at the very end.
        submit.draw(batch);

        // Draw operation tab if it is visible.
        operationTab.draw(batch);

        // Draw context menus if they can be seen.
        wildcardsTab.draw(batch);
        starcardsTab.draw(batch);

        // Draw root level message shower.
        BotMaker.messages.draw(batch);
    }

    @Override
    @UnhandledMethod
    public void clear() {
    }

    @Override
    @UnhandledMethod
    public void dispose() {
    }
}