Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/gamestates2/src/libraries/core/IOConsole.cc @ 7072

Last change on this file since 7072 was 6595, checked in by rgrieder, 15 years ago

Merged remaining revisions from gamestate to gamestates2.

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