Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/usability/src/libraries/core/command/IOConsoleWindows.cc @ 8187

Last change on this file since 8187 was 7287, checked in by rgrieder, 14 years ago

Split IOConsole in two separate files for better browsing.

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