/* 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$ * */ //#define PSP_KB_SHELL /* Need a hack to properly load the keyboard from the PSP shell */ #ifdef PSP_KB_SHELL #define PSP_KB_SHELL_PATH "ms0:/psp/game4xx/scummvm-solid/" /* path to kbd.zip */ #endif #include #include #include #include "backends/platform/psp/psppixelformat.h" #include "backends/platform/psp/pspkeyboard.h" #include "common/keyboard.h" #include "common/fs.h" #include "common/unzip.h" //#define __PSP_DEBUG_FUNCS__ /* For debugging the stack */ //#define __PSP_DEBUG_PRINT__ #include "backends/platform/psp/trace.h" #define PSP_SCREEN_WIDTH 480 #define PSP_SCREEN_HEIGHT 272 #define K(x) ((short)(Common::KEYCODE_INVALID + (x))) #define C(x) ((short)(Common::KEYCODE_##x)) // Layout of the keyboard: Order for the boxes is clockwise and then middle: // 1 // 4 5 2 // 3 // and order of letters is clockwise in each box, plus 2 top letters: // e f // a // d b // c // K(x) is used for ascii values. C(x) is used for keys without ascii values short PSPKeyboard::_modeChar[MODE_COUNT][5][6] = { { //standard letters { K('a'), K('b'), K('c'), K('d'), K('f'), K('g') }, { K('h'), K('i'), K('l'), K('m'), K('j'), K('k') }, { K('o'), K('n'), K('r'), K('s'), K('p'), K('q') }, { K('u'), K('v'), K('w'), K('y'), K('x'), K('z') }, { K('\b'), K('t'), K(' '), K('e'), K(0), K(0) } }, { //capital letters { K('A'), K('B'), K('C'), K('D'), K('F'), K('G') }, { K('H'), K('I'), K('L'), K('M'), K('J'), K('K') }, { K('O'), K('N'), K('R'), K('S'), K('P'), K('Q') }, { K('U'), K('V'), K('W'), K('Y'), K('X'), K('Z') }, { K('\b'), K('T'), K(' '), K('E'), K(0), K(0) } }, { //numbers { K('1'), K('2'), K('3'), K('4'), K(0), K(0) }, { C(F5), C(F8), C(F7), C(F6), C(F9), C(F10) }, { K('5'), K('6'), K('7'), K('8'), K(0), K(0) }, { C(F1), C(F4), C(F3), C(F2), K(0), K(0) }, { K('\b'), K('0'), K(' '), K('9'), K(0), K(0) } }, { //symbols { K('!'), K(')'), K('?'), K('('), K('<'), K('>') }, { K('+'), K('/'), K('='), K('\\'), K('\''), K('"') }, { K(':'), K(']'), K(';'), K('['), K('@'), K('#') }, { K('-'), K('}'), K('_'), K('{'), K('*'), K('$') }, { K('\b'), K('.'), K(' '), K(','), K(0), K(0) } } }; // Read function for png library to be able to read from our SeekableReadStream // void pngReadStreamRead(png_structp png_ptr, png_bytep data, png_size_t length) { Common::SeekableReadStream *file; file = (Common::SeekableReadStream *)png_ptr->io_ptr; file->read(data, length); } // Array with file names const char *PSPKeyboard::_guiStrings[] = { "keys4.png", "keys_s4.png", "keys_c4.png", "keys_s_c4.png", "nums4.png", "nums_s4.png", "syms4.png", "syms_s4.png" }; // Constructor PSPKeyboard::PSPKeyboard() { DEBUG_ENTER_FUNC(); _init = false; // we're not initialized yet _prevButtons = 0; // Reset previous buttons _dirty = false; // keyboard needs redrawing _mode = 0; // charset selected. (0: letters, 1: uppercase 2: numbers 3: symbols) _oldCursor = kCenter; // Center cursor by default _movedX = 20; // Default starting location _movedY = 50; _moved = false; // Keyboard wasn't moved recently _state = kInvisible; // We start invisible _lastState = kInvisible; // Constant renderer settings _renderer.setAlphaBlending(true); _renderer.setColorTest(false); _renderer.setUseGlobalScaler(false); } // Destructor PSPKeyboard::~PSPKeyboard() { DEBUG_ENTER_FUNC(); if (!_init) { return; } for (int i = 0; i < guiStringsSize; i++) { _buffers[i].deallocate(); _palettes[i].deallocate(); } _init = false; } void PSPKeyboard::setVisible(bool val) { if (val && _state == kInvisible && _init) { // Check also that were loaded correctly _lastState = _state; _state = kMove; } else if (!val && _state != kInvisible) { _lastState = _state; _state = kInvisible; } setDirty(); } /* move the position the keyboard is currently drawn at */ void PSPKeyboard::moveTo(const int newX, const int newY) { DEBUG_ENTER_FUNC(); _movedX = newX; _movedY = newY; setDirty(); } /* move the position the keyboard is currently drawn at */ void PSPKeyboard::increaseKeyboardLocationX(int amount) { DEBUG_ENTER_FUNC(); int newX = _movedX + amount; if (newX > PSP_SCREEN_WIDTH - 5 || newX < 0 - 140) { // clamp return; } _movedX = newX; setDirty(); } /* move the position the keyboard is currently drawn at */ void PSPKeyboard::increaseKeyboardLocationY(int amount) { DEBUG_ENTER_FUNC(); int newY = _movedY + amount; if (newY > PSP_SCREEN_HEIGHT - 5 || newY < 0 - 140) { // clamp return; } _movedY = newY; setDirty(); } /* draw the keyboard at the current position */ void PSPKeyboard::render() { DEBUG_ENTER_FUNC(); unsigned int currentBuffer = _mode << 1; // Draw the background letters // Set renderer to current buffer & palette _renderer.setBuffer(&_buffers[currentBuffer]); _renderer.setPalette(&_palettes[currentBuffer]); _renderer.setOffsetOnScreen(_movedX, _movedY); _renderer.setOffsetInBuffer(0, 0); _renderer.setDrawWholeBuffer(); _renderer.render(); // Get X and Y coordinates for the orange block int x, y; convertCursorToXY(_oldCursor, x, y); const int OrangeBlockSize = 64; const int GrayBlockSize = 43; // Draw the current Highlighted Selector (orange block) _renderer.setBuffer(&_buffers[currentBuffer + 1]); _renderer.setPalette(&_palettes[currentBuffer + 1]); _renderer.setOffsetOnScreen(_movedX + (x * GrayBlockSize), _movedY + (y * GrayBlockSize)); _renderer.setOffsetInBuffer(x * OrangeBlockSize, y * OrangeBlockSize); _renderer.setDrawSize(OrangeBlockSize, OrangeBlockSize); _renderer.render(); } inline void PSPKeyboard::convertCursorToXY(CursorDirections cur, int &x, int &y) { switch (cur) { case kUp: x = 1; y = 0; break; case kRight: x = 2; y = 1; break; case kDown: x = 1; y = 2; break; case kLeft: x = 0; y = 1; break; default: x = 1; y = 1; break; } } /* load the keyboard into memory */ bool PSPKeyboard::load() { DEBUG_ENTER_FUNC(); if (_init) { PSP_DEBUG_PRINT("keyboard already loaded into memory\n"); return true; } // For the shell, we must use a hack #ifdef PSP_KB_SHELL Common::FSNode node(PSP_KB_SHELL_PATH); #else /* normal mode */ Common::FSNode node("."); // Look in current directory #endif PSP_DEBUG_PRINT("path[%s]\n", node.getPath().c_str()); Common::Archive *fileArchive = NULL; Common::Archive *zipArchive = NULL; Common::SeekableReadStream * file = 0; if (node.getChild("kbd").exists() && node.getChild("kbd").isDirectory()) { PSP_DEBUG_PRINT("found directory ./kbd\n"); fileArchive = new Common::FSDirectory(node.getChild("kbd")); } if (node.getChild("kbd.zip").exists()) { PSP_DEBUG_PRINT("found kbd.zip\n"); zipArchive = Common::makeZipArchive(node.getChild("kbd.zip")); } int i; // Loop through all png images for (i = 0; i < guiStringsSize; i++) { uint32 height = 0, width = 0, paletteSize = 0; PSP_DEBUG_PRINT("Opening %s.\n", _guiStrings[i]); // Look for the file in the kbd directory if (fileArchive && fileArchive->hasFile(_guiStrings[i])) { PSP_DEBUG_PRINT("found it in kbd directory.\n"); file = fileArchive->createReadStreamForMember(_guiStrings[i]); if (!file) { PSP_ERROR("Can't open kbd/%s for keyboard. No keyboard will load.\n", _guiStrings[i]); goto ERROR; } } // We didn't find it. Look for it in the zip file else if (zipArchive && zipArchive->hasFile(_guiStrings[i])) { PSP_DEBUG_PRINT("found it in kbd.zip.\n"); file = zipArchive->createReadStreamForMember(_guiStrings[i]); if (!file) { PSP_ERROR("Can't open %s in kbd.zip for keyboard. No keyboard will load.\n", _guiStrings[i]); goto ERROR; } } else { // Couldn't find the file PSP_ERROR("Can't find %s for keyboard. No keyboard will load.\n", _guiStrings[i]); goto ERROR; } if (getPngImageSize(file, &width, &height, &paletteSize) == 0) { // Check image size and palette size // Allocate memory for image PSP_DEBUG_PRINT("width[%d], height[%d], paletteSize[%d]\n", width, height, paletteSize); _buffers[i].setSize(width, height, Buffer::kSizeByTextureSize); if (paletteSize) { // 8 or 4-bit image if (paletteSize <= 16) { // 4 bit _buffers[i].setPixelFormat(PSPPixelFormat::Type_Palette_4bit); _palettes[i].setPixelFormats(PSPPixelFormat::Type_4444, PSPPixelFormat::Type_Palette_4bit); paletteSize = 16; } else if (paletteSize <= 256) { // 8-bit image paletteSize = 256; _buffers[i].setPixelFormat(PSPPixelFormat::Type_Palette_8bit); _palettes[i].setPixelFormats(PSPPixelFormat::Type_4444, PSPPixelFormat::Type_Palette_8bit); } else { PSP_ERROR("palette of %d too big!\n", paletteSize); goto ERROR; } } else { // 32-bit image _buffers[i].setPixelFormat(PSPPixelFormat::Type_8888); } _buffers[i].allocate(); _palettes[i].allocate(); // Try to load the image file->seek(0); // Go back to start if (loadPngImage(file, _buffers[i], _palettes[i]) != 0) goto ERROR; else { // Success PSP_DEBUG_PRINT("Managed to load the image\n"); if (paletteSize == 16) // 4-bit _buffers[i].flipNibbles(); delete file; } } else { PSP_ERROR("couldn't obtain PNG image size\n"); goto ERROR; } } /* for loop */ _init = true; delete fileArchive; delete zipArchive; return true; ERROR: delete file; delete fileArchive; delete zipArchive; for (int j = 0; j < i; j++) { _buffers[j].deallocate(); _palettes[j].deallocate(); } _init = false; return false; } static void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { // ignore PNG warnings } /* Get the width and height of a png image */ int PSPKeyboard::getPngImageSize(Common::SeekableReadStream *file, uint32 *png_width, uint32 *png_height, u32 *paletteSize) { DEBUG_ENTER_FUNC(); png_structp png_ptr; png_infop info_ptr; unsigned int sig_read = 0; png_uint_32 width, height; int bit_depth, color_type, interlace_type; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { return -1; } png_set_error_fn(png_ptr, (png_voidp) NULL, (png_error_ptr) NULL, user_warning_fn); info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL); return -1; } // Set the png lib to use our read function png_set_read_fn(png_ptr, (void *)file, pngReadStreamRead); png_set_sig_bytes(png_ptr, sig_read); png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, int_p_NULL, int_p_NULL); if (color_type & PNG_COLOR_MASK_PALETTE) *paletteSize = info_ptr->num_palette; else *paletteSize = 0; png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); *png_width = width; *png_height = height; return 0; } // Load a texture from a png image // int PSPKeyboard::loadPngImage(Common::SeekableReadStream *file, Buffer &buffer, Palette &palette) { DEBUG_ENTER_FUNC(); png_structp png_ptr; png_infop info_ptr; unsigned int sig_read = 0; png_uint_32 width, height; int bit_depth, color_type, interlace_type; size_t y; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { PSP_ERROR("Couldn't create read struct to load keyboard\n"); return -1; } // Use dummy error function png_set_error_fn(png_ptr, (png_voidp) NULL, (png_error_ptr) NULL, user_warning_fn); info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { PSP_ERROR("Couldn't create info struct to load keyboard\n"); png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL); return -1; } // Set the png lib to use our customized read function png_set_read_fn(png_ptr, (void *)file, pngReadStreamRead); png_set_sig_bytes(png_ptr, sig_read); png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, int_p_NULL, int_p_NULL); // Strip off 16 bit channels. Not really needed but whatever png_set_strip_16(png_ptr); if (color_type == PNG_COLOR_TYPE_PALETTE) { // Copy the palette png_colorp srcPal = info_ptr->palette; for (int i = 0; i < info_ptr->num_palette; i++) { unsigned char alphaVal = (i < info_ptr->num_trans) ? info_ptr->trans[i] : 0xFF; // Load alpha if it's there palette.setSingleColorRGBA(i, srcPal->red, srcPal->green, srcPal->blue, alphaVal); srcPal++; } } else { // Not a palettized image // Round up grayscale images if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_gray_1_2_4_to_8(png_ptr); // Convert trans channel to alpha for 32 bits if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); // Filler for alpha? png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); } unsigned char *line = (unsigned char*) malloc(info_ptr->rowbytes); if (!line) { png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL); PSP_ERROR("Couldn't allocate line\n"); return -1; } for (y = 0; y < height; y++) { png_read_row(png_ptr, line, png_bytep_NULL); buffer.copyFromRect(line, info_ptr->rowbytes, 0, y, width, 1); // Copy into buffer //memcpy(buffer.getPixels()[y * buffer.getWidthInBytes()], line, info_ptr->rowbytes); } free(line); png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); return 0; } // Defines for working with PSP buttons #define CHANGED(x) (_buttonsChanged & (x)) #define PRESSED(x) ((_buttonsChanged & (x)) && (pad.Buttons & (x))) #define UNPRESSED(x) ((_buttonsChanged & (x)) && !(pad.Buttons & (x))) #define DOWN(x) (pad.Buttons & (x)) #define UP(x) (!(pad.Buttons & (x))) #define PSP_DPAD (PSP_CTRL_DOWN|PSP_CTRL_UP|PSP_CTRL_LEFT|PSP_CTRL_RIGHT) #define PSP_4BUTTONS (PSP_CTRL_CROSS | PSP_CTRL_CIRCLE | PSP_CTRL_TRIANGLE | PSP_CTRL_SQUARE) /* * Attempts to read a character from the controller * Uses the state machine. * returns whether we have an event */ bool PSPKeyboard::processInput(Common::Event &event, SceCtrlData &pad) { DEBUG_ENTER_FUNC(); bool haveEvent = false; // Whether we have an event for the event manager to process event.kbd.flags = 0; _buttonsChanged = _prevButtons ^ pad.Buttons; if (!_init) // In case we never had init return false; if (_state == kInvisible) // Return if we're invisible return false; if (_state != kMove && PRESSED(PSP_CTRL_SELECT)) { _lastState = _state; _state = kMove; // Check for move or visible state } else if (CHANGED(PSP_CTRL_START)) { // Handle start button: enter, make KB invisible event.kbd.ascii = '\r'; event.kbd.keycode = Common::KEYCODE_RETURN; event.type = DOWN(PSP_CTRL_START) ? Common::EVENT_KEYDOWN : Common::EVENT_KEYUP; haveEvent = true; _dirty = true; if (UP(PSP_CTRL_START)) _state = kInvisible; // Make us invisible if unpressed } // Check for being in state of moving the keyboard onscreen or pressing select else if (_state == kMove) handleMoveState(pad); else if (_state == kDefault) haveEvent = handleDefaultState(event, pad); else if (_state == kCornersSelected) haveEvent = handleCornersSelectedState(event, pad); else if (_state == kRTriggerDown) handleRTriggerDownState(pad); // Deal with trigger states else if (_state == kLTriggerDown) handleLTriggerDownState(pad); // Deal with trigger states _prevButtons = pad.Buttons; return haveEvent; } void PSPKeyboard::handleMoveState(SceCtrlData &pad) { DEBUG_ENTER_FUNC(); if (UP(PSP_CTRL_SELECT)) { // Toggle between visible and invisible _state = (_lastState == kInvisible) ? kDefault : kInvisible; _dirty = true; if (_moved) { // We moved the keyboard. Keep the keyboard onscreen anyway _state = kDefault; _moved = false; // reset moved flag } } else if (DOWN(PSP_DPAD)) { // How we move the KB onscreen _moved = true; _dirty = true; if (DOWN(PSP_CTRL_DOWN)) increaseKeyboardLocationY(5); else if (DOWN(PSP_CTRL_UP)) increaseKeyboardLocationY(-5); else if (DOWN(PSP_CTRL_LEFT)) increaseKeyboardLocationX(-5); else /* DOWN(PSP_CTRL_RIGHT) */ increaseKeyboardLocationX(5); } } bool PSPKeyboard::handleDefaultState(Common::Event &event, SceCtrlData &pad) { DEBUG_ENTER_FUNC(); bool haveEvent = false; if (PRESSED(PSP_CTRL_LTRIGGER)) // Don't say we used up the input _state = kLTriggerDown; else if (PRESSED(PSP_CTRL_RTRIGGER)) // Don't say we used up the input _state = kRTriggerDown; else if (CHANGED(PSP_4BUTTONS)) // We only care about the 4 buttons haveEvent = getInputChoice(event, pad); else if (!DOWN(PSP_4BUTTONS)) // Must be up to move cursor getCursorMovement(pad); return haveEvent; } bool PSPKeyboard::handleCornersSelectedState(Common::Event &event, SceCtrlData &pad) { DEBUG_ENTER_FUNC(); // We care about 4 buttons + triggers (for letter selection) bool haveEvent = false; if (CHANGED(PSP_4BUTTONS | PSP_CTRL_RTRIGGER | PSP_CTRL_LTRIGGER)) haveEvent = getInputChoice(event, pad); if (!DOWN(PSP_4BUTTONS | PSP_CTRL_RTRIGGER | PSP_CTRL_LTRIGGER)) // Must be up to move cursor getCursorMovement(pad); return haveEvent; } bool PSPKeyboard::getInputChoice(Common::Event &event, SceCtrlData &pad) { DEBUG_ENTER_FUNC(); int innerChoice; bool haveEvent = false; if (UNPRESSED(PSP_CTRL_TRIANGLE)) { innerChoice = 0; event.type = Common::EVENT_KEYUP; // We give priority to key_up } else if (UNPRESSED(PSP_CTRL_CIRCLE)) { innerChoice = 1; event.type = Common::EVENT_KEYUP; // We give priority to key_up } else if (UNPRESSED(PSP_CTRL_CROSS)) { innerChoice = 2; event.type = Common::EVENT_KEYUP; // We give priority to key_up } else if (UNPRESSED(PSP_CTRL_SQUARE)) { innerChoice = 3; event.type = Common::EVENT_KEYUP; // We give priority to key_up } else if (UNPRESSED(PSP_CTRL_LTRIGGER) && _state == kCornersSelected) { innerChoice = 4; event.type = Common::EVENT_KEYUP; // We give priority to key_up } else if (UNPRESSED(PSP_CTRL_RTRIGGER) && _state == kCornersSelected) { innerChoice = 5; event.type = Common::EVENT_KEYUP; // We give priority to key_up } else if (PRESSED(PSP_CTRL_TRIANGLE)) { innerChoice = 0; event.type = Common::EVENT_KEYDOWN; } else if (PRESSED(PSP_CTRL_CIRCLE)) { innerChoice = 1; event.type = Common::EVENT_KEYDOWN; } else if (PRESSED(PSP_CTRL_CROSS)) { innerChoice = 2; event.type = Common::EVENT_KEYDOWN; } else if (PRESSED(PSP_CTRL_SQUARE)) { innerChoice = 3; event.type = Common::EVENT_KEYDOWN; } else if (PRESSED(PSP_CTRL_LTRIGGER) && _state == kCornersSelected) { innerChoice = 4; event.type = Common::EVENT_KEYDOWN; // We give priority to key_up } else { /* (PRESSED(PSP_CTRL_RTRIGGER)) && _state == kCornersSelected */ innerChoice = 5; event.type = Common::EVENT_KEYDOWN; // We give priority to key_up } #define IS_UPPERCASE(x) ((x) >= (unsigned short)'A' && (x) <= (unsigned short)'Z') #define TO_LOWER(x) ((x) += 'a'-'A') //Now grab the value out of the array short choice = _modeChar[_mode][_oldCursor][innerChoice]; event.kbd.ascii = choice <= 255 ? choice : 0; // Handle upper-case which is missing in Common::KeyCode if (IS_UPPERCASE(choice)) { event.kbd.keycode = (Common::KeyCode) TO_LOWER(choice); event.kbd.flags = Common::KBD_SHIFT; } else event.kbd.keycode = (Common::KeyCode) choice; haveEvent = (choice != Common::KEYCODE_INVALID) ? true : false; // We have an event/don't if it's invalid return haveEvent; } void PSPKeyboard::getCursorMovement(SceCtrlData &pad) { DEBUG_ENTER_FUNC(); CursorDirections cursor; // Find where the cursor is pointing cursor = kCenter; _state = kDefault; if (DOWN(PSP_DPAD)) { _state = kCornersSelected; if (DOWN(PSP_CTRL_UP)) cursor = kUp; else if (DOWN(PSP_CTRL_RIGHT)) cursor = kRight; else if (DOWN(PSP_CTRL_DOWN)) cursor = kDown; else if (DOWN(PSP_CTRL_LEFT)) cursor = kLeft; } if (cursor != _oldCursor) { //If we've moved, update dirty and return _dirty = true; _oldCursor = cursor; } } void PSPKeyboard::handleLTriggerDownState(SceCtrlData &pad) { DEBUG_ENTER_FUNC(); if (UNPRESSED(PSP_CTRL_LTRIGGER)) { _dirty = true; if (_mode < 2) _mode = 2; else _mode = (_mode == 2) ? 3 : 2; _state = kDefault; } } void PSPKeyboard::handleRTriggerDownState(SceCtrlData &pad) { DEBUG_ENTER_FUNC(); if (UNPRESSED(PSP_CTRL_RTRIGGER)) { _dirty = true; if (_mode > 1) _mode = 0; else _mode = (_mode == 0) ? 1 : 0; _state = kDefault; } }