You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1216 lines
32 KiB

/*==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 <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
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 )
{
pfGUIColorScheme *scheme = GetColorScheme();
fDynTextMap->SetFont( scheme->fFontFace, scheme->fFontSize, scheme->fFontFlags,
!HasFlag( kXparentBgnd ));
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; i<fElements.GetCount(); i++)
if (fElements[i]->IsSelected())
{
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<fElementBounds.GetCount() && fElementBounds[ i ].Contains( clickX, clickY ) )
{
clickItem = (Int16)i;
break;
}
}
if( clickItem > 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;
}