New grow and skid sounds from remaxim
[supertux.git] / src / console.cpp
index b15416a..9c82a0e 100644 (file)
@@ -19,6 +19,9 @@
 #include <config.h>
 
 #include <iostream>
+#include <math.h>
+#include <SDL_timer.h>
+#include <SDL_keyboard.h>
 #include "console.hpp"
 #include "video/drawing_context.hpp"
 #include "video/surface.hpp"
 #include "main.hpp"
 #include "log.hpp"
 #include "resources.hpp"
+#include "gameconfig.hpp"
 
 /// speed (pixels/s) the console closes
 static const float FADE_SPEED = 1;
 
 Console::Console()
-  : history_position(history.end()), vm(NULL), backgroundOffset(0), 
+  : history_position(history.end()), vm(NULL), backgroundOffset(0),
     height(0), alpha(1.0), offset(0), focused(false), stayOpen(0) {
   fontheight = 8;
 }
 
-Console::~Console() 
+Console::~Console()
 {
   if(vm != NULL) {
     sq_release(Scripting::global_vm, &vm_object);
@@ -49,15 +53,14 @@ Console::~Console()
 void
 Console::init_graphics()
 {
-  font.reset(new Font("images/engine/fonts/white-small.png",
-                      "images/engine/fonts/shadow-small.png", 8, 9, 1));
+  font.reset(new Font(Font::FIXED,"fonts/andale12.stf",1));
   fontheight = font->get_height();
   background.reset(new Surface("images/engine/console.png"));
   background2.reset(new Surface("images/engine/console2.png"));
 }
 
-void 
-Console::flush(ConsoleStreamBuffer* buffer) 
+void
+Console::flush(ConsoleStreamBuffer* buffer)
 {
   if (buffer == &outputBuffer) {
     std::string s = outputBuffer.str();
@@ -67,15 +70,6 @@ Console::flush(ConsoleStreamBuffer* buffer)
       outputBuffer.str(std::string());
     }
   }
-  if (buffer == &inputBuffer) {
-    std::string s = inputBuffer.str();
-    if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))) {
-      while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')) s.erase(s.length()-1);
-      addLines("> "+s);
-      parse(s);
-      inputBuffer.str(std::string());
-    }
-  }
 }
 
 void
@@ -93,7 +87,7 @@ Console::ready_vm()
       throw Scripting::SquirrelError(vm, "Couldn't get vm object for console");
     sq_addref(vm, &vm_object);
     sq_pop(vm, 1);
-    
+
     // create new roottable for thread
     sq_newtable(new_vm);
     sq_pushroottable(new_vm);
@@ -103,7 +97,7 @@ Console::ready_vm()
     sq_setroottable(new_vm);
 
     vm = new_vm;
-    
+
     try {
       std::string filename = "scripts/console.nut";
       IFileStream stream(filename);
@@ -119,15 +113,15 @@ Console::execute_script(const std::string& command)
 {
   using namespace Scripting;
 
-  ready_vm(); 
+  ready_vm();
 
-  SQInteger oldtop = sq_gettop(vm); 
+  SQInteger oldtop = sq_gettop(vm);
   try {
     if(SQ_FAILED(sq_compilebuffer(vm, command.c_str(), command.length(),
                  "", SQTrue)))
       throw SquirrelError(vm, "Couldn't compile command");
 
-    sq_pushroottable(vm); 
+    sq_pushroottable(vm);
     if(SQ_FAILED(sq_call(vm, 1, SQTrue, SQTrue)))
       throw SquirrelError(vm, "Problem while executing command");
 
@@ -144,18 +138,40 @@ Console::execute_script(const std::string& command)
   }
 }
 
+void 
+Console::input(char c)
+{
+  inputBuffer.insert(inputBufferPosition, 1, c);
+  inputBufferPosition++;
+}
+
 void
 Console::backspace()
 {
-  std::string s = inputBuffer.str();
-  if (s.length() > 0) {
-    s.erase(s.length()-1);
-    inputBuffer.str(s);
-    inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
+  if ((inputBufferPosition > 0) && (inputBuffer.length() > 0)) {
+    inputBuffer.erase(inputBufferPosition-1, 1);
+    inputBufferPosition--;
   }
 }
 
 void
+Console::eraseChar()
+{
+  if (inputBufferPosition < (int)inputBuffer.length()) {
+    inputBuffer.erase(inputBufferPosition, 1);
+  }
+}
+
+void
+Console::enter()
+{
+  addLines("> "+inputBuffer);
+  parse(inputBuffer);
+  inputBuffer = "";
+  inputBufferPosition = 0;
+}
+
+void
 Console::scroll(int numLines)
 {
   offset += numLines;
@@ -174,54 +190,114 @@ Console::show_history(int offset)
     offset++;
   }
   if (history_position == history.end()) {
-    inputBuffer.str(std::string());
+    inputBuffer = "";
+    inputBufferPosition = 0;
   } else {
-    inputBuffer.str(*history_position);
-    inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
+    inputBuffer = *history_position;
+    inputBufferPosition = inputBuffer.length();
+  }
+}
+
+void 
+Console::move_cursor(int offset)
+{
+  if (offset == -65535) inputBufferPosition = 0;
+  if (offset == +65535) inputBufferPosition = inputBuffer.length();
+  inputBufferPosition+=offset;
+  if (inputBufferPosition < 0) inputBufferPosition = 0;
+  if (inputBufferPosition > (int)inputBuffer.length()) inputBufferPosition = inputBuffer.length();
+}
+
+// Helper functions for Console::autocomplete
+// TODO: Fix rough documentation
+namespace {
+
+void sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix);
+
+/**
+ * Acts upon key,value on top of stack:
+ * Appends key (plus type-dependent suffix) to cmds if table_prefix+key starts with search_prefix;
+ * Calls sq_insert_commands if search_prefix starts with table_prefix+key (and value is a table/class/instance);
+ */
+void
+sq_insert_command(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
+{
+  const SQChar* key_chars;
+  if (SQ_FAILED(sq_getstring(vm, -2, &key_chars))) return;
+  std::string key_string = table_prefix + key_chars;
+
+  switch (sq_gettype(vm, -1)) {
+    case OT_INSTANCE:
+      key_string+=".";
+      if (search_prefix.substr(0, key_string.length()) == key_string) {
+        sq_getclass(vm, -1);
+        sq_insert_commands(cmds, vm, key_string, search_prefix);
+        sq_pop(vm, 1);
+      }
+      break;
+    case OT_TABLE:
+    case OT_CLASS:
+      key_string+=".";
+      if (search_prefix.substr(0, key_string.length()) == key_string) {
+        sq_insert_commands(cmds, vm, key_string, search_prefix);
+      }
+      break;
+    case OT_CLOSURE:
+    case OT_NATIVECLOSURE:
+      key_string+="()";
+      break;
+    default:
+      break;
+  }
+
+  if (key_string.substr(0, search_prefix.length()) == search_prefix) {
+    cmds.push_back(key_string);
   }
+
 }
 
+/**
+ * calls sq_insert_command for all entries of table/class on top of stack
+ */
+void
+sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
+{
+  sq_pushnull(vm); // push iterator
+  while (SQ_SUCCEEDED(sq_next(vm,-2))) {
+    sq_insert_command(cmds, vm, table_prefix, search_prefix);
+    sq_pop(vm, 2); // pop key, val
+  }
+  sq_pop(vm, 1); // pop iterator
+}
+
+
+}
+// End of Console::autocomplete helper functions
+
 void
 Console::autocomplete()
 {
-  std::string cmdPart = inputBuffer.str();
-  addLines("> "+cmdPart);
-
-  std::string cmdList = "";
-  int cmdListLen = 0;
-
-  // append all known CCRs to cmdList
-  for (std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.begin(); i != commands.end(); i++) {
-    std::string cmdKnown = i->first;
-    if (cmdKnown.substr(0, cmdPart.length()) == cmdPart) {
-      if (cmdListLen > 0) cmdList = cmdList + ", ";
-      cmdList = cmdList + cmdKnown;
-      cmdListLen++;
-    }
+  //int autocompleteFrom = inputBuffer.find_last_of(" ();+", inputBufferPosition);
+  int autocompleteFrom = inputBuffer.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_->.", inputBufferPosition);
+  if (autocompleteFrom != (int)std::string::npos) {
+    autocompleteFrom += 1;
+  } else {
+    autocompleteFrom = 0;
   }
+  std::string prefix = inputBuffer.substr(autocompleteFrom, inputBufferPosition - autocompleteFrom);
+  addLines("> "+prefix);
+
+  std::list<std::string> cmds;
 
   ready_vm();
 
-  // append all keys of the current root table to cmdList
+  // append all keys of the current root table to list
   sq_pushroottable(vm); // push root table
   while(true) {
-    sq_pushnull(vm); // push null
-    while (SQ_SUCCEEDED(sq_next(vm,-2))) {
-      const SQChar* s;
-      if(SQ_SUCCEEDED(sq_getstring(vm, -2, &s))) {
-        std::string cmdKnown = s;
-        if (cmdKnown.substr(0, cmdPart.length()) == cmdPart) {
-          if (cmdListLen > 0) cmdList = cmdList + ", ";
-          cmdList = cmdList + cmdKnown;
-          cmdListLen++;
-        }
-      }
+    // check all keys (and their children) for matches
+    sq_insert_commands(cmds, vm, "", prefix);
 
-      sq_pop(vm, 2); // pop key, val
-    }
-   
     // cycle through parent(delegate) table
-    sq_pop(vm, 1); // pop iterator
     SQInteger oldtop = sq_gettop(vm);
     if(SQ_FAILED(sq_getdelegate(vm, -1)) || oldtop == sq_gettop(vm)) {
       break;
@@ -231,44 +307,62 @@ Console::autocomplete()
   sq_pop(vm, 1); // remove table
 
   // depending on number of hits, show matches or autocomplete
-  if (cmdListLen == 0) addLines("No known command starts with \""+cmdPart+"\"");
-  if (cmdListLen == 1) {
-    inputBuffer.str(cmdList);
-    inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
+  if (cmds.size() == 0) addLines("No known command starts with \""+prefix+"\"");
+  if (cmds.size() == 1) {
+    // one match: just replace input buffer with full command
+    std::string replaceWith = cmds.front();
+    inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
+    inputBufferPosition += (replaceWith.length() - prefix.length());
+  }
+  if (cmds.size() > 1) {
+    // multiple matches: show all matches and set input buffer to longest common prefix
+    std::string commonPrefix = cmds.front();
+    while (cmds.begin() != cmds.end()) {
+      std::string cmd = cmds.front();
+      cmds.pop_front();
+      addLines(cmd);
+      for (int n = commonPrefix.length(); n >= 1; n--) {
+        if (cmd.compare(0, n, commonPrefix) != 0) commonPrefix.resize(n-1); else break;
+      }
+    }
+    std::string replaceWith = commonPrefix;
+    inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
+    inputBufferPosition += (replaceWith.length() - prefix.length());
   }
-  if (cmdListLen > 1) addLines(cmdList);
 }
 
-void 
-Console::addLines(std::string s) 
+void
+Console::addLines(std::string s)
 {
   std::istringstream iss(s);
   std::string line;
   while (std::getline(iss, line, '\n')) addLine(line);
 }
 
-void 
-Console::addLine(std::string s) 
+void
+Console::addLine(std::string s)
 {
   // output line to stderr
   std::cerr << s << std::endl;
 
   // wrap long lines
   std::string overflow;
+  unsigned int line_count = 0;
   do {
     lines.push_front(Font::wrap_to_chars(s, 99, &overflow));
+    line_count++;
     s = overflow;
   } while (s.length() > 0);
 
   // trim scrollback buffer
   while (lines.size() >= 1000)
     lines.pop_back();
+
   // increase console height if necessary
   if (height < 64) {
     if(height < 4)
       height = 4;
-    height += fontheight;
+    height += fontheight * line_count;
   }
 
   // reset console to full opacity
@@ -280,7 +374,7 @@ Console::addLine(std::string s)
 }
 
 void
-Console::parse(std::string s) 
+Console::parse(std::string s)
 {
   // make sure we actually have something to parse
   if (s.length() == 0) return;
@@ -308,53 +402,22 @@ Console::parse(std::string s)
   // ignore if it's an internal command
   if (consoleCommand(command,args)) return;
 
-  // look up registered ccr
-  std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(command);
-  if ((i == commands.end()) || (i->second.size() == 0)) {
-    try {
-      execute_script(s);
-    } catch(std::exception& e) {
-      addLines(e.what());
-    }
-    return;
+  try {
+    execute_script(s);
+  } catch(std::exception& e) {
+    addLines(e.what());
   }
 
-  // send command to the most recently registered ccr
-  ConsoleCommandReceiver* ccr = i->second.front();
-  if (ccr->consoleCommand(command, args) != true) log_warning << "Sent command to registered ccr, but command was unhandled" << std::endl;
 }
 
 bool
-Console::consoleCommand(std::string command, std::vector<std::string> arguments) 
+Console::consoleCommand(std::string /*command*/, std::vector<std::string> /*arguments*/)
 {
-  if (command == "ccrs") {
-    if (arguments.size() != 1) {
-      log_info << "Usage: ccrs <command>" << std::endl;
-      return true;
-    }
-    std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(arguments[0]);
-    if ((i == commands.end()) || (i->second.size() == 0)) {
-      log_info << "unknown command: \"" << arguments[0] << "\"" << std::endl;
-      return true;
-    }
-
-    std::ostringstream ccr_list;
-    std::list<ConsoleCommandReceiver*> &ccrs = i->second;
-    std::list<ConsoleCommandReceiver*>::iterator j;
-    for (j = ccrs.begin(); j != ccrs.end(); j++) {
-      if (j != ccrs.begin()) ccr_list << ", ";
-      ccr_list << "[" << *j << "]";
-    }
-
-    log_info << "registered ccrs for \"" << arguments[0] << "\": " << ccr_list.str() << std::endl;
-    return true;
-  }
-
   return false;
 }
 
 bool
-Console::hasFocus() 
+Console::hasFocus()
 {
   return focused;
 }
@@ -362,12 +425,16 @@ Console::hasFocus()
 void
 Console::show()
 {
+  if(!config->console_enabled)
+    return;
+
   focused = true;
   height = 256;
   alpha = 1.0;
+  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
 }
 
-void 
+void
 Console::hide()
 {
   focused = false;
@@ -375,16 +442,18 @@ Console::hide()
   stayOpen = 0;
 
   // clear input buffer
-  inputBuffer.str(std::string());
+  inputBuffer = "";
+  inputBufferPosition = 0;
+  SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
 }
 
-void 
+void
 Console::toggle()
 {
   if (Console::hasFocus()) {
-    Console::hide(); 
-  } 
-  else { 
+    Console::hide();
+  }
+  else {
     Console::show();
   }
 }
@@ -405,7 +474,7 @@ Console::update(float elapsed_time)
   }
 }
 
-void 
+void
 Console::draw(DrawingContext& context)
 {
   if (height == 0)
@@ -417,7 +486,9 @@ Console::draw(DrawingContext& context)
   context.set_alpha(alpha);
   context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 - background->get_width() + backgroundOffset, height - background->get_height()), layer);
   context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 + backgroundOffset, height - background->get_height()), layer);
-  context.draw_surface(background.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2, height - background->get_height()), layer);
+  for (int x = (SCREEN_WIDTH/2 - background->get_width()/2 - (static_cast<int>(ceilf((float)SCREEN_WIDTH / (float)background->get_width()) - 1) * background->get_width())); x < SCREEN_WIDTH; x+=background->get_width()) {
+    context.draw_surface(background.get(), Vector(x, height - background->get_height()), layer);
+  }
   backgroundOffset+=10;
   if (backgroundOffset > (int)background->get_width()) backgroundOffset -= (int)background->get_width();
 
@@ -425,59 +496,28 @@ Console::draw(DrawingContext& context)
 
   if (focused) {
     lineNo++;
-    float py = height-4-1*9;
-    context.draw_text(font.get(), "> "+inputBuffer.str()+"_", Vector(4, py), LEFT_ALLIGN, layer);
+    float py = height-4-1 * font->get_height();
+    context.draw_text(font.get(), "> "+inputBuffer, Vector(4, py), ALIGN_LEFT, layer);
+    if (SDL_GetTicks() % 1000 < 750) {
+      int cursor_px = 2 + inputBufferPosition;
+      context.draw_text(font.get(), "_", Vector(4 + (cursor_px * font->get_text_width("X")), py), ALIGN_LEFT, layer);
+    }
   }
 
   int skipLines = -offset;
   for (std::list<std::string>::iterator i = lines.begin(); i != lines.end(); i++) {
     if (skipLines-- > 0) continue;
     lineNo++;
-    float py = height-4-lineNo*9;
-    if (py < -9) break;
-    context.draw_text(font.get(), *i, Vector(4, py), LEFT_ALLIGN, layer);
+    float py = height - 4 - lineNo*font->get_height();
+    if (py < -font->get_height()) break;
+    context.draw_text(font.get(), *i, Vector(4, py), ALIGN_LEFT, layer);
   }
-
   context.pop_transform();
 }
 
-void 
-Console::registerCommand(std::string command, ConsoleCommandReceiver* ccr)
-{
-  commands[command].push_front(ccr);
-}
-
-void 
-Console::unregisterCommand(std::string command, ConsoleCommandReceiver* ccr)
-{
-  std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(command);
-  if ((i == commands.end()) || (i->second.size() == 0)) {
-    log_warning << "Command \"" << command << "\" not associated with a command receiver. Not dissociated." << std::endl;
-    return;
-  }
-  std::list<ConsoleCommandReceiver*>::iterator j = find(i->second.begin(), i->second.end(), ccr);
-  if (j == i->second.end()) {
-    log_warning << "Command \"" << command << "\" not associated with given command receiver. Not dissociated." << std::endl;
-    return;
-  }
-  i->second.erase(j);
-}
-
-void 
-Console::unregisterCommands(ConsoleCommandReceiver* ccr)
-{
-  for (std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.begin(); i != commands.end(); i++) {
-    std::list<ConsoleCommandReceiver*> &ccrs = i->second;
-    std::list<ConsoleCommandReceiver*>::iterator j;
-    while ((j = find(ccrs.begin(), ccrs.end(), ccr)) != ccrs.end()) {
-      ccrs.erase(j);
-    }
-  }
-}
-
 Console* Console::instance = NULL;
-ConsoleStreamBuffer Console::inputBuffer;
+int Console::inputBufferPosition = 0;
+std::string Console::inputBuffer;
 ConsoleStreamBuffer Console::outputBuffer;
-std::ostream Console::input(&Console::inputBuffer);
 std::ostream Console::output(&Console::outputBuffer);