/* 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$ * $Id$ * */ #include "backends/common/virtual-keyboard.h" #include "backends/common/virtual-keyboard-parser.h" #include "common/config-manager.h" #include "common/events.h" #include "common/unzip.h" #include "graphics/cursorman.h" #include "graphics/imageman.h" #include "graphics/surface-keycolored.h" #include "gui/newgui.h" namespace Common { VirtualKeyboard::VirtualKeyboard() : _currentMode(0), _keyDown(0) { assert(g_system); _system = g_system; _parser = new VirtualKeyboardParser(this); _loaded = _displaying = _drag = false; _lastScreenChanged = _system->getScreenChangeID(); memset(_cursor, 0xFF, sizeof(_cursor)); } VirtualKeyboard::~VirtualKeyboard() { // TODO: clean up event data pointers deleteEventData(); delete _parser; } void VirtualKeyboard::reset() { // TODO: clean up event data pointers deleteEventData(); _modes.clear(); _initialMode = _currentMode = 0; _kbdBound.left = _kbdBound.top = _kbdBound.right = _kbdBound.bottom = 0; _hAlignment = kAlignCentre; _vAlignment = kAlignBottom; _keyQueue.clear(); _keyDown = 0; _displaying = _drag = false; _firstRun = true; _lastScreenChanged = _system->getScreenChangeID(); } void VirtualKeyboard::deleteEventData() { ModeMap::iterator it_m; EventMap::iterator it_e; for (it_m = _modes.begin(); it_m != _modes.end(); it_m++) { EventMap *evt = &(it_m->_value.events); for (it_e = evt->begin(); it_e != evt->end(); it_e++) delete it_e->_value.data; } } bool VirtualKeyboard::loadKeyboardPack(Common::String packName) { if (Common::File::exists(packName + ".xml")) { // uncompressed keyboard pack if (!_parser->loadFile(packName + ".xml")) return false; } else if (Common::File::exists(packName + ".zip")) { // compressed keyboard pack #ifdef USE_ZLIB unzFile zipFile = unzOpen((packName + ".zip").c_str()); if (zipFile && unzLocateFile(zipFile, (packName + ".xml").c_str(), 2) == UNZ_OK) { unz_file_info fileInfo; unzOpenCurrentFile(zipFile); unzGetCurrentFileInfo(zipFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0); byte *buffer = new byte[fileInfo.uncompressed_size+1]; assert(buffer); memset(buffer, 0, (fileInfo.uncompressed_size+1)*sizeof(byte)); unzReadCurrentFile(zipFile, buffer, fileInfo.uncompressed_size); unzCloseCurrentFile(zipFile); if (!_parser->loadBuffer(buffer, fileInfo.uncompressed_size+1, true)) { unzClose(zipFile); return false; } } else { warning("Could not find %s.xml file in %s.zip keyboard pack\n", packName.c_str(), packName.c_str()); unzClose(zipFile); return false; } unzClose(zipFile); ImageMan.addArchive(packName + ".zip"); #else return false; #endif } else { warning("Keyboard pack not found"); return false; } _parser->setParseMode(kParseFull); _loaded = _parser->parse(); if (_loaded) printf("Keyboard pack '%s' loaded successfully!\n", packName.c_str()); return _loaded; } void VirtualKeyboard::setDefaultPosition() { int16 scrW = _system->getOverlayWidth(), scrH = _system->getOverlayHeight(); int16 kbdW = _kbdBound.width(), kbdH = _kbdBound.height(); int16 posX = 0, posY = 0; if (scrW != kbdW) { switch (_hAlignment) { case kAlignLeft: posX = 0; break; case kAlignCentre: posX = (scrW - kbdW) / 2; break; case kAlignRight: posX = scrW - kbdW; break; } } if (scrH != kbdH) { switch (_vAlignment) { case kAlignTop: posY = 0; break; case kAlignMiddle: posY = (scrH - kbdH) / 2; break; case kAlignBottom: posY = scrH - kbdH; break; } } _kbdBound.moveTo(posX, posY); } bool VirtualKeyboard::checkModeResolutions() { _parser->setParseMode(kParseCheckResolutions); _loaded = _parser->parse(); return _loaded; } void VirtualKeyboard::move(int16 x, int16 y) { // snap to edge of screen if (ABS(x) < SNAP_WIDTH) x = 0; int16 x2 = _system->getOverlayWidth() - _kbdBound.width(); if (ABS(x - x2) < SNAP_WIDTH) x = x2; if (ABS(y) < SNAP_WIDTH) y = 0; int16 y2 = _system->getOverlayHeight() - _kbdBound.height(); if (ABS(y - y2) < SNAP_WIDTH) y = y2; _kbdBound.moveTo(x, y); } Common::String VirtualKeyboard::findArea(int16 x, int16 y) { x -= _kbdBound.left; y -= _kbdBound.top; if (x < 0 || x > _kbdBound.width()) return ""; if (y < 0 || y > _kbdBound.height()) return ""; return _currentMode->imageMap.findMapArea(x, y); } void VirtualKeyboard::processClick(const Common::String& area) { if (!_currentMode->events.contains(area)) return; Event evt = _currentMode->events[area]; switch (evt.type) { case kEventKey: // add virtual keypress to queue _keyQueue.push(*(Common::KeyState*)evt.data); break; case kEventSwitchMode: // switch to new mode switchMode(*(Common::String *)evt.data); break; case kEventClose: // close virtual keyboard _displaying = false; break; } } void VirtualKeyboard::switchMode(Mode *newMode) { _kbdBound.setWidth(newMode->image->w); _kbdBound.setHeight(newMode->image->h); _currentMode = newMode; _needRedraw = true; } void VirtualKeyboard::switchMode(const Common::String& newMode) { if (!_modes.contains(newMode)) { warning("Keyboard mode '%s' unknown", newMode.c_str()); return; } switchMode(&_modes[newMode]); } void VirtualKeyboard::show() { if (!_loaded) { // if not loaded then load default "vkeybd" pack if (!loadKeyboardPack("vkeybd")) { warning("Keyboard not loaded therefore can't be shown"); return; } } if (_lastScreenChanged != _system->getScreenChangeID()) screenChanged(); switchMode(_initialMode); _displaying = true; if (_firstRun) { _firstRun = false; setDefaultPosition(); } if (!g_gui.isActive()) { _system->showOverlay(); _system->clearOverlay(); } _overlayBackup.create(_system->getOverlayWidth(), _system->getOverlayHeight(), sizeof(OverlayColor)); _system->grabOverlay((OverlayColor*)_overlayBackup.pixels, _overlayBackup.w); setupCursor(); runLoop(); removeCursor(); _system->copyRectToOverlay((OverlayColor*)_overlayBackup.pixels, _overlayBackup.w, 0, 0, _overlayBackup.w, _overlayBackup.h); if (!g_gui.isActive()) _system->hideOverlay(); _overlayBackup.free(); } void VirtualKeyboard::hide() { _displaying = false; } void VirtualKeyboard::screenChanged() { _lastScreenChanged = _system->getScreenChangeID(); if (!checkModeResolutions()) _displaying = false; } void VirtualKeyboard::runLoop() { Common::EventManager *eventMan = _system->getEventManager(); while (_displaying) { if (_needRedraw) redraw(); animateCursor(); _system->updateScreen(); Common::Event event; while (eventMan->pollEvent(event)) { switch (event.type) { case Common::EVENT_LBUTTONDOWN: if (_kbdBound.contains(event.mouse)) { _areaDown = findArea(event.mouse.x, event.mouse.y); if (_areaDown.empty()) { _drag = true; _dragPoint.x = event.mouse.x - _kbdBound.left; _dragPoint.y = event.mouse.y - _kbdBound.top; } } else _areaDown.clear(); break; case Common::EVENT_LBUTTONUP: if (_drag) _drag = false; if (!_areaDown.empty() && _areaDown == findArea(event.mouse.x, event.mouse.y)) { processClick(_areaDown); _areaDown.clear(); } break; case Common::EVENT_MOUSEMOVE: if (_drag) { move(event.mouse.x - _dragPoint.x, event.mouse.y - _dragPoint.y); _needRedraw = true; } break; case Common::EVENT_SCREEN_CHANGED: screenChanged(); break; case Common::EVENT_QUIT: _system->quit(); return; default: break; } } // Delay for a moment _system->delayMillis(10); } // push keydown & keyup events into the event manager Common::Event evt; evt.synthetic = false; while (!_keyQueue.empty()) { evt.kbd = _keyQueue.pop(); evt.type = Common::EVENT_KEYDOWN; eventMan->pushEvent(evt); evt.type = Common::EVENT_KEYUP; eventMan->pushEvent(evt); } } void VirtualKeyboard::redraw() { Graphics::SurfaceKeyColored surf; surf.create(_system->getOverlayWidth(), _system->getOverlayHeight(), sizeof(OverlayColor)); memcpy(surf.pixels, _overlayBackup.pixels, surf.w * surf.h * sizeof(OverlayColor)); surf.blit(_currentMode->image, _kbdBound.left, _kbdBound.top, _currentMode->transparentColor); _system->copyRectToOverlay((OverlayColor*)surf.pixels, surf.w, 0, 0, surf.w, surf.h); surf.free(); _needRedraw = false; } void VirtualKeyboard::setupCursor() { const byte palette[] = { 255, 255, 255, 0, 255, 255, 255, 0, 171, 171, 171, 0, 87, 87, 87, 0 }; CursorMan.pushCursorPalette(palette, 0, 4); CursorMan.pushCursor(NULL, 0, 0, 0, 0); CursorMan.showMouse(true); } void VirtualKeyboard::animateCursor() { int time = _system->getMillis(); if (time > _cursorAnimateTimer + kCursorAnimateDelay) { for (int i = 0; i < 15; i++) { if ((i < 6) || (i > 8)) { _cursor[16 * 7 + i] = _cursorAnimateCounter; _cursor[16 * i + 7] = _cursorAnimateCounter; } } CursorMan.replaceCursor(_cursor, 16, 16, 7, 7); _cursorAnimateTimer = time; _cursorAnimateCounter = (_cursorAnimateCounter + 1) % 4; } } void VirtualKeyboard::removeCursor() { CursorMan.popCursor(); CursorMan.popCursorPalette(); } } // end of namespace Common