Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Performance and robustness improvements for the IOConsole under Windows.
There should only be one way to screw the console now: Change the console properties manually at runtime (right click on the taskbar icon), esp. screen buffer size.
If you can find another bug, please let me know.

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