Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

removed onlyLastLineChanged() callback from ShellListener, since it's not possible to print a line of output word by word anymore, instead only whole lines are printed.

  • 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("IOConsole", 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::DebugOutput:     colour = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
204
205            case Shell::UserError:       colour = FOREGROUND_INTENSITY | FOREGROUND_RED | 0                | 0              ; break;
206            case Shell::UserWarning:     colour = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | 0              ; break;
207            case Shell::UserStatus:      colour = FOREGROUND_INTENSITY | 0              | FOREGROUND_GREEN | 0              ; break;
208            case Shell::UserInfo:        colour = FOREGROUND_INTENSITY | 0              | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
209
210            case Shell::InternalError:   colour = 0                    | FOREGROUND_RED | 0                | 0              ; break;
211            case Shell::InternalWarning: colour = 0                    | FOREGROUND_RED | FOREGROUND_GREEN | 0              ; break;
212            case Shell::InternalStatus:  colour = 0                    | 0              | FOREGROUND_GREEN | 0              ; break;
213            case Shell::InternalInfo:    colour = 0                    | 0              | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
214
215            case Shell::Verbose:         colour = FOREGROUND_INTENSITY | 0              | 0                | FOREGROUND_BLUE; break;
216            case Shell::VerboseMore:     colour = 0                    | 0              | 0                | FOREGROUND_BLUE; break;
217            case Shell::VerboseUltra:    colour = 0                    | 0              | 0                | FOREGROUND_BLUE; break;
218
219            case Shell::Command:         colour = FOREGROUND_INTENSITY | FOREGROUND_RED | 0                | FOREGROUND_BLUE; break;
220            case Shell::Hint:            colour = 0                    | FOREGROUND_RED | 0                | FOREGROUND_BLUE; break;
221
222            default:                     colour = 0                    | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
223        }
224
225        // Print output line
226        this->writeText(text, pos, colour);
227    }
228
229    //! Prints all status lines with current content
230    void IOConsole::printStatusLines()
231    {
232        // Prepare text to be written
233        std::ostringstream oss;
234        oss << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
235        oss <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
236        // Clear rest of the line by inserting spaces
237        oss << std::string(this->terminalWidth_ - oss.str().size(), ' ');
238        this->writeText(oss.str(), makeCOORD(0, this->inputLineRow_ + this->inputLineHeight_), FOREGROUND_GREEN);
239    }
240
241    //! Changes the console parameters for unbuffered input
242    void IOConsole::setTerminalMode()
243    {
244        // Set the console mode to no-echo, raw input, and no window or mouse events
245        this->stdOutHandle_ = GetStdHandle(STD_OUTPUT_HANDLE);
246        this->stdInHandle_  = GetStdHandle(STD_INPUT_HANDLE);
247        if (this->stdInHandle_ == INVALID_HANDLE_VALUE
248            || !GetConsoleMode(this->stdInHandle_, &this->originalTerminalSettings_)
249            || !SetConsoleMode(this->stdInHandle_, 0))
250        {
251            COUT(1) << "Error: Could not set Windows console settings" << std::endl;
252            return;
253        }
254        FlushConsoleInputBuffer(this->stdInHandle_);
255    }
256
257    //! Restores the console parameters
258    void IOConsole::resetTerminalMode()
259    {
260        SetConsoleMode(this->stdInHandle_, this->originalTerminalSettings_);
261    }
262
263    //! Sets this->terminalWidth_ and this->terminalHeight_
264    void IOConsole::getTerminalSize()
265    {
266        CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
267        GetConsoleScreenBufferInfo(this->stdOutHandle_, &screenBufferInfo);
268        this->terminalWidth_  = screenBufferInfo.dwSize.X;
269        this->terminalHeight_ = screenBufferInfo.dwSize.Y;
270    }
271
272    //! Writes arbitrary text to the console with a certain colour and screen buffer position
273    void IOConsole::writeText(const std::string& text, const COORD& coord, WORD attributes)
274    {
275        DWORD count;
276        WriteConsoleOutputCharacter(stdOutHandle_, text.c_str(), text.size(), coord, &count);
277        FillConsoleOutputAttribute(stdOutHandle_, attributes, text.size(), coord, &count);
278    }
279
280    /** Scrolls the console screen buffer to create empty lines above the input line.
281    @details
282        If the input and status lines are already at the bottom of the screen buffer
283        the whole output gets scrolled up. In the other case the input and status
284        lines get scrolled down.
285        In any case the status and input lines get scrolled down as far as possible.
286    @param lines
287        Number of lines to be inserted. Behavior for negative values is undefined.
288    */
289    void IOConsole::createNewOutputLines(int lines)
290    {
291        CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
292        // Lines to scroll input/status down (if possible)
293        int linesDown = clamp(terminalHeight_ - inputLineRow_ - inputLineHeight_ - statusLines_, 0, lines);
294        if (linesDown > 0)
295        {
296            // Scroll input and status lines down
297            SMALL_RECT oldRect = {0, this->inputLineRow_,
298                this->terminalWidth_ - 1, this->inputLineRow_ + this->inputLineHeight_ + this->statusLines_ - 1};
299            this->inputLineRow_ += linesDown;
300            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, this->inputLineRow_), &fillChar);
301            // Move cursor down to the new bottom so the user can see the status lines
302            COORD pos = {0, this->inputLineRow_ + this->inputLineHeight_ - 1 + this->statusLines_};
303            SetConsoleCursorPosition(stdOutHandle_, pos);
304            // Get cursor back to the right position
305            this->cursorChanged();
306        }
307        // Check how many lines we still have to scroll up the output
308        if (lines - linesDown > 0)
309        {
310            // Scroll output up
311            SMALL_RECT oldRect = {0, lines - linesDown, this->terminalWidth_ - 1, this->inputLineRow_ - 1};
312            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, 0), &fillChar);
313        }
314    }
315
316    // ###############################
317    // ###  ShellListener methods  ###
318    // ###############################
319
320    //! Called if all output-lines have to be reprinted
321    void IOConsole::linesChanged()
322    {
323        // Method only gets called upon start to draw all the lines
324        // or when scrolling. But scrolling is disabled and the output
325        // is already in std::cout when we start the IOConsole
326    }
327
328    //! Called if a command is about to be executed
329    void IOConsole::executed()
330    {
331        this->shell_->addOutput(this->promptString_ + this->shell_->getInput() + '\n', Shell::Command);
332    }
333
334    //! Called if the console gets closed
335    void IOConsole::exit()
336    {
337        // Exit is not an option, just do nothing (Shell doesn't really exit too)
338    }
339
340    //! Called if the text in the input line has changed
341    void IOConsole::inputChanged()
342    {
343        int newInputLineLength = this->promptString_.size() + this->shell_->getInput().size();
344        int newInputLineHeight = 1 + newInputLineLength / this->terminalWidth_;
345        int newLines = newInputLineHeight - this->inputLineHeight_;
346        if (newLines > 0)
347        {
348            // Abuse this function to scroll the console
349            this->createNewOutputLines(newLines);
350            // Either Compensate for side effects (input/status lines scrolled down)
351            // or we have to do this anyway (output scrolled up)
352            this->inputLineRow_ -= newLines;
353        }
354        else if (newLines < 0)
355        {
356            // Scroll status lines up
357            int statusLineRow = this->inputLineRow_ + this->inputLineHeight_;
358            SMALL_RECT oldRect = {0, statusLineRow, this->terminalWidth_ - 1, statusLineRow + this->statusLines_};
359            CHAR_INFO fillChar = {{' '}, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED};
360            ScrollConsoleScreenBuffer(stdOutHandle_, &oldRect, NULL, makeCOORD(0, statusLineRow + newLines), &fillChar);
361            // Clear potential leftovers
362            if (-newLines - this->statusLines_ > 0)
363            {
364                COORD pos = {0, this->inputLineRow_ + newInputLineHeight + this->statusLines_};
365                this->writeText(std::string((-newLines - this->statusLines_) * this->terminalWidth_, ' '), pos);
366            }
367        }
368        this->inputLineHeight_ = newInputLineHeight;
369
370        // Print the whole line, including spaces that erase leftovers
371        std::string inputLine = this->promptString_ + this->shell_->getInput();
372        inputLine += std::string(this->terminalWidth_ - newInputLineLength % this->terminalWidth_, ' ');
373        this->writeText(inputLine, makeCOORD(0, this->inputLineRow_), FOREGROUND_GREEN | FOREGROUND_INTENSITY);
374        // If necessary, move cursor
375        if (newLines != 0)
376            this->cursorChanged();
377    }
378
379    //! Called if the position of the cursor in the input-line has changed
380    void IOConsole::cursorChanged()
381    {
382        int rawCursorPos = this->promptString_.size() + this->buffer_->getCursorPosition();
383        // Compensate for cursor further to the right than the terminal width
384        COORD pos;
385        pos.X = rawCursorPos % this->terminalWidth_;
386        pos.Y = this->inputLineRow_ + rawCursorPos / this->terminalWidth_;
387        SetConsoleCursorPosition(stdOutHandle_, pos);
388    }
389
390    //! Called if a new output line was added
391    void IOConsole::lineAdded()
392    {
393        Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
394        // Scroll console
395        this->lastOutputLineHeight_ = 1 + it->first.size() / this->terminalWidth_;
396        this->createNewOutputLines(this->lastOutputLineHeight_);
397        // Write the text
398        COORD pos = {0, this->inputLineRow_ - this->lastOutputLineHeight_};
399        this->printOutputLine(it->first, it->second, pos);
400    }
401}
Note: See TracBrowser for help on using the repository browser.