Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Fixed config value naming problem with the soft debug levels.

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