Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 6214 was 6214, checked in by scheusso, 14 years ago

a small fix in IOConsole
some changes in GUI-system and preparation for keybindings menu
fix in menu handling

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