console_gui.cpp

Go to the documentation of this file.
00001 /* $Id: console_gui.cpp 23531 2011-12-16 16:27:45Z truebrain $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "textbuf_gui.h"
00014 #include "window_gui.h"
00015 #include "console_gui.h"
00016 #include "console_internal.h"
00017 #include "window_func.h"
00018 #include "string_func.h"
00019 #include "strings_func.h"
00020 #include "gfx_func.h"
00021 #include "settings_type.h"
00022 #include "console_func.h"
00023 #include "rev.h"
00024 
00025 #include "widgets/console_widget.h"
00026 
00027 #include "table/strings.h"
00028 
00029 static const uint ICON_HISTORY_SIZE       = 20;
00030 static const uint ICON_LINE_SPACING       =  2;
00031 static const uint ICON_RIGHT_BORDERWIDTH  = 10;
00032 static const uint ICON_BOTTOM_BORDERWIDTH = 12;
00033 
00037 struct IConsoleLine {
00038   static IConsoleLine *front; 
00039   static int size;            
00040 
00041   IConsoleLine *previous; 
00042   char *buffer;           
00043   TextColour colour;      
00044   uint16 time;            
00045 
00051   IConsoleLine(char *buffer, TextColour colour) :
00052       previous(IConsoleLine::front),
00053       buffer(buffer),
00054       colour(colour),
00055       time(0)
00056   {
00057     IConsoleLine::front = this;
00058     IConsoleLine::size++;
00059   }
00060 
00064   ~IConsoleLine()
00065   {
00066     IConsoleLine::size--;
00067     free(buffer);
00068 
00069     delete previous;
00070   }
00071 
00075   static const IConsoleLine *Get(uint index)
00076   {
00077     const IConsoleLine *item = IConsoleLine::front;
00078     while (index != 0 && item != NULL) {
00079       index--;
00080       item = item->previous;
00081     }
00082 
00083     return item;
00084   }
00085 
00093   static bool Truncate()
00094   {
00095     IConsoleLine *cur = IConsoleLine::front;
00096     if (cur == NULL) return false;
00097 
00098     int count = 1;
00099     for (IConsoleLine *item = cur->previous; item != NULL; count++, cur = item, item = item->previous) {
00100       if (item->time > _settings_client.gui.console_backlog_timeout &&
00101           count > _settings_client.gui.console_backlog_length) {
00102         delete item;
00103         cur->previous = NULL;
00104         return true;
00105       }
00106 
00107       if (item->time != MAX_UVALUE(uint16)) item->time++;
00108     }
00109 
00110     return false;
00111   }
00112 
00116   static void Reset()
00117   {
00118     delete IConsoleLine::front;
00119     IConsoleLine::front = NULL;
00120     IConsoleLine::size = 0;
00121   }
00122 };
00123 
00124 /* static */ IConsoleLine *IConsoleLine::front = NULL;
00125 /* static */ int IConsoleLine::size  = 0;
00126 
00127 
00128 /* ** main console cmd buffer ** */
00129 static Textbuf _iconsole_cmdline;
00130 static char *_iconsole_history[ICON_HISTORY_SIZE];
00131 static int _iconsole_historypos;
00132 IConsoleModes _iconsole_mode;
00133 
00134 /* *************** *
00135  *  end of header  *
00136  * *************** */
00137 
00138 static void IConsoleClearCommand()
00139 {
00140   memset(_iconsole_cmdline.buf, 0, ICON_CMDLN_SIZE);
00141   _iconsole_cmdline.chars = _iconsole_cmdline.bytes = 1; // only terminating zero
00142   _iconsole_cmdline.pixels = 0;
00143   _iconsole_cmdline.caretpos = 0;
00144   _iconsole_cmdline.caretxoffs = 0;
00145   SetWindowDirty(WC_CONSOLE, 0);
00146 }
00147 
00148 static inline void IConsoleResetHistoryPos()
00149 {
00150   _iconsole_historypos = -1;
00151 }
00152 
00153 
00154 static const char *IConsoleHistoryAdd(const char *cmd);
00155 static void IConsoleHistoryNavigate(int direction);
00156 
00157 static const struct NWidgetPart _nested_console_window_widgets[] = {
00158   NWidget(WWT_EMPTY, INVALID_COLOUR, WID_C_BACKGROUND), SetResize(1, 1),
00159 };
00160 
00161 static const WindowDesc _console_window_desc(
00162   WDP_MANUAL, 0, 0,
00163   WC_CONSOLE, WC_NONE,
00164   0,
00165   _nested_console_window_widgets, lengthof(_nested_console_window_widgets)
00166 );
00167 
00168 struct IConsoleWindow : Window
00169 {
00170   static int scroll;
00171   int line_height;   
00172   int line_offset;
00173 
00174   IConsoleWindow() : Window()
00175   {
00176     _iconsole_mode = ICONSOLE_OPENED;
00177     this->line_height = FONT_HEIGHT_NORMAL + ICON_LINE_SPACING;
00178     this->line_offset = GetStringBoundingBox("] ").width + 5;
00179 
00180     this->InitNested(&_console_window_desc, 0);
00181     ResizeWindow(this, _screen.width, _screen.height / 3);
00182   }
00183 
00184   ~IConsoleWindow()
00185   {
00186     _iconsole_mode = ICONSOLE_CLOSED;
00187   }
00188 
00193   void Scroll(int amount)
00194   {
00195     int max_scroll = max<int>(0, IConsoleLine::size + 1 - this->height / this->line_height);
00196     IConsoleWindow::scroll = Clamp<int>(IConsoleWindow::scroll + amount, 0, max_scroll);
00197     this->SetDirty();
00198   }
00199 
00200   virtual void OnPaint()
00201   {
00202     const int right = this->width - 5;
00203 
00204     GfxFillRect(0, 0, this->width - 1, this->height - 1, PC_BLACK);
00205     int ypos = this->height - this->line_height;
00206     for (const IConsoleLine *print = IConsoleLine::Get(IConsoleWindow::scroll); print != NULL; print = print->previous) {
00207       SetDParamStr(0, print->buffer);
00208       ypos = DrawStringMultiLine(5, right, -this->line_height, ypos, STR_JUST_RAW_STRING, print->colour, SA_LEFT | SA_BOTTOM | SA_FORCE) - ICON_LINE_SPACING;
00209       if (ypos < 0) break;
00210     }
00211     /* If the text is longer than the window, don't show the starting ']' */
00212     int delta = this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH;
00213     if (delta > 0) {
00214       DrawString(5, right, this->height - this->line_height, "]", (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
00215       delta = 0;
00216     }
00217 
00218     DrawString(this->line_offset + delta, right, this->height - this->line_height, _iconsole_cmdline.buf, (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
00219 
00220     if (_focused_window == this && _iconsole_cmdline.caret) {
00221       DrawString(this->line_offset + delta + _iconsole_cmdline.caretxoffs, right, this->height - this->line_height, "_", TC_WHITE, SA_LEFT | SA_FORCE);
00222     }
00223   }
00224 
00225   virtual void OnHundredthTick()
00226   {
00227     if (IConsoleLine::Truncate() &&
00228         (IConsoleWindow::scroll > IConsoleLine::size)) {
00229       IConsoleWindow::scroll = max(0, IConsoleLine::size - (this->height / this->line_height) + 1);
00230       this->SetDirty();
00231     }
00232   }
00233 
00234   virtual void OnMouseLoop()
00235   {
00236     if (HandleCaret(&_iconsole_cmdline)) this->SetDirty();
00237   }
00238 
00239   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
00240   {
00241     if (_focused_window != this) return ES_NOT_HANDLED;
00242 
00243     const int scroll_height = (this->height / this->line_height) - 1;
00244     switch (keycode) {
00245       case WKC_UP:
00246         IConsoleHistoryNavigate(1);
00247         this->SetDirty();
00248         break;
00249 
00250       case WKC_DOWN:
00251         IConsoleHistoryNavigate(-1);
00252         this->SetDirty();
00253         break;
00254 
00255       case WKC_SHIFT | WKC_PAGEDOWN:
00256         this->Scroll(-scroll_height);
00257         break;
00258 
00259       case WKC_SHIFT | WKC_PAGEUP:
00260         this->Scroll(scroll_height);
00261         break;
00262 
00263       case WKC_SHIFT | WKC_DOWN:
00264         this->Scroll(-1);
00265         break;
00266 
00267       case WKC_SHIFT | WKC_UP:
00268         this->Scroll(1);
00269         break;
00270 
00271       case WKC_BACKQUOTE:
00272         IConsoleSwitch();
00273         break;
00274 
00275       case WKC_RETURN: case WKC_NUM_ENTER: {
00276         /* We always want the ] at the left side; we always force these strings to be left
00277          * aligned anyway. So enforce this in all cases by addding a left-to-right marker,
00278          * otherwise it will be drawn at the wrong side with right-to-left texts. */
00279         IConsolePrintF(CC_COMMAND, LRM "] %s", _iconsole_cmdline.buf);
00280         const char *cmd = IConsoleHistoryAdd(_iconsole_cmdline.buf);
00281         IConsoleClearCommand();
00282 
00283         if (cmd != NULL) IConsoleCmdExec(cmd);
00284         break;
00285       }
00286 
00287       case WKC_CTRL | WKC_RETURN:
00288         _iconsole_mode = (_iconsole_mode == ICONSOLE_FULL) ? ICONSOLE_OPENED : ICONSOLE_FULL;
00289         IConsoleResize(this);
00290         MarkWholeScreenDirty();
00291         break;
00292 
00293 #ifdef WITH_COCOA
00294       case (WKC_META | 'V'):
00295 #endif
00296       case (WKC_CTRL | 'V'):
00297         if (InsertTextBufferClipboard(&_iconsole_cmdline)) {
00298           IConsoleResetHistoryPos();
00299           this->SetDirty();
00300         }
00301         break;
00302 
00303       case (WKC_CTRL | 'L'):
00304         IConsoleCmdExec("clear");
00305         break;
00306 
00307 #ifdef WITH_COCOA
00308       case (WKC_META | 'U'):
00309 #endif
00310       case (WKC_CTRL | 'U'):
00311         DeleteTextBufferAll(&_iconsole_cmdline);
00312         this->SetDirty();
00313         break;
00314 
00315       case WKC_BACKSPACE: case WKC_DELETE:
00316         if (DeleteTextBufferChar(&_iconsole_cmdline, keycode)) {
00317           IConsoleResetHistoryPos();
00318           this->SetDirty();
00319         }
00320         break;
00321 
00322       case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
00323         if (MoveTextBufferPos(&_iconsole_cmdline, keycode)) {
00324           IConsoleResetHistoryPos();
00325           this->SetDirty();
00326         }
00327         break;
00328 
00329       default:
00330         if (IsValidChar(key, CS_ALPHANUMERAL)) {
00331           IConsoleWindow::scroll = 0;
00332           InsertTextBufferChar(&_iconsole_cmdline, key);
00333           IConsoleResetHistoryPos();
00334           this->SetDirty();
00335         } else {
00336           return ES_NOT_HANDLED;
00337         }
00338         break;
00339     }
00340     return ES_HANDLED;
00341   }
00342 
00343   virtual void OnMouseWheel(int wheel)
00344   {
00345     this->Scroll(-wheel);
00346   }
00347 };
00348 
00349 int IConsoleWindow::scroll = 0;
00350 
00351 void IConsoleGUIInit()
00352 {
00353   IConsoleResetHistoryPos();
00354   _iconsole_mode = ICONSOLE_CLOSED;
00355 
00356   IConsoleLine::Reset();
00357   memset(_iconsole_history, 0, sizeof(_iconsole_history));
00358 
00359   _iconsole_cmdline.buf = CallocT<char>(ICON_CMDLN_SIZE); // create buffer and zero it
00360   _iconsole_cmdline.max_bytes = ICON_CMDLN_SIZE;
00361   _iconsole_cmdline.max_chars = ICON_CMDLN_SIZE;
00362 
00363   IConsolePrintF(CC_WARNING, "OpenTTD Game Console Revision 7 - %s", _openttd_revision);
00364   IConsolePrint(CC_WHITE,  "------------------------------------");
00365   IConsolePrint(CC_WHITE,  "use \"help\" for more information");
00366   IConsolePrint(CC_WHITE,  "");
00367   IConsoleClearCommand();
00368 }
00369 
00370 void IConsoleClearBuffer()
00371 {
00372   IConsoleLine::Reset();
00373 }
00374 
00375 void IConsoleGUIFree()
00376 {
00377   free(_iconsole_cmdline.buf);
00378   IConsoleClearBuffer();
00379 }
00380 
00382 void IConsoleResize(Window *w)
00383 {
00384   switch (_iconsole_mode) {
00385     case ICONSOLE_OPENED:
00386       w->height = _screen.height / 3;
00387       w->width = _screen.width;
00388       break;
00389     case ICONSOLE_FULL:
00390       w->height = _screen.height - ICON_BOTTOM_BORDERWIDTH;
00391       w->width = _screen.width;
00392       break;
00393     default: return;
00394   }
00395 
00396   MarkWholeScreenDirty();
00397 }
00398 
00400 void IConsoleSwitch()
00401 {
00402   switch (_iconsole_mode) {
00403     case ICONSOLE_CLOSED:
00404       new IConsoleWindow();
00405       break;
00406 
00407     case ICONSOLE_OPENED: case ICONSOLE_FULL:
00408       DeleteWindowById(WC_CONSOLE, 0);
00409       break;
00410   }
00411 
00412   MarkWholeScreenDirty();
00413 }
00414 
00416 void IConsoleClose()
00417 {
00418   if (_iconsole_mode == ICONSOLE_OPENED) IConsoleSwitch();
00419 }
00420 
00427 static const char *IConsoleHistoryAdd(const char *cmd)
00428 {
00429   /* Strip all spaces at the begin */
00430   while (IsWhitespace(*cmd)) cmd++;
00431 
00432   /* Do not put empty command in history */
00433   if (StrEmpty(cmd)) return NULL;
00434 
00435   /* Do not put in history if command is same as previous */
00436   if (_iconsole_history[0] == NULL || strcmp(_iconsole_history[0], cmd) != 0) {
00437     free(_iconsole_history[ICON_HISTORY_SIZE - 1]);
00438     memmove(&_iconsole_history[1], &_iconsole_history[0], sizeof(_iconsole_history[0]) * (ICON_HISTORY_SIZE - 1));
00439     _iconsole_history[0] = strdup(cmd);
00440   }
00441 
00442   /* Reset the history position */
00443   IConsoleResetHistoryPos();
00444   return _iconsole_history[0];
00445 }
00446 
00451 static void IConsoleHistoryNavigate(int direction)
00452 {
00453   if (_iconsole_history[0] == NULL) return; // Empty history
00454   _iconsole_historypos = Clamp(_iconsole_historypos + direction, -1, ICON_HISTORY_SIZE - 1);
00455 
00456   if (direction > 0 && _iconsole_history[_iconsole_historypos] == NULL) _iconsole_historypos--;
00457 
00458   if (_iconsole_historypos == -1) {
00459     *_iconsole_cmdline.buf = '\0';
00460   } else {
00461     ttd_strlcpy(_iconsole_cmdline.buf, _iconsole_history[_iconsole_historypos], _iconsole_cmdline.max_bytes);
00462   }
00463   UpdateTextBufferSize(&_iconsole_cmdline);
00464 }
00465 
00475 void IConsoleGUIPrint(TextColour colour_code, char *str)
00476 {
00477   new IConsoleLine(str, colour_code);
00478   SetWindowDirty(WC_CONSOLE, 0);
00479 }
00480 
00481 
00487 bool IsValidConsoleColour(TextColour c)
00488 {
00489   /* A normal text colour is used. */
00490   if (!(c & TC_IS_PALETTE_COLOUR)) return TC_BEGIN <= c && c < TC_END;
00491 
00492   /* A text colour from the palette is used; must be the company
00493    * colour gradient, so it must be one of those. */
00494   c &= ~TC_IS_PALETTE_COLOUR;
00495   for (uint i = COLOUR_BEGIN; i < COLOUR_END; i++) {
00496     if (_colour_gradient[i][4] == c) return true;
00497   }
00498 
00499   return false;
00500 }