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

controls.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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1994-1998 Revolution Software Ltd.
 *
 * 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-0-11-1/engines/sword2/controls.cpp $
 * $Id: controls.cpp 30944 2008-02-23 22:50:18Z sev $
 */


#include "common/events.h"
#include "common/rect.h"
#include "common/config-manager.h"
#include "common/system.h"

#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/controls.h"
#include "sword2/mouse.h"
#include "sword2/resman.h"
#include "sword2/screen.h"
#include "sword2/sound.h"

#define     MAX_STRING_LEN    64    // 20 was too low; better to be safe ;)
#define CHARACTER_OVERLAP 2   // overlap characters by 3 pixels

// our fonts start on SPACE character (32)
#define SIZE_OF_CHAR_SET  (256 - 32)

namespace Sword2 {

static int baseSlot = 0;

class Widget;

/**
 * Base class for all widgets.
 */

00059 class Widget {
protected:
      Sword2Engine *_vm;
      Dialog *_parent;

      SpriteInfo *_sprites;

      struct WidgetSurface {
            byte *_surface;
            bool _original;
      };

      WidgetSurface *_surfaces;
      int _numStates;
      int _state;

      Common::Rect _hitRect;

public:
      Widget(Dialog *parent, int states);

      virtual ~Widget();

      void createSurfaceImage(int state, uint32 res, int x, int y, uint32 pc);
      void linkSurfaceImage(Widget *from, int state, int x, int y);

      void createSurfaceImages(uint32 res, int x, int y);
      void linkSurfaceImages(Widget *from, int x, int y);

      void setHitRect(int x, int y, int width, int height);
      bool isHit(int16 x, int16 y);

      void setState(int state);
      int getState();

      virtual void paint(Common::Rect *clipRect = NULL);

      virtual void onMouseEnter() {}
      virtual void onMouseExit() {}
      virtual void onMouseMove(int x, int y) {}
      virtual void onMouseDown(int x, int y) {}
      virtual void onMouseUp(int x, int y) {}
      virtual void onWheelUp(int x, int y) {}
      virtual void onWheelDown(int x, int y) {}
      virtual void onKey(KeyboardEvent *ke) {}
      virtual void onTick() {}

      virtual void releaseMouse(int x, int y) {}
};

/**
 * This class is used to draw text in dialogs, buttons, etc.
 */

00113 class FontRendererGui {
private:
      Sword2Engine *_vm;

      struct Glyph {
            byte *_data;
            int _width;
            int _height;
      };

      Glyph _glyph[SIZE_OF_CHAR_SET];

      int _fontId;

public:
      enum {
            kAlignLeft,
            kAlignRight,
            kAlignCenter
      };

      FontRendererGui(Sword2Engine *vm, int fontId);
      ~FontRendererGui();

      void fetchText(uint32 textId, byte *buf);

      int getCharWidth(byte c);
      int getCharHeight(byte c);

      int getTextWidth(byte *text);
      int getTextWidth(uint32 textId);

      void drawText(byte *text, int x, int y, int alignment = kAlignLeft);
      void drawText(uint32 textId, int x, int y, int alignment = kAlignLeft);
};

FontRendererGui::FontRendererGui(Sword2Engine *vm, int fontId)
      : _vm(vm), _fontId(fontId) {
      byte *font = _vm->_resman->openResource(fontId);
      SpriteInfo sprite;

      sprite.type = RDSPR_NOCOMPRESSION | RDSPR_TRANS;

      for (int i = 0; i < SIZE_OF_CHAR_SET; i++) {
            byte *frame = _vm->fetchFrameHeader(font, i);

            FrameHeader frame_head;

            frame_head.read(frame);

            sprite.data = frame + FrameHeader::size();
            sprite.w = frame_head.width;
            sprite.h = frame_head.height;
            _vm->_screen->createSurface(&sprite, &_glyph[i]._data);
            _glyph[i]._width = frame_head.width;
            _glyph[i]._height = frame_head.height;
      }

      _vm->_resman->closeResource(fontId);
}

FontRendererGui::~FontRendererGui() {
      for (int i = 0; i < SIZE_OF_CHAR_SET; i++)
            _vm->_screen->deleteSurface(_glyph[i]._data);
}

void FontRendererGui::fetchText(uint32 textId, byte *buf) {
      byte *data = _vm->fetchTextLine(_vm->_resman->openResource(textId / SIZE), textId & 0xffff);
      int i;

      for (i = 0; data[i + 2]; i++) {
            if (buf)
                  buf[i] = data[i + 2];
      }

      buf[i] = 0;
      _vm->_resman->closeResource(textId / SIZE);
}

int FontRendererGui::getCharWidth(byte c) {
      if (c < 32)
            return 0;
      return _glyph[c - 32]._width;
}

int FontRendererGui::getCharHeight(byte c) {
      if (c < 32)
            return 0;
      return _glyph[c - 32]._height;
}

int FontRendererGui::getTextWidth(byte *text) {
      int textWidth = 0;

      for (int i = 0; text[i]; i++)
            if (text[i] >= ' ')
                  textWidth += (getCharWidth(text[i]) - CHARACTER_OVERLAP);
      return textWidth;
}

int FontRendererGui::getTextWidth(uint32 textId) {
      byte text[MAX_STRING_LEN];

      fetchText(textId, text);
      return getTextWidth(text);
}

void FontRendererGui::drawText(byte *text, int x, int y, int alignment) {
      SpriteInfo sprite;
      int i;

      if (alignment != kAlignLeft) {
            int textWidth = getTextWidth(text);

            switch (alignment) {
            case kAlignRight:
                  x -= textWidth;
                  break;
            case kAlignCenter:
                  x -= (textWidth / 2);
                  break;
            }
      }

      sprite.x = x;
      sprite.y = y;

      for (i = 0; text[i]; i++) {
            if (text[i] >= ' ') {
                  sprite.w = getCharWidth(text[i]);
                  sprite.h = getCharHeight(text[i]);

                  _vm->_screen->drawSurface(&sprite, _glyph[text[i] - 32]._data);

                  sprite.x += (getCharWidth(text[i]) - CHARACTER_OVERLAP);
            }
      }
}

void FontRendererGui::drawText(uint32 textId, int x, int y, int alignment) {
      byte text[MAX_STRING_LEN];

      fetchText(textId, text);
      drawText(text, x, y, alignment);
}

//
// Dialog class functions
//

Dialog::Dialog(Sword2Engine *vm)
      : _numWidgets(0), _finish(false), _result(0), _vm(vm) {
      _vm->_screen->setFullPalette(CONTROL_PANEL_PALETTE);
      _vm->_screen->clearScene();
      _vm->_screen->updateDisplay();

      // Usually the mouse pointer will already be "normal", but not always.
      _vm->_mouse->setMouse(NORMAL_MOUSE_ID);
}

Dialog::~Dialog() {
      for (int i = 0; i < _numWidgets; i++)
            delete _widgets[i];
      _vm->_screen->clearScene();
      _vm->_screen->updateDisplay();
}

void Dialog::registerWidget(Widget *widget) {
      if (_numWidgets < MAX_WIDGETS)
            _widgets[_numWidgets++] = widget;
}

void Dialog::paint() {
      _vm->_screen->clearScene();
      for (int i = 0; i < _numWidgets; i++)
            _widgets[i]->paint();
}

void Dialog::setResult(int result) {
      _result = result;
      _finish = true;
}

int Dialog::runModal() {
      uint32 oldFilter = _vm->setInputEventFilter(0);

      int i;

      paint();

      int oldMouseX = -1;
      int oldMouseY = -1;

      while (!_finish) {
            // So that the menu icons will reach their full size
            _vm->_mouse->processMenu();
            _vm->_screen->updateDisplay(false);

            int newMouseX, newMouseY;

            _vm->_mouse->getPos(newMouseX, newMouseY);

            newMouseY += 40;

            MouseEvent *me = _vm->mouseEvent();
            KeyboardEvent *ke = _vm->keyboardEvent();

            if (ke) {
                  if (ke->kbd.keycode == Common::KEYCODE_ESCAPE)
                        setResult(0);
                  else if (ke->kbd.keycode == Common::KEYCODE_RETURN || ke->kbd.keycode == Common::KEYCODE_KP_ENTER)
                        setResult(1);
            }

            int oldHit = -1;
            int newHit = -1;

            // Find out which widget the mouse was over the last time, and
            // which it is currently over. This assumes the widgets do not
            // overlap.

            for (i = 0; i < _numWidgets; i++) {
                  if (_widgets[i]->isHit(oldMouseX, oldMouseY))
                        oldHit = i;
                  if (_widgets[i]->isHit(newMouseX, newMouseY))
                        newHit = i;
            }

            // Was the mouse inside a widget the last time?

            if (oldHit >= 0) {
                  if (newHit != oldHit)
                        _widgets[oldHit]->onMouseExit();
            }

            // Is the mouse currently in a widget?

            if (newHit >= 0) {
                  if (newHit != oldHit)
                        _widgets[newHit]->onMouseEnter();

                  if (me) {
                        switch (me->buttons) {
                        case RD_LEFTBUTTONDOWN:
                              _widgets[newHit]->onMouseDown(newMouseX, newMouseY);
                              break;
                        case RD_LEFTBUTTONUP:
                              _widgets[newHit]->onMouseUp(newMouseX, newMouseY);
                              break;
                        case RD_WHEELUP:
                              _widgets[newHit]->onWheelUp(newMouseX, newMouseY);
                              break;
                        case RD_WHEELDOWN:
                              _widgets[newHit]->onWheelDown(newMouseX, newMouseY);
                              break;
                        }
                  }
            }

            // Some events are passed to the widgets regardless of where
            // the mouse cursor is.

            for (i = 0; i < _numWidgets; i++) {
                  if (me && me->buttons == RD_LEFTBUTTONUP) {
                        // So that slider widgets will know when the
                        // user releases the mouse button, even if the
                        // cursor is outside of the slider's hit area.
                        _widgets[i]->releaseMouse(newMouseX, newMouseY);
                  }

                  // This is to make it easier to drag the slider widget

                  if (newMouseX != oldMouseX || newMouseY != oldMouseY)
                        _widgets[i]->onMouseMove(newMouseX, newMouseY);

                  if (ke)
                        _widgets[i]->onKey(ke);

                  _widgets[i]->onTick();
            }

            oldMouseX = newMouseX;
            oldMouseY = newMouseY;

            _vm->_system->delayMillis(20);

            if (_vm->_quit)
                  setResult(0);
      }

      _vm->setInputEventFilter(oldFilter);
      return _result;
}

//
// Widget functions
//

Widget::Widget(Dialog *parent, int states)
      : _vm(parent->_vm), _parent(parent), _numStates(states), _state(0) {
      _sprites = (SpriteInfo *)calloc(states, sizeof(SpriteInfo));
      _surfaces = (WidgetSurface *)calloc(states, sizeof(WidgetSurface));

      _hitRect.left = _hitRect.right = _hitRect.top = _hitRect.bottom = -1;
}

Widget::~Widget() {
      for (int i = 0; i < _numStates; i++) {
            if (_surfaces[i]._original)
                  _vm->_screen->deleteSurface(_surfaces[i]._surface);
      }
      free(_sprites);
      free(_surfaces);
}

void Widget::createSurfaceImage(int state, uint32 res, int x, int y, uint32 pc) {
      byte *file, *colTablePtr = NULL;
      AnimHeader anim_head;
      FrameHeader frame_head;
      CdtEntry cdt_entry;
      uint32 spriteType = RDSPR_TRANS;

      // open anim resource file, point to base
      file = _vm->_resman->openResource(res);

      byte *frame = _vm->fetchFrameHeader(file, pc);

      anim_head.read(_vm->fetchAnimHeader(file));
      cdt_entry.read(_vm->fetchCdtEntry(file, pc));
      frame_head.read(frame);

      // If the frame is flipped. (Only really applicable to frames using
      // offsets.)

      if (cdt_entry.frameType & FRAME_FLIPPED)
            spriteType |= RDSPR_FLIP;

      // Which compression was used?

      switch (anim_head.runTimeComp) {
      case NONE:
            spriteType |= RDSPR_NOCOMPRESSION;
            break;
      case RLE256:
            spriteType |= RDSPR_RLE256;
            break;
      case RLE16:
            spriteType |= RDSPR_RLE256;
            // Points to just after last cdt_entry, i.e. start of colour
            // table
            colTablePtr = _vm->fetchAnimHeader(file) + AnimHeader::size()
                  + anim_head.noAnimFrames * CdtEntry::size();
            break;
      }

      _sprites[state].x = x;
      _sprites[state].y = y;
      _sprites[state].w = frame_head.width;
      _sprites[state].h = frame_head.height;
      _sprites[state].scale = 0;
      _sprites[state].type = spriteType;
      _sprites[state].blend = anim_head.blend;

      // Points to just after frame header, ie. start of sprite data
      _sprites[state].data = frame + FrameHeader::size();

      _vm->_screen->createSurface(&_sprites[state], &_surfaces[state]._surface);
      _surfaces[state]._original = true;

      // Release the anim resource
      _vm->_resman->closeResource(res);
}

void Widget::linkSurfaceImage(Widget *from, int state, int x, int y) {
      _sprites[state].x = x;
      _sprites[state].y = y;
      _sprites[state].w = from->_sprites[state].w;
      _sprites[state].h = from->_sprites[state].h;
      _sprites[state].scale = from->_sprites[state].scale;
      _sprites[state].type = from->_sprites[state].type;
      _sprites[state].blend = from->_sprites[state].blend;

      _surfaces[state]._surface = from->_surfaces[state]._surface;
      _surfaces[state]._original = false;
}

void Widget::createSurfaceImages(uint32 res, int x, int y) {
      for (int i = 0; i < _numStates; i++)
            createSurfaceImage(i, res, x, y, i);
}

void Widget::linkSurfaceImages(Widget *from, int x, int y) {
      for (int i = 0; i < from->_numStates; i++)
            linkSurfaceImage(from, i, x, y);
}

void Widget::setHitRect(int x, int y, int width, int height) {
      _hitRect.left = x;
      _hitRect.right = x + width;
      _hitRect.top = y;
      _hitRect.bottom = y + height;
}

bool Widget::isHit(int16 x, int16 y) {
      return _hitRect.left >= 0 && _hitRect.contains(x, y);
}

void Widget::setState(int state) {
      if (state != _state) {
            _state = state;
            paint();
      }
}

int Widget::getState() {
      return _state;
}

void Widget::paint(Common::Rect *clipRect) {
      _vm->_screen->drawSurface(&_sprites[_state], _surfaces[_state]._surface, clipRect);
}

/**
 * Standard button class.
 */

00539 class Button : public Widget {
public:
      Button(Dialog *parent, int x, int y, int w, int h)
            : Widget(parent, 2) {
            setHitRect(x, y, w, h);
      }

      virtual void onMouseExit() {
            setState(0);
      }

      virtual void onMouseDown(int x, int y) {
            setState(1);
      }

      virtual void onMouseUp(int x, int y) {
            if (getState() != 0) {
                  setState(0);
                  _parent->onAction(this);
            }
      }
};

/**
 * Scroll buttons are used to scroll the savegame list. The difference between
 * this and a normal button is that we want this to repeat.
 */

00567 class ScrollButton : public Widget {
private:
      uint32 _holdCounter;

public:
      ScrollButton(Dialog *parent, int x, int y, int w, int h)
            : Widget(parent, 2), _holdCounter(0) {
            setHitRect(x, y, w, h);
      }

      virtual void onMouseExit() {
            setState(0);
      }

      virtual void onMouseDown(int x, int y) {
            setState(1);
            _parent->onAction(this);
            _holdCounter = 0;
      }

      virtual void onMouseUp(int x, int y) {
            setState(0);
      }

      virtual void onTick() {
            if (getState() != 0) {
                  _holdCounter++;
                  if (_holdCounter > 16 && (_holdCounter % 4) == 0)
                        _parent->onAction(this);
            }
      }
};

/**
 * A switch is a button that changes state when clicked, and keeps that state
 * until clicked again.
 */

00605 class Switch : public Widget {
private:
      bool _holding, _value;
      int _upState, _downState;

public:
      Switch(Dialog *parent, int x, int y, int w, int h)
            : Widget(parent, 2), _holding(false), _value(false),
              _upState(0), _downState(1) {
            setHitRect(x, y, w, h);
      }

      // The sound mute switches have 0 as their "down" state and 1 as
      // their "up" state, so this function is needed to get consistent
      // behaviour.

      void reverseStates() {
            _upState = 1;
            _downState = 0;
      }

      void setValue(bool value) {
            _value = value;
            if (_value)
                  setState(_downState);
            else
                  setState(_upState);
      }

      bool getValue() {
            return _value;
      }

      virtual void onMouseExit() {
            if (_holding && !_value)
                  setState(_upState);
            _holding = false;
      }

      virtual void onMouseDown(int x, int y) {
            _holding = true;
            setState(_downState);
      }

      virtual void onMouseUp(int x, int y) {
            if (_holding) {
                  _holding = false;
                  _value = !_value;
                  if (_value)
                        setState(_downState);
                  else
                        setState(_upState);
                  _parent->onAction(this, getState());
            }
      }
};

/**
 * A slider is used to specify a value within a pre-defined range.
 */

00666 class Slider : public Widget {
private:
      Widget *_background;
      bool _dragging;
      int _value, _targetValue;
      int _maxValue;
      int _valueStep;
      int _dragOffset;

      int posFromValue(int value) {
            return _hitRect.left + (value * (_hitRect.width() - 38)) / _maxValue;
      }

      int valueFromPos(int x) {
            return (int)((double)(_maxValue * (x - _hitRect.left)) / (double)(_hitRect.width() - 38) + 0.5);
      }

public:
      Slider(Dialog *parent, Widget *background, int max,
            int x, int y, int w, int h, int step, Widget *base = NULL)
            : Widget(parent, 1), _background(background),
              _dragging(false), _value(0), _targetValue(0),
              _maxValue(max), _valueStep(step) {
            setHitRect(x, y, w, h);

            if (_valueStep <= 0)
                  _valueStep = 1;

            if (base)
                  linkSurfaceImages(base, x, y);
            else
                  createSurfaceImages(3406, x, y);
      }

      virtual void paint(Common::Rect *clipRect = NULL) {
            // This will redraw a bit more than is strictly necessary,
            // but I doubt that will make any noticeable difference.

            _background->paint(&_hitRect);
            Widget::paint(clipRect);
      }

      void setValue(int value) {
            _value = value;
            _targetValue = value;
            _sprites[0].x = posFromValue(_value);
            paint();
      }

      int getValue() {
            return _value;
      }

      virtual void onMouseMove(int x, int y) {
            if (_dragging) {
                  int newX = x - _dragOffset;
                  int newValue;

                  if (newX < _hitRect.left)
                        newX = _hitRect.left;
                  else if (newX + 38 > _hitRect.right)
                        newX = _hitRect.right - 38;

                  _sprites[0].x = newX;

                  newValue = valueFromPos(newX);
                  if (newValue != _value) {
                        _value = newValue;
                        _targetValue = newValue;
                        _parent->onAction(this, newValue);
                  }

                  paint();
            }
      }

      virtual void onMouseDown(int x, int y) {
            if (x >= _sprites[0].x && x < _sprites[0].x + 38) {
                  _dragging = true;
                  _dragOffset = x - _sprites[0].x;
            } else if (x < _sprites[0].x) {
                  if (_targetValue >= _valueStep)
                        _targetValue -= _valueStep;
                  else
                        _targetValue = 0;
            } else {
                  if (_targetValue < _maxValue - _valueStep)
                        _targetValue += _valueStep;
                  else
                        _targetValue = _maxValue;
            }
      }

      virtual void releaseMouse(int x, int y) {
            if (_dragging)
                  _dragging = false;
      }

      virtual void onTick() {
            if (!_dragging) {
                  int target = posFromValue(_targetValue);

                  if (target != _sprites[0].x) {
                        if (target < _sprites[0].x) {
                              _sprites[0].x -= 4;
                              if (_sprites[0].x < target)
                                    _sprites[0].x = target;
                        } else if (target > _sprites[0].x) {
                              _sprites[0].x += 4;
                              if (_sprites[0].x > target)
                                    _sprites[0].x = target;
                        }

                        int newValue = valueFromPos(_sprites[0].x);
                        if (newValue != _value) {
                              _value = newValue;
                              _parent->onAction(this, newValue);
                        }

                        paint();
                  }
            }
      }
};

/**
 * The "mini" dialog.
 */

00795 MiniDialog::MiniDialog(Sword2Engine *vm, uint32 headerTextId, uint32 okTextId, uint32 cancelTextId) : Dialog(vm) {
      _headerTextId = headerTextId;
      _okTextId = okTextId;
      _cancelTextId = cancelTextId;

      _fr = new FontRendererGui(_vm, _vm->_controlsFontId);

      _panel = new Widget(this, 1);
      _panel->createSurfaceImages(1996, 203, 104);

      _okButton = new Button(this, 243, 214, 24, 24);
      _okButton->createSurfaceImages(2002, 243, 214);

      _cancelButton = new Button(this, 243, 276, 24, 24);
      _cancelButton->linkSurfaceImages(_okButton, 243, 276);

      registerWidget(_panel);
      registerWidget(_okButton);
      registerWidget(_cancelButton);
}

MiniDialog::~MiniDialog() {
      delete _fr;
}

void MiniDialog::paint() {
      Dialog::paint();

      if (_headerTextId)
            _fr->drawText(_headerTextId, 310, 134, FontRendererGui::kAlignCenter);
      _fr->drawText(_okTextId, 270, 214);
      _fr->drawText(_cancelTextId, 270, 276);
}

void MiniDialog::onAction(Widget *widget, int result) {
      if (widget == _okButton)
            setResult(1);
      else if (widget == _cancelButton)
            setResult(0);
}

StartDialog::StartDialog(Sword2Engine *vm) : MiniDialog(vm, 0) {}

int StartDialog::runModal() {
      while (1) {
            MiniDialog startDialog(_vm, 0, TEXT_RESTART, TEXT_RESTORE);

            if (startDialog.runModal())
                  return 1;

            if (_vm->_quit)
                  return 0;

            RestoreDialog restoreDialog(_vm);

            if (restoreDialog.runModal())
                  return 0;

            if (_vm->_quit)
                  return 0;
      }

      return 1;
}

/**
 * The restart dialog.
 */

RestartDialog::RestartDialog(Sword2Engine *vm) : MiniDialog(vm, TEXT_RESTART) {}

int RestartDialog::runModal() {
      int result = MiniDialog::runModal();

      if (result)
            _vm->restartGame();

      return result;
}

/**
 * The quit dialog.
 */

QuitDialog::QuitDialog(Sword2Engine *vm) : MiniDialog(vm, TEXT_QUIT) {}

int QuitDialog::runModal() {
      int result = MiniDialog::runModal();

      if (result)
            _vm->closeGame();

      return result;
}

/**
 * The game settings dialog.
 */

OptionsDialog::OptionsDialog(Sword2Engine *vm) : Dialog(vm) {
      _fr = new FontRendererGui(_vm, _vm->_controlsFontId);

      _mixer = _vm->_mixer;

      _panel = new Widget(this, 1);
      _panel->createSurfaceImages(3405, 0, 40);

      _objectLabelsSwitch = new Switch(this, 304, 100, 53, 32);
      _objectLabelsSwitch->createSurfaceImages(3687, 304, 100);

      _subtitlesSwitch = new Switch(this, 510, 100, 53, 32);
      _subtitlesSwitch->linkSurfaceImages(_objectLabelsSwitch, 510, 100);

      _reverseStereoSwitch = new Switch(this, 304, 293, 53, 32);
      _reverseStereoSwitch->linkSurfaceImages(_objectLabelsSwitch, 304, 293);

      _musicSwitch = new Switch(this, 516, 157, 40, 32);
      _musicSwitch->createSurfaceImages(3315, 516, 157);
      _musicSwitch->reverseStates();

      _speechSwitch = new Switch(this, 516, 205, 40, 32);
      _speechSwitch->linkSurfaceImages(_musicSwitch, 516, 205);
      _speechSwitch->reverseStates();

      _fxSwitch = new Switch(this, 516, 250, 40, 32);
      _fxSwitch->linkSurfaceImages(_musicSwitch, 516, 250);
      _fxSwitch->reverseStates();

      int volStep = Audio::Mixer::kMaxMixerVolume / 10;

      _musicSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 161, 170, 27, volStep);
      _speechSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 208, 170, 27, volStep, _musicSlider);
      _fxSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 254, 170, 27, volStep, _musicSlider);
      _gfxSlider = new Slider(this, _panel, 3, 309, 341, 170, 27, 1, _musicSlider);

      _gfxPreview = new Widget(this, 4);
      _gfxPreview->createSurfaceImages(256, 495, 310);

      _okButton = new Button(this, 203, 382, 53, 32);
      _okButton->createSurfaceImages(901, 203, 382);

      _cancelButton = new Button(this, 395, 382, 53, 32);
      _cancelButton->linkSurfaceImages(_okButton, 395, 382);

      registerWidget(_panel);
      registerWidget(_objectLabelsSwitch);
      registerWidget(_subtitlesSwitch);
      registerWidget(_reverseStereoSwitch);
      registerWidget(_musicSwitch);
      registerWidget(_speechSwitch);
      registerWidget(_fxSwitch);
      registerWidget(_musicSlider);
      registerWidget(_speechSlider);
      registerWidget(_fxSlider);
      registerWidget(_gfxSlider);
      registerWidget(_gfxPreview);
      registerWidget(_okButton);
      registerWidget(_cancelButton);

      _objectLabelsSwitch->setValue(_vm->_mouse->getObjectLabels());
      _subtitlesSwitch->setValue(_vm->getSubtitles());
      _reverseStereoSwitch->setValue(_vm->_sound->isReverseStereo());
      _musicSwitch->setValue(!_vm->_sound->isMusicMute());
      _speechSwitch->setValue(!_vm->_sound->isSpeechMute());
      _fxSwitch->setValue(!_vm->_sound->isFxMute());

      _musicSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
      _speechSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
      _fxSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));

      _gfxSlider->setValue(_vm->_screen->getRenderLevel());
      _gfxPreview->setState(_vm->_screen->getRenderLevel());
}

OptionsDialog::~OptionsDialog() {
      delete _fr;
}

void OptionsDialog::paint() {
      Dialog::paint();

      int maxWidth = 0;
      int width;

      uint32 alignTextIds[] = {
            TEXT_OBJECT_LABELS,
            TEXT_MUSIC_VOLUME,
            TEXT_SPEECH_VOLUME,
            TEXT_FX_VOLUME,
            TEXT_GFX_QUALITY,
            TEXT_REVERSE_STEREO
      };

      for (int i = 0; i < ARRAYSIZE(alignTextIds); i++) {
            width = _fr->getTextWidth(alignTextIds[i]);
            if (width > maxWidth)
                  maxWidth = width;
      }

      _fr->drawText(TEXT_OPTIONS, 321, 55, FontRendererGui::kAlignCenter);
      _fr->drawText(TEXT_SUBTITLES, 500, 103, FontRendererGui::kAlignRight);
      _fr->drawText(TEXT_OBJECT_LABELS, 299 - maxWidth, 103);
      _fr->drawText(TEXT_MUSIC_VOLUME, 299 - maxWidth, 161);
      _fr->drawText(TEXT_SPEECH_VOLUME, 299 - maxWidth, 208);
      _fr->drawText(TEXT_FX_VOLUME, 299 - maxWidth, 254);
      _fr->drawText(TEXT_REVERSE_STEREO, 299 - maxWidth, 296);
      _fr->drawText(TEXT_GFX_QUALITY, 299 - maxWidth, 341);
      _fr->drawText(TEXT_OK, 193, 382, FontRendererGui::kAlignRight);
      _fr->drawText(TEXT_CANCEL, 385, 382, FontRendererGui::kAlignRight);
}

void OptionsDialog::onAction(Widget *widget, int result) {
      // Since there is music playing while the dialog is displayed we need
      // to update music volume immediately.

      if (widget == _musicSwitch) {
            _vm->_sound->muteMusic(result != 0);
      } else if (widget == _musicSlider) {
            _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, result);
            _vm->_sound->muteMusic(result == 0);
            _musicSwitch->setValue(result != 0);
      } else if (widget == _speechSlider) {
            _speechSwitch->setValue(result != 0);
      } else if (widget == _fxSlider) {
            _fxSwitch->setValue(result != 0);
      } else if (widget == _gfxSlider) {
            _gfxPreview->setState(result);
            _vm->_screen->setRenderLevel(result);
      } else if (widget == _okButton) {
            // Apply the changes
            _vm->setSubtitles(_subtitlesSwitch->getValue());
            _vm->_mouse->setObjectLabels(_objectLabelsSwitch->getValue());
            _vm->_sound->muteMusic(!_musicSwitch->getValue());
            _vm->_sound->muteSpeech(!_speechSwitch->getValue());
            _vm->_sound->muteFx(!_fxSwitch->getValue());
            _vm->_sound->setReverseStereo(_reverseStereoSwitch->getValue());
            _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _musicSlider->getValue());
            _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, _speechSlider->getValue());
            _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, _fxSlider->getValue());
            _vm->_screen->setRenderLevel(_gfxSlider->getValue());

            _vm->writeSettings();
            setResult(1);
      } else if (widget == _cancelButton) {
            // Revert the changes
            _vm->readSettings();
            setResult(0);
      }
}

// Slot button actions. Note that keyboard input generates positive actions

enum {
      kSelectSlot = -1,
      kDeselectSlot = -2,
      kWheelDown = -3,
      kWheelUp = -4,
      kStartEditing = -5,
      kCursorTick = -6
};

class Slot : public Widget {
private:
      int _mode;
      FontRendererGui *_fr;
      byte _text[SAVE_DESCRIPTION_LEN];
      bool _clickable;
      bool _editable;

public:
      Slot(Dialog *parent, int x, int y, int w, int h)
            : Widget(parent, 2), _clickable(false), _editable(false) {
            setHitRect(x, y, w, h);
            _text[0] = 0;
      }

      void setMode(int mode) {
            _mode = mode;
      }

      void setClickable(bool clickable) {
            _clickable = clickable;
      }

      void setEditable(bool editable) {
            _editable = editable;
            _vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, editable);
      }

      bool isEditable() {
            return _editable;
      }

      void setText(FontRendererGui *fr, int slot, byte *text) {
            _fr = fr;
            if (text)
                  sprintf((char *)_text, "%d.  %s", slot, text);
            else
                  sprintf((char *)_text, "%d.  ", slot);
      }

      byte *getText() {
            return &_text[0];
      }

      virtual void paint(Common::Rect *clipRect = NULL) {
            Widget::paint();

            // HACK: The main dialog is responsible for drawing the text
            // when in editing mode.

            if (!_editable)
                  _fr->drawText(_text, _sprites[0].x + 16, _sprites[0].y + 4 + 2 * getState());
      }

      virtual void onMouseDown(int x, int y) {
            if (_clickable) {
                  if (getState() == 0) {
                        setState(1);
                        _parent->onAction(this, kSelectSlot);
                        if (_mode == kSaveDialog)
                              _parent->onAction(this, kStartEditing);
                  } else if (_mode == kRestoreDialog) {
                        setState(0);
                        _parent->onAction(this, kDeselectSlot);
                  }
            }
      }

      virtual void onWheelUp(int x, int y) {
            _parent->onAction(this, kWheelUp);
      }

      virtual void onWheelDown(int x, int y) {
            _parent->onAction(this, kWheelDown);
      }

      virtual void onKey(KeyboardEvent *ke) {
            if (_editable) {
                  if (ke->kbd.keycode == Common::KEYCODE_BACKSPACE)
                        _parent->onAction(this, Common::KEYCODE_BACKSPACE);
                  else if (ke->kbd.ascii >= ' ' && ke->kbd.ascii <= 255) {
                        // Accept the character if the font renderer
                        // has what looks like a valid glyph for it.
                        if (_fr->getCharWidth(ke->kbd.ascii))
                              _parent->onAction(this, ke->kbd.ascii);
                  }
            }
      }

      virtual void onTick() {
            if (_editable)
                  _parent->onAction(this, kCursorTick);
      }

      void setY(int y) {
            for (int i = 0; i < _numStates; i++)
                  _sprites[i].y = y;
            setHitRect(_hitRect.left, y, _hitRect.width(), _hitRect.height());
      }

      int getY() {
            return _sprites[0].y;
      }
};

SaveRestoreDialog::SaveRestoreDialog(Sword2Engine *vm, int mode) : Dialog(vm) {
      int i;

      _mode = mode;
      _selectedSlot = -1;

      // FIXME: The "control font" and the "red font" are currently always
      // the same font, so one should be eliminated.

      _fr1 = new FontRendererGui(_vm, _vm->_controlsFontId);
      _fr2 = new FontRendererGui(_vm, _vm->_redFontId);

      _panel = new Widget(this, 1);
      _panel->createSurfaceImages(2016, 0, 40);

      for (i = 0; i < 4; i++) {
            _slotButton[i] = new Slot(this, 114, 0, 384, 36);
            _slotButton[i]->createSurfaceImages(2006 + i, 114, 0);
            _slotButton[i]->setMode(mode);
            _slotButton[i + 4] = new Slot(this, 114, 0, 384, 36);
            _slotButton[i + 4]->linkSurfaceImages(_slotButton[i], 114, 0);
            _slotButton[i + 4]->setMode(mode);
      }

      updateSlots();

      _zupButton = new ScrollButton(this, 516, 65, 17, 17);
      _zupButton->createSurfaceImages(1982, 516, 65);

      _upButton = new ScrollButton(this, 516, 85, 17, 17);
      _upButton->createSurfaceImages(2067, 516, 85);

      _downButton = new ScrollButton(this, 516, 329, 17, 17);
      _downButton->createSurfaceImages(1986, 516, 329);

      _zdownButton = new ScrollButton(this, 516, 350, 17, 17);
      _zdownButton->createSurfaceImages(1988, 516, 350);

      _okButton = new Button(this, 130, 377, 24, 24);
      _okButton->createSurfaceImages(2002, 130, 377);

      _cancelButton = new Button(this, 350, 377, 24, 24);
      _cancelButton->linkSurfaceImages(_okButton, 350, 377);

      registerWidget(_panel);

      for (i = 0; i < 8; i++)
            registerWidget(_slotButton[i]);

      registerWidget(_zupButton);
      registerWidget(_upButton);
      registerWidget(_downButton);
      registerWidget(_zdownButton);
      registerWidget(_okButton);
      registerWidget(_cancelButton);
}

SaveRestoreDialog::~SaveRestoreDialog() {
      delete _fr1;
      delete _fr2;
}

// There aren't really a hundred different button objects of course, there are
// only eight. Re-arrange them to simulate scrolling.

void SaveRestoreDialog::updateSlots() {
      for (int i = 0; i < 8; i++) {
            Slot *slot = _slotButton[(baseSlot + i) % 8];
            FontRendererGui *fr;
            byte description[SAVE_DESCRIPTION_LEN];

            slot->setY(72 + i * 36);

            if (baseSlot + i == _selectedSlot) {
                  slot->setEditable(_mode == kSaveDialog);
                  slot->setState(1);
                  fr = _fr2;
            } else {
                  slot->setEditable(false);
                  slot->setState(0);
                  fr = _fr1;
            }

            if (_vm->getSaveDescription(baseSlot + i, description) == SR_OK) {
                  slot->setText(fr, baseSlot + i, description);
                  slot->setClickable(true);
            } else {
                  slot->setText(fr, baseSlot + i, NULL);
                  slot->setClickable(_mode == kSaveDialog);
            }

            if (slot->isEditable())
                  drawEditBuffer(slot);
            else
                  slot->paint();
      }
}

void SaveRestoreDialog::drawEditBuffer(Slot *slot) {
      if (_selectedSlot == -1)
            return;

      // This will redraw a bit more than is strictly necessary, but I doubt
      // that will make any noticeable difference.

      slot->paint();
      _fr2->drawText(_editBuffer, 130, 78 + (_selectedSlot - baseSlot) * 36);
}

void SaveRestoreDialog::onAction(Widget *widget, int result) {
      if (widget == _zupButton) {
            if (baseSlot > 0) {
                  if (baseSlot >= 8)
                        baseSlot -= 8;
                  else
                        baseSlot = 0;
                  updateSlots();
            }
      } else if (widget == _upButton) {
            if (baseSlot > 0) {
                  baseSlot--;
                  updateSlots();
            }
      } else if (widget == _downButton) {
            if (baseSlot < 92) {
                  baseSlot++;
                  updateSlots();
            }
      } else if (widget == _zdownButton) {
            if (baseSlot < 92) {
                  if (baseSlot <= 84)
                        baseSlot += 8;
                  else
                        baseSlot = 92;
                  updateSlots();
            }
      } else if (widget == _okButton) {
            setResult(1);
      } else if (widget == _cancelButton) {
            setResult(0);
      } else {
            Slot *slot = (Slot *)widget;
            int textWidth;
            byte tmp;
            int i;
            int j;

            switch (result) {
            case kWheelUp:
                  onAction(_upButton);
                  break;
            case kWheelDown:
                  onAction(_downButton);
                  break;
            case kSelectSlot:
            case kDeselectSlot:
                  if (result == kSelectSlot)
                        _selectedSlot = baseSlot + (slot->getY() - 72) / 35;
                  else if (result == kDeselectSlot)
                        _selectedSlot = -1;

                  for (i = 0; i < 8; i++)
                        if (widget == _slotButton[i])
                              break;

                  for (j = 0; j < 8; j++) {
                        if (j != i) {
                              _slotButton[j]->setEditable(false);
                              _slotButton[j]->setState(0);
                        }
                  }
                  break;
            case kStartEditing:
                  if (_selectedSlot >= 10)
                        _firstPos = 5;
                  else
                        _firstPos = 4;

                  strcpy((char *)_editBuffer, (char *)slot->getText());
                  _editPos = strlen((char *)_editBuffer);
                  _cursorTick = 0;
                  _editBuffer[_editPos] = '_';
                  _editBuffer[_editPos + 1] = 0;
                  slot->setEditable(true);
                  drawEditBuffer(slot);
                  break;
            case kCursorTick:
                  _cursorTick++;
                  if (_cursorTick == 7) {
                        _editBuffer[_editPos] = ' ';
                        drawEditBuffer(slot);
                  } else if (_cursorTick == 14) {
                        _cursorTick = 0;
                        _editBuffer[_editPos] = '_';
                        drawEditBuffer(slot);
                  }
                  break;
            case Common::KEYCODE_BACKSPACE:
                  if (_editPos > _firstPos) {
                        _editBuffer[_editPos - 1] = _editBuffer[_editPos];
                        _editBuffer[_editPos--] = 0;
                        drawEditBuffer(slot);
                  }
                  break;
            default:
                  tmp = _editBuffer[_editPos];
                  _editBuffer[_editPos] = 0;
                  textWidth = _fr2->getTextWidth(_editBuffer);
                  _editBuffer[_editPos] = tmp;

                  if (textWidth < 340 && _editPos < SAVE_DESCRIPTION_LEN - 2) {
                        _editBuffer[_editPos + 1] = _editBuffer[_editPos];
                        _editBuffer[_editPos + 2] = 0;
                        _editBuffer[_editPos++] = result;
                        drawEditBuffer(slot);
                  }
                  break;
            }
      }
}

void SaveRestoreDialog::paint() {
      Dialog::paint();

      _fr1->drawText((_mode == kRestoreDialog) ? TEXT_RESTORE : TEXT_SAVE, 165, 377);
      _fr1->drawText(TEXT_CANCEL, 382, 377);
}

void SaveRestoreDialog::setResult(int result) {
      if (result) {
            if (_selectedSlot == -1)
                  return;

            if (_mode == kSaveDialog) {
                  if (_editPos <= _firstPos)
                        return;
            }
      }

      Dialog::setResult(result);
}

int SaveRestoreDialog::runModal() {
      int result = Dialog::runModal();

      if (result) {
            switch (_mode) {
            case kSaveDialog:
                  // Remove the cursor character from the savegame name
                  _editBuffer[_editPos] = 0;

                  if (_vm->saveGame(_selectedSlot, (byte *)&_editBuffer[_firstPos]) != SR_OK)
                        result = 0;
                  break;
            case kRestoreDialog:
                  if (_vm->restoreGame(_selectedSlot) != SR_OK)
                        result = 0;
                  break;
            }
      }

      return result;
}

} // End of namespace Sword2

Generated by  Doxygen 1.6.0   Back to index