#ifndef RESOURCE_PICKER_IMPLEMENTATION_INCLUDED #define RESOURCE_PICKER_IMPLEMENTATION_INCLUDED #include "ResourcePicker.h" #include "resourceDatabase.h" #include "resourceManager.h" #include "usageDatabase.h" #include "speechHints.h" #include "Room.h" #include "imageCache.h" #include "minorGems/util/stringUtils.h" #include "minorGems/util/log/AppLog.h" #include #include #include extern TextGL *largeTextFixed; template ResourcePicker::ResourcePicker( double inAnchorX, double inAnchorY, SimpleVector *inStack ) : BorderPanel( inAnchorX, inAnchorY, 70, 93, new Color( 0, 0, 0, 1 ), new Color( 0.35, 0.35, 0.35, 1 ), 1 ), mNumResourcesOnScreen( 0 ), mSearchResultOffset( 0 ), mNumSearchResults( 0 ), mStackMode( false ), mStack( inStack ) { double tileSpace = P; // room for button borders double buttonSpace = tileSpace + 4; // use same separation for both x and y double buttonSeparation = ( mWidth - buttonSpace * mGridW ) / (mGridW + 1); // put left and right at very bottom mLeftButton = new LeftButtonGL( mAnchorX + buttonSeparation, mAnchorY + buttonSeparation, 8, 8 ); add( mLeftButton ); mLeftButton->setEnabled( false ); mLeftButton->addActionListener( this ); mRightButton = new RightButtonGL( mAnchorX + mWidth - buttonSeparation - 8, mAnchorY + buttonSeparation, 8, 8 ); add( mRightButton ); mRightButton->setEnabled( false ); mRightButton->addActionListener( this ); // centered above picker mStackSearchButton = new StackSearchButtonGL( mAnchorX + (mWidth-8)/3 - 1, mAnchorY + 1, 8, 8 ); add( mStackSearchButton ); mStackSearchButton->addActionListener( this ); double baseY = mAnchorY + buttonSeparation + 8; double topButtonY = baseY + buttonSeparation + mGridH * ( buttonSpace + buttonSeparation ) - buttonSpace; for( int y=0; ysetEnabled( false ); mButtonGrid[y][x]->addActionListener( this ); } } mLastActionFromPress = false; int fieldHeight = 8; int fieldWidth = 8 * 8; const char *defaultSearch = ""; double fieldAnchorY = topButtonY + buttonSpace + 1; mSearchField = new TextFieldGL( mAnchorX + 2, fieldAnchorY, fieldWidth, fieldHeight, 1, defaultSearch, largeTextFixed, new Color( 0.75, 0.75, 0.75 ), new Color( 0.75, 0.75, 0.75 ), new Color( 0.15, 0.15, 0.15 ), new Color( 0.75, 0.75, 0.75 ), 8, false ); add( mSearchField ); mSearchField->setFocus( false ); //mSearchField->lockFocus( true ); mSearchField->setCursorPosition( strlen( defaultSearch ) ); mSearchField->addActionListener( this ); mSelectedResourceButton = new DraggableTwoSpriteButtonGL( NULL, NULL, 1, mAnchorX + mWidth - buttonSpace - 2, mAnchorY + mHeight - buttonSpace - 2, buttonSpace, buttonSpace ); add( mSelectedResourceButton ); mSelectedResourceButton->setEnabled( true ); mSelectedResourceButton->addActionListener( this ); refreshSelectedResourceSprite(); mDeleteButton = new DeleteButtonGL( mSelectedResourceButton->getAnchorX() - 8, mAnchorY + mHeight - 10, 8, 8 ); add( mDeleteButton ); mDeleteButton->addActionListener( this ); mDeleteButton->setEnabled( false ); // concatonate char *tipKey = autoSprintf( "tip_delete_%s", ResourceType::getResourceType() ); mDeleteButton->setToolTip( tipKey ); delete [] tipKey; EightPixelLabel *tileLabel = new EightPixelLabel( mAnchorX + 2, mAnchorY + mHeight - 8 - 2, (char*)( ResourceType::getResourceType() ) ); add( tileLabel ); mSearchLabel = new EightPixelLabel( mAnchorX + 2, fieldAnchorY + fieldHeight + 1, "search" ); add( mSearchLabel ); mStackLabel = new EightPixelLabel( mAnchorX + 2, fieldAnchorY + fieldHeight + 1, "stack" ); } template ResourcePicker::~ResourcePicker() { if( !contains( mSearchLabel ) ) { delete mSearchLabel; } if( !contains( mSearchField ) ) { delete mSearchField; } if( !contains( mStackLabel ) ) { delete mStackLabel; } } template ResourceType ResourcePicker::getSelectedResource() { return mSelectedResource; } template uniqueID ResourcePicker::getSelectedResourceID() { return mSelectedResource.getUniqueID(); } template void ResourcePicker::refreshSelectedResourceSprite() { Sprite *backgroundSprite = getButtonGridBackground(); char useTrans = false; if( backgroundSprite != NULL ) { useTrans = true; } mSelectedResourceButton->setFrontSprite( mSelectedResource.getSprite( useTrans ) ); mSelectedResourceButton->setSprite( backgroundSprite ); char *tip = mSelectedResource.getName(); mSelectedResourceButton->setToolTip( tip, false ); delete [] tip; } template void ResourcePicker::recheckDeletable() { if( equal( mSelectedResource.getUniqueID(), ResourceType::getDefaultResource().getUniqueID() ) ) { // can't delete blank mDeleteButton->setEnabled( false ); } else if( isUsed( mSelectedResource.getUniqueID() ) ) { uniqueID user = getUser( mSelectedResource.getUniqueID() ); char *userName = getResourceName( user ); char *tip; char *resourceName = mSelectedResource.getName(); if( userName != NULL ) { tip = autoSprintf( "%s (used in %s)", resourceName, userName ); } else { tip = autoSprintf( "%s (used in current edit)", resourceName ); } delete [] resourceName; mSelectedResourceButton->setToolTip( tip, false ); delete [] tip; // can't delete a resource used elsewhere mDeleteButton->setEnabled( false ); } else { // not default, and not used anywhere. Deletable! mDeleteButton->setEnabled( true ); } } template void ResourcePicker::setSelectedResource( ResourceType inResource, char inForceResultRefresh, char inFireEvent ) { if( !equal( mSelectedResource.getUniqueID(), inResource.getUniqueID() ) ) { // a change mSelectedResource = inResource; refreshSelectedResourceSprite(); // remove from lower in stack uniqueID id = mSelectedResource.getUniqueID(); char found = false; int stackSize = mStack->size(); for( int i=0; igetElement( i ) ) ) ) { mStack->deleteElement( i ); found = true; } } mStack->push_back( id ); if( mStack->size() > 256 ) { // size limit reached // delete oldest mStack->deleteElement( 0 ); } recheckDeletable(); if( mStackMode ) { // back to top whenever top changes mSearchResultOffset = 0; } if( inForceResultRefresh ) { newSearch( true ); } else if( mStackMode ) { if( found ) { // just stack order changed getFreshResults(); } else { // something new added to stack... maybe a size change newSearch( false ); } } if( inFireEvent ) { mLastActionFromPress = false; fireActionPerformed( this ); } } } template void ResourcePicker::forceNewSearch() { newSearch( false ); } template void ResourcePicker::newSearch( char inPreservePageOffset, char inMoveBackward ) { char *search = stringDuplicate( mSearchField->getText() ); int numResults; if( !mStackMode ) { numResults = countSearchResults( ResourceType::getResourceType(), search ); } else { numResults = countStackResults(); } //printf( "New resource search, %d results\n", numResults ); delete [] search; int oldOffset = mSearchResultOffset; mNumSearchResults = numResults; mSearchResultOffset = 0; if( inPreservePageOffset ) { if( oldOffset < numResults ) { // still in range mSearchResultOffset = oldOffset; } } if( numResults > mNumResourcesDisplayedMax ) { // scroll numResults = mNumResourcesDisplayedMax; mLeftButton->setEnabled( true ); mRightButton->setEnabled( true ); } else { // only one page mLeftButton->setEnabled( false ); mRightButton->setEnabled( false ); } getFreshResults( inMoveBackward ); } template Sprite *ResourcePicker::getButtonGridBackground() { return NULL; } template void ResourcePicker::getFreshResults( char inMoveBackward ) { uniqueID ids[mNumResourcesDisplayedMax]; // clear old sprites int i; for( i=0; isetEnabled( false ); } char *search = stringDuplicate( mSearchField->getText() ); //printf( "Fresh results with offset %d from %d results\n", // mSearchResultOffset, mNumSearchResults ); if( !mStackMode ) { mNumResourcesOnScreen = getSearchResults( ResourceType::getResourceType(), search, mSearchResultOffset, mNumResourcesDisplayedMax, ids ); } else { mNumResourcesOnScreen = getStackResults( mSearchResultOffset, mNumResourcesDisplayedMax, ids ); } delete [] search; Sprite *backgroundSprite = getButtonGridBackground(); char useTrans = false; if( backgroundSprite != NULL ) { useTrans = true; // delete this test sprite, because we need to add a copy // to every button below delete backgroundSprite; } char badResultCount = 0; for( i=0; isetEnabled( true ); mButtonGrid[y][x]->setFrontSprite( r.getSprite( useTrans ) ); mButtonGrid[y][x]->setSprite( getButtonGridBackground() ); char *tip = r.getName(); mButtonGrid[y][x]->setToolTip( tip, false ); delete [] tip; if( ! equal( ids[i], r.getUniqueID() ) ) { // this resource failed to load (bogus search result in index, // and it will be removed by resourceManager) // it will show up as a "default" resource with blank sprite, // even if the current search does not match the word "default", // which is confusing. badResultCount++; // abandon this search and start a brand new one, attempting // to maintain the page offset // however, finish loading rest of resources in this batch // in case it contains multiple bad ones (they are only removed // by resourceManager when they are loaded), instead of removing // them one-by-one with several newSearches (slow) } } if( badResultCount > 0 ) { if( inMoveBackward ) { // when moving backward through results, // the removal of bad results can cause the left button // to appear to have no effect // account for this mNumSearchResults -= badResultCount; mSearchResultOffset -= badResultCount; lowerBoundSearchOffset(); } newSearch( true, inMoveBackward ); } } template void ResourcePicker::lowerBoundSearchOffset() { if( mSearchResultOffset < 0 ) { // roll back to last page int numOnLastPage = mNumSearchResults % mNumResourcesDisplayedMax; mSearchResultOffset = mNumSearchResults - numOnLastPage; if( numOnLastPage == 0 ) { // an even number of full pages // show last page mSearchResultOffset = mSearchResultOffset - mNumResourcesDisplayedMax; } } } template void ResourcePicker::actionPerformed( GUIComponent *inTarget ) { if( inTarget == mSearchField ) { // search changed newSearch(); } else if( inTarget == mDeleteButton ) { // confirmed delete! uniqueID id = mSelectedResource.getUniqueID(); deleteResource( ResourceType::getResourceType(), id ); removeUsages( id ); // delete from stack, too char found = false; int stackSize = mStack->size(); // walk backward... most likely at top of stack (right?) for( int i=stackSize-1; i>=0 && !found; i-- ) { if( equal( id, *( mStack->getElement( i ) ) ) ) { AppLog::detail( "Found element to delete in stack\n" ); mStack->deleteElement( i ); found = true; } } // delete from image cache, too clearCachedImages( id ); // also hint no longer needed clearSpeechHint( id ); // refresh results (in case they show the deleted tile) newSearch( true ); if( !mStackMode || mStack->size() == 0 ) { // set to default setSelectedResource( ResourceType::getDefaultResource() ); } else { // take next top of stack ResourceType r( *( mStack->getElement( mStack->size() - 1 ) ) ); setSelectedResource( r ); } } else if( inTarget == mLeftButton ) { mSearchResultOffset -= mNumResourcesDisplayedMax; lowerBoundSearchOffset(); getFreshResults( true ); } else if( inTarget == mRightButton ) { mSearchResultOffset += mNumResourcesDisplayedMax; if( mSearchResultOffset > mNumSearchResults - 1 ) { // wrap back around mSearchResultOffset = 0; } getFreshResults(); } else if( inTarget == mStackSearchButton ) { mStackMode = mStackSearchButton->getState(); if( mStackMode ) { remove( mSearchField ); remove( mSearchLabel ); add( mStackLabel ); } else { remove( mStackLabel ); add( mSearchLabel ); add( mSearchField ); } newSearch(); } else if( inTarget == mSelectedResourceButton ) { if( mSelectedResourceButton->mLastActionFromPress ) { // event from a press.... start drag and drop? mLastActionFromPress = true; mCurrentDraggedResource = mSelectedResource; fireActionPerformed( this ); } } else { // consider a button press char found = false; for( int i=0; imLastActionFromPress ) { // don't let it fire an event... // (because it only fires on item change) setSelectedResource( mResourcesDisplayed[ i ], false, false ); // ...we fire our own, even if there's no change mLastActionFromPress = false; fireActionPerformed( this ); /* if( mStackMode ) { // show top of stack again mSearchResultOffset = 0; getFreshResults(); } */ } else { // event from a press.... start drag and drop? mLastActionFromPress = true; mCurrentDraggedResource = mResourcesDisplayed[ i ]; fireActionPerformed( this ); } } } } } template void ResourcePicker::setState( ResourceType inSelectedResource, char *inSearchString, int inResultOffset, char inStackMode ) { mSearchField->setText( inSearchString ); mSearchResultOffset = inResultOffset; char stackModeChange = ( mStackMode != inStackMode ); mStackMode = inStackMode; mStackSearchButton->setState( mStackMode ); if( stackModeChange ) { if( mStackMode ) { remove( mSearchField ); remove( mSearchLabel ); add( mStackLabel ); } else { remove( mStackLabel ); add( mSearchLabel ); add( mSearchField ); } } setSelectedResource( inSelectedResource ); // always do new search after clone (in case selected resource doesn't // change, so no new search would be triggered) newSearch( true ); } template void ResourcePicker::cloneState( ResourcePicker *inOtherPicker ) { inOtherPicker->setState( mSelectedResource, mSearchField->getText(), mSearchResultOffset, mStackMode ); } template int ResourcePicker::countStackResults() { return mStack->size(); } template int ResourcePicker::getStackResults( int inNumToSkip, int inNumToGet, uniqueID *outIDs ) { int numGotten = 0; int stackSize = mStack->size(); int end = inNumToSkip + inNumToGet; if( end > stackSize ) { end = stackSize; } for( int i=inNumToSkip; igetElement( stackSize - i - 1 ) ); numGotten++; } return numGotten; } template char ResourcePicker::wasLastActionFromPress() { return mLastActionFromPress; } template ResourceType ResourcePicker::getDraggedResource() { return mCurrentDraggedResource; } template char ResourcePicker::isSearchFieldFocused() { return mSearchField->isFocused(); } #endif