Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/console/src/libraries/core/IOConsole.cc @ 6016

Last change on this file since 6016 was 6015, checked in by rgrieder, 15 years ago

IOConsole cleanup and added ConsoleWriter to the OutputHandler so that we get std::cout output before the creation of the IOConsole.

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