/* CodeQ: an online programming tutor. Copyright (C) 2015 UL FRI This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ /** * Created by robert on 9/17/15. * * The prolog state of the state machine. When it is entered it'll prepare the console and code editor and load a sub-state machine which represents the 4 different parts fo the screen. */ (function() { var subScreens, //this will be the actual (sub)state machine stateNameTag = 'stateName', //a tag for data which is added to some html elements jqScreen = $('#screen_prolog'), // the screen container element //quadrants jqDescription = jqScreen.find('.block1'), jqCode = jqScreen.find('.block2'), jqConsole = jqScreen.find('.block3'), jqInfo = jqScreen.find('.block4'), jqAllQuadrants = jqDescription.add(jqCode).add(jqConsole).add(jqInfo), // all the quadrants // buttons jqBtnPlan = jqScreen.find('.btn-plan'), jqBtnHint = jqScreen.find('.btn-hint').ladda(), jqBtnTest = jqScreen.find('.btn-test').ladda(), jqAllButtons = jqBtnPlan.add(jqBtnHint).add(jqBtnTest), // all the buttons // misc currentSubState = null, transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below) substates = { 'description': { 'enter': function () { currentSubState = 'block1'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } }, 'code': { 'enter': function () { currentSubState = 'block2'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } }, 'info': { 'enter': function () { currentSubState = 'block4'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } }, 'console': { 'enter': function () { currentSubState = 'block3'; jqScreen.addClass(currentSubState); }, 'exit': function () { jqScreen.removeClass(currentSubState); currentSubState = null; } } }; var prologHandler; //created when we enter the prolog state and destroyed once we leave it codeq.globalStateMachine.register('prolog', { 'enter': function (problemDef, commonDef, currentSolution) { $('#navigation-problem_list').css('display', ''); $("#navigation-prolog").addClass("active"); $('#navigation-prolog').css('display', ''); jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly prologHandler = createPrologHandler(problemDef, commonDef, currentSolution); subScreens = codeq.makeStateMachine(substates); subScreens.transition(jqDescription.data(stateNameTag)); /* Q.delay(100).then(function(){ jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading }).done();*/ jqAllButtons.on(transitionEventName, function (event) { subScreens.transition('info'); // set focus on the hints quadrant event.stopPropagation(); // don't allow the event to go on and trigger further transition }); jqAllQuadrants.on(transitionEventName, function () { subScreens.transition($(this).data(stateNameTag)); }); }, 'exit': function () { jqAllButtons.off(); // unregister all event handlers jqAllQuadrants.off(); jqScreen.css('display', 'none'); // jqAllQuadrants.removeClass('transition'); prologHandler.destroy(); prologHandler = null; subScreens.destroy(); subScreens = null; jqScreen.addClass('block1'); $('#navigation-problem_list').css('display', 'none'); $("#navigation-prolog").removeClass("active"); $('#navigation-prolog').css('display', 'none'); } }); jqDescription.data(stateNameTag, 'description'); jqCode.data(stateNameTag, 'code'); jqConsole.data(stateNameTag, 'console'); jqInfo.data(stateNameTag, 'info'); // a constant var firstCharacterPos = {'line': 0, 'ch': 0}; var makePrologTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) { var promptMode = true, // default: query composition; alternative: query result browsing manualStop = false,// if the user stopped showing next answers (false) or if there are no more answers (true) terminal = codeq.makeConsole(jqConsole, { 'greeting': 'CodeQ Prolog terminal proxy', 'autoHistory': true }), tcs = function terminalCommandSuccess (data) { var t, lines, i; if (data.code === 0) { t = data.terminal; terminal.append(t.messages.join('\n'), 'output'); promptMode = !t.have_more; } else { terminal.append(data.message, 'error'); promptMode = true; } if (promptMode) { terminal.setLineBuffered(); terminal.append(manualStop ? '?- ' : '.\n?- ', 'output'); } }, tcf = function terminalCommandFailed (error) { promptMode = true; terminal.setLineBuffered(); terminal.append(error + '\n', 'error'); terminal.append('?- ', 'output'); }; terminal.onKeypress = function (c) { if (promptMode) return c; // query composition: return the character unchanged switch (c) { case ' ': case ';': case 'n': case 'r': return ';'; // show next answer on space, semicolon, 'n' or 'r' default: return '.'; // everything else: stop searching for answers } }; terminal.onInput = function (command) { if (promptMode) { promptMode = false; manualStop = false; terminal.setNotBuffered(); return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'run', 'program': editor.getDoc().getValue(), 'query': command, 'trace': activityHandler.addAndPurge({'typ': 'prolog_solve', 'query': command}) }, problem_id).then(tcs, tcf); } else { terminal.append('\n', 'input'); if (command == ';') { // show next answer return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'next', 'trace': activityHandler.addAndPurge({'typ': 'prolog_next'}) }, problem_id).then(tcs, tcf); } else { // stop searching for answers manualStop = true; return codeq.comms.sendQuery({ 'problem_id': problem_id, 'step': 'end', 'trace': activityHandler.addAndPurge({'typ': 'prolog_end'}) }, problem_id).then(tcs, tcf); } } }; terminal.leftmostCol = 3; terminal.append('?- ', 'output'); return terminal; }; codeq.on('init', function (args) { codeq.tr.registerDictionary('prolog', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active }); createPrologHandler = function (problemDef, commonDef, currentSolution) { var jqDescriptionContent = jqDescription.find('.description'), jqEditor = jqCode.find('.code_editor'), jqTerminal = jqConsole.find('.console'), jqHints = jqInfo.find('.hints'), editor = codeq.makeEditor(jqEditor[0], { mode: 'prolog', indentUnit: 4, value: currentSolution || '' }, function () { jqTerminal.click(); }), activityHandler = codeq.makeActivityHandler(editor, problemDef.id), terminal = makePrologTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler), hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'prolog_hints', problemDef, commonDef), commError = function (error) { alert(error); }; codeq.tr.registerDictionary('prolog', problemDef.translations); codeq.tr.translateDom(jqScreen); jqBtnPlan.prop('disabled', !hinter.hasNextPlan()); editor.on('change', function (instance, changeObj) { var doc = editor.getDoc(), pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)), text; text = changeObj.removed.join('\n'); if (text) { activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'txt': text}); } text = changeObj.text.join('\n'); if (text) { activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': text}); } }); jqBtnPlan.on('click', function () { activityHandler.queueTrace({'typ': 'plan'}); if (!hinter.planNext()) { jqBtnPlan.prop('disabled', true).blur(); } }); jqBtnHint.on('click', function () { editor.setOption('readOnly', true); terminal.inputDisable(); jqBtnTest.prop('disabled', true); jqBtnHint.ladda('start'); codeq.comms.sendHint({ 'language': 'prolog', 'program': editor.getDoc().getValue(), 'problem_id': problemDef.id }) .then(function (data) { if (data.code === 0) { activityHandler.queueTrace({'typ': 'hint', 'feedback': data.hints}); hinter.handle(data.hints); } else { terminal.append(data.message + '\n', 'error'); } }) .fail(commError) .fin(function () { editor.setOption('readOnly', false); terminal.inputEnable(); jqBtnHint.ladda('stop'); jqBtnTest.prop('disabled', false); }) .done(); }); jqBtnTest.on('click', function () { editor.setOption('readOnly', true); terminal.inputDisable(); jqBtnHint.prop('disabled', true); jqBtnTest.ladda('start'); codeq.comms.sendTest({ 'language': 'prolog', 'program': editor.getDoc().getValue(), 'problem_id': problemDef.id }) .then(function (data) { if (data.code === 0) { activityHandler.queueTrace({'typ': 'test', 'feedback': data.hints}); hinter.handle(data.hints); } else { terminal.append(data.message + '\n', 'error'); } }) .fail(commError) .fin(function () { editor.setOption('readOnly', false); terminal.inputEnable(); jqBtnTest.ladda('stop'); jqBtnHint.prop('disabled', false); }) .done(); }); codeq.comms.loadProblem(problemDef.id).done(); activityHandler.queueTrace({ 'typ': 'open', 'time': Date.now(), 'content': editor.getDoc().getValue() }); return { destroy: function () { codeq.comms.endProblem().done(); $('#screen_prolog .title').text('');//empty the title text jqAllButtons.off(); editor.off('change'); activityHandler.queueTrace({'typ': 'close'}); activityHandler.flush(); hinter.destroy(); terminal.destroy(); jqDescriptionContent.empty(); jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it jqTerminal.empty(); // TODO: the same with the console jqDescriptionContent = null; jqEditor = null; jqTerminal = null; jqHints = null; codeq.tr.registerDictionary('prolog', codeq.tr.emptyDictionary); } }; }; })();