Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/output/src/libraries/core/command/IOConsoleWindows.cc @ 8805

Last change on this file since 8805 was 8805, checked in by landauf, 13 years ago

added new output level "message" for output directed to the user

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