Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

IOConsole: Resolved flickering problem and moved status line below input line

  • Property svn:eol-style set to native
File size: 14.0 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 <cstring>
33#include <iomanip>
34#include <iostream>
35
36#include "util/Clock.h"
37#include "util/Debug.h"
38#include "util/Sleep.h"
39#include "core/CommandExecutor.h"
40#include "core/Game.h"
41#include "core/GameMode.h"
42#include "core/Shell.h"
43#include "core/input/InputBuffer.h"
44
45#ifdef ORXONOX_PLATFORM_UNIX
46#include <termios.h>
47#endif
48
49namespace orxonox
50{
51    IOConsole* IOConsole::singletonPtr_s = NULL;
52
53#ifdef ORXONOX_PLATFORM_UNIX
54
55    namespace EscapeMode
56    {
57        enum Value
58        {
59            None,
60            First,
61            Second
62        };
63    }
64
65    IOConsole::IOConsole()
66        : shell_(new Shell("IOConsole", false, true))
67        , buffer_(shell_->getInputBuffer())
68        , originalTerminalSettings_(new termios())
69        , bStatusPrinted_(false)
70        , promptString_("orxonox # ")
71    {
72        this->setTerminalMode();
73        this->shell_->registerListener(this);
74
75        // Manually set the widths of the individual status lines
76        this->statusLineWidths_.push_back(20);
77        this->statusLineMaxWidth_ = 20;
78    }
79
80    IOConsole::~IOConsole()
81    {
82        // Goto last line and erase it
83        if (this->bStatusPrinted_)
84            std::cout << "\033[" << this->statusLineWidths_.size() << 'E';
85        std::cout << "\033[1G\033[K";
86        std::cout.flush();
87        resetTerminalMode();
88        delete this->originalTerminalSettings_;
89        this->shell_->destroy();
90    }
91
92    void IOConsole::setTerminalMode()
93    {
94        termios new_settings;
95
96        tcgetattr(0, this->originalTerminalSettings_);
97        new_settings = *this->originalTerminalSettings_;
98        new_settings.c_lflag &= ~(ICANON | ECHO);
99        //         new_settings.c_lflag |= ( ISIG | IEXTEN );
100        new_settings.c_cc[VTIME] = 0;
101        new_settings.c_cc[VMIN]  = 0;
102        tcsetattr(0, TCSANOW, &new_settings);
103        COUT(0) << endl;
104        //       atexit(&IOConsole::resetTerminalMode);
105    }
106
107    void IOConsole::resetTerminalMode()
108    {
109        tcsetattr(0, TCSANOW, IOConsole::originalTerminalSettings_);
110    }
111
112    void IOConsole::update(const Clock& time)
113    {
114        unsigned char c = 0;
115        std::string escapeSequence;
116        EscapeMode::Value escapeMode = EscapeMode::None;
117        while (read(STDIN_FILENO, &c, 1) == 1)
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
188        // If there is still an escape key pending (escape key ONLY), then
189        // it sure isn't an escape sequence anymore
190        if (escapeMode == EscapeMode::First)
191            this->buffer_->buttonPressed(KeyEvent(KeyCode::Escape, '\033', 0));
192
193        // Determine terminal width and height
194        this->getTerminalSize();
195
196        if (!this->bStatusPrinted_ && this->willPrintStatusLines())
197        {
198            // Print new lines to make way for status lines
199            std::cout << std::string(this->statusLineWidths_.size(), '\n');
200            // Move cursor up again
201            std::cout << "\033[" << this->statusLineWidths_.size() << 'A';
202            this->bStatusPrinted_ = true;
203        }
204        // Move cursor horizontally and erase status and input lines
205        std::cout << "\033[1G\033[J";
206        this->printInputLine();
207        this->printStatusLines();
208        std::cout.flush();
209    }
210
211    void IOConsole::printLogText(const std::string& text)
212    {
213        std::string output;
214
215        // Handle line colouring by inspecting the first letter
216        char level = 0;
217        if (!text.empty())
218            level = text[0];
219        if (level >= -1 && level <= 6)
220            output = text.substr(1);
221        else
222            output = text;
223
224        // Colour line
225/*
226        switch (level)
227        {
228        case -1: std::cout << "\033[37m"; break;
229        case  1: std::cout << "\033[91m"; break;
230        case  2: std::cout << "\033[31m"; break;
231        case  3: std::cout << "\033[34m"; break;
232        case  4: std::cout << "\033[36m"; break;
233        case  5: std::cout << "\033[35m"; break;
234        case  6: std::cout << "\033[37m"; break;
235        default: break;
236        }
237*/
238
239        // Print output line
240        std::cout << output;
241
242        // Reset colour to white
243//        std::cout << "\033[37m";
244    }
245
246    void IOConsole::printInputLine()
247    {
248        // Set cursor to the beginning of the line and erase the line
249        std::cout << "\033[1G\033[K";
250        // Indicate a command prompt
251        std::cout << this->promptString_;
252        // Save cursor position
253        std::cout << "\033[s";
254        // Print command line buffer
255        std::cout << this->shell_->getInput();
256        // Restore cursor position and move it to the right
257        std::cout << "\033[u";
258        if (this->buffer_->getCursorPosition() > 0)
259            std::cout << "\033[" << this->buffer_->getCursorPosition() << "C";
260    }
261
262    void IOConsole::printStatusLines()
263    {
264        if (this->willPrintStatusLines())
265        {
266            // Save cursor position
267            std::cout << "\033[s";
268            // Move cursor down (don't create a new line here because the buffer might flush then!)
269            std::cout << "\033[1E";
270            std::cout << std::fixed << std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgFPS() << " fps, ";
271            std::cout <<               std::setprecision(2) << std::setw(5) << Game::getInstance().getAvgTickTime() << " ms tick time";
272            // Restore cursor position
273            std::cout << "\033[u";
274            this->bStatusPrinted_ = true;
275        }
276        else
277            this->bStatusPrinted_ = false;
278    }
279
280    inline bool IOConsole::willPrintStatusLines()
281    {
282        return !this->statusLineWidths_.empty()
283             && this->terminalWidth_  >= this->statusLineMaxWidth_
284             && this->terminalHeight_ >= (this->minOutputLines_ + this->statusLineWidths_.size());
285    }
286
287    void IOConsole::getTerminalSize()
288    {
289#ifdef TIOCGSIZE
290        struct ttysize win;
291        if (!ioctl(STDIN_FILENO, TIOCGSIZE, &win))
292        {
293            this->terminalWidth_  = win.ts_cols;
294            this->terminalHeight_ = win.ts_lines;
295            return;
296        }
297#elif defined TIOCGWINSZ
298        struct winsize win;
299        if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &win))
300        {
301            this->terminalWidth_  = win.ws_col;
302            this->terminalHeight_ = win.ws_row;
303            return;
304        }
305#else
306        const char* s = getenv("COLUMNS");
307        this->terminalWidth_  = s ? strtol(s, NULL, 10) : 80;
308        s = getenv("LINES");
309        this->terminalHeight_ = s ? strtol(s, NULL, 10) : 24;
310        return;
311#endif
312        this->terminalWidth_  = 80;
313        this->terminalHeight_ = 24;
314    }
315
316#elif defined(ORXONOX_PLATFORM_WINDOWS)
317
318    IOConsole::IOConsole()
319        : shell_(new Shell("IOConsole", false))
320        , buffer_(shell_->getInputBuffer())
321    {
322        this->setTerminalMode();
323    }
324
325    IOConsole::~IOConsole()
326    {
327    }
328
329    void IOConsole::setTerminalMode()
330    {
331    }
332
333    void IOConsole::resetTerminalMode()
334    {
335    }
336
337    void IOConsole::update(const Clock& time)
338    {
339    }
340
341    void IOConsole::printLogText(const std::string& text)
342    {
343    }
344
345    void IOConsole::printInputLine()
346    {
347    }
348
349    void IOConsole::printStatusLines()
350    {
351    }
352
353#endif /* ORXONOX_PLATFORM_UNIX */
354
355    // ###############################
356    // ###  ShellListener methods  ###
357    // ###############################
358
359    /**
360    @brief
361        Called if all output-lines have to be redrawn.
362    */
363    void IOConsole::linesChanged()
364    {
365        // Method only gets called upon start to draw all the lines
366        // But we are using std::cout anyway, so do nothing here
367    }
368
369    /**
370    @brief
371        Called if only the last output-line has changed.
372    */
373    void IOConsole::onlyLastLineChanged()
374    {
375        // Save cursor position and move it to the beginning of the first output line
376        std::cout << "\033[s\033[1F";
377        // Erase the line
378        std::cout << "\033[K";
379        // Reprint the last output line
380        this->printLogText(*(this->shell_->getNewestLineIterator()));
381        // Restore cursor
382        std::cout << "\033[u";
383        std::cout.flush();
384    }
385
386    /**
387    @brief
388        Called if a new output-line was added.
389    */
390    void IOConsole::lineAdded()
391    {
392        // Move cursor to the bottom line
393        if (this->bStatusPrinted_)
394            std::cout << "\033[" << this->statusLineWidths_.size() << 'E';
395        // Create the new line on the screen
396        std::cout << '\n';
397        // Move cursor to the beginning of the new (last) output line
398        std::cout << "\033[" << (1 + this->statusLineWidths_.size()) << 'F';
399        // Erase screen from here
400        std::cout << "\033[J";
401        // Print the new output line
402        this->printLogText(*(this->shell_->getNewestLineIterator()));
403        // Move cursor down
404        std::cout << "\033[1E";
405        // Print status and input lines
406        this->printInputLine();
407        this->printStatusLines();
408        std::cout.flush();
409    }
410
411    /**
412    @brief
413        Called if the text in the input-line has changed.
414    */
415    void IOConsole::inputChanged()
416    {
417        this->printInputLine();
418        std::cout.flush();
419    }
420
421    /**
422    @brief
423        Called if the position of the cursor in the input-line has changed.
424    */
425    void IOConsole::cursorChanged()
426    {
427        this->printInputLine();
428        std::cout.flush();
429    }
430
431    /**
432    @brief
433        Called if a command is about to be executed
434    */
435    void IOConsole::executed()
436    {
437        this->shell_->addOutputLine(this->promptString_ + this->shell_->getInput());
438    }
439
440
441    /**
442    @brief
443        Called if the console gets closed.
444    */
445    void IOConsole::exit()
446    {
447        // Exit is not an option, IOConsole always exists
448    }
449
450}
Note: See TracBrowser for help on using the repository browser.