Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 6196 was 6196, checked in by landauf, 14 years ago

fixed some warnings in IOConsole

  • 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    // ###############################
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->preUpdate(Game::getInstance().getGameClock());
119    }
120
121    IOConsole::~IOConsole()
122    {
123        // Empty all buffers
124        this->preUpdate(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::preUpdate(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, this->shell_->getNewestLineIterator()->second);
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        {
419            Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
420            this->printOutputLine(it->first.substr(i*this->terminalWidth_, this->terminalWidth_), it->second);
421        }
422        // Move cursor down
423        this->cout_ << "\033[1B\033[1G";
424        // Print status and input lines
425        this->printInputLine();
426        this->printStatusLines();
427        this->cout_.flush();
428    }
429
430    //! Called if the text in the input-line has changed
431    void IOConsole::inputChanged()
432    {
433        this->printInputLine();
434        this->cout_.flush();
435    }
436
437    //! Called if the position of the cursor in the input-line has changed
438    void IOConsole::cursorChanged()
439    {
440        this->printInputLine();
441        this->cout_.flush();
442    }
443}
444
445#elif defined(ORXONOX_PLATFORM_WINDOWS)
446// ##################################
447// ###   Windows Implementation   ###
448// ##################################
449
450#include <windows.h>
451
452namespace orxonox
453{
454    //! Redirects std::cout, creates the corresponding Shell and changes the terminal mode
455    IOConsole::IOConsole()
456        : shell_(new Shell("IOConsole", false))
457        , buffer_(shell_->getInputBuffer())
458        , cout_(std::cout.rdbuf())
459        , promptString_("orxonox # ")
460        , inputLineHeight_(1)
461        , statusLines_(1)
462        , lastOutputLineHeight_(0)
463    {
464        // Disable standard this->cout_ logging
465        OutputHandler::getInstance().disableCout();
466        // Redirect std::cout to an ostringstream
467        // (Other part is in the initialiser list)
468        std::cout.rdbuf(this->origCout_.rdbuf());
469
470        this->setTerminalMode();
471        CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
472        GetConsoleScreenBufferInfo(this->stdOutHandle_, &screenBufferInfo);
473        this->terminalWidth_  = screenBufferInfo.dwSize.X;
474        this->terminalHeight_ = screenBufferInfo.dwSize.Y;
475        // Determines where we are in respect to output already written with std::cout
476        this->inputLineRow_ = screenBufferInfo.dwCursorPosition.Y;
477/*
478        this->lastTerminalWidth_  = this->terminalWidth_;
479        this->lastTerminalHeight_ = this->terminalHeight_;
480*/
481
482        // Cursor already at the end of the screen buffer?
483        // (assuming the current input line height is 1)
484        if (this->inputLineRow_ >= this->terminalHeight_ - this->statusLines_)
485            SetConsoleCursorPosition(this->stdOutHandle_, makeCOORD(0, this->terminalHeight_ - this->statusLines_));
486
487        // Prevent input line from overflowing
488        int maxInputLength = (this->terminalHeight_ - this->statusLines_) * this->terminalWidth_ - 1 - this->promptString_.size();
489        // Consider that the echo of a command might include the command plus some other characters (assumed max 80)
490        // Also put a minimum so the config file parser is not overwhelmed with the command history
491        this->buffer_->setMaxLength(std::min(8192, (maxInputLength - 80) / 2));
492
493        // Print input and status line and position cursor
494        this->inputChanged();
495        this->cursorChanged();
496        this->lastRefreshTime_ = Game::getInstance().getGameClock().getRealMicroseconds();
497        this->preUpdate(Game::getInstance().getGameClock());
498
499        this->shell_->registerListener(this);
500    }
501
502    //! Resets std::cout redirection and restores the terminal mode
503    IOConsole::~IOConsole()
504    {
505        this->shell_->unregisterListener(this);
506        // Empty all buffers
507        this->preUpdate(Game::getInstance().getGameClock());
508
509        // Erase input and status lines
510        COORD pos = {0, this->inputLineRow_};
511        this->writeText(std::string((this->inputLineHeight_ + this->statusLines_) * this->terminalWidth_, ' '), pos);
512        // Move cursor to the beginning of the line
513        SetConsoleCursorPosition(stdOutHandle_, pos);
514
515        // Restore this->cout_ redirection
516        std::cout.rdbuf(this->cout_.rdbuf());
517        // Enable standard this->cout_ logging again
518        OutputHandler::getInstance().enableCout();
519
520        resetTerminalMode();
521        this->shell_->destroy();
522    }
523
524    //! Processes the pending input key strokes, refreshes the status lines and handles std::cout (redirected)
525    void IOConsole::preUpdate(const Clock& time)
526    {
527        // Process input
528        while (true)
529        {
530            DWORD count;
531            INPUT_RECORD inrec;
532            PeekConsoleInput(this->stdInHandle_, &inrec, 1, &count);
533            if (count == 0)
534                break;
535            ReadConsoleInput(this->stdInHandle_, &inrec, 1, &count);
536            if (inrec.EventType == KEY_EVENT && inrec.Event.KeyEvent.bKeyDown)
537            {
538                // Process keyboard modifiers (Ctrl, Alt and Shift)
539                DWORD modifiersIn = inrec.Event.KeyEvent.dwControlKeyState;
540                int modifiersOut = 0;
541                if ((modifiersIn & (LEFT_ALT_PRESSED  | RIGHT_ALT_PRESSED))  != 0)
542                    modifiersOut |= KeyboardModifier::Alt;
543                if ((modifiersIn & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0)
544                    modifiersOut |= KeyboardModifier::Ctrl;
545                if ((modifiersIn & SHIFT_PRESSED) != 0)
546                    modifiersOut |= KeyboardModifier::Shift;
547
548                // ASCII character (0 for special keys)
549                char asciiChar = inrec.Event.KeyEvent.uChar.AsciiChar;
550
551                // Process special keys and if not found, use Key::A as dummy (InputBuffer uses the ASCII text anyway)
552                switch (inrec.Event.KeyEvent.wVirtualKeyCode)
553                {
554                case VK_BACK:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Back,     asciiChar, modifiersOut)); break;
555                case VK_TAB:    this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab,      asciiChar, modifiersOut)); break;
556                case VK_RETURN: this->buffer_->buttonPressed(KeyEvent(KeyCode::Return,   asciiChar, modifiersOut)); break;
557                case VK_PAUSE:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Pause,    asciiChar, modifiersOut)); break;
558                case VK_ESCAPE: this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape,   asciiChar, modifiersOut)); break;
559                case VK_SPACE:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Space,    asciiChar, modifiersOut)); break;
560                case VK_PRIOR:  this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   asciiChar, modifiersOut)); break;
561                case VK_NEXT:   this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, asciiChar, modifiersOut)); break;
562                case VK_END:    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      asciiChar, modifiersOut)); break;
563                case VK_HOME:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     asciiChar, modifiersOut)); break;
564                case VK_LEFT:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     asciiChar, modifiersOut)); break;
565                case VK_UP:     this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       asciiChar, modifiersOut)); break;
566                case VK_RIGHT:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    asciiChar, modifiersOut)); break;
567                case VK_DOWN:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     asciiChar, modifiersOut)); break;
568                case VK_INSERT: this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   asciiChar, modifiersOut)); break;
569                case VK_DELETE: this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   asciiChar, modifiersOut)); break;
570                default:        this->buffer_->buttonPressed(KeyEvent(KeyCode::A,        asciiChar, modifiersOut));
571                }
572            }
573        }
574
575        // TODO: Respect screen buffer size changes
576/*
577        // The user can manually adjust the screen buffer size on Windows
578        // And we don't want to screw the console because of that
579        this->lastTerminalWidth_ = this->terminalWidth_;
580        this->lastTerminalHeight_ = this->terminalHeight_;
581        this->getTerminalSize(); // Also sets this->inputLineRow_ according to the cursor position
582        // Is there still enough space below the cursor for the status line(s)?
583        if (this->inputLineRow_ >= this->terminalHeight_ - this->statusLines_)
584            this->moveCursor(0, -this->inputLineRow_ + this->terminalHeight_ - this->statusLines_ - 1);
585*/
586
587        // Refresh status line 5 times per second
588        if (time.getMicroseconds() > this->lastRefreshTime_ + 1000000)
589        {
590            this->printStatusLines();
591            this->lastRefreshTime_ = time.getMicroseconds();
592        }
593
594        // Process output written to std::cout
595        if (!this->origCout_.str().empty())
596        {
597            this->shell_->addOutputLine(this->origCout_.str(), Shell::None);
598            this->origCout_.str("");
599        }
600    }
601
602    //! Prints output text. Similar to writeText, but sets the colour according to the output level
603    void IOConsole::printOutputLine(const std::string& text, Shell::LineType type, const COORD& pos)
604    {
605        // Colour line
606        WORD colour = 0;
607        switch (type)
608        {
609        case Shell::Error:   colour = FOREGROUND_INTENSITY                    | FOREGROUND_RED; break;
610        case Shell::Warning: colour = FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED; break;
611        case Shell::Info:
612        case Shell::Debug:
613        case Shell::Verbose:
614        case Shell::Ultra:   colour = FOREGROUND_INTENSITY                                     ; break;
615        case Shell::Command: colour =                        FOREGROUND_GREEN                  | FOREGROUND_BLUE; break;
616        case Shell::Hint:    colour =                        FOREGROUND_GREEN | FOREGROUND_RED                  ; break;
617        default:             colour =                        FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE; break;
618        }
619
620        // Print output line
621        this->writeText(text, pos, colour);
622    }
623
624    //! Prints all status lines with current content
625    void IOConsole::printStatusLines()
626    {
627        // Prepare text to be written
628        std::ostringstream oss;
629        oss << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
630        oss <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
631        // Clear rest of the line by inserting spaces
632        oss << std::string(this->terminalWidth_ - oss.str().size(), ' ');
633        this->writeText(oss.str(), makeCOORD(0, this->inputLineRow_ + this->inputLineHeight_), FOREGROUND_GREEN);
634    }
635
636    //! Changes the console parameters for unbuffered input
637    void IOConsole::setTerminalMode()
638    {
639        // Set the console mode to no-echo, raw input, and no window or mouse events
640        this->stdOutHandle_ = GetStdHandle(STD_OUTPUT_HANDLE);
641        this->stdInHandle_  = GetStdHandle(STD_INPUT_HANDLE);
642        if (this->stdInHandle_ == INVALID_HANDLE_VALUE
643            || !GetConsoleMode(this->stdInHandle_, &this->originalTerminalSettings_)
644            || !SetConsoleMode(this->stdInHandle_, 0))
645        {
646            COUT(1) << "Error: Could not set Windows console settings" << std::endl;
647            return;
648        }
649        FlushConsoleInputBuffer(this->stdInHandle_);
650    }
651
652    //! Restores the console parameters
653    void IOConsole::resetTerminalMode()
654    {
655        SetConsoleMode(this->stdInHandle_, this->originalTerminalSettings_);
656    }
657
658    //! Sets this->terminalWidth_ and this->terminalHeight_
659    void IOConsole::getTerminalSize()
660    {
661        CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
662        GetConsoleScreenBufferInfo(this->stdOutHandle_, &screenBufferInfo);
663        this->terminalWidth_  = screenBufferInfo.dwSize.X;
664        this->terminalHeight_ = screenBufferInfo.dwSize.Y;
665    }
666
667    //! Writes arbitrary text to the console with a certain colour and screen buffer position
668    void IOConsole::writeText(const std::string& text, const COORD& coord, WORD attributes)
669    {
670        DWORD count;
671        WriteConsoleOutputCharacter(stdOutHandle_, text.c_str(), text.size(), coord, &count);
672        FillConsoleOutputAttribute(stdOutHandle_, attributes, text.size(), coord, &count);
673    }
674
675    /** Scrolls the console screen buffer to create empty lines above the input line.
676    @details
677        If the input and status lines are already at the bottom of the screen buffer
678        the whole output gets scrolled up. In the other case the input and status
679        lines get scrolled down.
680        In any case the status and input lines get scrolled down as far as possible.
681    @param lines
682        Number of lines to be inserted. Behavior for negative values is undefined.
683    */
684    void IOConsole::createNewOutputLines(int lines)
685    {
686        CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
687        // Lines to scroll input/status down (if possible)
688        int linesDown = clamp(terminalHeight_ - inputLineRow_ - inputLineHeight_ - statusLines_, 0, lines);
689        if (linesDown > 0)
690        {
691            // Scroll input and status lines down
692            SMALL_RECT oldRect = {0, this->inputLineRow_,
693                this->terminalWidth_ - 1, this->inputLineRow_ + this->inputLineHeight_ + this->statusLines_ - 1};
694            this->inputLineRow_ += linesDown;
695            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, this->inputLineRow_), &fillChar);
696            // Move cursor down to the new bottom so the user can see the status lines
697            COORD pos = {0, this->inputLineRow_ + this->inputLineHeight_ - 1 + this->statusLines_};
698            SetConsoleCursorPosition(stdOutHandle_, pos);
699            // Get cursor back to the right position
700            this->cursorChanged();
701        }
702        // Check how many lines we still have to scroll up the output
703        if (lines - linesDown > 0)
704        {
705            // Scroll output up
706            SMALL_RECT oldRect = {0, lines - linesDown, this->terminalWidth_ - 1, this->inputLineRow_ - 1};
707            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, 0), &fillChar);
708        }
709    }
710
711    // ###############################
712    // ###  ShellListener methods  ###
713    // ###############################
714
715    //! Called if the text in the input line has changed
716    void IOConsole::inputChanged()
717    {
718        int newInputLineLength = this->promptString_.size() + this->shell_->getInput().size();
719        int newInputLineHeight = 1 + newInputLineLength / this->terminalWidth_;
720        int newLines = newInputLineHeight - this->inputLineHeight_;
721        if (newLines > 0)
722        {
723            // Abuse this function to scroll the console
724            this->createNewOutputLines(newLines);
725            // Either Compensate for side effects (input/status lines scrolled down)
726            // or we have to do this anyway (output scrolled up)
727            this->inputLineRow_ -= newLines;
728        }
729        else if (newLines < 0)
730        {
731            // Scroll status lines up
732            int statusLineRow = this->inputLineRow_ + this->inputLineHeight_;
733            SMALL_RECT oldRect = {0, statusLineRow, this->terminalWidth_ - 1, statusLineRow + this->statusLines_};
734            CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
735            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, statusLineRow + newLines), &fillChar);
736            // Clear potential leftovers
737            if (-newLines - this->statusLines_ > 0)
738            {
739                COORD pos = {0, this->inputLineRow_ + newInputLineHeight + this->statusLines_};
740                this->writeText(std::string((-newLines - this->statusLines_) * this->terminalWidth_, ' '), pos);
741            }
742        }
743        this->inputLineHeight_ = newInputLineHeight;
744
745        // Print the whole line, including spaces that erase leftovers
746        std::string inputLine = this->promptString_ + this->shell_->getInput();
747        inputLine += std::string(this->terminalWidth_ - newInputLineLength % this->terminalWidth_, ' ');
748        this->writeText(inputLine, makeCOORD(0, this->inputLineRow_), FOREGROUND_GREEN | FOREGROUND_INTENSITY);
749        // If necessary, move cursor
750        if (newLines != 0)
751            this->cursorChanged();
752    }
753
754    //! Called if the position of the cursor in the input-line has changed
755    void IOConsole::cursorChanged()
756    {
757        int rawCursorPos = this->promptString_.size() + this->buffer_->getCursorPosition();
758        // Compensate for cursor further to the right than the terminal width
759        COORD pos;
760        pos.X = rawCursorPos % this->terminalWidth_;
761        pos.Y = this->inputLineRow_ + rawCursorPos / this->terminalWidth_;
762        SetConsoleCursorPosition(stdOutHandle_, pos);
763    }
764
765    //! Called if only the last output-line has changed
766    void IOConsole::onlyLastLineChanged()
767    {
768        int newLineHeight = 1 + this->shell_->getNewestLineIterator()->first.size() / this->terminalWidth_;
769        // Compute the number of new lines needed
770        int newLines = newLineHeight - this->lastOutputLineHeight_;
771        this->lastOutputLineHeight_ = newLineHeight;
772        // Scroll console if necessary
773        if (newLines > 0) // newLines < 0 is assumed impossible
774            this->createNewOutputLines(newLines);
775        Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
776        this->printOutputLine(it->first, it->second, makeCOORD(0, this->inputLineRow_ - newLineHeight));
777    }
778
779    //! Called if a new output line was added
780    void IOConsole::lineAdded()
781    {
782        Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
783        // Scroll console
784        this->lastOutputLineHeight_ = 1 + it->first.size() / this->terminalWidth_;
785        this->createNewOutputLines(this->lastOutputLineHeight_);
786        // Write the text
787        COORD pos = {0, this->inputLineRow_ - this->lastOutputLineHeight_};
788        this->printOutputLine(it->first, it->second, pos);
789    }
790}
791
792#endif /* ORXONOX_PLATFORM_UNIX */
Note: See TracBrowser for help on using the repository browser.