/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. 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 3 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, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ ////////////////////////////////////////////////////////////////////////////// // // // pfGUIListBoxMod Definition // // // ////////////////////////////////////////////////////////////////////////////// #include "hsTypes.h" #include "pfGUIListBoxMod.h" #include "pfGUIListElement.h" #include "pfGameGUIMgr.h" #include "pfGUIUpDownPairMod.h" #include "pfGUIControlHandlers.h" #include "pfGUIDialogMod.h" #include "../pnMessage/plRefMsg.h" #include "../pfMessage/pfGameGUIMsg.h" #include "../plMessage/plAnimCmdMsg.h" #include "../plAvatar/plAGModifier.h" #include "../plGImage/plDynamicTextMap.h" #include "../plInputCore/plInputInterface.h" #include "plgDispatch.h" #include "hsResMgr.h" #define kIndentAmount 16 //// plSmallRect Stuff /////////////////////////////////////////////////////// void pfGUIListBoxMod::plSmallRect::Set( Int16 l, Int16 t, Int16 r, Int16 b ) { fLeft = l; fTop = t; fRight = r; fBottom = b; } hsBool pfGUIListBoxMod::plSmallRect::Contains( Int16 x, Int16 y ) { if( x < fLeft || x > fRight || y < fTop || y > fBottom ) return false; return true; } //// Wee Little Control Proc for scrolling /////////////////////////////////// class pfScrollProc : public pfGUICtrlProcObject { protected: pfGUIListBoxMod *fParent; public: pfScrollProc( pfGUIListBoxMod *parent ) : fParent( parent ) {} virtual void DoSomething( pfGUIControlMod *ctrl ) { // Do a check here to make sure we actually changed scroll // positions--if not, we don't want to update, since that'll be // slow as hell int newScrollPos = (int)fParent->fScrollControl->GetMax() - (int)fParent->fScrollControl->GetCurrValue(); if( newScrollPos != fParent->fScrollPos ) { fParent->IUpdate(); fParent->HandleExtendedEvent( pfGUIListBoxMod::kScrollPosChanged ); } } }; //// Constructor/Destructor ////////////////////////////////////////////////// pfGUIListBoxMod::pfGUIListBoxMod() { SetFlag( kWantsInterest ); fCurrClick = -1; fCurrHover = -1; fModsAtDragTime = 0; fScrollControl = nil; fCheckScroll = false; fSingleSelElement = -1; fScrollRangeUpdateDeferred = false; fScrollPos = 0; fLocked = false; fReadyToRoll = false; fClicking = false; fScrollProc = nil; } pfGUIListBoxMod::~pfGUIListBoxMod() { ClearAllElements(); if( fScrollProc && fScrollProc->DecRef() ) delete fScrollProc; } //// IEval /////////////////////////////////////////////////////////////////// hsBool pfGUIListBoxMod::IEval( double secs, hsScalar del, UInt32 dirty ) { return pfGUIControlMod::IEval( secs, del, dirty ); } //// MsgReceive ////////////////////////////////////////////////////////////// hsBool pfGUIListBoxMod::MsgReceive( plMessage *msg ) { plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef( msg ); if( refMsg != nil ) { if( refMsg->fType == kRefScrollCtrl ) { if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) ) { fScrollControl = pfGUIValueCtrl::ConvertNoRef( refMsg->GetRef() ); fScrollControl->SetHandler( fScrollProc ); fScrollControl->SetStep( 1.f ); ICalcScrollRange(); } else fScrollControl = nil; return true; } else if( refMsg->fType == kRefDynTextMap ) { // Capture this so we can reset our flag, but do NOT just return from it! if( !( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) ) ) fReadyToRoll = false; } } return pfGUIControlMod::MsgReceive( msg ); } //// IPostSetUpDynTextMap //////////////////////////////////////////////////// void pfGUIListBoxMod::IPostSetUpDynTextMap( void ) { ICalcWrapStarts(); ICalcScrollRange(); fReadyToRoll = true; } //// IUpdate ///////////////////////////////////////////////////////////////// void pfGUIListBoxMod::IUpdate( void ) { int i, j; UInt16 x, y, width, height, maxHeight; if( !fReadyToRoll ) return; if( fScrollRangeUpdateDeferred ) { fScrollRangeUpdateDeferred = false; ICalcScrollRange(); } fDynTextMap->ClearToColor( GetColorScheme()->fBackColor ); if( fElements.GetCount() == 0 ) { fDynTextMap->FlushToHost(); return; } if( fLocked ) { hsStatusMessage( "WARNING: Forcing unlock on GUI list box due to call to IUpdate()\n" ); UnlockList(); } if( fScrollControl != nil ) { // We reverse the value, since we want the "up" button to scroll "up", which actually // *decreases* the scrollPos fScrollPos = (int)fScrollControl->GetMax() - (int)fScrollControl->GetCurrValue(); if( fCheckScroll ) { // If this is set, we just did something to change the selection to a single item. // Since it makes sense to try to ensure this item is on the screen, we thus do so here. // We don't want to do this every time, because we could be simply clicking on the // scroll bars, in which case we DON'T want to do this check, since it would be // fighting against the user, which is a big UI design no-no. // To do this check, we search on either side of our scroll range (we can only // search past after we draw, unfortunately). If there's a selected item out // of our range, then we best move the scrollPos to move it into view for( j = 0; j < fScrollPos; j++ ) { int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount(); hsBool anySelected = false; for( i = fWrapStartIdxs[ j ]; i < end; i++ ) anySelected |= fElements[ i ]->IsSelected(); if( anySelected ) { // Shit. Move the scrollPos up to this item at the very least fScrollPos = j; fScrollControl->SetCurrValue( (hsScalar)( (int)fScrollControl->GetMax() - fScrollPos ) ); fCheckScroll = false; break; } } } } else fScrollPos = 0; fElementBounds.SetCountAndZero( fElements.GetCount() ); if( !HasFlag( kScrollLeftToRight ) ) { for( j = fScrollPos, y = 4; j < fWrapStartIdxs.GetCount() && y < fDynTextMap->GetVisibleHeight() - 8; j++ ) { int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount(); for( maxHeight = 0, i = fWrapStartIdxs[ j ], x = 0; i < end; i++ ) { if( HasFlag( kGrowLeavesAndProcessOxygen ) && fElements[ i ]->IsCollapsed() ) continue; // Skip collapsed items fElements[ i ]->GetSize( fDynTextMap, &width, &height ); if( !HasFlag( kAllowMultipleElementsPerRow ) ) width = fDynTextMap->GetVisibleWidth(); if( y + height >= fDynTextMap->GetVisibleHeight() - 8 ) height = fDynTextMap->GetVisibleHeight() - 8 - y; if( HasFlag( kGrowLeavesAndProcessOxygen ) ) { x += fElements[ i ]->GetIndentLevel() * kIndentAmount; if( x + width > fDynTextMap->GetVisibleWidth() ) width = fDynTextMap->GetVisibleWidth() - x; } if( !fElements[ i ]->Draw( fDynTextMap, x, y, width, height ) ) { // Couldn't draw, so force it to be at the end of the scroll range y = fDynTextMap->GetVisibleHeight() - 8; j--; break; } maxHeight = ( height > maxHeight ) ? height : maxHeight; fElementBounds[ i ].Set( x, y, x + width - 1, y + height - 1 ); x += width; } y += maxHeight; } } else { for( j = fScrollPos, x = 4; j < fWrapStartIdxs.GetCount() && x < fDynTextMap->GetVisibleWidth() - 8; j++ ) { int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount(); for( maxHeight = 0, i = fWrapStartIdxs[ j ], y = 0; i < end; i++ ) { fElements[ i ]->GetSize( fDynTextMap, &width, &height ); if( !HasFlag( kAllowMultipleElementsPerRow ) ) height = fDynTextMap->GetVisibleHeight(); if( x + width >= fDynTextMap->GetVisibleWidth() - 8 ) width = fDynTextMap->GetVisibleWidth() - 8 - x; if( !fElements[ i ]->Draw( fDynTextMap, x, y, width, height ) ) { // Couldn't draw, so force it to be at the end of the scroll range x = fDynTextMap->GetVisibleWidth() - 8; j--; break; } maxHeight = ( width > maxHeight ) ? width : maxHeight; fElementBounds[ i ].Set( x, y, x + width - 1, y + height - 1 ); y += height; } x += maxHeight; } } // Second part of our scroll check--if we got here, then there was nothing selected // before the viewing area. So check the rest if( fCheckScroll && fScrollControl != nil ) { fCheckScroll = false; for( ; j < fWrapStartIdxs.GetCount(); j++ ) { int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount(); hsBool anySelected = false; for( i = fWrapStartIdxs[ j ]; i < end; i++ ) anySelected |= fElements[ i ]->IsSelected(); if( anySelected ) { fScrollPos = j; fScrollControl->SetCurrValue( (hsScalar)( (int)fScrollControl->GetMax() - fScrollPos ) ); IUpdate(); // Gotta update again, since we just changed the scrollPos after the fact return; } } } fDynTextMap->FlushToHost(); } //// ICalcWrapStarts ///////////////////////////////////////////////////////// // Calculates the starting indexes for each row of items. Call whenever you // update the element list. void pfGUIListBoxMod::ICalcWrapStarts( void ) { UInt16 i, x, y, maxHeight, width, height; fWrapStartIdxs.Reset(); if( HasFlag( kAllowMultipleElementsPerRow ) ) { if( !HasFlag( kScrollLeftToRight ) ) { for( i = 0, x = (UInt16)-1, y = 4, maxHeight = 0; i < fElements.GetCount(); i++ ) { fElements[ i ]->GetSize( fDynTextMap, &width, &height ); if( x + width >= fDynTextMap->GetVisibleWidth() ) { x = 0; y += maxHeight; maxHeight = 0; fWrapStartIdxs.Append( i ); } x += width; maxHeight = ( height > maxHeight ) ? height : maxHeight; } } else { for( i = 0, y = (UInt16)-1, x = 4, maxHeight = 0; i < fElements.GetCount(); i++ ) { fElements[ i ]->GetSize( fDynTextMap, &width, &height ); if( y + height >= fDynTextMap->GetVisibleHeight() ) { y = 0; x += maxHeight; maxHeight = 0; fWrapStartIdxs.Append( i ); } y += height; maxHeight = ( width > maxHeight ) ? width : maxHeight; } } } else { for( i = 0; i < fElements.GetCount(); i++ ) { if( HasFlag( kGrowLeavesAndProcessOxygen ) && fElements[ i ]->IsCollapsed() ) continue; fWrapStartIdxs.Append( i ); } } } //// ICalcScrollRange //////////////////////////////////////////////////////// // Call whenever you update the element list void pfGUIListBoxMod::ICalcScrollRange( void ) { UInt16 ctrlHt, ctrlWd, height, width, maxHeight; int i, j, end; if( !fReadyToRoll ) { fScrollRangeUpdateDeferred = true; return; } // Basically, the scroll range is 0 to (numElements-1), but since if we scroll // that far, we won't see anything left except the last element, we have to calculate // how many elements away from the end we can get before we see blank space. Which means // counting up from the end until we have passed the height of our control. end = fElements.GetCount(); if( !HasFlag( kScrollLeftToRight ) ) { if( HasFlag( kGrowLeavesAndProcessOxygen ) ) { // OK, that really isn't the end, the end will the count of non-collapsed elements. Haha. for( i = 0, end = 0; i < fElements.GetCount(); i++ ) { if( !fElements[ i ]->IsCollapsed() ) end++; } } for( i = fWrapStartIdxs.GetCount() - 1, height = 0; i >= 0; i-- ) { // Get the max height of this row maxHeight = 0; for( j = fWrapStartIdxs[ i ]; j < end; j++ ) { fElements[ j ]->GetSize( fDynTextMap, &width, &ctrlHt ); maxHeight = ( ctrlHt > maxHeight ) ? ctrlHt : maxHeight; } end = fWrapStartIdxs[ i ]; height += maxHeight; if( height > fDynTextMap->GetVisibleHeight() - 8 ) break; } } else { for( i = fWrapStartIdxs.GetCount() - 1, width = 0; i >= 0; i-- ) { // Get the max width of this column maxHeight = 0; for( j = fWrapStartIdxs[ i ]; j < end; j++ ) { fElements[ j ]->GetSize( fDynTextMap, &ctrlWd, &ctrlHt ); maxHeight = ( ctrlWd > maxHeight ) ? ctrlWd : maxHeight; } end = fWrapStartIdxs[ i ]; width += maxHeight; if( width > fDynTextMap->GetVisibleWidth() - 8 ) break; } } // Note: i went one past, so our scroll range is really 0 to i+1 if( fScrollControl ) { // Because we reverse the position on draw, we have to do some special trick here // to make sure the reversed position stays the same fScrollPos = (int)fScrollControl->GetMax() - (int)fScrollControl->GetCurrValue(); if( fScrollPos >= fWrapStartIdxs.GetCount() ) fScrollPos = fWrapStartIdxs.GetCount() - 1; if( i < 0 ) // Smaller than the viewing area, so we don't scroll at all fScrollControl->SetRange( 0.f, 0.f ); else fScrollControl->SetRange( 0.f, (hsScalar)( i + 1 ) ); fScrollControl->SetCurrValue( (hsScalar)( (int)fScrollControl->GetMax() - fScrollPos ) ); } } void pfGUIListBoxMod::PurgeDynaTextMapImage() { if ( fDynTextMap != nil ) fDynTextMap->PurgeImage(); } //// Read/Write ////////////////////////////////////////////////////////////// void pfGUIListBoxMod::Read( hsStream *s, hsResMgr *mgr ) { pfGUIControlMod::Read(s, mgr); fScrollControl = nil; if( s->ReadBool() ) { fScrollProc = TRACKED_NEW pfScrollProc( this ); fScrollProc->IncRef(); mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefScrollCtrl ), plRefFlags::kActiveRef ); } if( HasFlag( kDisableSelection ) ) ClearFlag( kWantsInterest ); } void pfGUIListBoxMod::Write( hsStream *s, hsResMgr *mgr ) { pfGUIControlMod::Write( s, mgr ); if( fScrollControl != nil ) { s->WriteBool( true ); mgr->WriteKey( s, fScrollControl->GetKey() ); } else s->WriteBool( false ); } //// FilterMousePosition ///////////////////////////////////////////////////// // Tests the mouse point and decides whether we still want to accept input // based on the position. This allows us to act etheral (i.e. pass mouse // messages through) when the mouse is over an empty portion of the list. hsBool pfGUIListBoxMod::FilterMousePosition( hsPoint3 &mousePt ) { if( !HasFlag( kAllowMousePassThrough ) ) return true; Int32 hover = fCurrClick = IGetItemFromPoint( mousePt ); if( hover == -1 ) return false; return true; } //// HandleMouseDown ///////////////////////////////////////////////////////// // What we do: normal click deselects all and selects the item clicked on // (if any). Shift-click and ctrl-click avoids the deselect and toggles // the item clicked on. void pfGUIListBoxMod::HandleMouseDown( hsPoint3 &mousePt, UInt8 modifiers ) { Int32 i; int lastSelection = -1; if (HasFlag(kForbidNoSelection)) { // grab the last item we had selected just in case they clicked outside the list of selectable objects for (i=0; iIsSelected()) { lastSelection = i; break; } } if( HasFlag(kSingleSelect) || ( !( modifiers & ( pfGameGUIMgr::kShiftDown | pfGameGUIMgr::kCtrlDown ) ) && !HasFlag( kHandsOffMultiSelect ) ) ) { // Deselect everyone! for( i = 0; i < fElements.GetCount(); i++ ) fElements[ i ]->SetSelected( false ); fSingleSelElement = -1; } else if( modifiers & pfGameGUIMgr::kShiftDown ) { IFindSelectionRange( &fMinSel, &fMaxSel ); } fCurrHover = fCurrClick = IGetItemFromPoint( mousePt ); if( fCurrClick != -1 ) { if( ( ( modifiers & pfGameGUIMgr::kShiftDown ) && !HasFlag( kSingleSelect ) ) ) { // Select our new range, deselect everything outside if( fCurrClick <= fMaxSel ) { ISelectRange( 0, (Int8)(fCurrClick - 1), false ); ISelectRange( (Int8)fCurrClick, (Int8)fMaxSel, true ); ISelectRange( (Int8)(fMaxSel + 1), -1, false ); } else if( fCurrClick >= fMinSel ) { ISelectRange( 0, (Int8)(fMinSel - 1), false ); ISelectRange( (Int8)fMinSel, (Int8)fCurrClick, true ); ISelectRange( (Int8)(fCurrClick + 1), -1, false ); } fElements[ fCurrClick ]->SetSelected( true ); } else { fElements[ fCurrClick ]->SetSelected( true ); fSingleSelElement = fCurrClick; } } else { if (HasFlag(kForbidNoSelection) && (lastSelection != -1)) { fElements[ lastSelection ]->SetSelected(true); fSingleSelElement = lastSelection; } } // We want drag/up events to be processed with the modifiers we had on // mouse down, not the current ones. So store 'em for reference fModsAtDragTime = modifiers; fClicking = true; IUpdate(); } //// HandleMouseUp /////////////////////////////////////////////////////////// void pfGUIListBoxMod::HandleMouseUp( hsPoint3 &mousePt, UInt8 modifiers ) { fClicking = false; if( !( fModsAtDragTime & ( pfGameGUIMgr::kShiftDown | pfGameGUIMgr::kCtrlDown ) ) && !HasFlag( kHandsOffMultiSelect ) ) { // No modifiers--simply select whatever item we're on Int32 item = IGetItemFromPoint( mousePt ); if( item != fCurrClick ) { if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() ) fElements[ fCurrClick ]->SetSelected( false ); fCurrHover = fCurrClick = item; if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() ) fElements[ fCurrClick ]->SetSelected( true ); else if (HasFlag(kForbidNoSelection) && fCurrClick == -1) { fElements[fSingleSelElement]->SetSelected(true); fCurrClick = fSingleSelElement; } fCheckScroll = true; fSingleSelElement = fCurrClick; IUpdate(); } else { // We didn't change selection, so go ahead and pass the click on to // the item, in case it wants to do something if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() ) { hsPoint3 localPt = mousePt; IScreenToLocalPt( localPt ); UInt16 lX = (UInt16)(( localPt.fX * ( fDynTextMap->GetVisibleWidth() - 1 ) ) - fElementBounds[ fCurrClick ].fLeft); UInt16 lY = (UInt16)(( localPt.fY * ( fDynTextMap->GetVisibleHeight() - 1 ) )- fElementBounds[ fCurrClick ].fTop); if( fElements[ fCurrClick ]->MouseClicked( lX, lY ) ) { ICalcWrapStarts(); ICalcScrollRange(); IUpdate(); } } } } DoSomething(); // Change in selection triggers our DoSomething() call } //// HandleMouseHover //////////////////////////////////////////////////////// // Just updates our currHover item so we can update the mouse appropriately void pfGUIListBoxMod::HandleMouseHover( hsPoint3 &mousePt, UInt8 modifiers ) { fCurrHover = IGetItemFromPoint( mousePt ); } //// HandleMouseDrag ///////////////////////////////////////////////////////// void pfGUIListBoxMod::HandleMouseDrag( hsPoint3 &mousePt, UInt8 modifiers ) { int i; if( ( fModsAtDragTime & pfGameGUIMgr::kShiftDown && !HasFlag( kSingleSelect ) ) ) { // Select our new range, deselect everything outside if( fCurrClick <= fMaxSel ) { ISelectRange( 0, (Int8)(fCurrClick - 1), false ); ISelectRange( (Int8)fCurrClick, (Int8)fMaxSel, true ); ISelectRange( (Int8)(fMaxSel + 1), -1, false ); } else if( fCurrClick >= fMinSel ) { ISelectRange( 0, (Int8)(fMinSel - 1), false ); ISelectRange( (Int8)fMinSel, (Int8)fCurrClick, true ); ISelectRange( (Int8)(fCurrClick + 1), -1, false ); } IUpdate(); } else if( !( fModsAtDragTime & ( pfGameGUIMgr::kShiftDown | pfGameGUIMgr::kCtrlDown ) ) ) { // No modifiers--simply select whatever item we're on if( HasFlag( kDragAndDropCapable ) ) { // If we're drag and drop capable, then a mouse drag function results in // the dialog entering Drag Mode(tm). Basically, we tell our parent dialog // which items to drag and it'll run with the rest fDialog->ClearDragList(); for( i = 0; i < fElements.GetCount(); i++ ) { if( fElements[ i ]->IsSelected() ) fDialog->AddToDragList( fElements[ i ] ); } fDialog->EnterDragMode( this ); } else { Int32 item = IGetItemFromPoint( mousePt ); if( item != fCurrClick ) { if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() ) fElements[ fCurrClick ]->SetSelected( false ); fCurrHover = fCurrClick = item; if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() ) fElements[ fCurrClick ]->SetSelected( true ); fCheckScroll = true; fSingleSelElement = fCurrClick; IUpdate(); } } } } //// HandleMouseDblClick ///////////////////////////////////////////////////// void pfGUIListBoxMod::HandleMouseDblClick( hsPoint3 &mousePt, UInt8 modifiers ) { if( !HasFlag( kGrowLeavesAndProcessOxygen ) ) return; // We only care about double clicks if we make oxygen Int32 item = IGetItemFromPoint( mousePt ); if( item != -1 ) { if( fElements[ item ]->GetType() == pfGUIListElement::kTreeRoot ) { pfGUIListTreeRoot *root = (pfGUIListTreeRoot *)fElements[ item ]; root->ShowChildren( !root->IsShowingChildren() ); if( !fLocked ) { ICalcWrapStarts(); ICalcScrollRange(); IUpdate(); } } } } //// IGetItemFromPoint /////////////////////////////////////////////////////// Int32 pfGUIListBoxMod::IGetItemFromPoint( hsPoint3 &mousePt ) { if( !fBounds.IsInside( &mousePt ) ) return -1; hsPoint3 localPt = mousePt; // despite getting a ref to the point (why?) we do NOT want to modify it IScreenToLocalPt( localPt ); UInt32 i; Int16 clickItem, clickY = (Int16)( localPt.fY * ( fDynTextMap->GetVisibleHeight() - 1 ) ); Int16 clickX = (Int16)( localPt.fX * ( fDynTextMap->GetVisibleWidth() - 1 ) ); // We have a nice array that has the starting (top) Y's of each visible element. So we just // check against that. UInt32 startAt = 0; // make sure that we have a valid fScrollPos if ( fScrollPos != -1 ) { // make sure that there is an Idx at fScrollPos if ( fWrapStartIdxs.GetCount() > fScrollPos ) { startAt = fWrapStartIdxs[ fScrollPos ]; clickItem = -1; } } for( i = startAt; i < fElements.GetCount(); i++ ) { if( i fElements.GetCount() - 1 || clickItem < 0 ) clickItem = -1; return clickItem; } //// IFindSelectionRange ///////////////////////////////////////////////////// // Find the min and max item that's selected. Returns -1 for both if nobody // is selected void pfGUIListBoxMod::IFindSelectionRange( Int32 *min, Int32 *max ) { Int32 i; *min = *max = -1; for( i = 0; i < fElements.GetCount(); i++ ) { if( fElements[ i ]->IsSelected() ) { *min = i; break; } } for( i = fElements.GetCount() - 1; i >= 0; i-- ) { if( fElements[ i ]->IsSelected() ) { *max = i; break; } } } //// ISelectRange //////////////////////////////////////////////////////////// void pfGUIListBoxMod::ISelectRange( Int8 min, Int8 max, hsBool select ) { Int16 i; if( max == -1 ) max = fElements.GetCount() - 1; for( i = min; i <= max; i++ ) fElements[ i ]->SetSelected( select ); } //// SetSelection //////////////////////////////////////////////////////////// void pfGUIListBoxMod::SetSelection( Int32 item ) { if( HasFlag( kSingleSelect ) ) { // Easy, only one item selected if( fSingleSelElement != -1 ) fElements[ fSingleSelElement ]->SetSelected( false ); fSingleSelElement = item; } else ISelectRange( 0, -1, false ); if( item != -1 && item < fElements.GetCount() ) fElements[ item ]->SetSelected( true ); fCheckScroll = true; IUpdate(); } void pfGUIListBoxMod::RemoveSelection( Int32 item ) { // make sure the item is valid if ( item != -1 && item < fElements.GetCount() ) { // if single select and its what is selected if ( HasFlag( kSingleSelect) && fSingleSelElement == item ) { // then remove the selection and make nothing selected fElements[ fSingleSelElement ]->SetSelected( false ); fSingleSelElement = item; } // else if multiple selection else // just remove the selection fElements[ item ]->SetSelected( false ); } fCheckScroll = true; IUpdate(); } void pfGUIListBoxMod::AddSelection( Int32 item ) { // make sure the item is valid (can't add a non-selection!) if ( item != -1 && item < fElements.GetCount() ) { // if single select then just like SetSelection if ( HasFlag( kSingleSelect) ) { SetSelection(item); } // else if multiple selection else // just set its selection fElements[ item ]->SetSelected( true ); } fCheckScroll = true; IUpdate(); } //// HandleKeyPress ////////////////////////////////////////////////////////// hsBool pfGUIListBoxMod::HandleKeyPress( char key, UInt8 modifiers ) { return false; } hsBool pfGUIListBoxMod::HandleKeyEvent( pfGameGUIMgr::EventType event, plKeyDef key, UInt8 modifiers ) { if( key == KEY_CAPSLOCK ) return false; if( HasFlag( kDisableKeyActions ) ) return false; if( event == pfGameGUIMgr::kKeyDown || event == pfGameGUIMgr::kKeyRepeat ) { // Use arrow keys to do our dirty work if( key == KEY_UP ) { if( fCurrClick == -1 && fElements.GetCount() > 0 ) fCurrClick = 0; else while( fCurrClick > 0 ) { fCurrClick--; if( !HasFlag( kGrowLeavesAndProcessOxygen ) || !fElements[ fCurrClick ]->IsCollapsed() ) break; } } else if( key == KEY_DOWN ) { if( fCurrClick == -1 && fElements.GetCount() > 0 ) fCurrClick = fElements.GetCount() - 1; else while( fCurrClick < fElements.GetCount() - 1 ) { fCurrClick++; if( !HasFlag( kGrowLeavesAndProcessOxygen ) || !fElements[ fCurrClick ]->IsCollapsed() ) break; } } else if( key == KEY_HOME ) { if( fElements.GetCount() > 0 ) fCurrClick = 0; } else if( key == KEY_END ) { if( fElements.GetCount() > 0 ) fCurrClick = fElements.GetCount() - 1; } else if( key == KEY_ENTER && HasFlag( kGrowLeavesAndProcessOxygen ) ) { if( fCurrClick != -1 && fElements[ fCurrClick ]->GetType() == pfGUIListElement::kTreeRoot ) { pfGUIListTreeRoot *root = (pfGUIListTreeRoot *)fElements[ fCurrClick ]; root->ShowChildren( !root->IsShowingChildren() ); if( !fLocked ) { ICalcWrapStarts(); ICalcScrollRange(); } } } else { return false; } ISelectRange( 0, -1, false ); if( fCurrClick != -1 ) fElements[ fCurrClick ]->SetSelected( true ); fSingleSelElement = fCurrClick; DoSomething(); // Change in selection triggers our DoSomething() call fCheckScroll = true; IUpdate(); return true; } return false; } //// ScrollToEnd ///////////////////////////////////////////////////////////// void pfGUIListBoxMod::ScrollToEnd( void ) { if( fScrollControl != nil ) { fScrollPos = (int)fScrollControl->GetMin(); fScrollControl->SetCurrValue( fScrollControl->GetMax() ); } IUpdate(); } //// ScrollToBegin /////////////////////////////////////////////////////////// void pfGUIListBoxMod::ScrollToBegin( void ) { if( fScrollControl != nil ) { fScrollPos = (int)fScrollControl->GetMax(); fScrollControl->SetCurrValue( fScrollControl->GetMin() ); } IUpdate(); } void pfGUIListBoxMod::SetColorScheme( pfGUIColorScheme *newScheme ) { UInt16 i; pfGUIControlMod::SetColorScheme( newScheme ); for( i = 0; i < fElements.GetCount(); i++ ) { fElements[ i ]->SetColorScheme( newScheme ); fElements[ i ]->SetSkin( fSkin ); } } void pfGUIListBoxMod::SetScrollPos( Int32 pos ) { if ( pos >= (int)fScrollControl->GetMin() && pos <= (int)fScrollControl->GetMax() ) { fScrollControl->SetCurrValue( (hsScalar)pos ); fScrollPos = (int)fScrollControl->GetMax() - pos; } IUpdate(); } Int32 pfGUIListBoxMod::GetScrollPos( void ) { return (int)fScrollControl->GetCurrValue(); } Int32 pfGUIListBoxMod::GetScrollRange( void ) { return (int)fScrollControl->GetMax() - (int)fScrollControl->GetMin(); } ////////////////////////////////////////////////////////////////////////////// //// Element Manipulation //////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// UInt16 pfGUIListBoxMod::AddElement( pfGUIListElement *el ) { UInt16 idx = fElements.GetCount(); fElements.Append( el ); el->SetColorScheme( GetColorScheme() ); el->SetSkin( fSkin ); if( !fLocked ) { ICalcWrapStarts(); ICalcScrollRange(); IUpdate(); HandleExtendedEvent( pfGUIListBoxMod::kItemAdded ); } return idx; } void pfGUIListBoxMod::RemoveElement( UInt16 index ) { // Make sure no other elements care about this one UInt16 i, j; for( i = 0; i < fElements.GetCount(); i++ ) { if( fElements[ i ]->GetType() == pfGUIListElement::kTreeRoot ) { pfGUIListTreeRoot *root = (pfGUIListTreeRoot *)fElements[ i ]; for( j = 0; j < root->GetNumChildren(); ) { if( root->GetChild( j ) == fElements[ index ] ) root->RemoveChild( j ); else j++; } } } delete fElements[ index ]; fElements.Remove( index ); if( index == fSingleSelElement ) fSingleSelElement = -1; else if( index < fSingleSelElement ) fSingleSelElement--; fCurrHover = fCurrClick = -1; if( !fLocked ) { ICalcWrapStarts(); ICalcScrollRange(); IUpdate(); HandleExtendedEvent( pfGUIListBoxMod::kItemRemoved ); } } Int16 pfGUIListBoxMod::FindElement( pfGUIListElement *toCompareTo ) { int i; for( i = 0; i < fElements.GetCount(); i++ ) { if( fElements[ i ]->CompareTo( toCompareTo ) == 0 ) return i; } return (Int16)-1; } void pfGUIListBoxMod::ClearAllElements( void ) { int i; for( i = 0; i < fElements.GetCount(); i++ ) delete fElements[ i ]; fElements.Reset(); fSingleSelElement = -1; if( !fLocked ) { ICalcWrapStarts(); ICalcScrollRange(); IUpdate(); } HandleExtendedEvent( pfGUIListBoxMod::kListCleared ); } UInt16 pfGUIListBoxMod::AddString( const char *string ) { return AddElement( TRACKED_NEW pfGUIListText( string ) ); } UInt16 pfGUIListBoxMod::AddString( const wchar_t *string ) { return AddElement( TRACKED_NEW pfGUIListText( string ) ); } Int16 pfGUIListBoxMod::FindString( const char *toCompareTo ) { pfGUIListText text( toCompareTo ); return FindElement( &text ); } Int16 pfGUIListBoxMod::FindString( const wchar_t *toCompareTo ) { pfGUIListText text( toCompareTo ); return FindElement( &text ); } UInt16 pfGUIListBoxMod::GetNumElements( void ) { return fElements.GetCount(); } pfGUIListElement *pfGUIListBoxMod::GetElement( UInt16 idx ) { return fElements[ idx ]; } void pfGUIListBoxMod::LockList( void ) { fLocked = true; } void pfGUIListBoxMod::UnlockList( void ) { fLocked = false; ICalcWrapStarts(); ICalcScrollRange(); IUpdate(); } //// IGetDesiredCursor /////////////////////////////////////////////////////// UInt32 pfGUIListBoxMod::IGetDesiredCursor( void ) const { if( fCurrHover == -1 ) return plInputInterface::kNullCursor; if( fClicking ) return plInputInterface::kCursorClicked; return plInputInterface::kCursorPoised; }