Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/consolecommands3/src/libraries/core/command/IOConsole.cc @ 7203

Last change on this file since 7203 was 7203, checked in by landauf, 14 years ago

adjusted includes in core/command/*

  • Property svn:eol-style set to native
File size: 33.1 KB
RevLine 
[5971]1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
[5970]23 *      Oliver Scheuss
24 *      Reto Grieder
[5971]25 *   Co-authors:
26 *      ...
27 *
28 */
29
30#include "IOConsole.h"
31
32#include <iomanip>
33#include <iostream>
[6417]34
35#include "util/Clock.h"
36#include "util/Math.h"
[7203]37#include "core/Game.h"
38#include "core/input/InputBuffer.h"
[5971]39
[6015]40// ##########################
41// ###   Mutual methods   ###
42// ##########################
43namespace orxonox
44{
45    IOConsole* IOConsole::singletonPtr_s = NULL;
46
47    // ###############################
48    // ###  ShellListener methods  ###
49    // ###############################
50
51    //! Called if all output-lines have to be reprinted
52    void IOConsole::linesChanged()
53    {
54        // Method only gets called upon start to draw all the lines
[6037]55        // or when scrolling. But scrolling is disabled and the output
[6015]56        // is already in std::cout when we start the IOConsole
57    }
58
59    //! Called if a command is about to be executed
60    void IOConsole::executed()
61    {
[6417]62        this->shell_->addOutput(this->promptString_ + this->shell_->getInput() + '\n', Shell::Command);
[6015]63    }
64
65    //! Called if the console gets closed
66    void IOConsole::exit()
67    {
68        // Exit is not an option, just do nothing (Shell doesn't really exit too)
69    }
70}
71
[5971]72#ifdef ORXONOX_PLATFORM_UNIX
[6015]73// ###############################
74// ###   Unix Implementation   ###
75// ###############################
76
[5971]77#include <termios.h>
[6014]78#include <sys/ioctl.h>
[5971]79
80namespace orxonox
81{
[5995]82    namespace EscapeMode
83    {
84        enum Value
85        {
86            None,
87            First,
88            Second
89        };
90    }
91
[5971]92    IOConsole::IOConsole()
[6417]93        : shell_(new Shell("IOConsole", false))
[6004]94        , buffer_(shell_->getInputBuffer())
[6037]95        , cout_(std::cout.rdbuf())
[6417]96        , promptString_("orxonox # ")
[5995]97        , bStatusPrinted_(false)
[6417]98        , originalTerminalSettings_(0)
[5971]99    {
100        this->setTerminalMode();
[6004]101        this->shell_->registerListener(this);
[5995]102
103        // Manually set the widths of the individual status lines
[6014]104        this->statusLineWidths_.push_back(29);
105        this->statusLineMaxWidth_ = 29;
106
107        this->getTerminalSize();
108        this->lastTerminalWidth_ = this->terminalWidth_;
109        this->lastTerminalHeight_ = this->terminalHeight_;
[6015]110
111        // Disable standard std::cout logging
112        OutputHandler::getInstance().disableCout();
[6037]113        // Redirect std::cout to an ostringstream
114        // (Other part is in the initialiser list)
115        std::cout.rdbuf(this->origCout_.rdbuf());
116
117        // Make sure we make way for the status lines
[6417]118        this->preUpdate(Game::getInstance().getGameClock());
[5971]119    }
120
121    IOConsole::~IOConsole()
122    {
[6417]123        // Process output written to std::cout in the meantime
124        std::cout.flush();
125        if (!this->origCout_.str().empty())
126            this->shell_->addOutput(this->origCout_.str(), Shell::None);
[6103]127        // Erase input and status lines
128        this->cout_ << "\033[1G\033[J";
[6105]129        // Move cursor to the bottom
130        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
131        // Scroll terminal to compensate for erased lines
132        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'T';
[6015]133
[5971]134        resetTerminalMode();
[6004]135        this->shell_->destroy();
[5971]136
[6037]137        // Restore this->cout_ redirection
138        std::cout.rdbuf(this->cout_.rdbuf());
[6015]139        // Enable standard std::cout logging again
140        OutputHandler::getInstance().enableCout();
[5971]141    }
142
[6417]143    void IOConsole::preUpdate(const Clock& time)
[5971]144    {
[6103]145        unsigned char c;
[5995]146        std::string escapeSequence;
[5998]147        EscapeMode::Value escapeMode = EscapeMode::None;
[6015]148        while (std::cin.good())
[5971]149        {
[6015]150            c = std::cin.get();
[6089]151            if (!std::cin.good())
[6015]152                break;
153
[5995]154            if (escapeMode == EscapeMode::First && (c == '[' || c=='O') )
155                escapeMode = EscapeMode::Second;
[5975]156            // Get Alt+Tab combination when switching applications
[5998]157            else if (escapeMode == EscapeMode::First && c == '\t')
[5971]158            {
[5975]159                this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab, '\t', KeyboardModifier::Alt));
[5995]160                escapeMode = EscapeMode::None;
[5975]161            }
[5995]162            else if (escapeMode == EscapeMode::Second)
[5975]163            {
[5995]164                escapeSequence += c;
165                escapeMode = EscapeMode::None;
166                if      (escapeSequence == "A")
[5971]167                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       0, 0));
[5995]168                else if (escapeSequence == "B")
[5971]169                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     0, 0));
[5995]170                else if (escapeSequence == "C")
[5971]171                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    0, 0));
[5995]172                else if (escapeSequence == "D")
[5971]173                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     0, 0));
[5995]174                else if (escapeSequence == "1~" || escapeSequence == "H")
[5971]175                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     0, 0));
[5995]176                else if (escapeSequence == "2~")
[5971]177                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   0, 0));
[5995]178                else if (escapeSequence == "3~")
[5973]179                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   0, 0));
[5995]180                else if (escapeSequence == "4~" || escapeSequence == "F")
[5971]181                    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      0, 0));
[5995]182                else if (escapeSequence == "5~")
[6010]183                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   0, 0));
[5995]184                else if (escapeSequence == "6~")
[6010]185                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, 0, 0));
[5971]186                else
[5975]187                    // Waiting for sequence to complete
[5995]188                    // If the user presses ESC and then '[' or 'O' while the loop is not
189                    // running (for instance while loading), the whole sequence gets dropped
190                    escapeMode = EscapeMode::Second;
[5971]191            }
[5975]192            else // not in an escape sequence OR user might have pressed just ESC
[5971]193            {
[5995]194                if (escapeMode == EscapeMode::First)
[5975]195                {
[5983]196                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, c, 0));
[5995]197                    escapeMode = EscapeMode::None;
[5975]198                }
[5971]199                if (c == '\033')
[5975]200                {
[5995]201                    escapeMode = EscapeMode::First;
202                    escapeSequence.clear();
[5975]203                }
[5971]204                else
205                {
206                    KeyCode::ByEnum code;
207                    switch (c)
208                    {
[5995]209                    case '\n'  : case '\r': code = KeyCode::Return; break;
210                    case '\177': case '\b': code = KeyCode::Back;   break;
211                    case '\t'             : code = KeyCode::Tab;    break;
[5971]212                    default:
213                        // We don't encode the key code (would be a very large switch)
214                        // because the InputBuffer will only insert the text anyway
215                        // Replacement character is simply KeyCode::A
216                        code = KeyCode::A;
217                    }
218                    this->buffer_->buttonPressed(KeyEvent(code, c, 0));
219                }
220            }
221        }
[6015]222        // Reset error flags in std::cin
223        std::cin.clear();
[5973]224
[5975]225        // If there is still an escape key pending (escape key ONLY), then
[5995]226        // it sure isn't an escape sequence anymore
227        if (escapeMode == EscapeMode::First)
[5983]228            this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, '\033', 0));
[5975]229
[6013]230        // Determine terminal width and height
[6014]231        this->lastTerminalWidth_ = this->terminalWidth_;
232        this->lastTerminalHeight_ = this->terminalHeight_;
[6013]233        this->getTerminalSize();
234
[6014]235        int heightDiff = this->terminalHeight_ - this->lastTerminalHeight_;
236        if (this->bStatusPrinted_ && heightDiff < 0)
237        {
[6015]238            // Terminal width has shrunk. The cursor will still be on the input line,
[6014]239            // but that line might very well be the last
240            int newLines = std::min((int)this->statusLineWidths_.size(), -heightDiff);
[6089]241            // Scroll terminal to create new lines
[6103]242            this->cout_ << "\033[" << newLines << 'S';
[6014]243        }
244
[6013]245        if (!this->bStatusPrinted_ && this->willPrintStatusLines())
246        {
[6089]247            // Scroll console to make way for status lines
[6103]248            this->cout_ << "\033[" << this->statusLineWidths_.size() << 'S';
[6013]249            this->bStatusPrinted_ = true;
250        }
[6103]251
[6417]252        // We always assume that the cursor is on the input line.
[6103]253        // But we cannot always be sure about that, esp. if we scroll the console
254        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
255        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'A';
256
[6014]257        // Erase status and input lines
[6037]258        this->cout_ << "\033[1G\033[J";
[6013]259        this->printInputLine();
[6004]260        this->printStatusLines();
[6037]261        this->cout_.flush();
[6103]262
263        // Process output written to std::cout
[6417]264        std::cout.flush();
[6103]265        if (!this->origCout_.str().empty())
266        {
[6417]267            this->shell_->addOutput(this->origCout_.str(), Shell::None);
[6103]268            this->origCout_.str("");
269        }
[5971]270    }
271
[6417]272    void IOConsole::printOutputLine(const std::string& text, Shell::LineType type)
[5971]273    {
[6044]274/*
[5971]275        // Colour line
[6417]276        switch (type)
[5971]277        {
[6417]278        case Shell::None:    this->cout_ << "\033[37m"; break;
279        case Shell::Error:   this->cout_ << "\033[91m"; break;
280        case Shell::Warning: this->cout_ << "\033[31m"; break;
281        case Shell::Info:    this->cout_ << "\033[34m"; break;
282        case Shell::Debug:   this->cout_ << "\033[36m"; break;
283        case Shell::Verbose: this->cout_ << "\033[35m"; break;
284        case Shell::Ultra:   this->cout_ << "\033[37m"; break;
[5972]285        default: break;
[5971]286        }
[5995]287*/
[5971]288
289        // Print output line
[6417]290        this->cout_ << text;
[5971]291
[5983]292        // Reset colour to white
[6037]293//        this->cout_ << "\033[37m";
[5971]294    }
295
296    void IOConsole::printInputLine()
297    {
[5995]298        // Set cursor to the beginning of the line and erase the line
[6037]299        this->cout_ << "\033[1G\033[K";
[5995]300        // Indicate a command prompt
[6037]301        this->cout_ << this->promptString_;
[5995]302        // Save cursor position
[6037]303        this->cout_ << "\033[s";
[5995]304        // Print command line buffer
[6037]305        this->cout_ << this->shell_->getInput();
[5995]306        // Restore cursor position and move it to the right
[6037]307        this->cout_ << "\033[u";
[5971]308        if (this->buffer_->getCursorPosition() > 0)
[6417]309            this->cout_ << "\033[" << this->buffer_->getCursorPosition() << 'C';
[5971]310    }
311
[5995]312    void IOConsole::printStatusLines()
313    {
[6013]314        if (this->willPrintStatusLines())
[5995]315        {
[6013]316            // Save cursor position
[6037]317            this->cout_ << "\033[s";
[6013]318            // Move cursor down (don't create a new line here because the buffer might flush then!)
[6089]319            this->cout_ << "\033[1B\033[1G";
[6044]320            this->cout_ << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
321            this->cout_ <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
[6013]322            // Restore cursor position
[6037]323            this->cout_ << "\033[u";
[6004]324            this->bStatusPrinted_ = true;
[5995]325        }
[6013]326        else
327            this->bStatusPrinted_ = false;
[5995]328    }
329
[6015]330    void IOConsole::setTerminalMode()
[5995]331    {
[6015]332        termios new_settings;
[6417]333        this->originalTerminalSettings_ = new termios();
[6015]334
335        tcgetattr(0, this->originalTerminalSettings_);
336        new_settings = *this->originalTerminalSettings_;
337        new_settings.c_lflag &= ~(ICANON | ECHO);
338        //new_settings.c_lflag |= (ISIG | IEXTEN);
339        new_settings.c_cc[VTIME] = 0;
340        new_settings.c_cc[VMIN]  = 0;
341        tcsetattr(0, TCSANOW, &new_settings);
[6417]342        atexit(&IOConsole::resetTerminalMode);
[6013]343    }
344
[6417]345    /*static*/ void IOConsole::resetTerminalMode()
[6015]346    {
[6422]347        if (IOConsole::singletonPtr_s && IOConsole::singletonPtr_s->originalTerminalSettings_)
[6417]348        {
349            tcsetattr(0, TCSANOW, IOConsole::singletonPtr_s->originalTerminalSettings_);
350            delete IOConsole::singletonPtr_s->originalTerminalSettings_;
351            IOConsole::singletonPtr_s->originalTerminalSettings_ = 0;
352        }
[6015]353    }
354
[6013]355    void IOConsole::getTerminalSize()
356    {
[5995]357#ifdef TIOCGSIZE
358        struct ttysize win;
[6013]359        if (!ioctl(STDIN_FILENO, TIOCGSIZE, &win))
360        {
361            this->terminalWidth_  = win.ts_cols;
362            this->terminalHeight_ = win.ts_lines;
363            return;
364        }
365#elif defined TIOCGWINSZ
[5995]366        struct winsize win;
[6013]367        if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &win))
[5995]368        {
[6013]369            this->terminalWidth_  = win.ws_col;
370            this->terminalHeight_ = win.ws_row;
371            return;
[5995]372        }
[6013]373#else
374        const char* s = getenv("COLUMNS");
375        this->terminalWidth_  = s ? strtol(s, NULL, 10) : 80;
376        s = getenv("LINES");
377        this->terminalHeight_ = s ? strtol(s, NULL, 10) : 24;
378        return;
[5995]379#endif
[6013]380        this->terminalWidth_  = 80;
381        this->terminalHeight_ = 24;
[5995]382    }
383
[6417]384    inline bool IOConsole::willPrintStatusLines()
385    {
386        return !this->statusLineWidths_.empty()
387             && this->terminalWidth_  >= this->statusLineMaxWidth_
388             && this->terminalHeight_ >= this->minOutputLines_ + (int)this->statusLineWidths_.size();
389    }
390
[5971]391    // ###############################
392    // ###  ShellListener methods  ###
393    // ###############################
394
[6015]395    //! Called if only the last output-line has changed
[5971]396    void IOConsole::onlyLastLineChanged()
397    {
[5995]398        // Save cursor position and move it to the beginning of the first output line
[6089]399        this->cout_ << "\033[s\033[1A\033[1G";
[5995]400        // Erase the line
[6037]401        this->cout_ << "\033[K";
[5995]402        // Reprint the last output line
[6417]403        this->printOutputLine(this->shell_->getNewestLineIterator()->first, this->shell_->getNewestLineIterator()->second);
[5994]404        // Restore cursor
[6037]405        this->cout_ << "\033[u";
406        this->cout_.flush();
[5971]407    }
408
[6015]409    //! Called if a new output-line was added
[5971]410    void IOConsole::lineAdded()
411    {
[6417]412        int newLines = this->shell_->getNewestLineIterator()->first.size() / this->terminalWidth_ + 1;
[6103]413        // Create new lines by scrolling the screen
414        this->cout_ << "\033[" << newLines << 'S';
[6013]415        // Move cursor to the beginning of the new (last) output line
[6103]416        this->cout_ << "\033[" << newLines << "A\033[1G";
[6013]417        // Erase screen from here
[6037]418        this->cout_ << "\033[J";
[6103]419        // Print the new output lines
[6014]420        for (int i = 0; i < newLines; ++i)
[6417]421        {
422            Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
423            this->printOutputLine(it->first.substr(i*this->terminalWidth_, this->terminalWidth_), it->second);
424        }
[6013]425        // Move cursor down
[6089]426        this->cout_ << "\033[1B\033[1G";
[6013]427        // Print status and input lines
428        this->printInputLine();
[6004]429        this->printStatusLines();
[6037]430        this->cout_.flush();
[5971]431    }
[6417]432
433    //! Called if the text in the input-line has changed
434    void IOConsole::inputChanged()
435    {
436        this->printInputLine();
437        this->cout_.flush();
438    }
439
440    //! Called if the position of the cursor in the input-line has changed
441    void IOConsole::cursorChanged()
442    {
443        this->printInputLine();
444        this->cout_.flush();
445    }
[6015]446}
[5971]447
[6015]448#elif defined(ORXONOX_PLATFORM_WINDOWS)
449// ##################################
450// ###   Windows Implementation   ###
451// ##################################
452
[6417]453#include <windows.h>
454
[6015]455namespace orxonox
456{
[6417]457    //! Redirects std::cout, creates the corresponding Shell and changes the terminal mode
[6015]458    IOConsole::IOConsole()
[6417]459        : shell_(new Shell("IOConsole", false))
[6015]460        , buffer_(shell_->getInputBuffer())
[6041]461        , cout_(std::cout.rdbuf())
[6015]462        , promptString_("orxonox # ")
[6417]463        , inputLineHeight_(1)
464        , statusLines_(1)
465        , lastOutputLineHeight_(0)
[5971]466    {
[6417]467        // Disable standard this->cout_ logging
468        OutputHandler::getInstance().disableCout();
469        // Redirect std::cout to an ostringstream
470        // (Other part is in the initialiser list)
471        std::cout.rdbuf(this->origCout_.rdbuf());
472
473        this->setTerminalMode();
474        CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
475        GetConsoleScreenBufferInfo(this->stdOutHandle_, &screenBufferInfo);
476        this->terminalWidth_  = screenBufferInfo.dwSize.X;
477        this->terminalHeight_ = screenBufferInfo.dwSize.Y;
478        // Determines where we are in respect to output already written with std::cout
479        this->inputLineRow_ = screenBufferInfo.dwCursorPosition.Y;
[6015]480/*
[6417]481        this->lastTerminalWidth_  = this->terminalWidth_;
482        this->lastTerminalHeight_ = this->terminalHeight_;
483*/
[6015]484
[6417]485        // Cursor already at the end of the screen buffer?
486        // (assuming the current input line height is 1)
487        if (this->inputLineRow_ >= this->terminalHeight_ - this->statusLines_)
488            SetConsoleCursorPosition(this->stdOutHandle_, makeCOORD(0, this->terminalHeight_ - this->statusLines_));
[6015]489
[6417]490        // Prevent input line from overflowing
491        int maxInputLength = (this->terminalHeight_ - this->statusLines_) * this->terminalWidth_ - 1 - this->promptString_.size();
492        // Consider that the echo of a command might include the command plus some other characters (assumed max 80)
493        // Also put a minimum so the config file parser is not overwhelmed with the command history
494        this->buffer_->setMaxLength(std::min(8192, (maxInputLength - 80) / 2));
[6015]495
[6417]496        // Print input and status line and position cursor
497        this->inputChanged();
498        this->cursorChanged();
499        this->lastRefreshTime_ = Game::getInstance().getGameClock().getRealMicroseconds();
500        this->preUpdate(Game::getInstance().getGameClock());
501
502        this->shell_->registerListener(this);
[5971]503    }
504
[6417]505    //! Resets std::cout redirection and restores the terminal mode
[6015]506    IOConsole::~IOConsole()
[5971]507    {
[6417]508        // Process output written to std::cout in the meantime
509        std::cout.flush();
510        if (!this->origCout_.str().empty())
511            this->shell_->addOutput(this->origCout_.str(), Shell::None);
[6015]512
[6417]513        this->shell_->unregisterListener(this);
514
515        // Erase input and status lines
516        COORD pos = {0, this->inputLineRow_};
517        this->writeText(std::string((this->inputLineHeight_ + this->statusLines_) * this->terminalWidth_, ' '), pos);
518        // Move cursor to the beginning of the line
519        SetConsoleCursorPosition(stdOutHandle_, pos);
520
521        // Restore this->cout_ redirection
522        std::cout.rdbuf(this->cout_.rdbuf());
[6037]523        // Enable standard this->cout_ logging again
[6015]524        OutputHandler::getInstance().enableCout();
[6417]525
526        resetTerminalMode();
527        this->shell_->destroy();
[5971]528    }
529
[6417]530    //! Processes the pending input key strokes, refreshes the status lines and handles std::cout (redirected)
531    void IOConsole::preUpdate(const Clock& time)
[5983]532    {
[6417]533        // Process input
534        while (true)
[6015]535        {
[6417]536            DWORD count;
537            INPUT_RECORD inrec;
538            PeekConsoleInput(this->stdInHandle_, &inrec, 1, &count);
539            if (count == 0)
[6015]540                break;
[6417]541            ReadConsoleInput(this->stdInHandle_, &inrec, 1, &count);
542            if (inrec.EventType == KEY_EVENT && inrec.Event.KeyEvent.bKeyDown)
543            {
544                // Process keyboard modifiers (Ctrl, Alt and Shift)
545                DWORD modifiersIn = inrec.Event.KeyEvent.dwControlKeyState;
546                int modifiersOut = 0;
547                if ((modifiersIn & (LEFT_ALT_PRESSED  | RIGHT_ALT_PRESSED))  != 0)
548                    modifiersOut |= KeyboardModifier::Alt;
549                if ((modifiersIn & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0)
550                    modifiersOut |= KeyboardModifier::Ctrl;
551                if ((modifiersIn & SHIFT_PRESSED) != 0)
552                    modifiersOut |= KeyboardModifier::Shift;
553
554                // ASCII character (0 for special keys)
555                char asciiChar = inrec.Event.KeyEvent.uChar.AsciiChar;
556
557                // Process special keys and if not found, use Key::A as dummy (InputBuffer uses the ASCII text anyway)
558                switch (inrec.Event.KeyEvent.wVirtualKeyCode)
559                {
560                case VK_BACK:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Back,     asciiChar, modifiersOut)); break;
561                case VK_TAB:    this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab,      asciiChar, modifiersOut)); break;
562                case VK_RETURN: this->buffer_->buttonPressed(KeyEvent(KeyCode::Return,   asciiChar, modifiersOut)); break;
563                case VK_PAUSE:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Pause,    asciiChar, modifiersOut)); break;
564                case VK_ESCAPE: this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape,   asciiChar, modifiersOut)); break;
565                case VK_SPACE:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Space,    asciiChar, modifiersOut)); break;
566                case VK_PRIOR:  this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   asciiChar, modifiersOut)); break;
567                case VK_NEXT:   this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, asciiChar, modifiersOut)); break;
568                case VK_END:    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      asciiChar, modifiersOut)); break;
569                case VK_HOME:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     asciiChar, modifiersOut)); break;
570                case VK_LEFT:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     asciiChar, modifiersOut)); break;
571                case VK_UP:     this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       asciiChar, modifiersOut)); break;
572                case VK_RIGHT:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    asciiChar, modifiersOut)); break;
573                case VK_DOWN:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     asciiChar, modifiersOut)); break;
574                case VK_INSERT: this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   asciiChar, modifiersOut)); break;
575                case VK_DELETE: this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   asciiChar, modifiersOut)); break;
576                default:        this->buffer_->buttonPressed(KeyEvent(KeyCode::A,        asciiChar, modifiersOut));
577                }
578            }
[6015]579        }
580
[6417]581        // TODO: Respect screen buffer size changes
582/*
583        // The user can manually adjust the screen buffer size on Windows
584        // And we don't want to screw the console because of that
[6015]585        this->lastTerminalWidth_ = this->terminalWidth_;
586        this->lastTerminalHeight_ = this->terminalHeight_;
[6417]587        this->getTerminalSize(); // Also sets this->inputLineRow_ according to the cursor position
588        // Is there still enough space below the cursor for the status line(s)?
589        if (this->inputLineRow_ >= this->terminalHeight_ - this->statusLines_)
590            this->moveCursor(0, -this->inputLineRow_ + this->terminalHeight_ - this->statusLines_ - 1);
[6015]591*/
[6417]592
593        // Refresh status line 5 times per second
594        if (time.getMicroseconds() > this->lastRefreshTime_ + 1000000)
595        {
596            this->printStatusLines();
597            this->lastRefreshTime_ = time.getMicroseconds();
598        }
599
600        // Process output written to std::cout
601        std::cout.flush();
602        if (!this->origCout_.str().empty())
603        {
604            this->shell_->addOutput(this->origCout_.str(), Shell::None);
605            this->origCout_.str("");
606        }
[5983]607    }
608
[6417]609    //! Prints output text. Similar to writeText, but sets the colour according to the output level
610    void IOConsole::printOutputLine(const std::string& text, Shell::LineType type, const COORD& pos)
[6015]611    {
[6417]612        // Colour line
613        WORD colour = 0;
614        switch (type)
615        {
616        case Shell::Error:   colour = FOREGROUND_INTENSITY                    | FOREGROUND_RED; break;
617        case Shell::Warning: colour = FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED; break;
618        case Shell::Info:
619        case Shell::Debug:
620        case Shell::Verbose:
621        case Shell::Ultra:   colour = FOREGROUND_INTENSITY                                     ; break;
622        case Shell::Command: colour =                        FOREGROUND_GREEN                  | FOREGROUND_BLUE; break;
623        case Shell::Hint:    colour =                        FOREGROUND_GREEN | FOREGROUND_RED                  ; break;
624        default:             colour =                        FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE; break;
625        }
[5983]626
[6417]627        // Print output line
628        this->writeText(text, pos, colour);
[5971]629    }
630
[6417]631    //! Prints all status lines with current content
[6015]632    void IOConsole::printStatusLines()
633    {
[6417]634        // Prepare text to be written
635        std::ostringstream oss;
636        oss << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
637        oss <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
638        // Clear rest of the line by inserting spaces
639        oss << std::string(this->terminalWidth_ - oss.str().size(), ' ');
640        this->writeText(oss.str(), makeCOORD(0, this->inputLineRow_ + this->inputLineHeight_), FOREGROUND_GREEN);
[6015]641    }
642
[6417]643    //! Changes the console parameters for unbuffered input
[6015]644    void IOConsole::setTerminalMode()
645    {
[6417]646        // Set the console mode to no-echo, raw input, and no window or mouse events
647        this->stdOutHandle_ = GetStdHandle(STD_OUTPUT_HANDLE);
648        this->stdInHandle_  = GetStdHandle(STD_INPUT_HANDLE);
649        if (this->stdInHandle_ == INVALID_HANDLE_VALUE
650            || !GetConsoleMode(this->stdInHandle_, &this->originalTerminalSettings_)
651            || !SetConsoleMode(this->stdInHandle_, 0))
652        {
653            COUT(1) << "Error: Could not set Windows console settings" << std::endl;
654            return;
655        }
656        FlushConsoleInputBuffer(this->stdInHandle_);
[6015]657    }
658
[6417]659    //! Restores the console parameters
[6015]660    void IOConsole::resetTerminalMode()
661    {
[6417]662        SetConsoleMode(this->stdInHandle_, this->originalTerminalSettings_);
[6015]663    }
664
[6417]665    //! Sets this->terminalWidth_ and this->terminalHeight_
[6015]666    void IOConsole::getTerminalSize()
667    {
[6417]668        CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
669        GetConsoleScreenBufferInfo(this->stdOutHandle_, &screenBufferInfo);
670        this->terminalWidth_  = screenBufferInfo.dwSize.X;
671        this->terminalHeight_ = screenBufferInfo.dwSize.Y;
[6015]672    }
673
[6417]674    //! Writes arbitrary text to the console with a certain colour and screen buffer position
675    void IOConsole::writeText(const std::string& text, const COORD& coord, WORD attributes)
676    {
677        DWORD count;
678        WriteConsoleOutputCharacter(stdOutHandle_, text.c_str(), text.size(), coord, &count);
679        FillConsoleOutputAttribute(stdOutHandle_, attributes, text.size(), coord, &count);
680    }
681
682    /** Scrolls the console screen buffer to create empty lines above the input line.
683    @details
684        If the input and status lines are already at the bottom of the screen buffer
685        the whole output gets scrolled up. In the other case the input and status
686        lines get scrolled down.
687        In any case the status and input lines get scrolled down as far as possible.
688    @param lines
689        Number of lines to be inserted. Behavior for negative values is undefined.
690    */
691    void IOConsole::createNewOutputLines(int lines)
692    {
693        CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
694        // Lines to scroll input/status down (if possible)
695        int linesDown = clamp(terminalHeight_ - inputLineRow_ - inputLineHeight_ - statusLines_, 0, lines);
696        if (linesDown > 0)
697        {
698            // Scroll input and status lines down
699            SMALL_RECT oldRect = {0, this->inputLineRow_,
700                this->terminalWidth_ - 1, this->inputLineRow_ + this->inputLineHeight_ + this->statusLines_ - 1};
701            this->inputLineRow_ += linesDown;
702            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, this->inputLineRow_), &fillChar);
703            // Move cursor down to the new bottom so the user can see the status lines
704            COORD pos = {0, this->inputLineRow_ + this->inputLineHeight_ - 1 + this->statusLines_};
705            SetConsoleCursorPosition(stdOutHandle_, pos);
706            // Get cursor back to the right position
707            this->cursorChanged();
708        }
709        // Check how many lines we still have to scroll up the output
710        if (lines - linesDown > 0)
711        {
712            // Scroll output up
713            SMALL_RECT oldRect = {0, lines - linesDown, this->terminalWidth_ - 1, this->inputLineRow_ - 1};
714            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, 0), &fillChar);
715        }
716    }
717
[6015]718    // ###############################
719    // ###  ShellListener methods  ###
720    // ###############################
721
[6417]722    //! Called if the text in the input line has changed
723    void IOConsole::inputChanged()
724    {
725        int newInputLineLength = this->promptString_.size() + this->shell_->getInput().size();
726        int newInputLineHeight = 1 + newInputLineLength / this->terminalWidth_;
727        int newLines = newInputLineHeight - this->inputLineHeight_;
728        if (newLines > 0)
729        {
730            // Abuse this function to scroll the console
731            this->createNewOutputLines(newLines);
732            // Either Compensate for side effects (input/status lines scrolled down)
733            // or we have to do this anyway (output scrolled up)
734            this->inputLineRow_ -= newLines;
735        }
736        else if (newLines < 0)
737        {
738            // Scroll status lines up
739            int statusLineRow = this->inputLineRow_ + this->inputLineHeight_;
740            SMALL_RECT oldRect = {0, statusLineRow, this->terminalWidth_ - 1, statusLineRow + this->statusLines_};
741            CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
742            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, statusLineRow + newLines), &fillChar);
743            // Clear potential leftovers
744            if (-newLines - this->statusLines_ > 0)
745            {
746                COORD pos = {0, this->inputLineRow_ + newInputLineHeight + this->statusLines_};
747                this->writeText(std::string((-newLines - this->statusLines_) * this->terminalWidth_, ' '), pos);
748            }
749        }
750        this->inputLineHeight_ = newInputLineHeight;
751
752        // Print the whole line, including spaces that erase leftovers
753        std::string inputLine = this->promptString_ + this->shell_->getInput();
754        inputLine += std::string(this->terminalWidth_ - newInputLineLength % this->terminalWidth_, ' ');
755        this->writeText(inputLine, makeCOORD(0, this->inputLineRow_), FOREGROUND_GREEN | FOREGROUND_INTENSITY);
756        // If necessary, move cursor
757        if (newLines != 0)
758            this->cursorChanged();
759    }
760
761    //! Called if the position of the cursor in the input-line has changed
762    void IOConsole::cursorChanged()
763    {
764        int rawCursorPos = this->promptString_.size() + this->buffer_->getCursorPosition();
765        // Compensate for cursor further to the right than the terminal width
766        COORD pos;
767        pos.X = rawCursorPos % this->terminalWidth_;
768        pos.Y = this->inputLineRow_ + rawCursorPos / this->terminalWidth_;
769        SetConsoleCursorPosition(stdOutHandle_, pos);
770    }
771
[6015]772    //! Called if only the last output-line has changed
773    void IOConsole::onlyLastLineChanged()
774    {
[6417]775        int newLineHeight = 1 + this->shell_->getNewestLineIterator()->first.size() / this->terminalWidth_;
776        // Compute the number of new lines needed
777        int newLines = newLineHeight - this->lastOutputLineHeight_;
778        this->lastOutputLineHeight_ = newLineHeight;
779        // Scroll console if necessary
780        if (newLines > 0) // newLines < 0 is assumed impossible
781            this->createNewOutputLines(newLines);
782        Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
783        this->printOutputLine(it->first, it->second, makeCOORD(0, this->inputLineRow_ - newLineHeight));
[6015]784    }
785
[6417]786    //! Called if a new output line was added
[6015]787    void IOConsole::lineAdded()
788    {
[6417]789        Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
790        // Scroll console
791        this->lastOutputLineHeight_ = 1 + it->first.size() / this->terminalWidth_;
792        this->createNewOutputLines(this->lastOutputLineHeight_);
793        // Write the text
794        COORD pos = {0, this->inputLineRow_ - this->lastOutputLineHeight_};
795        this->printOutputLine(it->first, it->second, pos);
[6015]796    }
[5971]797}
[6015]798
799#endif /* ORXONOX_PLATFORM_UNIX */
Note: See TracBrowser for help on using the repository browser.