Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 8729 was 8729, checked in by rgrieder, 13 years ago

Merged unity_build branch back to trunk.

Features:

  • Implemented fully automatic build units to speed up compilation if requested
  • Added DOUT macro for quick debug output
  • Activated text colouring in the POSIX IOConsole
  • DeclareToluaInterface is not necessary anymore

Improvements:

  • Output levels now change appropriately when switch back and forth from dev mode
  • Log level for the file output is now also correct during startup
  • Removed some header file dependencies in core and tools to speed up compilation

no more file for command line options

  • Improved util::tribool by adapting some concepts from boost::tribool

Regressions:

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