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

menu.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-0-11-1/engines/lure/menu.cpp $
 * $Id: menu.cpp 30944 2008-02-23 22:50:18Z sev $
 *
 */

#include "lure/menu.h"
#include "lure/luredefs.h"
#include "lure/decode.h"
#include "lure/surface.h"
#include "lure/res_struct.h"
#include "lure/res.h"
#include "lure/strings.h"
#include "lure/room.h"
#include "lure/events.h"
#include "lure/lure.h"

#if defined(_WIN32_WCE) || defined(__SYMBIAN32__)
#define LURE_CLICKABLE_MENUS
#endif

namespace Lure {

MenuRecord::MenuRecord(const MenuRecordBounds *bounds, int numParams, ...) {
      // Store list of pointers to strings
      va_list params;

      _numEntries = numParams;
      _entries = (const char **) malloc(sizeof(const char *) * _numEntries);  

      va_start(params, numParams);
      for (int index = 0; index < _numEntries; ++index)
            _entries[index] = va_arg(params, const char *);

      // Store position data
      _hsxstart = bounds->left; _hsxend = bounds->right;
      _xstart = bounds->contentsX << 3;
      _width = (bounds->contentsWidth + 3) << 3;
}

const char *MenuRecord::getEntry(uint8 index) {
      if (index >= _numEntries) error("Invalid menuitem index specified: %d", index);
      return _entries[index];
}

/*--------------------------------------------------------------------------*/

static Menu *int_menu = NULL;

const MenuRecordLanguage menuList[] = {
      {EN_ANY, {{40, 87, 3, 7}, {127, 179, 13, 12}, {224, 281, 27, 10}}},
      {IT_ITA, {{40, 98, 4, 6}, {120, 195, 14, 11}, {208, 281, 24, 13}}},
      {FR_FRA, {{40, 90, 3, 7}, {120, 195, 13, 11}, {232, 273, 23, 13}}},
      {DE_DEU, {{44, 95, 1, 11}, {135, 178, 8, 23}, {232, 273, 22, 15}}},
      {ES_ESP, {{40, 90, 3, 8}, {120, 195, 11, 13}, {208, 281, 17, 18}}},
      {UNK_LANG, {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}}
};

Menu::Menu() {
      int_menu = this;
      StringList &sl = Resources::getReference().stringList();
      Common::Language language = LureEngine::getReference().getLanguage();

      MemoryBlock *data = Disk::getReference().getEntry(MENU_RESOURCE_ID);
      PictureDecoder decoder;
      _menu = decoder.decode(data, SCREEN_SIZE);
      delete data;

      const MenuRecordLanguage *rec = &menuList[0];
      while ((rec->language != UNK_LANG) && (rec->language != language)) 
            ++rec;
      if (rec->language == UNK_LANG)
            error("Unknown language encountered in top line handler");

      _menus[0] = new MenuRecord(&rec->menus[0], 1, sl.getString(S_CREDITS));
      _menus[1] = new MenuRecord(&rec->menus[1], 3, 
            sl.getString(S_RESTART_GAME), sl.getString(S_SAVE_GAME), sl.getString(S_RESTORE_GAME));
      _menus[2] = new MenuRecord(&rec->menus[2], 3,
            sl.getString(S_QUIT), sl.getString(S_SLOW_TEXT), sl.getString(S_SOUND_ON));

      _selectedMenu = NULL;
}

Menu::~Menu() {
      for (int ctr=0; ctr<NUM_MENUS; ++ctr) delete _menus[ctr];
      delete _menu;
}

Menu &Menu::getReference() {
      return *int_menu;
}

uint8 Menu::execute() {
      OSystem &system = *g_system;
      Mouse &mouse = Mouse::getReference();
      Events &events = Events::getReference();
      Screen &screen = Screen::getReference();

      mouse.setCursorNum(CURSOR_ARROW);
      system.copyRectToScreen(_menu->data(), FULL_SCREEN_WIDTH, 0, 0, 
            FULL_SCREEN_WIDTH, MENUBAR_Y_SIZE);

      _selectedMenu = NULL;
      _surfaceMenu = NULL;
      _selectedIndex = 0;

      while (mouse.lButton() || mouse.rButton()) {
            while (events.pollEvent()) {
                  if (events.quitFlag) return MENUITEM_NONE;

                  if (mouse.y() < MENUBAR_Y_SIZE) {
                        MenuRecord *p = getMenuAt(mouse.x());

                        if (_selectedMenu != p) {
                              // If necessary, remove prior menu
                              if (_selectedMenu) {
                                    toggleHighlight(_selectedMenu);
                                    screen.updateArea(0, 0, FULL_SCREEN_WIDTH, _surfaceMenu->height() + 8);
                                    delete _surfaceMenu;
                                    _surfaceMenu = NULL;
                                    _selectedIndex = 0;
                              }                                   

                              _selectedMenu = p;

                              // If a new menu is selected, show it
                              if (_selectedMenu) {
                                    toggleHighlight(_selectedMenu);
                                    _surfaceMenu = Surface::newDialog(
                                          _selectedMenu->width(), _selectedMenu->numEntries(), 
                                          _selectedMenu->entries(), false, MENU_UNSELECTED_COLOUR);
                                    _surfaceMenu->copyToScreen(_selectedMenu->xstart(), MENUBAR_Y_SIZE);
                              }

                              system.copyRectToScreen(_menu->data(), FULL_SCREEN_WIDTH, 0, 0, 
                                    FULL_SCREEN_WIDTH, MENUBAR_Y_SIZE);
                        }
                  }

                  // Check for changing selected index
                  uint8 index = getIndexAt(mouse.x(), mouse.y());
                  if (index != _selectedIndex) {
                        if (_selectedIndex != 0) toggleHighlightItem(_selectedIndex);
                        _selectedIndex = index;
                        if (_selectedIndex != 0) toggleHighlightItem(_selectedIndex);
                  }
            }

            system.updateScreen();
            system.delayMillis(10);
      }

      if (_surfaceMenu) delete _surfaceMenu;

      // Deselect the currently selected menu header
      if (_selectedMenu) 
            toggleHighlight(_selectedMenu);

      // Restore the previous screen
      screen.update();
      
      if ((_selectedMenu == NULL) || (_selectedIndex == 0)) return MENUITEM_NONE;
      else if (_selectedMenu == _menus[0])
            return MENUITEM_CREDITS;
      else if (_selectedMenu == _menus[1]) {
            switch (_selectedIndex) {
            case 1:
                  return MENUITEM_RESTART_GAME;
            case 2:
                  return MENUITEM_SAVE_GAME;
            case 3:
                  return MENUITEM_RESTORE_GAME;
            }
      } else {
            switch (_selectedIndex) {
            case 1:
                  return MENUITEM_QUIT;
            case 2:
                  return MENUITEM_TEXT_SPEED;
            case 3:
                  return MENUITEM_SOUND;
            }
      }
      return MENUITEM_NONE;
}     

MenuRecord *Menu::getMenuAt(int x) {
      for (int ctr = 0; ctr < NUM_MENUS; ++ctr)
            if ((x >= _menus[ctr]->hsxstart()) && (x <= _menus[ctr]->hsxend())) 
                  return _menus[ctr];

      return NULL;
}

uint8 Menu::getIndexAt(uint16 x, uint16 y) {
      if (!_selectedMenu) return 0;

      int ys = MENUBAR_Y_SIZE + DIALOG_EDGE_SIZE + 3;
      int ye = MENUBAR_Y_SIZE + _surfaceMenu->height() - DIALOG_EDGE_SIZE - 3;
      if ((y < ys) || (y > ye)) return 0;

      uint16 yRelative = y - ys;
      uint8 index = (uint8) (yRelative / 8) + 1;
      if (index > _selectedMenu->numEntries()) index = _selectedMenu->numEntries();
      return index;
}

void Menu::toggleHighlight(MenuRecord *menuRec) {
      byte *addr = _menu->data();

      for (uint16 y=0; y<MENUBAR_Y_SIZE; ++y) {
            for (uint16 x=menuRec->hsxstart(); x<=menuRec->hsxend(); ++x) {
                  if (addr[x] == MENUBAR_SELECTED_COLOUR) addr[x] = 0;
                  else if (addr[x] == 0) addr[x] = MENUBAR_SELECTED_COLOUR;
            }
            addr += FULL_SCREEN_WIDTH;
      }
}

void Menu::toggleHighlightItem(uint8 index) {
      byte *p = _surfaceMenu->data().data() + (DIALOG_EDGE_SIZE + 3 + 
            ((index - 1) * (FONT_HEIGHT - 1))) * _surfaceMenu->width();
      uint32 numBytes = (FONT_HEIGHT - 1) * _surfaceMenu->width();

      while (numBytes-- > 0) {
            if (*p == MENU_UNSELECTED_COLOUR) *p = MENU_SELECTED_COLOUR;
            else if (*p == MENU_SELECTED_COLOUR) *p = MENU_UNSELECTED_COLOUR;
            ++p;
      }

      _surfaceMenu->copyToScreen(_selectedMenu->xstart(), MENUBAR_Y_SIZE);
}

/*--------------------------------------------------------------------------*/

uint16 PopupMenu::ShowInventory() {
      Resources &rsc = Resources::getReference();
      StringData &strings = StringData::getReference();

      uint16 numItems = rsc.numInventoryItems();
      uint16 itemCtr = 0;
      char **itemNames = (char **) Memory::alloc(sizeof(char *) * numItems);
      uint16 *idList = (uint16 *) Memory::alloc(sizeof(uint16) * numItems);

      HotspotDataList::iterator i;
      for (i = rsc.hotspotData().begin(); i != rsc.hotspotData().end(); ++i) {
            HotspotData *hotspot = *i;
            if (hotspot->roomNumber == PLAYER_ID) {
                  idList[itemCtr] = hotspot->hotspotId;
                  char *hotspotName = itemNames[itemCtr++] = (char *) malloc(MAX_HOTSPOT_NAME_SIZE);
                  strings.getString(hotspot->nameId, hotspotName);
            }
      }
      
      uint16 result = Show(numItems, const_cast<const char **>(itemNames));
      if (result != 0xffff) result = idList[result];

      for (itemCtr = 0; itemCtr < numItems; ++itemCtr)
            free(itemNames[itemCtr]);

      delete itemNames;
      delete idList;
      return result;
}

#define MAX_NUM_DISPLAY_ITEMS 20

uint16 PopupMenu::ShowItems(Action contextAction, uint16 roomNumber) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      RoomDataList &rooms = res.roomData();
      HotspotDataList &hotspots = res.hotspotData();
      StringData &strings = StringData::getReference();
      Room &room = Room::getReference();
      Screen &screen = Screen::getReference();
      Mouse &mouse = Mouse::getReference();
      RoomDataList::iterator ir;
      HotspotDataList::iterator ih;
      uint16 entryIds[MAX_NUM_DISPLAY_ITEMS];
      uint16 nameIds[MAX_NUM_DISPLAY_ITEMS];
      char *entryNames[MAX_NUM_DISPLAY_ITEMS];
      int numItems = 0;
      int itemCtr;
      uint32 contextBitflag = 1 << (contextAction - 1);

      // Loop for rooms
      for (ir = rooms.begin(); ir != rooms.end(); ++ir) {
            RoomData *roomData = *ir;
            // Pre-condition checks for whether to skip room
            if ((roomData->hdrFlags != 15) && ((roomData->hdrFlags & fields.hdrFlagMask()) == 0))
                  continue;
            if (((roomData->flags & HOTSPOTFLAG_MENU_EXCLUSION) != 0) || ((roomData->flags & HOTSPOTFLAG_FOUND) == 0))
                  continue;
            if ((roomData->actions & contextBitflag) == 0)
                  continue;

            // Add room to list of entries to display
            if (numItems == MAX_NUM_DISPLAY_ITEMS) error("Out of space in ask list");
            entryIds[numItems] = roomData->roomNumber;
            nameIds[numItems] = roomData->roomNumber;
            entryNames[numItems] = (char *) Memory::alloc(MAX_HOTSPOT_NAME_SIZE);
            strings.getString(roomData->roomNumber, entryNames[numItems]);
            ++numItems;
      }

      // Loop for hotspots
      for (ih = hotspots.begin(); ih != hotspots.end(); ++ih) {
            HotspotData *hotspot = *ih;

            if ((hotspot->headerFlags != 15) && 
                  ((hotspot->headerFlags & fields.hdrFlagMask()) == 0))
                  continue;

            if (((hotspot->flags & HOTSPOTFLAG_MENU_EXCLUSION) != 0) || ((hotspot->flags & HOTSPOTFLAG_FOUND) == 0))
                  // Skip the current hotspot         
                  continue;

            // If the hotspot is room specific, skip if the character will not be in the specified room
            if (((hotspot->flags & HOTSPOTFLAG_ROOM_SPECIFIC) != 0) && 
                  (hotspot->roomNumber != roomNumber))
                  continue;

            // If hotspot does not allow action, then skip it
            if ((hotspot->actions & contextBitflag) == 0)
                  continue;

            // If a special hotspot Id, then skip displaying
            if ((hotspot->nameId == 0x17A) || (hotspot->nameId == 0x147))
                  continue;

            // Check if the hotspot's name is already used in an already set item
            itemCtr = 0;
            while ((itemCtr < numItems) && (nameIds[itemCtr] != hotspot->nameId))
                  ++itemCtr;
            if (itemCtr != numItems)
                  // Item's name is already present - skip hotspot
                  continue;

            // Add hotspot to list of entries to display
            if (numItems == MAX_NUM_DISPLAY_ITEMS) error("Out of space in ask list");
            entryIds[numItems] = hotspot->hotspotId;
            nameIds[numItems] = hotspot->nameId;
            entryNames[numItems] = (char *) Memory::alloc(MAX_HOTSPOT_NAME_SIZE);
            strings.getString(hotspot->nameId, entryNames[numItems]);
            ++numItems;
      }

      if (numItems == 0) 
            // No items, so add a 'nothing' to the statusLine
            strcat(room.statusLine(), "(nothing)");

      room.update();
      screen.update();
      mouse.waitForRelease();

      if (numItems == 0)
            // Return flag for no items to ask for
            return 0xfffe;

      // Display items
      uint16 result = Show(numItems, const_cast<const char **>(entryNames));
      if (result != 0xffff) result = entryIds[result];

      // Deallocate display strings
      for (itemCtr = 0; itemCtr < numItems; ++itemCtr)
            Memory::dealloc(entryNames[itemCtr]);

      return result;
}

static int entryCompare(const char **p1, const char **p2) {
      return strcmp(*p1, *p2);
}

typedef int (*CompareMethod)(const void*, const void*);

Action PopupMenu::Show(uint32 actionMask) {
      StringList &stringList = Resources::getReference().stringList();
      int numEntries = 0;
      uint32 v = actionMask;
      int index;
      int currentAction;
      uint16 resultIndex;
      Action resultAction;

      for (index = 1; index <= EXAMINE; ++index, v >>= 1) {
            if (v & 1) ++numEntries;
      }

      const char **strList = (const char **) Memory::alloc(sizeof(char *) * numEntries);

      int strIndex = 0;
      for (currentAction = 0; currentAction < (int)EXAMINE; ++currentAction) {
            if ((actionMask & (1 << currentAction)) != 0) {
                  strList[strIndex] = stringList.getString(currentAction);
                  ++strIndex;
            }
      }

      // Sort the list
      qsort(strList, numEntries, sizeof(const char *), (CompareMethod) entryCompare);

      // Show the entries
      resultIndex = Show(numEntries, strList);

      resultAction = NONE;
      if (resultIndex != 0xffff) {
            // Scan through the list of actions to find the selected entry
            for (currentAction = 0; currentAction < (int)EXAMINE; ++currentAction) {
                  if (strList[resultIndex] == stringList.getString(currentAction)) {
                        resultAction = (Action) (currentAction + 1);
                        break;
                  }
            }
      }

      Memory::dealloc(strList);
      return resultAction;
}

Action PopupMenu::Show(int numEntries, Action *actions) {
      StringList &stringList = Resources::getReference().stringList();
      const char **strList = (const char **) Memory::alloc(sizeof(char *) * numEntries);
      Action *actionPtr = actions;
      for (int index = 0; index < numEntries; ++index)
            strList[index] = stringList.getString(*actionPtr++);
      uint16 result = Show(numEntries, strList);
      
      delete strList;
      if (result == 0xffff) return NONE;
      else return actions[result];
}

uint16 PopupMenu::Show(int numEntries, const char *actions[]) {
      if (numEntries == 0) return 0xffff;
      Events &e = Events::getReference();
      Mouse &mouse = Mouse::getReference();
      OSystem &system = *g_system;
      Screen &screen = Screen::getReference();
      Rect r;

      uint16 oldX = mouse.x();
      uint16 oldY = mouse.y();
      const uint16 yMiddle = FULL_SCREEN_HEIGHT / 2;
#ifndef LURE_CLICKABLE_MENUS
      mouse.cursorOff();
      mouse.setPosition(FULL_SCREEN_WIDTH / 2, yMiddle);

      // Round up number of lines in dialog to next odd number
      uint16 numLines = (numEntries / 2) * 2 + 1;
      if (numLines > 5) numLines = 5;
#else
      mouse.pushCursorNum(CURSOR_ARROW);

      // In WinCE, the whole menu is shown and the items are click-selectable
      uint16 numLines = numEntries;
#endif

      // Figure out the character width
      uint16 numCols = 0;
      for (int ctr = 0; ctr < numEntries; ++ctr) {
            int len = strlen(actions[ctr]);
            if (len > numCols)
                  numCols = len;
      }

      // Create the dialog surface
      Surface *s = new Surface(DIALOG_EDGE_SIZE * 2 + numCols * FONT_WIDTH, 
            DIALOG_EDGE_SIZE * 2 + numLines * FONT_HEIGHT);
      s->createDialog();

      int selectedIndex = 0;
      bool refreshFlag = true;
      r.left = DIALOG_EDGE_SIZE;
      r.right = s->width() - DIALOG_EDGE_SIZE - 1;
      r.top = DIALOG_EDGE_SIZE;
      r.bottom = s->height() - DIALOG_EDGE_SIZE - 1;

      for (;;) {
            if (refreshFlag) {
                  // Set up the contents of the menu
                  s->fillRect(r, 0);

                  for (int index = 0; index < numLines; ++index) {
#ifndef LURE_CLICKABLE_MENUS
                        int actionIndex = selectedIndex - (numLines / 2) + index;
#else
                        int actionIndex = index;
#endif
                        if ((actionIndex >= 0) && (actionIndex < numEntries)) {
                              s->writeString(DIALOG_EDGE_SIZE, DIALOG_EDGE_SIZE + index * FONT_HEIGHT,
                                    actions[actionIndex], true, 
#ifndef LURE_CLICKABLE_MENUS
                                    (index == (numLines / 2)) ? MENU_SELECTED_COLOUR : MENU_UNSELECTED_COLOUR,
#else
                                    (index == selectedIndex) ? MENU_SELECTED_COLOUR : MENU_UNSELECTED_COLOUR,
#endif
                                    false);
                        }
                  }

                  s->copyToScreen(0, yMiddle-(s->height() / 2));
                  system.updateScreen();
                  refreshFlag = false;
            }

            while (e.pollEvent()) {
                  if (e.quitFlag) {
                        selectedIndex = 0xffff;
                        goto bail_out;
                  }

                  else if (e.type() == Common::EVENT_KEYDOWN) {
                        uint16 keycode = e.event().kbd.keycode;

                        if (((keycode == Common::KEYCODE_KP8) || (keycode == Common::KEYCODE_UP)) && (selectedIndex > 0)) {
                              --selectedIndex;
                              refreshFlag = true;
                        } else if (((keycode == Common::KEYCODE_KP2) || (keycode == Common::KEYCODE_DOWN)) && 
                                    (selectedIndex < numEntries-1)) {
                              ++selectedIndex;
                              refreshFlag = true;
                        } else if ((keycode == Common::KEYCODE_RETURN) || (keycode == Common::KEYCODE_KP_ENTER)) {
                              goto bail_out;
                        } else if (keycode == Common::KEYCODE_ESCAPE) {
                              selectedIndex = 0xffff;
                              goto bail_out;
                        }

#ifdef LURE_CLICKABLE_MENUS
                  } else if (e.type() == Common::EVENT_LBUTTONDOWN || e.type() == Common::EVENT_MOUSEMOVE) {
                        int16 x = mouse.x();
                        int16 y = mouse.y() - yMiddle + (s->height() / 2);
                        if (r.contains(x, y)) {
                              selectedIndex = (y - r.top) / FONT_HEIGHT;
                              if (e.type() == Common::EVENT_LBUTTONDOWN)
                                    goto bail_out;
                              else
                                    refreshFlag = true;
                        }
#else
                  } else if (e.type() == Common::EVENT_LBUTTONDOWN) {
                        //mouse.waitForRelease();
                        goto bail_out;
#endif
                  } else if (e.type() == Common::EVENT_RBUTTONDOWN) {
                        mouse.waitForRelease();
                        selectedIndex = 0xffff;
                        goto bail_out;
                  }
            }

#ifndef LURE_CLICKABLE_MENUS
            // Warping the mouse to "neutral" even if the top/bottom menu
            // entry has been reached has both pros and cons. It makes the
            // menu behave a bit more sensibly, but it also makes it harder
            // to move the mouse pointer out of the ScummVM window.

            if (mouse.y() < yMiddle - POPMENU_CHANGE_SENSITIVITY) {
                  if (selectedIndex > 0) {
                        --selectedIndex;
                        refreshFlag = true;
                  }
                  mouse.setPosition(FULL_SCREEN_WIDTH / 2, yMiddle);
            } else if (mouse.y() > yMiddle + POPMENU_CHANGE_SENSITIVITY) {
                  if (selectedIndex < numEntries - 1) {
                        ++selectedIndex;
                        refreshFlag = true;
                  }
                  mouse.setPosition(FULL_SCREEN_WIDTH / 2, yMiddle);
            }
#endif

            system.delayMillis(20);
      }

bail_out:
#ifndef LURE_CLICKABLE_MENUS
      mouse.setPosition(oldX, oldY);
      mouse.cursorOn();
#else
      mouse.popCursor();
#endif
      screen.update();
      return selectedIndex;
}

} // end of namespace Lure

Generated by  Doxygen 1.6.0   Back to index