Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/IOConsole.cc @ 6372

Last change on this file since 6372 was 6105, checked in by rgrieder, 15 years ago

Merged console branch back to trunk.

  • Property svn:eol-style set to native
File size: 18.1 KB
RevLine 
[5971]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:
[5970]23 *      Oliver Scheuss
24 *      Reto Grieder
[5971]25 *   Co-authors:
26 *      ...
27 *
28 */
29
30#include "IOConsole.h"
31
32#include <iomanip>
33#include <iostream>
34#include "core/Game.h"
35#include "core/input/InputBuffer.h"
36
[6015]37// ##########################
38// ###   Mutual methods   ###
39// ##########################
40namespace orxonox
41{
42    IOConsole* IOConsole::singletonPtr_s = NULL;
43
44    int IOConsole::extractLogLevel(std::string* text)
45    {
46        // Handle line colouring by inspecting the first letter
47        char level = 0;
48        if (!text->empty())
49        {
50            level = (*text)[0];
51            if (level == -1 || level >= 1 && level <= 6)
52            {
53                *text = text->substr(1);
54                if (level != -1)
55                    return level;
56            }
57        }
58        return 0;
59    }
60
61    inline bool IOConsole::willPrintStatusLines()
62    {
63        return !this->statusLineWidths_.empty()
64             && this->terminalWidth_  >= this->statusLineMaxWidth_
65             && this->terminalHeight_ >= (this->minOutputLines_ + this->statusLineWidths_.size());
66    }
67
68    // ###############################
69    // ###  ShellListener methods  ###
70    // ###############################
71
72    //! Called if all output-lines have to be reprinted
73    void IOConsole::linesChanged()
74    {
75        // Method only gets called upon start to draw all the lines
[6037]76        // or when scrolling. But scrolling is disabled and the output
[6015]77        // is already in std::cout when we start the IOConsole
78    }
79
80    //! Called if the text in the input-line has changed
81    void IOConsole::inputChanged()
82    {
83        this->printInputLine();
[6037]84        this->cout_.flush();
[6015]85    }
86
87    //! Called if the position of the cursor in the input-line has changed
88    void IOConsole::cursorChanged()
89    {
90        this->printInputLine();
[6037]91        this->cout_.flush();
[6015]92    }
93
94    //! Called if a command is about to be executed
95    void IOConsole::executed()
96    {
97        this->shell_->addOutputLine(this->promptString_ + this->shell_->getInput());
98    }
99
100    //! Called if the console gets closed
101    void IOConsole::exit()
102    {
103        // Exit is not an option, just do nothing (Shell doesn't really exit too)
104    }
105}
106
[5971]107#ifdef ORXONOX_PLATFORM_UNIX
[6015]108// ###############################
109// ###   Unix Implementation   ###
110// ###############################
111
[5971]112#include <termios.h>
[6014]113#include <sys/ioctl.h>
[5971]114
115namespace orxonox
116{
[5995]117    namespace EscapeMode
118    {
119        enum Value
120        {
121            None,
122            First,
123            Second
124        };
125    }
126
[5971]127    IOConsole::IOConsole()
[6010]128        : shell_(new Shell("IOConsole", false, true))
[6004]129        , buffer_(shell_->getInputBuffer())
[6037]130        , cout_(std::cout.rdbuf())
[5995]131        , bStatusPrinted_(false)
[6013]132        , promptString_("orxonox # ")
[6037]133        , originalTerminalSettings_(new termios())
[5971]134    {
135        this->setTerminalMode();
[6004]136        this->shell_->registerListener(this);
[5995]137
138        // Manually set the widths of the individual status lines
[6014]139        this->statusLineWidths_.push_back(29);
140        this->statusLineMaxWidth_ = 29;
141
142        this->getTerminalSize();
143        this->lastTerminalWidth_ = this->terminalWidth_;
144        this->lastTerminalHeight_ = this->terminalHeight_;
[6015]145
146        // Disable standard std::cout logging
147        OutputHandler::getInstance().disableCout();
[6037]148        // Redirect std::cout to an ostringstream
149        // (Other part is in the initialiser list)
150        std::cout.rdbuf(this->origCout_.rdbuf());
151
152        // Make sure we make way for the status lines
153        this->update(Game::getInstance().getGameClock());
[5971]154    }
155
156    IOConsole::~IOConsole()
157    {
[6037]158        // Empty all buffers
159        this->update(Game::getInstance().getGameClock());
[6103]160        // Erase input and status lines
161        this->cout_ << "\033[1G\033[J";
[6105]162        // Move cursor to the bottom
163        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
164        // Scroll terminal to compensate for erased lines
165        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'T';
[6015]166
[5971]167        resetTerminalMode();
168        delete this->originalTerminalSettings_;
[6004]169        this->shell_->destroy();
[5971]170
[6037]171        // Restore this->cout_ redirection
172        std::cout.rdbuf(this->cout_.rdbuf());
[6015]173        // Enable standard std::cout logging again
174        OutputHandler::getInstance().enableCout();
[5971]175    }
176
177    void IOConsole::update(const Clock& time)
178    {
[6103]179        unsigned char c;
[5995]180        std::string escapeSequence;
[5998]181        EscapeMode::Value escapeMode = EscapeMode::None;
[6015]182        while (std::cin.good())
[5971]183        {
[6015]184            c = std::cin.get();
[6089]185            if (!std::cin.good())
[6015]186                break;
187
[5995]188            if (escapeMode == EscapeMode::First && (c == '[' || c=='O') )
189                escapeMode = EscapeMode::Second;
[5975]190            // Get Alt+Tab combination when switching applications
[5998]191            else if (escapeMode == EscapeMode::First && c == '\t')
[5971]192            {
[5975]193                this->buffer_->buttonPressed(KeyEvent(KeyCode::Tab, '\t', KeyboardModifier::Alt));
[5995]194                escapeMode = EscapeMode::None;
[5975]195            }
[5995]196            else if (escapeMode == EscapeMode::Second)
[5975]197            {
[5995]198                escapeSequence += c;
199                escapeMode = EscapeMode::None;
200                if      (escapeSequence == "A")
[5971]201                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Up,       0, 0));
[5995]202                else if (escapeSequence == "B")
[5971]203                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Down,     0, 0));
[5995]204                else if (escapeSequence == "C")
[5971]205                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Right,    0, 0));
[5995]206                else if (escapeSequence == "D")
[5971]207                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Left,     0, 0));
[5995]208                else if (escapeSequence == "1~" || escapeSequence == "H")
[5971]209                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Home,     0, 0));
[5995]210                else if (escapeSequence == "2~")
[5971]211                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Insert,   0, 0));
[5995]212                else if (escapeSequence == "3~")
[5973]213                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Delete,   0, 0));
[5995]214                else if (escapeSequence == "4~" || escapeSequence == "F")
[5971]215                    this->buffer_->buttonPressed(KeyEvent(KeyCode::End,      0, 0));
[5995]216                else if (escapeSequence == "5~")
[6010]217                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageUp,   0, 0));
[5995]218                else if (escapeSequence == "6~")
[6010]219                    this->buffer_->buttonPressed(KeyEvent(KeyCode::PageDown, 0, 0));
[5971]220                else
[5975]221                    // Waiting for sequence to complete
[5995]222                    // If the user presses ESC and then '[' or 'O' while the loop is not
223                    // running (for instance while loading), the whole sequence gets dropped
224                    escapeMode = EscapeMode::Second;
[5971]225            }
[5975]226            else // not in an escape sequence OR user might have pressed just ESC
[5971]227            {
[5995]228                if (escapeMode == EscapeMode::First)
[5975]229                {
[5983]230                    this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, c, 0));
[5995]231                    escapeMode = EscapeMode::None;
[5975]232                }
[5971]233                if (c == '\033')
[5975]234                {
[5995]235                    escapeMode = EscapeMode::First;
236                    escapeSequence.clear();
[5975]237                }
[5971]238                else
239                {
240                    KeyCode::ByEnum code;
241                    switch (c)
242                    {
[5995]243                    case '\n'  : case '\r': code = KeyCode::Return; break;
244                    case '\177': case '\b': code = KeyCode::Back;   break;
245                    case '\t'             : code = KeyCode::Tab;    break;
[5971]246                    default:
247                        // We don't encode the key code (would be a very large switch)
248                        // because the InputBuffer will only insert the text anyway
249                        // Replacement character is simply KeyCode::A
250                        code = KeyCode::A;
251                    }
252                    this->buffer_->buttonPressed(KeyEvent(code, c, 0));
253                }
254            }
255        }
[6015]256        // Reset error flags in std::cin
257        std::cin.clear();
[5973]258
[5975]259        // If there is still an escape key pending (escape key ONLY), then
[5995]260        // it sure isn't an escape sequence anymore
261        if (escapeMode == EscapeMode::First)
[5983]262            this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, '\033', 0));
[5975]263
[6013]264        // Determine terminal width and height
[6014]265        this->lastTerminalWidth_ = this->terminalWidth_;
266        this->lastTerminalHeight_ = this->terminalHeight_;
[6013]267        this->getTerminalSize();
268
[6014]269        int heightDiff = this->terminalHeight_ - this->lastTerminalHeight_;
270        if (this->bStatusPrinted_ && heightDiff < 0)
271        {
[6015]272            // Terminal width has shrunk. The cursor will still be on the input line,
[6014]273            // but that line might very well be the last
274            int newLines = std::min((int)this->statusLineWidths_.size(), -heightDiff);
[6089]275            // Scroll terminal to create new lines
[6103]276            this->cout_ << "\033[" << newLines << 'S';
[6014]277        }
278
[6013]279        if (!this->bStatusPrinted_ && this->willPrintStatusLines())
280        {
[6089]281            // Scroll console to make way for status lines
[6103]282            this->cout_ << "\033[" << this->statusLineWidths_.size() << 'S';
[6013]283            this->bStatusPrinted_ = true;
284        }
[6103]285
286        // We always assume that the cursor is on the inputline.
287        // But we cannot always be sure about that, esp. if we scroll the console
288        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'B';
289        this->cout_ << "\033[" << this->statusLineWidths_.size() << 'A';
290
[6014]291        // Erase status and input lines
[6037]292        this->cout_ << "\033[1G\033[J";
[6013]293        this->printInputLine();
[6004]294        this->printStatusLines();
[6037]295        this->cout_.flush();
[6103]296
297        // Process output written to std::cout
298        if (!this->origCout_.str().empty())
299        {
300            this->shell_->addOutputLine(this->origCout_.str());
301            this->origCout_.str("");
302        }
[5971]303    }
304
[5995]305    void IOConsole::printLogText(const std::string& text)
[5971]306    {
[6015]307        std::string output = text;
[6103]308        /*int level =*/ this->extractLogLevel(&output);
[5971]309
[6044]310/*
[5971]311        // Colour line
312        switch (level)
313        {
[6037]314        case -1: this->cout_ << "\033[37m"; break;
315        case  1: this->cout_ << "\033[91m"; break;
316        case  2: this->cout_ << "\033[31m"; break;
317        case  3: this->cout_ << "\033[34m"; break;
318        case  4: this->cout_ << "\033[36m"; break;
319        case  5: this->cout_ << "\033[35m"; break;
320        case  6: this->cout_ << "\033[37m"; break;
[5972]321        default: break;
[5971]322        }
[5995]323*/
[5971]324
325        // Print output line
[6037]326        this->cout_ << output;
[5971]327
[5983]328        // Reset colour to white
[6037]329//        this->cout_ << "\033[37m";
[5971]330    }
331
332    void IOConsole::printInputLine()
333    {
[5995]334        // Set cursor to the beginning of the line and erase the line
[6037]335        this->cout_ << "\033[1G\033[K";
[5995]336        // Indicate a command prompt
[6037]337        this->cout_ << this->promptString_;
[5995]338        // Save cursor position
[6037]339        this->cout_ << "\033[s";
[5995]340        // Print command line buffer
[6037]341        this->cout_ << this->shell_->getInput();
[5995]342        // Restore cursor position and move it to the right
[6037]343        this->cout_ << "\033[u";
[5971]344        if (this->buffer_->getCursorPosition() > 0)
[6037]345            this->cout_ << "\033[" << this->buffer_->getCursorPosition() << "C";
[5971]346    }
347
[5995]348    void IOConsole::printStatusLines()
349    {
[6013]350        if (this->willPrintStatusLines())
[5995]351        {
[6013]352            // Save cursor position
[6037]353            this->cout_ << "\033[s";
[6013]354            // Move cursor down (don't create a new line here because the buffer might flush then!)
[6089]355            this->cout_ << "\033[1B\033[1G";
[6044]356            this->cout_ << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
357            this->cout_ <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
[6013]358            // Restore cursor position
[6037]359            this->cout_ << "\033[u";
[6004]360            this->bStatusPrinted_ = true;
[5995]361        }
[6013]362        else
363            this->bStatusPrinted_ = false;
[5995]364    }
365
[6015]366    void IOConsole::setTerminalMode()
[5995]367    {
[6015]368        termios new_settings;
369
370        tcgetattr(0, this->originalTerminalSettings_);
371        new_settings = *this->originalTerminalSettings_;
372        new_settings.c_lflag &= ~(ICANON | ECHO);
373        //new_settings.c_lflag |= (ISIG | IEXTEN);
374        new_settings.c_cc[VTIME] = 0;
375        new_settings.c_cc[VMIN]  = 0;
376        tcsetattr(0, TCSANOW, &new_settings);
[6013]377    }
378
[6015]379    void IOConsole::resetTerminalMode()
380    {
381        tcsetattr(0, TCSANOW, IOConsole::originalTerminalSettings_);
382    }
383
[6013]384    void IOConsole::getTerminalSize()
385    {
[5995]386#ifdef TIOCGSIZE
387        struct ttysize win;
[6013]388        if (!ioctl(STDIN_FILENO, TIOCGSIZE, &win))
389        {
390            this->terminalWidth_  = win.ts_cols;
391            this->terminalHeight_ = win.ts_lines;
392            return;
393        }
394#elif defined TIOCGWINSZ
[5995]395        struct winsize win;
[6013]396        if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &win))
[5995]397        {
[6013]398            this->terminalWidth_  = win.ws_col;
399            this->terminalHeight_ = win.ws_row;
400            return;
[5995]401        }
[6013]402#else
403        const char* s = getenv("COLUMNS");
404        this->terminalWidth_  = s ? strtol(s, NULL, 10) : 80;
405        s = getenv("LINES");
406        this->terminalHeight_ = s ? strtol(s, NULL, 10) : 24;
407        return;
[5995]408#endif
[6013]409        this->terminalWidth_  = 80;
410        this->terminalHeight_ = 24;
[5995]411    }
412
[5971]413    // ###############################
414    // ###  ShellListener methods  ###
415    // ###############################
416
[6015]417    //! Called if only the last output-line has changed
[5971]418    void IOConsole::onlyLastLineChanged()
419    {
[5995]420        // Save cursor position and move it to the beginning of the first output line
[6089]421        this->cout_ << "\033[s\033[1A\033[1G";
[5995]422        // Erase the line
[6037]423        this->cout_ << "\033[K";
[5995]424        // Reprint the last output line
[6004]425        this->printLogText(*(this->shell_->getNewestLineIterator()));
[5994]426        // Restore cursor
[6037]427        this->cout_ << "\033[u";
428        this->cout_.flush();
[5971]429    }
430
[6015]431    //! Called if a new output-line was added
[5971]432    void IOConsole::lineAdded()
433    {
[6014]434        int newLines = this->shell_->getNewestLineIterator()->size() / this->terminalWidth_ + 1;
[6103]435        // Create new lines by scrolling the screen
436        this->cout_ << "\033[" << newLines << 'S';
[6013]437        // Move cursor to the beginning of the new (last) output line
[6103]438        this->cout_ << "\033[" << newLines << "A\033[1G";
[6013]439        // Erase screen from here
[6037]440        this->cout_ << "\033[J";
[6103]441        // Print the new output lines
[6014]442        for (int i = 0; i < newLines; ++i)
443            this->printLogText(this->shell_->getNewestLineIterator()->substr(i*this->terminalWidth_, this->terminalWidth_));
[6013]444        // Move cursor down
[6089]445        this->cout_ << "\033[1B\033[1G";
[6013]446        // Print status and input lines
447        this->printInputLine();
[6004]448        this->printStatusLines();
[6037]449        this->cout_.flush();
[5971]450    }
[6015]451}
[5971]452
[6015]453#elif defined(ORXONOX_PLATFORM_WINDOWS)
454// ##################################
455// ###   Windows Implementation   ###
456// ##################################
457
458namespace orxonox
459{
460    IOConsole::IOConsole()
461        : shell_(new Shell("IOConsole", false, true))
462        , buffer_(shell_->getInputBuffer())
[6041]463        , cout_(std::cout.rdbuf())
[6015]464        , bStatusPrinted_(false)
465        , promptString_("orxonox # ")
[5971]466    {
[6015]467/*
468        this->setTerminalMode();
469        this->shell_->registerListener(this);
470
471        // Manually set the widths of the individual status lines
472        this->statusLineWidths_.push_back(29);
473        this->statusLineMaxWidth_ = 29;
474
475        this->getTerminalSize();
476        this->lastTerminalWidth_ = this->terminalWidth_;
477        this->lastTerminalHeight_ = this->terminalHeight_;
478
[6037]479        // Disable standard this->cout_ logging
[6015]480        OutputHandler::getInstance().disableCout();
481*/
[5971]482    }
483
[6015]484    IOConsole::~IOConsole()
[5971]485    {
[6015]486/*
487        resetTerminalMode();
488        this->shell_->destroy();
489
[6037]490        // Enable standard this->cout_ logging again
[6015]491        OutputHandler::getInstance().enableCout();
492*/
[5971]493    }
494
[6015]495    void IOConsole::update(const Clock& time)
[5983]496    {
[6015]497/*
498        unsigned char c = 0;
499        while (std::cin.good())
500        {
501            c = std::cin.get();
502            if (std::cin.bad())
503                break;
504        }
505        // Reset error flags in std::cin
506        std::cin.clear();
507
508        // Determine terminal width and height
509        this->lastTerminalWidth_ = this->terminalWidth_;
510        this->lastTerminalHeight_ = this->terminalHeight_;
511        this->getTerminalSize();
512*/
[5983]513    }
514
[6015]515    void IOConsole::printLogText(const std::string& text)
516    {
517    }
[5983]518
[6015]519    void IOConsole::printInputLine()
[5971]520    {
521    }
522
[6015]523    void IOConsole::printStatusLines()
524    {
525/*
526        if (this->willPrintStatusLines())
527        {
528            this->bStatusPrinted_ = true;
529        }
530        else
531            this->bStatusPrinted_ = false;
532*/
533    }
534
535    void IOConsole::setTerminalMode()
536    {
537    }
538
539    void IOConsole::resetTerminalMode()
540    {
541    }
542
543    void IOConsole::getTerminalSize()
544    {
545    }
546
547    // ###############################
548    // ###  ShellListener methods  ###
549    // ###############################
550
551    //! Called if only the last output-line has changed
552    void IOConsole::onlyLastLineChanged()
553    {
554    }
555
556    //! Called if a new output-line was added
557    void IOConsole::lineAdded()
558    {
559    }
[5971]560}
[6015]561
562#endif /* ORXONOX_PLATFORM_UNIX */
Note: See TracBrowser for help on using the repository browser.