Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

text.cpp

/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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; either version 2
 * of the License, or (at your option) any later version.

 * This program 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.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-2-0/engines/agi/text.cpp $
 * $Id: text.cpp 49881 2010-06-15 18:17:51Z eriktorbjorn $
 *
 */

#include "agi/agi.h"
#include "agi/sprite.h"       // for commit_both()
#include "agi/graphics.h"
#include "agi/keyboard.h"

namespace Agi {

void AgiEngine::printText2(int l, const char *msg, int foff, int xoff, int yoff,
                        int len, int fg, int bg, bool checkerboard) {
      int x1, y1;
      int maxx, minx, ofoff;
      int update;
      // Note: Must be unsigned to use AGDS cyrillic characters!
#ifdef __DS__
      // On the DS, a compiler bug causes the text to render incorrectly, because
      // GCC tries to optimisie out writes to this pointer (tested on DevkitARM v19b and v20)
      // Making this pointer volatile fixes this.
      volatile const unsigned char *m;
#else
      const unsigned char *m;
#endif

      // kludge!
      update = 1;
      if (l == 2) {
            update = l = 0;
      }

      // FR: strings with len == 1 were not printed
      if (len == 1) {
            _gfx->putTextCharacter(l, xoff + foff, yoff, *msg, fg, bg, checkerboard);
            maxx = 1;
            minx = 0;
            ofoff = foff;
            y1 = 0;           // Check this
      } else {
            maxx = 0;
            minx = GFX_WIDTH;
            ofoff = foff;

            for (m = (const unsigned char *)msg, x1 = y1 = 0; *m; m++) {

                  if (*m >= 0x20 || *m == 1 || *m == 2 || *m == 3) {
                        // FIXME: Fingolfin asks: why is there a FIXME here? Please either clarify what
                        // needs fixing, or remove it!
                        // FIXME
                        int ypos;

                        ypos = (y1 * CHAR_LINES) + yoff;

                        if ((x1 != (len - 1) || x1 == 39) && (ypos <= (GFX_HEIGHT - CHAR_LINES))) {
                              int xpos;

                              xpos = (x1 * CHAR_COLS) + xoff + foff;

                              if (xpos >= GFX_WIDTH)
                                    continue;

                              _gfx->putTextCharacter(l, xpos, ypos, *m, fg, bg, checkerboard);

                              if (x1 > maxx)
                                    maxx = x1;
                              if (x1 < minx)
                                    minx = x1;
                        }

                        x1++;

                        // DF: changed the len-1 to len...
                        if (x1 == len && m[len] != '\n')
                              y1++, x1 = foff = 0;
                  } else {
                        y1++;
                        x1 = foff = 0;
                  }
            }
      }

      if (l)
            return;

      if (maxx < minx)
            return;

      maxx *= CHAR_COLS;
      minx *= CHAR_COLS;

      if (update) {
            _gfx->scheduleUpdate(foff + xoff + minx, yoff, ofoff + xoff + maxx + CHAR_COLS - 1,
                        yoff + y1 * CHAR_LINES + CHAR_LINES + 1);

            // Making synchronous text updates reduces CPU load
            // when updating status line and input area
            _gfx->doUpdate();
      }
}

//
// len is in characters, not pixels!!
//
void AgiEngine::blitTextbox(const char *p, int y, int x, int len) {
      // if x | y = -1, then center the box
      int xoff, yoff, lin, h, w;
      char *msg, *m;

      debugC(3, kDebugLevelText, "blitTextbox(): x=%d, y=%d, len=%d", x, y, len);
      if (_game.window.active)
            closeWindow();

      if (x == 0 && y == 0 && len == 0)
            x = y = -1;

      if (len <= 0)
            len = 30;

      xoff = x * CHAR_COLS;
      yoff = y * CHAR_LINES;

      m = msg = wordWrapString(agiSprintf(p), &len);

      for (lin = 1; *m; m++) {
            // Test \r for MacOS 8
            if (*m == '\n' || *m == '\r')
                  lin++;
      }

      if (lin * CHAR_LINES > GFX_HEIGHT)
            lin = (GFX_HEIGHT / CHAR_LINES);

      w = (len + 2) * CHAR_COLS;
      h = (lin + 2) * CHAR_LINES;

      if (xoff < 0)
            xoff = (GFX_WIDTH - w - CHAR_COLS) / 2;
      else
            xoff -= CHAR_COLS;

      if (yoff < 0)
            yoff = (GFX_HEIGHT - 3 * CHAR_LINES - h) / 2;

      drawWindow(xoff, yoff, xoff + w - 1, yoff + h - 1);

      printText2(2, msg, 0, CHAR_COLS + xoff, CHAR_LINES + yoff,
                  len + 1, MSG_BOX_TEXT, MSG_BOX_COLOUR);

      free(msg);

      _gfx->doUpdate();
}

void AgiEngine::eraseTextbox() {
      if (!_game.window.active) {
            debugC(3, kDebugLevelText, "eraseTextbox(): no window active");
            return;
      }

      debugC(4, kDebugLevelText, "eraseTextbox(): x1=%d, y1=%d, x2=%d, y2=%d", _game.window.x1,
                  _game.window.y1, _game.window.x2, _game.window.y2);

      _gfx->restoreBlock(_game.window.x1, _game.window.y1,
                  _game.window.x2, _game.window.y2, _game.window.buffer);

      free(_game.window.buffer);
      _game.window.active = false;

      _gfx->doUpdate();
}

/*
 * Public functions
 */

/**
 * Print text in the AGI engine screen.
 */
void AgiEngine::printText(const char *msg, int f, int x, int y, int len, int fg, int bg, bool checkerboard) {
      f *= CHAR_COLS;
      x *= CHAR_COLS;
      y *= CHAR_LINES;

      debugC(4, kDebugLevelText, "printText(): %s, %d, %d, %d, %d, %d, %d", msg, f, x, y, len, fg, bg);
      printText2(0, agiSprintf(msg), f, x, y, len, fg, bg, checkerboard);
}

/**
 * Print text in the AGI engine console.
 */
void AgiEngine::printTextConsole(const char *msg, int x, int y, int len, int fg, int bg) {
      x *= CHAR_COLS;
      y *= 10;

      debugC(4, kDebugLevelText, "printTextConsole(): %s, %d, %d, %d, %d, %d", msg, x, y, len, fg, bg);
      printText2(1, msg, 0, x, y, len, fg, bg);
}

/**
 * Wrap text line to the specified width.
 * @param str  String to wrap.
 * @param len  Length of line.
 *
 * Based on GBAGI implementaiton with permission from the author
 */
char *AgiEngine::wordWrapString(const char *s, int *len) {
      char *outStr, *msgBuf, maxWidth = *len;
      const char *pWord;
      int lnLen, wLen;

      msgBuf = outStr = strdup(s);

      int msgWidth = 0;

      lnLen = 0;

      while (*s) {
            pWord = s;
            wLen = 0;

            while (*s != '\0' && *s != ' ' && *s != '\n' && *s != '\r')
                  s++;

            wLen = (int)(s - pWord);

            if (wLen && *s == '\n' && s[-1] == ' ')
                  wLen--;

            if (wLen + lnLen >= maxWidth) {
                  if (outStr != msgBuf) {
                        if (outStr[-1] == ' ')
                              outStr[-1] = '\n';
                        else
                              *outStr++ = '\n';
                  }

                  lnLen = 0;

                  while (wLen >= maxWidth) {
                        msgWidth = maxWidth;

                        memcpy(outStr, pWord, maxWidth);

                        wLen -= maxWidth;
                        outStr += maxWidth;
                        pWord  += maxWidth;
                        *outStr++ = '\n';
                  }
            }

            if (wLen) {
                  memcpy(outStr, pWord, wLen);
                  outStr += wLen;
            }
            lnLen += wLen+1;

            if (lnLen > msgWidth) {
                  msgWidth = lnLen;

                  if (*s == '\0' || *s == ' ' || *s == '\n' || *s == '\r')
                        msgWidth--;
            }

            if (*s == '\n')
                  lnLen = 0;

            if (*s)
                  *outStr++ = *s++;
      }
      *outStr = '\0';
      *len = msgWidth;

      return msgBuf;
}

/**
 * Remove existing window, if any.
 */
void AgiEngine::closeWindow() {
      debugC(4, kDebugLevelText, "closeWindow()");

      _sprites->eraseBoth();
      eraseTextbox();   // remove window, if any
      _sprites->blitBoth();
      _sprites->commitBoth();       // redraw sprites
      _game.hasWindow = false;
}

/**
 * Display a message box.
 * This function displays the specified message in a text box
 * centered in the screen and waits until a key is pressed.
 * @param p The text to be displayed
 */
int AgiEngine::messageBox(const char *s) {
      int k;

      _sprites->eraseBoth();
      blitTextbox(s, -1, -1, -1);
      _sprites->blitBoth();
      k = waitKey();
      debugC(4, kDebugLevelText, "messageBox(): wait_key returned %02x", k);
      closeWindow();

      return k;
}

/**
 * Display a message box with buttons.
 * This function displays the specified message in a text box
 * centered in the screen and waits until a button is pressed.
 * @param p The text to be displayed
 * @param b NULL-terminated list of button labels
 */
int AgiEngine::selectionBox(const char *m, const char **b) {
      int numButtons = 0;
      int x, y, i, s;
      int key, active = 0;
      int rc = -1;
      int bx[5], by[5];

      _noSaveLoadAllowed = true;

      _sprites->eraseBoth();
      blitTextbox(m, -1, -1, -1);

      x = _game.window.x1 + 5 * CHAR_COLS / 2;
      y = _game.window.y2 - 5 * CHAR_LINES / 2;
      s = _game.window.x2 - _game.window.x1 + 1 - 5 * CHAR_COLS;
      debugC(3, kDebugLevelText, "selectionBox(): s = %d", s);

      // Automatically position buttons
      for (i = 0; b[i]; i++) {
            numButtons++;
            s -= CHAR_COLS * strlen(b[i]);
      }

      if (i > 1) {
            debugC(3, kDebugLevelText, "selectionBox(): s / %d = %d", i - 1, s / (i - 1));
            s /= (i - 1);
      } else {
            x += s / 2;
      }

      for (i = 0; b[i]; i++) {
            bx[i] = x;
            by[i] = y;
            x += CHAR_COLS * strlen(b[i]) + s;
      }

      _sprites->blitBoth();

      clearKeyQueue();

      AllowSyntheticEvents on(this);

      debugC(4, kDebugLevelText, "selectionBox(): waiting...");
      while (!(shouldQuit() || _restartGame)) {
            for (i = 0; b[i]; i++)
                  _gfx->drawCurrentStyleButton(bx[i], by[i], b[i], i == active, false, i == 0);

            pollTimer();
            key = doPollKeyboard();
            switch (key) {
            case KEY_ENTER:
                  rc = active;
                  goto press;
            case KEY_ESCAPE:
                  rc = -1;
                  goto getout;
            case KEY_RIGHT:
                  active++;
                  if (active >= numButtons)
                        active = 0;
                  break;
            case KEY_LEFT:
                  active--;
                  if (active < 0)
                        active = numButtons - 1;
                  break;
            case BUTTON_LEFT:
                  for (i = 0; b[i]; i++) {
                        if (_gfx->testButton(bx[i], by[i], b[i])) {
                              rc = active = i;
                              goto press;
                        }
                  }
                  break;
            case 0x09:  // Tab
                  debugC(3, kDebugLevelText, "selectionBox(): Focus change");
                  active++;
                  active %= i;
                  break;
            }
            _gfx->doUpdate();
      }

press:
      debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc);

getout:
      closeWindow();
      debugC(2, kDebugLevelText, "selectionBox(): Result = %d", rc);

      _noSaveLoadAllowed = false;

      return rc;
}

/**
 *
 */
int AgiEngine::print(const char *p, int lin, int col, int len) {
      if (p == NULL)
            return 0;

      debugC(4, kDebugLevelText, "print(): lin = %d, col = %d, len = %d", lin, col, len);

      if (col == 0 && lin == 0 && len == 0)
            lin = col = -1;

      if (len == 0)
            len = 30;

      blitTextbox(p, lin, col, len);

      if (getflag(fOutputMode)) {
            // non-blocking window
            setflag(fOutputMode, false);
            return 1;
      }

      // blocking

      _noSaveLoadAllowed = true;

      if (_game.vars[vWindowReset] == 0) {
            int k;
            setvar(vKey, 0);
            k = waitKey();
            closeWindow();

            _noSaveLoadAllowed = false;

            return k;
      }

      // timed window

      debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", getvar(21));
      _game.msgBoxTicks = getvar(vWindowReset) * 10;
      setvar(vKey, 0);

      _menuSelected = false;

      do {
            if (getflag(fRestoreJustRan))
                  break;

            if (_menuSelected)
                  break;

            mainCycle();
            if (_game.keypress == KEY_ENTER) {
                  debugC(4, kDebugLevelText, "KEY_ENTER");
                  setvar(vWindowReset, 0);
                  _game.keypress = 0;
                  break;
            }
      } while (_game.msgBoxTicks > 0 && !(shouldQuit() || _restartGame));

      setvar(vWindowReset, 0);

      closeWindow();

      _noSaveLoadAllowed = false;

      return 0;
}

/**
 *
 */
void AgiEngine::printStatus(const char *message, ...) {
      char x[42];
      va_list args;

      va_start(args, message);

      vsprintf(x, message, args);

      va_end(args);

      debugC(4, kDebugLevelText, "fg=%d, bg=%d", STATUS_FG, STATUS_BG);
      printText(x, 0, 0, _game.lineStatus, 40, STATUS_FG, STATUS_BG);
}

static void safeStrcat(Common::String &p, const char *t) {
      if (t != NULL)
            p += t;
}

/**
 * Formats AGI string.
 * This function turns a AGI string into a real string expanding values
 * according to the AGI format specifiers.
 * @param s  string containing the format specifier
 * @param n  logic number
 */
char *AgiEngine::agiSprintf(const char *s) {
      static char agiSprintf_buf[768];
      Common::String p;
      char z[16];

      debugC(3, kDebugLevelText, "logic %d, '%s'", _game.lognum, s);

      while (*s) {
            switch (*s) {
            case '%':
                  s++;
                  switch (*s++) {
                        int i;
                  case 'v':
                        i = strtoul(s, NULL, 10);
                        while (*s >= '0' && *s <= '9')
                              s++;
                        sprintf(z, "%015i", getvar(i));

                        i = 99;
                        if (*s == '|') {
                              s++;
                              i = strtoul(s, NULL, 10);
                              while (*s >= '0' && *s <= '9')
                                    s++;
                        }

                        if (i == 99) {
                              // remove all leading 0
                              // don't remove the 3rd zero if 000
                              for (i = 0; z[i] == '0' && i < 14; i++)
                                  ;
                        } else {
                              i = 15 - i;
                        }
                        safeStrcat(p, z + i);
                        break;
                  case '0':
                        i = strtoul(s, NULL, 10) - 1;
                        safeStrcat(p, objectName(i));
                        break;
                  case 'g':
                        i = strtoul(s, NULL, 10) - 1;
                        safeStrcat(p, _game.logics[0].texts[i]);
                        break;
                  case 'w':
                        i = strtoul(s, NULL, 10) - 1;
                        safeStrcat(p, _game.egoWords[i].word);
                        break;
                  case 's':
                        i = strtoul(s, NULL, 10);
                        safeStrcat(p, agiSprintf(_game.strings[i]));
                        break;
                  case 'm':
                        i = strtoul(s, NULL, 10) - 1;
                        if (_game.logics[_game.lognum].numTexts > i)
                              safeStrcat(p, agiSprintf(_game.logics[_game.lognum].texts[i]));
                        break;
                  }

                  while (*s >= '0' && *s <= '9')
                        s++;
                  break;

            case '\\':
                  s++;
                  // FALL THROUGH

            default:
                  p += *s++;
                  break;
            }
      }

      assert(p.size() < sizeof(agiSprintf_buf));
      strcpy(agiSprintf_buf, p.c_str());
      return agiSprintf_buf;
}

/**
 * Write the status line.
 */
void AgiEngine::writeStatus() {
      char x[64];

      if (_debug.statusline) {
            printStatus("%3d(%03d) %3d,%3d(%3d,%3d)               ",
                        getvar(0), getvar(1), _game.viewTable[0].xPos,
                        _game.viewTable[0].yPos, WIN_TO_PIC_X(_mouse.x),
                        WIN_TO_PIC_Y(_mouse.y));
            return;
      }

      if (!_game.statusLine) {
            clearLines(_game.lineStatus, _game.lineStatus, 0);
            flushLines(_game.lineStatus, _game.lineStatus);

#if 0
            // FIXME: Breaks wrist watch prompt in SQ2

            // Clear the user input line as well when clearing the status line
            // Fixes bug #1893564 - AGI: Texts messed out in Naturette 1
            clearLines(_game.lineUserInput, _game.lineUserInput, 0);
            flushLines(_game.lineUserInput, _game.lineUserInput);
#endif
            return;
      }

      switch (getLanguage()) {
      case Common::RU_RUS:
            sprintf(x, " \x91\xe7\xa5\xe2: %i \xa8\xa7 %-3i", _game.vars[vScore], _game.vars[vMaxScore]);
            printStatus("%-17s              \x87\xa2\xe3\xaa:%s", x, getflag(fSoundOn) ? "\xa2\xaa\xab " : "\xa2\xeb\xaa\xab");
            break;
      default:
            sprintf(x, " Score:%i of %-3i", _game.vars[vScore], _game.vars[vMaxScore]);
            printStatus("%-17s             Sound:%s ", x, getflag(fSoundOn) ? "on " : "off");
            break;
      }
}

/**
 * Print user input prompt.
 */
void AgiEngine::writePrompt() {
      int l, fg, bg, pos;
      int promptLength = strlen(agiSprintf(_game.strings[0]));

      if (!_game.inputEnabled || _game.inputMode != INPUT_NORMAL)
            return;

      l = _game.lineUserInput;
      fg = _game.colorFg;
      bg = _game.colorBg;
      pos = _game.cursorPos;

      debugC(4, kDebugLevelText, "erase line %d", l);
      clearLines(l, l, _game.colorBg);

      debugC(4, kDebugLevelText, "prompt = '%s'", agiSprintf(_game.strings[0]));
      printText(_game.strings[0], 0, 0, l, promptLength + 1, fg, bg);
      printText((char *)_game.inputBuffer, 0, promptLength, l, pos + 1, fg, bg);
      _gfx->printCharacter(pos + promptLength, l, _game.cursorChar, fg, bg);

      flushLines(l, l);
      _gfx->doUpdate();
}

void AgiEngine::clearPrompt() {
      int l;

      l = _game.lineUserInput;
      clearLines(l, l, _game.colorBg);
      flushLines(l, l);

      _gfx->doUpdate();
}

/**
 * Clear text lines in the screen.
 * @param l1  start line
 * @param l2  end line
 * @param c   color
 */
void AgiEngine::clearLines(int l1, int l2, int c) {
      // do we need to adjust for +8 on topline?
      // inc for endline so it matches the correct num
      // ie, from 22 to 24 is 3 lines, not 2 lines.

      debugC(4, kDebugLevelText, "clearLines(%d, %d, %d)", l1, l2, c);

      l1 *= CHAR_LINES;
      l2 *= CHAR_LINES;
      l2 += CHAR_LINES - 1;

      _gfx->drawRectangle(0, l1, GFX_WIDTH - 1, l2, c);
}

/**
 *
 */
void AgiEngine::flushLines(int l1, int l2) {
      l1 *= CHAR_LINES;
      l2 *= CHAR_LINES;
      l2 += CHAR_LINES - 1;

      _gfx->flushBlock(0, l1, GFX_WIDTH - 1, l2);
}

/**
 *
 */
void AgiEngine::drawWindow(int x1, int y1, int x2, int y2) {
      _game.window.active = true;
      _game.window.x1 = x1;
      _game.window.y1 = y1;
      _game.window.x2 = x2;
      _game.window.y2 = y2;
      _game.window.buffer = (uint8 *)malloc((x2 - x1 + 1) * (y2 - y1 + 1));

      debugC(4, kDebugLevelText, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2);
      _gfx->saveBlock(x1, y1, x2, y2, _game.window.buffer);
      _gfx->drawBox(x1, y1, x2, y2, MSG_BOX_COLOUR, MSG_BOX_LINE, 2);
}

} // End of namespace Agi

Generated by  Doxygen 1.6.0   Back to index