Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

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

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