Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/presentation2/src/libraries/core/IOConsole.cc @ 6180

Last change on this file since 6180 was 6180, checked in by rgrieder, 14 years ago

Extended Shell line colouring in order to distinguish output from COUT, entered commands and hints.

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