Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/consolecommands/src/libraries/core/IOConsole.cc @ 6372

Last change on this file since 6372 was 6214, checked in by scheusso, 15 years ago

a small fix in IOConsole
some changes in GUI-system and preparation for keybindings menu
fix in menu handling

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