Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/IOConsolePOSIX.cc @ 8858

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

merged output branch back to trunk.

Changes:

  • you have to include util/Output.h instead of util/Debug.h
  • COUT(x) is now called orxout(level)
  • output levels are now defined by an enum instead of numbers. see util/Output.h for the definition
  • it's possible to use output contexts with orxout(level, context). see util/Output.h for some common contexts. you can define more contexts
  • you must use 'endl' at the end of an output message, '\n' does not flush the message

Output levels:

  • instead of COUT(0) use orxout()
  • instead of COUT(1) use orxout(user_error) or orxout(internal_error)
  • instead of COUT(2) use orxout(user_warning) or orxout(internal_warning)
  • instead of COUT(3) use orxout(user_status/user_info) or orxout(internal_status/internal_info)
  • instead of COUT(4) use orxout(verbose)
  • instead of COUT(5) use orxout(verbose_more)
  • instead of COUT(6) use orxout(verbose_ultra)

Guidelines:

  • user_* levels are for the user, visible in the console and the log-file
  • internal_* levels are for developers, visible in the log-file
  • verbose_* levels are for debugging, only visible if the context of the output is activated

Usage in C++:

  • orxout() << "message" << endl;
  • orxout(level) << "message" << endl;
  • orxout(level, context) << "message" << endl;

Usage in Lua:

  • orxout("message")
  • orxout(orxonox.level.levelname, "message")
  • orxout(orxonox.level.levelname, "context", "message")

Usage in Tcl (and in the in-game-console):

  • orxout levelname message
  • orxout_context levelname context message
  • shortcuts: log message, error message, warning message, status message, info message, debug message
  • Property svn:eol-style set to native
File size: 15.9 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 *      Oliver Scheuss
24 *      Reto Grieder
25 *   Co-authors:
26 *      ...
27 *
28 */
29
30#include "IOConsole.h"
31
32#include <iomanip>
33#include <iostream>
34#include <termios.h>
35#include <sys/ioctl.h>
36
37#include "util/Clock.h"
38#include "util/Math.h"
39#include "util/output/ConsoleWriter.h"
40#include "core/Game.h"
41#include "core/input/InputBuffer.h"
42
43namespace orxonox
44{
45    IOConsole* IOConsole::singletonPtr_s = NULL;
46
47    namespace EscapeMode
48    {
49        enum Value
50        {
51            None,
52            First,
53            Second
54        };
55    }
56
57    IOConsole::IOConsole()
58        : shell_(new Shell("Console", false))
59        , buffer_(shell_->getInputBuffer())
60        , cout_(std::cout.rdbuf())
61        , promptString_("orxonox # ")
62        , bStatusPrinted_(false)
63        , originalTerminalSettings_(0)
64    {
65        this->setTerminalMode();
66        this->shell_->registerListener(this);
67
68        // Manually set the widths of the individual status lines
69        this->statusLineWidths_.push_back(29);
70        this->statusLineMaxWidth_ = 29;
71
72        this->getTerminalSize();
73        this->lastTerminalWidth_ = this->terminalWidth_;
74        this->lastTerminalHeight_ = this->terminalHeight_;
75
76        // Disable standard std::cout logging
77        ConsoleWriter::getInstance().disable();
78        // Redirect std::cout to an ostringstream
79        // (Other part is in the initialiser list)
80        std::cout.rdbuf(this->origCout_.rdbuf());
81
82        // Make sure we make way for the status lines
83        this->preUpdate(Game::getInstance().getGameClock());
84    }
85
86    IOConsole::~IOConsole()
87    {
88        // Process output written to std::cout in the meantime
89        std::cout.flush();
90        if (!this->origCout_.str().empty())
91            this->shell_->addOutput(this->origCout_.str(), Shell::Cout);
92        // Erase input and status lines
93        this->cout_ << "\033[1G\033[J";
94        // Move cursor to the bottom
95        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
96        // Scroll terminal to compensate for erased lines
97        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'T';
98
99        resetTerminalMode();
100        this->shell_->destroy();
101
102        // Restore this->cout_ redirection
103        std::cout.rdbuf(this->cout_.rdbuf());
104        // Enable standard std::cout logging again
105        ConsoleWriter::getInstance().enable();
106    }
107
108    void IOConsole::preUpdate(const Clock& time)
109    {
110        unsigned char c;
111        std::string escapeSequence;
112        EscapeMode::Value escapeMode = EscapeMode::None;
113        while (std::cin.good())
114        {
115            c = std::cin.get();
116            if (!std::cin.good())
117                break;
118
119            if (escapeMode == EscapeMode::First && (c == '[' || c=='O') )
120                escapeMode = EscapeMode::Second;
121            // Get Alt+Tab combination when switching applications
122            else if (escapeMode == EscapeMode::First && c == '\t')
123            {
124                this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab, '\t', KeyboardModifier::Alt));
125                escapeMode = EscapeMode::None;
126            }
127            else if (escapeMode == EscapeMode::Second)
128            {
129                escapeSequence += c;
130                escapeMode = EscapeMode::None;
131                if      (escapeSequence == "A")
132                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       0, 0));
133                else if (escapeSequence == "B")
134                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     0, 0));
135                else if (escapeSequence == "C")
136                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    0, 0));
137                else if (escapeSequence == "D")
138                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     0, 0));
139                else if (escapeSequence == "1~" || escapeSequence == "H")
140                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     0, 0));
141                else if (escapeSequence == "2~")
142                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   0, 0));
143                else if (escapeSequence == "3~")
144                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   0, 0));
145                else if (escapeSequence == "4~" || escapeSequence == "F")
146                    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      0, 0));
147                else if (escapeSequence == "5~")
148                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   0, 0));
149                else if (escapeSequence == "6~")
150                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, 0, 0));
151                else
152                    // Waiting for sequence to complete
153                    // If the user presses ESC and then '[' or 'O' while the loop is not
154                    // running (for instance while loading), the whole sequence gets dropped
155                    escapeMode = EscapeMode::Second;
156            }
157            else // not in an escape sequence OR user might have pressed just ESC
158            {
159                if (escapeMode == EscapeMode::First)
160                {
161                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, c, 0));
162                    escapeMode = EscapeMode::None;
163                }
164                if (c == '\033')
165                {
166                    escapeMode = EscapeMode::First;
167                    escapeSequence.clear();
168                }
169                else
170                {
171                    KeyCode::ByEnum code;
172                    switch (c)
173                    {
174                    case '\n'  : case '\r': code = KeyCode::Return; break;
175                    case '\177': case '\b': code = KeyCode::Back;   break;
176                    case '\t'             : code = KeyCode::Tab;    break;
177                    default:
178                        // We don't encode the key code (would be a very large switch)
179                        // because the InputBuffer will only insert the text anyway
180                        // Replacement character is simply KeyCode::A
181                        code = KeyCode::A;
182                    }
183                    this->buffer_->buttonPressed(KeyEvent(code, c, 0));
184                }
185            }
186        }
187        // Reset error flags in std::cin
188        std::cin.clear();
189
190        // If there is still an escape key pending (escape key ONLY), then
191        // it sure isn't an escape sequence anymore
192        if (escapeMode == EscapeMode::First)
193            this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, '\033', 0));
194
195        // Determine terminal width and height
196        this->lastTerminalWidth_ = this->terminalWidth_;
197        this->lastTerminalHeight_ = this->terminalHeight_;
198        this->getTerminalSize();
199
200        int heightDiff = this->terminalHeight_ - this->lastTerminalHeight_;
201        if (this->bStatusPrinted_ && heightDiff < 0)
202        {
203            // Terminal width has shrunk. The cursor will still be on the input line,
204            // but that line might very well be the last
205            int newLines = std::min((int)this->statusLineWidths_.size(), -heightDiff);
206            // Scroll terminal to create new lines
207            this->cout_ << "\033[" << newLines << 'S';
208        }
209
210        if (!this->bStatusPrinted_ && this->willPrintStatusLines())
211        {
212            // Scroll console to make way for status lines
213            this->cout_ << "\033[" << this->statusLineWidths_.size() << 'S';
214            this->bStatusPrinted_ = true;
215        }
216
217        // We always assume that the cursor is on the input line.
218        // But we cannot always be sure about that, esp. if we scroll the console
219        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
220        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'A';
221
222        // Erase status and input lines
223        this->cout_ << "\033[1G\033[J";
224        this->printInputLine();
225        this->printStatusLines();
226        this->cout_.flush();
227
228        // Process output written to std::cout
229        std::cout.flush();
230        if (!this->origCout_.str().empty())
231        {
232            this->shell_->addOutput(this->origCout_.str(), Shell::Cout);
233            this->origCout_.str("");
234        }
235    }
236
237    void IOConsole::printOutputLine(const std::string& text, Shell::LineType type)
238    {
239        // Colour line
240        switch (type)
241        {
242            case Shell::Message:
243            case Shell::DebugOutput:     this->cout_ << "\033[0m"; break;
244
245            case Shell::UserError:       this->cout_ << "\033[91m"; break;
246            case Shell::UserWarning:     this->cout_ << "\033[93m"; break;
247            case Shell::UserStatus:      this->cout_ << "\033[92m"; break;
248            case Shell::UserInfo:        this->cout_ << "\033[96m"; break;
249
250            case Shell::InternalError:   this->cout_ << "\033[31m"; break;
251            case Shell::InternalWarning: this->cout_ << "\033[33m"; break;
252            case Shell::InternalStatus:  this->cout_ << "\033[32m"; break;
253            case Shell::InternalInfo:    this->cout_ << "\033[36m"; break;
254
255            case Shell::Verbose:         this->cout_ << "\033[94m"; break;
256            case Shell::VerboseMore:     this->cout_ << "\033[34m"; break;
257            case Shell::VerboseUltra:    this->cout_ << "\033[34m"; break;
258
259            case Shell::Command:         this->cout_ << "\033[95m"; break;
260            case Shell::Hint:            this->cout_ << "\033[35m"; break;
261
262            default:                     this->cout_ << "\033[37m"; break;
263        }
264
265        // Print output line
266        this->cout_ << text;
267
268        // Reset colour atributes
269        this->cout_ << "\033[0m";
270    }
271
272    void IOConsole::printInputLine()
273    {
274        // Set cursor to the beginning of the line and erase the line
275        this->cout_ << "\033[1G\033[K";
276        // Indicate a command prompt
277        this->cout_ << this->promptString_;
278        // Save cursor position
279        this->cout_ << "\033[s";
280        // Print command line buffer
281        this->cout_ << this->shell_->getInput();
282        // Restore cursor position and move it to the right
283        this->cout_ << "\033[u";
284        if (this->buffer_->getCursorPosition() > 0)
285            this->cout_ << "\033[" << this->buffer_->getCursorPosition() << 'C';
286    }
287
288    void IOConsole::printStatusLines()
289    {
290        if (this->willPrintStatusLines())
291        {
292            // Save cursor position
293            this->cout_ << "\033[s";
294            // Move cursor down (don't create a new line here because the buffer might flush then!)
295            this->cout_ << "\033[1B\033[1G";
296            this->cout_ << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
297            this->cout_ <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
298            // Restore cursor position
299            this->cout_ << "\033[u";
300            this->bStatusPrinted_ = true;
301        }
302        else
303            this->bStatusPrinted_ = false;
304    }
305
306    void IOConsole::setTerminalMode()
307    {
308        termios new_settings;
309        this->originalTerminalSettings_ = new termios();
310
311        tcgetattr(0, this->originalTerminalSettings_);
312        new_settings = *this->originalTerminalSettings_;
313        new_settings.c_lflag &= ~(ICANON | ECHO);
314        //new_settings.c_lflag |= (ISIG | IEXTEN);
315        new_settings.c_cc[VTIME] = 0;
316        new_settings.c_cc[VMIN]  = 0;
317        tcsetattr(0, TCSANOW, &new_settings);
318        atexit(&IOConsole::resetTerminalMode);
319    }
320
321    /*static*/ void IOConsole::resetTerminalMode()
322    {
323        if (IOConsole::singletonPtr_s && IOConsole::singletonPtr_s->originalTerminalSettings_)
324        {
325            tcsetattr(0, TCSANOW, IOConsole::singletonPtr_s->originalTerminalSettings_);
326            delete IOConsole::singletonPtr_s->originalTerminalSettings_;
327            IOConsole::singletonPtr_s->originalTerminalSettings_ = 0;
328        }
329    }
330
331    void IOConsole::getTerminalSize()
332    {
333        this->terminalWidth_  = 0;
334        this->terminalHeight_ = 0;
335
336#ifdef TIOCGSIZE
337        struct ttysize win;
338        if (!ioctl(STDIN_FILENO, TIOCGSIZE, &win))
339        {
340            this->terminalWidth_  = win.ts_cols;
341            this->terminalHeight_ = win.ts_lines;
342        }
343#elif defined TIOCGWINSZ
344        struct winsize win;
345        if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &win))
346        {
347            this->terminalWidth_  = win.ws_col;
348            this->terminalHeight_ = win.ws_row;
349        }
350#endif
351
352        const char* s;
353        if (!this->terminalWidth_ && (s = getenv("COLUMNS")))
354            this->terminalWidth_  = strtol(s, NULL, 10);
355        if (!this->terminalWidth_)
356            this->terminalWidth_ = 80;
357        if (!this->terminalHeight_ && (s = getenv("LINES")))
358            this->terminalHeight_ = strtol(s, NULL, 10);
359        if (!this->terminalHeight_)
360            this->terminalHeight_ = 24;
361    }
362
363    inline bool IOConsole::willPrintStatusLines()
364    {
365        return !this->statusLineWidths_.empty()
366             && this->terminalWidth_  >= this->statusLineMaxWidth_
367             && this->terminalHeight_ >= this->minOutputLines_ + (int)this->statusLineWidths_.size();
368    }
369
370    // ###############################
371    // ###  ShellListener methods  ###
372    // ###############################
373
374    //! Called if all output-lines have to be reprinted
375    void IOConsole::linesChanged()
376    {
377        // Method only gets called upon start to draw all the lines
378        // or when scrolling. But scrolling is disabled and the output
379        // is already in std::cout when we start the IOConsole
380    }
381
382    //! Called if a command is about to be executed
383    void IOConsole::executed()
384    {
385        this->shell_->addOutput(this->promptString_ + this->shell_->getInput(), Shell::Command);
386    }
387
388    //! Called if the console gets closed
389    void IOConsole::exit()
390    {
391        // Exit is not an option, just do nothing (Shell doesn't really exit too)
392    }
393
394    //! Called if a new output-line was added
395    void IOConsole::lineAdded()
396    {
397        int newLines = this->shell_->getNewestLineIterator()->first.size() / this->terminalWidth_ + 1;
398        // Create new lines by scrolling the screen
399        this->cout_ << "\033[" << newLines << 'S';
400        // Move cursor to the beginning of the new (last) output line
401        this->cout_ << "\033[" << newLines << "A\033[1G";
402        // Erase screen from here
403        this->cout_ << "\033[J";
404        // Print the new output lines
405        for (int i = 0; i < newLines; ++i)
406        {
407            Shell::LineList::const_iterator it = this->shell_->getNewestLineIterator();
408            this->printOutputLine(it->first.substr(i*this->terminalWidth_, this->terminalWidth_), it->second);
409        }
410        // Move cursor down
411        this->cout_ << "\033[1B\033[1G";
412        // Print status and input lines
413        this->printInputLine();
414        this->printStatusLines();
415        this->cout_.flush();
416    }
417
418    //! Called if the text in the input-line has changed
419    void IOConsole::inputChanged()
420    {
421        this->printInputLine();
422        this->cout_.flush();
423    }
424
425    //! Called if the position of the cursor in the input-line has changed
426    void IOConsole::cursorChanged()
427    {
428        this->printInputLine();
429        this->cout_.flush();
430    }
431}
Note: See TracBrowser for help on using the repository browser.