fix cr/lfs and remove trailing whitespaces...
[supertux.git] / src / console.cpp
index 091619c..d6b2238 100644 (file)
@@ -1,4 +1,4 @@
-//  $Id: worldmap.cpp 3209 2006-04-02 22:19:22Z sommer $
+//  $Id$
 //
 //  SuperTux - Console
 //  Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
 #include "video/drawing_context.hpp"
 #include "video/surface.hpp"
 #include "scripting/squirrel_error.hpp"
-#include "scripting/wrapper_util.hpp"
+#include "scripting/squirrel_util.hpp"
+#include "physfs/physfs_stream.hpp"
 #include "player_status.hpp"
-#include "script_manager.hpp"
 #include "main.hpp"
+#include "log.hpp"
 #include "resources.hpp"
 
-namespace {
-  int ticks; // TODO: use a clock?
-}
+/// speed (pixels/s) the console closes
+static const float FADE_SPEED = 1;
 
 Console::Console()
+  : history_position(history.end()), vm(NULL), backgroundOffset(0),
+    height(0), alpha(1.0), offset(0), focused(false), stayOpen(0) {
+  fontheight = 8;
+}
+
+Console::~Console()
 {
-  background = new Surface("images/engine/console.png");
-  background2 = new Surface("images/engine/console2.png");
+  if(vm != NULL) {
+    sq_release(Scripting::global_vm, &vm_object);
+  }
 }
 
-Console::~Console() 
+void
+Console::init_graphics()
 {
-  delete background;
-  delete background2;
+  font.reset(new Font("images/engine/fonts/white-small.png",
+                      "images/engine/fonts/shadow-small.png", 8, 9, 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();
     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);
-      addLine(s);
+      addLines(s);
       outputBuffer.str(std::string());
     }
   }
@@ -60,7 +71,7 @@ Console::flush(ConsoleStreamBuffer* buffer)
     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);
-      addLine("> "+s);
+      addLines("> "+s);
       parse(s);
       inputBuffer.str(std::string());
     }
@@ -68,33 +79,66 @@ Console::flush(ConsoleStreamBuffer* buffer)
 }
 
 void
+Console::ready_vm()
+{
+  if(vm == NULL) {
+    vm = Scripting::global_vm;
+    HSQUIRRELVM new_vm = sq_newthread(vm, 16);
+    if(new_vm == NULL)
+      throw Scripting::SquirrelError(vm, "Couldn't create new VM thread for console");
+
+    // store reference to thread
+    sq_resetobject(&vm_object);
+    if(SQ_FAILED(sq_getstackobj(vm, -1, &vm_object)))
+      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);
+    if(SQ_FAILED(sq_setdelegate(new_vm, -2)))
+      throw Scripting::SquirrelError(new_vm, "Couldn't set console_table delegate");
+
+    sq_setroottable(new_vm);
+
+    vm = new_vm;
+
+    try {
+      std::string filename = "scripts/console.nut";
+      IFileStream stream(filename);
+      Scripting::compile_and_run(vm, stream, filename);
+    } catch(std::exception& e) {
+      log_warning << "Couldn't load console.nut: " << e.what() << std::endl;
+    }
+  }
+}
+
+void
 Console::execute_script(const std::string& command)
 {
   using namespace Scripting;
 
-  HSQUIRRELVM vm = script_manager->get_global_vm();
+  ready_vm();
 
-  if(command == "")
-    return;
-  
-  int 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);
-    if(SQ_FAILED(sq_call(vm, 1, SQTrue)))
+    if(SQ_FAILED(sq_call(vm, 1, SQTrue, SQTrue)))
       throw SquirrelError(vm, "Problem while executing command");
 
     if(sq_gettype(vm, -1) != OT_NULL)
-      addLine(squirrel2string(vm, -1));
+      addLines(squirrel2string(vm, -1));
   } catch(std::exception& e) {
-    addLine(e.what());
+    addLines(e.what());
   }
-  int newtop = sq_gettop(vm);
+  SQInteger newtop = sq_gettop(vm);
   if(newtop < oldtop) {
-    msg_fatal << "Script destroyed squirrel stack..." << std::endl;
+    log_fatal << "Script destroyed squirrel stack..." << std::endl;
   } else {
     sq_settop(vm, oldtop);
   }
@@ -119,52 +163,187 @@ Console::scroll(int numLines)
 }
 
 void
+Console::show_history(int offset)
+{
+  while ((offset > 0) && (history_position != history.end())) {
+    history_position++;
+    offset--;
+  }
+  while ((offset < 0) && (history_position != history.begin())) {
+    history_position--;
+    offset++;
+  }
+  if (history_position == history.end()) {
+    inputBuffer.str(std::string());
+  } else {
+    inputBuffer.str(*history_position);
+    inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
+  }
+}
+
+// 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();
-  addLine("> "+cmdPart);
+  std::string prefix = inputBuffer.str();
+  addLines("> "+prefix);
+
+  std::list<std::string> cmds;
 
-  std::string cmdList = "";
-  int cmdListLen = 0;
+  // append all known CCRs to list
   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++;
+    if (cmdKnown.substr(0, prefix.length()) == prefix) {
+      cmds.push_back(cmdKnown);
+    }
+  }
+
+  ready_vm();
+
+  // append all keys of the current root table to list
+  sq_pushroottable(vm); // push root table
+  while(true) {
+    // check all keys (and their children) for matches
+    sq_insert_commands(cmds, vm, "", prefix);
+
+    // cycle through parent(delegate) table
+    SQInteger oldtop = sq_gettop(vm);
+    if(SQ_FAILED(sq_getdelegate(vm, -1)) || oldtop == sq_gettop(vm)) {
+      break;
     }
+    sq_remove(vm, -2); // remove old table
   }
-  if (cmdListLen == 0) addLine("No known command starts with \""+cmdPart+"\"");
-  if (cmdListLen == 1) {
-    inputBuffer.str(cmdList);
+  sq_pop(vm, 1); // remove table
+
+  // depending on number of hits, show matches or autocomplete
+  if (cmds.size() == 0) addLines("No known command starts with \""+prefix+"\"");
+  if (cmds.size() == 1) {
+    inputBuffer.str(cmds.front());
     inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
   }
-  if (cmdListLen > 1) addLine(cmdList);
+  if (cmds.size() > 1) {
+    while (cmds.begin() != cmds.end()) {
+      addLines(cmds.front());
+      cmds.pop_front();
+    }
+  }
 }
 
-void 
-Console::addLine(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)
+{
+  // output line to stderr
   std::cerr << s << std::endl;
-  while (s.length() > 99) {
-    lines.push_front(s.substr(0, 99-3)+"...");
-    s = "..."+s.substr(99-3);
-  }
-  lines.push_front(s);
-  while (lines.size() >= 65535) lines.pop_back();
+
+  // wrap long lines
+  std::string overflow;
+  do {
+    lines.push_front(Font::wrap_to_chars(s, 99, &overflow));
+    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+9) height=4+9;
-    height+=9;
+    if(height < 4)
+      height = 4;
+    height += fontheight;
   }
-  ticks=60;
+
+  // reset console to full opacity
+  alpha = 1.0;
+
+  // increase time that console stays open
+  if(stayOpen < 6)
+    stayOpen += 1.5;
 }
 
 void
-Console::parse(std::string s) 
+Console::parse(std::string s)
 {
   // make sure we actually have something to parse
   if (s.length() == 0) return;
-       
+
+  // add line to history
+  history.push_back(s);
+  history_position = history.end();
+
   // split line into list of args
   std::vector<std::string> args;
   size_t start = 0;
@@ -190,27 +369,27 @@ Console::parse(std::string s)
     try {
       execute_script(s);
     } catch(std::exception& e) {
-      addLine(e.what());
+      addLines(e.what());
     }
     return;
   }
 
   // send command to the most recently registered ccr
   ConsoleCommandReceiver* ccr = i->second.front();
-  if (ccr->consoleCommand(command, args) != true) msg_warning << "Sent command to registered ccr, but command was unhandled" << std::endl;
+  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) {
-      msg_info << "Usage: ccrs <command>" << std::endl;
+      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)) {
-      msg_info << "unknown command: \"" << arguments[0] << "\"" << std::endl;
+      log_info << "unknown command: \"" << arguments[0] << "\"" << std::endl;
       return true;
     }
 
@@ -222,7 +401,7 @@ Console::consoleCommand(std::string command, std::vector<std::string> arguments)
       ccr_list << "[" << *j << "]";
     }
 
-    msg_info << "registered ccrs for \"" << arguments[0] << "\": " << ccr_list.str() << std::endl;
+    log_info << "registered ccrs for \"" << arguments[0] << "\": " << ccr_list.str() << std::endl;
     return true;
   }
 
@@ -230,7 +409,7 @@ Console::consoleCommand(std::string command, std::vector<std::string> arguments)
 }
 
 bool
-Console::hasFocus() 
+Console::hasFocus()
 {
   return focused;
 }
@@ -240,45 +419,62 @@ Console::show()
 {
   focused = true;
   height = 256;
+  alpha = 1.0;
+  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
 }
 
-void 
+void
 Console::hide()
 {
   focused = false;
   height = 0;
+  stayOpen = 0;
 
   // clear input buffer
   inputBuffer.str(std::string());
+  SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
 }
 
-void 
+void
 Console::toggle()
 {
   if (Console::hasFocus()) {
-    Console::hide(); 
-  } 
-  else { 
+    Console::hide();
+  }
+  else {
     Console::show();
   }
 }
 
-void 
-Console::draw(DrawingContext& context)
+void
+Console::update(float elapsed_time)
 {
-  if (height == 0) return;
-  if (!focused) {
-    if (ticks-- < 0) {
-      height-=10;
-      ticks=0;
-      if (height < 0) height=0;
+  if(stayOpen > 0) {
+    stayOpen -= elapsed_time;
+    if(stayOpen < 0)
+      stayOpen = 0;
+  } else if(!focused && height > 0) {
+    alpha -= elapsed_time * FADE_SPEED;
+    if(alpha < 0) {
+      alpha = 0;
+      height = 0;
     }
-    if (height == 0) return;
   }
+}
 
-  context.draw_surface(background2, Vector(SCREEN_WIDTH/2 - background->get_width()/2 - background->get_width() + backgroundOffset, height - background->get_height()), LAYER_FOREGROUND1+1);
-  context.draw_surface(background2, Vector(SCREEN_WIDTH/2 - background->get_width()/2 + backgroundOffset, height - background->get_height()), LAYER_FOREGROUND1+1);
-  context.draw_surface(background, Vector(SCREEN_WIDTH/2 - background->get_width()/2, height - background->get_height()), LAYER_FOREGROUND1+1);
+void
+Console::draw(DrawingContext& context)
+{
+  if (height == 0)
+    return;
+
+  int layer = LAYER_GUI + 1;
+
+  context.push_transform();
+  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);
   backgroundOffset+=10;
   if (backgroundOffset > (int)background->get_width()) backgroundOffset -= (int)background->get_width();
 
@@ -287,7 +483,7 @@ Console::draw(DrawingContext& context)
   if (focused) {
     lineNo++;
     float py = height-4-1*9;
-    context.draw_text(white_small_text, "> "+inputBuffer.str()+"_", Vector(4, py), LEFT_ALLIGN, LAYER_FOREGROUND1+1);
+    context.draw_text(font.get(), "> "+inputBuffer.str()+"_", Vector(4, py), LEFT_ALLIGN, layer);
   }
 
   int skipLines = -offset;
@@ -296,33 +492,35 @@ Console::draw(DrawingContext& context)
     lineNo++;
     float py = height-4-lineNo*9;
     if (py < -9) break;
-    context.draw_text(white_small_text, *i, Vector(4, py), LEFT_ALLIGN, LAYER_FOREGROUND1+1);
+    context.draw_text(font.get(), *i, Vector(4, py), LEFT_ALLIGN, layer);
   }
+
+  context.pop_transform();
 }
 
-void 
+void
 Console::registerCommand(std::string command, ConsoleCommandReceiver* ccr)
 {
   commands[command].push_front(ccr);
 }
 
-void 
+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)) {
-    msg_warning << "Command \"" << command << "\" not associated with a command receiver. Not dissociated." << std::endl;
+    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()) {
-    msg_warning << "Command \"" << command << "\" not associated with given command receiver. Not dissociated." << std::endl;
+    log_warning << "Command \"" << command << "\" not associated with given command receiver. Not dissociated." << std::endl;
     return;
   }
   i->second.erase(j);
 }
 
-void 
+void
 Console::unregisterCommands(ConsoleCommandReceiver* ccr)
 {
   for (std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.begin(); i != commands.end(); i++) {
@@ -334,14 +532,8 @@ Console::unregisterCommands(ConsoleCommandReceiver* ccr)
   }
 }
 
-int Console::height = 0;
-bool Console::focused = false;
-std::list<std::string> Console::lines;
-std::map<std::string, std::list<ConsoleCommandReceiver*> > Console::commands;
+Console* Console::instance = NULL;
 ConsoleStreamBuffer Console::inputBuffer;
 ConsoleStreamBuffer Console::outputBuffer;
 std::ostream Console::input(&Console::inputBuffer);
 std::ostream Console::output(&Console::outputBuffer);
-int Console::offset = 0;
-int Console::backgroundOffset = 0;
-