Added m_ prefixes to Console
[supertux.git] / src / supertux / console.cpp
1 //  SuperTux - Console
2 //  Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include "supertux/console.hpp"
18
19 #include <math.h>
20 #include <iostream>
21
22 #include "physfs/ifile_stream.hpp"
23 #include "scripting/squirrel_util.hpp"
24 #include "supertux/gameconfig.hpp"
25 #include "supertux/globals.hpp"
26 #include "video/drawing_context.hpp"
27
28 /// speed (pixels/s) the console closes
29 static const float FADE_SPEED = 1;
30
31 Console::Console() :
32   m_history(),
33   m_history_position(m_history.end()),
34   m_lines(),
35   m_background(Surface::create("images/engine/console.png")),
36   m_background2(Surface::create("images/engine/console2.png")),
37   m_vm(NULL),
38   m_vm_object(),
39   m_backgroundOffset(0),
40   m_height(0),
41   m_alpha(1.0),
42   m_offset(0),
43   m_focused(false),
44   m_font(new Font(Font::FIXED, "fonts/andale12.stf", 1)),
45   m_stayOpen(0)
46 {
47 }
48
49 Console::~Console()
50 {
51   if (m_vm != NULL)
52   {
53     sq_release(scripting::global_vm, &m_vm_object);
54   }
55 }
56
57 void
58 Console::flush(ConsoleStreamBuffer* buffer)
59 {
60   if (buffer == &outputBuffer) {
61     std::string s = outputBuffer.str();
62     if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))) {
63       while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')) s.erase(s.length()-1);
64       addLines(s);
65       outputBuffer.str(std::string());
66     }
67   }
68 }
69
70 void
71 Console::ready_vm()
72 {
73   if(m_vm == NULL) {
74     m_vm = scripting::global_vm;
75     HSQUIRRELVM new_vm = sq_newthread(m_vm, 16);
76     if(new_vm == NULL)
77       throw scripting::SquirrelError(m_vm, "Couldn't create new VM thread for console");
78
79     // store reference to thread
80     sq_resetobject(&m_vm_object);
81     if(SQ_FAILED(sq_getstackobj(m_vm, -1, &m_vm_object)))
82       throw scripting::SquirrelError(m_vm, "Couldn't get vm object for console");
83     sq_addref(m_vm, &m_vm_object);
84     sq_pop(m_vm, 1);
85
86     // create new roottable for thread
87     sq_newtable(new_vm);
88     sq_pushroottable(new_vm);
89     if(SQ_FAILED(sq_setdelegate(new_vm, -2)))
90       throw scripting::SquirrelError(new_vm, "Couldn't set console_table delegate");
91
92     sq_setroottable(new_vm);
93
94     m_vm = new_vm;
95
96     try {
97       std::string filename = "scripts/console.nut";
98       IFileStream stream(filename);
99       scripting::compile_and_run(m_vm, stream, filename);
100     } catch(std::exception& e) {
101       log_warning << "Couldn't load console.nut: " << e.what() << std::endl;
102     }
103   }
104 }
105
106 void
107 Console::execute_script(const std::string& command)
108 {
109   using namespace scripting;
110
111   ready_vm();
112
113   SQInteger oldtop = sq_gettop(m_vm);
114   try {
115     if(SQ_FAILED(sq_compilebuffer(m_vm, command.c_str(), command.length(),
116                                   "", SQTrue)))
117       throw SquirrelError(m_vm, "Couldn't compile command");
118
119     sq_pushroottable(m_vm);
120     if(SQ_FAILED(sq_call(m_vm, 1, SQTrue, SQTrue)))
121       throw SquirrelError(m_vm, "Problem while executing command");
122
123     if(sq_gettype(m_vm, -1) != OT_NULL)
124       addLines(squirrel2string(m_vm, -1));
125   } catch(std::exception& e) {
126     addLines(e.what());
127   }
128   SQInteger newtop = sq_gettop(m_vm);
129   if(newtop < oldtop) {
130     log_fatal << "Script destroyed squirrel stack..." << std::endl;
131   } else {
132     sq_settop(m_vm, oldtop);
133   }
134 }
135
136 void
137 Console::input(char c)
138 {
139   inputBuffer.insert(inputBufferPosition, 1, c);
140   inputBufferPosition++;
141 }
142
143 void
144 Console::backspace()
145 {
146   if ((inputBufferPosition > 0) && (inputBuffer.length() > 0)) {
147     inputBuffer.erase(inputBufferPosition-1, 1);
148     inputBufferPosition--;
149   }
150 }
151
152 void
153 Console::eraseChar()
154 {
155   if (inputBufferPosition < (int)inputBuffer.length()) {
156     inputBuffer.erase(inputBufferPosition, 1);
157   }
158 }
159
160 void
161 Console::enter()
162 {
163   addLines("> "+inputBuffer);
164   parse(inputBuffer);
165   inputBuffer = "";
166   inputBufferPosition = 0;
167 }
168
169 void
170 Console::scroll(int numLines)
171 {
172   m_offset += numLines;
173   if (m_offset > 0) m_offset = 0;
174 }
175
176 void
177 Console::show_history(int offset_)
178 {
179   while ((offset_ > 0) && (m_history_position != m_history.end())) {
180     m_history_position++;
181     offset_--;
182   }
183   while ((offset_ < 0) && (m_history_position != m_history.begin())) {
184     m_history_position--;
185     offset_++;
186   }
187   if (m_history_position == m_history.end()) {
188     inputBuffer = "";
189     inputBufferPosition = 0;
190   } else {
191     inputBuffer = *m_history_position;
192     inputBufferPosition = inputBuffer.length();
193   }
194 }
195
196 void
197 Console::move_cursor(int offset_)
198 {
199   if (offset_ == -65535) inputBufferPosition = 0;
200   if (offset_ == +65535) inputBufferPosition = inputBuffer.length();
201   inputBufferPosition+=offset_;
202   if (inputBufferPosition < 0) inputBufferPosition = 0;
203   if (inputBufferPosition > (int)inputBuffer.length()) inputBufferPosition = inputBuffer.length();
204 }
205
206 // Helper functions for Console::autocomplete
207 // TODO: Fix rough documentation
208 namespace {
209
210 void sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix);
211
212 /**
213  * Acts upon key,value on top of stack:
214  * Appends key (plus type-dependent suffix) to cmds if table_prefix+key starts with search_prefix;
215  * Calls sq_insert_commands if search_prefix starts with table_prefix+key (and value is a table/class/instance);
216  */
217 void
218 sq_insert_command(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
219 {
220   const SQChar* key_chars;
221   if (SQ_FAILED(sq_getstring(vm, -2, &key_chars))) return;
222   std::string key_string = table_prefix + key_chars;
223
224   switch (sq_gettype(vm, -1)) {
225     case OT_INSTANCE:
226       key_string+=".";
227       if (search_prefix.substr(0, key_string.length()) == key_string) {
228         sq_getclass(vm, -1);
229         sq_insert_commands(cmds, vm, key_string, search_prefix);
230         sq_pop(vm, 1);
231       }
232       break;
233     case OT_TABLE:
234     case OT_CLASS:
235       key_string+=".";
236       if (search_prefix.substr(0, key_string.length()) == key_string) {
237         sq_insert_commands(cmds, vm, key_string, search_prefix);
238       }
239       break;
240     case OT_CLOSURE:
241     case OT_NATIVECLOSURE:
242       key_string+="()";
243       break;
244     default:
245       break;
246   }
247
248   if (key_string.substr(0, search_prefix.length()) == search_prefix) {
249     cmds.push_back(key_string);
250   }
251
252 }
253
254 /**
255  * calls sq_insert_command for all entries of table/class on top of stack
256  */
257 void
258 sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
259 {
260   sq_pushnull(vm); // push iterator
261   while (SQ_SUCCEEDED(sq_next(vm,-2))) {
262     sq_insert_command(cmds, vm, table_prefix, search_prefix);
263     sq_pop(vm, 2); // pop key, val
264   }
265   sq_pop(vm, 1); // pop iterator
266 }
267
268 }
269 // End of Console::autocomplete helper functions
270
271 void
272 Console::autocomplete()
273 {
274   //int autocompleteFrom = inputBuffer.find_last_of(" ();+", inputBufferPosition);
275   int autocompleteFrom = inputBuffer.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_->.", inputBufferPosition);
276   if (autocompleteFrom != (int)std::string::npos) {
277     autocompleteFrom += 1;
278   } else {
279     autocompleteFrom = 0;
280   }
281   std::string prefix = inputBuffer.substr(autocompleteFrom, inputBufferPosition - autocompleteFrom);
282   addLines("> "+prefix);
283
284   std::list<std::string> cmds;
285
286   ready_vm();
287
288   // append all keys of the current root table to list
289   sq_pushroottable(m_vm); // push root table
290   while(true) {
291     // check all keys (and their children) for matches
292     sq_insert_commands(cmds, m_vm, "", prefix);
293
294     // cycle through parent(delegate) table
295     SQInteger oldtop = sq_gettop(m_vm);
296     if(SQ_FAILED(sq_getdelegate(m_vm, -1)) || oldtop == sq_gettop(m_vm)) {
297       break;
298     }
299     sq_remove(m_vm, -2); // remove old table
300   }
301   sq_pop(m_vm, 1); // remove table
302
303   // depending on number of hits, show matches or autocomplete
304   if (cmds.empty()) addLines("No known command starts with \""+prefix+"\"");
305   if (cmds.size() == 1) {
306     // one match: just replace input buffer with full command
307     std::string replaceWith = cmds.front();
308     inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
309     inputBufferPosition += (replaceWith.length() - prefix.length());
310   }
311   if (cmds.size() > 1) {
312     // multiple matches: show all matches and set input buffer to longest common prefix
313     std::string commonPrefix = cmds.front();
314     while (cmds.begin() != cmds.end()) {
315       std::string cmd = cmds.front();
316       cmds.pop_front();
317       addLines(cmd);
318       for (int n = commonPrefix.length(); n >= 1; n--) {
319         if (cmd.compare(0, n, commonPrefix) != 0) commonPrefix.resize(n-1); else break;
320       }
321     }
322     std::string replaceWith = commonPrefix;
323     inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
324     inputBufferPosition += (replaceWith.length() - prefix.length());
325   }
326 }
327
328 void
329 Console::addLines(std::string s)
330 {
331   std::istringstream iss(s);
332   std::string line;
333   while (std::getline(iss, line, '\n')) addLine(line);
334 }
335
336 void
337 Console::addLine(std::string s)
338 {
339   // output line to stderr
340   std::cerr << s << std::endl;
341
342   // wrap long lines
343   std::string overflow;
344   unsigned int line_count = 0;
345   do {
346     m_lines.push_front(Font::wrap_to_chars(s, 99, &overflow));
347     line_count++;
348     s = overflow;
349   } while (s.length() > 0);
350
351   // trim scrollback buffer
352   while (m_lines.size() >= 1000)
353     m_lines.pop_back();
354
355   // increase console height if necessary
356   if ((m_stayOpen > 0) && (m_height < 64)) {
357     if(m_height < 4)
358       m_height = 4;
359     m_height += m_font->get_height() * line_count;
360   }
361
362   // reset console to full opacity
363   m_alpha = 1.0;
364 }
365
366 void
367 Console::parse(std::string s)
368 {
369   // make sure we actually have something to parse
370   if (s.length() == 0) return;
371
372   // add line to history
373   m_history.push_back(s);
374   m_history_position = m_history.end();
375
376   // split line into list of args
377   std::vector<std::string> args;
378   size_t start = 0;
379   size_t end = 0;
380   while (1) {
381     start = s.find_first_not_of(" ,", end);
382     end = s.find_first_of(" ,", start);
383     if (start == s.npos) break;
384     args.push_back(s.substr(start, end-start));
385   }
386
387   // command is args[0]
388   if (args.size() == 0) return;
389   std::string command = args.front();
390   args.erase(args.begin());
391
392   // ignore if it's an internal command
393   if (consoleCommand(command,args)) return;
394
395   try {
396     execute_script(s);
397   } catch(std::exception& e) {
398     addLines(e.what());
399   }
400
401 }
402
403 bool
404 Console::consoleCommand(std::string /*command*/, std::vector<std::string> /*arguments*/)
405 {
406   return false;
407 }
408
409 bool
410 Console::hasFocus()
411 {
412   return m_focused;
413 }
414
415 void
416 Console::show()
417 {
418   if(!g_config->console_enabled)
419     return;
420
421   m_focused = true;
422   m_height = 256;
423   m_alpha = 1.0;
424 //  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // Useless in SDL2 :  if you want to disable repeat, then you need to check if the key was repeated and ignore it.
425 }
426
427 void
428 Console::open()
429 {
430   if(m_stayOpen < 2)
431     m_stayOpen += 1.5;
432 }
433
434 void
435 Console::hide()
436 {
437   m_focused = false;
438   m_height = 0;
439   m_stayOpen = 0;
440
441   // clear input buffer
442   inputBuffer = "";
443   inputBufferPosition = 0;
444  // SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
445 }
446
447 void
448 Console::toggle()
449 {
450   if (Console::hasFocus()) {
451     Console::hide();
452   }
453   else {
454     Console::show();
455   }
456 }
457
458 void
459 Console::update(float elapsed_time)
460 {
461   if(m_stayOpen > 0) {
462     m_stayOpen -= elapsed_time;
463     if(m_stayOpen < 0)
464       m_stayOpen = 0;
465   } else if(!m_focused && m_height > 0) {
466     m_alpha -= elapsed_time * FADE_SPEED;
467     if(m_alpha < 0) {
468       m_alpha = 0;
469       m_height = 0;
470     }
471   }
472 }
473
474 void
475 Console::draw(DrawingContext& context)
476 {
477   if (m_height == 0)
478     return;
479
480   int layer = LAYER_GUI + 1;
481
482   context.push_transform();
483   context.set_alpha(m_alpha);
484   context.draw_surface(m_background2, 
485                        Vector(SCREEN_WIDTH/2 - m_background->get_width()/2 - m_background->get_width() + m_backgroundOffset, 
486                               m_height - m_background->get_height()), 
487                        layer);
488   context.draw_surface(m_background2, 
489                        Vector(SCREEN_WIDTH/2 - m_background->get_width()/2 + m_backgroundOffset,
490                               m_height - m_background->get_height()), 
491                        layer);
492   for (int x = (SCREEN_WIDTH/2 - m_background->get_width()/2 
493                 - (static_cast<int>(ceilf((float)SCREEN_WIDTH /
494                                           (float)m_background->get_width()) - 1) * m_background->get_width())); 
495        x < SCREEN_WIDTH; 
496        x += m_background->get_width())
497   {
498     context.draw_surface(m_background, Vector(x, m_height - m_background->get_height()), layer);
499   }
500   m_backgroundOffset+=10;
501   if (m_backgroundOffset > (int)m_background->get_width()) m_backgroundOffset -= (int)m_background->get_width();
502
503   int lineNo = 0;
504
505   if (m_focused) {
506     lineNo++;
507     float py = m_height-4-1 * m_font->get_height();
508     context.draw_text(m_font, "> "+inputBuffer, Vector(4, py), ALIGN_LEFT, layer);
509     if (SDL_GetTicks() % 1000 < 750) {
510       int cursor_px = 2 + inputBufferPosition;
511       context.draw_text(m_font, "_", Vector(4 + (cursor_px * m_font->get_text_width("X")), py), ALIGN_LEFT, layer);
512     }
513   }
514
515   int skipLines = -m_offset;
516   for (std::list<std::string>::iterator i = m_lines.begin(); i != m_lines.end(); i++) {
517     if (skipLines-- > 0) continue;
518     lineNo++;
519     float py = m_height - 4 - lineNo * m_font->get_height();
520     if (py < -m_font->get_height()) break;
521     context.draw_text(m_font, *i, Vector(4, py), ALIGN_LEFT, layer);
522   }
523   context.pop_transform();
524 }
525
526 int Console::inputBufferPosition = 0;
527 std::string Console::inputBuffer;
528 ConsoleStreamBuffer Console::outputBuffer;
529 std::ostream Console::output(&Console::outputBuffer);
530
531 /* EOF */