Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Fixed Linux build and removed a warning

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