/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Benjamin Grauer co-programmer: ... */ //#define DEBUG_SPECIAL_MODULE DEBUG_MODULE_ #include "shell.h" #include "shell_command.h" #include "text_engine.h" #include "list.h" #include "graphics_engine.h" #include "event_handler.h" #include "debug.h" #include "class_list.h" #include "key_names.h" #include #include using namespace std; /** * standard constructor */ Shell::Shell () { this->setClassID(CL_SHELL, "Shell"); this->setName("Shell"); this->keepBufferArray[0] = '\0'; this->keepBuffer = false; this->bActive = false; this->buffer = new tList; this->bufferIterator = this->buffer->getIterator(); this->inputHistory = new tList; //this->commandList = new tList; this->textSize = 15; this->lineSpacing = 5; //this->bufferSize = 0; this->bufferText = NULL; this->setBufferSize(100); this->bufferDisplaySize = 10; this->setAbsCoor2D(3, -400); this->delayed = 0; this->setRepeatDelay(.3, .05); this->pressedKey = SDLK_FIRST; this->inputLineText = NULL; this->inputLine = new char[1]; this->inputLine[0] = '\0'; this->rebuildText(); this->completionList = NULL; // EVENT-Handler subscription of '`' to all States, and all other keyboard commands to ES_SEHLL EventHandler* evh = EventHandler::getInstance(); evh->subscribe(this, ES_ALL, SDLK_BACKQUOTE); for (int i = 1; i < SDLK_LAST; i++) evh->subscribe(this, ES_SHELL, i); //void ShellCommand::registerCommand(const char* commandName, ClassID classID, T* object, void* functionPointer, unsigned int paramCount, ...) ShellCommand::registerCommand("clear", CL_SHELL, &Shell::clear); } Shell* Shell::singletonRef = NULL; /** * standard deconstructor */ Shell::~Shell () { // delete the displayable Buffers for (int i = 0; i < this->bufferDisplaySize; i++) delete this->bufferText[i]; delete[] this->bufferText; // delete the inputLine delete this->inputLineText; delete this->inputLine; // delete all the Chars in the Buffers char* charElem = this->bufferIterator->firstElement(); while (charElem != NULL) { delete charElem; charElem = this->bufferIterator->nextElement(); } delete this->bufferIterator; // if (this->completionList != NULL) //delete this->completionList; Shell::singletonRef = NULL; } /** * activates the shell * * This also feeds the Last few lines from the main buffers into the displayBuffer */ void Shell::activate() { if (this->bActive == true) PRINTF(3)("The shell is already active\n"); this->bActive = true; EventHandler::getInstance()->setState(ES_SHELL); this->setRelCoorSoft2D(0, 0, 1, 5); this->bufferIterator->lastElement(); for (int i = 0; i < this->bufferDisplaySize; i++) this->bufferText[i]->setText(this->bufferIterator->prevElement()); } /** * deactiveates the Shell. */ void Shell::deactivate() { if (this->bActive == false) PRINTF(3)("The shell is already inactive\n"); this->bActive = false; EventHandler::getInstance()->setState(ES_GAME); this->setRelCoorSoft2D(0, -400, 1, 5); } /** * sets the size of the text and spacing * @param textSize the size of the Text in Pixels * @param lineSpacing the size of the Spacing between two lines in pixels * * this also rebuilds the entire Text, inputLine and displayBuffer, * to be accurate again. */ void Shell::setTextSize(unsigned int textSize, unsigned int lineSpacing) { this->textSize = textSize; this->lineSpacing = lineSpacing; this->rebuildText(); } /** * rebuilds the Text's * * use this function, if you changed the Font/Size or something else. */ void Shell::rebuildText() { if (this->inputLineText == NULL) delete this->inputLineText; this->inputLineText = TextEngine::getInstance()->createText("fonts/Aniron_Bold.ttf", this->textSize, TEXT_RENDER_DYNAMIC); this->inputLineText->setColor(1, 0, 0); this->inputLineText->setAlignment(TEXT_ALIGN_LEFT); this->inputLineText->setText(NULL); this->inputLineText->setParent2D(this); this->inputLineText->setRelCoor2D(5, (this->textSize + this->lineSpacing)*this->bufferDisplaySize + this->textSize); this->setBufferDisplaySize(this->bufferDisplaySize); } /** * sets The count of Lines to display in the buffer. * @param bufferDisplaySize the count of lines to display in the Shell-Buffer. */ void Shell::setBufferDisplaySize(unsigned int bufferDisplaySize) { if (this->bufferText != NULL) { for (unsigned int i = 0; i < this->bufferDisplaySize; i++) delete this->bufferText[i]; delete[] this->bufferText; } this->bufferText = new Text*[bufferDisplaySize]; for (unsigned int i = 0; i < bufferDisplaySize; i++) { this->bufferText[i] = TextEngine::getInstance()->createText("fonts/Aniron_Bold.ttf", this->textSize, TEXT_RENDER_DYNAMIC); this->bufferText[i]->setColor(1, 0, 0); this->bufferText[i]->setAlignment(TEXT_ALIGN_LEFT); this->bufferText[i]->setRelCoor2D(calculateLinePosition(i)); this->bufferText[i]->setText(NULL); this->bufferText[i]->setParent2D(this); } this->bufferDisplaySize = bufferDisplaySize; this->shellHeight = (this->textSize + this->lineSpacing) * (bufferDisplaySize+1); } /** * deletes all the Buffers */ void Shell::flushBuffers() { // remove all chars from the BufferTexts. if (this->bufferText) for (int i = 0; i < this->bufferDisplaySize; i++) { this->bufferText[i]->setText(NULL, true); } // delete all the Chars in the Buffers tIterator* charIterator = this->buffer->getIterator(); char* charElem = charIterator->firstElement(); while (charElem != NULL) { delete charElem; charElem = charIterator->nextElement(); } delete charIterator; delete this->buffer; this->buffer = new tList; } /** * adds a new Line to the List of Buffers * @param line the Line as in the first argument in printf * @param args the arguments as a va_list */ bool Shell::addBufferLineStatic(const char* line, ...) { va_list arguments; va_start(arguments, line); #if DEBUG < 3 if (Shell::singletonRef == NULL) #endif vprintf(line, arguments); #if DEBUG < 3 else #else if (Shell::singletonRef != NULL) #endif Shell::singletonRef->addBufferLine(line, arguments); return true; } /** * add a Line to the List of Buffers * @param line * @param arguments * * This function Adds one line to the buffer. * and displays the line as the First Line of the display-buffer */ void Shell::addBufferLine(const char* line, va_list arguments) { vsprintf(this->bufferArray, line, arguments); char* inputEnd; char* newLineBegin; char* newLineEnd; // check if we have something left in the buffers if (unlikely(this->keepBuffer)) { strcat(this->keepBufferArray, this->bufferArray); inputEnd = this->keepBufferArray + strlen(this->keepBufferArray); newLineBegin = this->keepBufferArray; this->keepBuffer = false; } else { inputEnd = this->bufferArray + strlen(this->bufferArray); newLineBegin = this->bufferArray; } // adding all the new Lines while (newLineBegin < inputEnd) { newLineEnd = strchr(newLineBegin, '\n'); if (newLineEnd != NULL && *newLineEnd == '\n') *newLineEnd = '\0'; else { // newLineEnd = newLineBegin + strlen(newLineBegin); strcpy(this->keepBufferArray, newLineBegin); this->keepBuffer = true; break; } char* addLine = new char[strlen(newLineBegin)+1]; strcpy(addLine, newLineBegin); this->buffer->add(addLine); if (this->buffer->getSize() > this->bufferSize) { delete this->buffer->firstElement(); this->buffer->remove(this->buffer->firstElement()); } if (this->bActive) { this->printToDisplayBuffer(addLine); } newLineBegin = newLineEnd+1; } } /** * prints out some text to the input-buffers * @param text the text to output. */ void Shell::printToDisplayBuffer(const char* text) { if(likely(bufferText != NULL)) { Text* lastText = this->bufferText[this->bufferDisplaySize-1]; Text* swapText; Text* moveText = this->bufferText[0]; this->bufferText[0]->setRelCoorSoft2D(this->calculateLinePosition(1),10); for (unsigned int i = 1; i < this->bufferDisplaySize; i++) { if ( i < this->bufferDisplaySize-1) this->bufferText[i]->setRelCoorSoft2D(this->calculateLinePosition(i+1),5); swapText = this->bufferText[i]; this ->bufferText[i] = moveText; moveText = swapText; } lastText->setRelCoor2D(this->calculateLinePosition(0)); this->bufferText[0] = lastText; this->bufferText[0]->setText(text, true); } } /** * moves the buffer around lineCount lines upwards (negative values move down) * @param lineCount the Count of lines to move upwards * * @todo */ void Shell::moveBuffer(int lineCount) { } /** * @param lineNumber the n-th line from the bottom * @returns the Buffer at Line lineNumber */ const char* Shell::getBufferLine(unsigned int lineNumber) { tIterator* charIterator = this->buffer->getIterator(); char* charElem = charIterator->firstElement(); int i = 1; while (charElem != NULL) { if (i++ < lineNumber) { delete charIterator; return charElem; } charElem = charIterator->nextElement(); } delete charIterator; } /** * deletes the InputLine */ void Shell::flushInputLine() { if (likely(this->inputLine != NULL)) { delete[] this->inputLine; } this->inputLine = new char[1]; *this->inputLine = '\0'; this->inputLineText->setText(this->inputLine, true); } /** * adds one character to the inputLine * @param character the character to add to the inputLine */ void Shell::addCharacter(char character) { char* addCharLine = new char[strlen(inputLine)+2]; sprintf(addCharLine, "%s%c", this->inputLine, character); delete this->inputLine; this->inputLine = addCharLine; this->inputLineText->setText(inputLine, true); } /** * adds multiple Characters to thr inputLine * @param characters a '\0' terminated char-array to add to the InputLine */ void Shell::addCharacters(const char* characters) { char* addCharLine = new char[strlen(inputLine)+strlen(characters)+1]; sprintf(addCharLine, "%s%s", this->inputLine, characters); delete this->inputLine; this->inputLine = addCharLine; this->inputLineText->setText(inputLine); } /** * removes characterCount characters from the InputLine * @param characterCount the count of Characters to remove from the input Line */ void Shell::removeCharacters(unsigned int characterCount) { if (strlen(this->inputLine) == 0) return; if (characterCount > strlen(this->inputLine)) characterCount = strlen(this->inputLine); char* removeCharLine = new char[strlen(inputLine)-characterCount+1]; strncpy(removeCharLine, this->inputLine, strlen(inputLine)-characterCount); removeCharLine[strlen(inputLine)-characterCount] = '\0'; delete this->inputLine; this->inputLine = removeCharLine; this->inputLineText->setText(inputLine); } /** * executes the command stored in the inputLine * @return true if the command was commited successfully, false otherwise */ bool Shell::executeCommand() { this->addBufferLineStatic("Execute Command: %s\n", this->inputLine); char* newCommand = new char[strlen(this->inputLine)+1]; strcpy(newCommand, this->inputLine); this->inputHistory->add(newCommand); ShellCommandBase::execute(this->inputLine); // if (!strcmp(this->inputLine, "clear")) // { // this->clear(); // } this->flushInputLine(); return false; } void Shell::clear() { this->flushBuffers(); this->addBufferLine("orxonox - shell\n ==================== \n", NULL); } /** * sets the Repeate-delay and rate * @param repeatDelay the Delay it takes, to repeate a key * @param repeatRate the rate to repeate a pressed key */ void Shell::setRepeatDelay(float repeatDelay, float repeatRate) { this->repeatDelay = repeatDelay; this->repeatRate = repeatRate; } /** * listens for some event * @param event the Event happened */ void Shell::process(const Event &event) { if (event.bPressed) { PRINTF(5)("Shell received command %s\n", SDLKToKeyname(event.type)); if (event.type == SDLK_BACKQUOTE) { if (EventHandler::getInstance()->getState() == ES_GAME) this->activate(); else this->deactivate(); } else if (event.type == SDLK_F1) this->help(); else if (event.type == SDLK_F2) this->debug(); else if (event.type == SDLK_TAB) this->autoComplete(); else if (event.type == SDLK_BACKSPACE) { this->delayed = this->repeatDelay; this->pressedKey = SDLK_BACKSPACE; this->removeCharacters(1); } else if (event.type == SDLK_RETURN) this->executeCommand(); /* else if (event.type == SDLK_UP) { // this->flushInputLine(); tIterator* iterator = this->commandList->getIterator(); char* command = iterator->lastElement(); while (command) { if (!strcmp (command, inputLine)) { inputLine = iterator->prevElement(); return; } command = iterator->prevElement(); } inputLine = iterator->lastElement(); } */ else if (likely(event.type < 127)) { Uint8 *keystate = SDL_GetKeyState(NULL); this->delayed = this->repeatDelay; if (unlikely( keystate[SDLK_LSHIFT] || keystate[SDLK_RSHIFT] )) { this->pressedKey = event.type-32; this->addCharacter(event.type-32); } else { this->pressedKey = event.type; this->addCharacter(event.type); } } } else // if(!event.bPressed) { if (this->pressedKey == event.type || (this->pressedKey == event.type - 32)) { this->pressedKey = SDLK_FIRST; this->delayed = 0.0; } } } /** * ticks the Shell for dt Seconds * @param dt the elapsed time since the last tick(); */ void Shell::tick(float dt) { if (this->delayed > 0.0) this->delayed -= dt; else if (this->pressedKey != SDLK_FIRST ) { this->delayed = this->repeatRate; if (this->pressedKey == SDLK_BACKSPACE) this->removeCharacters(1); else if (pressedKey < 127) this->addCharacter(this->pressedKey); } } /** * displays the Shell */ void Shell::draw() const { glPushMatrix(); // transform for alignment. // setting the Blending effects glColor4f(0.0f, 0.0f, 0.8f, .4); glEnable(GL_BLEND); glDisable(GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE); // glBindTexture(GL_TEXTURE_2D, this->texture); glBegin(GL_QUADS); // glTexCoord2f(this->texCoord.minU, this->texCoord.minV); glVertex2f(this->getAbsCoor2D().x, this->getAbsCoor2D().y ); // glTexCoord2f(this->texCoord.maxU, this->texCoord.minV); glVertex2f(GraphicsEngine::getInstance()->getResolutionX() - this->getAbsCoor2D().x, this->getAbsCoor2D().y ); // glTexCoord2f(this->texCoord.maxU, this->texCoord.maxV); glVertex2f(GraphicsEngine::getInstance()->getResolutionX() - this->getAbsCoor2D().x, this->getAbsCoor2D().y + this->shellHeight); // glTexCoord2f(this->texCoord.minU, this->texCoord.maxV); glVertex2f(this->getAbsCoor2D().x, this->getAbsCoor2D().y + this->shellHeight); glEnd(); } /** * autocompletes the Shell's inputLine * @returns true, if a result was found, false otherwise * * @todo implement it!! */ bool Shell::autoComplete() { //PRINTF(3)("AutoCompletion not implemented yet\n"); char* completionLine = new char[strlen(inputLine)+1]; strcpy(completionLine, this->inputLine); char* commandBegin = strrchr(completionLine, ' '); if (commandBegin == NULL) commandBegin = completionLine; else { if(commandBegin >= completionLine + strlen(completionLine)) commandBegin = completionLine + strlen(completionLine); else commandBegin++; } char* objectStart; if (objectStart = strstr(commandBegin, "::")) { char* classIdentity = new char[objectStart - commandBegin +1]; strncpy(classIdentity, commandBegin, objectStart - commandBegin); classIdentity[objectStart - commandBegin] = '\0'; this->objectComplete(objectStart+2, ClassList::StringToID(classIdentity)); delete[] classIdentity; } else this->classComplete(commandBegin); delete[] completionLine; } /** * autocompletes a className * @param classBegin the Beginning of a String to autoComplete * @return true on success, false otherwise */ bool Shell::classComplete(const char* classBegin) { if (unlikely(classBegin == NULL)) return false; const tList* clList = ClassList::getClassList(); if (clList != NULL) { const tList* classList = this->createCompleteList(clList, classBegin); if (classList != NULL) this->generalComplete(classList, classBegin, "%s::", "::"); else return false; } else return false; return true; } /** * autocompletes an ObjectName * @param objectBegin the beginning string of a Object * @param classID the ID of the Class to search for. * @return true on success, false otherwise */ bool Shell::objectComplete(const char* objectBegin, long classID) { printf("%s\n", objectBegin); if (unlikely(objectBegin == NULL)) return false; tList* boList = ClassList::getList(classID); if (boList != NULL) { printf("\n", boList->firstElement()->getName()); const tList* objectList = this->createCompleteList(boList, objectBegin); if (objectList != NULL) this->generalComplete(objectList, objectBegin, "%s"); else return false; } else return false; return true; } bool Shell::functionComplete(const char* functionBegin) { } /** * completes the inputline on grounds of an inputList * @param stringList the List to parse through * @param begin the String to search in the inputList, and to extend with it. * @param displayAs how to display the found value to the user, printf-style, !!with only one %s!! ex.: "::%s::" * @param addBack what should be added at the end of the completion * @param addFront what should be added to the front of one finished completion * @return true if ok, false otherwise */ bool Shell::generalComplete(const tList* stringList, const char* begin, const char* displayAs, const char* addBack, const char* addFront) { if (stringList->getSize() == 0) return false; const char* addString = stringList->firstElement(); unsigned int addLength = 0; unsigned int inputLenght = strlen(begin); if (addString != NULL) addLength = strlen(addString); tIterator* charIterator = stringList->getIterator(); const char* charElem = charIterator->firstElement(); while (charElem != NULL) { PRINTF(0)(displayAs, charElem); for (unsigned int i = inputLenght; i < addLength; i++) if (addString[i] != charElem[i]) { addLength = i; break; } charElem = charIterator->nextElement(); } delete charIterator; if (addLength >= inputLenght) { char* adder = new char[addLength+1]; strncpy(adder, addString, addLength); adder[addLength] = '\0'; this->removeCharacters(inputLenght); this->addCharacters(adder); if (addBack != NULL && stringList->getSize() == 1) this->addCharacters("::"); delete[] adder; } return true; } /** * searches for classes, which beginn with classNameBegin * @param inputList the List to parse through * @param classNameBegin the beginning string * @return a NEW char-array with ClassNames. The LIST should be deleted afterwards, * !! The strings MUST NOT be deleted !! */ const tList* Shell::createCompleteList(const tList* inputList, const char* classNameBegin) { if (inputList == NULL || classNameBegin == NULL) return NULL; unsigned int searchLength = strlen(classNameBegin); if (this->completionList != NULL) delete this->completionList; this->completionList = new tList; // tList* classList = ClassList::getClassList(); tIterator* iterator = inputList->getIterator(); const char* enumString = iterator->firstElement(); while (enumString != NULL) { if (strlen(enumString)>searchLength+1 && !strncasecmp(enumString, classNameBegin, searchLength)) { this->completionList->add(enumString); } enumString = iterator->nextElement(); } delete iterator; return this->completionList; } /** * searches for classes, which beginn with classNameBegin * @param inputList the List to parse through * @param classNameBegin the beginning string * @return a NEW char-array with ClassNames. The LIST should be deleted afterwards, * !! The strings MUST NOT be deleted !! */ const tList* Shell::createCompleteList(const tList* inputList, const char* classNameBegin) { if (inputList == NULL || classNameBegin == NULL) return NULL; unsigned int searchLength = strlen(classNameBegin); if (this->completionList != NULL) delete this->completionList; this->completionList = new tList; tIterator* iterator = inputList->getIterator(); BaseObject* enumBO = iterator->firstElement(); while (enumBO != NULL) { if (enumBO->getName() != NULL && strlen(enumBO->getName())>searchLength+1 && !strncasecmp(enumBO->getName(), classNameBegin, searchLength)) { this->completionList->add(enumBO->getName()); } enumBO = iterator->nextElement(); } delete iterator; return this->completionList; } void Shell::help() const { PRINT(0)("Help for the most important Shell-commands\n"); PRINT(0)("F1 - HELP; F2 - DEBUG; ` - open/close shell\n"); PRINT(0)("input order:\n"); PRINT(0)("ClassName::objectName function [parameter1, [parameter2 ...]] or\n"); PRINT(0)("Command [parameter]\n"); } /////////////////////// // HELPER FUNCTIONS // /////////////////////// Vector Shell::calculateLinePosition(unsigned int lineNumber) { return Vector(5, (this->textSize + this->lineSpacing)*(this->bufferDisplaySize - lineNumber -1) + this->textSize, 0); } /** * displays some nice output from the Shell */ void Shell::debug() const { PRINT(3)("Debugging output to console (not this shell)\n"); if (this->pressedKey != SDLK_FIRST) printf("%s::%f %f\n", SDLKToKeyname(this->pressedKey), this->delayed, this->repeatDelay); char* tmpChar = this->bufferIterator->firstElement(); while(tmpChar != NULL) { printf(tmpChar); tmpChar = this->bufferIterator->nextElement(); } }