forked from teamnwah/openmw-tes3coop
db0e0d376e
Previously, reusing the console's object selection code made it so using the same Ptr twice in a row was akin to clicking on the same object twice in the console window, i.e. the object was deselected the second time around. Additionally, that same code was setting key focus to the hidden console window, preventing players from moving until they activated another window (such as their inventory or chat window).
467 lines
15 KiB
C++
467 lines
15 KiB
C++
#include "console.hpp"
|
|
|
|
#include <MyGUI_EditBox.h>
|
|
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/filesystem/fstream.hpp>
|
|
|
|
#include <components/compiler/exception.hpp>
|
|
#include <components/compiler/extensions0.hpp>
|
|
|
|
#include "../mwscript/extensions.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
namespace MWGui
|
|
{
|
|
class ConsoleInterpreterContext : public MWScript::InterpreterContext
|
|
{
|
|
Console& mConsole;
|
|
|
|
public:
|
|
|
|
ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference);
|
|
|
|
virtual void report (const std::string& message);
|
|
};
|
|
|
|
ConsoleInterpreterContext::ConsoleInterpreterContext (Console& console,
|
|
MWWorld::Ptr reference)
|
|
: MWScript::InterpreterContext (
|
|
reference.isEmpty() ? 0 : &reference.getRefData().getLocals(), reference),
|
|
mConsole (console)
|
|
{}
|
|
|
|
void ConsoleInterpreterContext::report (const std::string& message)
|
|
{
|
|
mConsole.printOK (message);
|
|
}
|
|
|
|
bool Console::compile (const std::string& cmd, Compiler::Output& output)
|
|
{
|
|
try
|
|
{
|
|
ErrorHandler::reset();
|
|
|
|
std::istringstream input (cmd + '\n');
|
|
|
|
Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions());
|
|
|
|
Compiler::LineParser parser (*this, mCompilerContext, output.getLocals(),
|
|
output.getLiterals(), output.getCode(), true);
|
|
|
|
scanner.scan (parser);
|
|
|
|
return isGood();
|
|
}
|
|
catch (const Compiler::SourceException&)
|
|
{
|
|
// error has already been reported via error handler
|
|
}
|
|
catch (const std::exception& error)
|
|
{
|
|
printError (std::string ("Error: ") + error.what());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Console::report (const std::string& message, const Compiler::TokenLoc& loc, Type type)
|
|
{
|
|
std::ostringstream error;
|
|
error << "column " << loc.mColumn << " (" << loc.mLiteral << "):";
|
|
|
|
printError (error.str());
|
|
printError ((type==ErrorMessage ? "error: " : "warning: ") + message);
|
|
}
|
|
|
|
void Console::report (const std::string& message, Type type)
|
|
{
|
|
printError ((type==ErrorMessage ? "error: " : "warning: ") + message);
|
|
}
|
|
|
|
void Console::listNames()
|
|
{
|
|
if (mNames.empty())
|
|
{
|
|
// keywords
|
|
std::istringstream input ("");
|
|
|
|
Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions());
|
|
|
|
scanner.listKeywords (mNames);
|
|
|
|
// identifier
|
|
const MWWorld::ESMStore& store =
|
|
MWBase::Environment::get().getWorld()->getStore();
|
|
|
|
for (MWWorld::ESMStore::iterator it = store.begin(); it != store.end(); ++it)
|
|
{
|
|
it->second->listIdentifier (mNames);
|
|
}
|
|
|
|
// exterior cell names aren't technically identifiers, but since the COC function accepts them,
|
|
// we should list them too
|
|
for (MWWorld::Store<ESM::Cell>::iterator it = store.get<ESM::Cell>().extBegin();
|
|
it != store.get<ESM::Cell>().extEnd(); ++it)
|
|
{
|
|
if (!it->mName.empty())
|
|
mNames.push_back(it->mName);
|
|
}
|
|
|
|
// sort
|
|
std::sort (mNames.begin(), mNames.end());
|
|
|
|
// remove duplicates
|
|
mNames.erase( std::unique( mNames.begin(), mNames.end() ), mNames.end() );
|
|
}
|
|
}
|
|
|
|
Console::Console(int w, int h, bool consoleOnlyScripts)
|
|
: WindowBase("openmw_console.layout"),
|
|
mCompilerContext (MWScript::CompilerContext::Type_Console),
|
|
mConsoleOnlyScripts (consoleOnlyScripts)
|
|
{
|
|
setCoord(10,10, w-10, h/2);
|
|
|
|
getWidget(mCommandLine, "edit_Command");
|
|
getWidget(mHistory, "list_History");
|
|
|
|
// Set up the command line box
|
|
mCommandLine->eventEditSelectAccept +=
|
|
newDelegate(this, &Console::acceptCommand);
|
|
mCommandLine->eventKeyButtonPressed +=
|
|
newDelegate(this, &Console::keyPress);
|
|
|
|
// Set up the log window
|
|
mHistory->setOverflowToTheLeft(true);
|
|
|
|
// compiler
|
|
Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts);
|
|
mCompilerContext.setExtensions (&mExtensions);
|
|
}
|
|
|
|
void Console::onOpen()
|
|
{
|
|
// Give keyboard focus to the combo box whenever the console is
|
|
// turned on
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
|
|
}
|
|
|
|
void Console::setFont(const std::string &fntName)
|
|
{
|
|
mHistory->setFontName(fntName);
|
|
mCommandLine->setFontName(fntName);
|
|
}
|
|
|
|
void Console::print(const std::string &msg, const std::string& color)
|
|
{
|
|
mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg));
|
|
}
|
|
|
|
void Console::printOK(const std::string &msg)
|
|
{
|
|
print(msg + "\n", "#FF00FF");
|
|
}
|
|
|
|
void Console::printError(const std::string &msg)
|
|
{
|
|
print(msg + "\n", "#FF2222");
|
|
}
|
|
|
|
void Console::execute (const std::string& command)
|
|
{
|
|
// Log the command
|
|
print("> " + command + "\n");
|
|
|
|
Compiler::Locals locals;
|
|
Compiler::Output output (locals);
|
|
|
|
if (compile (command + "\n", output))
|
|
{
|
|
try
|
|
{
|
|
ConsoleInterpreterContext interpreterContext (*this, mPtr);
|
|
Interpreter::Interpreter interpreter;
|
|
MWScript::installOpcodes (interpreter, mConsoleOnlyScripts);
|
|
std::vector<Interpreter::Type_Code> code;
|
|
output.getCode (code);
|
|
interpreter.run (&code[0], code.size(), interpreterContext);
|
|
}
|
|
catch (const std::exception& error)
|
|
{
|
|
printError (std::string ("Error: ") + error.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Console::executeFile (const std::string& path)
|
|
{
|
|
namespace bfs = boost::filesystem;
|
|
bfs::ifstream stream ((bfs::path(path)));
|
|
|
|
if (!stream.is_open())
|
|
printError ("failed to open file: " + path);
|
|
else
|
|
{
|
|
std::string line;
|
|
|
|
while (std::getline (stream, line))
|
|
execute (line);
|
|
}
|
|
}
|
|
|
|
void Console::clear()
|
|
{
|
|
resetReference();
|
|
}
|
|
|
|
void Console::keyPress(MyGUI::Widget* _sender,
|
|
MyGUI::KeyCode key,
|
|
MyGUI::Char _char)
|
|
{
|
|
if( key == MyGUI::KeyCode::Tab)
|
|
{
|
|
std::vector<std::string> matches;
|
|
listNames();
|
|
std::string oldCaption = mCommandLine->getCaption();
|
|
std::string newCaption = complete( mCommandLine->getOnlyText(), matches );
|
|
mCommandLine->setCaption(newCaption);
|
|
|
|
// List candidates if repeatedly pressing tab
|
|
if (oldCaption == newCaption && !matches.empty())
|
|
{
|
|
int i = 0;
|
|
printOK("");
|
|
for(std::vector<std::string>::iterator it=matches.begin(); it < matches.end(); ++it,++i )
|
|
{
|
|
printOK( *it );
|
|
if( i == 50 )
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(mCommandHistory.empty()) return;
|
|
|
|
// Traverse history with up and down arrows
|
|
if(key == MyGUI::KeyCode::ArrowUp)
|
|
{
|
|
// If the user was editing a string, store it for later
|
|
if(mCurrent == mCommandHistory.end())
|
|
mEditString = mCommandLine->getOnlyText();
|
|
|
|
if(mCurrent != mCommandHistory.begin())
|
|
{
|
|
--mCurrent;
|
|
mCommandLine->setCaption(*mCurrent);
|
|
}
|
|
}
|
|
else if(key == MyGUI::KeyCode::ArrowDown)
|
|
{
|
|
if(mCurrent != mCommandHistory.end())
|
|
{
|
|
++mCurrent;
|
|
|
|
if(mCurrent != mCommandHistory.end())
|
|
mCommandLine->setCaption(*mCurrent);
|
|
else
|
|
// Restore the edit string
|
|
mCommandLine->setCaption(mEditString);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Console::acceptCommand(MyGUI::EditBox* _sender)
|
|
{
|
|
const std::string &cm = mCommandLine->getOnlyText();
|
|
if(cm.empty()) return;
|
|
|
|
// Add the command to the history, and set the current pointer to
|
|
// the end of the list
|
|
if (mCommandHistory.empty() || mCommandHistory.back() != cm)
|
|
mCommandHistory.push_back(cm);
|
|
mCurrent = mCommandHistory.end();
|
|
mEditString.clear();
|
|
|
|
// Reset the command line before the command execution.
|
|
// It prevents the re-triggering of the acceptCommand() event for the same command
|
|
// during the actual command execution
|
|
mCommandLine->setCaption("");
|
|
|
|
execute (cm);
|
|
}
|
|
|
|
std::string Console::complete( std::string input, std::vector<std::string> &matches )
|
|
{
|
|
std::string output = input;
|
|
std::string tmp = input;
|
|
bool has_front_quote = false;
|
|
|
|
/* Does the input string contain things that don't have to be completed? If yes erase them. */
|
|
/* Are there quotation marks? */
|
|
if( tmp.find('"') != std::string::npos ) {
|
|
int numquotes=0;
|
|
for(std::string::iterator it=tmp.begin(); it < tmp.end(); ++it) {
|
|
if( *it == '"' )
|
|
numquotes++;
|
|
}
|
|
|
|
/* Is it terminated?*/
|
|
if( numquotes % 2 ) {
|
|
tmp.erase( 0, tmp.rfind('"')+1 );
|
|
has_front_quote = true;
|
|
}
|
|
else {
|
|
size_t pos;
|
|
if( ( ((pos = tmp.rfind(' ')) != std::string::npos ) ) && ( pos > tmp.rfind('"') ) ) {
|
|
tmp.erase( 0, tmp.rfind(' ')+1);
|
|
}
|
|
else {
|
|
tmp.clear();
|
|
}
|
|
has_front_quote = false;
|
|
}
|
|
}
|
|
/* No quotation marks. Are there spaces?*/
|
|
else {
|
|
size_t rpos;
|
|
if( (rpos=tmp.rfind(' ')) != std::string::npos ) {
|
|
if( rpos == 0 ) {
|
|
tmp.clear();
|
|
}
|
|
else {
|
|
tmp.erase(0, rpos+1);
|
|
}
|
|
}
|
|
}
|
|
/* Erase the input from the output string so we can easily append the completed form later. */
|
|
output.erase(output.end()-tmp.length(), output.end());
|
|
|
|
/* Is there still something in the input string? If not just display all commands and return the unchanged input. */
|
|
if( tmp.length() == 0 ) {
|
|
matches=mNames;
|
|
return input;
|
|
}
|
|
|
|
/* Iterate through the vector. */
|
|
for(std::vector<std::string>::iterator it=mNames.begin(); it < mNames.end();++it) {
|
|
bool string_different=false;
|
|
|
|
/* Is the string shorter than the input string? If yes skip it. */
|
|
if( (*it).length() < tmp.length() )
|
|
continue;
|
|
|
|
/* Is the beginning of the string different from the input string? If yes skip it. */
|
|
for( std::string::iterator iter=tmp.begin(), iter2=(*it).begin(); iter < tmp.end();++iter, ++iter2) {
|
|
if( Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2) ) {
|
|
string_different=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( string_different )
|
|
continue;
|
|
|
|
/* The beginning of the string matches the input string, save it for the next test. */
|
|
matches.push_back(*it);
|
|
}
|
|
|
|
/* There are no matches. Return the unchanged input. */
|
|
if( matches.empty() )
|
|
{
|
|
return input;
|
|
}
|
|
|
|
/* Only one match. We're done. */
|
|
if( matches.size() == 1 ) {
|
|
/* Adding quotation marks when the input string started with a quotation mark or has spaces in it*/
|
|
if( ( matches.front().find(' ') != std::string::npos ) ) {
|
|
if( !has_front_quote )
|
|
output.append(std::string("\""));
|
|
return output.append(matches.front() + std::string("\" "));
|
|
}
|
|
else if( has_front_quote ) {
|
|
return output.append(matches.front() + std::string("\" "));
|
|
}
|
|
else {
|
|
return output.append(matches.front() + std::string(" "));
|
|
}
|
|
}
|
|
|
|
/* Check if all matching strings match further than input. If yes complete to this match. */
|
|
int i = tmp.length();
|
|
|
|
for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) {
|
|
for(std::vector<std::string>::iterator it=matches.begin(); it < matches.end();++it) {
|
|
if( Misc::StringUtils::toLower((*it)[i]) != Misc::StringUtils::toLower(*iter) ) {
|
|
/* Append the longest match to the end of the output string*/
|
|
output.append(matches.front().substr( 0, i));
|
|
return output;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* All keywords match with the shortest. Append it to the output string and return it. */
|
|
return output.append(matches.front());
|
|
}
|
|
|
|
void Console::onResChange(int width, int height)
|
|
{
|
|
setCoord(10,10, width-10, height/2);
|
|
}
|
|
|
|
void Console::setSelectedObject(const MWWorld::Ptr& object)
|
|
{
|
|
if (!object.isEmpty())
|
|
{
|
|
if (object == mPtr)
|
|
{
|
|
setTitle("#{sConsoleTitle}");
|
|
mPtr=MWWorld::Ptr();
|
|
}
|
|
else
|
|
{
|
|
setTitle("#{sConsoleTitle} (" + object.getCellRef().getRefId() + ")");
|
|
mPtr = object;
|
|
}
|
|
// User clicked on an object. Restore focus to the console command line.
|
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
|
|
}
|
|
else
|
|
{
|
|
setTitle("#{sConsoleTitle}");
|
|
mPtr = MWWorld::Ptr();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Allow the direct setting of a console's Ptr, without the assumption that an object
|
|
was clicked and that key focus should be restored to the console window, for console
|
|
commands executed via server scripts
|
|
*/
|
|
void Console::setPtr(const MWWorld::Ptr& object)
|
|
{
|
|
mPtr = object;
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
void Console::onReferenceUnavailable()
|
|
{
|
|
setSelectedObject(MWWorld::Ptr());
|
|
}
|
|
|
|
void Console::resetReference()
|
|
{
|
|
ReferenceInterface::resetReference();
|
|
setSelectedObject(MWWorld::Ptr());
|
|
}
|
|
}
|