|
|
|
|
/*==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==*/
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// //
|
|
|
|
|
// pfGUIMultiLineEditCtrl Definition //
|
|
|
|
|
// //
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
#include "hsTypes.h"
|
|
|
|
|
#include "pfGUIMultiLineEditCtrl.h"
|
|
|
|
|
#include "pfGameGUIMgr.h"
|
|
|
|
|
#include "pfGUIUpDownPairMod.h"
|
|
|
|
|
#include "pfGUIControlHandlers.h"
|
|
|
|
|
#include "pfGUIDialogMod.h"
|
|
|
|
|
#include "pfGUIDialogHandlers.h"
|
|
|
|
|
|
|
|
|
|
#include "../pnMessage/plRefMsg.h"
|
|
|
|
|
#include "../pfMessage/pfGameGUIMsg.h"
|
|
|
|
|
#include "../plMessage/plAnimCmdMsg.h"
|
|
|
|
|
#include "../plAvatar/plAGModifier.h"
|
|
|
|
|
#include "../plGImage/plDynamicTextMap.h"
|
|
|
|
|
#include "plgDispatch.h"
|
|
|
|
|
#include "hsResMgr.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//// Tiny Helper Class ///////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
class plStringSlicer
|
|
|
|
|
{
|
|
|
|
|
wchar_t *fString;
|
|
|
|
|
wchar_t fTempChar;
|
|
|
|
|
UInt32 fStart, fEnd;
|
|
|
|
|
|
|
|
|
|
typedef wchar_t *CharPtr;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
plStringSlicer( wchar_t *string, UInt32 start, UInt32 end )
|
|
|
|
|
{
|
|
|
|
|
fString = string;
|
|
|
|
|
fTempChar = string[ end ];
|
|
|
|
|
string[ end ] = 0L;
|
|
|
|
|
fStart = start;
|
|
|
|
|
fEnd = end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plStringSlicer( hsTArray<wchar_t> &string, UInt32 start, UInt32 end )
|
|
|
|
|
{
|
|
|
|
|
fString = string.AcquireArray();
|
|
|
|
|
fStart = start;
|
|
|
|
|
if( end < string.GetCount() )
|
|
|
|
|
fEnd = end;
|
|
|
|
|
else
|
|
|
|
|
fEnd = fStart;
|
|
|
|
|
|
|
|
|
|
if( fEnd > fStart )
|
|
|
|
|
{
|
|
|
|
|
fTempChar = fString[ end ];
|
|
|
|
|
fString[ end ] = 0L;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~plStringSlicer()
|
|
|
|
|
{
|
|
|
|
|
if( fEnd > fStart )
|
|
|
|
|
fString[ fEnd ] = fTempChar;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
operator const CharPtr() const
|
|
|
|
|
{
|
|
|
|
|
return &fString[ fStart ];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//// Wee Little Control Proc for scrolling ///////////////////////////////////
|
|
|
|
|
|
|
|
|
|
class pfMLScrollProc : public pfGUICtrlProcObject
|
|
|
|
|
{
|
|
|
|
|
protected:
|
|
|
|
|
|
|
|
|
|
pfGUIMultiLineEditCtrl *fParent;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
pfMLScrollProc( pfGUIMultiLineEditCtrl *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();
|
|
|
|
|
fParent->SetScrollPosition( newScrollPos );
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//// Statics /////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
wchar_t pfGUIMultiLineEditCtrl::fColorCodeChar = (wchar_t)1;
|
|
|
|
|
wchar_t pfGUIMultiLineEditCtrl::fStyleCodeChar = (wchar_t)2;
|
|
|
|
|
UInt32 pfGUIMultiLineEditCtrl::fColorCodeSize = (wchar_t)5;
|
|
|
|
|
UInt32 pfGUIMultiLineEditCtrl::fStyleCodeSize = (wchar_t)3;
|
|
|
|
|
|
|
|
|
|
//// Constructor/Destructor //////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
pfGUIMultiLineEditCtrl::pfGUIMultiLineEditCtrl()
|
|
|
|
|
{
|
|
|
|
|
SetFlag( kWantsInterest );
|
|
|
|
|
SetFlag( kTakesSpecialKeys );
|
|
|
|
|
fCursorPos = 0;
|
|
|
|
|
fLastCursorLine = 0;
|
|
|
|
|
fBuffer.Append( 0L );
|
|
|
|
|
fBufferLimit = -1;
|
|
|
|
|
fIgnoreNextKey = false;
|
|
|
|
|
fScrollControl = nil;
|
|
|
|
|
fScrollProc = nil;
|
|
|
|
|
fScrollPos = 0;
|
|
|
|
|
fReadyToRender = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fLastKeyModifiers = 0;
|
|
|
|
|
fLastKeyPressed = 0;
|
|
|
|
|
fLockCount = 0;
|
|
|
|
|
|
|
|
|
|
fLastDeadKey = 0;
|
|
|
|
|
SetupDeadKeyConverter();
|
|
|
|
|
|
|
|
|
|
fNextCtrl = nil;
|
|
|
|
|
fPrevCtrl = nil;
|
|
|
|
|
|
|
|
|
|
fEventProc = nil;
|
|
|
|
|
|
|
|
|
|
fTopMargin = fLeftMargin = 0;
|
|
|
|
|
fBottomMargin = fRightMargin = 0;
|
|
|
|
|
|
|
|
|
|
fFontFace = "";
|
|
|
|
|
fFontColor.FromARGB32(0xFF000000);
|
|
|
|
|
fFontSize = 0;
|
|
|
|
|
fFontStyle = 0;
|
|
|
|
|
fFontFlagsSet = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pfGUIMultiLineEditCtrl::~pfGUIMultiLineEditCtrl()
|
|
|
|
|
{
|
|
|
|
|
ClearNext(); // make sure that no one is referencing us
|
|
|
|
|
ClearPrev();
|
|
|
|
|
|
|
|
|
|
if( fScrollProc && fScrollProc->DecRef() )
|
|
|
|
|
delete fScrollProc;
|
|
|
|
|
if (fEventProc)
|
|
|
|
|
delete fEventProc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetupDeadKeyConverter()
|
|
|
|
|
{
|
|
|
|
|
int i,j;
|
|
|
|
|
for (i=0; i<255; i++)
|
|
|
|
|
for (j=0; j<255; j++)
|
|
|
|
|
fDeadKeyConverter[i][j] = 0L;
|
|
|
|
|
|
|
|
|
|
// we are adding 100 to the indexes because some of these chars have a negative index for some reason
|
|
|
|
|
fDeadKeyConverter['^'+100]['a'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['e'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['i'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['o'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['u'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['A'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['E'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['I'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['O'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['^'+100]['U'] = L'<EFBFBD>';
|
|
|
|
|
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['a'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['e'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['i'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['o'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['u'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['A'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['E'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['I'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['O'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['U'] = L'<EFBFBD>';
|
|
|
|
|
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['a'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['e'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['i'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['o'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['u'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['y'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['A'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['E'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['I'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['O'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['U'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['<EFBFBD>'+100]['Y'] = L'<EFBFBD>';
|
|
|
|
|
|
|
|
|
|
fDeadKeyConverter['`'+100]['a'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['e'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['i'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['o'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['u'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['A'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['E'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['I'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['O'] = L'<EFBFBD>';
|
|
|
|
|
fDeadKeyConverter['`'+100]['U'] = L'<EFBFBD>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IEval ///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::IEval( double secs, hsScalar del, UInt32 dirty )
|
|
|
|
|
{
|
|
|
|
|
return pfGUIControlMod::IEval( secs, del, dirty );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// MsgReceive //////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::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 );
|
|
|
|
|
IUpdateScrollRange();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
fScrollControl = nil;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pfGUIControlMod::MsgReceive( msg );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// SetScrollPosition ///////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetScrollPosition( Int32 topLine )
|
|
|
|
|
{
|
|
|
|
|
if( topLine < 0 )
|
|
|
|
|
topLine = 0;
|
|
|
|
|
else if( topLine > fLineStarts.GetCount() - ICalcNumVisibleLines() + 1 )
|
|
|
|
|
topLine = fLineStarts.GetCount() - ICalcNumVisibleLines() + 1;
|
|
|
|
|
|
|
|
|
|
if( fScrollPos == topLine )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (topLine >= 0)
|
|
|
|
|
fScrollPos = topLine;
|
|
|
|
|
else
|
|
|
|
|
fScrollPos = 0;
|
|
|
|
|
|
|
|
|
|
IUpdate();
|
|
|
|
|
|
|
|
|
|
if( fScrollControl != nil )
|
|
|
|
|
// Scroll control values are reversed
|
|
|
|
|
fScrollControl->SetCurrValue( fScrollControl->GetMax() - (hsScalar)fScrollPos );
|
|
|
|
|
|
|
|
|
|
HandleExtendedEvent( pfGUIMultiLineEditCtrl::kScrollPosChanged );
|
|
|
|
|
|
|
|
|
|
// notify thru the dialog something has changed
|
|
|
|
|
if (fDialog && fDialog->GetHandler())
|
|
|
|
|
fDialog->GetHandler()->DoSomething(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// MoveCursor - by direction command////////////////////////////////////////////////
|
|
|
|
|
void pfGUIMultiLineEditCtrl::MoveCursor( Direction dir )
|
|
|
|
|
{
|
|
|
|
|
IMoveCursor(dir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//// IUpdateScrollRange //////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IUpdateScrollRange( void )
|
|
|
|
|
{
|
|
|
|
|
if( fScrollControl == nil )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if( fLineStarts.GetCount() > ICalcNumVisibleLines() - 1 )
|
|
|
|
|
{
|
|
|
|
|
// +1 here because the last visible line is only a partial, but we want to be able to view
|
|
|
|
|
// full lines all the way to the end.
|
|
|
|
|
hsScalar newMax = (hsScalar)( fLineStarts.GetCount() - ICalcNumVisibleLines() + 1 );
|
|
|
|
|
|
|
|
|
|
if( newMax != fScrollControl->GetMax() )
|
|
|
|
|
{
|
|
|
|
|
fScrollControl->SetRange( 0, (hsScalar)(fLineStarts.GetCount() - ICalcNumVisibleLines() + 1) );
|
|
|
|
|
fScrollControl->SetEnabled( true );
|
|
|
|
|
if( fScrollPos > fLineStarts.GetCount() - ICalcNumVisibleLines() + 1 )
|
|
|
|
|
{
|
|
|
|
|
fScrollPos = fLineStarts.GetCount() - ICalcNumVisibleLines() + 1;
|
|
|
|
|
fScrollControl->SetCurrValue( fScrollControl->GetMax() - (hsScalar)fScrollPos );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All bets are off on scrolling, so refresh the whole area
|
|
|
|
|
IUpdate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if( fScrollControl->GetMax() > 0 )
|
|
|
|
|
{
|
|
|
|
|
fScrollControl->SetRange( 0, 0 );
|
|
|
|
|
fScrollControl->SetEnabled( false );
|
|
|
|
|
fScrollPos = 0;
|
|
|
|
|
IUpdate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetScrollEnable( hsBool state )
|
|
|
|
|
{
|
|
|
|
|
if (fScrollControl == nil )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if ( state )
|
|
|
|
|
{
|
|
|
|
|
IUpdateScrollRange();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fScrollControl->SetRange( 0, 0 );
|
|
|
|
|
fScrollControl->SetEnabled( false );
|
|
|
|
|
fScrollPos = 0;
|
|
|
|
|
IUpdate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IPostSetUpDynTextMap ////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IPostSetUpDynTextMap( void )
|
|
|
|
|
{
|
|
|
|
|
pfGUIColorScheme *scheme = GetColorScheme();
|
|
|
|
|
|
|
|
|
|
// fill in any blanks
|
|
|
|
|
if (!(fFontFlagsSet & kFontFaceSet))
|
|
|
|
|
{
|
|
|
|
|
fFontFace = scheme->fFontFace;
|
|
|
|
|
fFontFlagsSet |= kFontFaceSet;
|
|
|
|
|
}
|
|
|
|
|
if (!(fFontFlagsSet & kFontColorSet))
|
|
|
|
|
{
|
|
|
|
|
fFontColor = scheme->fForeColor;
|
|
|
|
|
fFontFlagsSet |= kFontColorSet;
|
|
|
|
|
}
|
|
|
|
|
if (!(fFontFlagsSet & kFontSizeSet))
|
|
|
|
|
{
|
|
|
|
|
fFontSize = scheme->fFontSize;
|
|
|
|
|
fFontFlagsSet |= kFontSizeSet;
|
|
|
|
|
}
|
|
|
|
|
if (!(fFontFlagsSet & kFontStyleSet))
|
|
|
|
|
{
|
|
|
|
|
fFontStyle = scheme->fFontFlags;
|
|
|
|
|
fFontFlagsSet |= kFontStyleSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fDynTextMap->SetFont( fFontFace.c_str(), fFontSize, fFontStyle,
|
|
|
|
|
HasFlag( kXparentBgnd ) ? false : true );
|
|
|
|
|
|
|
|
|
|
// Calculate a height for each line
|
|
|
|
|
fDynTextMap->CalcStringWidth( L"The quick brown fox jumped over the lazy dog.", &fLineHeight );
|
|
|
|
|
fCalcedFontSize = fFontSize;
|
|
|
|
|
|
|
|
|
|
fReadyToRender = true;
|
|
|
|
|
IRecalcLineStarts( 0, true );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// ICalcNumVisibleLines ////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
Int32 pfGUIMultiLineEditCtrl::ICalcNumVisibleLines( void ) const
|
|
|
|
|
{
|
|
|
|
|
if (fDynTextMap == nil || fLineHeight == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
Int32 numLines = 0;
|
|
|
|
|
numLines = (fDynTextMap->GetVisibleHeight() + fLineHeight - (fTopMargin+fBottomMargin+1))/fLineHeight;
|
|
|
|
|
return numLines;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IUpdate /////////////////////////////////////////////////////////////////
|
|
|
|
|
// Ranged version
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IUpdate( Int32 startLine, Int32 endLine )
|
|
|
|
|
{
|
|
|
|
|
hsColorRGBA c;
|
|
|
|
|
static int testingFlip = 0;
|
|
|
|
|
bool clearEachLine = true;
|
|
|
|
|
UInt32 line, x, y = 0;
|
|
|
|
|
Int32 numVisibleLines, lastVisibleLine;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( !fReadyToRender )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Detect whether we need to recalc all of our dimensions entirely
|
|
|
|
|
if( fFontFlagsSet & (kFontFaceSet & kFontColorSet & kFontSizeSet & kFontStyleSet) )
|
|
|
|
|
IPostSetUpDynTextMap();
|
|
|
|
|
|
|
|
|
|
if( fLineStarts.GetCount() == 0 )
|
|
|
|
|
{
|
|
|
|
|
// Just clear and go away
|
|
|
|
|
fDynTextMap->ClearToColor( GetColorScheme()->fBackColor );
|
|
|
|
|
if( IsFocused() )
|
|
|
|
|
fDynTextMap->FrameRect( fLeftMargin, fTopMargin, 2, fLineHeight, GetColorScheme()->fSelForeColor );
|
|
|
|
|
fDynTextMap->FlushToHost();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Recalculate the visible range due to our visible area and the scroll range.
|
|
|
|
|
// adjust the scroll position if we are linked
|
|
|
|
|
if (fPrevCtrl) // the first control (which has a nil fPrevCtrl) shouldn't adjust it's scroll pos because the app may want it to start there
|
|
|
|
|
{
|
|
|
|
|
IUpdateBuffer(); // make sure we are rendering the correct text
|
|
|
|
|
fScrollPos = GetFirstVisibleLine();
|
|
|
|
|
}
|
|
|
|
|
numVisibleLines = ICalcNumVisibleLines();
|
|
|
|
|
if (fNextCtrl || fPrevCtrl)
|
|
|
|
|
numVisibleLines--; // we don't want "partially visible" lines
|
|
|
|
|
lastVisibleLine = fScrollPos + numVisibleLines - 1;
|
|
|
|
|
if( lastVisibleLine > fLineStarts.GetCount() - 1 )
|
|
|
|
|
lastVisibleLine = fLineStarts.GetCount() - 1;
|
|
|
|
|
|
|
|
|
|
if( startLine < fScrollPos )
|
|
|
|
|
startLine = fScrollPos;
|
|
|
|
|
if( endLine > lastVisibleLine )
|
|
|
|
|
endLine = lastVisibleLine;
|
|
|
|
|
|
|
|
|
|
if( startLine == fScrollPos && endLine == lastVisibleLine )
|
|
|
|
|
{
|
|
|
|
|
c.Set( 0.f, 0.f, 0.f, 1.f );
|
|
|
|
|
fDynTextMap->ClearToColor( GetColorScheme()->fBackColor );
|
|
|
|
|
clearEachLine = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start at our line
|
|
|
|
|
y = ( startLine - fScrollPos ) * fLineHeight + fTopMargin;
|
|
|
|
|
// And loop!
|
|
|
|
|
|
|
|
|
|
for( line = startLine; line <= endLine; line++ )
|
|
|
|
|
{
|
|
|
|
|
// Clear this line
|
|
|
|
|
if( clearEachLine )
|
|
|
|
|
{
|
|
|
|
|
fDynTextMap->FillRect( 0, (UInt16)y, fDynTextMap->GetVisibleWidth(), fLineHeight,
|
|
|
|
|
GetColorScheme()->fBackColor );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UInt32 start = fLineStarts[ line ], end;
|
|
|
|
|
if( line == fLineStarts.GetCount() - 1 )
|
|
|
|
|
end = fBuffer.GetCount();
|
|
|
|
|
else
|
|
|
|
|
end = fLineStarts[ line + 1 ];
|
|
|
|
|
|
|
|
|
|
// Render the actual text
|
|
|
|
|
IRenderLine( fLeftMargin, (UInt16)y, start, end );
|
|
|
|
|
|
|
|
|
|
// Render the cursor
|
|
|
|
|
if( fCursorPos >= start && fCursorPos < end && IsFocused() )
|
|
|
|
|
{
|
|
|
|
|
if( fCursorPos > start )
|
|
|
|
|
x = IRenderLine( fLeftMargin, (UInt16)y, start, fCursorPos, true );
|
|
|
|
|
else
|
|
|
|
|
x = fLeftMargin;
|
|
|
|
|
|
|
|
|
|
fDynTextMap->FrameRect( (UInt16)x, (UInt16)y, 2, fLineHeight, GetColorScheme()->fSelForeColor );
|
|
|
|
|
|
|
|
|
|
// Store the cursor X,Y pair. Go figure, the ONLY time we actually need this is
|
|
|
|
|
// to move up or down one line, and even then it's only because we want to keep
|
|
|
|
|
// the same approximate horizontal position (versus same character offset)
|
|
|
|
|
fCurrCursorX = (UInt16)x;
|
|
|
|
|
fCurrCursorY = (UInt16)y;
|
|
|
|
|
}
|
|
|
|
|
y += fLineHeight;
|
|
|
|
|
}
|
|
|
|
|
if( clearEachLine && line >= fLineStarts.GetCount() && y < fDynTextMap->GetVisibleHeight()-fBottomMargin )
|
|
|
|
|
{
|
|
|
|
|
// No lines left, so clear the rest of the visible area
|
|
|
|
|
fDynTextMap->FillRect( 0, (UInt16)y, fDynTextMap->GetVisibleWidth(), (UInt16)(fDynTextMap->GetVisibleHeight() - y),
|
|
|
|
|
GetColorScheme()->fBackColor );
|
|
|
|
|
}
|
|
|
|
|
fDynTextMap->FlushToHost();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IReadColorCode //////////////////////////////////////////////////////////
|
|
|
|
|
// Reads a color code from the given position and advances the given position
|
|
|
|
|
// appropriately
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IReadColorCode( Int32 &pos, hsColorRGBA &color ) const
|
|
|
|
|
{
|
|
|
|
|
UInt16 *buffer = (UInt16 *)fBuffer.AcquireArray() + pos;
|
|
|
|
|
UInt8 r, g, b;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hsAssert( buffer[ 0 ] == fColorCodeChar, "Invalid position in IReadColorCode()" );
|
|
|
|
|
buffer++;
|
|
|
|
|
r = (UInt8)buffer[ 0 ];
|
|
|
|
|
g = (UInt8)buffer[ 1 ];
|
|
|
|
|
b = (UInt8)buffer[ 2 ];
|
|
|
|
|
pos += fColorCodeSize; // We have a duplicate code at the end of this block, for searching backwards
|
|
|
|
|
color.Set( r / 255.f, g / 255.f, b / 255.f, fFontColor.a );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IReadStyleCode //////////////////////////////////////////////////////////
|
|
|
|
|
// Reads a style code from the given position and advances the given position
|
|
|
|
|
// appropriately
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IReadStyleCode( Int32 &pos, UInt8 &fontFlags ) const
|
|
|
|
|
{
|
|
|
|
|
UInt16 *buffer = (UInt16 *)fBuffer.AcquireArray() + pos;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hsAssert( buffer[ 0 ] == fStyleCodeChar, "Invalid position in IReadStyleCode()" );
|
|
|
|
|
fontFlags = (UInt8)buffer[ 1 ];
|
|
|
|
|
pos += fStyleCodeSize; // We have a duplicate code at the end of this block, for searching backwards
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool pfGUIMultiLineEditCtrl::IIsRenderable( const wchar_t c )
|
|
|
|
|
{
|
|
|
|
|
return ( !IIsCodeChar( c ) && c != L'\n' && c != L'\t' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool pfGUIMultiLineEditCtrl::IIsCodeChar( const wchar_t c )
|
|
|
|
|
{
|
|
|
|
|
return ( c == fColorCodeChar || c == fStyleCodeChar );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IFindLastCode Functions /////////////////////////////////////////////////
|
|
|
|
|
// Given a position, these functions start at that position and work
|
|
|
|
|
// backward until they find their respective code type, then return that code
|
|
|
|
|
// type. If none is found, they set the given parameter to the default value
|
|
|
|
|
// and return false.
|
|
|
|
|
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::IFindLastColorCode( Int32 pos, hsColorRGBA &color, hsBool ignoreFirstCharacter ) const
|
|
|
|
|
{
|
|
|
|
|
for( ; pos >= 0; pos -= IOffsetToNextCharFromPos( pos - 1 ) )
|
|
|
|
|
{
|
|
|
|
|
if( fBuffer[ pos ] == fColorCodeChar && !ignoreFirstCharacter )
|
|
|
|
|
{
|
|
|
|
|
IReadColorCode( pos, color );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
ignoreFirstCharacter = false;
|
|
|
|
|
}
|
|
|
|
|
color = fFontColor; // use our default color
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::IFindLastStyleCode( Int32 pos, UInt8 &style, hsBool ignoreFirstCharacter ) const
|
|
|
|
|
{
|
|
|
|
|
for( ; pos >= 0; pos -= IOffsetToNextCharFromPos( pos - 1 ) )
|
|
|
|
|
{
|
|
|
|
|
if( fBuffer[ pos ] == fStyleCodeChar && !ignoreFirstCharacter )
|
|
|
|
|
{
|
|
|
|
|
IReadStyleCode( pos, style );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
ignoreFirstCharacter = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
style = fFontStyle; // use our default style
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IRenderLine /////////////////////////////////////////////////////////////
|
|
|
|
|
// Renders a null-terminated string to the dynamic text map at the location
|
|
|
|
|
// given. Takes into account style codes and special characters (like returns
|
|
|
|
|
// and tabs). Returns the final X value after rendering.
|
|
|
|
|
|
|
|
|
|
UInt32 pfGUIMultiLineEditCtrl::IRenderLine( UInt16 x, UInt16 y, Int32 start, Int32 end, hsBool dontRender )
|
|
|
|
|
{
|
|
|
|
|
Int32 pos;
|
|
|
|
|
hsColorRGBA currColor = fFontColor;
|
|
|
|
|
UInt8 currStyle;
|
|
|
|
|
const wchar_t *buffer = fBuffer.AcquireArray();
|
|
|
|
|
|
|
|
|
|
// First, gotta go back from our starting position and find a color and style code to use
|
|
|
|
|
IFindLastColorCode( start, currColor );
|
|
|
|
|
IFindLastStyleCode( start, currStyle );
|
|
|
|
|
|
|
|
|
|
fDynTextMap->SetTextColor( currColor, HasFlag( kXparentBgnd ) ? true : false );
|
|
|
|
|
fDynTextMap->SetFont( fFontFace.c_str(), fFontSize, GetColorScheme()->fFontFlags | currStyle,
|
|
|
|
|
HasFlag( kXparentBgnd ) ? false : true );
|
|
|
|
|
|
|
|
|
|
// Now, start from our start and go to the end and keep eating up as many chunks
|
|
|
|
|
// that aren't made up of control codes or carriage returns or tabs (since we render
|
|
|
|
|
// those separately)
|
|
|
|
|
for( pos = start; pos < end; )
|
|
|
|
|
{
|
|
|
|
|
start = pos;
|
|
|
|
|
if( IIsRenderable( buffer[ pos ] ) )
|
|
|
|
|
{
|
|
|
|
|
// State 1: Render a renderable chunk
|
|
|
|
|
while( pos < end && IIsRenderable( buffer[ pos ] ) )
|
|
|
|
|
pos++;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
plStringSlicer slicer( fBuffer, start, pos );
|
|
|
|
|
if( !dontRender )
|
|
|
|
|
fDynTextMap->DrawString( x, y, slicer );
|
|
|
|
|
x += fDynTextMap->CalcStringWidth( slicer );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// State 2: Process non-renderable characters
|
|
|
|
|
if( buffer[ pos ] == fColorCodeChar )
|
|
|
|
|
{
|
|
|
|
|
// Read color and switch to that one
|
|
|
|
|
IReadColorCode( pos, currColor );
|
|
|
|
|
if( !dontRender )
|
|
|
|
|
fDynTextMap->SetTextColor( currColor, HasFlag( kXparentBgnd ) ? true : false );
|
|
|
|
|
}
|
|
|
|
|
else if( buffer[ pos ] == fStyleCodeChar )
|
|
|
|
|
{
|
|
|
|
|
// Read style and switch to that one
|
|
|
|
|
IReadStyleCode( pos, currStyle );
|
|
|
|
|
if( !dontRender )
|
|
|
|
|
fDynTextMap->SetFont( fFontFace.c_str(), fFontSize , GetColorScheme()->fFontFlags | currStyle,
|
|
|
|
|
HasFlag( kXparentBgnd ) ? false : true );
|
|
|
|
|
}
|
|
|
|
|
else if( buffer[ pos ] == L'\n' )
|
|
|
|
|
{
|
|
|
|
|
// We stop at carriage returns
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if( buffer[ pos ] == L'\t' )
|
|
|
|
|
{
|
|
|
|
|
// Increment by tab amount
|
|
|
|
|
x += 16;
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
hsAssert( false, "Unknown unrenderable character, ignoring" );
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::PurgeDynaTextMapImage()
|
|
|
|
|
{
|
|
|
|
|
if ( fDynTextMap != nil )
|
|
|
|
|
fDynTextMap->PurgeImage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IUpdate /////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IUpdate( void )
|
|
|
|
|
{
|
|
|
|
|
// Just call the ranged one with a full range
|
|
|
|
|
IUpdate( 0, fLineStarts.GetCount() - 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// Read/Write //////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::Read( hsStream *s, hsResMgr *mgr )
|
|
|
|
|
{
|
|
|
|
|
pfGUIControlMod::Read(s, mgr);
|
|
|
|
|
|
|
|
|
|
fScrollControl = nil;
|
|
|
|
|
if( s->ReadBool() )
|
|
|
|
|
{
|
|
|
|
|
fScrollProc = TRACKED_NEW pfMLScrollProc( this );
|
|
|
|
|
fScrollProc->IncRef();
|
|
|
|
|
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefScrollCtrl ), plRefFlags::kActiveRef );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::Write( hsStream *s, hsResMgr *mgr )
|
|
|
|
|
{
|
|
|
|
|
pfGUIControlMod::Write( s, mgr );
|
|
|
|
|
|
|
|
|
|
if( fScrollControl != nil )
|
|
|
|
|
{
|
|
|
|
|
s->WriteBool( true );
|
|
|
|
|
mgr->WriteKey( s, fScrollControl->GetKey() );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
s->WriteBool( false );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IPointToPosition ////////////////////////////////////////////////////////
|
|
|
|
|
// Translates a 2D point on the visible texture surface to a cursor position.
|
|
|
|
|
|
|
|
|
|
Int32 pfGUIMultiLineEditCtrl::IPointToPosition( Int16 ptX, Int16 ptY, hsBool searchOutsideBounds )
|
|
|
|
|
{
|
|
|
|
|
// Find our line
|
|
|
|
|
Int32 line, start, pos, end, lastVisibleLine;
|
|
|
|
|
Int16 x, y;
|
|
|
|
|
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
fScrollPos = GetFirstVisibleLine(); // update the scroll position if we are linked
|
|
|
|
|
|
|
|
|
|
if( searchOutsideBounds )
|
|
|
|
|
lastVisibleLine = fLineStarts.GetCount() - 1;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
lastVisibleLine = fScrollPos + ICalcNumVisibleLines() - 1;
|
|
|
|
|
if( lastVisibleLine > fLineStarts.GetCount() - 1 )
|
|
|
|
|
lastVisibleLine = fLineStarts.GetCount() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
line = searchOutsideBounds ? 0 : fScrollPos;
|
|
|
|
|
y = (Int16)(-( fScrollPos - line ) * fLineHeight);
|
|
|
|
|
y += fTopMargin;
|
|
|
|
|
for( ; line < lastVisibleLine; line++, y += fLineHeight )
|
|
|
|
|
{
|
|
|
|
|
if( ptY >= y && ptY < y + fLineHeight )
|
|
|
|
|
{
|
|
|
|
|
if (line < 0)
|
|
|
|
|
break; // abort, and yes, this IS possible with this crappy code
|
|
|
|
|
|
|
|
|
|
// Found the line, figure out what character
|
|
|
|
|
start = fLineStarts[ line ];
|
|
|
|
|
end = ( line == fLineStarts.GetCount() - 1 ) ? fBuffer.GetCount() - 1 : fLineStarts[ line + 1 ];
|
|
|
|
|
|
|
|
|
|
for( pos = start; pos < end; pos++ )
|
|
|
|
|
{
|
|
|
|
|
x = (Int16)IRenderLine( fLeftMargin, 0, start, pos, true );
|
|
|
|
|
if( x > ptX )
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Go figure, our test puts it 1 past no matter what :)
|
|
|
|
|
return pos - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Just put us at the end of the last line
|
|
|
|
|
return fBuffer.GetCount() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IIsWordBreaker //////////////////////////////////////////////////////////
|
|
|
|
|
// Returns whether the given character is one that can break a line
|
|
|
|
|
|
|
|
|
|
inline bool IIsWordBreaker( const wchar_t c )
|
|
|
|
|
{
|
|
|
|
|
return ( wcschr( L" \t,.;\n", c ) != nil ) ? true : false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IOffsetToNextChar ///////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
inline Int32 pfGUIMultiLineEditCtrl::IOffsetToNextChar( wchar_t stringChar )
|
|
|
|
|
{
|
|
|
|
|
if( stringChar == fColorCodeChar )
|
|
|
|
|
return fColorCodeSize;
|
|
|
|
|
else if( stringChar == fStyleCodeChar )
|
|
|
|
|
return fStyleCodeSize;
|
|
|
|
|
else
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline Int32 pfGUIMultiLineEditCtrl::IOffsetToNextCharFromPos( Int32 position ) const
|
|
|
|
|
{
|
|
|
|
|
if( position >= 0 )
|
|
|
|
|
return IOffsetToNextChar( fBuffer[ position ] );
|
|
|
|
|
else
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IRecalcLineStarts ///////////////////////////////////////////////////////
|
|
|
|
|
// Recalculates all the word wrapping/line start values, starting at the
|
|
|
|
|
// given line. If not forced, recalc will stop once a calculated line start
|
|
|
|
|
// matches one already stored (this implying that everything after will be
|
|
|
|
|
// the same as well, assuming contents are the same). If this assumption can't
|
|
|
|
|
// be made, force recalc of all the lines.
|
|
|
|
|
// Returns the last line recalced.
|
|
|
|
|
// The starting line is basically the first line whose start might have
|
|
|
|
|
// changed, so we assume as a hint that every line before the starting line
|
|
|
|
|
// is still valid. If startingLine = 0, we recalc 'em all.
|
|
|
|
|
|
|
|
|
|
Int32 pfGUIMultiLineEditCtrl::IRecalcLineStarts( Int32 startingLine, hsBool force, hsBool dontUpdate )
|
|
|
|
|
{
|
|
|
|
|
UInt16 wrapWidth, widthCounter;
|
|
|
|
|
UInt32 charPos = 0, nextPos, startPos, lastStartPos;
|
|
|
|
|
Int32 currLine, realStartingLine;
|
|
|
|
|
bool firstLine;
|
|
|
|
|
wchar_t *buffer;
|
|
|
|
|
const wchar_t wordBreaks[] = L" \t,.";
|
|
|
|
|
const wchar_t wordSeparators[] = L" \t,.\n";
|
|
|
|
|
|
|
|
|
|
if( fPrevCtrl )
|
|
|
|
|
IUpdateBuffer(); // make sure our buffer is correct if we are linked
|
|
|
|
|
|
|
|
|
|
if( fDynTextMap == nil )
|
|
|
|
|
{
|
|
|
|
|
// Can't calculate anything. Just return invalid
|
|
|
|
|
fLineStarts.Reset();
|
|
|
|
|
IUpdateScrollRange();
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Figure out our starting character
|
|
|
|
|
if( startingLine > 0 )
|
|
|
|
|
{
|
|
|
|
|
if( startingLine >= fLineStarts.GetCount() )
|
|
|
|
|
{
|
|
|
|
|
// Must be a problem, force full recalc
|
|
|
|
|
hsStatusMessage( "Invalid starting line in IRecalcLineStarts(), forcing full recalc" );
|
|
|
|
|
currLine = 0;
|
|
|
|
|
force = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Start 1 line before, since we assume that line start is still valid
|
|
|
|
|
currLine = startingLine - 1;
|
|
|
|
|
charPos = fLineStarts[ currLine ];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
currLine = 0;
|
|
|
|
|
|
|
|
|
|
realStartingLine = currLine; // For the IUpdate call later
|
|
|
|
|
|
|
|
|
|
// Precalculate some helper values
|
|
|
|
|
wrapWidth = fDynTextMap->GetVisibleWidth() - fRightMargin;
|
|
|
|
|
buffer = fBuffer.AcquireArray();
|
|
|
|
|
firstLine = true;
|
|
|
|
|
lastStartPos = (UInt32)-1;
|
|
|
|
|
|
|
|
|
|
for( ; charPos < fBuffer.GetCount(); currLine++ )
|
|
|
|
|
{
|
|
|
|
|
//// Store this line start
|
|
|
|
|
startPos = charPos;
|
|
|
|
|
if( IStoreLineStart( currLine, startPos ) )
|
|
|
|
|
{
|
|
|
|
|
if( currLine > startingLine )
|
|
|
|
|
{
|
|
|
|
|
// We just stored a line that didn't change its starting position (first line doesn't count)
|
|
|
|
|
if( !force )
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if( currLine > realStartingLine )
|
|
|
|
|
{
|
|
|
|
|
// Line start didn't change, but we're at the start. See, we always go one line back in case
|
|
|
|
|
// something changed that would cause the line before to update. But if we're here, both the
|
|
|
|
|
// line before and this line have the same starting position, which means that the line before
|
|
|
|
|
// didn't change, so we can increment realStartingLine and skip re-drawing that line
|
|
|
|
|
realStartingLine++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
firstLine = false;
|
|
|
|
|
|
|
|
|
|
//// We do a walk where we find the start of the next word (i.e. the end of this word plus
|
|
|
|
|
//// any "white space"), and then see if we can fit everything up to that point. If we can,
|
|
|
|
|
//// keep walking, if not, stop at whatever we had as a starting point.
|
|
|
|
|
nextPos = charPos;
|
|
|
|
|
for( widthCounter = 0; widthCounter < wrapWidth; )
|
|
|
|
|
{
|
|
|
|
|
charPos = nextPos;
|
|
|
|
|
|
|
|
|
|
// Are we on a line break?
|
|
|
|
|
if( nextPos >= fBuffer.GetCount() || buffer[ nextPos ] == L'\n' || buffer[ nextPos ] == 0L )
|
|
|
|
|
{
|
|
|
|
|
charPos++;
|
|
|
|
|
break; // Yup, so do so
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the end of this word
|
|
|
|
|
while( nextPos < fBuffer.GetCount() && !IIsWordBreaker( buffer[ nextPos ] ) )
|
|
|
|
|
nextPos += IOffsetToNextChar( buffer[ nextPos ] );
|
|
|
|
|
|
|
|
|
|
// Now we're at some white space, keep going until we hit the next word
|
|
|
|
|
while( nextPos < fBuffer.GetCount() && IIsWordBreaker( buffer[ nextPos ] ) && buffer[ nextPos ] != L'\n' )
|
|
|
|
|
nextPos += IOffsetToNextChar( buffer[ nextPos ] );
|
|
|
|
|
|
|
|
|
|
// Now see how much width this is
|
|
|
|
|
widthCounter = (UInt16)IRenderLine( fLeftMargin, 0, startPos, nextPos, true );
|
|
|
|
|
|
|
|
|
|
// Now we loop. If wrapWidth is too much, we'll break the loop with charPos pointing to the
|
|
|
|
|
// end of our line. If not, charPos will advance to start the search again
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check to see if the line was just too long to wrap at all
|
|
|
|
|
if( startPos == charPos )
|
|
|
|
|
{
|
|
|
|
|
// OK, so just try again and break as soon as necessary. nextPos should be
|
|
|
|
|
// already advanced too far, so start from there and go back
|
|
|
|
|
while( widthCounter >= wrapWidth && nextPos > startPos )
|
|
|
|
|
{
|
|
|
|
|
nextPos -= IOffsetToNextChar( buffer[ nextPos - 1 ] );
|
|
|
|
|
widthCounter = (UInt16)IRenderLine( fLeftMargin, 0, startPos, nextPos, true );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
charPos = nextPos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Continue on!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( charPos >= fBuffer.GetCount() )
|
|
|
|
|
{
|
|
|
|
|
// Make sure there are no lines stored after this one
|
|
|
|
|
fLineStarts.SetCount( currLine );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IUpdateScrollRange();
|
|
|
|
|
|
|
|
|
|
if( !dontUpdate )
|
|
|
|
|
// We just changed some of the line starts, so time to redraw
|
|
|
|
|
IUpdate( realStartingLine, currLine - 1 );
|
|
|
|
|
|
|
|
|
|
// we just updated the line starts, so set all following controls to the same set
|
|
|
|
|
if (fNextCtrl)
|
|
|
|
|
fNextCtrl->ISetLineStarts(fLineStarts);
|
|
|
|
|
|
|
|
|
|
return currLine;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IStoreLineStart /////////////////////////////////////////////////////////
|
|
|
|
|
// Stores a single line start, expanding the array if necessary.
|
|
|
|
|
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::IStoreLineStart( UInt32 line, Int32 start )
|
|
|
|
|
{
|
|
|
|
|
if( fLineStarts.GetCount() <= line )
|
|
|
|
|
{
|
|
|
|
|
hsAssert( line == fLineStarts.GetCount(), "Trying to store a line way past the end of line starts!" );
|
|
|
|
|
fLineStarts.Expand( line + 1 );
|
|
|
|
|
fLineStarts.SetCount( line + 1 );
|
|
|
|
|
fLineStarts[ line ] = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hsBool same = ( fLineStarts[ line ] == start ) ? true : false;
|
|
|
|
|
fLineStarts[ line ] = start;
|
|
|
|
|
return same;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IFindCursorLine /////////////////////////////////////////////////////////
|
|
|
|
|
// Calculates the line the cursor is sitting on
|
|
|
|
|
|
|
|
|
|
Int32 pfGUIMultiLineEditCtrl::IFindCursorLine( Int32 cursorPos ) const
|
|
|
|
|
{
|
|
|
|
|
Int32 line;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( cursorPos == -1 )
|
|
|
|
|
cursorPos = fCursorPos;
|
|
|
|
|
|
|
|
|
|
for( line = 0; line < fLineStarts.GetCount() - 1; line++ )
|
|
|
|
|
{
|
|
|
|
|
if( fLineStarts[ line + 1 ] > cursorPos )
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IRecalcFromCursor ///////////////////////////////////////////////////////
|
|
|
|
|
// Recalcs starting with the line that the cursor is sitting on
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IRecalcFromCursor( hsBool force )
|
|
|
|
|
{
|
|
|
|
|
IRecalcLineStarts( IFindCursorLine(), force );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IOffsetLineStarts ///////////////////////////////////////////////////////
|
|
|
|
|
// Given the position and offset, offsets all line starts >= that position
|
|
|
|
|
// by the given amount. Used to insert a character and you know that all the
|
|
|
|
|
// line offsets after that character should just bump up one, for example.
|
|
|
|
|
// Also offsets the end of the current selection if desired and in range.
|
|
|
|
|
// Why only the end of the selection? Because in the only cases where we're
|
|
|
|
|
// doing this, we are inserting into (and offseting inside) a selection,
|
|
|
|
|
// so we don't want the start moving around.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IOffsetLineStarts( UInt32 position, Int32 offset, hsBool offsetSelectionEnd )
|
|
|
|
|
{
|
|
|
|
|
Int32 line;
|
|
|
|
|
|
|
|
|
|
// Check our first line and make sure offsetting it won't make it invalid.
|
|
|
|
|
// If it will, we need to recalc the line starts entirely (which is fine,
|
|
|
|
|
// since this function is just called to try to optimize out doing so, but
|
|
|
|
|
// when you gotta, you gotta...)
|
|
|
|
|
line = IFindCursorLine( position );
|
|
|
|
|
if( line < fLineStarts.GetCount() - 1 )
|
|
|
|
|
{
|
|
|
|
|
if( fLineStarts[ line + 1 ] + offset <= fLineStarts[ line ] )
|
|
|
|
|
{
|
|
|
|
|
// We want to force the line starts to recalc. Otherwise, IRecalc() will attempt
|
|
|
|
|
// to stop once it detects a line start that hasn't changed. Which doesn't work
|
|
|
|
|
// because our line starts just got offsetted, hence why this function was called.
|
|
|
|
|
// Even then, it isn't necessarily a bad thing, since that line will be OK, but
|
|
|
|
|
// the assumption IRecalc() makes is that all the lines *after* that must be OK too
|
|
|
|
|
// b/c it just found the same line start, which in this case it didn't.
|
|
|
|
|
// Just trust me.
|
|
|
|
|
IRecalcLineStarts( line, true );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Offset all lines past our given position
|
|
|
|
|
for( line = 0; line < fLineStarts.GetCount(); line++ )
|
|
|
|
|
{
|
|
|
|
|
if( fLineStarts[ line ] > position )
|
|
|
|
|
fLineStarts[ line ] += offset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now update all following controls
|
|
|
|
|
if (fNextCtrl)
|
|
|
|
|
fNextCtrl->ISetLineStarts(fLineStarts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// HandleMouseDown /////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::HandleMouseDown( hsPoint3 &mousePt, UInt8 modifiers )
|
|
|
|
|
{
|
|
|
|
|
if( fDynTextMap == nil || !fBounds.IsInside( &mousePt ) )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
IScreenToLocalPt( mousePt );
|
|
|
|
|
mousePt.fX *= fDynTextMap->GetVisibleWidth();
|
|
|
|
|
mousePt.fY *= fDynTextMap->GetVisibleHeight();
|
|
|
|
|
|
|
|
|
|
IMoveCursorTo( IPointToPosition( (Int16)(mousePt.fX), (Int16)(mousePt.fY) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// HandleMouseUp ///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::HandleMouseUp( hsPoint3 &mousePt, UInt8 modifiers )
|
|
|
|
|
{
|
|
|
|
|
if( fDynTextMap == nil || !fBounds.IsInside( &mousePt ) )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
IScreenToLocalPt( mousePt );
|
|
|
|
|
mousePt.fX *= fDynTextMap->GetVisibleWidth();
|
|
|
|
|
mousePt.fY *= fDynTextMap->GetVisibleHeight();
|
|
|
|
|
|
|
|
|
|
IMoveCursorTo( IPointToPosition( (Int16)(mousePt.fX), (Int16)(mousePt.fY) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// HandleMouseDrag /////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::HandleMouseDrag( hsPoint3 &mousePt, UInt8 modifiers )
|
|
|
|
|
{
|
|
|
|
|
if( fDynTextMap == nil || !fBounds.IsInside( &mousePt ) )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
IScreenToLocalPt( mousePt );
|
|
|
|
|
mousePt.fX *= fDynTextMap->GetVisibleWidth();
|
|
|
|
|
mousePt.fY *= fDynTextMap->GetVisibleHeight();
|
|
|
|
|
|
|
|
|
|
IMoveCursorTo( IPointToPosition( (Int16)(mousePt.fX), (Int16)(mousePt.fY) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::HandleKeyPress( char keyIn, UInt8 modifiers )
|
|
|
|
|
{
|
|
|
|
|
wchar_t key = (wchar_t)keyIn;
|
|
|
|
|
|
|
|
|
|
if ((fPrevCtrl || fNextCtrl) && (fLineStarts.GetCount() <= GetFirstVisibleLine()))
|
|
|
|
|
return true; // we're ignoring if we can't actually edit our visible frame (and we're linked)
|
|
|
|
|
|
|
|
|
|
if (modifiers & pfGameGUIMgr::kCtrlDown)
|
|
|
|
|
return true; // we're ignoring ctrl key events
|
|
|
|
|
|
|
|
|
|
if( fIgnoreNextKey )
|
|
|
|
|
{
|
|
|
|
|
// So we don't process keys that already got handled by HandleKeyEvent()
|
|
|
|
|
fIgnoreNextKey = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store info for the event we're about to send out
|
|
|
|
|
fLastKeyModifiers = modifiers;
|
|
|
|
|
fLastKeyPressed = key;
|
|
|
|
|
|
|
|
|
|
// Send out a key pressed event.
|
|
|
|
|
HandleExtendedEvent( kKeyPressedEvent );
|
|
|
|
|
|
|
|
|
|
// We discard keys when locked only after we give our handler the key
|
|
|
|
|
if( IsLocked() )
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (plKeyboardDevice::KeyIsDeadKey())
|
|
|
|
|
{
|
|
|
|
|
if (fLastDeadKey != 0)
|
|
|
|
|
{
|
|
|
|
|
wchar_t temp = key; // we have two dead keys in a row, print out the old one and store the new one
|
|
|
|
|
key = fLastDeadKey;
|
|
|
|
|
fLastDeadKey = temp;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fLastDeadKey = key; // store the dead key and don't print it until we get the next char
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fLastDeadKey != 0) // we have a dead key that needs to be added in
|
|
|
|
|
{
|
|
|
|
|
wchar_t translatedKey = fDeadKeyConverter[(char)fLastDeadKey+100][(char)key];
|
|
|
|
|
if (translatedKey == 0) // no translation possible?
|
|
|
|
|
{
|
|
|
|
|
// so we need to print the dead key, followed by the typed key
|
|
|
|
|
// unless key is a space, then we just type the dead key
|
|
|
|
|
if (key == ' ')
|
|
|
|
|
{
|
|
|
|
|
// Insert character at the current cursor position, then inc the cursor by one
|
|
|
|
|
// Note: we always want selection mode off when we're typing
|
|
|
|
|
InsertChar( fLastDeadKey );
|
|
|
|
|
fLastDeadKey = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// Insert characters at the current cursor position, then inc the cursor by one
|
|
|
|
|
// Note: we always want selection mode off when we're typing
|
|
|
|
|
InsertChar( fLastDeadKey );
|
|
|
|
|
InsertChar( key );
|
|
|
|
|
fLastDeadKey = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// ok, so we have a translated key now, so assign it to our key and print it normally
|
|
|
|
|
key = translatedKey;
|
|
|
|
|
fLastDeadKey = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Insert character at the current cursor position, then inc the cursor by one
|
|
|
|
|
// Note: we always want selection mode off when we're typing
|
|
|
|
|
InsertChar( key );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::HandleKeyEvent( pfGameGUIMgr::EventType event, plKeyDef key, UInt8 modifiers )
|
|
|
|
|
{
|
|
|
|
|
if( key == KEY_CAPSLOCK )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if ((fPrevCtrl || fNextCtrl) && (fLineStarts.GetCount() <= GetFirstVisibleLine()))
|
|
|
|
|
return true; // we're ignoring if we can't actually edit our visible frame (and we're linked)
|
|
|
|
|
|
|
|
|
|
if (modifiers & pfGameGUIMgr::kCtrlDown)
|
|
|
|
|
return true; // we're ignoring ctrl key events
|
|
|
|
|
|
|
|
|
|
if( event == pfGameGUIMgr::kKeyDown || event == pfGameGUIMgr::kKeyRepeat )
|
|
|
|
|
{
|
|
|
|
|
// Use arrow keys to do our dirty work
|
|
|
|
|
if( key == KEY_UP )
|
|
|
|
|
IMoveCursor( kOneLineUp );
|
|
|
|
|
else if( key == KEY_DOWN )
|
|
|
|
|
IMoveCursor( kOneLineDown );
|
|
|
|
|
else if( key == KEY_HOME )
|
|
|
|
|
IMoveCursor( ( modifiers & pfGameGUIMgr::kCtrlDown ) ? kBufferStart : kLineStart );
|
|
|
|
|
else if( key == KEY_END )
|
|
|
|
|
IMoveCursor( ( modifiers & pfGameGUIMgr::kCtrlDown ) ? kBufferEnd : kLineEnd );
|
|
|
|
|
else if( key == KEY_PAGEUP )
|
|
|
|
|
IMoveCursor( kPageUp );
|
|
|
|
|
else if( key == KEY_PAGEDOWN )
|
|
|
|
|
IMoveCursor( kPageDown );
|
|
|
|
|
|
|
|
|
|
else if( key == KEY_LEFT )
|
|
|
|
|
IMoveCursor( ( modifiers & pfGameGUIMgr::kCtrlDown ) ? kOneWordBack : kOneBack );
|
|
|
|
|
else if( key == KEY_RIGHT )
|
|
|
|
|
IMoveCursor( ( modifiers & pfGameGUIMgr::kCtrlDown ) ? kOneWordForward : kOneForward );
|
|
|
|
|
|
|
|
|
|
else if( key == KEY_BACKSPACE )
|
|
|
|
|
{
|
|
|
|
|
if( IsLocked() )
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if( fCursorPos > 0 )
|
|
|
|
|
{
|
|
|
|
|
IMoveCursor(kOneBack);
|
|
|
|
|
DeleteChar();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if( key == KEY_DELETE )
|
|
|
|
|
{
|
|
|
|
|
if( IsLocked() )
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
DeleteChar();
|
|
|
|
|
}
|
|
|
|
|
else if( key == KEY_ESCAPE )
|
|
|
|
|
{
|
|
|
|
|
// fEscapedFlag = true;
|
|
|
|
|
DoSomething(); // Query WasEscaped() to see if it was escape vs enter
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fIgnoreNextKey = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fIgnoreNextKey = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
// We don't process them, but we don't want anybody else processing them either
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// ISetCursor //////////////////////////////////////////////////////////////
|
|
|
|
|
// Only moves the cursor and redraws, doesn't bother with selections. You
|
|
|
|
|
// should probably call IMoveCursorTo() unless you really know what you're
|
|
|
|
|
// doing and don't want the current selection updated.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::ISetCursor( Int32 newPosition )
|
|
|
|
|
{
|
|
|
|
|
fCursorPos = newPosition;
|
|
|
|
|
|
|
|
|
|
Int32 newLine = IFindCursorLine();
|
|
|
|
|
|
|
|
|
|
// Rescroll if necessary
|
|
|
|
|
if( fLastCursorLine != newLine )
|
|
|
|
|
{
|
|
|
|
|
if( newLine < fScrollPos )
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl) // we are linked
|
|
|
|
|
{
|
|
|
|
|
fPrevCtrl->ISetCursor(newPosition); // so tell the prev control to set its cursor
|
|
|
|
|
fPrevCtrl->GetOwnerDlg()->SetFocus(fPrevCtrl); // give control to the prev control (since we just moved our cursor there)
|
|
|
|
|
}
|
|
|
|
|
else if (!fPrevCtrl && fNextCtrl) // we are linked, but there isn't anyone before us
|
|
|
|
|
IHitBeginningOfControlList(newPosition); // send an event to the person that wanted it
|
|
|
|
|
else
|
|
|
|
|
SetScrollPosition( newLine );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// -2 here for a reason: 1 because we want the last fully visible line, not partially visible,
|
|
|
|
|
// and 1 because we want the actual last visible line index, which is of course start + len - 1
|
|
|
|
|
Int32 delta = newLine - ( fScrollPos + ICalcNumVisibleLines() - 2 );
|
|
|
|
|
if( delta > 0 )
|
|
|
|
|
{
|
|
|
|
|
if (fNextCtrl) // we are linked
|
|
|
|
|
{
|
|
|
|
|
fNextCtrl->ISetCursor(newPosition); // so tell the next control to set its cursor
|
|
|
|
|
fNextCtrl->GetOwnerDlg()->SetFocus(fNextCtrl); // give control to the next control (since we just moved our cursor there)
|
|
|
|
|
}
|
|
|
|
|
else if (!fNextCtrl && fPrevCtrl) // we are linked, but there isn't anyone after us
|
|
|
|
|
IHitEndOfControlList(newPosition); // send an event to the person that wanted it
|
|
|
|
|
else
|
|
|
|
|
SetScrollPosition( fScrollPos + delta );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( fLastCursorLine < newLine )
|
|
|
|
|
IUpdate( fLastCursorLine, newLine );
|
|
|
|
|
else
|
|
|
|
|
IUpdate( newLine, fLastCursorLine );
|
|
|
|
|
|
|
|
|
|
fLastCursorLine = newLine;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IMoveCursor /////////////////////////////////////////////////////////////
|
|
|
|
|
// Moves the cursor in a relative direction.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IMoveCursor( pfGUIMultiLineEditCtrl::Direction dir )
|
|
|
|
|
{
|
|
|
|
|
Int32 cursor = fCursorPos, line, offset, end;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch( dir )
|
|
|
|
|
{
|
|
|
|
|
case kLineStart:
|
|
|
|
|
while( cursor > 0 && fBuffer[ cursor - 1 ] != L'\n' )
|
|
|
|
|
cursor--;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kLineEnd:
|
|
|
|
|
while( cursor < fBuffer.GetCount() - 1 && fBuffer[ cursor ] != L'\n' )
|
|
|
|
|
cursor++;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kBufferStart:
|
|
|
|
|
cursor = 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kBufferEnd:
|
|
|
|
|
cursor = fBuffer.GetCount() - 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kOneBack:
|
|
|
|
|
if( cursor > 0 )
|
|
|
|
|
{
|
|
|
|
|
cursor--;
|
|
|
|
|
while( cursor > 0 && ( fBuffer[ cursor ] == fColorCodeChar || fBuffer[ cursor ] == fStyleCodeChar ) )
|
|
|
|
|
cursor -= IOffsetToNextChar( fBuffer[ cursor ] );
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kOneForward:
|
|
|
|
|
if( cursor < fBuffer.GetCount() - 1 )
|
|
|
|
|
{
|
|
|
|
|
cursor++;
|
|
|
|
|
while( cursor < fBuffer.GetCount() - 1 && ( fBuffer[ cursor ] == fColorCodeChar || fBuffer[ cursor ] == fStyleCodeChar ) )
|
|
|
|
|
cursor += IOffsetToNextChar( fBuffer[ cursor ] );
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kOneWordBack:
|
|
|
|
|
if( cursor > 0 )
|
|
|
|
|
{
|
|
|
|
|
for( ; cursor > 0 && IIsWordBreaker( fBuffer[ cursor - 1 ] ); cursor-- );
|
|
|
|
|
for( ; cursor > 0 && !IIsWordBreaker( fBuffer[ cursor - 1 ] ); cursor-- );
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kOneWordForward:
|
|
|
|
|
if( cursor < fBuffer.GetCount() - 1 )
|
|
|
|
|
{
|
|
|
|
|
for( ; cursor < fBuffer.GetCount() - 1 && !IIsWordBreaker( fBuffer[ cursor ] ); cursor++ );
|
|
|
|
|
for( ; cursor < fBuffer.GetCount() - 1 && IIsWordBreaker( fBuffer[ cursor ] ); cursor++ );
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kOneLineUp:
|
|
|
|
|
// The wonderful thing is, since we keep going on the position of the cursor (which magically
|
|
|
|
|
// is the left side of the character we're on), we end up drifting left as we keep going up
|
|
|
|
|
// or down. So to compensate, we put a little fudge factor in that lets us attempt to stay
|
|
|
|
|
// "on course", as it were.
|
|
|
|
|
if( IFindCursorLine( cursor ) > 0 )
|
|
|
|
|
cursor = IPointToPosition( fCurrCursorX + ( fLineHeight >> 2 ), fCurrCursorY - fLineHeight, true );
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kOneLineDown:
|
|
|
|
|
if( IFindCursorLine( cursor ) < fLineStarts.GetCount() - 1 )
|
|
|
|
|
cursor = IPointToPosition( fCurrCursorX + ( fLineHeight >> 2 ), fCurrCursorY + fLineHeight, true );
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kPageUp:
|
|
|
|
|
line = IFindCursorLine( cursor );
|
|
|
|
|
offset = cursor - fLineStarts[ line ];
|
|
|
|
|
|
|
|
|
|
line -= ( ICalcNumVisibleLines() - 1 );
|
|
|
|
|
if( line < 0 )
|
|
|
|
|
line = 0;
|
|
|
|
|
|
|
|
|
|
end = ( line < fLineStarts.GetCount() - 1 ) ? fLineStarts[ line + 1 ] : fBuffer.GetCount() - 1;
|
|
|
|
|
if( fLineStarts[ line ] + offset > end )
|
|
|
|
|
offset = end - fLineStarts[ line ] - 1;
|
|
|
|
|
|
|
|
|
|
cursor = fLineStarts[ line ] + offset;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case kPageDown:
|
|
|
|
|
line = IFindCursorLine( cursor );
|
|
|
|
|
offset = cursor - fLineStarts[ line ];
|
|
|
|
|
|
|
|
|
|
line += ( ICalcNumVisibleLines() - 1 );
|
|
|
|
|
if( line > fLineStarts.GetCount() - 1 )
|
|
|
|
|
line = fLineStarts.GetCount() - 1;
|
|
|
|
|
|
|
|
|
|
end = ( line < fLineStarts.GetCount() - 1 ) ? fLineStarts[ line + 1 ] : fBuffer.GetCount() - 1;
|
|
|
|
|
if( fLineStarts[ line ] + offset > end )
|
|
|
|
|
offset = end - fLineStarts[ line ] - 1;
|
|
|
|
|
|
|
|
|
|
cursor = fLineStarts[ line ] + offset;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IMoveCursorTo( cursor );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// IMoveCursorTo ///////////////////////////////////////////////////////////
|
|
|
|
|
// Moves the cursor to the given absolute position and updates the selection
|
|
|
|
|
// area accordingly and if necessary.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IMoveCursorTo( Int32 position )
|
|
|
|
|
{
|
|
|
|
|
ISetCursor( position );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
//// Buffer Modification /////////////////////////////////////////////////////
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
//// InsertChar //////////////////////////////////////////////////////////////
|
|
|
|
|
// Inserts a character at the cursor, or if there is a selection, replaces
|
|
|
|
|
// the selection with the given character. Either way, at the end the cursor
|
|
|
|
|
// is after the inserted character.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::InsertChar( char c )
|
|
|
|
|
{
|
|
|
|
|
InsertChar((wchar_t)c); // a simple cast should be fine
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::InsertChar( wchar_t c )
|
|
|
|
|
{
|
|
|
|
|
// Error checking--make sure our actual character isn't in the range of our
|
|
|
|
|
// code characters, or chaos could erupt
|
|
|
|
|
//if( c < 3 )
|
|
|
|
|
//return;
|
|
|
|
|
if (c == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if ( fBufferLimit == -1 || fBuffer.GetCount()+1 < fBufferLimit-1)
|
|
|
|
|
{
|
|
|
|
|
fBuffer.Insert( fCursorPos, c );
|
|
|
|
|
|
|
|
|
|
ISetGlobalBuffer(); // update the global buffer
|
|
|
|
|
|
|
|
|
|
IOffsetLineStarts( fCursorPos, 1 );
|
|
|
|
|
IMoveCursor( kOneForward );
|
|
|
|
|
IRecalcFromCursor();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// InsertString ////////////////////////////////////////////////////////////
|
|
|
|
|
// Same as InsertChar, only with a null-terminated string of characters
|
|
|
|
|
// instead of just one.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::InsertString( const char *string )
|
|
|
|
|
{
|
|
|
|
|
wchar_t* temp = hsStringToWString(string);
|
|
|
|
|
InsertString(temp);
|
|
|
|
|
delete [] temp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::InsertString( const wchar_t *string )
|
|
|
|
|
{
|
|
|
|
|
int numChars = wcslen( string );
|
|
|
|
|
|
|
|
|
|
if ( fBufferLimit == -1 || fBuffer.GetCount() + numChars < fBufferLimit-1)
|
|
|
|
|
{
|
|
|
|
|
// Don't freak out, the cast is OK here. Insert() wants an array of chars.
|
|
|
|
|
// It should really take a const array, since it's just copying them,
|
|
|
|
|
// but since it doesn't and we know it's just copying them, we cast and
|
|
|
|
|
// be happy for now.
|
|
|
|
|
fBuffer.Insert( fCursorPos, numChars, (wchar_t *)string );
|
|
|
|
|
|
|
|
|
|
ISetGlobalBuffer(); // update the global buffer
|
|
|
|
|
|
|
|
|
|
IOffsetLineStarts( fCursorPos, numChars );
|
|
|
|
|
IMoveCursorTo( fCursorPos + numChars );
|
|
|
|
|
IRecalcFromCursor();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// InsertColor /////////////////////////////////////////////////////////////
|
|
|
|
|
// Inserts a color control code into the buffer, or if there is a selection,
|
|
|
|
|
// places TWO color codes in: one before the selection with the given color
|
|
|
|
|
// and the other after the given selection with the previous color.
|
|
|
|
|
// There is one catch to all of this, though: if we have a selection AND
|
|
|
|
|
// we're inserting a code that conflicts with any code contained in the
|
|
|
|
|
// selection, we remove the conflicting codes.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::InsertColor( hsColorRGBA &color )
|
|
|
|
|
{
|
|
|
|
|
IActuallyInsertColor( fCursorPos, color );
|
|
|
|
|
IOffsetLineStarts( fCursorPos, fColorCodeSize );
|
|
|
|
|
fCursorPos += fColorCodeSize;
|
|
|
|
|
IRecalcFromCursor( true ); // Force update of all following lines, since
|
|
|
|
|
// insertion of this code changes appearance of following characters
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IActuallyInsertColor( Int32 pos, hsColorRGBA &color )
|
|
|
|
|
{
|
|
|
|
|
if ( fBufferLimit == -1 || fBuffer.GetCount()+4 < fBufferLimit-1 )
|
|
|
|
|
{
|
|
|
|
|
fBuffer.Insert( pos, fColorCodeChar );
|
|
|
|
|
fBuffer.Insert( pos + 1, (UInt8)( color.r * 255.f ) );
|
|
|
|
|
fBuffer.Insert( pos + 2, (UInt8)( color.g * 255.f ) );
|
|
|
|
|
fBuffer.Insert( pos + 3, (UInt8)( color.b * 255.f ) );
|
|
|
|
|
fBuffer.Insert( pos + 4, fColorCodeChar );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// InsertStyle /////////////////////////////////////////////////////////////
|
|
|
|
|
// Same thing as InsertColor(), only with a style code (or two).
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::InsertStyle( UInt8 fontStyle )
|
|
|
|
|
{
|
|
|
|
|
IActuallyInsertStyle( fCursorPos, fontStyle );
|
|
|
|
|
|
|
|
|
|
IOffsetLineStarts( fCursorPos, fStyleCodeSize );
|
|
|
|
|
fCursorPos += fStyleCodeSize;
|
|
|
|
|
IRecalcFromCursor( true ); // Force update of all following lines, since
|
|
|
|
|
// insertion of this code changes appearance of following characters
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IActuallyInsertStyle( Int32 pos, UInt8 style )
|
|
|
|
|
{
|
|
|
|
|
if ( fBufferLimit == -1 || fBuffer.GetCount() + 3 < fBufferLimit-1 )
|
|
|
|
|
{
|
|
|
|
|
fBuffer.Insert( pos, fStyleCodeChar );
|
|
|
|
|
fBuffer.Insert( pos + 1, (wchar_t)style );
|
|
|
|
|
fBuffer.Insert( pos + 2, fStyleCodeChar );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// DeleteChar //////////////////////////////////////////////////////////////
|
|
|
|
|
// If there is no selection, deletes the single character that the cursor
|
|
|
|
|
// is in front of. Otherwise, deletes the current selection and places the
|
|
|
|
|
// cursor where the selection used to be.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::DeleteChar( void )
|
|
|
|
|
{
|
|
|
|
|
if( fCursorPos < fBuffer.GetCount() - 1 )
|
|
|
|
|
{
|
|
|
|
|
Int32 offset = IOffsetToNextChar( fBuffer[ fCursorPos ] );
|
|
|
|
|
bool forceUpdate = IIsCodeChar( fBuffer[ fCursorPos ] );
|
|
|
|
|
fBuffer.Remove( fCursorPos, offset );
|
|
|
|
|
|
|
|
|
|
ISetGlobalBuffer(); // update the global buffer
|
|
|
|
|
|
|
|
|
|
IOffsetLineStarts( fCursorPos, -offset );
|
|
|
|
|
IRecalcFromCursor( forceUpdate );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// ICopyRange //////////////////////////////////////////////////////////////
|
|
|
|
|
// Generic coded-to-non-coded conversion. Returns a copy of the string that
|
|
|
|
|
// the caller must free.
|
|
|
|
|
|
|
|
|
|
wchar_t *pfGUIMultiLineEditCtrl::ICopyRange( Int32 start, Int32 end ) const
|
|
|
|
|
{
|
|
|
|
|
Int32 stringSize, pos;
|
|
|
|
|
wchar_t *string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// First loop, just count how much space we need
|
|
|
|
|
for( stringSize = 0, pos = start; pos < end; pos = pos + IOffsetToNextChar( fBuffer[ pos ] ) )
|
|
|
|
|
{
|
|
|
|
|
if( !IIsCodeChar( fBuffer[ pos ] ) )
|
|
|
|
|
stringSize++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Our string...
|
|
|
|
|
string = TRACKED_NEW wchar_t[ stringSize + 1 ];
|
|
|
|
|
|
|
|
|
|
// Now actually copy the characters
|
|
|
|
|
for( stringSize = 0, pos = start; pos < end; pos = pos + IOffsetToNextChar( fBuffer[ pos ] ) )
|
|
|
|
|
{
|
|
|
|
|
if( !IIsCodeChar( fBuffer[ pos ] ) )
|
|
|
|
|
string[ stringSize++ ] = fBuffer[ pos ];
|
|
|
|
|
}
|
|
|
|
|
string[ stringSize++ ] = 0;
|
|
|
|
|
|
|
|
|
|
// All done!
|
|
|
|
|
return string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// ClearBuffer /////////////////////////////////////////////////////////////
|
|
|
|
|
// Clears everything, including the undo list.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::ClearBuffer( void )
|
|
|
|
|
{
|
|
|
|
|
fBuffer.Reset();
|
|
|
|
|
fBuffer.Append( 0 );
|
|
|
|
|
fCursorPos = 0;
|
|
|
|
|
fLastCursorLine = 0;
|
|
|
|
|
fScrollPos = 0;
|
|
|
|
|
IRecalcLineStarts( 0, true );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// SetBuffer ///////////////////////////////////////////////////////////////
|
|
|
|
|
// Replaces the entire contents of the buffer with the given text. Also
|
|
|
|
|
// clears the undo list.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetBuffer( const char *asciiText )
|
|
|
|
|
{
|
|
|
|
|
SetBuffer( (const UInt8 *)asciiText, (UInt32)strlen( asciiText ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetBuffer( const wchar_t *asciiText )
|
|
|
|
|
{
|
|
|
|
|
SetBuffer( (const UInt16 *)asciiText, (UInt32)wcslen( asciiText ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// SetBuffer ///////////////////////////////////////////////////////////////
|
|
|
|
|
// The non-0-terminated-string version that can handle buffers with style
|
|
|
|
|
// codes in them.
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetBuffer( const UInt8 *codedText, UInt32 length )
|
|
|
|
|
{
|
|
|
|
|
// convert to UInt16 and set
|
|
|
|
|
UInt16 *convertedText = TRACKED_NEW UInt16[ length ];
|
|
|
|
|
for( Int32 curChar = 0; curChar < length; curChar++ )
|
|
|
|
|
convertedText[ curChar ] = (UInt16)codedText[ curChar ];
|
|
|
|
|
SetBuffer(convertedText,length);
|
|
|
|
|
delete [] convertedText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetBuffer( const UInt16 *codedText, UInt32 length )
|
|
|
|
|
{
|
|
|
|
|
// recursively call back to the first control and set it
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
{
|
|
|
|
|
fPrevCtrl->SetBuffer(codedText,length);
|
|
|
|
|
IUpdateBuffer();
|
|
|
|
|
}
|
|
|
|
|
else // we are the first control, so set our buffer
|
|
|
|
|
{
|
|
|
|
|
fBuffer.Reset();
|
|
|
|
|
// Why o why doesn't Insert() take a const array....
|
|
|
|
|
fBuffer.Insert( 0, length, (wchar_t *)codedText );
|
|
|
|
|
fBuffer.Append( 0 );
|
|
|
|
|
IRecalcLineStarts( 0, true ); // only the first control will recalc, this function recurses down to handle all following controls
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fScrollPos = GetFirstVisibleLine();
|
|
|
|
|
fCursorPos = 0;
|
|
|
|
|
fLastCursorLine = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// GetNonCodedBuffer ///////////////////////////////////////////////////////
|
|
|
|
|
// Copies the entire buffer into an ASCII string (i.e. strips formatting)
|
|
|
|
|
// and returns it. The caller is responsible for freeing it.
|
|
|
|
|
// To avoid code duplication, we'll just cheat and use CopySelection()...
|
|
|
|
|
|
|
|
|
|
char *pfGUIMultiLineEditCtrl::GetNonCodedBuffer( void ) const
|
|
|
|
|
{
|
|
|
|
|
// recursively search back to the first control in the linked list and grab its buffer
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
return fPrevCtrl->GetNonCodedBuffer();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// -1 for the null terminator
|
|
|
|
|
wchar_t *buffer = ICopyRange( 0, fBuffer.GetCount() - 1 );
|
|
|
|
|
char *retVal = hsWStringToString(buffer);
|
|
|
|
|
delete [] buffer;
|
|
|
|
|
|
|
|
|
|
return retVal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wchar_t *pfGUIMultiLineEditCtrl::GetNonCodedBufferW( void ) const
|
|
|
|
|
{
|
|
|
|
|
// recursively search back to the first control in the linked list and grab its buffer
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
return fPrevCtrl->GetNonCodedBufferW();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// -1 for the null terminator
|
|
|
|
|
return ICopyRange( 0, fBuffer.GetCount() - 1 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// GetCodedBuffer //////////////////////////////////////////////////////////
|
|
|
|
|
// Basically does a blanket copy of the entire buffer and returns it and
|
|
|
|
|
// the length. The caller is responsible for freeing the buffer.
|
|
|
|
|
|
|
|
|
|
UInt8 *pfGUIMultiLineEditCtrl::GetCodedBuffer( UInt32 &length ) const
|
|
|
|
|
{
|
|
|
|
|
// recursively search back to the first control in the linked list and grab its buffer
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
return fPrevCtrl->GetCodedBuffer(length);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
length = fBuffer.GetCount() - 1;
|
|
|
|
|
|
|
|
|
|
// convert to UInt8 and return
|
|
|
|
|
UInt8 *buffer = TRACKED_NEW UInt8[ length ];
|
|
|
|
|
|
|
|
|
|
for (Int32 curChar = 0; curChar < length; curChar++)
|
|
|
|
|
{
|
|
|
|
|
if (fBuffer[ curChar ] > (wchar_t)0xFF)
|
|
|
|
|
{
|
|
|
|
|
// char doesn't fit, fake it with a space
|
|
|
|
|
buffer[ curChar ] = (UInt8)(L' ');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
buffer[ curChar ] = (UInt8)fBuffer[ curChar ];
|
|
|
|
|
}
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UInt16 *pfGUIMultiLineEditCtrl::GetCodedBufferW( UInt32 &length ) const
|
|
|
|
|
{
|
|
|
|
|
// recursively search back to the first control in the linked list and grab its buffer
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
return fPrevCtrl->GetCodedBufferW(length);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
length = fBuffer.GetCount() - 1;
|
|
|
|
|
|
|
|
|
|
UInt16 *buffer = TRACKED_NEW UInt16[ length ];
|
|
|
|
|
|
|
|
|
|
// AcquireArray() isn't const...
|
|
|
|
|
memcpy( buffer, &fBuffer[ 0 ], length * sizeof(UInt16) );
|
|
|
|
|
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UInt32 pfGUIMultiLineEditCtrl::GetBufferSize()
|
|
|
|
|
{
|
|
|
|
|
return fBuffer.GetCount() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// ICharPosToBufferPos /////////////////////////////////////////////////////
|
|
|
|
|
// Given a character position (i.e. a buffer position if we didn't have
|
|
|
|
|
// control codes), returns the actual buffer pos.
|
|
|
|
|
|
|
|
|
|
Int32 pfGUIMultiLineEditCtrl::ICharPosToBufferPos( Int32 charPos ) const
|
|
|
|
|
{
|
|
|
|
|
Int32 pos;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for( pos = 0; charPos > 0 && pos < fBuffer.GetCount() - 1; pos += IOffsetToNextCharFromPos( pos ), charPos-- );
|
|
|
|
|
|
|
|
|
|
return pos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// Locking /////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::Lock( void )
|
|
|
|
|
{
|
|
|
|
|
fLockCount++;
|
|
|
|
|
IUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::Unlock( void )
|
|
|
|
|
{
|
|
|
|
|
fLockCount--;
|
|
|
|
|
//hsAssert( fLockCount >= 0, "Too many unlocks for pfGUIMultiLineEditCtrl" );
|
|
|
|
|
if (fLockCount < 0)
|
|
|
|
|
fLockCount = 0; // instead of asserting, hande it nicely
|
|
|
|
|
IUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// linking functions, for linking multiple multi-line edit controls together
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetNext( pfGUIMultiLineEditCtrl *newNext )
|
|
|
|
|
{
|
|
|
|
|
ClearNext(); // clear the existing link (if there is one)
|
|
|
|
|
if (newNext)
|
|
|
|
|
{
|
|
|
|
|
newNext->ClearPrev(); // clear any existing prev link
|
|
|
|
|
fNextCtrl = newNext;
|
|
|
|
|
fNextCtrl->fPrevCtrl = this;
|
|
|
|
|
fNextCtrl->IUpdateBuffer();
|
|
|
|
|
fNextCtrl->fScrollPos = fNextCtrl->GetFirstVisibleLine();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::ClearNext()
|
|
|
|
|
{
|
|
|
|
|
if (fNextCtrl)
|
|
|
|
|
fNextCtrl->fPrevCtrl = nil; // unlink ourselves from the next control
|
|
|
|
|
fNextCtrl = nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetPrev( pfGUIMultiLineEditCtrl *newPrev )
|
|
|
|
|
{
|
|
|
|
|
ClearPrev(); // clear existing link
|
|
|
|
|
if (newPrev)
|
|
|
|
|
{
|
|
|
|
|
newPrev->ClearNext(); // clear any existing next link
|
|
|
|
|
fPrevCtrl = newPrev;
|
|
|
|
|
fPrevCtrl->fNextCtrl = this;
|
|
|
|
|
IUpdateBuffer();
|
|
|
|
|
fScrollPos = GetFirstVisibleLine();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::ClearPrev()
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
fPrevCtrl->fNextCtrl = nil; // unlink ourselves from the prev control
|
|
|
|
|
fPrevCtrl = nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetEventProc(pfGUIMultiLineEditProc *eventProc)
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
fPrevCtrl->SetEventProc(eventProc);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (fEventProc)
|
|
|
|
|
delete fEventProc;
|
|
|
|
|
fEventProc = eventProc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::ClearEventProc()
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
fPrevCtrl->ClearEventProc();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (fEventProc)
|
|
|
|
|
delete fEventProc;
|
|
|
|
|
fEventProc = nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Int32 pfGUIMultiLineEditCtrl::GetFirstVisibleLine()
|
|
|
|
|
{
|
|
|
|
|
// recursively search back to the first control and work our way forwards to where we are supposed to be
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
return fPrevCtrl->GetLastVisibleLine();
|
|
|
|
|
return fScrollPos; // we're the first control, so we show the first part of the buffer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Int32 pfGUIMultiLineEditCtrl::GetLastVisibleLine()
|
|
|
|
|
{
|
|
|
|
|
// simply add our number of lines to our first visible line
|
|
|
|
|
return GetFirstVisibleLine()+ICalcNumVisibleLines()-1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetGlobalStartLine(Int32 line)
|
|
|
|
|
{
|
|
|
|
|
// recursively call back to the first control and set it
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
fPrevCtrl->SetGlobalStartLine(line);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fScrollPos = line;
|
|
|
|
|
IRecalcLineStarts(fScrollPos,true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IUpdateLineStarts()
|
|
|
|
|
{
|
|
|
|
|
// make sure our line starts array matches the gloabl one
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
{
|
|
|
|
|
fPrevCtrl->IUpdateLineStarts();
|
|
|
|
|
fLineStarts = fPrevCtrl->fLineStarts;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IUpdateBuffer()
|
|
|
|
|
{
|
|
|
|
|
// make sure our local buffer matches the global one
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
{
|
|
|
|
|
// copy the buffer from our global one
|
|
|
|
|
UInt32 length;
|
|
|
|
|
UInt16 *codedText = GetCodedBufferW(length);
|
|
|
|
|
fBuffer.Reset();
|
|
|
|
|
fBuffer.Insert( 0, length, (wchar_t *)codedText );
|
|
|
|
|
fBuffer.Append( 0 );
|
|
|
|
|
delete [] codedText;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::ISetGlobalBuffer()
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
{
|
|
|
|
|
fPrevCtrl->fBuffer.Reset();
|
|
|
|
|
int i;
|
|
|
|
|
for (i=0; i<fBuffer.GetCount(); i++)
|
|
|
|
|
fPrevCtrl->fBuffer.Append(fBuffer[i]);
|
|
|
|
|
fPrevCtrl->ISetGlobalBuffer(); // pass the update backwards
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::ISetLineStarts(hsTArray<Int32> lineStarts)
|
|
|
|
|
{
|
|
|
|
|
if (fNextCtrl)
|
|
|
|
|
fNextCtrl->ISetLineStarts(lineStarts); // pass it on down
|
|
|
|
|
|
|
|
|
|
IUpdateBuffer(); // make sure the buffer is correct
|
|
|
|
|
fLineStarts = lineStarts;
|
|
|
|
|
|
|
|
|
|
IUpdate(); // make sure everything looks right
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetMargins(int top, int left, int bottom, int right)
|
|
|
|
|
{
|
|
|
|
|
fTopMargin = top;
|
|
|
|
|
fLeftMargin = left;
|
|
|
|
|
fBottomMargin = bottom;
|
|
|
|
|
fRightMargin = right;
|
|
|
|
|
IRecalcLineStarts(0,false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IHitEndOfControlList(Int32 cursorPos)
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
fPrevCtrl->IHitEndOfControlList(cursorPos);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (fEventProc)
|
|
|
|
|
fEventProc->OnEndOfControlList(cursorPos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::IHitBeginningOfControlList(Int32 cursorPos)
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl)
|
|
|
|
|
fPrevCtrl->IHitBeginningOfControlList(cursorPos);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (fEventProc)
|
|
|
|
|
fEventProc->OnBeginningOfControlList(cursorPos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetFontFace(std::string fontFace)
|
|
|
|
|
{
|
|
|
|
|
fFontFace = fontFace;
|
|
|
|
|
fFontFlagsSet |= kFontFaceSet;
|
|
|
|
|
fDynTextMap->SetFont( fFontFace.c_str(), fFontSize, fFontStyle,
|
|
|
|
|
HasFlag( kXparentBgnd ) ? false : true );
|
|
|
|
|
fDynTextMap->CalcStringWidth( "The quick brown fox jumped over the lazy dog.", &fLineHeight );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::SetFontSize(UInt8 fontSize)
|
|
|
|
|
{
|
|
|
|
|
fFontSize = fontSize;
|
|
|
|
|
fFontFlagsSet |= kFontSizeSet;
|
|
|
|
|
fCalcedFontSize = fontSize;
|
|
|
|
|
fDynTextMap->SetFont( fFontFace.c_str(), fFontSize, fFontStyle,
|
|
|
|
|
HasFlag( kXparentBgnd ) ? false : true );
|
|
|
|
|
fDynTextMap->CalcStringWidth( "The quick brown fox jumped over the lazy dog.", &fLineHeight );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// are we showing the beginning of the buffer? (controls before us are too far up)
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::ShowingBeginningOfBuffer()
|
|
|
|
|
{
|
|
|
|
|
if (fScrollPos == 0)
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// are we showing the end of the buffer? (controls after us are too far down)
|
|
|
|
|
hsBool pfGUIMultiLineEditCtrl::ShowingEndOfBuffer()
|
|
|
|
|
{
|
|
|
|
|
//IRecalcLineStarts(0,true); // This function gets called a lot from the journal book, so IRecalcLineStarts() REALLY slows things
|
|
|
|
|
// down if we're looking at a large amount of text, hopefully we can mess with the existing line starts for now without issue
|
|
|
|
|
if (GetLastVisibleLine() >= fLineStarts.GetCount())
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pfGUIMultiLineEditCtrl::DeleteLinesFromTop(int numLines)
|
|
|
|
|
{
|
|
|
|
|
if (fPrevCtrl || fNextCtrl)
|
|
|
|
|
return; // don't do anything
|
|
|
|
|
|
|
|
|
|
UInt32 bufferLen = 0;
|
|
|
|
|
UInt16* buffer = GetCodedBufferW(bufferLen);
|
|
|
|
|
|
|
|
|
|
if (bufferLen == 0)
|
|
|
|
|
{
|
|
|
|
|
delete [] buffer;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int curLine = 0; curLine < numLines; ++curLine)
|
|
|
|
|
{
|
|
|
|
|
bool hitEnd = true; // did we hit the end of the buffer before we hit a newline?
|
|
|
|
|
|
|
|
|
|
// search for the first newline and nuke it and everything before it
|
|
|
|
|
bool skippingColor = false, skippingStyle = false;
|
|
|
|
|
int curColorPos = 0, curStylePos = 0;
|
|
|
|
|
for (UInt32 curChar = 0; curChar < bufferLen - 1; ++curChar)
|
|
|
|
|
{
|
|
|
|
|
// we need to skip the crappy color and style "tags" so non-character values inside them
|
|
|
|
|
// don't trigger our newline check
|
|
|
|
|
if (!skippingColor && !skippingStyle)
|
|
|
|
|
{
|
|
|
|
|
if (buffer[curChar] == fColorCodeChar)
|
|
|
|
|
{
|
|
|
|
|
curColorPos = 0;
|
|
|
|
|
skippingColor = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else if (buffer[curChar] == fStyleCodeChar)
|
|
|
|
|
{
|
|
|
|
|
curStylePos = 0;
|
|
|
|
|
skippingStyle = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (skippingColor)
|
|
|
|
|
{
|
|
|
|
|
++curColorPos;
|
|
|
|
|
if (curColorPos == fColorCodeSize)
|
|
|
|
|
skippingColor = false;
|
|
|
|
|
else
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (skippingStyle)
|
|
|
|
|
{
|
|
|
|
|
++curStylePos;
|
|
|
|
|
if (curStylePos == fStyleCodeSize)
|
|
|
|
|
skippingStyle = false;
|
|
|
|
|
else
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if it's a newline, erase it and everything before it
|
|
|
|
|
if ((buffer[curChar] == L'\n') || (buffer[curChar] == L'\r'))
|
|
|
|
|
{
|
|
|
|
|
hitEnd = false;
|
|
|
|
|
UInt32 newBufferStart = curChar + 1; // +1 so we eat the newline as well
|
|
|
|
|
UInt32 newBufferLen = bufferLen - newBufferStart;
|
|
|
|
|
MemCopy(buffer, buffer + newBufferStart, newBufferLen * sizeof(UInt16)); // copy all bytes after the newline to the beginning
|
|
|
|
|
MemSet(buffer + newBufferLen, 0, (bufferLen - newBufferLen) * sizeof(UInt16)); // fill out the rest of the buffer with null chars
|
|
|
|
|
bufferLen = newBufferLen;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hitEnd)
|
|
|
|
|
{
|
|
|
|
|
delete [] buffer;
|
|
|
|
|
SetBuffer(L""); // we are removing too many (or all) lines, just clear it
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// still got lines to go, start looking for the next line
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we got here, so buffer is now our new buffer
|
|
|
|
|
SetBuffer(buffer, bufferLen);
|
|
|
|
|
delete [] buffer;
|
|
|
|
|
return;
|
|
|
|
|
}
|