Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/IOConsoleWindows.cc @ 12398

Last change on this file since 12398 was 11071, checked in by landauf, 9 years ago

merged branch cpp11_v3 back to trunk

  • Property svn:eol-style set to native
File size: 19.6 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 *      Reto Grieder
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "IOConsole.h"
30
31#include <iomanip>
32#include <iostream>
33
34#include "util/Clock.h"
35#include "util/Math.h"
36#include "util/output/ConsoleWriter.h"
37#include "util/output/OutputManager.h"
38#include "core/Game.h"
39#include "core/input/InputBuffer.h"
40
41namespace orxonox
42{
43    IOConsole* IOConsole::singletonPtr_s = nullptr;
44
45    //! Redirects std::cout, creates the corresponding Shell and changes the terminal mode
46    IOConsole::IOConsole()
47        : shell_(new Shell("Console", false))
48        , buffer_(shell_->getInputBuffer())
49        , cout_(std::cout.rdbuf())
50        , promptString_("orxonox # ")
51        , inputLineHeight_(1)
52        , statusLines_(1)
53        , lastOutputLineHeight_(0)
54    {
55        // Disable standard this->cout_ logging
56        OutputManager::getInstance().getConsoleWriter()->disable();
57        // Redirect std::cout to an ostringstream
58        // (Other part is in the initialiser list)
59        std::cout.rdbuf(this->origCout_.rdbuf());
60
61        this->setTerminalMode();
62        CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
63        GetConsoleScreenBufferInfo(this->stdOutHandle_, &screenBufferInfo);
64        this->terminalWidth_  = screenBufferInfo.dwSize.X;
65        this->terminalHeight_ = screenBufferInfo.dwSize.Y;
66        // Determines where we are in respect to output already written with std::cout
67        this->inputLineRow_ = screenBufferInfo.dwCursorPosition.Y;
68/*
69        this->lastTerminalWidth_  = this->terminalWidth_;
70        this->lastTerminalHeight_ = this->terminalHeight_;
71*/
72
73        // Cursor already at the end of the screen buffer?
74        // (assuming the current input line height is 1)
75        if (this->inputLineRow_ >= this->terminalHeight_ - this->statusLines_)
76            SetConsoleCursorPosition(this->stdOutHandle_, makeCOORD(0, this->terminalHeight_ - this->statusLines_));
77
78        // Prevent input line from overflowing
79        int maxInputLength = (this->terminalHeight_ - this->statusLines_) * this->terminalWidth_ - 1 - this->promptString_.size();
80        // Consider that the echo of a command might include the command plus some other characters (assumed max 80)
81        // Also put a minimum so the config file parser is not overwhelmed with the command history
82        this->buffer_->setMaxLength(std::min(8192, (maxInputLength - 80) / 2));
83
84        // Print input and status line and position cursor
85        this->inputChanged();
86        this->cursorChanged();
87        this->lastRefreshTime_ = Game::getInstance().getGameClock().getRealMicroseconds();
88        this->preUpdate(Game::getInstance().getGameClock());
89
90        this->shell_->registerListener(this);
91    }
92
93    //! Resets std::cout redirection and restores the terminal mode
94    IOConsole::~IOConsole()
95    {
96        // Process output written to std::cout in the meantime
97        std::cout.flush();
98        if (!this->origCout_.str().empty())
99            this->shell_->addOutput(this->origCout_.str(), Shell::LineType::Cout);
100
101        this->shell_->unregisterListener(this);
102
103        // Erase input and status lines
104        COORD pos = makeCOORD(0, this->inputLineRow_);
105        this->writeText(std::string((this->inputLineHeight_ + this->statusLines_) * this->terminalWidth_, ' '), pos);
106        // Move cursor to the beginning of the line
107        SetConsoleCursorPosition(stdOutHandle_, pos);
108
109        // Restore this->cout_ redirection
110        std::cout.rdbuf(this->cout_.rdbuf());
111        // Enable standard this->cout_ logging again
112        OutputManager::getInstance().getConsoleWriter()->enable();
113
114        resetTerminalMode();
115        delete this->shell_;
116    }
117
118    //! Processes the pending input key strokes, refreshes the status lines and handles std::cout (redirected)
119    void IOConsole::preUpdate(const Clock& time)
120    {
121        // Process input
122        while (true)
123        {
124            DWORD count;
125            INPUT_RECORD inrec;
126            PeekConsoleInput(this->stdInHandle_, &inrec, 1, &count);
127            if (count == 0)
128                break;
129            ReadConsoleInput(this->stdInHandle_, &inrec, 1, &count);
130            if (inrec.EventType == KEY_EVENT && inrec.Event.KeyEvent.bKeyDown)
131            {
132                // Process keyboard modifiers (Ctrl, Alt and Shift)
133                DWORD modifiersIn = inrec.Event.KeyEvent.dwControlKeyState;
134                int modifiersOut = 0;
135                if ((modifiersIn & (LEFT_ALT_PRESSED  | RIGHT_ALT_PRESSED))  != 0)
136                    modifiersOut |= KeyboardModifier::Alt;
137                if ((modifiersIn & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0)
138                    modifiersOut |= KeyboardModifier::Ctrl;
139                if ((modifiersIn & SHIFT_PRESSED) != 0)
140                    modifiersOut |= KeyboardModifier::Shift;
141
142                // ASCII character (0 for special keys)
143                char asciiChar = inrec.Event.KeyEvent.uChar.AsciiChar;
144
145                // Process special keys and if not found, use Key::A as dummy (InputBuffer uses the ASCII text anyway)
146                switch (inrec.Event.KeyEvent.wVirtualKeyCode)
147                {
148                case VK_BACK:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Back,     asciiChar, modifiersOut)); break;
149                case VK_TAB:    this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab,      asciiChar, modifiersOut)); break;
150                case VK_RETURN: this->buffer_->buttonPressed(KeyEvent(KeyCode::Return,   asciiChar, modifiersOut)); break;
151                case VK_PAUSE:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Pause,    asciiChar, modifiersOut)); break;
152                case VK_ESCAPE: this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape,   asciiChar, modifiersOut)); break;
153                case VK_SPACE:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Space,    asciiChar, modifiersOut)); break;
154                case VK_PRIOR:  this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   asciiChar, modifiersOut)); break;
155                case VK_NEXT:   this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, asciiChar, modifiersOut)); break;
156                case VK_END:    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      asciiChar, modifiersOut)); break;
157                case VK_HOME:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     asciiChar, modifiersOut)); break;
158                case VK_LEFT:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     asciiChar, modifiersOut)); break;
159                case VK_UP:     this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       asciiChar, modifiersOut)); break;
160                case VK_RIGHT:  this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    asciiChar, modifiersOut)); break;
161                case VK_DOWN:   this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     asciiChar, modifiersOut)); break;
162                case VK_INSERT: this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   asciiChar, modifiersOut)); break;
163                case VK_DELETE: this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   asciiChar, modifiersOut)); break;
164                default:        this->buffer_->buttonPressed(KeyEvent(KeyCode::A,        asciiChar, modifiersOut));
165                }
166            }
167        }
168
169        // TODO: Respect screen buffer size changes
170/*
171        // The user can manually adjust the screen buffer size on Windows
172        // And we don't want to screw the console because of that
173        this->lastTerminalWidth_ = this->terminalWidth_;
174        this->lastTerminalHeight_ = this->terminalHeight_;
175        this->getTerminalSize(); // Also sets this->inputLineRow_ according to the cursor position
176        // Is there still enough space below the cursor for the status line(s)?
177        if (this->inputLineRow_ >= this->terminalHeight_ - this->statusLines_)
178            this->moveCursor(0, -this->inputLineRow_ + this->terminalHeight_ - this->statusLines_ - 1);
179*/
180
181        // Refresh status line 5 times per second
182        if (time.getMicroseconds() > this->lastRefreshTime_ + 1000000)
183        {
184            this->printStatusLines();
185            this->lastRefreshTime_ = time.getMicroseconds();
186        }
187
188        // Process output written to std::cout
189        std::cout.flush();
190        if (!this->origCout_.str().empty())
191        {
192            this->shell_->addOutput(this->origCout_.str(), Shell::LineType::Cout);
193            this->origCout_.str("");
194        }
195    }
196
197    //! Prints output text. Similar to writeText, but sets the colour according to the output level
198    void IOConsole::printOutputLine(const std::string& text, Shell::LineType type, const COORD& pos)
199    {
200        // Colour line
201        WORD colour = 0;
202        switch (type)
203        {
204            case Shell::LineType::Message:
205            case Shell::LineType::DebugOutput:     colour = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
206
207            case Shell::LineType::UserError:       colour = FOREGROUND_INTENSITY | FOREGROUND_RED | 0                | 0              ; break;
208            case Shell::LineType::UserWarning:     colour = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | 0              ; break;
209            case Shell::LineType::UserStatus:      colour = FOREGROUND_INTENSITY | 0              | FOREGROUND_GREEN | 0              ; break;
210            case Shell::LineType::UserInfo:        colour = FOREGROUND_INTENSITY | 0              | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
211
212            case Shell::LineType::InternalError:   colour = 0                    | FOREGROUND_RED | 0                | 0              ; break;
213            case Shell::LineType::InternalWarning: colour = 0                    | FOREGROUND_RED | FOREGROUND_GREEN | 0              ; break;
214            case Shell::LineType::InternalStatus:  colour = 0                    | 0              | FOREGROUND_GREEN | 0              ; break;
215            case Shell::LineType::InternalInfo:    colour = 0                    | 0              | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
216
217            case Shell::LineType::Verbose:         colour = FOREGROUND_INTENSITY | 0              | 0                | FOREGROUND_BLUE; break;
218            case Shell::LineType::VerboseMore:     colour = FOREGROUND_INTENSITY | 0              | 0                | FOREGROUND_BLUE; break;
219            case Shell::LineType::VerboseUltra:    colour = FOREGROUND_INTENSITY | 0              | 0                | FOREGROUND_BLUE; break;
220
221            case Shell::LineType::Command:         colour = FOREGROUND_INTENSITY | FOREGROUND_RED | 0                | FOREGROUND_BLUE; break;
222            case Shell::LineType::Hint:            colour = 0                    | FOREGROUND_RED | 0                | FOREGROUND_BLUE; break;
223
224            default:                               colour = 0                    | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
225        }
226
227        // Print output line
228        this->writeText(text, pos, colour);
229    }
230
231    //! Prints all status lines with current content
232    void IOConsole::printStatusLines()
233    {
234        // Prepare text to be written
235        std::ostringstream oss;
236        oss << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
237        oss <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
238        // Clear rest of the line by inserting spaces
239        oss << std::string(this->terminalWidth_ - oss.str().size(), ' ');
240        this->writeText(oss.str(), makeCOORD(0, this->inputLineRow_ + this->inputLineHeight_), FOREGROUND_GREEN);
241    }
242
243    //! Changes the console parameters for unbuffered input
244    void IOConsole::setTerminalMode()
245    {
246        // Set the console mode to no-echo, raw input, and no window or mouse events
247        this->stdOutHandle_ = GetStdHandle(STD_OUTPUT_HANDLE);
248        this->stdInHandle_  = GetStdHandle(STD_INPUT_HANDLE);
249        if (this->stdInHandle_ == INVALID_HANDLE_VALUE
250            || !GetConsoleMode(this->stdInHandle_, &this->originalTerminalSettings_)
251            || !SetConsoleMode(this->stdInHandle_, 0))
252        {
253            orxout(user_error) << "Could not set Windows console settings" << endl;
254            return;
255        }
256        FlushConsoleInputBuffer(this->stdInHandle_);
257    }
258
259    //! Restores the console parameters
260    void IOConsole::resetTerminalMode()
261    {
262        SetConsoleMode(this->stdInHandle_, this->originalTerminalSettings_);
263    }
264
265    //! Sets this->terminalWidth_ and this->terminalHeight_
266    void IOConsole::getTerminalSize()
267    {
268        CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
269        GetConsoleScreenBufferInfo(this->stdOutHandle_, &screenBufferInfo);
270        this->terminalWidth_  = screenBufferInfo.dwSize.X;
271        this->terminalHeight_ = screenBufferInfo.dwSize.Y;
272    }
273
274    //! Writes arbitrary text to the console with a certain colour and screen buffer position
275    void IOConsole::writeText(const std::string& text, const COORD& coord, WORD attributes)
276    {
277        DWORD count;
278        WriteConsoleOutputCharacter(stdOutHandle_, text.c_str(), text.size(), coord, &count);
279        FillConsoleOutputAttribute(stdOutHandle_, attributes, text.size(), coord, &count);
280    }
281
282    /** Scrolls the console screen buffer to create empty lines above the input line.
283    @details
284        If the input and status lines are already at the bottom of the screen buffer
285        the whole output gets scrolled up. In the other case the input and status
286        lines get scrolled down.
287        In any case the status and input lines get scrolled down as far as possible.
288    @param lines
289        Number of lines to be inserted. Behavior for negative values is undefined.
290    */
291    void IOConsole::createNewOutputLines(int lines)
292    {
293        CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
294        // Lines to scroll input/status down (if possible)
295        int linesDown = clamp(terminalHeight_ - inputLineRow_ - inputLineHeight_ - statusLines_, 0, lines);
296        if (linesDown > 0)
297        {
298            // Scroll input and status lines down
299            SMALL_RECT oldRect = makeSMALL_RECT(0, this->inputLineRow_,
300                this->terminalWidth_ - 1, this->inputLineRow_ + this->inputLineHeight_ + this->statusLines_ - 1);
301            this->inputLineRow_ += linesDown;
302            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, nullptr, makeCOORD(0, this->inputLineRow_), &fillChar);
303            // Move cursor down to the new bottom so the user can see the status lines
304            COORD pos = makeCOORD(0, this->inputLineRow_ + this->inputLineHeight_ - 1 + this->statusLines_);
305            SetConsoleCursorPosition(stdOutHandle_, pos);
306            // Get cursor back to the right position
307            this->cursorChanged();
308        }
309        // Check how many lines we still have to scroll up the output
310        if (lines - linesDown > 0)
311        {
312            // Scroll output up
313            SMALL_RECT oldRect = makeSMALL_RECT(0, lines - linesDown, this->terminalWidth_ - 1, this->inputLineRow_ - 1);
314            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, nullptr, makeCOORD(0, 0), &fillChar);
315        }
316    }
317
318    // ###############################
319    // ###  ShellListener methods  ###
320    // ###############################
321
322    //! Called if all output-lines have to be reprinted
323    void IOConsole::linesChanged()
324    {
325        // Method only gets called upon start to draw all the lines
326        // or when scrolling. But scrolling is disabled and the output
327        // is already in std::cout when we start the IOConsole
328    }
329
330    //! Called if a command is about to be executed
331    void IOConsole::executed()
332    {
333        this->shell_->addOutput(this->promptString_ + this->shell_->getInput(), Shell::LineType::Command);
334    }
335
336    //! Called if the console gets closed
337    void IOConsole::exit()
338    {
339        // Exit is not an option, just do nothing (Shell doesn't really exit too)
340    }
341
342    //! Called if the text in the input line has changed
343    void IOConsole::inputChanged()
344    {
345        int newInputLineLength = this->promptString_.size() + this->shell_->getInput().size();
346        int newInputLineHeight = 1 + newInputLineLength / this->terminalWidth_;
347        int newLines = newInputLineHeight - this->inputLineHeight_;
348        if (newLines > 0)
349        {
350            // Abuse this function to scroll the console
351            this->createNewOutputLines(newLines);
352            // Either Compensate for side effects (input/status lines scrolled down)
353            // or we have to do this anyway (output scrolled up)
354            this->inputLineRow_ -= newLines;
355        }
356        else if (newLines < 0)
357        {
358            // Scroll status lines up
359            int statusLineRow = this->inputLineRow_ + this->inputLineHeight_;
360            SMALL_RECT oldRect = makeSMALL_RECT(0, statusLineRow, this->terminalWidth_ - 1, statusLineRow + this->statusLines_);
361            CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
362            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, nullptr, makeCOORD(0, statusLineRow + newLines), &fillChar);
363            // Clear potential leftovers
364            if (-newLines - this->statusLines_ > 0)
365            {
366                COORD pos = makeCOORD(0, this->inputLineRow_ + newInputLineHeight + this->statusLines_);
367                this->writeText(std::string((-newLines - this->statusLines_) * this->terminalWidth_, ' '), pos);
368            }
369        }
370        this->inputLineHeight_ = newInputLineHeight;
371
372        // Print the whole line, including spaces that erase leftovers
373        std::string inputLine = this->promptString_ + this->shell_->getInput();
374        inputLine += std::string(this->terminalWidth_ - newInputLineLength % this->terminalWidth_, ' ');
375        this->writeText(inputLine, makeCOORD(0, this->inputLineRow_), FOREGROUND_GREEN | FOREGROUND_INTENSITY);
376        // If necessary, move cursor
377        if (newLines != 0)
378            this->cursorChanged();
379    }
380
381    //! Called if the position of the cursor in the input-line has changed
382    void IOConsole::cursorChanged()
383    {
384        int rawCursorPos = this->promptString_.size() + this->buffer_->getCursorPosition();
385        // Compensate for cursor further to the right than the terminal width
386        COORD pos;
387        pos.X = rawCursorPos % this->terminalWidth_;
388        pos.Y = this->inputLineRow_ + rawCursorPos / this->terminalWidth_;
389        SetConsoleCursorPosition(stdOutHandle_, pos);
390    }
391
392    //! Called if a new output line was added
393    void IOConsole::lineAdded()
394    {
395        Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
396        // Scroll console
397        this->lastOutputLineHeight_ = 1 + it->first.size() / this->terminalWidth_;
398        this->createNewOutputLines(this->lastOutputLineHeight_);
399        // Write the text
400        COORD pos = makeCOORD(0, this->inputLineRow_ - this->lastOutputLineHeight_);
401        this->printOutputLine(it->first, it->second, pos);
402    }
403}
Note: See TracBrowser for help on using the repository browser.