diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /vgui2/vgui_controls | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'vgui2/vgui_controls')
76 files changed, 66514 insertions, 0 deletions
diff --git a/vgui2/vgui_controls/AnalogBar.cpp b/vgui2/vgui_controls/AnalogBar.cpp new file mode 100644 index 0000000..68e525d --- /dev/null +++ b/vgui2/vgui_controls/AnalogBar.cpp @@ -0,0 +1,460 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <assert.h> +#include <math.h> +#include <stdio.h> + +#include <vgui_controls/AnalogBar.h> +#include <vgui_controls/Controls.h> + +#include <vgui/ILocalize.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( AnalogBar ); + + +#define ANALOG_BAR_HOME_SIZE 4 +#define ANALOG_BAR_HOME_GAP 2 +#define ANALOG_BAR_LESS_TALL ( ANALOG_BAR_HOME_SIZE + ANALOG_BAR_HOME_GAP ) + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +AnalogBar::AnalogBar(Panel *parent, const char *panelName) : Panel(parent, panelName) +{ + _analogValue = 0.0f; + m_pszDialogVar = NULL; + SetSegmentInfo( 2, 6 ); + SetBarInset( 0 ); + m_iAnalogValueDirection = PROGRESS_EAST; + + m_fHomeValue = 2.0f; + m_HomeColor = GetFgColor(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +AnalogBar::~AnalogBar() +{ + delete [] m_pszDialogVar; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void AnalogBar::SetSegmentInfo( int gap, int width ) +{ + _segmentGap = gap; + _segmentWide = width; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the number of segment blocks drawn +//----------------------------------------------------------------------------- +int AnalogBar::GetDrawnSegmentCount() +{ + int wide, tall; + GetSize(wide, tall); + int segmentTotal = wide / (_segmentGap + _segmentWide); + return (int)(segmentTotal * _analogValue); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the total number of segment blocks drawn (active and inactive) +//----------------------------------------------------------------------------- +int AnalogBar::GetTotalSegmentCount() +{ + int wide, tall; + GetSize(wide, tall); + int segmentTotal = wide / (_segmentGap + _segmentWide); + return segmentTotal; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnalogBar::PaintBackground() +{ + // Don't draw a background +} + +void AnalogBar::PaintSegment( int &x, int &y, int tall, int wide, Color color, bool bHome ) +{ + switch( m_iAnalogValueDirection ) + { + case PROGRESS_EAST: + x += _segmentGap; + + if ( bHome ) + { + surface()->DrawSetColor( GetHomeColor() ); + surface()->DrawFilledRect(x, y, x + _segmentWide, y + ANALOG_BAR_HOME_SIZE ); + surface()->DrawFilledRect(x, y + tall - (y * 2) - ANALOG_BAR_HOME_SIZE, x + _segmentWide, y + tall - (y * 2) ); + } + + surface()->DrawSetColor( color ); + surface()->DrawFilledRect(x, y + ANALOG_BAR_LESS_TALL, x + _segmentWide, y + tall - (y * 2) - ANALOG_BAR_LESS_TALL ); + x += _segmentWide; + break; + + case PROGRESS_WEST: + x -= _segmentGap + _segmentWide; + + if ( bHome ) + { + surface()->DrawSetColor( GetHomeColor() ); + surface()->DrawFilledRect(x, y, x + _segmentWide, y + ANALOG_BAR_HOME_SIZE ); + surface()->DrawFilledRect(x, y + tall - (y * 2) - ANALOG_BAR_HOME_SIZE, x + _segmentWide, y + tall - (y * 2) ); + } + + surface()->DrawSetColor( color ); + surface()->DrawFilledRect(x, y + ANALOG_BAR_LESS_TALL, x + _segmentWide, y + tall - (y * 2) - ANALOG_BAR_LESS_TALL ); + break; + + case PROGRESS_NORTH: + y -= _segmentGap + _segmentWide; + + if ( bHome ) + { + surface()->DrawSetColor( GetHomeColor() ); + surface()->DrawFilledRect(x, y, x + ANALOG_BAR_HOME_SIZE, y + _segmentWide ); + surface()->DrawFilledRect(x + wide - (x * 2) - ANALOG_BAR_HOME_SIZE, y, x + wide - (x * 2), y + _segmentWide ); + } + + surface()->DrawSetColor( color ); + surface()->DrawFilledRect(x + ANALOG_BAR_LESS_TALL, y, x + wide - (x * 2) - ANALOG_BAR_LESS_TALL, y + _segmentWide); + break; + + case PROGRESS_SOUTH: + y += _segmentGap; + + if ( bHome ) + { + surface()->DrawSetColor( GetHomeColor() ); + surface()->DrawFilledRect(x, y, x + ANALOG_BAR_HOME_SIZE, y + _segmentWide ); + surface()->DrawFilledRect(x + wide - (x * 2) - ANALOG_BAR_HOME_SIZE, y, x + wide - (x * 2), y + _segmentWide ); + } + + surface()->DrawSetColor( color ); + surface()->DrawFilledRect(x + ANALOG_BAR_LESS_TALL, y, x + wide - (x * 2) - ANALOG_BAR_LESS_TALL, y + _segmentWide); + y += _segmentWide; + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnalogBar::Paint() +{ + int wide, tall; + GetSize(wide, tall); + + // gaps + int segmentTotal = 0, segmentsDrawn = 0; + int x = 0, y = 0; + + switch( m_iAnalogValueDirection ) + { + case PROGRESS_WEST: + x = wide; + y = m_iBarInset; + segmentTotal = wide / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _analogValue + 0.5f); + break; + + case PROGRESS_EAST: + x = 0; + y = m_iBarInset; + segmentTotal = wide / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _analogValue + 0.5f); + break; + + case PROGRESS_NORTH: + x = m_iBarInset; + y = tall; + segmentTotal = tall / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _analogValue + 0.5f); + break; + + case PROGRESS_SOUTH: + x = m_iBarInset; + y = 0; + segmentTotal = tall / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _analogValue + 0.5f); + break; + } + + int iHomeIndex = (int)( segmentTotal * m_fHomeValue + 0.5f ) - 1; + if ( iHomeIndex < 0 ) + iHomeIndex = 0; + + for (int i = 0; i < segmentsDrawn; i++) + PaintSegment( x, y, tall, wide, GetFgColor(), i == iHomeIndex ); + + for (int i = segmentsDrawn; i < segmentTotal; i++) + PaintSegment( x, y, tall, wide, GetBgColor(), i == iHomeIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnalogBar::SetAnalogValue(float analogValue) +{ + if (analogValue != _analogValue) + { + // clamp the analogValue value within the range + if (analogValue < 0.0f) + { + analogValue = 0.0f; + } + else if (analogValue > 1.0f) + { + analogValue = 1.0f; + } + + _analogValue = analogValue; + Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +float AnalogBar::GetAnalogValue() +{ + return _analogValue; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnalogBar::ApplySchemeSettings(IScheme *pScheme) +{ + Panel::ApplySchemeSettings(pScheme); + + SetBgColor( Color( 255 - GetFgColor().r(), 255 - GetFgColor().g(), 255 - GetFgColor().b(), GetFgColor().a() ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: utility function for calculating a time remaining string +//----------------------------------------------------------------------------- +bool AnalogBar::ConstructTimeRemainingString(wchar_t *output, int outputBufferSizeInBytes, float startTime, float currentTime, float currentAnalogValue, float lastAnalogValueUpdateTime, bool addRemainingSuffix) +{ + Assert( outputBufferSizeInBytes >= sizeof(output[0]) ); + Assert(lastAnalogValueUpdateTime <= currentTime); + output[0] = 0; + + // calculate pre-extrapolation values + float timeElapsed = lastAnalogValueUpdateTime - startTime; + float totalTime = timeElapsed / currentAnalogValue; + + // calculate seconds + int secondsRemaining = (int)(totalTime - timeElapsed); + if (lastAnalogValueUpdateTime < currentTime) + { + // old update, extrapolate + float analogValueRate = currentAnalogValue / timeElapsed; + float extrapolatedAnalogValue = analogValueRate * (currentTime - startTime); + float extrapolatedTotalTime = (currentTime - startTime) / extrapolatedAnalogValue; + secondsRemaining = (int)(extrapolatedTotalTime - timeElapsed); + } + // if there's some time, make sure it's at least one second left + if ( secondsRemaining == 0 && ( ( totalTime - timeElapsed ) > 0 ) ) + { + secondsRemaining = 1; + } + + // calculate minutes + int minutesRemaining = 0; + while (secondsRemaining >= 60) + { + minutesRemaining++; + secondsRemaining -= 60; + } + + char minutesBuf[16]; + Q_snprintf(minutesBuf, sizeof( minutesBuf ), "%d", minutesRemaining); + char secondsBuf[16]; + Q_snprintf(secondsBuf, sizeof( secondsBuf ), "%d", secondsRemaining); + + if (minutesRemaining > 0) + { + wchar_t unicodeMinutes[16]; + g_pVGuiLocalize->ConvertANSIToUnicode(minutesBuf, unicodeMinutes, sizeof( unicodeMinutes )); + wchar_t unicodeSeconds[16]; + g_pVGuiLocalize->ConvertANSIToUnicode(secondsBuf, unicodeSeconds, sizeof( unicodeSeconds )); + + const char *unlocalizedString = "#vgui_TimeLeftMinutesSeconds"; + if (minutesRemaining == 1 && secondsRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftMinuteSecond"; + } + else if (minutesRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftMinuteSeconds"; + } + else if (secondsRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftMinutesSecond"; + } + + char unlocString[64]; + Q_strncpy(unlocString, unlocalizedString,sizeof( unlocString )); + if (addRemainingSuffix) + { + Q_strncat(unlocString, "Remaining", sizeof(unlocString ), COPY_ALL_CHARACTERS); + } + g_pVGuiLocalize->ConstructString(output, outputBufferSizeInBytes, g_pVGuiLocalize->Find(unlocString), 2, unicodeMinutes, unicodeSeconds); + + } + else if (secondsRemaining > 0) + { + wchar_t unicodeSeconds[16]; + g_pVGuiLocalize->ConvertANSIToUnicode(secondsBuf, unicodeSeconds, sizeof( unicodeSeconds )); + + const char *unlocalizedString = "#vgui_TimeLeftSeconds"; + if (secondsRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftSecond"; + } + char unlocString[64]; + Q_strncpy(unlocString, unlocalizedString,sizeof(unlocString)); + if (addRemainingSuffix) + { + Q_strncat(unlocString, "Remaining",sizeof(unlocString), COPY_ALL_CHARACTERS); + } + g_pVGuiLocalize->ConstructString(output, outputBufferSizeInBytes, g_pVGuiLocalize->Find(unlocString), 1, unicodeSeconds); + } + else + { + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void AnalogBar::SetBarInset( int pixels ) +{ + m_iBarInset = pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int AnalogBar::GetBarInset( void ) +{ + return m_iBarInset; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnalogBar::ApplySettings(KeyValues *inResourceData) +{ + _analogValue = inResourceData->GetFloat("analogValue", 0.0f); + + const char *dialogVar = inResourceData->GetString("variable", ""); + if (dialogVar && *dialogVar) + { + m_pszDialogVar = new char[strlen(dialogVar) + 1]; + strcpy(m_pszDialogVar, dialogVar); + } + + BaseClass::ApplySettings(inResourceData); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnalogBar::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + outResourceData->SetFloat("analogValue", _analogValue ); + + if (m_pszDialogVar) + { + outResourceData->SetString("variable", m_pszDialogVar); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a string description of the panel fields for use in the UI +//----------------------------------------------------------------------------- +const char *AnalogBar::GetDescription( void ) +{ + static char buf[1024]; + _snprintf(buf, sizeof(buf), "%s, string analogValue, string variable", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: updates analogValue bar bases on values +//----------------------------------------------------------------------------- +void AnalogBar::OnDialogVariablesChanged(KeyValues *dialogVariables) +{ + if (m_pszDialogVar) + { + int val = dialogVariables->GetInt(m_pszDialogVar, -1); + if (val >= 0.0f) + { + SetAnalogValue(val / 100.0f); + } + } +} + + +DECLARE_BUILD_FACTORY( ContinuousAnalogBar ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ContinuousAnalogBar::ContinuousAnalogBar(Panel *parent, const char *panelName) : AnalogBar(parent, panelName) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ContinuousAnalogBar::Paint() +{ + int x = 0, y = 0; + int wide, tall; + GetSize(wide, tall); + + surface()->DrawSetColor(GetFgColor()); + + switch( m_iAnalogValueDirection ) + { + case PROGRESS_EAST: + surface()->DrawFilledRect( x, y, x + (int)( wide * _analogValue ), y + tall ); + break; + + case PROGRESS_WEST: + surface()->DrawFilledRect( x + (int)( wide * ( 1.0f - _analogValue ) ), y, x + wide, y + tall ); + break; + + case PROGRESS_NORTH: + surface()->DrawFilledRect( x, y + (int)( tall * ( 1.0f - _analogValue ) ), x + wide, y + tall ); + break; + + case PROGRESS_SOUTH: + surface()->DrawFilledRect( x, y, x + wide, y + (int)( tall * _analogValue ) ); + break; + } +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/AnimatingImagePanel.cpp b/vgui2/vgui_controls/AnimatingImagePanel.cpp new file mode 100644 index 0000000..65c4798 --- /dev/null +++ b/vgui2/vgui_controls/AnimatingImagePanel.cpp @@ -0,0 +1,216 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> +#define PROTECTED_THINGS_DISABLE + +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/IImage.h> +#include <vgui/IVGui.h> +#include <KeyValues.h> + +#include <vgui_controls/AnimatingImagePanel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( AnimatingImagePanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +AnimatingImagePanel::AnimatingImagePanel(Panel *parent, const char *name) : Panel(parent, name) +{ + m_iCurrentImage = 0; + m_iFrameTimeMillis = 100; // 10Hz frame rate + m_iNextFrameTime = 0; + m_pImageName = NULL; + m_bFiltered = false; + m_bScaleImage = false; + m_bAnimating = false; + ivgui()->AddTickSignal(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: Layout the panel for drawing. +//----------------------------------------------------------------------------- +void AnimatingImagePanel::PerformLayout() +{ + Panel::PerformLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add an image to the end of the list of animations +//----------------------------------------------------------------------------- +void AnimatingImagePanel::AddImage(IImage *image) +{ + m_Frames.AddToTail(image); + + if ( !m_bScaleImage && image != NULL ) + { + int wide,tall; + image->GetSize(wide,tall); + SetSize(wide,tall); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Load a set of animations by name. +// Input: +// baseName: is the name of the animations without their frame number or +// file extension, (e.g. c1.tga becomes just c.) +// framecount: number of frames in the animation +//----------------------------------------------------------------------------- +void AnimatingImagePanel::LoadAnimation(const char *baseName, int frameCount) +{ + m_Frames.RemoveAll(); + for (int i = 1; i <= frameCount; i++) + { + char imageName[512]; + Q_snprintf(imageName, sizeof( imageName ), "%s%d", baseName, i); + AddImage(scheme()->GetImage(imageName, m_bFiltered)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the current image +//----------------------------------------------------------------------------- +void AnimatingImagePanel::PaintBackground() +{ + if ( m_Frames.IsValidIndex( m_iCurrentImage ) && m_Frames[m_iCurrentImage] != NULL ) + { + IImage *pImage = m_Frames[m_iCurrentImage]; + + surface()->DrawSetColor( 255, 255, 255, 255 ); + pImage->SetPos(0, 0); + + if ( m_bScaleImage ) + { + // Image size is stored in the bitmap, so temporarily set its size + // to our panel size and then restore after we draw it. + + int imageWide, imageTall; + pImage->GetSize( imageWide, imageTall ); + + int wide, tall; + GetSize( wide, tall ); + pImage->SetSize( wide, tall ); + + pImage->SetColor( Color( 255,255,255,255 ) ); + pImage->Paint(); + + pImage->SetSize( imageWide, imageTall ); + } + else + { + pImage->Paint(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame the panel is visible +//----------------------------------------------------------------------------- +void AnimatingImagePanel::OnTick() +{ + if (m_bAnimating && system()->GetTimeMillis() >= m_iNextFrameTime) + { + m_iNextFrameTime = system()->GetTimeMillis() + m_iFrameTimeMillis; + m_iCurrentImage++; + if (!m_Frames.IsValidIndex(m_iCurrentImage)) + { + m_iCurrentImage = 0; + } + Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get control settings for editing +// Output: outResourceData- a set of keyvalues of imagenames. +//----------------------------------------------------------------------------- +void AnimatingImagePanel::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + if (m_pImageName) + { + outResourceData->SetString("image", m_pImageName); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Applies resource settings +//----------------------------------------------------------------------------- +void AnimatingImagePanel::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + + const char *imageName = inResourceData->GetString("image", NULL); + if (imageName) + { + m_bScaleImage = ( inResourceData->GetInt( "scaleImage", 0 ) == 1 ); + + delete [] m_pImageName; + int len = Q_strlen(imageName) + 1; + m_pImageName = new char[len]; + Q_strncpy(m_pImageName, imageName, len); + + // add in the command + LoadAnimation(m_pImageName, inResourceData->GetInt("frames")); + } + + m_iFrameTimeMillis = inResourceData->GetInt( "anim_framerate", 100 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get editing details +//----------------------------------------------------------------------------- +const char *AnimatingImagePanel::GetDescription() +{ + static char buf[1024]; + Q_snprintf(buf, sizeof(buf), "%s, string image", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: Starts the image doing its animation +//----------------------------------------------------------------------------- +void AnimatingImagePanel::StartAnimation() +{ + m_bAnimating = true; +// ivgui()->AddTickSignal(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: Stops the images animation +//----------------------------------------------------------------------------- +void AnimatingImagePanel::StopAnimation() +{ + m_bAnimating = false; +// ivgui()->RemoveTickSignal(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the animation to the start of the sequence. +//----------------------------------------------------------------------------- +void AnimatingImagePanel::ResetAnimation(int frame) +{ + if(m_Frames.IsValidIndex(frame)) + { + m_iCurrentImage = frame; + } + else + { + m_iCurrentImage = 0; + } + Repaint(); +} diff --git a/vgui2/vgui_controls/AnimationController.cpp b/vgui2/vgui_controls/AnimationController.cpp new file mode 100644 index 0000000..cee769a --- /dev/null +++ b/vgui2/vgui_controls/AnimationController.cpp @@ -0,0 +1,1846 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#pragma warning( disable : 4244 ) // conversion from 'double' to 'float', possible loss of data + +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/IVGui.h> +#include <KeyValues.h> +#include <vgui_controls/AnimationController.h> +#include "filesystem.h" +#include "filesystem_helpers.h" + +#include <stdio.h> +#include <math.h> +#include "mempool.h" +#include "utldict.h" +#include "mathlib/mathlib.h" +#include "characterset.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/dbg.h> +// for SRC +#include <vstdlib/random.h> +#include <tier0/memdbgon.h> + +using namespace vgui; + +static CUtlSymbolTable g_ScriptSymbols(0, 128, true); + +// singleton accessor for animation controller for use by the vgui controls +namespace vgui +{ +AnimationController *GetAnimationController() +{ + static AnimationController *s_pAnimationController = new AnimationController(NULL); + return s_pAnimationController; +} +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +AnimationController::AnimationController(Panel *parent) : BaseClass(parent, NULL) +{ + m_hSizePanel = 0; + m_nScreenBounds[ 0 ] = m_nScreenBounds[ 1 ] = -1; + m_nScreenBounds[ 2 ] = m_nScreenBounds[ 3 ] = -1; + + m_bAutoReloadScript = false; + + // always invisible + SetVisible(false); + + SetProportional(true); + + // get the names of common types + m_sPosition = g_ScriptSymbols.AddString("position"); + m_sSize = g_ScriptSymbols.AddString("size"); + m_sFgColor = g_ScriptSymbols.AddString("fgcolor"); + m_sBgColor = g_ScriptSymbols.AddString("bgcolor"); + + m_sXPos = g_ScriptSymbols.AddString("xpos"); + m_sYPos = g_ScriptSymbols.AddString("ypos"); + m_sWide = g_ScriptSymbols.AddString("wide"); + m_sTall = g_ScriptSymbols.AddString("tall"); + + m_sModelPos = g_ScriptSymbols.AddString( "model_pos" ); + + m_flCurrentTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +AnimationController::~AnimationController() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Sets which script file to use +//----------------------------------------------------------------------------- +bool AnimationController::SetScriptFile( VPANEL sizingPanel, const char *fileName, bool wipeAll /*=false*/ ) +{ + m_hSizePanel = sizingPanel; + + if ( wipeAll ) + { + // clear the current script + m_Sequences.RemoveAll(); + m_ScriptFileNames.RemoveAll(); + + CancelAllAnimations(); + } + + // Store off this filename for reloading later on (if we don't have it already) + UtlSymId_t sFilename = g_ScriptSymbols.AddString( fileName ); + if ( m_ScriptFileNames.Find( sFilename ) == m_ScriptFileNames.InvalidIndex() ) + { + m_ScriptFileNames.AddToTail( sFilename ); + } + + UpdateScreenSize(); + + // load the new script file + return LoadScriptFile( fileName ); +} + +//----------------------------------------------------------------------------- +// Purpose: reloads the currently set script file +//----------------------------------------------------------------------------- +void AnimationController::ReloadScriptFile() +{ + // Clear all current sequences + m_Sequences.RemoveAll(); + + UpdateScreenSize(); + + // Reload each file we've loaded + for ( int i = 0; i < m_ScriptFileNames.Count(); i++ ) + { + const char *lpszFilename = g_ScriptSymbols.String( m_ScriptFileNames[i] ); + if ( strlen( lpszFilename ) > 0) + { + if ( LoadScriptFile( lpszFilename ) == false ) + { + Assert( 0 ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: loads a script file from disk +//----------------------------------------------------------------------------- +bool AnimationController::LoadScriptFile(const char *fileName) +{ + FileHandle_t f = g_pFullFileSystem->Open(fileName, "rt"); + if (!f) + { + Warning("Couldn't find script file %s\n", fileName); + return false; + } + + // read the whole thing into memory + int size = g_pFullFileSystem->Size(f); + // read into temporary memory block + int nBufSize = size+1; + if ( IsXbox() ) + { + nBufSize = AlignValue( nBufSize, 512 ); + } + char *pMem = (char *)malloc(nBufSize); + int bytesRead = g_pFullFileSystem->ReadEx(pMem, nBufSize, size, f); + Assert(bytesRead <= size); + pMem[bytesRead] = 0; + g_pFullFileSystem->Close(f); + // parse + bool success = ParseScriptFile(pMem, bytesRead); + free(pMem); + return success; +} + +AnimationController::RelativeAlignmentLookup AnimationController::g_AlignmentLookup[] = +{ + { AnimationController::a_northwest , "northwest" }, + { AnimationController::a_north , "north" }, + { AnimationController::a_northeast , "northeast" }, + { AnimationController::a_west , "west" }, + { AnimationController::a_center , "center" }, + { AnimationController::a_east , "east" }, + { AnimationController::a_southwest , "southwest" }, + { AnimationController::a_south , "south" }, + { AnimationController::a_southeast , "southeast" }, + + { AnimationController::a_northwest , "nw" }, + { AnimationController::a_north , "n" }, + { AnimationController::a_northeast , "ne" }, + { AnimationController::a_west , "w" }, + { AnimationController::a_center , "c" }, + { AnimationController::a_east , "e" }, + { AnimationController::a_southwest , "sw" }, + { AnimationController::a_south , "s" }, + { AnimationController::a_southeast , "se" }, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +AnimationController::RelativeAlignment AnimationController::LookupAlignment( char const *token ) +{ + int c = ARRAYSIZE( g_AlignmentLookup ); + + for ( int i = 0; i < c; i++ ) + { + if ( !Q_stricmp( token, g_AlignmentLookup[ i ].name ) ) + { + return g_AlignmentLookup[ i ].align; + } + } + + return AnimationController::a_northwest; +} + +//----------------------------------------------------------------------------- +// Purpose: Parse position including right edge and center adjustment out of a +// token. This is relative to the screen +//----------------------------------------------------------------------------- +void AnimationController::SetupPosition( AnimCmdAnimate_t& cmd, float *output, char const *psz, int screendimension ) +{ + bool r = false, c = false; + int pos; + if ( psz[0] == '(' ) + { + psz++; + + if ( Q_strstr( psz, ")" ) ) + { + char sz[ 256 ]; + Q_strncpy( sz, psz, sizeof( sz ) ); + + char *colon = Q_strstr( sz, ":" ); + if ( colon ) + { + *colon = 0; + + RelativeAlignment ra = LookupAlignment( sz ); + + colon++; + + char *panelName = colon; + char *panelEnd = Q_strstr( panelName, ")" ); + if ( panelEnd ) + { + *panelEnd = 0; + + if ( Q_strlen( panelName ) > 0 ) + { + // + cmd.align.relativePosition = true; + cmd.align.alignPanel = g_ScriptSymbols.AddString(panelName); + cmd.align.alignment = ra; + } + } + } + + psz = Q_strstr( psz, ")" ) + 1; + } + } + else if (psz[0] == 'r' || psz[0] == 'R') + { + r = true; + psz++; + } + else if (psz[0] == 'c' || psz[0] == 'C') + { + c = true; + psz++; + } + + // get the number + pos = atoi(psz); + + // scale the values + if (IsProportional()) + { + pos = vgui::scheme()->GetProportionalScaledValueEx( GetScheme(), pos ); + } + + // adjust the positions + if (r) + { + pos = screendimension - pos; + } + if (c) + { + pos = (screendimension / 2) + pos; + } + + // set the value + *output = static_cast<float>( pos ); +} + + +//----------------------------------------------------------------------------- +// Purpose: parses a script into sequences +//----------------------------------------------------------------------------- +bool AnimationController::ParseScriptFile(char *pMem, int length) +{ + // get the scheme (for looking up color names) + IScheme *scheme = vgui::scheme()->GetIScheme(GetScheme()); + + // get our screen size (for left/right/center alignment) + int screenWide = m_nScreenBounds[ 2 ]; + int screenTall = m_nScreenBounds[ 3 ]; + + // start by getting the first token + char token[512]; + pMem = ParseFile(pMem, token, NULL); + while (token[0]) + { + bool bAccepted = true; + + // should be 'event' + if (stricmp(token, "event")) + { + Warning("Couldn't parse script file: expected 'event', found '%s'\n", token); + return false; + } + + // get the event name + pMem = ParseFile(pMem, token, NULL); + if (strlen(token) < 1) + { + Warning("Couldn't parse script file: expected <event name>, found nothing\n"); + return false; + } + + int seqIndex; + UtlSymId_t nameIndex = g_ScriptSymbols.AddString(token); + + // Create a new sequence + seqIndex = m_Sequences.AddToTail(); + AnimSequence_t &seq = m_Sequences[seqIndex]; + seq.name = nameIndex; + seq.duration = 0.0f; + + // get the open brace or a conditional + pMem = ParseFile(pMem, token, NULL); + if ( Q_stristr( token, "[$" ) ) + { + bAccepted = EvaluateConditional( token ); + + // now get the open brace + pMem = ParseFile(pMem, token, NULL); + } + + if (stricmp(token, "{")) + { + Warning("Couldn't parse script sequence '%s': expected '{', found '%s'\n", g_ScriptSymbols.String(seq.name), token); + return false; + } + + // walk the commands + while (token[0]) + { + // get the command type + pMem = ParseFile(pMem, token, NULL); + + // skip out when we hit the end of the sequence + if (token[0] == '}') + break; + + // create a new command + int cmdIndex = seq.cmdList.AddToTail(); + AnimCommand_t &animCmd = seq.cmdList[cmdIndex]; + memset(&animCmd, 0, sizeof(animCmd)); + if (!stricmp(token, "animate")) + { + animCmd.commandType = CMD_ANIMATE; + // parse out the animation commands + AnimCmdAnimate_t &cmdAnimate = animCmd.cmdData.animate; + // panel to manipulate + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.panel = g_ScriptSymbols.AddString(token); + // variable to change + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.variable = g_ScriptSymbols.AddString(token); + // target value + pMem = ParseFile(pMem, token, NULL); + if (cmdAnimate.variable == m_sPosition) + { + // Get first token + SetupPosition( cmdAnimate, &cmdAnimate.target.a, token, screenWide ); + + // Get second token from "token" + char token2[32]; + char *psz = ParseFile(token, token2, NULL); + psz = ParseFile(psz, token2, NULL); + psz = token2; + + // Position Y goes into ".b" + SetupPosition( cmdAnimate, &cmdAnimate.target.b, psz, screenTall ); + } + else if ( cmdAnimate.variable == m_sXPos ) + { + // XPos and YPos both use target ".a" + SetupPosition( cmdAnimate, &cmdAnimate.target.a, token, screenWide ); + } + else if ( cmdAnimate.variable == m_sYPos ) + { + // XPos and YPos both use target ".a" + SetupPosition( cmdAnimate, &cmdAnimate.target.a, token, screenTall ); + } + else + { + // parse the floating point values right out + if (0 == sscanf(token, "%f %f %f %f", &cmdAnimate.target.a, &cmdAnimate.target.b, &cmdAnimate.target.c, &cmdAnimate.target.d)) + { + //============================================================================= + // HPE_BEGIN: + // [pfreese] Improved handling colors not defined in scheme + //============================================================================= + + // could be referencing a value in the scheme file, lookup + Color default_invisible_black(0, 0, 0, 0); + Color col = scheme->GetColor(token, default_invisible_black); + + // we don't have a way of seeing if the color is not declared in the scheme, so we use this + // silly method of trying again with a different default to see if we get the fallback again + if (col == default_invisible_black) + { + Color error_pink(255, 0, 255, 255); // make it extremely obvious if a scheme lookup fails + col = scheme->GetColor(token, error_pink); + + // commented out for Soldier/Demo release...(getting spammed in console) + // we'll try to figure this out after the update is out +// if (col == error_pink) +// { +// Warning("Missing color in scheme: %s\n", token); +// } + } + + //============================================================================= + // HPE_END + //============================================================================= + + cmdAnimate.target.a = col[0]; + cmdAnimate.target.b = col[1]; + cmdAnimate.target.c = col[2]; + cmdAnimate.target.d = col[3]; + } + } + + // fix up scale + if (cmdAnimate.variable == m_sSize) + { + if (IsProportional()) + { + cmdAnimate.target.a = static_cast<float>( vgui::scheme()->GetProportionalScaledValueEx(GetScheme(), cmdAnimate.target.a) ); + cmdAnimate.target.b = static_cast<float>( vgui::scheme()->GetProportionalScaledValueEx(GetScheme(), cmdAnimate.target.b) ); + } + } + else if (cmdAnimate.variable == m_sWide || + cmdAnimate.variable == m_sTall ) + { + if (IsProportional()) + { + // Wide and tall both use.a + cmdAnimate.target.a = static_cast<float>( vgui::scheme()->GetProportionalScaledValueEx(GetScheme(), cmdAnimate.target.a) ); + } + } + + // interpolation function + pMem = ParseFile(pMem, token, NULL); + if (!stricmp(token, "Accel")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_ACCEL; + } + else if (!stricmp(token, "Deaccel")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_DEACCEL; + } + else if ( !stricmp(token, "Spline")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_SIMPLESPLINE; + } + else if (!stricmp(token,"Pulse")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_PULSE; + // frequencey + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.interpolationParameter = (float)atof(token); + } + else if (!stricmp(token,"Bias")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_BIAS; + // bias + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.interpolationParameter = (float)atof(token); + } + else if (!stricmp(token,"Gain")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_GAIN; + // bias + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.interpolationParameter = (float)atof(token); + } + else if ( !stricmp( token, "Flicker")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_FLICKER; + // noiseamount + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.interpolationParameter = (float)atof(token); + } + else if (!stricmp(token, "Bounce")) + { + cmdAnimate.interpolationFunction = INTERPOLATOR_BOUNCE; + } + else + { + cmdAnimate.interpolationFunction = INTERPOLATOR_LINEAR; + } + // start time + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.startTime = (float)atof(token); + // duration + pMem = ParseFile(pMem, token, NULL); + cmdAnimate.duration = (float)atof(token); + // check max duration + if (cmdAnimate.startTime + cmdAnimate.duration > seq.duration) + { + seq.duration = cmdAnimate.startTime + cmdAnimate.duration; + } + } + else if (!stricmp(token, "runevent")) + { + animCmd.commandType = CMD_RUNEVENT; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if (!stricmp(token, "runeventchild")) + { + animCmd.commandType = CMD_RUNEVENTCHILD; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if (!stricmp(token, "firecommand")) + { + animCmd.commandType = CMD_FIRECOMMAND; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + } + else if ( !stricmp(token, "playsound") ) + { + animCmd.commandType = CMD_PLAYSOUND; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + } + else if (!stricmp(token, "setvisible")) + { + animCmd.commandType = CMD_SETVISIBLE; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable2 = atoi(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if (!stricmp(token, "setinputenabled")) + { + animCmd.commandType = CMD_SETINPUTENABLED; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable2 = atoi(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if (!stricmp(token, "stopevent")) + { + animCmd.commandType = CMD_STOPEVENT; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if (!stricmp(token, "StopPanelAnimations")) + { + animCmd.commandType = CMD_STOPPANELANIMATIONS; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if (!stricmp(token, "stopanimation")) + { + animCmd.commandType = CMD_STOPANIMATION; + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if ( !stricmp( token, "SetFont" )) + { + animCmd.commandType = CMD_SETFONT; + // Panel name + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + // Font parameter + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + // Font name from scheme + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable2 = g_ScriptSymbols.AddString(token); + + // Set time + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if ( !stricmp( token, "SetTexture" )) + { + animCmd.commandType = CMD_SETTEXTURE; + // Panel name + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + // Texture Id + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + // material name + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable2 = g_ScriptSymbols.AddString(token); + + // Set time + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else if ( !stricmp( token, "SetString" )) + { + animCmd.commandType = CMD_SETSTRING; + // Panel name + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.event = g_ScriptSymbols.AddString(token); + // String variable name + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable = g_ScriptSymbols.AddString(token); + // String value to set + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.variable2 = g_ScriptSymbols.AddString(token); + + // Set time + pMem = ParseFile(pMem, token, NULL); + animCmd.cmdData.runEvent.timeDelay = (float)atof(token); + } + else + { + Warning("Couldn't parse script sequence '%s': expected <anim command>, found '%s'\n", g_ScriptSymbols.String(seq.name), token); + return false; + } + + // Look ahead one token for a conditional + char *peek = ParseFile(pMem, token, NULL); + if ( Q_stristr( token, "[$" ) ) + { + if ( !EvaluateConditional( token ) ) + { + seq.cmdList.Remove( cmdIndex ); + } + pMem = peek; + } + } + + if ( bAccepted ) + { + // Attempt to find a collision in the sequences, replacing the old one if found + int seqIterator; + for ( seqIterator = 0; seqIterator < m_Sequences.Count()-1; seqIterator++ ) + { + if ( m_Sequences[seqIterator].name == nameIndex ) + { + // Get rid of it, we're overriding it + m_Sequences.Remove( seqIndex ); + break; + } + } + } + else + { + // Dump the entire sequence + m_Sequences.Remove( seqIndex ); + } + + // get the next token, if any + pMem = ParseFile(pMem, token, NULL); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: checks all posted animation events, firing if time +//----------------------------------------------------------------------------- +void AnimationController::UpdatePostedMessages(bool bRunToCompletion) +{ + CUtlVector<RanEvent_t> eventsRanThisFrame; + + // check all posted messages + for (int i = 0; i < m_PostedMessages.Count(); i++) + { + PostedMessage_t &msgRef = m_PostedMessages[i]; + + if ( !msgRef.canBeCancelled && bRunToCompletion ) + continue; + + if (m_flCurrentTime < msgRef.startTime && !bRunToCompletion) + continue; + + // take a copy of th message + PostedMessage_t msg = msgRef; + + // remove the event + // do this before handling the message because the message queue may be messed with + m_PostedMessages.Remove(i); + // reset the count, start the whole queue again + i = -1; + + if ( msg.parent.Get() == NULL ) + continue; + + // handle the event + switch (msg.commandType) + { + case CMD_RUNEVENT: + { + RanEvent_t curEvent; + curEvent.pParent = NULL; + curEvent.event = msg.event; + + curEvent.pParent = msg.parent.Get(); + + // run the event, but only if we haven't already run it this frame, for this parent + if (!eventsRanThisFrame.HasElement(curEvent)) + { + eventsRanThisFrame.AddToTail(curEvent); + RunCmd_RunEvent(msg); + } + } + break; + case CMD_RUNEVENTCHILD: + { + RanEvent_t curEvent; + curEvent.pParent = NULL; + curEvent.event = msg.event; + + curEvent.pParent = msg.parent.Get()->FindChildByName( g_ScriptSymbols.String(msg.variable), true ); + msg.parent = curEvent.pParent; + + // run the event, but only if we haven't already run it this frame, for this parent + if (!eventsRanThisFrame.HasElement(curEvent)) + { + eventsRanThisFrame.AddToTail(curEvent); + RunCmd_RunEvent(msg); + } + } + break; + case CMD_FIRECOMMAND: + { + msg.parent->OnCommand( g_ScriptSymbols.String(msg.variable) ); + } + break; + case CMD_PLAYSOUND: + { + vgui::surface()->PlaySound( g_ScriptSymbols.String(msg.variable) ); + } + break; + case CMD_SETVISIBLE: + { + Panel* pPanel = msg.parent.Get()->FindChildByName( g_ScriptSymbols.String(msg.variable), true ); + if ( pPanel ) + { + pPanel->SetVisible( msg.variable2 == 1 ); + } + } + break; + case CMD_SETINPUTENABLED: + { + Panel* pPanel = msg.parent.Get()->FindChildByName( g_ScriptSymbols.String(msg.variable), true ); + if ( pPanel ) + { + pPanel->SetMouseInputEnabled( msg.variable2 == 1 ); + pPanel->SetKeyBoardInputEnabled( msg.variable2 == 1 ); + } + } + break; + case CMD_STOPEVENT: + RunCmd_StopEvent(msg); + break; + case CMD_STOPPANELANIMATIONS: + RunCmd_StopPanelAnimations(msg); + break; + case CMD_STOPANIMATION: + RunCmd_StopAnimation(msg); + break; + case CMD_SETFONT: + RunCmd_SetFont(msg); + break; + case CMD_SETTEXTURE: + RunCmd_SetTexture(msg); + break; + case CMD_SETSTRING: + RunCmd_SetString( msg ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: runs the current animations +//----------------------------------------------------------------------------- +void AnimationController::UpdateActiveAnimations(bool bRunToCompletion) +{ + // iterate all the currently active animations + for (int i = 0; i < m_ActiveAnimations.Count(); i++) + { + ActiveAnimation_t &anim = m_ActiveAnimations[i]; + + if ( !anim.canBeCancelled && bRunToCompletion ) + continue; + + // see if the anim is ready to start + if (m_flCurrentTime < anim.startTime && !bRunToCompletion) + continue; + + if (!anim.panel.Get()) + { + // panel is gone, remove the animation + m_ActiveAnimations.Remove(i); + --i; + continue; + } + + if (!anim.started && !bRunToCompletion) + { + // start the animation from the current value + anim.startValue = GetValue(anim, anim.panel, anim.variable); + anim.started = true; + + // Msg( "Starting animation of %s => %.2f (seq: %s) (%s)\n", g_ScriptSymbols.String(anim.variable), anim.endValue.a, g_ScriptSymbols.String(anim.seqName), anim.panel->GetName()); + } + + // get the interpolated value + Value_t val; + if (m_flCurrentTime >= anim.endTime || bRunToCompletion) + { + // animation is done, use the last value + val = anim.endValue; + } + else + { + // get the interpolated value + val = GetInterpolatedValue(anim.interpolator, anim.interpolatorParam, m_flCurrentTime, anim.startTime, anim.endTime, anim.startValue, anim.endValue); + } + + // apply the new value to the panel + SetValue(anim, anim.panel, anim.variable, val); + + // Msg( "Animate value: %s => %.2f for panel '%s'\n", g_ScriptSymbols.String(anim.variable), val.a, anim.panel->GetName()); + + // see if we can remove the animation + if (m_flCurrentTime >= anim.endTime || bRunToCompletion) + { + m_ActiveAnimations.Remove(i); + --i; + } + } +} + +bool AnimationController::UpdateScreenSize() +{ + // get our screen size (for left/right/center alignment) + int screenWide, screenTall; + int sx = 0, sy = 0; + if ( m_hSizePanel != 0 ) + { + ipanel()->GetSize( m_hSizePanel, screenWide, screenTall ); + ipanel()->GetPos( m_hSizePanel, sx, sy ); + } + else + { + surface()->GetScreenSize(screenWide, screenTall); + } + + bool changed = m_nScreenBounds[ 0 ] != sx || + m_nScreenBounds[ 1 ] != sy || + m_nScreenBounds[ 2 ] != screenWide || + m_nScreenBounds[ 3 ] != screenTall; + + m_nScreenBounds[ 0 ] = sx; + m_nScreenBounds[ 1 ] = sy; + m_nScreenBounds[ 2 ] = screenWide; + m_nScreenBounds[ 3 ] = screenTall; + + return changed; +} +//----------------------------------------------------------------------------- +// Purpose: runs a frame of animation +//----------------------------------------------------------------------------- +void AnimationController::UpdateAnimations( float currentTime ) +{ + m_flCurrentTime = currentTime; + + if ( UpdateScreenSize() && m_ScriptFileNames.Count() ) + { + RunAllAnimationsToCompletion(); + ReloadScriptFile(); + } + + UpdatePostedMessages(false); + UpdateActiveAnimations(false); +} + +//----------------------------------------------------------------------------- +// Purpose: plays all animations to completion instantly +//----------------------------------------------------------------------------- +void AnimationController::RunAllAnimationsToCompletion() +{ + // Msg( "AnimationController::RunAllAnimationsToCompletion()\n" ); + UpdatePostedMessages(true); + UpdateActiveAnimations(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Stops all current animations +//----------------------------------------------------------------------------- +void AnimationController::CancelAllAnimations() +{ + // Msg( "AnimationController::CancelAllAnimations()\n" ); + + FOR_EACH_VEC_BACK( m_ActiveAnimations, i ) + { + if ( m_ActiveAnimations[i].canBeCancelled ) + m_ActiveAnimations.Remove( i ); + } + + FOR_EACH_VEC_BACK(m_PostedMessages, i) + { + if (m_PostedMessages[i].canBeCancelled) + m_PostedMessages.Remove(i); + } +} + +//----------------------------------------------------------------------------- +// Purpose: produces an interpolated value +//----------------------------------------------------------------------------- +AnimationController::Value_t AnimationController::GetInterpolatedValue(int interpolator, float interpolatorParam, float currentTime, float startTime, float endTime, Value_t &startValue, Value_t &endValue) +{ + // calculate how far we are into the animation + float pos = (currentTime - startTime) / (endTime - startTime); + + // adjust the percentage through by the interpolation function + switch (interpolator) + { + case INTERPOLATOR_ACCEL: + pos *= pos; + break; + case INTERPOLATOR_DEACCEL: + pos = sqrtf(pos); + break; + case INTERPOLATOR_SIMPLESPLINE: + pos = SimpleSpline( pos ); + break; + case INTERPOLATOR_PULSE: + // Make sure we end at 1.0, so use cosine + pos = 0.5f + 0.5f * ( cos( pos * 2.0f * M_PI * interpolatorParam ) ); + break; + case INTERPOLATOR_BIAS: + pos = Bias( pos, interpolatorParam ); + break; + case INTERPOLATOR_GAIN: + pos = Gain( pos, interpolatorParam ); + break; + case INTERPOLATOR_FLICKER: + if ( RandomFloat( 0.0f, 1.0f ) < interpolatorParam ) + { + pos = 1.0f; + } + else + { + pos = 0.0f; + } + break; + case INTERPOLATOR_BOUNCE: + { + // fall from startValue to endValue, bouncing a few times and settling out at endValue + const float hit1 = 0.33f; + const float hit2 = 0.67f; + const float hit3 = 1.0f; + + if ( pos < hit1 ) + { + pos = 1.0f - sin( M_PI * pos / hit1 ); + } + else if ( pos < hit2 ) + { + pos = 0.5f + 0.5f * ( 1.0f - sin( M_PI * ( pos - hit1 ) / ( hit2 - hit1 ) ) ); + } + else + { + pos = 0.8f + 0.2f * ( 1.0f - sin( M_PI * ( pos - hit2 ) / ( hit3 - hit2 ) ) ); + } + break; + } + case INTERPOLATOR_LINEAR: + default: + break; + } + + // calculate the value + Value_t val; + val.a = ((endValue.a - startValue.a) * pos) + startValue.a; + val.b = ((endValue.b - startValue.b) * pos) + startValue.b; + val.c = ((endValue.c - startValue.c) * pos) + startValue.c; + val.d = ((endValue.d - startValue.d) * pos) + startValue.d; + return val; +} + +//----------------------------------------------------------------------------- +// Purpose: sets that the script file should be reloaded each time a script is ran +// used for development +//----------------------------------------------------------------------------- +void AnimationController::SetAutoReloadScript(bool state) +{ + m_bAutoReloadScript = state; +} + +//----------------------------------------------------------------------------- +// Purpose: starts an animation sequence script +//----------------------------------------------------------------------------- +bool AnimationController::StartAnimationSequence(const char *sequenceName, bool bCanBeCancelled ) +{ + // We support calling an animation on elements that are not the calling + // panel's children. Use the base parent to start the search. + + return StartAnimationSequence( GetParent(), sequenceName, bCanBeCancelled ); +} + +//----------------------------------------------------------------------------- +// Purpose: starts an animation sequence script +//----------------------------------------------------------------------------- +bool AnimationController::StartAnimationSequence(Panel *pWithinParent, const char *sequenceName, bool bCanBeCancelled ) +{ + Assert( pWithinParent ); + + if (m_bAutoReloadScript) + { + // Reload the script files + ReloadScriptFile(); + } + + // lookup the symbol for the name + UtlSymId_t seqName = g_ScriptSymbols.Find(sequenceName); + if (seqName == UTL_INVAL_SYMBOL) + return false; + + // Msg("Starting animation sequence %s\n", sequenceName); + + // remove the existing command from the queue + RemoveQueuedAnimationCommands(seqName, pWithinParent); + + // look through for the sequence + int i; + for (i = 0; i < m_Sequences.Count(); i++) + { + if (m_Sequences[i].name == seqName) + break; + } + if (i >= m_Sequences.Count()) + return false; + + // execute the sequence + for (int cmdIndex = 0; cmdIndex < m_Sequences[i].cmdList.Count(); cmdIndex++) + { + ExecAnimationCommand(seqName, m_Sequences[i].cmdList[cmdIndex], pWithinParent, bCanBeCancelled); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: stops an animation sequence script +//----------------------------------------------------------------------------- +bool AnimationController::StopAnimationSequence( Panel *pWithinParent, const char *sequenceName ) +{ + Assert( pWithinParent ); + + // lookup the symbol for the name + UtlSymId_t seqName = g_ScriptSymbols.Find( sequenceName ); + if (seqName == UTL_INVAL_SYMBOL) + return false; + + // remove the existing command from the queue + RemoveQueuedAnimationCommands( seqName, pWithinParent ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Runs a custom command from code, not from a script file +//----------------------------------------------------------------------------- +void AnimationController::CancelAnimationsForPanel( Panel *pWithinParent ) +{ + // Msg("Removing queued anims for sequence %s\n", g_ScriptSymbols.String(seqName)); + + // remove messages posted by this sequence + // if pWithinParent is specified, remove only messages under that parent + { + for (int i = 0; i < m_PostedMessages.Count(); i++) + { + if ( m_PostedMessages[i].parent == pWithinParent ) + { + m_PostedMessages.Remove(i); + --i; + } + } + } + + // remove all animations + // if pWithinParent is specified, remove only animations under that parent + for (int i = 0; i < m_ActiveAnimations.Count(); i++) + { + Panel *animPanel = m_ActiveAnimations[i].panel; + + if ( !animPanel ) + continue; + + Panel *foundPanel = pWithinParent->FindChildByName(animPanel->GetName(),true); + + if ( foundPanel != animPanel ) + continue; + + m_ActiveAnimations.Remove(i); + --i; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Runs a custom command from code, not from a script file +//----------------------------------------------------------------------------- +void AnimationController::RunAnimationCommand(vgui::Panel *panel, const char *variable, float targetValue, float startDelaySeconds, float duration, Interpolators_e interpolator, float animParameter /* = 0 */ ) +{ + // clear any previous animations of this variable + UtlSymId_t var = g_ScriptSymbols.AddString(variable); + RemoveQueuedAnimationByType(panel, var, UTL_INVAL_SYMBOL); + + // build a new animation + AnimCmdAnimate_t animateCmd; + memset(&animateCmd, 0, sizeof(animateCmd)); + animateCmd.panel = 0; + animateCmd.variable = var; + animateCmd.target.a = targetValue; + animateCmd.interpolationFunction = interpolator; + animateCmd.interpolationParameter = animParameter; + animateCmd.startTime = startDelaySeconds; + animateCmd.duration = duration; + + // start immediately + StartCmd_Animate(panel, 0, animateCmd, true); +} + +//----------------------------------------------------------------------------- +// Purpose: Runs a custom command from code, not from a script file +//----------------------------------------------------------------------------- +void AnimationController::RunAnimationCommand(vgui::Panel *panel, const char *variable, Color targetValue, float startDelaySeconds, float duration, Interpolators_e interpolator, float animParameter /* = 0 */ ) +{ + // clear any previous animations of this variable + UtlSymId_t var = g_ScriptSymbols.AddString(variable); + RemoveQueuedAnimationByType(panel, var, UTL_INVAL_SYMBOL); + + // build a new animation + AnimCmdAnimate_t animateCmd; + memset(&animateCmd, 0, sizeof(animateCmd)); + animateCmd.panel = 0; + animateCmd.variable = var; + animateCmd.target.a = targetValue[0]; + animateCmd.target.b = targetValue[1]; + animateCmd.target.c = targetValue[2]; + animateCmd.target.d = targetValue[3]; + animateCmd.interpolationFunction = interpolator; + animateCmd.interpolationParameter = animParameter; + animateCmd.startTime = startDelaySeconds; + animateCmd.duration = duration; + + // start immediately + StartCmd_Animate(panel, 0, animateCmd, true); +} + +//----------------------------------------------------------------------------- +// Purpose: gets the length of an animation sequence, in seconds +//----------------------------------------------------------------------------- +float AnimationController::GetAnimationSequenceLength(const char *sequenceName) +{ + // lookup the symbol for the name + UtlSymId_t seqName = g_ScriptSymbols.Find(sequenceName); + if (seqName == UTL_INVAL_SYMBOL) + return 0.0f; + + // look through for the sequence + int i; + for (i = 0; i < m_Sequences.Count(); i++) + { + if (m_Sequences[i].name == seqName) + break; + } + if (i >= m_Sequences.Count()) + return 0.0f; + + // sequence found + return m_Sequences[i].duration; +} + +//----------------------------------------------------------------------------- +// Purpose: removes an existing set of commands from the queue +//----------------------------------------------------------------------------- +void AnimationController::RemoveQueuedAnimationCommands(UtlSymId_t seqName, Panel *pWithinParent) +{ + // Msg("Removing queued anims for sequence %s\n", g_ScriptSymbols.String(seqName)); + + // remove messages posted by this sequence + // if pWithinParent is specified, remove only messages under that parent + {for (int i = 0; i < m_PostedMessages.Count(); i++) + { + if ( ( m_PostedMessages[i].seqName == seqName ) && + ( !pWithinParent || ( m_PostedMessages[i].parent == pWithinParent ) ) ) + { + m_PostedMessages.Remove(i); + --i; + } + }} + + // remove all animations + // if pWithinParent is specified, remove only animations under that parent + for (int i = 0; i < m_ActiveAnimations.Count(); i++) + { + if ( m_ActiveAnimations[i].seqName != seqName ) + continue; + + // panel this anim is on, m_ActiveAnimations[i].panel + if ( pWithinParent ) + { + Panel *animPanel = m_ActiveAnimations[i].panel; + + if ( !animPanel ) + continue; + + Panel *foundPanel = pWithinParent->FindChildByName(animPanel->GetName(),true); + + if ( foundPanel != animPanel ) + continue; + } + + m_ActiveAnimations.Remove(i); + --i; + } +} + +//----------------------------------------------------------------------------- +// Purpose: removes the specified queued animation +//----------------------------------------------------------------------------- +void AnimationController::RemoveQueuedAnimationByType(vgui::Panel *panel, UtlSymId_t variable, UtlSymId_t sequenceToIgnore) +{ + for (int i = 0; i < m_ActiveAnimations.Count(); i++) + { + if (m_ActiveAnimations[i].panel == panel && m_ActiveAnimations[i].variable == variable && m_ActiveAnimations[i].seqName != sequenceToIgnore) + { + // Msg("Removing queued anim %s::%s::%s\n", g_ScriptSymbols.String(m_ActiveAnimations[i].seqName), panel->GetName(), g_ScriptSymbols.String(variable)); + m_ActiveAnimations.Remove(i); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: runs a single line of the script +//----------------------------------------------------------------------------- +void AnimationController::ExecAnimationCommand(UtlSymId_t seqName, AnimCommand_t &animCommand, Panel *pWithinParent, bool bCanBeCancelled) +{ + if (animCommand.commandType == CMD_ANIMATE) + { + StartCmd_Animate(seqName, animCommand.cmdData.animate, pWithinParent, bCanBeCancelled); + } + else + { + // post the command to happen at the specified time + PostedMessage_t &msg = m_PostedMessages[m_PostedMessages.AddToTail()]; + msg.seqName = seqName; + msg.commandType = animCommand.commandType; + msg.event = animCommand.cmdData.runEvent.event; + msg.variable = animCommand.cmdData.runEvent.variable; + msg.variable2 = animCommand.cmdData.runEvent.variable2; + msg.startTime = m_flCurrentTime + animCommand.cmdData.runEvent.timeDelay; + msg.parent = pWithinParent; + msg.canBeCancelled = bCanBeCancelled; + } +} + +//----------------------------------------------------------------------------- +// Purpose: starts a variable animation +//----------------------------------------------------------------------------- +void AnimationController::StartCmd_Animate(UtlSymId_t seqName, AnimCmdAnimate_t &cmd, Panel *pWithinParent, bool bCanBeCancelled) +{ + Assert( pWithinParent ); + if ( !pWithinParent ) + return; + + // make sure the child exists + Panel *panel = pWithinParent->FindChildByName(g_ScriptSymbols.String(cmd.panel),true); + if ( !panel ) + { + // Check the parent + Panel *parent = GetParent(); + if ( !Q_stricmp( parent->GetName(), g_ScriptSymbols.String(cmd.panel) ) ) + { + panel = parent; + } + } + if (!panel) + return; + + StartCmd_Animate(panel, seqName, cmd, bCanBeCancelled); +} + +//----------------------------------------------------------------------------- +// Purpose: Starts an animation command for the specified panel +//----------------------------------------------------------------------------- +void AnimationController::StartCmd_Animate(Panel *panel, UtlSymId_t seqName, AnimCmdAnimate_t &cmd, bool bCanBeCancelled) +{ + // build a command to add to the animation queue + ActiveAnimation_t &anim = m_ActiveAnimations[m_ActiveAnimations.AddToTail()]; + anim.panel = panel; + anim.seqName = seqName; + anim.variable = cmd.variable; + anim.interpolator = cmd.interpolationFunction; + anim.interpolatorParam = cmd.interpolationParameter; + // timings + anim.startTime = m_flCurrentTime + cmd.startTime; + anim.endTime = anim.startTime + cmd.duration; + // values + anim.started = false; + anim.endValue = cmd.target; + + anim.canBeCancelled = bCanBeCancelled; + + anim.align = cmd.align; +} + +//----------------------------------------------------------------------------- +// Purpose: a posted message to run another event +//----------------------------------------------------------------------------- +void AnimationController::RunCmd_RunEvent(PostedMessage_t &msg) +{ + StartAnimationSequence(msg.parent.Get(), g_ScriptSymbols.String(msg.event), msg.canBeCancelled); +} + +//----------------------------------------------------------------------------- +// Purpose: a posted message to stop another event +//----------------------------------------------------------------------------- +void AnimationController::RunCmd_StopEvent(PostedMessage_t &msg) +{ + RemoveQueuedAnimationCommands(msg.event, msg.parent); +} + +//----------------------------------------------------------------------------- +// Purpose: a posted message to stop all animations relevant to a specified panel +//----------------------------------------------------------------------------- +void AnimationController::RunCmd_StopPanelAnimations(PostedMessage_t &msg) +{ + Panel *panel = FindSiblingByName(g_ScriptSymbols.String(msg.event)); + Assert(panel != NULL); + if (!panel) + return; + + // loop through all the active animations cancelling any that + // are operating on said panel, except for the event specified + for (int i = 0; i < m_ActiveAnimations.Count(); i++) + { + if (m_ActiveAnimations[i].panel == panel && m_ActiveAnimations[i].seqName != msg.seqName) + { + m_ActiveAnimations.Remove(i); + --i; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: a posted message to stop animations of a specific type +//----------------------------------------------------------------------------- +void AnimationController::RunCmd_StopAnimation(PostedMessage_t &msg) +{ + Panel *panel = FindSiblingByName(g_ScriptSymbols.String(msg.event)); + Assert(panel != NULL); + if (!panel) + return; + + RemoveQueuedAnimationByType(panel, msg.variable, msg.seqName); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnimationController::RunCmd_SetFont( PostedMessage_t &msg ) +{ + Panel *parent = msg.parent.Get(); + + if ( !parent ) + { + parent = GetParent(); + } + + Panel *panel = parent->FindChildByName(g_ScriptSymbols.String(msg.event), true); + Assert(panel != NULL); + if (!panel) + return; + + KeyValues *inputData = new KeyValues(g_ScriptSymbols.String(msg.variable)); + inputData->SetString(g_ScriptSymbols.String(msg.variable), g_ScriptSymbols.String(msg.variable2)); + if (!panel->SetInfo(inputData)) + { + // Assert(!("Unhandlable var in AnimationController::SetValue())")); + } + inputData->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnimationController::RunCmd_SetTexture( PostedMessage_t &msg ) +{ + Panel *panel = FindSiblingByName(g_ScriptSymbols.String(msg.event)); + Assert(panel != NULL); + if (!panel) + return; + + KeyValues *inputData = new KeyValues(g_ScriptSymbols.String(msg.variable)); + inputData->SetString(g_ScriptSymbols.String(msg.variable), g_ScriptSymbols.String(msg.variable2)); + if (!panel->SetInfo(inputData)) + { + // Assert(!("Unhandlable var in AnimationController::SetValue())")); + } + inputData->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void AnimationController::RunCmd_SetString( PostedMessage_t &msg ) +{ + Panel *panel = FindSiblingByName(g_ScriptSymbols.String(msg.event)); + Assert(panel != NULL); + if (!panel) + return; + + KeyValues *inputData = new KeyValues(g_ScriptSymbols.String(msg.variable)); + inputData->SetString(g_ScriptSymbols.String(msg.variable), g_ScriptSymbols.String(msg.variable2)); + if (!panel->SetInfo(inputData)) + { + // Assert(!("Unhandlable var in AnimationController::SetValue())")); + } + inputData->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int AnimationController::GetRelativeOffset( AnimAlign_t& align, bool xcoord ) +{ + if ( !align.relativePosition ) + return 0; + + Panel *panel = GetParent()->FindChildByName(g_ScriptSymbols.String(align.alignPanel), true); + if ( !panel ) + return 0; + + int x, y, w, h; + panel->GetBounds( x, y, w, h ); + + int offset =0; + switch ( align.alignment ) + { + default: + case a_northwest: + offset = xcoord ? x : y; + break; + case a_north: + offset = xcoord ? ( x + w ) / 2 : y; + break; + case a_northeast: + offset = xcoord ? ( x + w ) : y; + break; + case a_west: + offset = xcoord ? x : ( y + h ) / 2; + break; + case a_center: + offset = xcoord ? ( x + w ) / 2 : ( y + h ) / 2; + break; + case a_east: + offset = xcoord ? ( x + w ) : ( y + h ) / 2; + break; + case a_southwest: + offset = xcoord ? x : ( y + h ); + break; + case a_south: + offset = xcoord ? ( x + w ) / 2 : ( y + h ); + break; + case a_southeast: + offset = xcoord ? ( x + w ) : ( y + h ); + break; + } + + return offset; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the specified value from a panel +//----------------------------------------------------------------------------- +AnimationController::Value_t AnimationController::GetValue(ActiveAnimation_t& anim, Panel *panel, UtlSymId_t var) +{ + Value_t val = { 0, 0, 0, 0 }; + if (var == m_sPosition) + { + int x, y; + panel->GetPos(x, y); + val.a = (float)(x - GetRelativeOffset( anim.align, true ) ); + val.b = (float)(y - GetRelativeOffset( anim.align, false ) ); + } + else if (var == m_sSize) + { + int w, t; + panel->GetSize(w, t); + val.a = (float)w; + val.b = (float)t; + } + else if (var == m_sFgColor) + { + Color col = panel->GetFgColor(); + val.a = col[0]; + val.b = col[1]; + val.c = col[2]; + val.d = col[3]; + } + else if (var == m_sBgColor) + { + Color col = panel->GetBgColor(); + val.a = col[0]; + val.b = col[1]; + val.c = col[2]; + val.d = col[3]; + } + else if ( var == m_sXPos ) + { + int x, y; + panel->GetPos(x, y); + val.a = (float)( x - GetRelativeOffset( anim.align, true ) ); + } + else if ( var == m_sYPos ) + { + int x, y; + panel->GetPos(x, y); + val.a = (float)( y - GetRelativeOffset( anim.align, false ) ); + } + else if ( var == m_sWide ) + { + int w, h; + panel->GetSize(w, h); + val.a = (float)w; + } + else if ( var == m_sTall ) + { + int w, h; + panel->GetSize(w, h); + val.a = (float)h; + } + else + { + KeyValues *outputData = new KeyValues(g_ScriptSymbols.String(var)); + if (panel->RequestInfo(outputData)) + { + // find the var and lookup it's type + KeyValues *kv = outputData->FindKey(g_ScriptSymbols.String(var)); + if (kv && kv->GetDataType() == KeyValues::TYPE_FLOAT) + { + val.a = kv->GetFloat(); + val.b = 0.0f; + val.c = 0.0f; + val.d = 0.0f; + } + else if (kv && kv->GetDataType() == KeyValues::TYPE_COLOR) + { + Color col = kv->GetColor(); + val.a = col[0]; + val.b = col[1]; + val.c = col[2]; + val.d = col[3]; + } + } + else + { + // Assert(!("Unhandlable var in AnimationController::GetValue())")); + } + outputData->deleteThis(); + } + return val; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets a value in a panel +//----------------------------------------------------------------------------- +void AnimationController::SetValue(ActiveAnimation_t& anim, Panel *panel, UtlSymId_t var, Value_t &value) +{ + if (var == m_sPosition) + { + int x = (int)value.a + GetRelativeOffset( anim.align, true ); + int y = (int)value.b + GetRelativeOffset( anim.align, false ); + panel->SetPos(x, y); + } + else if (var == m_sSize) + { + panel->SetSize((int)value.a, (int)value.b); + } + else if (var == m_sFgColor) + { + Color col = panel->GetFgColor(); + col[0] = (unsigned char)value.a; + col[1] = (unsigned char)value.b; + col[2] = (unsigned char)value.c; + col[3] = (unsigned char)value.d; + panel->SetFgColor(col); + } + else if (var == m_sBgColor) + { + Color col = panel->GetBgColor(); + col[0] = (unsigned char)value.a; + col[1] = (unsigned char)value.b; + col[2] = (unsigned char)value.c; + col[3] = (unsigned char)value.d; + panel->SetBgColor(col); + } + else if (var == m_sXPos) + { + int newx = (int)value.a + GetRelativeOffset( anim.align, true ); + int x, y; + panel->GetPos( x, y ); + x = newx; + panel->SetPos(x, y); + } + else if (var == m_sYPos) + { + int newy = (int)value.a + GetRelativeOffset( anim.align, false ); + int x, y; + panel->GetPos( x, y ); + y = newy; + panel->SetPos(x, y); + } + else if (var == m_sWide) + { + int neww = (int)value.a; + int w, h; + panel->GetSize( w, h ); + w = neww; + panel->SetSize(w, h); + } + else if (var == m_sTall) + { + int newh = (int)value.a; + int w, h; + panel->GetSize( w, h ); + h = newh; + panel->SetSize(w, h); + } + else + { + KeyValues *inputData = new KeyValues(g_ScriptSymbols.String(var)); + // set the custom value + if (value.b == 0.0f && value.c == 0.0f && value.d == 0.0f) + { + // only the first value is non-zero, so probably just a float value + inputData->SetFloat(g_ScriptSymbols.String(var), value.a); + } + else + { + // multivalue, set the color + Color col((unsigned char)value.a, (unsigned char)value.b, (unsigned char)value.c, (unsigned char)value.d); + inputData->SetColor(g_ScriptSymbols.String(var), col); + } + if (!panel->SetInfo(inputData)) + { + // Assert(!("Unhandlable var in AnimationController::SetValue())")); + } + inputData->deleteThis(); + } +} +// Hooks between panels and animation controller system + +class CPanelAnimationDictionary +{ +public: + CPanelAnimationDictionary() : m_PanelAnimationMapPool( 32 ) + { + } + + ~CPanelAnimationDictionary() + { + m_PanelAnimationMapPool.Clear(); + } + + PanelAnimationMap *FindOrAddPanelAnimationMap( char const *className ); + PanelAnimationMap *FindPanelAnimationMap( char const *className ); + void PanelAnimationDumpVars( char const *className ); +private: + + struct PanelAnimationMapDictionaryEntry + { + PanelAnimationMap *map; + }; + + char const *StripNamespace( char const *className ); + void PanelAnimationDumpMap( PanelAnimationMap *map, bool recursive ); + + CClassMemoryPool< PanelAnimationMap > m_PanelAnimationMapPool; + CUtlDict< PanelAnimationMapDictionaryEntry, int > m_AnimationMaps; +}; + + +char const *CPanelAnimationDictionary::StripNamespace( char const *className ) +{ + if ( !Q_strnicmp( className, "vgui::", 6 ) ) + { + return className + 6; + } + return className; +} + +//----------------------------------------------------------------------------- +// Purpose: Find but don't add mapping +//----------------------------------------------------------------------------- +PanelAnimationMap *CPanelAnimationDictionary::FindPanelAnimationMap( char const *className ) +{ + int lookup = m_AnimationMaps.Find( StripNamespace( className ) ); + if ( lookup != m_AnimationMaps.InvalidIndex() ) + { + return m_AnimationMaps[ lookup ].map; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +PanelAnimationMap *CPanelAnimationDictionary::FindOrAddPanelAnimationMap( char const *className ) +{ + PanelAnimationMap *map = FindPanelAnimationMap( className ); + if ( map ) + return map; + + Panel::InitPropertyConverters(); + + PanelAnimationMapDictionaryEntry entry; + entry.map = (PanelAnimationMap *)m_PanelAnimationMapPool.Alloc(); + m_AnimationMaps.Insert( StripNamespace( className ), entry ); + return entry.map; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPanelAnimationDictionary::PanelAnimationDumpMap( PanelAnimationMap *map, bool recursive ) +{ + if ( map->pfnClassName ) + { + Msg( "%s\n", (*map->pfnClassName)() ); + } + int c = map->entries.Count(); + for ( int i = 0; i < c; i++ ) + { + PanelAnimationMapEntry *e = &map->entries[ i ]; + Msg( " %s %s\n", e->type(), e->name() ); + } + + if ( recursive && map->baseMap ) + { + PanelAnimationDumpMap( map->baseMap, recursive ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPanelAnimationDictionary::PanelAnimationDumpVars( char const *className ) +{ + if ( className == NULL ) + { + for ( int i = 0; i < (int)m_AnimationMaps.Count(); i++ ) + { + PanelAnimationDumpMap( m_AnimationMaps[ i ].map, false ); + } + } + else + { + PanelAnimationMap *map = FindPanelAnimationMap( className ); + if ( map ) + { + PanelAnimationDumpMap( map, true ); + } + else + { + Msg( "No such Panel Animation class %s\n", className ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: singleton accessor +//----------------------------------------------------------------------------- +CPanelAnimationDictionary& GetPanelAnimationDictionary() +{ + static CPanelAnimationDictionary dictionary; + return dictionary; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +PanelAnimationMap *FindOrAddPanelAnimationMap( char const *className ) +{ + return GetPanelAnimationDictionary().FindOrAddPanelAnimationMap( className ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find but don't add mapping +//----------------------------------------------------------------------------- +PanelAnimationMap *FindPanelAnimationMap( char const *className ) +{ + return GetPanelAnimationDictionary().FindPanelAnimationMap( className ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PanelAnimationDumpVars( char const *className ) +{ + GetPanelAnimationDictionary().PanelAnimationDumpVars( className ); +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/BitmapImagePanel.cpp b/vgui2/vgui_controls/BitmapImagePanel.cpp new file mode 100644 index 0000000..7e76a3c --- /dev/null +++ b/vgui2/vgui_controls/BitmapImagePanel.cpp @@ -0,0 +1,364 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> + +#include "vgui_controls/BitmapImagePanel.h" +#include "vgui/ISurface.h" +#include "vgui/IScheme.h" +#include "vgui/IBorder.h" +#include "KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +using namespace vgui; + +//----------------------------------------------------------------------------- +/** + * Simple utility function to allocate memory and duplicate a string + */ +static inline char *CloneString( const char *str ) +{ + char *cloneStr = new char [ strlen(str)+1 ]; + strcpy( cloneStr, str ); + return cloneStr; +} + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CBitmapImagePanel, BitmapImagePanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CBitmapImagePanel::CBitmapImagePanel( Panel *parent, char const *panelName, + char const *filename /*= NULL*/ ) : Panel( parent, panelName ) +{ + m_pImage = NULL; + + SetBounds( 0, 0, 100, 100 ); + + m_pszImageName = NULL; + m_pszColorName = NULL; + + m_hardwareFiltered = false; + m_preserveAspectRatio = false; + m_contentAlignment = Label::a_center; + + if ( filename && filename[ 0 ] ) + { + m_pImage = scheme()->GetImage( filename, NULL ); + m_pszImageName = CloneString( filename ); + } + + m_bgColor = Color(255, 255, 255, 255); +} +CBitmapImagePanel::~CBitmapImagePanel() +{ + delete [] m_pszImageName; + delete [] m_pszColorName; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBitmapImagePanel::ComputeImagePosition(int &x, int &y, int &w, int &h) +{ + if (!m_pImage) + { + x = y = w = h = 0; + return; + } + + if ( !m_preserveAspectRatio ) + { + x = y = 0; + GetSize( w, h ); + return; + } + + int panelWide, panelTall; + GetSize( panelWide, panelTall ); + + int imageWide, imageTall; + m_pImage->GetSize( imageWide, imageTall ); + + if ( panelWide > 0 && panelTall > 0 && imageWide > 0 && imageTall > 0 ) + { + float xScale = (float)panelWide / (float)imageWide; + float yScale = (float)panelTall / (float)imageTall; + float scale = min( xScale, yScale ); + + w = (int) (imageWide * scale); + h = (int) (imageTall * scale); + + switch (m_contentAlignment) + { + case Label::a_northwest: + x = y = 0; + break; + case Label::a_north: + x = (panelWide - w) / 2; + y = 0; + break; + case Label::a_northeast: + x = (panelWide - w); + y = 0; + break; + case Label::a_west: + x = 0; + y = (panelTall - h) / 2; + break; + case Label::a_center: + x = (panelWide - w) / 2; + y = (panelTall - h) / 2; + break; + case Label::a_east: + x = (panelWide - w); + y = (panelTall - h) / 2; + break; + case Label::a_southwest: + x = (panelWide - w); + y = 0; + break; + case Label::a_south: + x = (panelWide - w); + y = (panelTall - h) / 2; + break; + case Label::a_southeast: + x = (panelWide - w); + y = (panelTall - h); + break; + default: + x = y = 0; + break; + } + } + else + { + x = y = 0; + w = panelWide; + h = panelTall; + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBitmapImagePanel::PaintBorder() +{ + int x, y, w, h; + ComputeImagePosition(x, y, w, h); + + IBorder *pBorder = GetBorder(); + if ( pBorder ) + pBorder->Paint( x, y, x+w, y+h, -1, 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBitmapImagePanel::PaintBackground() +{ + if (!m_pImage) + return; + + int x, y, w, h; + ComputeImagePosition(x, y, w, h); + + m_pImage->SetPos(x, y); + m_pImage->SetSize( w, h ); + m_pImage->SetColor( m_bgColor ); + surface()->DrawSetColor( m_bgColor ); + m_pImage->Paint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBitmapImagePanel::setTexture( char const *filename, bool hardwareFiltered ) +{ + m_hardwareFiltered = hardwareFiltered; + + if ( m_pszImageName ) + { + delete[] m_pszImageName; + m_pszImageName = NULL; + } + if ( filename && filename[ 0 ] ) + { + m_pImage = scheme()->GetImage( filename, m_hardwareFiltered ); + m_pszImageName = CloneString( filename ); + } + else + { + m_pImage = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBitmapImagePanel::SetContentAlignment(Label::Alignment alignment) +{ + m_contentAlignment=alignment; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets control settings for editing +//----------------------------------------------------------------------------- +void CBitmapImagePanel::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + if (m_pszImageName) + { + outResourceData->SetString("image", m_pszImageName); + } + if (m_pszColorName) + { + outResourceData->SetString("imagecolor", m_pszColorName); + } + const char *alignmentString = ""; + switch ( m_contentAlignment ) + { + case Label::a_northwest: alignmentString = "north-west"; break; + case Label::a_north: alignmentString = "north"; break; + case Label::a_northeast: alignmentString = "north-east"; break; + case Label::a_center: alignmentString = "center"; break; + case Label::a_east: alignmentString = "east"; break; + case Label::a_southwest: alignmentString = "south-west"; break; + case Label::a_south: alignmentString = "south"; break; + case Label::a_southeast: alignmentString = "south-east"; break; + case Label::a_west: + default: alignmentString = "center"; break; + } + outResourceData->SetString( "imageAlignment", alignmentString ); + outResourceData->SetInt("preserveAspectRatio", m_preserveAspectRatio); + outResourceData->SetInt("filtered", m_hardwareFiltered); +} + +//----------------------------------------------------------------------------- +// Purpose: Applies designer settings from res file +//----------------------------------------------------------------------------- +void CBitmapImagePanel::ApplySettings(KeyValues *inResourceData) +{ + if ( m_pszImageName ) + { + delete[] m_pszImageName; + m_pszImageName = NULL; + } + + if ( m_pszColorName ) + { + delete[] m_pszColorName; + m_pszColorName = NULL; + } + + const char *imageName = inResourceData->GetString("image", ""); + if (*imageName) + { + setTexture( imageName ); + } + + const char *colorName = inResourceData->GetString("imagecolor", ""); + if (*colorName) + { + m_pszColorName = CloneString( colorName ); + InvalidateLayout(false,true); // force ApplySchemeSettings to run + } + + const char *keyString = inResourceData->GetString("imageAlignment", ""); + if (keyString && *keyString) + { + int align = -1; + + if ( !stricmp(keyString, "north-west") ) + { + align = Label::a_northwest; + } + else if ( !stricmp(keyString, "north") ) + { + align = Label::a_north; + } + else if ( !stricmp(keyString, "north-east") ) + { + align = Label::a_northeast; + } + else if ( !stricmp(keyString, "west") ) + { + align = Label::a_west; + } + else if ( !stricmp(keyString, "center") ) + { + align = Label::a_center; + } + else if ( !stricmp(keyString, "east") ) + { + align = Label::a_east; + } + else if ( !stricmp(keyString, "south-west") ) + { + align = Label::a_southwest; + } + else if ( !stricmp(keyString, "south") ) + { + align = Label::a_south; + } + else if ( !stricmp(keyString, "south-east") ) + { + align = Label::a_southeast; + } + + if ( align != -1 ) + { + SetContentAlignment( (Label::Alignment)align ); + } + } + + keyString = inResourceData->GetString("preserveAspectRatio", ""); + if (keyString && *keyString) + { + m_preserveAspectRatio = atoi( keyString ) != 0; + } + + keyString = inResourceData->GetString("filtered", ""); + if (keyString && *keyString) + { + m_hardwareFiltered = atoi( keyString ) != 0; + } + + BaseClass::ApplySettings(inResourceData); +} + +//----------------------------------------------------------------------------- +// Purpose: load the image, this is done just before this control is displayed +//----------------------------------------------------------------------------- +void CBitmapImagePanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings(pScheme); + + if ( m_pszColorName ) + { + setImageColor( pScheme->GetColor( m_pszColorName, m_bgColor ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Describes editing details +//----------------------------------------------------------------------------- +const char *CBitmapImagePanel::GetDescription() +{ + static char buf[1024]; + _snprintf(buf, sizeof(buf), "%s, string image, string imagecolor, alignment imageAlignment, int preserveAspectRatio, int filtered", BaseClass::GetDescription()); + return buf; +} diff --git a/vgui2/vgui_controls/BuildFactoryHelper.cpp b/vgui2/vgui_controls/BuildFactoryHelper.cpp new file mode 100644 index 0000000..46f38cd --- /dev/null +++ b/vgui2/vgui_controls/BuildFactoryHelper.cpp @@ -0,0 +1,104 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Helper for the CHudElement class to add themselves to the list of hud elements +// +// $NoKeywords: $ +//=============================================================================// +#include "vgui/IVGui.h" +#include "vgui_controls/MessageMap.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +// Start with empty list +CBuildFactoryHelper *CBuildFactoryHelper::m_sHelpers = NULL; + +//----------------------------------------------------------------------------- +// Purpose: Constructs a panel factory +// Input : pfnCreate - fn Ptr to a function which generates a panel +//----------------------------------------------------------------------------- +CBuildFactoryHelper::CBuildFactoryHelper( char const *className, PANELCREATEFUNC func ) +{ + // Make this fatal + if ( HasFactory( className ) ) + { + Error( "CBuildFactoryHelper: Factory for '%s' already exists!!!!\n", className ); + } + + //List is empty, or element belongs at front, insert here + m_pNext = m_sHelpers; + m_sHelpers = this; + + Assert( func ); + m_CreateFunc = func; + Assert( className ); + m_pClassName = className; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns next object in list +// Output : CBuildFactoryHelper +//----------------------------------------------------------------------------- +CBuildFactoryHelper *CBuildFactoryHelper::GetNext( void ) +{ + return m_pNext; +} + +char const *CBuildFactoryHelper::GetClassName() const +{ + return m_pClassName; +} + +vgui::Panel *CBuildFactoryHelper::CreatePanel() +{ + if ( !m_CreateFunc ) + return NULL; + + return ( *m_CreateFunc )(); +} + +// private static meethod +bool CBuildFactoryHelper::HasFactory( char const *className ) +{ + CBuildFactoryHelper *p = m_sHelpers; + while ( p ) + { + if ( !Q_stricmp( className, p->GetClassName() ) ) + return true; + + p = p->GetNext(); + } + return false; +} + +// static method +vgui::Panel *CBuildFactoryHelper::InstancePanel( char const *className ) +{ + CBuildFactoryHelper *p = m_sHelpers; + while ( p ) + { + if ( !Q_stricmp( className, p->GetClassName() ) ) + return p->CreatePanel(); + + p = p->GetNext(); + } + return NULL; +} + +// static method +void CBuildFactoryHelper::GetFactoryNames( CUtlVector< char const * >& list ) +{ + list.RemoveAll(); + + CBuildFactoryHelper *p = m_sHelpers; + while ( p ) + { + list.AddToTail( p->GetClassName() ); + p = p->GetNext(); + } +} + + + diff --git a/vgui2/vgui_controls/BuildGroup.cpp b/vgui2/vgui_controls/BuildGroup.cpp new file mode 100644 index 0000000..328b1de --- /dev/null +++ b/vgui2/vgui_controls/BuildGroup.cpp @@ -0,0 +1,1544 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + //========= Copyright � 1996-2003, Valve LLC, All rights reserved. ============ +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + + +#include <stdio.h> +#define PROTECTED_THINGS_DISABLE + +#include "utldict.h" + +#include <vgui/KeyCode.h> +#include <vgui/Cursor.h> +#include <vgui/MouseCode.h> +#include <KeyValues.h> +#include <vgui/IInput.h> +#include <vgui/ISystem.h> +#include <vgui/IVGui.h> +#include <vgui/ISurface.h> + +#include <vgui_controls/BuildGroup.h> +#include <vgui_controls/Panel.h> +#include <vgui_controls/PHandle.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/EditablePanel.h> +#include <vgui_controls/MessageBox.h> +#include "filesystem.h" +#include "tier0/icommandline.h" +#include "const.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar vgui_cache_res_files( "vgui_cache_res_files", "1" ); + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Handle table +//----------------------------------------------------------------------------- +IMPLEMENT_HANDLES( BuildGroup, 20 ) + +CUtlDict< KeyValues* > BuildGroup::m_dictCachedResFiles; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +BuildGroup::BuildGroup(Panel *parentPanel, Panel *contextPanel) +{ + CONSTRUCT_HANDLE( ); + + _enabled=false; + _snapX=1; + _snapY=1; + _cursor_sizenwse = dc_sizenwse; + _cursor_sizenesw = dc_sizenesw; + _cursor_sizewe = dc_sizewe; + _cursor_sizens = dc_sizens; + _cursor_sizeall = dc_sizeall; + _currentPanel=null; + _dragging=false; + m_pResourceName=NULL; + m_pResourcePathID = NULL; + m_hBuildDialog=NULL; + m_pParentPanel=parentPanel; + for (int i=0; i<4; ++i) + _rulerNumber[i] = NULL; + SetContextPanel(contextPanel); + _showRulers = false; + +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +BuildGroup::~BuildGroup() +{ + if (m_hBuildDialog) + delete m_hBuildDialog.Get(); + m_hBuildDialog = NULL; + + delete [] m_pResourceName; + delete [] m_pResourcePathID; + + for (int i=0; i <4; ++i) + { + if (_rulerNumber[i]) + { + delete _rulerNumber[i]; + _rulerNumber[i]= NULL; + } + } + + DESTRUCT_HANDLE(); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggles build mode on/off +// Input : state - new state +//----------------------------------------------------------------------------- +void BuildGroup::SetEnabled(bool state) +{ + if(_enabled != state) + { + _enabled = state; + _currentPanel = NULL; + + if ( state ) + { + ActivateBuildDialog(); + } + else + { + // hide the build dialog + if ( m_hBuildDialog ) + { + m_hBuildDialog->OnCommand("Close"); + } + + // request focus for our main panel + m_pParentPanel->RequestFocus(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check if buildgroup is enabled +//----------------------------------------------------------------------------- +bool BuildGroup::IsEnabled() +{ + return _enabled; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the list of panels that are currently selected +//----------------------------------------------------------------------------- +CUtlVector<PHandle> *BuildGroup::GetControlGroup() +{ + return &_controlGroup; +} + +//----------------------------------------------------------------------------- +// Purpose: Check if ruler display is activated +//----------------------------------------------------------------------------- +bool BuildGroup::HasRulersOn() +{ + return _showRulers; +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle ruler display +//----------------------------------------------------------------------------- +void BuildGroup::ToggleRulerDisplay() +{ + _showRulers = !_showRulers; + + if (_rulerNumber[0] == NULL) // rulers haven't been initialized + { + _rulerNumber[0] = new Label(m_pBuildContext, NULL, ""); + _rulerNumber[1] = new Label(m_pBuildContext, NULL, ""); + _rulerNumber[2] = new Label(m_pBuildContext, NULL, ""); + _rulerNumber[3] = new Label(m_pBuildContext, NULL, ""); + } + SetRulerLabelsVisible(_showRulers); + + m_pBuildContext->Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Tobble visibility of ruler number labels +//----------------------------------------------------------------------------- +void BuildGroup::SetRulerLabelsVisible(bool state) +{ + _rulerNumber[0]->SetVisible(state); + _rulerNumber[1]->SetVisible(state); + _rulerNumber[2]->SetVisible(state); + _rulerNumber[3]->SetVisible(state); +} + +void BuildGroup::ApplySchemeSettings( IScheme *pScheme ) +{ + DrawRulers(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw Rulers on screen if conditions are right +//----------------------------------------------------------------------------- +void BuildGroup::DrawRulers() +{ + // don't draw if visibility is off + if (!_showRulers) + { + return; + } + + // no drawing if we selected the context panel + if (m_pBuildContext == _currentPanel) + { + SetRulerLabelsVisible(false); + return; + } + else + SetRulerLabelsVisible(true); + + int x, y, wide, tall; + // get base panel's postition + m_pBuildContext->GetBounds(x, y, wide, tall); + m_pBuildContext->ScreenToLocal(x,y); + + int cx, cy, cwide, ctall; + _currentPanel->GetBounds (cx, cy, cwide, ctall); + + surface()->PushMakeCurrent(m_pBuildContext->GetVPanel(), false); + + // draw rulers + surface()->DrawSetColor(255, 255, 255, 255); // white color + + surface()->DrawFilledRect(0, cy, cx, cy+1); //top horiz left + surface()->DrawFilledRect(cx+cwide, cy, wide, cy+1); //top horiz right + + surface()->DrawFilledRect(0, cy+ctall-1, cx, cy+ctall); //bottom horiz left + surface()->DrawFilledRect(cx+cwide, cy+ctall-1, wide, cy+ctall); //bottom horiz right + + surface()->DrawFilledRect(cx,0,cx+1,cy); //top vert left + surface()->DrawFilledRect(cx+cwide-1,0, cx+cwide, cy); //top vert right + + surface()->DrawFilledRect(cx,cy+ctall, cx+1, tall); //bottom vert left + surface()->DrawFilledRect(cx+cwide-1, cy+ctall, cx+cwide, tall); //bottom vert right + + surface()->PopMakeCurrent(m_pBuildContext->GetVPanel()); + + // now let's put numbers with the rulers + char textstring[20]; + Q_snprintf (textstring, sizeof( textstring ), "%d", cx); + _rulerNumber[0]->SetText(textstring); + int twide, ttall; + _rulerNumber[0]->GetContentSize(twide,ttall); + _rulerNumber[0]->SetSize(twide,ttall); + _rulerNumber[0]->SetPos(cx/2-twide/2, cy-ttall+3); + + Q_snprintf (textstring, sizeof( textstring ), "%d", cy); + _rulerNumber[1]->SetText(textstring); + _rulerNumber[1]->GetContentSize(twide,ttall); + _rulerNumber[1]->SetSize(twide,ttall); + _rulerNumber[1]->GetSize(twide,ttall); + _rulerNumber[1]->SetPos(cx-twide + 3, cy/2-ttall/2); + + Q_snprintf (textstring, sizeof( textstring ), "%d", cy); + _rulerNumber[2]->SetText(textstring); + _rulerNumber[2]->GetContentSize(twide,ttall); + _rulerNumber[2]->SetSize(twide,ttall); + _rulerNumber[2]->SetPos(cx+cwide+(wide-cx-cwide)/2 - twide/2, cy+ctall-3); + + Q_snprintf (textstring, sizeof( textstring ), "%d", cy); + _rulerNumber[3]->SetText(textstring); + _rulerNumber[3]->GetContentSize(twide,ttall); + _rulerNumber[3]->SetSize(twide,ttall); + _rulerNumber[3]->SetPos(cx+cwide, cy+ctall+(tall-cy-ctall)/2 - ttall/2); + +} + +//----------------------------------------------------------------------------- +// Purpose: respond to cursor movments +//----------------------------------------------------------------------------- +bool BuildGroup::CursorMoved(int x, int y, Panel *panel) +{ + Assert(panel); + + if ( !m_hBuildDialog.Get() ) + { + if ( panel->GetParent() ) + { + EditablePanel *ep = dynamic_cast< EditablePanel * >( panel->GetParent() ); + if ( ep ) + { + BuildGroup *bg = ep->GetBuildGroup(); + if ( bg && bg != this ) + { + bg->CursorMoved( x, y, panel ); + } + } + } + return false; + } + + // no moving uneditable panels + // commented out because this has issues with panels moving + // to front and obscuring other panels + //if (!panel->IsBuildModeEditable()) + // return; + + if (_dragging) + { + input()->GetCursorPos(x, y); + + if (_dragMouseCode == MOUSE_RIGHT) + { + int newW = max( 1, _dragStartPanelSize[ 0 ] + x - _dragStartCursorPos[0] ); + int newH = max( 1, _dragStartPanelSize[ 1 ] + y - _dragStartCursorPos[1] ); + + bool shift = ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) ); + bool ctrl = ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ); + + if ( shift ) + { + newW = _dragStartPanelSize[ 0 ]; + } + if ( ctrl ) + { + newH = _dragStartPanelSize[ 1 ]; + } + + panel->SetSize( newW, newH ); + ApplySnap(panel); + } + else + { + for (int i=0; i < _controlGroup.Count(); ++i) + { + // now fix offset of member panels with respect to the one we are dragging + Panel *groupMember = _controlGroup[i].Get(); + groupMember->SetPos(_dragStartPanelPos[0] + _groupDeltaX[i] +(x-_dragStartCursorPos[0]), _dragStartPanelPos[1] + _groupDeltaY[i] +(y-_dragStartCursorPos[1])); + ApplySnap(groupMember); + } + } + + // update the build dialog + if (m_hBuildDialog) + { + KeyValues *keyval = new KeyValues("UpdateControlData"); + keyval->SetPtr("panel", GetCurrentPanel()); + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), keyval, NULL); + + keyval = new KeyValues("EnableSaveButton"); + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), keyval, NULL); + } + + panel->Repaint(); + panel->CallParentFunction(new KeyValues("Repaint")); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BuildGroup::MousePressed(MouseCode code, Panel *panel) +{ + Assert(panel); + + if ( !m_hBuildDialog.Get() ) + { + if ( panel->GetParent() ) + { + EditablePanel *ep = dynamic_cast< EditablePanel * >( panel->GetParent() ); + if ( ep ) + { + BuildGroup *bg = ep->GetBuildGroup(); + if ( bg && bg != this ) + { + bg->MousePressed( code, panel ); + } + } + } + return false; + } + + // if people click on the base build dialog panel. + if (panel == m_hBuildDialog) + { + // hide the click menu if its up + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("HideNewControlMenu"), NULL); + return true; + } + + // don't select unnamed items + if (strlen(panel->GetName()) < 1) + return true; + + bool shift = ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) ); + if (!shift) + { + _controlGroup.RemoveAll(); + } + + // Show new ctrl menu if they click on the bg (not on a subcontrol) + if ( code == MOUSE_RIGHT && panel == GetContextPanel()) + { + // trigger a drop down menu to create new controls + ivgui()->PostMessage (m_hBuildDialog->GetVPanel(), new KeyValues("ShowNewControlMenu"), NULL); + } + else + { + // don't respond if we click on ruler numbers + if (_showRulers) // rulers are visible + { + for ( int i=0; i < 4; i++) + { + if ( panel == _rulerNumber[i]) + return true; + } + } + + _dragging = true; + _dragMouseCode = code; + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("HideNewControlMenu"), NULL); + + int x, y; + input()->GetCursorPos(x, y); + + _dragStartCursorPos[0] = x; + _dragStartCursorPos[1] = y; + + + input()->SetMouseCapture(panel->GetVPanel()); + + _groupDeltaX.RemoveAll(); + _groupDeltaY.RemoveAll(); + + // basepanel is the panel that all the deltas will be calculated from. + // it is the last panel we clicked in because if we move the panels as a group + // it would be from that one + Panel *basePanel = NULL; + // find the panel we clicked in, that is the base panel + // it might already be in the group + for (int i=0; i< _controlGroup.Count(); ++i) + { + if (panel == _controlGroup[i].Get()) + { + basePanel = panel; + break; + } + } + + // if its not in the group we just added this panel. get it in the group + if (basePanel == NULL) + { + PHandle temp; + temp = panel; + _controlGroup.AddToTail(temp); + basePanel = panel; + } + + basePanel->GetPos(x,y); + _dragStartPanelPos[0]=x; + _dragStartPanelPos[1]=y; + + basePanel->GetSize( _dragStartPanelSize[ 0 ], _dragStartPanelSize[ 1 ] ); + + // figure out the deltas of the other panels from the base panel + for (int i=0; i<_controlGroup.Count(); ++i) + { + int cx, cy; + _controlGroup[i].Get()->GetPos(cx, cy); + _groupDeltaX.AddToTail(cx - x); + _groupDeltaY.AddToTail(cy - y); + } + + // if this panel wasn't already selected update the buildmode dialog controls to show its info + if(_currentPanel != panel) + { + _currentPanel = panel; + + if ( m_hBuildDialog ) + { + // think this is taken care of by SetActiveControl. + //ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("ApplyDataToControls"), NULL); + + KeyValues *keyval = new KeyValues("SetActiveControl"); + keyval->SetPtr("PanelPtr", GetCurrentPanel()); + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), keyval, NULL); + } + } + + // store undo information upon panel selection. + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("StoreUndo"), NULL); + + panel->RequestFocus(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BuildGroup::MouseReleased(MouseCode code, Panel *panel) +{ + if ( !m_hBuildDialog.Get() ) + { + if ( panel->GetParent() ) + { + EditablePanel *ep = dynamic_cast< EditablePanel * >( panel->GetParent() ); + if ( ep ) + { + BuildGroup *bg = ep->GetBuildGroup(); + if ( bg && bg != this ) + { + bg->MouseReleased( code, panel ); + } + } + } + return false; + } + + Assert(panel); + + _dragging=false; + input()->SetMouseCapture(null); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BuildGroup::MouseDoublePressed(MouseCode code, Panel *panel) +{ + Assert(panel); + return MousePressed( code, panel ); +} + +bool BuildGroup::KeyTyped( wchar_t unichar, Panel *panel ) +{ + if ( !m_hBuildDialog.Get() ) + { + if ( panel->GetParent() ) + { + EditablePanel *ep = dynamic_cast< EditablePanel * >( panel->GetParent() ); + if ( ep ) + { + BuildGroup *bg = ep->GetBuildGroup(); + if ( bg && bg != this ) + { + bg->KeyTyped( unichar, panel ); + } + } + } + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BuildGroup::KeyCodeTyped(KeyCode code, Panel *panel) +{ + if ( !m_hBuildDialog.Get() ) + { + if ( panel->GetParent() ) + { + EditablePanel *ep = dynamic_cast< EditablePanel * >( panel->GetParent() ); + if ( ep ) + { + BuildGroup *bg = ep->GetBuildGroup(); + if ( bg && bg != this ) + { + bg->KeyCodeTyped( code, panel ); + } + } + } + return false; + } + + Assert(panel); + + int dx=0; + int dy=0; + + bool shift = ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) ); + bool ctrl = ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + + if ( ctrl && shift && alt && code == KEY_B) + { + // enable build mode + EditablePanel *ep = dynamic_cast< EditablePanel * >( panel ); + if ( ep ) + { + ep->ActivateBuildMode(); + } + return true; + } + + switch (code) + { + case KEY_LEFT: + { + dx-=_snapX; + break; + } + case KEY_RIGHT: + { + dx+=_snapX; + break; + } + case KEY_UP: + { + dy-=_snapY; + break; + } + case KEY_DOWN: + { + dy+=_snapY; + break; + } + case KEY_DELETE: + { + // delete the panel we have selected + ivgui()->PostMessage (m_hBuildDialog->GetVPanel(), new KeyValues ("DeletePanel"), NULL); + break; + } + + } + + if (ctrl) + { + switch (code) + { + case KEY_Z: + { + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("Undo"), NULL); + break; + } + + case KEY_C: + { + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("Copy"), NULL); + break; + } + case KEY_V: + { + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("Paste"), NULL); + break; + } + } + } + + if(dx||dy) + { + //TODO: make this stuff actually snap + + int x,y,wide,tall; + + panel->GetBounds(x,y,wide,tall); + + if(shift) + { + panel->SetSize(wide+dx,tall+dy); + } + else + { + panel->SetPos(x+dx,y+dy); + } + + ApplySnap(panel); + + panel->Repaint(); + if (panel->GetVParent() != 0) + { + panel->PostMessage(panel->GetVParent(), new KeyValues("Repaint")); + } + + + // update the build dialog + if (m_hBuildDialog) + { + // post that it's active + KeyValues *keyval = new KeyValues("SetActiveControl"); + keyval->SetPtr("PanelPtr", GetCurrentPanel()); + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), keyval, NULL); + + // post that it's been changed + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), new KeyValues("PanelMoved"), NULL); + } + } + + // If holding key while dragging, simulate moving cursor so shift/ctrl key changes take effect + if ( _dragging && panel != GetContextPanel() ) + { + int x, y; + input()->GetCursorPos( x, y ); + CursorMoved( x, y, panel ); + } + + return true; +} + +bool BuildGroup::KeyCodeReleased(KeyCode code, Panel *panel ) +{ + if ( !m_hBuildDialog.Get() ) + { + if ( panel->GetParent() ) + { + EditablePanel *ep = dynamic_cast< EditablePanel * >( panel->GetParent() ); + if ( ep ) + { + BuildGroup *bg = ep->GetBuildGroup(); + if ( bg && bg != this ) + { + bg->KeyCodeTyped( code, panel ); + } + } + } + return false; + } + + // If holding key while dragging, simulate moving cursor so shift/ctrl key changes take effect + if ( _dragging && panel != GetContextPanel() ) + { + int x, y; + input()->GetCursorPos( x, y ); + CursorMoved( x, y, panel ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Searches for a BuildModeDialog in the hierarchy +//----------------------------------------------------------------------------- +Panel *BuildGroup::CreateBuildDialog( void ) +{ + // request the panel + Panel *buildDialog = NULL; + KeyValues *data = new KeyValues("BuildDialog"); + data->SetPtr("BuildGroupPtr", this); + if (m_pBuildContext->RequestInfo(data)) + { + buildDialog = (Panel *)data->GetPtr("PanelPtr"); + } + + // initialize the build dialog if found + if ( buildDialog ) + { + input()->ReleaseAppModalSurface(); + } + + return buildDialog; +} + +//----------------------------------------------------------------------------- +// Purpose: Activates the build mode settings dialog +//----------------------------------------------------------------------------- +void BuildGroup::ActivateBuildDialog( void ) +{ + // create the build mode dialog first time through + if (!m_hBuildDialog.Get()) + { + m_hBuildDialog = CreateBuildDialog(); + + if (!m_hBuildDialog.Get()) + return; + } + + m_hBuildDialog->SetVisible( true ); + + // send a message to set the initial dialog controls info + _currentPanel = m_pParentPanel; + KeyValues *keyval = new KeyValues("SetActiveControl"); + keyval->SetPtr("PanelPtr", GetCurrentPanel()); + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), keyval, NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +HCursor BuildGroup::GetCursor(Panel *panel) +{ + Assert(panel); + + int x,y,wide,tall; + input()->GetCursorPos(x,y); + panel->ScreenToLocal(x,y); + panel->GetSize(wide,tall); + + if(x < 2) + { + if(y < 4) + { + return _cursor_sizenwse; + } + else + if(y<(tall-4)) + { + return _cursor_sizewe; + } + else + { + return _cursor_sizenesw; + } + } + + return _cursor_sizeall; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void BuildGroup::ApplySnap(Panel *panel) +{ + Assert(panel); + + int x,y,wide,tall; + panel->GetBounds(x,y,wide,tall); + + x=(x/_snapX)*_snapX; + y=(y/_snapY)*_snapY; + panel->SetPos(x,y); + + int xx,yy; + xx=x+wide; + yy=y+tall; + + xx=(xx/_snapX)*_snapX; + yy=(yy/_snapY)*_snapY; + panel->SetSize(xx-x,yy-y); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the currently selected panel +//----------------------------------------------------------------------------- +Panel *BuildGroup::GetCurrentPanel() +{ + return _currentPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Add panel the list of panels that are in the build group +//----------------------------------------------------------------------------- +void BuildGroup::PanelAdded(Panel *panel) +{ + Assert(panel); + + PHandle temp; + temp = panel; + int c = _panelDar.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( _panelDar[ i ] == temp ) + { + return; + } + } + _panelDar.AddToTail(temp); +} + +//----------------------------------------------------------------------------- +// Purpose: loads the control settings from file +//----------------------------------------------------------------------------- +void BuildGroup::LoadControlSettings(const char *controlResourceName, const char *pathID, KeyValues *pPreloadedKeyValues, KeyValues *pConditions) +{ + // make sure the file is registered + RegisterControlSettingsFile(controlResourceName, pathID); + + // Use the keyvalues they passed in or load them. + KeyValues *rDat = pPreloadedKeyValues; + + bool bUsePrecaching = vgui_cache_res_files.GetBool(); + bool bUsingPrecachedSourceKeys = false; + bool bShouldCacheKeys = true; + bool bDeleteKeys = false; + + while ( !rDat ) + { + if ( bUsePrecaching ) + { + int nIndex = m_dictCachedResFiles.Find( controlResourceName ); + if ( nIndex != m_dictCachedResFiles.InvalidIndex() ) + { + rDat = m_dictCachedResFiles[nIndex]; + bUsingPrecachedSourceKeys = true; + bDeleteKeys = false; + bShouldCacheKeys = false; + break; + } + } + + // load the resource data from the file + rDat = new KeyValues( controlResourceName ); + + // check the skins directory first, if an explicit pathID hasn't been set + bool bSuccess = false; + if ( !pathID ) + { + bSuccess = rDat->LoadFromFile( g_pFullFileSystem, controlResourceName, "SKIN" ); + } + + if ( !V_stricmp( CommandLine()->ParmValue( "-game", "hl2" ), "tf" ) ) + { + if ( !bSuccess ) + { + bSuccess = rDat->LoadFromFile( g_pFullFileSystem, controlResourceName, "custom_mod" ); + } + if ( !bSuccess ) + { + bSuccess = rDat->LoadFromFile( g_pFullFileSystem, controlResourceName, "vgui" ); + } + if ( !bSuccess ) + { + bSuccess = rDat->LoadFromFile( g_pFullFileSystem, controlResourceName, "BSP" ); + } + // only allow to load loose files when using insecure mode + if ( !bSuccess && CommandLine()->FindParm( "-insecure" ) ) + { + bSuccess = rDat->LoadFromFile( g_pFullFileSystem, controlResourceName, pathID ); + } + } + else + { + if ( !bSuccess ) + { + bSuccess = rDat->LoadFromFile( g_pFullFileSystem, controlResourceName, pathID ); + } + } + + if ( bSuccess ) + { + if ( IsX360() ) + { + rDat->ProcessResolutionKeys( surface()->GetResolutionKey() ); + } + if ( IsPC() ) + { + ConVarRef cl_hud_minmode( "cl_hud_minmode", true ); + if ( cl_hud_minmode.IsValid() && cl_hud_minmode.GetBool() ) + { + rDat->ProcessResolutionKeys( "_minmode" ); + } + } + bDeleteKeys = true; + bShouldCacheKeys = true; + } + else + { + Warning( "Failed to load %s\n", controlResourceName ); + } + break; + } + + if ( pConditions && pConditions->GetFirstSubKey() ) + { + if ( bUsingPrecachedSourceKeys ) + { + // ProcessConditionalKeys modifies the KVs in place. We dont want + // that to happen to our cached keys + rDat = rDat->MakeCopy(); + bDeleteKeys = true; + } + + ProcessConditionalKeys(rDat, pConditions); + bShouldCacheKeys = false; + } + + // save off the resource name + delete [] m_pResourceName; + m_pResourceName = new char[strlen(controlResourceName) + 1]; + strcpy(m_pResourceName, controlResourceName); + + if (pathID) + { + delete [] m_pResourcePathID; + m_pResourcePathID = new char[strlen(pathID) + 1]; + strcpy(m_pResourcePathID, pathID); + } + + // delete any controls not in both files + DeleteAllControlsCreatedByControlSettingsFile(); + + // loop through the resource data sticking info into controls + ApplySettings(rDat); + + if (m_pParentPanel) + { + m_pParentPanel->InvalidateLayout(); + m_pParentPanel->Repaint(); + } + + if ( bShouldCacheKeys && bUsePrecaching ) + { + Assert( m_dictCachedResFiles.Find( controlResourceName ) == m_dictCachedResFiles.InvalidIndex() ); + m_dictCachedResFiles.Insert( controlResourceName, rDat ); + } + else if ( bDeleteKeys ) + { + Assert( m_dictCachedResFiles.Find( controlResourceName ) != m_dictCachedResFiles.InvalidIndex() || pConditions || pPreloadedKeyValues ); + rDat->deleteThis(); + } +} + +void BuildGroup::ProcessConditionalKeys( KeyValues *pData, KeyValues *pConditions ) +{ + // for each condition, look for it in keys + // if its a positive condition, promote all of its children, replacing values + + if ( pData ) + { + KeyValues *pSubKey = pData->GetFirstSubKey(); + if ( !pSubKey ) + { + // not a block + return; + } + + for ( ; pSubKey != NULL; pSubKey = pSubKey->GetNextKey() ) + { + // recursively descend each sub block + ProcessConditionalKeys( pSubKey, pConditions ); + + KeyValues *pCondition = pConditions->GetFirstSubKey(); + for ( ; pCondition != NULL; pCondition = pCondition->GetNextKey() ) + { + // if we match any conditions in this sub block, copy up + KeyValues *pConditionBlock = pSubKey->FindKey( pCondition->GetName() ); + if ( pConditionBlock ) + { + KeyValues *pOverridingKey; + for ( pOverridingKey = pConditionBlock->GetFirstSubKey(); pOverridingKey != NULL; pOverridingKey = pOverridingKey->GetNextKey() ) + { + KeyValues *pExistingKey = pSubKey->FindKey( pOverridingKey->GetName() ); + if ( pExistingKey ) + { + pExistingKey->SetStringValue( pOverridingKey->GetString() ); + } + else + { + KeyValues *copy = pOverridingKey->MakeCopy(); + pSubKey->AddSubKey( copy ); + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: registers that a control settings file may be loaded +// use when the dialog may have multiple states and the editor will need to be able to switch between them +//----------------------------------------------------------------------------- +void BuildGroup::RegisterControlSettingsFile(const char *controlResourceName, const char *pathID) +{ + // add the file into a list for build mode + CUtlSymbol sym(controlResourceName); + if (!m_RegisteredControlSettingsFiles.IsValidIndex(m_RegisteredControlSettingsFiles.Find(sym))) + { + m_RegisteredControlSettingsFiles.AddToTail(sym); + } +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor / iterator +//----------------------------------------------------------------------------- +int BuildGroup::GetRegisteredControlSettingsFileCount() +{ + return m_RegisteredControlSettingsFiles.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +const char *BuildGroup::GetRegisteredControlSettingsFileByIndex(int index) +{ + return m_RegisteredControlSettingsFiles[index].String(); +} + +//----------------------------------------------------------------------------- +// Purpose: reloads the control settings from file +//----------------------------------------------------------------------------- +void BuildGroup::ReloadControlSettings() +{ + delete m_hBuildDialog.Get(); + m_hBuildDialog = NULL; + + // loop though objects in the current control group and remove them all + // the 0th panel is always the contextPanel which is not deletable + for( int i = 1; i < _panelDar.Count(); i++ ) + { + if (!_panelDar[i].Get()) // this can happen if we had two of the same handle in the list + { + _panelDar.Remove(i); + --i; + continue; + } + + // only delete deletable panels, as the only deletable panels + // are the ones created using the resource file + if ( _panelDar[i].Get()->IsBuildModeDeletable()) + { + delete _panelDar[i].Get(); + _panelDar.Remove(i); + --i; + } + } + + if (m_pResourceName) + { + EditablePanel *edit = dynamic_cast<EditablePanel *>(m_pParentPanel); + if (edit) + { + edit->LoadControlSettings(m_pResourceName, m_pResourcePathID); + } + else + { + LoadControlSettings(m_pResourceName, m_pResourcePathID); + } + } + + _controlGroup.RemoveAll(); + + ActivateBuildDialog(); +} + +//----------------------------------------------------------------------------- +// Purpose: changes which control settings are currently loaded +//----------------------------------------------------------------------------- +void BuildGroup::ChangeControlSettingsFile(const char *controlResourceName) +{ + // clear any current state + _controlGroup.RemoveAll(); + _currentPanel = m_pParentPanel; + + // load the new state, via the dialog if possible + EditablePanel *edit = dynamic_cast<EditablePanel *>(m_pParentPanel); + if (edit) + { + edit->LoadControlSettings(controlResourceName, m_pResourcePathID); + } + else + { + LoadControlSettings(controlResourceName, m_pResourcePathID); + } + + // force it to update + KeyValues *keyval = new KeyValues("SetActiveControl"); + keyval->SetPtr("PanelPtr", GetCurrentPanel()); + ivgui()->PostMessage(m_hBuildDialog->GetVPanel(), keyval, NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: saves control settings to file +//----------------------------------------------------------------------------- +bool BuildGroup::SaveControlSettings( void ) +{ + bool bSuccess = false; + if ( m_pResourceName ) + { + KeyValues *rDat = new KeyValues( m_pResourceName ); + + // get the data from our controls + GetSettings( rDat ); + + char fullpath[ 512 ]; + g_pFullFileSystem->RelativePathToFullPath( m_pResourceName, m_pResourcePathID, fullpath, sizeof( fullpath ) ); + + // save the data out to a file + bSuccess = rDat->SaveToFile( g_pFullFileSystem, fullpath, NULL ); + if (!bSuccess) + { + MessageBox *dlg = new MessageBox("BuildMode - Error saving file", "Error: Could not save changes. File is most likely read only."); + dlg->DoModal(); + } + + rDat->deleteThis(); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Purpose: Deletes all the controls not created by the code +//----------------------------------------------------------------------------- +void BuildGroup::DeleteAllControlsCreatedByControlSettingsFile() +{ + // loop though objects in the current control group and remove them all + // the 0th panel is always the contextPanel which is not deletable + for ( int i = 1; i < _panelDar.Count(); i++ ) + { + if (!_panelDar[i].Get()) // this can happen if we had two of the same handle in the list + { + _panelDar.Remove(i); + --i; + continue; + } + + // only delete deletable panels, as the only deletable panels + // are the ones created using the resource file + if ( _panelDar[i].Get()->IsBuildModeDeletable()) + { + delete _panelDar[i].Get(); + _panelDar.Remove(i); + --i; + } + } + + _currentPanel = m_pBuildContext; + _currentPanel->InvalidateLayout(); + m_pBuildContext->Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: serializes settings from a resource data container +//----------------------------------------------------------------------------- +void BuildGroup::ApplySettings( KeyValues *resourceData ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + // loop through all the keys, applying them wherever + for (KeyValues *controlKeys = resourceData->GetFirstSubKey(); controlKeys != NULL; controlKeys = controlKeys->GetNextKey()) + { + bool bFound = false; + + // Skip keys that are atomic.. + if (controlKeys->GetDataType() != KeyValues::TYPE_NONE) + continue; + + char const *keyName = controlKeys->GetName(); + + // check to see if any buildgroup panels have this name + for ( int i = 0; i < _panelDar.Count(); i++ ) + { + Panel *panel = _panelDar[i].Get(); + + if (!panel) // this can happen if we had two of the same handle in the list + { + _panelDar.Remove(i); + --i; + continue; + } + + + Assert (panel); + + // make the control name match CASE INSENSITIVE! + char const *panelName = panel->GetName(); + + if (!Q_stricmp(panelName, keyName)) + { + // apply the settings + panel->ApplySettings(controlKeys); + bFound = true; + break; + } + } + + if ( !bFound ) + { + // the key was not found in the registered list, check to see if we should create it + if ( keyName /*controlKeys->GetInt("AlwaysCreate", false)*/ ) + { + // create the control even though it wasn't registered + NewControl( controlKeys ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new control in the context panel +// Input: name: class name of control to create +// controlKeys: keyvalues of settings for the panel. +// name OR controlKeys should be set, not both. +// x,y position relative to base panel +// Output: Panel *newPanel, NULL if failed to create new control. +//----------------------------------------------------------------------------- +Panel *BuildGroup::NewControl( const char *name, int x, int y) +{ + Assert (name); + + Panel *newPanel = NULL; + // returns NULL on failure + newPanel = static_cast<EditablePanel *>(m_pParentPanel)->CreateControlByName(name); + + if (newPanel) + { + // panel successfully created + newPanel->SetParent(m_pParentPanel); + newPanel->SetBuildGroup(this); + newPanel->SetPos(x, y); + + char newFieldName[255]; + GetNewFieldName(newFieldName, sizeof(newFieldName), newPanel); + newPanel->SetName(newFieldName); + + newPanel->AddActionSignalTarget(m_pParentPanel); + newPanel->SetBuildModeEditable(true); + newPanel->SetBuildModeDeletable(true); + + // make sure it gets freed + newPanel->SetAutoDelete(true); + } + + return newPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new control in the context panel +// Input: controlKeys: keyvalues of settings for the panel only works when applying initial settings. +// Output: Panel *newPanel, NULL if failed to create new control. +//----------------------------------------------------------------------------- +Panel *BuildGroup::NewControl( KeyValues *controlKeys, int x, int y) +{ + Assert (controlKeys); + + Panel *newPanel = NULL; + if (controlKeys) + { +// Warning( "Creating new control \"%s\" of type \"%s\"\n", controlKeys->GetString( "fieldName" ), controlKeys->GetString( "ControlName" ) ); + KeyValues *keyVal = new KeyValues("ControlFactory", "ControlName", controlKeys->GetString("ControlName")); + m_pBuildContext->RequestInfo(keyVal); + // returns NULL on failure + newPanel = (Panel *)keyVal->GetPtr("PanelPtr"); + keyVal->deleteThis(); + } + else + { + return NULL; + } + + if (newPanel) + { + // panel successfully created + newPanel->SetParent(m_pParentPanel); + newPanel->SetBuildGroup(this); + newPanel->SetPos(x, y); + + newPanel->SetName(controlKeys->GetName()); // name before applysettings :) + newPanel->ApplySettings(controlKeys); + + newPanel->AddActionSignalTarget(m_pParentPanel); + newPanel->SetBuildModeEditable(true); + newPanel->SetBuildModeDeletable(true); + + // make sure it gets freed + newPanel->SetAutoDelete(true); + } + + return newPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Get a new unique fieldname for a new control +//----------------------------------------------------------------------------- +void BuildGroup::GetNewFieldName(char *newFieldName, int newFieldNameSize, Panel *newPanel) +{ + int fieldNameNumber=1; + char defaultName[25]; + + Q_strncpy( defaultName, newPanel->GetClassName(), sizeof( defaultName ) ); + + while (1) + { + Q_snprintf (newFieldName, newFieldNameSize, "%s%d", defaultName, fieldNameNumber); + if ( FieldNameTaken(newFieldName) == NULL) + break; + ++fieldNameNumber; + } +} + +//----------------------------------------------------------------------------- +// Purpose: check to see if any buildgroup panels have this fieldname +// Input : fieldName, name to check +// Output : ptr to a panel that has the name if it is taken +//----------------------------------------------------------------------------- +Panel *BuildGroup::FieldNameTaken(const char *fieldName) +{ + for ( int i = 0; i < _panelDar.Count(); i++ ) + { + Panel *panel = _panelDar[i].Get(); + if ( !panel ) + continue; + + if (!stricmp(panel->GetName(), fieldName) ) + { + return panel; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: serializes settings to a resource data container +//----------------------------------------------------------------------------- +void BuildGroup::GetSettings( KeyValues *resourceData ) +{ + // loop through all the objects getting their settings + for( int i = 0; i < _panelDar.Count(); i++ ) + { + Panel *panel = _panelDar[i].Get(); + if (!panel) + continue; + + bool isRuler = false; + // do not get setting for ruler labels. + if (_showRulers) // rulers are visible + { + for (int j = 0; j < 4; j++) + { + if (panel == _rulerNumber[j]) + { + isRuler = true; + break; + } + } + if (isRuler) + { + isRuler = false; + continue; + } + } + + // Don't save the setting of the buildmodedialog + if (!stricmp(panel->GetName(), "BuildDialog")) + continue; + + // get the keys section from the data file + if (panel->GetName() && *panel->GetName()) + { + KeyValues *datKey = resourceData->FindKey(panel->GetName(), true); + + // get the settings + panel->GetSettings(datKey); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: loop though objects in the current control group and remove them all +//----------------------------------------------------------------------------- +void BuildGroup::RemoveSettings() +{ + // loop though objects in the current control group and remove them all + int i; + for( i = 0; i < _controlGroup.Count(); i++ ) + { + // only delete delatable panels + if ( _controlGroup[i].Get()->IsBuildModeDeletable()) + { + delete _controlGroup[i].Get(); + _controlGroup.Remove(i); + --i; + } + } + + // remove deleted panels from the handle list + for( i = 0; i < _panelDar.Count(); i++ ) + { + if ( !_panelDar[i].Get() ) + { + _panelDar.Remove(i); + --i; + } + } + + _currentPanel = m_pBuildContext; + _currentPanel->InvalidateLayout(); + m_pBuildContext->Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the panel from which the build group gets all it's object creation info +//----------------------------------------------------------------------------- +void BuildGroup::SetContextPanel(Panel *contextPanel) +{ + m_pBuildContext = contextPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the panel from which the build group gets all it's object creation info +//----------------------------------------------------------------------------- +Panel *BuildGroup::GetContextPanel() +{ + return m_pBuildContext; +} + +//----------------------------------------------------------------------------- +// Purpose: get the list of panels in the buildgroup +//----------------------------------------------------------------------------- +CUtlVector<PHandle> *BuildGroup::GetPanelList() +{ + return &_panelDar; +} + +//----------------------------------------------------------------------------- +// Purpose: dialog variables +//----------------------------------------------------------------------------- +KeyValues *BuildGroup::GetDialogVariables() +{ + EditablePanel *edit = dynamic_cast<EditablePanel *>(m_pParentPanel); + if (edit) + { + return edit->GetDialogVariables(); + } + + return NULL; +} + +bool BuildGroup::PrecacheResFile( const char* pszResFileName ) +{ + KeyValues *pkvResFile = new KeyValues( pszResFileName ); + if ( pkvResFile->LoadFromFile( g_pFullFileSystem, pszResFileName, "GAME" ) ) + { + Assert( m_dictCachedResFiles.Find( pszResFileName ) == m_dictCachedResFiles.InvalidIndex() ); + m_dictCachedResFiles.Insert( pszResFileName, pkvResFile ); + return true; + } + + return false; +} + +void BuildGroup::ClearResFileCache() +{ + int nIndex = m_dictCachedResFiles.First(); + while( m_dictCachedResFiles.IsValidIndex( nIndex ) ) + { + m_dictCachedResFiles[ nIndex ]->deleteThis(); + nIndex = m_dictCachedResFiles.Next( nIndex ); + } + m_dictCachedResFiles.Purge(); +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/BuildModeDialog.cpp b/vgui2/vgui_controls/BuildModeDialog.cpp new file mode 100644 index 0000000..c743c20 --- /dev/null +++ b/vgui2/vgui_controls/BuildModeDialog.cpp @@ -0,0 +1,1441 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <ctype.h> +#include <stdio.h> +#include <utlvector.h> + +#include <vgui/IInput.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> + +#include <vgui_controls/BuildModeDialog.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/CheckButton.h> +#include <vgui_controls/RadioButton.h> +#include <vgui_controls/MenuButton.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/BuildGroup.h> +#include <vgui_controls/MessageBox.h> +#include <vgui_controls/Menu.h> +#include <vgui_controls/Divider.h> +#include <vgui_controls/PanelListPanel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +struct PanelItem_t +{ + PanelItem_t() : m_EditLabel(NULL) {} + + Panel *m_EditLabel; + TextEntry *m_EditPanel; + ComboBox *m_pCombo; + Button *m_EditButton; + char m_szName[64]; + int m_iType; +}; + +class CSmallTextEntry : public TextEntry +{ + DECLARE_CLASS_SIMPLE( CSmallTextEntry, TextEntry ); +public: + + CSmallTextEntry( Panel *parent, char const *panelName ) : + BaseClass( parent, panelName ) + { + } + + virtual void ApplySchemeSettings( IScheme *scheme ) + { + BaseClass::ApplySchemeSettings( scheme ); + + SetFont( scheme->GetFont( "DefaultVerySmall" ) ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Holds a list of all the edit fields for the currently selected panel +//----------------------------------------------------------------------------- +class BuildModeDialog::PanelList +{ +public: + + CUtlVector<PanelItem_t> m_PanelList; + + void AddItem( Panel *label, TextEntry *edit, ComboBox *combo, Button *button, const char *name, int type ) + { + PanelItem_t item; + item.m_EditLabel = label; + item.m_EditPanel = edit; + Q_strncpy(item.m_szName, name, sizeof(item.m_szName)); + item.m_iType = type; + item.m_pCombo = combo; + item.m_EditButton = button; + + m_PanelList.AddToTail( item ); + } + + void RemoveAll( void ) + { + for ( int i = 0; i < m_PanelList.Size(); i++ ) + { + PanelItem_t *item = &m_PanelList[i]; + delete item->m_EditLabel; + delete item->m_EditPanel; + delete item->m_EditButton; + } + + m_PanelList.RemoveAll(); + m_pControls->RemoveAll(); + } + + KeyValues *m_pResourceData; + PanelListPanel *m_pControls; +}; + +//----------------------------------------------------------------------------- +// Purpose: Dialog for adding localized strings +//----------------------------------------------------------------------------- +class BuildModeLocalizedStringEditDialog : public Frame +{ + DECLARE_CLASS_SIMPLE(BuildModeLocalizedStringEditDialog, Frame); + +public: + +#pragma warning( disable : 4355 ) + BuildModeLocalizedStringEditDialog() : Frame(this, NULL) + { + m_pTokenEntry = new TextEntry(this, NULL); + m_pValueEntry = new TextEntry(this, NULL); + m_pFileCombo = new ComboBox(this, NULL, 12, false); + m_pOKButton = new Button(this, NULL, "OK"); + m_pCancelButton = new Button(this, NULL, "Cancel"); + + m_pCancelButton->SetCommand("Close"); + m_pOKButton->SetCommand("OK"); + + // add the files to the combo + for (int i = 0; i < g_pVGuiLocalize->GetLocalizationFileCount(); i++) + { + m_pFileCombo->AddItem(g_pVGuiLocalize->GetLocalizationFileName(i), NULL); + } + } +#pragma warning( default : 4355 ) + + virtual void DoModal(const char *token) + { + input()->SetAppModalSurface(GetVPanel()); + + // setup data + m_pTokenEntry->SetText(token); + + // lookup the value + StringIndex_t val = g_pVGuiLocalize->FindIndex(token); + if (val != INVALID_LOCALIZE_STRING_INDEX) + { + m_pValueEntry->SetText(g_pVGuiLocalize->GetValueByIndex(val)); + + // set the place in the file combo + m_pFileCombo->SetText(g_pVGuiLocalize->GetFileNameByIndex(val)); + } + else + { + m_pValueEntry->SetText(""); + } + } + +private: + virtual void PerformLayout() + { + } + + virtual void OnClose() + { + input()->SetAppModalSurface(NULL); + BaseClass::OnClose(); + //PostActionSignal(new KeyValues("Command" + } + + virtual void OnCommand(const char *command) + { + if (!stricmp(command, "OK")) + { + //!! apply changes + } + else + { + BaseClass::OnCommand(command); + } + } + + vgui::TextEntry *m_pTokenEntry; + vgui::TextEntry *m_pValueEntry; + vgui::ComboBox *m_pFileCombo; + vgui::Button *m_pOKButton; + vgui::Button *m_pCancelButton; +}; + +class CBuildModeDialogMgr +{ +public: + + void Add( BuildModeDialog *pDlg ); + void Remove( BuildModeDialog *pDlg ); + + int Count() const; + +private: + CUtlVector< BuildModeDialog * > m_vecBuildDialogs; +}; + +static CBuildModeDialogMgr g_BuildModeDialogMgr; + +void CBuildModeDialogMgr::Add( BuildModeDialog *pDlg ) +{ + if ( m_vecBuildDialogs.Find( pDlg ) == m_vecBuildDialogs.InvalidIndex() ) + { + m_vecBuildDialogs.AddToTail( pDlg ); + } +} + +void CBuildModeDialogMgr::Remove( BuildModeDialog *pDlg ) +{ + m_vecBuildDialogs.FindAndRemove( pDlg ); +} + +int CBuildModeDialogMgr::Count() const +{ + return m_vecBuildDialogs.Count(); +} + +int GetBuildModeDialogCount() +{ + return g_BuildModeDialogMgr.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +BuildModeDialog::BuildModeDialog(BuildGroup *buildGroup) : Frame(buildGroup->GetContextPanel(), "BuildModeDialog") +{ + SetMinimumSize(300, 256); + SetSize(300, 420); + m_pCurrentPanel = NULL; + m_pEditableParents = NULL; + m_pEditableChildren = NULL; + m_pNextChild = NULL; + m_pPrevChild = NULL; + m_pBuildGroup = buildGroup; + _undoSettings = NULL; + _copySettings = NULL; + _autoUpdate = false; + MakePopup(); + SetTitle("VGUI Build Mode Editor", true); + + CreateControls(); + LoadUserConfig("BuildModeDialog"); + + g_BuildModeDialogMgr.Add( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +BuildModeDialog::~BuildModeDialog() +{ + g_BuildModeDialogMgr.Remove( this ); + + m_pPanelList->m_pResourceData->deleteThis(); + m_pPanelList->m_pControls->DeleteAllItems(); + if (_undoSettings) + _undoSettings->deleteThis(); + if (_copySettings) + _copySettings->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: makes sure build mode has been shut down properly +//----------------------------------------------------------------------------- +void BuildModeDialog::OnClose() +{ + if (m_pBuildGroup->IsEnabled()) + { + m_pBuildGroup->SetEnabled(false); + } + else + { + BaseClass::OnClose(); + MarkForDeletion(); + } +} + +class CBuildModeNavCombo : public ComboBox +{ + DECLARE_CLASS_SIMPLE( CBuildModeNavCombo, ComboBox ); +public: + + CBuildModeNavCombo(Panel *parent, const char *panelName, int numLines, bool allowEdit, bool getParents, Panel *context ) : + BaseClass( parent, panelName, numLines, allowEdit ), + m_bParents( getParents ) + { + m_hContext = context; + } + + virtual void OnShowMenu(Menu *menu) + { + menu->DeleteAllItems(); + if ( !m_hContext.Get() ) + return; + + if ( m_bParents ) + { + Panel *p = m_hContext->GetParent(); + while ( p ) + { + EditablePanel *ep = dynamic_cast < EditablePanel * >( p ); + if ( ep && ep->GetBuildGroup() ) + { + KeyValues *kv = new KeyValues( "Panel" ); + kv->SetPtr( "ptr", p ); + char const *text = ep->GetName() ? ep->GetName() : "unnamed"; + menu->AddMenuItem( text, new KeyValues("SetText", "text", text), GetParent(), kv ); + } + p = p->GetParent(); + } + } + else + { + int i; + int c = m_hContext->GetChildCount(); + for ( i = 0; i < c; ++i ) + { + EditablePanel *ep = dynamic_cast < EditablePanel * >( m_hContext->GetChild( i ) ); + if ( ep && ep->IsVisible() && ep->GetBuildGroup() ) + { + KeyValues *kv = new KeyValues( "Panel" ); + kv->SetPtr( "ptr", ep ); + char const *text = ep->GetName() ? ep->GetName() : "unnamed"; + menu->AddMenuItem( text, new KeyValues("SetText", "text", text), GetParent(), kv ); + } + } + } + } +private: + bool m_bParents; + vgui::PHandle m_hContext; +}; + +//----------------------------------------------------------------------------- +// Purpose: Creates the build mode editing controls +//----------------------------------------------------------------------------- +void BuildModeDialog::CreateControls() +{ + int i; + m_pPanelList = new PanelList; + m_pPanelList->m_pResourceData = new KeyValues( "BuildDialog" ); + m_pPanelList->m_pControls = new PanelListPanel(this, "BuildModeControls"); + + // file to edit combo box is first + m_pFileSelectionCombo = new ComboBox(this, "FileSelectionCombo", 10, false); + for ( i = 0; i < m_pBuildGroup->GetRegisteredControlSettingsFileCount(); i++) + { + m_pFileSelectionCombo->AddItem(m_pBuildGroup->GetRegisteredControlSettingsFileByIndex(i), NULL); + } + if (m_pFileSelectionCombo->GetItemCount() < 2) + { + m_pFileSelectionCombo->SetEnabled(false); + } + + int buttonH = 18; + + // status info at top of dialog + m_pStatusLabel = new Label(this, "StatusLabel", "[nothing currently selected]"); + m_pStatusLabel->SetTextColorState(Label::CS_DULL); + m_pStatusLabel->SetTall( buttonH ); + m_pDivider = new Divider(this, "Divider"); + // drop-down combo box for adding new controls + m_pAddNewControlCombo = new ComboBox(this, NULL, 30, false); + m_pAddNewControlCombo->SetSize(116, buttonH); + m_pAddNewControlCombo->SetOpenDirection(Menu::DOWN); + + m_pEditableParents = new CBuildModeNavCombo( this, NULL, 15, false, true, m_pBuildGroup->GetContextPanel() ); + m_pEditableParents->SetSize(116, buttonH); + m_pEditableParents->SetOpenDirection(Menu::DOWN); + + m_pEditableChildren = new CBuildModeNavCombo( this, NULL, 15, false, false, m_pBuildGroup->GetContextPanel() ); + m_pEditableChildren->SetSize(116, buttonH); + m_pEditableChildren->SetOpenDirection(Menu::DOWN); + + m_pNextChild = new Button( this, "NextChild", "Next", this ); + m_pNextChild->SetCommand( new KeyValues( "OnChangeChild", "direction", 1 ) ); + + m_pPrevChild = new Button( this, "PrevChild", "Prev", this ); + m_pPrevChild->SetCommand( new KeyValues( "OnChangeChild", "direction", -1 ) ); + + // controls that can be added + // this list comes from controls EditablePanel can create by name. + int defaultItem = m_pAddNewControlCombo->AddItem("None", NULL); + + CUtlVector< char const * > names; + CBuildFactoryHelper::GetFactoryNames( names ); + // Sort the names + CUtlRBTree< char const *, int > sorted( 0, 0, StringLessThan ); + + for ( i = 0; i < names.Count(); ++i ) + { + sorted.Insert( names[ i ] ); + } + + for ( i = sorted.FirstInorder(); i != sorted.InvalidIndex(); i = sorted.NextInorder( i ) ) + { + m_pAddNewControlCombo->AddItem( sorted[ i ], NULL ); + } + + m_pAddNewControlCombo->ActivateItem(defaultItem); + + m_pExitButton = new Button(this, "ExitButton", "&Exit"); + m_pExitButton->SetSize(64, buttonH); + + m_pSaveButton = new Button(this, "SaveButton", "&Save"); + m_pSaveButton->SetSize(64, buttonH); + + m_pApplyButton = new Button(this, "ApplyButton", "&Apply"); + m_pApplyButton->SetSize(64, buttonH); + + m_pReloadLocalization = new Button( this, "Localization", "&Reload Localization" ); + m_pReloadLocalization->SetSize( 100, buttonH ); + + m_pExitButton->SetCommand("Exit"); + m_pSaveButton->SetCommand("Save"); + m_pApplyButton->SetCommand("Apply"); + m_pReloadLocalization->SetCommand( new KeyValues( "ReloadLocalization" ) ); + + m_pDeleteButton = new Button(this, "DeletePanelButton", "Delete"); + m_pDeleteButton->SetSize(64, buttonH); + m_pDeleteButton->SetCommand("DeletePanel"); + + m_pVarsButton = new MenuButton(this, "VarsButton", "Variables"); + m_pVarsButton->SetSize(72, buttonH); + m_pVarsButton->SetOpenDirection(Menu::UP); + + // iterate the vars + KeyValues *vars = m_pBuildGroup->GetDialogVariables(); + if (vars && vars->GetFirstSubKey()) + { + // create the menu + m_pVarsButton->SetEnabled(true); + Menu *menu = new Menu(m_pVarsButton, "VarsMenu"); + + // set all the variables to be copied to the clipboard when selected + for (KeyValues *kv = vars->GetFirstSubKey(); kv != NULL; kv = kv->GetNextKey()) + { + char buf[32]; + _snprintf(buf, sizeof(buf), "%%%s%%", kv->GetName()); + menu->AddMenuItem(kv->GetName(), new KeyValues("SetClipboardText", "text", buf), this); + } + + m_pVarsButton->SetMenu(menu); + } + else + { + // no variables + m_pVarsButton->SetEnabled(false); + } + + m_pApplyButton->SetTabPosition(1); + m_pPanelList->m_pControls->SetTabPosition(2); + m_pVarsButton->SetTabPosition(3); + m_pDeleteButton->SetTabPosition(4); + m_pAddNewControlCombo->SetTabPosition(5); + m_pSaveButton->SetTabPosition(6); + m_pExitButton->SetTabPosition(7); + + m_pEditableParents->SetTabPosition( 8 ); + m_pEditableChildren->SetTabPosition( 9 ); + + m_pPrevChild->SetTabPosition( 10 ); + m_pNextChild->SetTabPosition( 11 ); + + m_pReloadLocalization->SetTabPosition( 12 ); +} + +void BuildModeDialog::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + HFont font = pScheme->GetFont( "DefaultVerySmall" ); + m_pStatusLabel->SetFont( font ); + m_pReloadLocalization->SetFont( font ); + m_pExitButton->SetFont( font ); + m_pSaveButton->SetFont( font ); + m_pApplyButton->SetFont( font ); + m_pAddNewControlCombo->SetFont( font ); + m_pEditableParents->SetFont( font ); + m_pEditableChildren->SetFont( font ); + m_pDeleteButton->SetFont( font ); + m_pVarsButton->SetFont( font ); + m_pPrevChild->SetFont( font ); + m_pNextChild->SetFont( font ); +} + +//----------------------------------------------------------------------------- +// Purpose: lays out controls +//----------------------------------------------------------------------------- +void BuildModeDialog::PerformLayout() +{ + BaseClass::PerformLayout(); + + // layout parameters + const int BORDER_GAP = 16, YGAP_SMALL = 4, YGAP_LARGE = 8, TITLE_HEIGHT = 24, BOTTOM_CONTROLS_HEIGHT = 145, XGAP = 6; + + int wide, tall; + GetSize(wide, tall); + + int xpos = BORDER_GAP; + int ypos = BORDER_GAP + TITLE_HEIGHT; + + // controls from top down + // selection combo + m_pFileSelectionCombo->SetBounds(xpos, ypos, wide - (BORDER_GAP * 2), m_pStatusLabel->GetTall()); + ypos += (m_pStatusLabel->GetTall() + YGAP_SMALL); + + // status + m_pStatusLabel->SetBounds(xpos, ypos, wide - (BORDER_GAP * 2), m_pStatusLabel->GetTall()); + ypos += (m_pStatusLabel->GetTall() + YGAP_SMALL); + + // center control + m_pPanelList->m_pControls->SetPos(xpos, ypos); + m_pPanelList->m_pControls->SetSize(wide - (BORDER_GAP * 2), tall - (ypos + BOTTOM_CONTROLS_HEIGHT)); + + // controls from bottom-right + ypos = tall - BORDER_GAP; + xpos = BORDER_GAP + m_pVarsButton->GetWide() + m_pDeleteButton->GetWide() + m_pAddNewControlCombo->GetWide() + (XGAP * 2); + + // bottom row of buttons + ypos -= m_pApplyButton->GetTall(); + xpos -= m_pApplyButton->GetWide(); + m_pApplyButton->SetPos(xpos, ypos); + + xpos -= m_pExitButton->GetWide(); + xpos -= XGAP; + m_pExitButton->SetPos(xpos, ypos); + + xpos -= m_pSaveButton->GetWide(); + xpos -= XGAP; + m_pSaveButton->SetPos(xpos, ypos); + + // divider + xpos = BORDER_GAP; + ypos -= (YGAP_LARGE + m_pDivider->GetTall()); + m_pDivider->SetBounds(xpos, ypos, wide - (xpos + BORDER_GAP), 2); + + ypos -= (YGAP_LARGE + m_pVarsButton->GetTall()); + + xpos = BORDER_GAP; + m_pEditableParents->SetPos( xpos, ypos ); + m_pEditableChildren->SetPos( xpos + 150, ypos ); + + ypos -= (YGAP_LARGE + 18 ); + xpos = BORDER_GAP; + m_pReloadLocalization->SetPos( xpos, ypos ); + + xpos += ( XGAP ) + m_pReloadLocalization->GetWide(); + + m_pPrevChild->SetPos( xpos, ypos ); + m_pPrevChild->SetSize( 64, m_pReloadLocalization->GetTall() ); + xpos += ( XGAP ) + m_pPrevChild->GetWide(); + + m_pNextChild->SetPos( xpos, ypos ); + m_pNextChild->SetSize( 64, m_pReloadLocalization->GetTall() ); + + ypos -= (YGAP_LARGE + m_pVarsButton->GetTall()); + xpos = BORDER_GAP; + + // edit buttons + m_pVarsButton->SetPos(xpos, ypos); + xpos += (XGAP + m_pVarsButton->GetWide()); + m_pDeleteButton->SetPos(xpos, ypos); + xpos += (XGAP + m_pDeleteButton->GetWide()); + m_pAddNewControlCombo->SetPos(xpos, ypos); +} + + +//----------------------------------------------------------------------------- +// Purpose: Deletes all the controls from the panel +//----------------------------------------------------------------------------- +void BuildModeDialog::RemoveAllControls( void ) +{ + // free the array + m_pPanelList->RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: simple helper function to get a token from a string +// Input : char **string - pointer to the string pointer, which will be incremented +// Output : const char * - pointer to the token +//----------------------------------------------------------------------------- +const char *ParseTokenFromString( const char **string ) +{ + static char buf[128]; + buf[0] = 0; + + // find the first alnum character + const char *tok = *string; + while ( !V_isalnum(*tok) && *tok != 0 ) + { + tok++; + } + + // read in all the alnum characters + int pos = 0; + while ( V_isalnum(tok[pos]) ) + { + buf[pos] = tok[pos]; + pos++; + } + + // null terminate the token + buf[pos] = 0; + + // update the main string pointer + *string = &(tok[pos]); + + // return a pointer to the static buffer + return buf; +} + +void BuildModeDialog::OnTextKillFocus() +{ + if ( !m_pCurrentPanel ) + return; + + ApplyDataToControls(); +} + + +//----------------------------------------------------------------------------- +// Purpose: sets up the current control to edit +//----------------------------------------------------------------------------- +void BuildModeDialog::SetActiveControl(Panel *controlToEdit) +{ + if (m_pCurrentPanel == controlToEdit) + { + // it's already set, so just update the property data and quit + if (m_pCurrentPanel) + { + UpdateControlData(m_pCurrentPanel); + } + return; + } + + // reset the data + m_pCurrentPanel = controlToEdit; + RemoveAllControls(); + m_pPanelList->m_pControls->MoveScrollBarToTop(); + + if (!m_pCurrentPanel) + { + m_pStatusLabel->SetText("[nothing currently selected]"); + m_pStatusLabel->SetTextColorState(Label::CS_DULL); + RemoveAllControls(); + return; + } + + // get the control description string + const char *controlDesc = m_pCurrentPanel->GetDescription(); + + // parse out the control description + int tabPosition = 1; + while (1) + { + const char *dataType = ParseTokenFromString(&controlDesc); + + // finish when we have no more tokens + if (*dataType == 0) + break; + + // default the data type to a string + int datat = TYPE_STRING; + + if (!stricmp(dataType, "int")) + { + datat = TYPE_STRING; //!! just for now + } + else if (!stricmp(dataType, "alignment")) + { + datat = TYPE_ALIGNMENT; + } + else if (!stricmp(dataType, "autoresize")) + { + datat = TYPE_AUTORESIZE; + } + else if (!stricmp(dataType, "corner")) + { + datat = TYPE_CORNER; + } + else if (!stricmp(dataType, "localize")) + { + datat = TYPE_LOCALIZEDSTRING; + } + + // get the field name + const char *fieldName = ParseTokenFromString(&controlDesc); + + int itemHeight = 18; + + // build a control & label + Label *label = new Label(this, NULL, fieldName); + label->SetSize(96, itemHeight); + label->SetContentAlignment(Label::a_east); + + TextEntry *edit = NULL; + ComboBox *editCombo = NULL; + Button *editButton = NULL; + if (datat == TYPE_ALIGNMENT) + { + // drop-down combo box + editCombo = new ComboBox(this, NULL, 9, false); + editCombo->AddItem("north-west", NULL); + editCombo->AddItem("north", NULL); + editCombo->AddItem("north-east", NULL); + editCombo->AddItem("west", NULL); + editCombo->AddItem("center", NULL); + editCombo->AddItem("east", NULL); + editCombo->AddItem("south-west", NULL); + editCombo->AddItem("south", NULL); + editCombo->AddItem("south-east", NULL); + + edit = editCombo; + } + else if (datat == TYPE_AUTORESIZE) + { + // drop-down combo box + editCombo = new ComboBox(this, NULL, 4, false); + editCombo->AddItem( "0 - no auto-resize", NULL); + editCombo->AddItem( "1 - resize right", NULL); + editCombo->AddItem( "2 - resize down", NULL); + editCombo->AddItem( "3 - down & right", NULL); + + edit = editCombo; + } + else if (datat == TYPE_CORNER) + { + // drop-down combo box + editCombo = new ComboBox(this, NULL, 4, false); + editCombo->AddItem("0 - top-left", NULL); + editCombo->AddItem("1 - top-right", NULL); + editCombo->AddItem("2 - bottom-left", NULL); + editCombo->AddItem("3 - bottom-right", NULL); + + edit = editCombo; + } + else if (datat == TYPE_LOCALIZEDSTRING) + { + editButton = new Button(this, NULL, "..."); + editButton->SetParent(this); + editButton->AddActionSignalTarget(this); + editButton->SetTabPosition(tabPosition++); + editButton->SetTall( itemHeight ); + label->SetAssociatedControl(editButton); + } + else + { + // normal string edit + edit = new CSmallTextEntry(this, NULL); + } + + if (edit) + { + edit->SetTall( itemHeight ); + edit->SetParent(this); + edit->AddActionSignalTarget(this); + edit->SetTabPosition(tabPosition++); + label->SetAssociatedControl(edit); + } + + HFont smallFont = scheme()->GetIScheme( GetScheme() )->GetFont( "DefaultVerySmall" ); + + if ( label ) + { + label->SetFont( smallFont ); + } + if ( edit ) + { + edit->SetFont( smallFont ); + } + if ( editCombo ) + { + editCombo->SetFont( smallFont ); + } + if ( editButton ) + { + editButton->SetFont( smallFont ); + } + + // add to our control list + m_pPanelList->AddItem(label, edit, editCombo, editButton, fieldName, datat); + + if ( edit ) + { + m_pPanelList->m_pControls->AddItem(label, edit); + } + else + { + m_pPanelList->m_pControls->AddItem(label, editButton); + } + } + + // check and see if the current panel is a Label + // iterate through the class hierarchy + if ( controlToEdit->IsBuildModeDeletable() ) + { + m_pDeleteButton->SetEnabled(true); + } + else + { + m_pDeleteButton->SetEnabled(false); + } + + // update the property data in the dialog + UpdateControlData(m_pCurrentPanel); + + // set our title + if ( m_pBuildGroup->GetResourceName() ) + { + m_pFileSelectionCombo->SetText(m_pBuildGroup->GetResourceName()); + } + else + { + m_pFileSelectionCombo->SetText("[ no resource file associated with dialog ]"); + } + + m_pApplyButton->SetEnabled(false); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the edit fields with information about the control +//----------------------------------------------------------------------------- +void BuildModeDialog::UpdateControlData(Panel *control) +{ + KeyValues *dat = m_pPanelList->m_pResourceData->FindKey( control->GetName(), true ); + control->GetSettings( dat ); + + // apply the settings to the edit panels + for ( int i = 0; i < m_pPanelList->m_PanelList.Size(); i++ ) + { + const char *name = m_pPanelList->m_PanelList[i].m_szName; + const char *datstring = dat->GetString( name, "" ); + + UpdateEditControl(m_pPanelList->m_PanelList[i], datstring); + } + + char statusText[512]; + Q_snprintf(statusText, sizeof(statusText), "%s: \'%s\'", control->GetClassName(), control->GetName()); + m_pStatusLabel->SetText(statusText); + m_pStatusLabel->SetTextColorState(Label::CS_NORMAL); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the data in a single edit control +//----------------------------------------------------------------------------- +void BuildModeDialog::UpdateEditControl(PanelItem_t &panelItem, const char *datstring) +{ + switch (panelItem.m_iType) + { + case TYPE_AUTORESIZE: + case TYPE_CORNER: + { + int dat = atoi(datstring); + panelItem.m_pCombo->ActivateItemByRow(dat); + } + break; + + case TYPE_LOCALIZEDSTRING: + { + panelItem.m_EditButton->SetText(datstring); + } + break; + + default: + { + wchar_t unicode[512]; + g_pVGuiLocalize->ConvertANSIToUnicode(datstring, unicode, sizeof(unicode)); + panelItem.m_EditPanel->SetText(unicode); + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when one of the buttons is pressed +//----------------------------------------------------------------------------- +void BuildModeDialog::OnCommand(const char *command) +{ + if (!stricmp(command, "Save")) + { + // apply the current data and save it to disk + ApplyDataToControls(); + if (m_pBuildGroup->SaveControlSettings()) + { + // disable save button until another change has been made + m_pSaveButton->SetEnabled(false); + } + } + else if (!stricmp(command, "Exit")) + { + // exit build mode + ExitBuildMode(); + } + else if (!stricmp(command, "Apply")) + { + // apply data to controls + ApplyDataToControls(); + } + else if (!stricmp(command, "DeletePanel")) + { + OnDeletePanel(); + } + else if (!stricmp(command, "RevertToSaved")) + { + RevertToSaved(); + } + else if (!stricmp(command, "ShowHelp")) + { + ShowHelp(); + } + else + { + BaseClass::OnCommand(command); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Deletes a panel from the buildgroup +//----------------------------------------------------------------------------- +void BuildModeDialog::OnDeletePanel() +{ + if (!m_pCurrentPanel->IsBuildModeEditable()) + { + return; + } + + m_pBuildGroup->RemoveSettings(); + SetActiveControl(m_pBuildGroup->GetCurrentPanel()); + + _undoSettings->deleteThis(); + _undoSettings = NULL; + m_pSaveButton->SetEnabled(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Applies the current settings to the build controls +//----------------------------------------------------------------------------- +void BuildModeDialog::ApplyDataToControls() +{ + // don't apply if the panel is not editable + if ( !m_pCurrentPanel->IsBuildModeEditable()) + { + UpdateControlData( m_pCurrentPanel ); + return; // return success, since we are behaving as expected. + } + + char fieldName[512]; + if (m_pPanelList->m_PanelList[0].m_EditPanel) + { + m_pPanelList->m_PanelList[0].m_EditPanel->GetText(fieldName, sizeof(fieldName)); + } + else + { + m_pPanelList->m_PanelList[0].m_EditButton->GetText(fieldName, sizeof(fieldName)); + } + + // check to see if any buildgroup panels have this name + Panel *panel = m_pBuildGroup->FieldNameTaken(fieldName); + if (panel) + { + if (panel != m_pCurrentPanel)// make sure name is taken by some other panel not this one + { + char messageString[255]; + Q_snprintf(messageString, sizeof( messageString ), "Fieldname is not unique: %s\nRename it and try again.", fieldName); + MessageBox *errorBox = new MessageBox("Cannot Apply", messageString); + errorBox->DoModal(); + UpdateControlData(m_pCurrentPanel); + m_pApplyButton->SetEnabled(false); + return; + } + } + + // create a section to store settings + // m_pPanelList->m_pResourceData->getSection( m_pCurrentPanel->GetName(), true ); + KeyValues *dat = new KeyValues( m_pCurrentPanel->GetName() ); + + // loop through the textedit filling in settings + for ( int i = 0; i < m_pPanelList->m_PanelList.Size(); i++ ) + { + const char *name = m_pPanelList->m_PanelList[i].m_szName; + char buf[512]; + if (m_pPanelList->m_PanelList[i].m_EditPanel) + { + m_pPanelList->m_PanelList[i].m_EditPanel->GetText(buf, sizeof(buf)); + } + else + { + m_pPanelList->m_PanelList[i].m_EditButton->GetText(buf, sizeof(buf)); + } + + switch (m_pPanelList->m_PanelList[i].m_iType) + { + case TYPE_CORNER: + case TYPE_AUTORESIZE: + // the integer value is assumed to be the first part of the string for these items + dat->SetInt(name, atoi(buf)); + break; + + default: + dat->SetString(name, buf); + break; + } + } + + // dat is built, hand it back to the control + m_pCurrentPanel->ApplySettings( dat ); + + if ( m_pBuildGroup->GetContextPanel() ) + { + m_pBuildGroup->GetContextPanel()->Repaint(); + } + + m_pApplyButton->SetEnabled(false); + m_pSaveButton->SetEnabled(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Store the settings of the current panel in a KeyValues +//----------------------------------------------------------------------------- +void BuildModeDialog::StoreUndoSettings() +{ + // don't save if the planel is not editable + if ( !m_pCurrentPanel->IsBuildModeEditable()) + { + if (_undoSettings) + _undoSettings->deleteThis(); + _undoSettings = NULL; + return; + } + + if (_undoSettings) + { + _undoSettings->deleteThis(); + _undoSettings = NULL; + } + + _undoSettings = StoreSettings(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Revert to the stored the settings of the current panel in a keyValues +//----------------------------------------------------------------------------- +void BuildModeDialog::DoUndo() +{ + if ( _undoSettings ) + { + m_pCurrentPanel->ApplySettings( _undoSettings ); + UpdateControlData(m_pCurrentPanel); + _undoSettings->deleteThis(); + _undoSettings = NULL; + } + + m_pSaveButton->SetEnabled(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Copy the settings of the current panel into a keyValues +//----------------------------------------------------------------------------- +void BuildModeDialog::DoCopy() +{ + if (_copySettings) + { + _copySettings->deleteThis(); + _copySettings = NULL; + } + + _copySettings = StoreSettings(); + Q_strncpy (_copyClassName, m_pCurrentPanel->GetClassName(), sizeof( _copyClassName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new Panel with the _copySettings applied +//----------------------------------------------------------------------------- +void BuildModeDialog::DoPaste() +{ + // Make a new control located where you had the mouse + int x, y; + input()->GetCursorPos(x, y); + m_pBuildGroup->GetContextPanel()->ScreenToLocal(x,y); + + Panel *newPanel = OnNewControl(_copyClassName, x, y); + if (newPanel) + { + newPanel->ApplySettings(_copySettings); + newPanel->SetPos(x, y); + char name[255]; + m_pBuildGroup->GetNewFieldName(name, sizeof(name), newPanel); + newPanel->SetName(name); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Store the settings of the current panel in a keyValues +//----------------------------------------------------------------------------- +KeyValues *BuildModeDialog::StoreSettings() +{ + KeyValues *storedSettings; + storedSettings = new KeyValues( m_pCurrentPanel->GetName() ); + + // loop through the textedit filling in settings + for ( int i = 0; i < m_pPanelList->m_PanelList.Size(); i++ ) + { + const char *name = m_pPanelList->m_PanelList[i].m_szName; + char buf[512]; + if (m_pPanelList->m_PanelList[i].m_EditPanel) + { + m_pPanelList->m_PanelList[i].m_EditPanel->GetText(buf, sizeof(buf)); + } + else + { + m_pPanelList->m_PanelList[i].m_EditButton->GetText(buf, sizeof(buf)); + } + + switch (m_pPanelList->m_PanelList[i].m_iType) + { + case TYPE_CORNER: + case TYPE_AUTORESIZE: + // the integer value is assumed to be the first part of the string for these items + storedSettings->SetInt(name, atoi(buf)); + break; + + default: + storedSettings->SetString(name, buf); + break; + } + } + + return storedSettings; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void BuildModeDialog::OnKeyCodeTyped(KeyCode code) +{ + if (code == KEY_ENTER) // if someone hits return apply the changes + { + ApplyDataToControls(); + } + else + { + Frame::OnKeyCodeTyped(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if any text has changed +//----------------------------------------------------------------------------- +void BuildModeDialog::OnTextChanged( Panel *panel ) +{ + if (panel == m_pFileSelectionCombo) + { + // reload file if it's changed + char newFile[512]; + m_pFileSelectionCombo->GetText(newFile, sizeof(newFile)); + + if (stricmp(newFile, m_pBuildGroup->GetResourceName()) != 0) + { + // file has changed, reload + SetActiveControl(NULL); + m_pBuildGroup->ChangeControlSettingsFile(newFile); + } + return; + } + + if (panel == m_pAddNewControlCombo) + { + char buf[40]; + m_pAddNewControlCombo->GetText(buf, 40); + if (stricmp(buf, "None") != 0) + { + OnNewControl(buf); + // reset box back to None + m_pAddNewControlCombo->ActivateItemByRow( 0 ); + } + } + + if ( panel == m_pEditableChildren ) + { + KeyValues *kv = m_pEditableChildren->GetActiveItemUserData(); + if ( kv ) + { + EditablePanel *ep = reinterpret_cast< EditablePanel * >( kv->GetPtr( "ptr" ) ); + if ( ep ) + { + ep->ActivateBuildMode(); + } + } + } + + if ( panel == m_pEditableParents ) + { + KeyValues *kv = m_pEditableParents->GetActiveItemUserData(); + if ( kv ) + { + EditablePanel *ep = reinterpret_cast< EditablePanel * >( kv->GetPtr( "ptr" ) ); + if ( ep ) + { + ep->ActivateBuildMode(); + } + } + } + + if (m_pCurrentPanel && m_pCurrentPanel->IsBuildModeEditable()) + { + m_pApplyButton->SetEnabled(true); + } + + if (_autoUpdate) + { + ApplyDataToControls(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void BuildModeDialog::ExitBuildMode( void ) +{ + // make sure rulers are off + if (m_pBuildGroup->HasRulersOn()) + { + m_pBuildGroup->ToggleRulerDisplay(); + } + m_pBuildGroup->SetEnabled(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new control in the context panel +//----------------------------------------------------------------------------- +Panel *BuildModeDialog::OnNewControl( const char *name, int x, int y) +{ + // returns NULL on failure + Panel *newPanel = m_pBuildGroup->NewControl(name, x, y); + if (newPanel) + { + // call mouse commands to simulate selecting the new + // panel. This will set everything up correctly in the buildGroup. + m_pBuildGroup->MousePressed(MOUSE_LEFT, newPanel); + m_pBuildGroup->MouseReleased(MOUSE_LEFT, newPanel); + } + + m_pSaveButton->SetEnabled(true); + + return newPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: enable the save button, useful when buildgroup needs to Activate it. +//----------------------------------------------------------------------------- +void BuildModeDialog::EnableSaveButton() +{ + m_pSaveButton->SetEnabled(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Revert to the saved settings in the .res file +//----------------------------------------------------------------------------- +void BuildModeDialog::RevertToSaved() +{ + // hide the dialog as reloading will destroy it + surface()->SetPanelVisible(this->GetVPanel(), false); + m_pBuildGroup->ReloadControlSettings(); +} + +//----------------------------------------------------------------------------- +// Purpose: Display some information about the editor +//----------------------------------------------------------------------------- +void BuildModeDialog::ShowHelp() +{ + char helpText[]= "In the Build Mode Dialog Window:\n" + "Delete button - deletes the currently selected panel if it is deletable.\n" + "Apply button - applies changes to the Context Panel.\n" + "Save button - saves all settings to file. \n" + "Revert to saved- reloads the last saved file.\n" + "Auto Update - any changes apply instantly.\n" + "Typing Enter in any text field applies changes.\n" + "New Control menu - creates a new panel in the upper left corner.\n\n" + "In the Context Panel:\n" + "After selecting and moving a panel Ctrl-z will undo the move.\n" + "Shift clicking panels allows multiple panels to be selected into a group.\n" + "Ctrl-c copies the settings of the last selected panel.\n" + "Ctrl-v creates a new panel with the copied settings at the location of the mouse pointer.\n" + "Arrow keys slowly move panels, holding shift + arrow will slowly resize it.\n" + "Holding right mouse button down opens a dropdown panel creation menu.\n" + " Panel will be created where the menu was opened.\n" + "Delete key deletes the currently selected panel if it is deletable.\n" + " Does nothing to multiple selections."; + + MessageBox *helpDlg = new MessageBox ("Build Mode Help", helpText, this); + helpDlg->AddActionSignalTarget(this); + helpDlg->DoModal(); +} + + +void BuildModeDialog::ShutdownBuildMode() +{ + m_pBuildGroup->SetEnabled(false); +} + +void BuildModeDialog::OnPanelMoved() +{ + m_pApplyButton->SetEnabled(true); +} + +//----------------------------------------------------------------------------- +// Purpose: message handles thats sets the text in the clipboard +//----------------------------------------------------------------------------- +void BuildModeDialog::OnSetClipboardText(const char *text) +{ + system()->SetClipboardText(text, strlen(text)); +} + +void BuildModeDialog::OnCreateNewControl( char const *text ) +{ + if ( !Q_stricmp( text, "None" ) ) + return; + + OnNewControl( text, m_nClick[ 0 ], m_nClick[ 1 ] ); +} + +void BuildModeDialog::OnShowNewControlMenu() +{ + if ( !m_pBuildGroup ) + return; + + int i; + + input()->GetCursorPos( m_nClick[ 0 ], m_nClick[ 1 ] ); + m_pBuildGroup->GetContextPanel()->ScreenToLocal( m_nClick[ 0 ], m_nClick[ 1 ] ); + + if ( m_hContextMenu ) + delete m_hContextMenu.Get(); + + m_hContextMenu = new Menu( this, "NewControls" ); + + // Show popup menu + m_hContextMenu->AddMenuItem( "None", "None", new KeyValues( "CreateNewControl", "text", "None" ), this ); + + CUtlVector< char const * > names; + CBuildFactoryHelper::GetFactoryNames( names ); + // Sort the names + CUtlRBTree< char const *, int > sorted( 0, 0, StringLessThan ); + + for ( i = 0; i < names.Count(); ++i ) + { + sorted.Insert( names[ i ] ); + } + + for ( i = sorted.FirstInorder(); i != sorted.InvalidIndex(); i = sorted.NextInorder( i ) ) + { + m_hContextMenu->AddMenuItem( sorted[ i ], sorted[ i ], new KeyValues( "CreateNewControl", "text", sorted[ i ] ), this ); + } + + Menu::PlaceContextMenu( this, m_hContextMenu ); +} + +void BuildModeDialog::OnReloadLocalization() +{ + // reload localization files + g_pVGuiLocalize->ReloadLocalizationFiles( ); +} + +bool BuildModeDialog::IsBuildGroupEnabled() +{ + // Don't ever edit the actual build dialog!!! + return false; +} + +void BuildModeDialog::OnChangeChild( int direction ) +{ + Assert( direction == 1 || direction == -1 ); + if ( !m_pBuildGroup ) + return; + + Panel *current = m_pCurrentPanel; + Panel *context = m_pBuildGroup->GetContextPanel(); + + if ( !current || current == context ) + { + current = NULL; + if ( context->GetChildCount() > 0 ) + { + current = context->GetChild( 0 ); + } + } + else + { + int i; + // Move in direction requested + int children = context->GetChildCount(); + for ( i = 0; i < children; ++i ) + { + Panel *child = context->GetChild( i ); + if ( child == current ) + { + break; + } + } + + if ( i < children ) + { + for ( int offset = 1; offset < children; ++offset ) + { + int test = ( i + ( direction * offset ) ) % children; + if ( test < 0 ) + test += children; + if ( test == i ) + continue; + + Panel *check = context->GetChild( test ); + BuildModeDialog *bm = dynamic_cast< BuildModeDialog * >( check ); + if ( bm ) + continue; + + current = check; + break; + } + } + } + + if ( !current ) + { + return; + } + + SetActiveControl( current ); +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/Button.cpp b/vgui2/vgui_controls/Button.cpp new file mode 100644 index 0000000..764c695 --- /dev/null +++ b/vgui2/vgui_controls/Button.cpp @@ -0,0 +1,1097 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Basic button control +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> +#include <utlsymbol.h> + +#include <vgui/IBorder.h> +#include <vgui/IInput.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/IVGui.h> +#include <vgui/MouseCode.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> + +#include <vgui_controls/Button.h> +#include <vgui_controls/FocusNavGroup.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +// global list of all the names of all the sounds played by buttons +CUtlSymbolTable g_ButtonSoundNames; + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( Button, Button ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Button::Button(Panel *parent, const char *panelName, const char *text, Panel *pActionSignalTarget, const char *pCmd ) : Label(parent, panelName, text) +{ + Init(); + if ( pActionSignalTarget && pCmd ) + { + AddActionSignalTarget( pActionSignalTarget ); + SetCommand( pCmd ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Button::Button(Panel *parent, const char *panelName, const wchar_t *wszText, Panel *pActionSignalTarget, const char *pCmd ) : Label(parent, panelName, wszText) +{ + Init(); + if ( pActionSignalTarget && pCmd ) + { + AddActionSignalTarget( pActionSignalTarget ); + SetCommand( pCmd ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::Init() +{ + _buttonFlags.SetFlag( USE_CAPTURE_MOUSE | BUTTON_BORDER_ENABLED ); + + _mouseClickMask = 0; + _actionMessage = NULL; + _defaultBorder = NULL; + _depressedBorder = NULL; + _keyFocusBorder = NULL; + m_bSelectionStateSaved = false; + m_bStaySelectedOnClick = false; + m_bStaySelectedOnClick = false; + m_sArmedSoundName = UTL_INVAL_SYMBOL; + m_sDepressedSoundName = UTL_INVAL_SYMBOL; + m_sReleasedSoundName = UTL_INVAL_SYMBOL; + SetTextInset(6, 0); + SetMouseClickEnabled( MOUSE_LEFT, true ); + SetButtonActivationType(ACTIVATE_ONPRESSEDANDRELEASED); + + // labels have this off by default, but we need it on + SetPaintBackgroundEnabled( true ); + + _paint = true; + + REGISTER_COLOR_AS_OVERRIDABLE( _defaultFgColor, "defaultFgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _defaultBgColor, "defaultBgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _armedFgColor, "armedFgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _armedBgColor, "armedBgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _depressedFgColor, "depressedFgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _depressedBgColor, "depressedBgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _selectedFgColor, "selectedFgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _selectedBgColor, "selectedBgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _keyboardFocusColor, "keyboardFocusColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _blinkFgColor, "blinkFgColor_override" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Button::~Button() +{ + if (_actionMessage) + { + _actionMessage->deleteThis(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::SetButtonActivationType(ActivationType_t activationType) +{ + _activationType = activationType; +} + +//----------------------------------------------------------------------------- +// Purpose: Set button border attribute enabled. +//----------------------------------------------------------------------------- +void Button::SetButtonBorderEnabled( bool state ) +{ + if ( state != _buttonFlags.IsFlagSet( BUTTON_BORDER_ENABLED ) ) + { + _buttonFlags.SetFlag( BUTTON_BORDER_ENABLED, state ); + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set button selected state. +//----------------------------------------------------------------------------- +void Button::SetSelected( bool state ) +{ + if ( _buttonFlags.IsFlagSet( SELECTED ) != state ) + { + _buttonFlags.SetFlag( SELECTED, state ); + RecalculateDepressedState(); + InvalidateLayout(false); + } + + if ( !m_bStayArmedOnClick && state && _buttonFlags.IsFlagSet( ARMED ) ) + { + _buttonFlags.SetFlag( ARMED, false ); + InvalidateLayout(false); + } +} + +void Button::SetBlink( bool state ) +{ + if ( _buttonFlags.IsFlagSet( BLINK ) != state ) + { + _buttonFlags.SetFlag( BLINK, state ); + RecalculateDepressedState(); + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set button force depressed state. +//----------------------------------------------------------------------------- +void Button::ForceDepressed(bool state) +{ + if ( _buttonFlags.IsFlagSet( FORCE_DEPRESSED ) != state ) + { + _buttonFlags.SetFlag( FORCE_DEPRESSED, state ); + RecalculateDepressedState(); + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set button depressed state with respect to the force depressed state. +//----------------------------------------------------------------------------- +void Button::RecalculateDepressedState( void ) +{ + bool newState; + if (!IsEnabled()) + { + newState = false; + } + else + { + if ( m_bStaySelectedOnClick && _buttonFlags.IsFlagSet( SELECTED ) ) + { + newState = false; + } + else + { + newState = _buttonFlags.IsFlagSet( FORCE_DEPRESSED ) ? true : (_buttonFlags.IsFlagSet(ARMED) && _buttonFlags.IsFlagSet( SELECTED ) ); + } + } + + _buttonFlags.SetFlag( DEPRESSED, newState ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets whether or not the button captures all mouse input when depressed +// Defaults to true +// Should be set to false for things like menu items where there is a higher-level mouse capture +//----------------------------------------------------------------------------- +void Button::SetUseCaptureMouse( bool state ) +{ + _buttonFlags.SetFlag( USE_CAPTURE_MOUSE, state ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if mouse capture is enabled. +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Button::IsUseCaptureMouseEnabled( void ) +{ + return _buttonFlags.IsFlagSet( USE_CAPTURE_MOUSE ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set armed state. +//----------------------------------------------------------------------------- +void Button::SetArmed(bool state) +{ + if ( _buttonFlags.IsFlagSet( ARMED ) != state ) + { + _buttonFlags.SetFlag( ARMED, state ); + RecalculateDepressedState(); + InvalidateLayout(false); + + // play any sounds specified + if (state && m_sArmedSoundName != UTL_INVAL_SYMBOL) + { + surface()->PlaySound(g_ButtonSoundNames.String(m_sArmedSoundName)); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check armed state +//----------------------------------------------------------------------------- +bool Button::IsArmed() +{ + return _buttonFlags.IsFlagSet( ARMED ); +} + + +KeyValues *Button::GetActionMessage() +{ + return _actionMessage->MakeCopy(); +} + +void Button::PlayButtonReleasedSound() +{ + // check for playing a transition sound + if ( m_sReleasedSoundName != UTL_INVAL_SYMBOL ) + { + surface()->PlaySound( g_ButtonSoundNames.String( m_sReleasedSoundName ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate a button click. +//----------------------------------------------------------------------------- +void Button::DoClick() +{ + SetSelected(true); + FireActionSignal(); + PlayButtonReleasedSound(); + + static ConVarRef vgui_nav_lock( "vgui_nav_lock" ); + if ( ( !vgui_nav_lock.IsValid() || vgui_nav_lock.GetInt() == 0 ) && NavigateActivate() ) + { + vgui_nav_lock.SetValue( 1 ); + } + + if ( !m_bStaySelectedOnClick ) + { + SetSelected(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check selected state +//----------------------------------------------------------------------------- +bool Button::IsSelected() +{ + return _buttonFlags.IsFlagSet( SELECTED ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check depressed state +//----------------------------------------------------------------------------- +bool Button::IsDepressed() +{ + return _buttonFlags.IsFlagSet( DEPRESSED ); +} + +bool Button::IsBlinking( void ) +{ + return _buttonFlags.IsFlagSet( BLINK ); +} + + +//----------------------------------------------------------------------------- +// Drawing focus box? +//----------------------------------------------------------------------------- +bool Button::IsDrawingFocusBox() +{ + return _buttonFlags.IsFlagSet( DRAW_FOCUS_BOX ); +} + +void Button::DrawFocusBox( bool bEnable ) +{ + _buttonFlags.SetFlag( DRAW_FOCUS_BOX, bEnable ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::NavigateTo() +{ + BaseClass::NavigateTo(); + + SetArmed( true ); + + if ( IsPC() ) + { + RequestFocus( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::NavigateFrom() +{ + BaseClass::NavigateFrom(); + + SetArmed( false ); + + OnKeyCodeReleased( KEY_XBUTTON_A ); +} + +//----------------------------------------------------------------------------- +// Purpose: Paint button on screen +//----------------------------------------------------------------------------- +void Button::Paint(void) +{ + if ( !ShouldPaint() ) + return; + + BaseClass::Paint(); + + if ( HasFocus() && IsEnabled() && IsDrawingFocusBox() ) + { + int x0, y0, x1, y1; + int wide, tall; + GetSize(wide, tall); + x0 = 3, y0 = 3, x1 = wide - 4 , y1 = tall - 2; + DrawFocusBorder(x0, y0, x1, y1); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Perform graphical layout of button. +//----------------------------------------------------------------------------- +void Button::PerformLayout() +{ + // reset our border + SetBorder( GetBorder(_buttonFlags.IsFlagSet( DEPRESSED ), _buttonFlags.IsFlagSet( ARMED ), _buttonFlags.IsFlagSet( SELECTED ), HasFocus() ) ); + + // set our color + SetFgColor(GetButtonFgColor()); + SetBgColor(GetButtonBgColor()); + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get button foreground color +// Output : Color +//----------------------------------------------------------------------------- +Color Button::GetButtonFgColor() +{ + if ( !_buttonFlags.IsFlagSet( BLINK ) ) + { + if (_buttonFlags.IsFlagSet( DEPRESSED )) + return _depressedFgColor; + if (_buttonFlags.IsFlagSet( ARMED )) + return _armedFgColor; + if (_buttonFlags.IsFlagSet( SELECTED)) + return _selectedFgColor; + return _defaultFgColor; + } + + Color cBlendedColor; + + if (_buttonFlags.IsFlagSet( DEPRESSED )) + cBlendedColor = _depressedFgColor; + else if (_buttonFlags.IsFlagSet( ARMED )) + cBlendedColor = _armedFgColor; + else if (_buttonFlags.IsFlagSet( SELECTED )) + cBlendedColor = _selectedFgColor; + else + cBlendedColor = _defaultFgColor; + + float fBlink = ( sinf( system()->GetTimeMillis() * 0.01f ) + 1.0f ) * 0.5f; + + if ( _buttonFlags.IsFlagSet( BLINK ) ) + { + cBlendedColor[ 0 ] = (float)cBlendedColor[ 0 ] * fBlink + (float)_blinkFgColor[ 0 ] * ( 1.0f - fBlink ); + cBlendedColor[ 1 ] = (float)cBlendedColor[ 1 ] * fBlink + (float)_blinkFgColor[ 1 ] * ( 1.0f - fBlink ); + cBlendedColor[ 2 ] = (float)cBlendedColor[ 2 ] * fBlink + (float)_blinkFgColor[ 2 ] * ( 1.0f - fBlink ); + cBlendedColor[ 3 ] = (float)cBlendedColor[ 3 ] * fBlink + (float)_blinkFgColor[ 3 ] * ( 1.0f - fBlink ); + } + + return cBlendedColor; +} + +//----------------------------------------------------------------------------- +// Purpose: Get button background color +//----------------------------------------------------------------------------- +Color Button::GetButtonBgColor() +{ + if (_buttonFlags.IsFlagSet( DEPRESSED )) + return _depressedBgColor; + if (_buttonFlags.IsFlagSet( ARMED )) + return _armedBgColor; + if (_buttonFlags.IsFlagSet( SELECTED )) + return _selectedBgColor; + return _defaultBgColor; +} + +//----------------------------------------------------------------------------- +// Purpose: Called when key focus is received +//----------------------------------------------------------------------------- +void Button::OnSetFocus() +{ + InvalidateLayout(false); + BaseClass::OnSetFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Respond when focus is killed +//----------------------------------------------------------------------------- +void Button::OnKillFocus() +{ + InvalidateLayout(false); + BaseClass::OnKillFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + // get the borders we need + _defaultBorder = pScheme->GetBorder("ButtonBorder"); + _depressedBorder = pScheme->GetBorder("ButtonDepressedBorder"); + _keyFocusBorder = pScheme->GetBorder("ButtonKeyFocusBorder"); + + _defaultFgColor = GetSchemeColor("Button.TextColor", Color(255, 255, 255, 255), pScheme); + _defaultBgColor = GetSchemeColor("Button.BgColor", Color(0, 0, 0, 255), pScheme); + + _armedFgColor = GetSchemeColor("Button.ArmedTextColor", _defaultFgColor, pScheme); + _armedBgColor = GetSchemeColor("Button.ArmedBgColor", _defaultBgColor, pScheme); + + _selectedFgColor = GetSchemeColor("Button.SelectedTextColor", _selectedFgColor, pScheme); + _selectedBgColor = GetSchemeColor("Button.SelectedBgColor", _selectedBgColor, pScheme); + + _depressedFgColor = GetSchemeColor("Button.DepressedTextColor", _defaultFgColor, pScheme); + _depressedBgColor = GetSchemeColor("Button.DepressedBgColor", _defaultBgColor, pScheme); + _keyboardFocusColor = GetSchemeColor("Button.FocusBorderColor", Color(0,0,0,255), pScheme); + + _blinkFgColor = GetSchemeColor("Button.BlinkColor", Color(255, 155, 0, 255), pScheme); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set default button colors. +//----------------------------------------------------------------------------- +void Button::SetDefaultColor(Color fgColor, Color bgColor) +{ + if (!(_defaultFgColor == fgColor && _defaultBgColor == bgColor)) + { + _defaultFgColor = fgColor; + _defaultBgColor = bgColor; + + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set armed button colors +//----------------------------------------------------------------------------- +void Button::SetArmedColor(Color fgColor, Color bgColor) +{ + if (!(_armedFgColor == fgColor && _armedBgColor == bgColor)) + { + _armedFgColor = fgColor; + _armedBgColor = bgColor; + + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set armed button colors +//----------------------------------------------------------------------------- +void Button::SetSelectedColor(Color fgColor, Color bgColor) +{ + if (!(_selectedFgColor == fgColor && _selectedBgColor == bgColor)) + { + _selectedFgColor = fgColor; + _selectedBgColor = bgColor; + + InvalidateLayout(false); + } +} +//----------------------------------------------------------------------------- +// Purpose: Set depressed button colors +//----------------------------------------------------------------------------- +void Button::SetDepressedColor(Color fgColor, Color bgColor) +{ + if (!(_depressedFgColor == fgColor && _depressedBgColor == bgColor)) + { + _depressedFgColor = fgColor; + _depressedBgColor = bgColor; + + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set blink button color +//----------------------------------------------------------------------------- +void Button::SetBlinkColor(Color fgColor) +{ + if (!(_blinkFgColor == fgColor)) + { + _blinkFgColor = fgColor; + + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set default button border attributes. +//----------------------------------------------------------------------------- +void Button::SetDefaultBorder(IBorder *border) +{ + _defaultBorder = border; + InvalidateLayout(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Set depressed button border attributes. +//----------------------------------------------------------------------------- +void Button::SetDepressedBorder(IBorder *border) +{ + _depressedBorder = border; + InvalidateLayout(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Set key focus button border attributes. +//----------------------------------------------------------------------------- +void Button::SetKeyFocusBorder(IBorder *border) +{ + _keyFocusBorder = border; + InvalidateLayout(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get button border attributes. +//----------------------------------------------------------------------------- +IBorder *Button::GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) +{ + if ( _buttonFlags.IsFlagSet( BUTTON_BORDER_ENABLED ) ) + { + // raised buttons with no armed state + if (depressed) + return _depressedBorder; + if (keyfocus) + return _keyFocusBorder; + if (IsEnabled() && _buttonFlags.IsFlagSet( DEFAULT_BUTTON )) + return _keyFocusBorder; + return _defaultBorder; + } + else + { + // flat buttons that raise + if (depressed) + return _depressedBorder; + if (armed) + return _defaultBorder; + } + + return _defaultBorder; +} + +//----------------------------------------------------------------------------- +// Purpose: sets this button to be the button that is accessed by default +// when the user hits ENTER or SPACE +//----------------------------------------------------------------------------- +void Button::SetAsCurrentDefaultButton(int state) +{ + if ( _buttonFlags.IsFlagSet( DEFAULT_BUTTON ) != (bool)state ) + { + _buttonFlags.SetFlag( DEFAULT_BUTTON, state ); + if (state) + { + // post a message up notifying our nav group that we're now the default button + KeyValues *msg = new KeyValues( "CurrentDefaultButtonSet" ); + msg->SetInt( "button", ToHandle() ); + CallParentFunction( msg ); + } + + InvalidateLayout(); + Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets this button to be the button that is accessed by default +// when the user hits ENTER or SPACE +//----------------------------------------------------------------------------- +void Button::SetAsDefaultButton(int state) +{ + if ( _buttonFlags.IsFlagSet( DEFAULT_BUTTON ) != (bool)state ) + { + _buttonFlags.SetFlag( DEFAULT_BUTTON, state ); + if (state) + { + // post a message up notifying our nav group that we're now the default button + KeyValues *msg = new KeyValues( "DefaultButtonSet" ); + msg->SetInt( "button", ToHandle() ); + CallParentFunction( msg ); + } + + InvalidateLayout(); + Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets rollover sound +//----------------------------------------------------------------------------- +void Button::SetArmedSound(const char *sound) +{ + if (sound) + { + m_sArmedSoundName = g_ButtonSoundNames.AddString(sound); + } + else + { + m_sArmedSoundName = UTL_INVAL_SYMBOL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::SetDepressedSound(const char *sound) +{ + if (sound) + { + m_sDepressedSoundName = g_ButtonSoundNames.AddString(sound); + } + else + { + m_sDepressedSoundName = UTL_INVAL_SYMBOL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::SetReleasedSound(const char *sound) +{ + if (sound) + { + m_sReleasedSoundName = g_ButtonSoundNames.AddString(sound); + } + else + { + m_sReleasedSoundName = UTL_INVAL_SYMBOL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set button to be mouse clickable or not. +//----------------------------------------------------------------------------- +void Button::SetMouseClickEnabled(MouseCode code,bool state) +{ + if(state) + { + //set bit to 1 + _mouseClickMask|=1<<((int)(code+1)); + } + else + { + //set bit to 0 + _mouseClickMask&=~(1<<((int)(code+1))); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check if button is mouse clickable +//----------------------------------------------------------------------------- +bool Button::IsMouseClickEnabled(MouseCode code) +{ + if(_mouseClickMask&(1<<((int)(code+1)))) + { + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the command to send when the button is pressed +//----------------------------------------------------------------------------- +void Button::SetCommand( const char *command ) +{ + SetCommand(new KeyValues("Command", "command", command)); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the message to send when the button is pressed +//----------------------------------------------------------------------------- +void Button::SetCommand( KeyValues *message ) +{ + // delete the old message + if (_actionMessage) + { + _actionMessage->deleteThis(); + } + + _actionMessage = message; +} + +//----------------------------------------------------------------------------- +// Purpose: Peeks at the message to send when button is pressed +// Input : - +// Output : KeyValues +//----------------------------------------------------------------------------- +KeyValues *Button::GetCommand() +{ + return _actionMessage; +} + +//----------------------------------------------------------------------------- +// Purpose: Message targets that the button has been pressed +//----------------------------------------------------------------------------- +void Button::FireActionSignal() +{ + // message-based action signal + if (_actionMessage) + { + // see if it's a url + if (!stricmp(_actionMessage->GetName(), "command") + && !strnicmp(_actionMessage->GetString("command", ""), "url ", strlen("url ")) + && strstr(_actionMessage->GetString("command", ""), "://")) + { + // it's a command to launch a url, run it + system()->ShellExecute("open", _actionMessage->GetString("command", " ") + 4); + } + PostActionSignal(_actionMessage->MakeCopy()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: gets info about the button +//----------------------------------------------------------------------------- +bool Button::RequestInfo(KeyValues *outputData) +{ + if (!stricmp(outputData->GetName(), "CanBeDefaultButton")) + { + outputData->SetInt("result", CanBeDefaultButton() ? 1 : 0); + return true; + } + else if (!stricmp(outputData->GetName(), "GetState")) + { + outputData->SetInt("state", IsSelected()); + return true; + } + else if ( !stricmp( outputData->GetName(), "GetCommand" )) + { + if ( _actionMessage ) + { + outputData->SetString( "command", _actionMessage->GetString( "command", "" ) ); + } + else + { + outputData->SetString( "command", "" ); + } + return true; + } + + + return BaseClass::RequestInfo(outputData); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Button::CanBeDefaultButton(void) +{ + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get control settings for editing +//----------------------------------------------------------------------------- +void Button::GetSettings( KeyValues *outResourceData ) +{ + BaseClass::GetSettings(outResourceData); + + if (_actionMessage) + { + outResourceData->SetString("command", _actionMessage->GetString("command", "")); + } + outResourceData->SetInt("default", _buttonFlags.IsFlagSet( DEFAULT_BUTTON ) ); + if ( m_bSelectionStateSaved ) + { + outResourceData->SetInt( "selected", IsSelected() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings(inResourceData); + + const char *cmd = inResourceData->GetString("command", ""); + if (*cmd) + { + // add in the command + SetCommand(cmd); + } + + // set default button state + int defaultButton = inResourceData->GetInt("default"); + if (defaultButton && CanBeDefaultButton()) + { + SetAsDefaultButton(true); + } + + // saved selection state + int iSelected = inResourceData->GetInt( "selected", -1 ); + if ( iSelected != -1 ) + { + SetSelected( iSelected != 0 ); + m_bSelectionStateSaved = true; + } + + m_bStaySelectedOnClick = inResourceData->GetBool( "stayselectedonclick", false ); + m_bStayArmedOnClick = inResourceData->GetBool( "stay_armed_on_click", false ); + + const char *sound = inResourceData->GetString("sound_armed", ""); + if (*sound) + { + SetArmedSound(sound); + } + sound = inResourceData->GetString("sound_depressed", ""); + if (*sound) + { + SetDepressedSound(sound); + } + sound = inResourceData->GetString("sound_released", ""); + if (*sound) + { + SetReleasedSound(sound); + } + + _activationType = (ActivationType_t)inResourceData->GetInt( "button_activation_type", ACTIVATE_ONRELEASED ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Describes editing details +//----------------------------------------------------------------------------- +const char *Button::GetDescription( void ) +{ + static char buf[1024]; + Q_snprintf(buf, sizeof(buf), "%s, string command, int default", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnSetState(int state) +{ + SetSelected((bool)state); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnCursorEntered() +{ + if (IsEnabled() && !IsSelected() ) + { + SetArmed( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnCursorExited() +{ + if ( !_buttonFlags.IsFlagSet( BUTTON_KEY_DOWN ) && !IsSelected() ) + { + SetArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnMousePressed(MouseCode code) +{ + if (!IsEnabled()) + return; + + if (!IsMouseClickEnabled(code)) + return; + + if (_activationType == ACTIVATE_ONPRESSED) + { + if ( IsKeyBoardInputEnabled() ) + { + RequestFocus(); + } + DoClick(); + return; + } + + // play activation sound + if (m_sDepressedSoundName != UTL_INVAL_SYMBOL) + { + surface()->PlaySound(g_ButtonSoundNames.String(m_sDepressedSoundName)); + } + + if (IsUseCaptureMouseEnabled() && _activationType == ACTIVATE_ONPRESSEDANDRELEASED) + { + { + if ( IsKeyBoardInputEnabled() ) + { + RequestFocus(); + } + SetSelected(true); + Repaint(); + } + + // lock mouse input to going to this button + input()->SetMouseCapture(GetVPanel()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnMouseDoublePressed(MouseCode code) +{ + OnMousePressed(code); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnMouseReleased(MouseCode code) +{ + // ensure mouse capture gets released + if (IsUseCaptureMouseEnabled()) + { + input()->SetMouseCapture(NULL); + } + + if (_activationType == ACTIVATE_ONPRESSED) + return; + + if (!IsMouseClickEnabled(code)) + return; + + if (!IsSelected() && _activationType == ACTIVATE_ONPRESSEDANDRELEASED) + return; + + // it has to be both enabled and (mouse over the button or using a key) to fire + if ( IsEnabled() && ( GetVPanel() == input()->GetMouseOver() || _buttonFlags.IsFlagSet( BUTTON_KEY_DOWN ) ) ) + { + DoClick(); + } + else if ( !m_bStaySelectedOnClick ) + { + SetSelected(false); + } + + // make sure the button gets unselected + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnKeyCodePressed(KeyCode code) +{ + KeyCode localCode = GetBaseButtonCode( code ); + + if( ( localCode == KEY_XBUTTON_A || localCode == STEAMCONTROLLER_A ) && IsEnabled() ) + { + SetArmed( true ); + _buttonFlags.SetFlag( BUTTON_KEY_DOWN ); + if( _activationType != ACTIVATE_ONRELEASED ) + { + DoClick(); + } + } + else if (code == KEY_SPACE || code == KEY_ENTER) + { + SetArmed(true); + _buttonFlags.SetFlag( BUTTON_KEY_DOWN ); + OnMousePressed(MOUSE_LEFT); + if (IsUseCaptureMouseEnabled()) // undo the mouse capture since its a fake mouse click! + { + input()->SetMouseCapture(NULL); + } + } + else + { + _buttonFlags.ClearFlag( BUTTON_KEY_DOWN ); + BaseClass::OnKeyCodePressed( code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Button::OnKeyCodeReleased( KeyCode keycode ) +{ + vgui::KeyCode code = GetBaseButtonCode( keycode ); + + if ( _buttonFlags.IsFlagSet( BUTTON_KEY_DOWN ) && ( code == KEY_XBUTTON_A || code == KEY_XBUTTON_START || code == STEAMCONTROLLER_A ) ) + { + SetArmed( true ); + if( _activationType != ACTIVATE_ONPRESSED ) + { + DoClick(); + } + } + else if (_buttonFlags.IsFlagSet( BUTTON_KEY_DOWN ) && (code == KEY_SPACE || code == KEY_ENTER)) + { + SetArmed(true); + OnMouseReleased(MOUSE_LEFT); + } + else + { + BaseClass::OnKeyCodeReleased( keycode ); + } + _buttonFlags.ClearFlag( BUTTON_KEY_DOWN ); + + if ( !( code == KEY_XSTICK1_UP || code == KEY_XSTICK1_DOWN || code == KEY_XSTICK1_LEFT || code == KEY_XSTICK1_RIGHT || + code == KEY_XSTICK2_UP || code == KEY_XSTICK2_DOWN || code == KEY_XSTICK2_LEFT || code == KEY_XSTICK2_RIGHT || + code == KEY_XBUTTON_UP || code == KEY_XBUTTON_DOWN || code == KEY_XBUTTON_LEFT || code == KEY_XBUTTON_RIGHT || + keycode == KEY_UP|| keycode == KEY_DOWN || keycode == KEY_LEFT || keycode == KEY_RIGHT ) ) + { + SetArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override this to draw different focus border +//----------------------------------------------------------------------------- +void Button::DrawFocusBorder(int tx0, int ty0, int tx1, int ty1) +{ + surface()->DrawSetColor(_keyboardFocusColor); + DrawDashedLine(tx0, ty0, tx1, ty0+1, 1, 1); // top + DrawDashedLine(tx0, ty0, tx0+1, ty1, 1, 1); // left + DrawDashedLine(tx0, ty1-1, tx1, ty1, 1, 1); // bottom + DrawDashedLine(tx1-1, ty0, tx1, ty1, 1, 1); // right +} + +//----------------------------------------------------------------------------- +// Purpose: Size the object to its button and text. - only works from in ApplySchemeSettings or PerformLayout() +//----------------------------------------------------------------------------- +void Button::SizeToContents() +{ + int wide, tall; + GetContentSize(wide, tall); + SetSize(wide + Label::Content, tall + Label::Content); +} diff --git a/vgui2/vgui_controls/CheckButton.cpp b/vgui2/vgui_controls/CheckButton.cpp new file mode 100644 index 0000000..72fbca4 --- /dev/null +++ b/vgui2/vgui_controls/CheckButton.cpp @@ -0,0 +1,216 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdarg.h> +#include <stdio.h> + +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <KeyValues.h> + +#include <vgui_controls/Image.h> +#include <vgui_controls/CheckButton.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +void CheckImage::Paint() +{ + DrawSetTextFont(GetFont()); + + // draw background + if (_CheckButton->IsEnabled() && _CheckButton->IsCheckButtonCheckable() ) + { + DrawSetTextColor(_bgColor); + } + else + { + DrawSetTextColor(_CheckButton->GetDisabledBgColor()); + } + DrawPrintChar(0, 1, 'g'); + + // draw border box + DrawSetTextColor(_borderColor1); + DrawPrintChar(0, 1, 'e'); + DrawSetTextColor(_borderColor2); + DrawPrintChar(0, 1, 'f'); + + // draw selected check + if (_CheckButton->IsSelected()) + { + if ( !_CheckButton->IsEnabled() ) + { + DrawSetTextColor( _CheckButton->GetDisabledFgColor() ); + } + else + { + DrawSetTextColor(_checkColor); + } + + DrawPrintChar(0, 2, 'b'); + } +} + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CheckButton, CheckButton ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CheckButton::CheckButton(Panel *parent, const char *panelName, const char *text) : ToggleButton(parent, panelName, text) +{ + SetContentAlignment(a_west); + m_bCheckButtonCheckable = true; + m_bUseSmallCheckImage = false; + + // create the image + _checkBoxImage = new CheckImage(this); + + SetTextImageIndex(1); + SetImageAtIndex(0, _checkBoxImage, CHECK_INSET); + + _selectedFgColor = Color( 196, 181, 80, 255 ); + _disabledFgColor = Color(130, 130, 130, 255); + _disabledBgColor = Color(62, 70, 55, 255); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CheckButton::~CheckButton() +{ + delete _checkBoxImage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckButton::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + m_bUseSmallCheckImage = inResourceData->GetBool( "smallcheckimage", false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckButton::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetDefaultColor( GetSchemeColor("CheckButton.TextColor", pScheme), GetBgColor() ); + _checkBoxImage->_bgColor = GetSchemeColor("CheckButton.BgColor", Color(62, 70, 55, 255), pScheme); + _checkBoxImage->_borderColor1 = GetSchemeColor("CheckButton.Border1", Color(20, 20, 20, 255), pScheme); + _checkBoxImage->_borderColor2 = GetSchemeColor("CheckButton.Border2", Color(90, 90, 90, 255), pScheme); + _checkBoxImage->_checkColor = GetSchemeColor("CheckButton.Check", Color(20, 20, 20, 255), pScheme); + _selectedFgColor = GetSchemeColor("CheckButton.SelectedTextColor", GetSchemeColor("ControlText", pScheme), pScheme); + _disabledFgColor = GetSchemeColor("CheckButton.DisabledFgColor", Color(130, 130, 130, 255), pScheme); + _disabledBgColor = GetSchemeColor("CheckButton.DisabledBgColor", Color(62, 70, 55, 255), pScheme); + + Color bgArmedColor = GetSchemeColor( "CheckButton.ArmedBgColor", Color(62, 70, 55, 255), pScheme); + SetArmedColor( GetFgColor(), bgArmedColor ); + + Color bgDepressedColor = GetSchemeColor( "CheckButton.DepressedBgColor", Color(62, 70, 55, 255), pScheme); + SetDepressedColor( GetFgColor(), bgDepressedColor ); + + _highlightFgColor = GetSchemeColor( "CheckButton.HighlightFgColor", Color(62, 70, 55, 255), pScheme); + + SetContentAlignment(Label::a_west); + + _checkBoxImage->SetFont( pScheme->GetFont( m_bUseSmallCheckImage ? "MarlettSmall" : "Marlett", IsProportional()) ); + _checkBoxImage->ResizeImageToContent(); + SetImageAtIndex(0, _checkBoxImage, CHECK_INSET); + + // don't draw a background + SetPaintBackgroundEnabled(false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +IBorder *CheckButton::GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Check the button +//----------------------------------------------------------------------------- +void CheckButton::SetSelected(bool state ) +{ + if (m_bCheckButtonCheckable) + { + // send a message saying we've been checked + KeyValues *msg = new KeyValues("CheckButtonChecked", "state", (int)state); + PostActionSignal(msg); + + BaseClass::SetSelected(state); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets whether or not the state of the check can be changed +//----------------------------------------------------------------------------- +void CheckButton::SetCheckButtonCheckable(bool state) +{ + m_bCheckButtonCheckable = state; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a different foreground text color if we are selected +//----------------------------------------------------------------------------- +#ifdef _X360 +Color CheckButton::GetButtonFgColor() +{ + if (HasFocus()) + { + return _selectedFgColor; + } + + return BaseClass::GetButtonFgColor(); +} +#else +Color CheckButton::GetButtonFgColor() +{ + if ( IsArmed() ) + { + return _highlightFgColor; + } + + if (IsSelected()) + { + return _selectedFgColor; + } + + return BaseClass::GetButtonFgColor(); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckButton::OnCheckButtonChecked(Panel *panel) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckButton::SetHighlightColor(Color fgColor) +{ + if ( _highlightFgColor != fgColor ) + { + _highlightFgColor = fgColor; + + InvalidateLayout(false); + } +} + diff --git a/vgui2/vgui_controls/CheckButtonList.cpp b/vgui2/vgui_controls/CheckButtonList.cpp new file mode 100644 index 0000000..f2fff2f --- /dev/null +++ b/vgui2/vgui_controls/CheckButtonList.cpp @@ -0,0 +1,212 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <vgui_controls/CheckButtonList.h> +#include <vgui_controls/CheckButton.h> +#include <vgui_controls/ScrollBar.h> +#include <KeyValues.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CheckButtonList::CheckButtonList(Panel *parent, const char *name) : BaseClass(parent, name) +{ + m_pScrollBar = new ScrollBar(this, NULL, true); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CheckButtonList::~CheckButtonList() +{ + RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: adds a check button to the list +//----------------------------------------------------------------------------- +int CheckButtonList::AddItem(const char *itemText, bool startsSelected, KeyValues *userData) +{ + CheckItem_t newItem; + newItem.checkButton = new vgui::CheckButton(this, NULL, itemText); + newItem.checkButton->SetSilentMode( true ); + newItem.checkButton->SetSelected(startsSelected); + newItem.checkButton->SetSilentMode( false ); + newItem.checkButton->AddActionSignalTarget(this); + newItem.userData = userData; + InvalidateLayout(); + return m_CheckItems.AddToTail(newItem); +} + +//----------------------------------------------------------------------------- +// Purpose: clears the list +//----------------------------------------------------------------------------- +void CheckButtonList::RemoveAll() +{ + for (int i = 0; i < m_CheckItems.Count(); i++) + { + m_CheckItems[i].checkButton->MarkForDeletion(); + if (m_CheckItems[i].userData) + { + m_CheckItems[i].userData->deleteThis(); + } + } + + m_CheckItems.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the number of items in list that are checked +//----------------------------------------------------------------------------- +int CheckButtonList::GetCheckedItemCount() +{ + int count = 0; + for (int i = 0; i < m_CheckItems.Count(); i++) + { + if (m_CheckItems[i].checkButton->IsSelected()) + { + count++; + } + } + + return count; +} + +//----------------------------------------------------------------------------- +// Purpose: lays out buttons +//----------------------------------------------------------------------------- +void CheckButtonList::PerformLayout() +{ + BaseClass::PerformLayout(); + + // get sizes + int x = 4, y = 4, wide = GetWide() - ((x * 2) + m_pScrollBar->GetWide()), tall = 22; + + // set scrollbar + int totalHeight = y + (m_CheckItems.Count() * tall); + if (totalHeight > GetTall()) + { + m_pScrollBar->SetRange(0, totalHeight + 1); + m_pScrollBar->SetRangeWindow(GetTall()); + m_pScrollBar->SetVisible(true); + m_pScrollBar->SetBounds(GetWide() - 21, 0, 19, GetTall() - 2); + SetPaintBorderEnabled(true); + y -= m_pScrollBar->GetValue(); + } + else + { + m_pScrollBar->SetVisible(false); + SetPaintBorderEnabled(false); + } + + // position the items + for (int i = 0; i < m_CheckItems.Count(); i++) + { + CheckButton *btn = m_CheckItems[i].checkButton; + btn->SetBounds(x, y, wide, tall); + y += tall; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the border on the window +//----------------------------------------------------------------------------- +void CheckButtonList::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); +} + +//----------------------------------------------------------------------------- +// Purpose: iteration +//----------------------------------------------------------------------------- +bool CheckButtonList::IsItemIDValid(int itemID) +{ + return m_CheckItems.IsValidIndex(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: iteration +//----------------------------------------------------------------------------- +int CheckButtonList::GetHighestItemID() +{ + return m_CheckItems.Count() - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: iteration +//----------------------------------------------------------------------------- +KeyValues *CheckButtonList::GetItemData(int itemID) +{ + return m_CheckItems[itemID].userData; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int CheckButtonList::GetItemCount() +{ + return m_CheckItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +bool CheckButtonList::IsItemChecked(int itemID) +{ + return m_CheckItems[itemID].checkButton->IsSelected(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the state of the check button +//----------------------------------------------------------------------------- +void CheckButtonList::SetItemCheckable(int itemID, bool state) +{ + m_CheckItems[itemID].checkButton->SetCheckButtonCheckable(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Forwards up check button selected message +//----------------------------------------------------------------------------- +void CheckButtonList::OnCheckButtonChecked( KeyValues *pParams ) +{ + vgui::Panel *pPanel = (vgui::Panel *)pParams->GetPtr( "panel" ); + int c = m_CheckItems.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( pPanel == m_CheckItems[i].checkButton ) + { + KeyValues *kv = new KeyValues( "CheckButtonChecked", "itemid", i ); + kv->SetInt( "state", pParams->GetInt( "state" ) ); + PostActionSignal( kv ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: updates from scrollbar movement +//----------------------------------------------------------------------------- +void CheckButtonList::OnScrollBarSliderMoved() +{ + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Mouse wheeled +//----------------------------------------------------------------------------- +void CheckButtonList::OnMouseWheeled(int delta) +{ + int val = m_pScrollBar->GetValue(); + val -= (delta * 15); + m_pScrollBar->SetValue(val); +} diff --git a/vgui2/vgui_controls/CircularProgressBar.cpp b/vgui2/vgui_controls/CircularProgressBar.cpp new file mode 100644 index 0000000..3036459 --- /dev/null +++ b/vgui2/vgui_controls/CircularProgressBar.cpp @@ -0,0 +1,353 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> +#include <math.h> +#include <stdio.h> + +#include <vgui_controls/CircularProgressBar.h> +#include <vgui_controls/Controls.h> + +#include <vgui/ILocalize.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> + +#include "mathlib/mathlib.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( CircularProgressBar ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CircularProgressBar::CircularProgressBar(Panel *parent, const char *panelName) + : ProgressBar(parent, panelName) + , m_bReverseProgress( false ) +{ + m_iProgressDirection = CircularProgressBar::PROGRESS_CW; + + for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ ) + { + m_nTextureId[i] = -1; + m_pszImageName[i] = NULL; + m_lenImageName[i] = 0; + } + + m_iStartSegment = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CircularProgressBar::~CircularProgressBar() +{ + for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ ) + { + if ( vgui::surface() && m_nTextureId[i] ) + { + vgui::surface()->DestroyTextureID( m_nTextureId[i] ); + m_nTextureId[i] = -1; + } + + delete [] m_pszImageName[i]; + m_lenImageName[i] = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CircularProgressBar::ApplySettings(KeyValues *inResourceData) +{ + for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ ) + { + delete [] m_pszImageName[i]; + m_pszImageName[i] = NULL; + m_lenImageName[i] = 0; + } + + const char *imageName = inResourceData->GetString("fg_image", ""); + if (*imageName) + { + SetFgImage( imageName ); + } + imageName = inResourceData->GetString("bg_image", ""); + if (*imageName) + { + SetBgImage( imageName ); + } + + BaseClass::ApplySettings( inResourceData ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CircularProgressBar::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("CircularProgressBar.FgColor", pScheme)); + SetBgColor(GetSchemeColor("CircularProgressBar.BgColor", pScheme)); + SetBorder(NULL); + + for ( int i = 0; i < NUM_PROGRESS_TEXTURES; i++ ) + { + if ( m_pszImageName[i] && strlen( m_pszImageName[i] ) > 0 ) + { + if ( m_nTextureId[i] == -1 ) + { + m_nTextureId[i] = surface()->CreateNewTextureID(); + } + + surface()->DrawSetTextureFile( m_nTextureId[i], m_pszImageName[i], true, false); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets an image by file name +//----------------------------------------------------------------------------- +void CircularProgressBar::SetImage(const char *imageName, progress_textures_t iPos) +{ + const char *pszDir = "vgui/"; + int len = Q_strlen(imageName) + 1; + len += strlen(pszDir); + + if ( m_pszImageName[iPos] && ( m_lenImageName[iPos] < len ) ) + { + // If we already have a buffer, but it is too short, then free the buffer + delete [] m_pszImageName[iPos]; + m_pszImageName[iPos] = NULL; + m_lenImageName[iPos] = 0; + } + + if ( !m_pszImageName[iPos] ) + { + m_pszImageName[iPos] = new char[ len ]; + m_lenImageName[iPos] = len; + } + + Q_snprintf( m_pszImageName[iPos], len, "%s%s", pszDir, imageName ); + InvalidateLayout(false, true); // force applyschemesettings to run +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CircularProgressBar::PaintBackground() +{ + // If we don't have a Bg image, use the foreground + int iTextureID = m_nTextureId[PROGRESS_TEXTURE_BG] != -1 ? m_nTextureId[PROGRESS_TEXTURE_BG] : m_nTextureId[PROGRESS_TEXTURE_FG]; + vgui::surface()->DrawSetTexture( iTextureID ); + vgui::surface()->DrawSetColor( GetBgColor() ); + + int wide, tall; + GetSize(wide, tall); + + vgui::surface()->DrawTexturedRect( 0, 0, wide, tall ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CircularProgressBar::Paint() +{ + float flProgress = GetProgress(); + float flEndAngle; + + flEndAngle = m_bReverseProgress ? ( 1.0 - flProgress ) : flProgress; + flEndAngle = m_iProgressDirection == PROGRESS_CCW ? ( 1.0 - flEndAngle ) : flEndAngle; + + DrawCircleSegment( GetFgColor(), flEndAngle, ( m_iProgressDirection == PROGRESS_CW ) ); +} + +typedef struct +{ + float minProgressRadians; + + float vert1x; + float vert1y; + float vert2x; + float vert2y; + + int swipe_dir_x; + int swipe_dir_y; +} circular_progress_segment_t; + +namespace vgui +{ +// This defines the properties of the 8 circle segments +// in the circular progress bar. +circular_progress_segment_t Segments[8] = +{ + { 0.0, 0.5, 0.0, 1.0, 0.0, 1, 0 }, + { M_PI * 0.25, 1.0, 0.0, 1.0, 0.5, 0, 1 }, + { M_PI * 0.5, 1.0, 0.5, 1.0, 1.0, 0, 1 }, + { M_PI * 0.75, 1.0, 1.0, 0.5, 1.0, -1, 0 }, + { M_PI, 0.5, 1.0, 0.0, 1.0, -1, 0 }, + { M_PI * 1.25, 0.0, 1.0, 0.0, 0.5, 0, -1 }, + { M_PI * 1.5, 0.0, 0.5, 0.0, 0.0, 0, -1 }, + { M_PI * 1.75, 0.0, 0.0, 0.5, 0.0, 1, 0 }, +}; + +}; + +#define SEGMENT_ANGLE ( M_PI / 4 ) + +// function to draw from A to B degrees, with a direction +// we draw starting from the top ( 0 progress ) +void CircularProgressBar::DrawCircleSegment( Color c, float flEndProgress, bool bClockwise ) +{ + if ( m_nTextureId[PROGRESS_TEXTURE_FG] == -1 ) + return; + + int wide, tall; + GetSize(wide, tall); + + float flWide = (float)wide; + float flTall = (float)tall; + + float flHalfWide = (float)wide / 2; + float flHalfTall = (float)tall / 2; + + vgui::surface()->DrawSetTexture( m_nTextureId[PROGRESS_TEXTURE_FG] ); + vgui::surface()->DrawSetColor( c ); + + // if we want to progress CCW, reverse a few things + if ( !bClockwise ) + { + float flEndProgressRadians = flEndProgress * M_PI * 2; + + int i; + for ( i=0;i<8;i++ ) + { + float segmentRadiansMin = Segments[i].minProgressRadians; + float segmentRadiansMax = segmentRadiansMin + SEGMENT_ANGLE; + + if ( flEndProgressRadians < segmentRadiansMax ) + { + vgui::Vertex_t v[3]; + + // vert 0 is ( 0.5, 0.5 ) + v[0].m_Position.Init( flHalfWide, flHalfTall ); + v[0].m_TexCoord.Init( 0.5f, 0.5f ); + + float flInternalProgress = segmentRadiansMax - flEndProgressRadians; + + if ( flInternalProgress < SEGMENT_ANGLE ) + { + // Calc how much of this slice we should be drawing + flInternalProgress = SEGMENT_ANGLE - flInternalProgress; + + if ( i % 2 == 1 ) + { + flInternalProgress = SEGMENT_ANGLE - flInternalProgress; + } + + float flTan = tan(flInternalProgress); + + float flDeltaX, flDeltaY; + + if ( i % 2 == 1 ) + { + flDeltaX = ( flHalfWide - flHalfTall * flTan ) * Segments[i].swipe_dir_x; + flDeltaY = ( flHalfTall - flHalfWide * flTan ) * Segments[i].swipe_dir_y; + } + else + { + flDeltaX = flHalfTall * flTan * Segments[i].swipe_dir_x; + flDeltaY = flHalfWide * flTan * Segments[i].swipe_dir_y; + } + + v[1].m_Position.Init( Segments[i].vert1x * flWide + flDeltaX, Segments[i].vert1y * flTall + flDeltaY ); + v[1].m_TexCoord.Init( Segments[i].vert1x + ( flDeltaX / flHalfWide ) * 0.5, Segments[i].vert1y + ( flDeltaY / flHalfTall ) * 0.5 ); + } + else + { + // full segment, easy calculation + v[1].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert1x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert1y - 0.5 ) ); + v[1].m_TexCoord.Init( Segments[i].vert1x, Segments[i].vert1y ); + } + + // vert 2 is ( Segments[i].vert1x, Segments[i].vert1y ) + v[2].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert2x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert2y - 0.5 ) ); + v[2].m_TexCoord.Init( Segments[i].vert2x, Segments[i].vert2y ); + + vgui::surface()->DrawTexturedPolygon( 3, v ); + } + } + return; + } + + + float flEndProgressRadians = flEndProgress * M_PI * 2; + + int cur_wedge = m_iStartSegment; + for ( int i=0;i<8;i++ ) + { + if ( flEndProgressRadians > Segments[cur_wedge].minProgressRadians) + { + vgui::Vertex_t v[3]; + + // vert 0 is ( 0.5, 0.5 ) + v[0].m_Position.Init( flHalfWide, flHalfTall ); + v[0].m_TexCoord.Init( 0.5f, 0.5f ); + + float flInternalProgress = flEndProgressRadians - Segments[cur_wedge].minProgressRadians; + + if ( flInternalProgress < SEGMENT_ANGLE ) + { + // Calc how much of this slice we should be drawing + + if ( i % 2 == 1 ) + { + flInternalProgress = SEGMENT_ANGLE - flInternalProgress; + } + + float flTan = tan(flInternalProgress); + + float flDeltaX, flDeltaY; + + if ( i % 2 == 1 ) + { + flDeltaX = ( flHalfWide - flHalfTall * flTan ) * Segments[i].swipe_dir_x; + flDeltaY = ( flHalfTall - flHalfWide * flTan ) * Segments[i].swipe_dir_y; + } + else + { + flDeltaX = flHalfTall * flTan * Segments[i].swipe_dir_x; + flDeltaY = flHalfWide * flTan * Segments[i].swipe_dir_y; + } + + v[2].m_Position.Init( Segments[i].vert1x * flWide + flDeltaX, Segments[i].vert1y * flTall + flDeltaY ); + v[2].m_TexCoord.Init( Segments[i].vert1x + ( flDeltaX / flHalfWide ) * 0.5, Segments[i].vert1y + ( flDeltaY / flHalfTall ) * 0.5 ); + } + else + { + // full segment, easy calculation + v[2].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert2x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert2y - 0.5 ) ); + v[2].m_TexCoord.Init( Segments[i].vert2x, Segments[i].vert2y ); + } + + // vert 2 is ( Segments[i].vert1x, Segments[i].vert1y ) + v[1].m_Position.Init( flHalfWide + flWide * ( Segments[i].vert1x - 0.5 ), flHalfTall + flTall * ( Segments[i].vert1y - 0.5 ) ); + v[1].m_TexCoord.Init( Segments[i].vert1x, Segments[i].vert1y ); + + vgui::surface()->DrawTexturedPolygon( 3, v ); + } + + cur_wedge++; + if ( cur_wedge >= 8) + cur_wedge = 0; + } +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/ComboBox.cpp b/vgui2/vgui_controls/ComboBox.cpp new file mode 100644 index 0000000..e7d0f86 --- /dev/null +++ b/vgui2/vgui_controls/ComboBox.cpp @@ -0,0 +1,1041 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#define PROTECTED_THINGS_DISABLE + +#include "vgui/Cursor.h" +#include "vgui/IInput.h" +#include "vgui/ILocalize.h" +#include "vgui/IScheme.h" +#include "vgui/ISurface.h" +#include "vgui/IPanel.h" +#include "KeyValues.h" + +#include "vgui_controls/ComboBox.h" +#include "vgui_controls/Menu.h" +#include "vgui_controls/MenuItem.h" +#include "vgui_controls/TextImage.h" + +#include <ctype.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +namespace vgui +{ +ComboBoxButton::ComboBoxButton(ComboBox *parent, const char *panelName, const char *text) : Button(parent, panelName, text) +{ + SetButtonActivationType(ACTIVATE_ONPRESSED); +} + +void ComboBoxButton::ApplySchemeSettings(IScheme *pScheme) +{ + Button::ApplySchemeSettings(pScheme); + + SetFont(pScheme->GetFont("Marlett", IsProportional())); + SetContentAlignment(Label::a_west); +#ifdef OSX + SetTextInset(-3, 0); +#else + SetTextInset(3, 0); +#endif + SetDefaultBorder(pScheme->GetBorder("ScrollBarButtonBorder")); + + // arrow changes color but the background doesnt. + SetDefaultColor(GetSchemeColor("ComboBoxButton.ArrowColor", pScheme), GetSchemeColor("ComboBoxButton.BgColor", pScheme)); + SetArmedColor(GetSchemeColor("ComboBoxButton.ArmedArrowColor", pScheme), GetSchemeColor("ComboBoxButton.BgColor", pScheme)); + SetDepressedColor(GetSchemeColor("ComboBoxButton.ArmedArrowColor", pScheme), GetSchemeColor("ComboBoxButton.BgColor", pScheme)); + m_DisabledBgColor = GetSchemeColor("ComboBoxButton.DisabledBgColor", pScheme); +} + +IBorder * ComboBoxButton::GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) +{ + return NULL; + // return Button::GetBorder(depressed, armed, selected, keyfocus); +} + +//----------------------------------------------------------------------------- +// Purpose: Dim the arrow on the button when exiting the box +// only if the menu is closed, so let the parent handle this. +//----------------------------------------------------------------------------- +void ComboBoxButton::OnCursorExited() +{ + // want the arrow to go grey when we exit the box if the menu is not open + CallParentFunction(new KeyValues("CursorExited")); +} + +} // namespace vgui + +vgui::Panel *ComboBox_Factory() +{ + return new ComboBox( NULL, NULL, 5, true ); +} +DECLARE_BUILD_FACTORY_CUSTOM( ComboBox, ComboBox_Factory ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : parent - parent class +// panelName +// numLines - number of lines in dropdown menu +// allowEdit - whether combobox is editable or not +//----------------------------------------------------------------------------- +ComboBox::ComboBox(Panel *parent, const char *panelName, int numLines, bool allowEdit ) : TextEntry(parent, panelName) +{ + SetEditable(allowEdit); + SetHorizontalScrolling(false); // do not scroll, always Start at the beginning of the text. + + // create the drop-down menu + m_pDropDown = new Menu(this, NULL); + m_pDropDown->AddActionSignalTarget(this); + m_pDropDown->SetTypeAheadMode( Menu::TYPE_AHEAD_MODE ); + + // button to Activate menu + m_pButton = new ComboBoxButton(this, "Button", "u"); + m_pButton->SetCommand("ButtonClicked"); + m_pButton->AddActionSignalTarget(this); + + SetNumberOfEditLines(numLines); + + m_bHighlight = false; + m_iDirection = Menu::DOWN; + m_iOpenOffsetY = 0; + m_bPreventTextChangeMessage = false; + m_szBorderOverride[0] = '\0'; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +ComboBox::~ComboBox() +{ + m_pDropDown->DeletePanel(); + m_pButton->DeletePanel(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the number of items in the dropdown menu. +// Input : numLines - number of items in dropdown menu +//----------------------------------------------------------------------------- +void ComboBox::SetNumberOfEditLines( int numLines ) +{ + m_pDropDown->SetNumberOfVisibleItems( numLines ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add an item to the drop down +// Input : char *itemText - name of dropdown menu item +//----------------------------------------------------------------------------- +int ComboBox::AddItem(const char *itemText, const KeyValues *userData) +{ + // when the menu item is selected it will send the custom message "SetText" + return m_pDropDown->AddMenuItem( itemText, new KeyValues("SetText", "text", itemText), this, userData ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add an item to the drop down +// Input : char *itemText - name of dropdown menu item +//----------------------------------------------------------------------------- +int ComboBox::AddItem(const wchar_t *itemText, const KeyValues *userData) +{ + // add the element to the menu + // when the menu item is selected it will send the custom message "SetText" + KeyValues *kv = new KeyValues("SetText"); + kv->SetWString("text", itemText); + // get an ansi version for the menuitem name + char ansi[128]; + g_pVGuiLocalize->ConvertUnicodeToANSI(itemText, ansi, sizeof(ansi)); + return m_pDropDown->AddMenuItem(ansi, kv, this, userData); +} + + +//----------------------------------------------------------------------------- +// Removes a single item +//----------------------------------------------------------------------------- +void ComboBox::DeleteItem( int itemID ) +{ + if ( !m_pDropDown->IsValidMenuID(itemID)) + return; + + m_pDropDown->DeleteItem( itemID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates a current item to the drop down +// Input : char *itemText - name of dropdown menu item +//----------------------------------------------------------------------------- +bool ComboBox::UpdateItem(int itemID, const char *itemText, const KeyValues *userData) +{ + if ( !m_pDropDown->IsValidMenuID(itemID)) + return false; + + // when the menu item is selected it will send the custom message "SetText" + m_pDropDown->UpdateMenuItem(itemID, itemText, new KeyValues("SetText", "text", itemText), userData); + InvalidateLayout(); + return true; +} +//----------------------------------------------------------------------------- +// Purpose: Updates a current item to the drop down +// Input : wchar_t *itemText - name of dropdown menu item +//----------------------------------------------------------------------------- +bool ComboBox::UpdateItem(int itemID, const wchar_t *itemText, const KeyValues *userData) +{ + if ( !m_pDropDown->IsValidMenuID(itemID)) + return false; + + // when the menu item is selected it will send the custom message "SetText" + KeyValues *kv = new KeyValues("SetText"); + kv->SetWString("text", itemText); + m_pDropDown->UpdateMenuItem(itemID, itemText, kv, userData); + InvalidateLayout(); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates a current item to the drop down +// Input : wchar_t *itemText - name of dropdown menu item +//----------------------------------------------------------------------------- +bool ComboBox::IsItemIDValid( int itemID ) +{ + return m_pDropDown->IsValidMenuID(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ComboBox::SetItemEnabled(const char *itemText, bool state) +{ + m_pDropDown->SetItemEnabled(itemText, state); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ComboBox::SetItemEnabled(int itemID, bool state) +{ + m_pDropDown->SetItemEnabled(itemID, state); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all items from the drop down menu +//----------------------------------------------------------------------------- +void ComboBox::RemoveAll() +{ + m_pDropDown->DeleteAllItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ComboBox::GetItemCount() const +{ + return m_pDropDown->GetItemCount(); +} + +int ComboBox::GetItemIDFromRow( int row ) +{ + // valid from [0, GetItemCount) + return m_pDropDown->GetMenuID( row ); +} + +//----------------------------------------------------------------------------- +// Purpose: Activate the item in the menu list, as if that menu item had been selected by the user +// Input : itemID - itemID from AddItem in list of dropdown items +//----------------------------------------------------------------------------- +void ComboBox::ActivateItem(int itemID) +{ + m_pDropDown->ActivateItem(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: Activate the item in the menu list, as if that menu item had been selected by the user +// Input : itemID - itemID from AddItem in list of dropdown items +//----------------------------------------------------------------------------- +void ComboBox::ActivateItemByRow(int row) +{ + m_pDropDown->ActivateItemByRow(row); +} + +//----------------------------------------------------------------------------- +// Purpose: Activate the item in the menu list, without sending a TextChanged message +// Input : row - row to activate +//----------------------------------------------------------------------------- +void ComboBox::SilentActivateItemByRow(int row) +{ + int itemID = GetItemIDFromRow( row ); + if ( itemID >= 0 ) + { + SilentActivateItem( itemID ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate the item in the menu list, without sending a TextChanged message +// Input : itemID - itemID from AddItem in list of dropdown items +//----------------------------------------------------------------------------- +void ComboBox::SilentActivateItem(int itemID) +{ + m_pDropDown->SilentActivateItem(itemID); + + // Now manually call our set text, with a wrapper to ensure we don't send the Text Changed message + wchar_t name[ 256 ]; + GetItemText( itemID, name, sizeof( name ) ); + + m_bPreventTextChangeMessage = true; + OnSetText( name ); + m_bPreventTextChangeMessage = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Allows a custom menu to be used with the combo box +//----------------------------------------------------------------------------- +void ComboBox::SetMenu( Menu *menu ) +{ + if ( m_pDropDown ) + { + m_pDropDown->MarkForDeletion(); + } + + m_pDropDown = menu; + if ( m_pDropDown ) + { + m_pDropDown->SetParent( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Layout the format of the combo box for drawing on screen +//----------------------------------------------------------------------------- +void ComboBox::PerformLayout() +{ + int wide, tall; + GetPaintSize(wide, tall); + + BaseClass::PerformLayout(); + + HFont buttonFont = m_pButton->GetFont(); + int fontTall = surface()->GetFontTall( buttonFont ); + + int buttonSize = min( tall, fontTall ); + + int buttonY = ( ( tall - 1 ) - buttonSize ) / 2; + + // Some dropdown button icons in our games are wider than they are taller. We need to factor that in. + int button_wide, button_tall; + m_pButton->GetContentSize(button_wide, button_tall); + button_wide = max( buttonSize, button_wide ); + + m_pButton->SetBounds( wide - button_wide, buttonY, button_wide, buttonSize ); + if ( IsEditable() ) + { + SetCursor(dc_ibeam); + } + else + { + SetCursor(dc_arrow); + } + + m_pButton->SetEnabled(IsEnabled()); + + DoMenuLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ComboBox::DoMenuLayout() +{ + m_pDropDown->PositionRelativeToPanel( this, m_iDirection, m_iOpenOffsetY ); + + // reset the width of the drop down menu to be the width of the combo box + m_pDropDown->SetFixedWidth(GetWide()); + m_pDropDown->ForceCalculateWidth(); + +} + +//----------------------------------------------------------------------------- +// Purpose: Sorts the items in the list +//----------------------------------------------------------------------------- +void ComboBox::SortItems( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: return the index of the last selected item +//----------------------------------------------------------------------------- +int ComboBox::GetActiveItem() +{ + return m_pDropDown->GetActiveItem(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *ComboBox::GetActiveItemUserData() +{ + return m_pDropDown->GetItemUserData(GetActiveItem()); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *ComboBox::GetItemUserData(int itemID) +{ + return m_pDropDown->GetItemUserData(itemID); +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void ComboBox::GetItemText( int itemID, wchar_t *text, int bufLenInBytes ) +{ + m_pDropDown->GetItemText( itemID, text, bufLenInBytes ); +} + +void ComboBox::GetItemText( int itemID, char *text, int bufLenInBytes ) +{ + m_pDropDown->GetItemText( itemID, text, bufLenInBytes ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ComboBox::IsDropdownVisible() +{ + return m_pDropDown->IsVisible(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *inResourceData - +//----------------------------------------------------------------------------- +void ComboBox::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetBorder( pScheme->GetBorder( m_szBorderOverride[0] ? m_szBorderOverride : "ComboBoxBorder" ) ); +} + +void ComboBox::ApplySettings( KeyValues *pInResourceData ) +{ + BaseClass::ApplySettings( pInResourceData ); + + const char *pBorderOverride = pInResourceData->GetString( "border_override", NULL ); + if ( pBorderOverride ) + { + V_strncpy( m_szBorderOverride, pBorderOverride, sizeof( m_szBorderOverride ) ); + } + + KeyValues *pKVButton = pInResourceData->FindKey( "Button" ); + if ( pKVButton && m_pButton ) + { + m_pButton->ApplySettings( pKVButton ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the visiblity of the drop down menu button. +//----------------------------------------------------------------------------- +void ComboBox::SetDropdownButtonVisible(bool state) +{ + m_pButton->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: overloads TextEntry MousePressed +//----------------------------------------------------------------------------- +void ComboBox::OnMousePressed(MouseCode code) +{ + if ( !m_pDropDown ) + return; + + if ( !IsEnabled() ) + return; + + // make sure it's getting pressed over us (it may not be due to mouse capture) + if ( !IsCursorOver() ) + { + HideMenu(); + return; + } + + if ( IsEditable() ) + { + BaseClass::OnMousePressed(code); + HideMenu(); + } + else + { + // clicking on a non-editable text box just activates the drop down menu + RequestFocus(); + DoClick(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Double-click acts the same as a single-click +//----------------------------------------------------------------------------- +void ComboBox::OnMouseDoublePressed(MouseCode code) +{ + if (IsEditable()) + { + BaseClass::OnMouseDoublePressed(code); + } + else + { + OnMousePressed(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a command is received from the menu +// Changes the label text to be that of the command +// Input : char *command - +//----------------------------------------------------------------------------- +void ComboBox::OnCommand( const char *command ) +{ + if (!stricmp(command, "ButtonClicked")) + { + // hide / show the menu underneath + DoClick(); + } + + Panel::OnCommand(command); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ComboBox::OnSetText(const wchar_t *newtext) +{ + // see if the combobox text has changed, and if so, post a message detailing the new text + const wchar_t *text = newtext; + + // check if the new text is a localized string, if so undo it + if (*text == '#') + { + char cbuf[255]; + g_pVGuiLocalize->ConvertUnicodeToANSI(text, cbuf, 255); + + // try lookup in localization tables + StringIndex_t unlocalizedTextSymbol = g_pVGuiLocalize->FindIndex(cbuf + 1); + + if (unlocalizedTextSymbol != INVALID_LOCALIZE_STRING_INDEX) + { + // we have a new text value + text = g_pVGuiLocalize->GetValueByIndex(unlocalizedTextSymbol); + } + } + + wchar_t wbuf[255]; + GetText(wbuf, 254); + + if ( wcscmp(wbuf, text) ) + { + // text has changed + SetText(text); + + // fire off that things have changed + if ( !m_bPreventTextChangeMessage ) + { + PostActionSignal(new KeyValues("TextChanged", "text", text)); + } + Repaint(); + } + + // close the box + HideMenu(); +} + +//----------------------------------------------------------------------------- +// Purpose: hides the menu +//----------------------------------------------------------------------------- +void ComboBox::HideMenu(void) +{ + if ( !m_pDropDown ) + return; + + // hide the menu + m_pDropDown->SetVisible(false); + Repaint(); + OnHideMenu(m_pDropDown); +} + +//----------------------------------------------------------------------------- +// Purpose: shows the menu +//----------------------------------------------------------------------------- +void ComboBox::ShowMenu(void) +{ + if ( !m_pDropDown ) + return; + + // hide the menu + m_pDropDown->SetVisible(false); + DoClick(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the window loses focus; hides the menu +//----------------------------------------------------------------------------- +void ComboBox::OnKillFocus() +{ + SelectNoText(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the menu is closed +//----------------------------------------------------------------------------- +void ComboBox::OnMenuClose() +{ + HideMenu(); + + if ( HasFocus() ) + { + SelectAllText(false); + } + else if ( m_bHighlight ) + { + m_bHighlight = false; + // we want the text to be highlighted when we request the focus +// SelectAllOnFirstFocus(true); + RequestFocus(); + } + // if cursor is in this box or the arrow box + else if ( IsCursorOver() )// make sure it's getting pressed over us (it may not be due to mouse capture) + { + SelectAllText(false); + OnCursorEntered(); + // Get focus so the box will unhighlight if we click somewhere else. + RequestFocus(); + } + else + { + m_pButton->SetArmed(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles hotkey accesses +// FIXME: make this open different directions as necessary see menubutton. +//----------------------------------------------------------------------------- +void ComboBox::DoClick() +{ + // menu is already visible, hide the menu + if ( m_pDropDown->IsVisible() ) + { + HideMenu(); + return; + } + + // do nothing if menu is not enabled + if ( !m_pDropDown->IsEnabled() ) + { + return; + } + // force the menu to Think + m_pDropDown->PerformLayout(); + + // make sure we're at the top of the draw order (and therefore our children as well) + // RequestFocus(); + + // We want the item that is shown in the combo box to show as selected + int itemToSelect = -1; + int i; + wchar_t comboBoxContents[255]; + GetText(comboBoxContents, 255); + for ( i = 0 ; i < m_pDropDown->GetItemCount() ; i++ ) + { + wchar_t menuItemName[255]; + int menuID = m_pDropDown->GetMenuID(i); + m_pDropDown->GetMenuItem(menuID)->GetText(menuItemName, 255); + if (!wcscmp(menuItemName, comboBoxContents)) + { + itemToSelect = i; + break; + } + } + // if we found a match, highlight it on opening the menu + if ( itemToSelect >= 0 ) + { + m_pDropDown->SetCurrentlyHighlightedItem( m_pDropDown->GetMenuID(itemToSelect) ); + } + + // reset the dropdown's position + DoMenuLayout(); + + + // make sure we're at the top of the draw order (and therefore our children as well) + // this important to make sure the menu will be drawn in the foreground + MoveToFront(); + + // !KLUDGE! Force alpha to solid. Otherwise, + // we run into weird VGUI problems with pops + // and the stencil test + Color c = m_pDropDown->GetBgColor(); + c[3] = 255; + m_pDropDown->SetBgColor( c ); + + // notify + OnShowMenu(m_pDropDown); + + // show the menu + m_pDropDown->SetVisible(true); + + // bring to focus + m_pDropDown->RequestFocus(); + + // no text is highlighted when the menu is opened + SelectNoText(); + + // highlight the arrow while menu is open + m_pButton->SetArmed(true); + + Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Brighten the arrow on the button when entering the box +//----------------------------------------------------------------------------- +void ComboBox::OnCursorEntered() +{ + // want the arrow to go white when we enter the box + m_pButton->OnCursorEntered(); + TextEntry::OnCursorEntered(); +} + +//----------------------------------------------------------------------------- +// Purpose: Dim the arrow on the button when exiting the box +//----------------------------------------------------------------------------- +void ComboBox::OnCursorExited() +{ + // want the arrow to go grey when we exit the box if the menu is not open + if ( !m_pDropDown->IsVisible() ) + { + m_pButton->SetArmed(false); + TextEntry::OnCursorExited(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef _X360 +void ComboBox::OnMenuItemSelected() +{ + m_bHighlight = true; + // For editable cbs, fill in the text field from whatever is chosen from the dropdown... + + //============================================================================= + // HPE_BEGIN: + // [pfreese] The text for the combo box should be updated regardless of its + // editable state, and in any case, the member variable below was never + // correctly initialized. + //============================================================================= + + // if ( m_bAllowEdit ) + + //============================================================================= + // HPE_END + //============================================================================= + { + int idx = GetActiveItem(); + if ( idx >= 0 ) + { + wchar_t name[ 256 ]; + GetItemText( idx, name, sizeof( name ) ); + + OnSetText( name ); + } + } + + Repaint(); + + // go to the next control + if(!NavigateDown()) + { + NavigateUp(); + } +} +#else +void ComboBox::OnMenuItemSelected() +{ + m_bHighlight = true; + // For editable cbs, fill in the text field from whatever is chosen from the dropdown... + //if ( m_bAllowEdit ) + { + int idx = GetActiveItem(); + if ( idx >= 0 ) + { + wchar_t name[ 256 ]; + GetItemText( idx, name, sizeof( name ) ); + + OnSetText( name ); + } + } + + Repaint(); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ComboBox::OnSizeChanged(int wide, int tall) +{ + BaseClass::OnSizeChanged( wide, tall); + + // set the drawwidth. + int bwide, btall; + PerformLayout(); + m_pButton->GetSize( bwide, btall); + SetDrawWidth( wide - bwide ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef _X360 +void ComboBox::OnSetFocus() +{ + BaseClass::OnSetFocus(); + + GotoTextEnd(); + SelectAllText(true); +} +#else +void ComboBox::OnSetFocus() +{ + BaseClass::OnSetFocus(); + + GotoTextEnd(); + SelectAllText(false); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef _X360 +void ComboBox::OnKeyCodePressed(KeyCode code) +{ + switch ( GetBaseButtonCode( code ) ) + { + case KEY_XBUTTON_A: + DoClick(); + break; + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + case KEY_XSTICK2_UP: + if(m_pDropDown->IsVisible()) + { + MoveAlongMenuItemList(-1); + } + else + { + BaseClass::OnKeyCodePressed(code); + } + break; + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + case KEY_XSTICK2_DOWN: + if(m_pDropDown->IsVisible()) + { + MoveAlongMenuItemList(1); + } + else + { + BaseClass::OnKeyCodePressed(code); + } + break; + default: + BaseClass::OnKeyCodePressed(code); + break; + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Handles up/down arrows +//----------------------------------------------------------------------------- +void ComboBox::OnKeyCodeTyped(KeyCode code) +{ + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + if (alt) + { + switch (code) + { + case KEY_UP: + case KEY_DOWN: + { + DoClick(); + break; + } + default: + { + BaseClass::OnKeyCodeTyped(code); + break; + } + } + } + else + { + switch (code) + { + case KEY_HOME: + case KEY_END: + case KEY_PAGEUP: + case KEY_PAGEDOWN: + case KEY_UP: + case KEY_DOWN: + { + int itemSelected = m_pDropDown->GetCurrentlyHighlightedItem(); + m_pDropDown->OnKeyCodeTyped(code); + int itemToSelect = m_pDropDown->GetCurrentlyHighlightedItem(); + + if ( itemToSelect != itemSelected ) + { + SelectMenuItem(itemToSelect); + } + break; + } + + case KEY_ENTER: + { + int itemToSelect = m_pDropDown->GetCurrentlyHighlightedItem(); + m_pDropDown->ActivateItem(itemToSelect); + break; + } + + default: + { + BaseClass::OnKeyCodeTyped(code); + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: handles key input +//----------------------------------------------------------------------------- +void ComboBox::OnKeyTyped(wchar_t unichar) +{ + if ( IsEditable() || unichar == '\t') // don't play with key presses in edit mode + { + BaseClass::OnKeyTyped( unichar ); + return; + } + + int itemSelected = m_pDropDown->GetCurrentlyHighlightedItem(); + m_pDropDown->OnKeyTyped(unichar); + int itemToSelect = m_pDropDown->GetCurrentlyHighlightedItem(); + + if ( itemToSelect != itemSelected ) + { + SelectMenuItem(itemToSelect); + } + else + { + BaseClass::OnKeyTyped( unichar ); + } +} + +void ComboBox::SelectMenuItem(int itemToSelect) +{ + // if we found this item, then we scroll up or down + if ( itemToSelect >= 0 && itemToSelect < m_pDropDown->GetItemCount() ) + { + wchar_t menuItemName[255]; + + int menuID = m_pDropDown->GetMenuID(itemToSelect); + m_pDropDown->GetMenuItem(menuID)->GetText(menuItemName, 254); + OnSetText(menuItemName); + SelectAllText(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ComboBox::MoveAlongMenuItemList(int direction) +{ + // We want the item that is shown in the combo box to show as selected + int itemToSelect = -1; + wchar_t menuItemName[255]; + int i; + + wchar_t comboBoxContents[255]; + GetText(comboBoxContents, 254); + for ( i = 0 ; i < m_pDropDown->GetItemCount() ; i++ ) + { + int menuID = m_pDropDown->GetMenuID(i); + m_pDropDown->GetMenuItem(menuID)->GetText(menuItemName, 254); + + if ( !wcscmp(menuItemName, comboBoxContents) ) + { + itemToSelect = i; + break; + } + } + + if ( itemToSelect >= 0 ) + { + int newItem = itemToSelect + direction; + if ( newItem < 0 ) + { + newItem = 0; + } + else if ( newItem >= m_pDropDown->GetItemCount() ) + { + newItem = m_pDropDown->GetItemCount() - 1; + } + SelectMenuItem(newItem); + } + +} + +void ComboBox::MoveToFirstMenuItem() +{ + SelectMenuItem(0); +} + +void ComboBox::MoveToLastMenuItem() +{ + SelectMenuItem(m_pDropDown->GetItemCount() - 1); +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the direction from the menu button the menu should open +//----------------------------------------------------------------------------- +void ComboBox::SetOpenDirection(Menu::MenuDirection_e direction) +{ + m_iDirection = direction; +} + +void ComboBox::SetFont( HFont font ) +{ + BaseClass::SetFont( font ); + + m_pDropDown->SetFont( font ); +} + + +void ComboBox::SetUseFallbackFont( bool bState, HFont hFallback ) +{ + BaseClass::SetUseFallbackFont( bState, hFallback ); + m_pDropDown->SetUseFallbackFont( bState, hFallback ); +} diff --git a/vgui2/vgui_controls/ControllerMap.cpp b/vgui2/vgui_controls/ControllerMap.cpp new file mode 100644 index 0000000..d4859c1 --- /dev/null +++ b/vgui2/vgui_controls/ControllerMap.cpp @@ -0,0 +1,160 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/ControllerMap.h" +#include "vgui/ISurface.h" +#include "vgui/KeyCode.h" +#include "KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +struct keystring_t +{ + int code; + const char *name; +}; + +static const keystring_t s_ControllerButtons[] = { { KEY_XBUTTON_UP, "KEY_XBUTTON_UP" }, + { KEY_XBUTTON_DOWN, "KEY_XBUTTON_DOWN" }, + { KEY_XBUTTON_LEFT, "KEY_XBUTTON_LEFT" }, + { KEY_XBUTTON_RIGHT, "KEY_XBUTTON_RIGHT" }, + { KEY_XBUTTON_START, "KEY_XBUTTON_START" }, + { KEY_XBUTTON_BACK, "KEY_XBUTTON_BACK" }, + { KEY_XBUTTON_STICK1, "KEY_XBUTTON_STICK1" }, + { KEY_XBUTTON_STICK2, "KEY_XBUTTON_STICK2" }, + { KEY_XBUTTON_A, "KEY_XBUTTON_A" }, + { KEY_XBUTTON_B, "KEY_XBUTTON_B" }, + { KEY_XBUTTON_X, "KEY_XBUTTON_X" }, + { KEY_XBUTTON_Y, "KEY_XBUTTON_Y" }, + { KEY_XBUTTON_LEFT_SHOULDER, "KEY_XBUTTON_LEFT_SHOULDER" }, + { KEY_XBUTTON_RIGHT_SHOULDER, "KEY_XBUTTON_RIGHT_SHOULDER" }, + { KEY_XBUTTON_LTRIGGER, "KEY_XBUTTON_LTRIGGER" }, + { KEY_XBUTTON_RTRIGGER, "KEY_XBUTTON_RTRIGGER" }, + { KEY_XSTICK1_UP, "KEY_XSTICK1_UP" }, + { KEY_XSTICK1_DOWN, "KEY_XSTICK1_DOWN" }, + { KEY_XSTICK1_LEFT, "KEY_XSTICK1_LEFT" }, + { KEY_XSTICK1_RIGHT, "KEY_XSTICK1_RIGHT" }, + { KEY_XSTICK2_UP, "KEY_XSTICK2_UP" }, + { KEY_XSTICK2_DOWN, "KEY_XSTICK2_DOWN" }, + { KEY_XSTICK2_LEFT, "KEY_XSTICK2_LEFT" }, + { KEY_XSTICK2_RIGHT, "KEY_XSTICK2_RIGHT" } }; + +//----------------------------------------------------------------------------- +// Purpose: for the UtlMap +//----------------------------------------------------------------------------- +bool lessFunc( const int &lhs, const int &rhs ) +{ + return lhs < rhs; +} + +//----------------------------------------------------------------------------- +// Purpose: converts a button name string to the equivalent keycode +//----------------------------------------------------------------------------- +int StringToButtonCode( const char *name ) +{ + for ( int i = 0; i < ARRAYSIZE( s_ControllerButtons ); ++i ) + { + if ( !Q_stricmp( s_ControllerButtons[i].name, name ) ) + return s_ControllerButtons[i].code; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: intercepts the keycode from its parent, and handles it according to +// the button map. If the keycode isn't handled, it gets passed on to the parent. +//----------------------------------------------------------------------------- +void CControllerMap::OnKeyCodeTyped( vgui::KeyCode code ) +{ + int idx = m_buttonMap.Find( code ); + if ( idx != m_buttonMap.InvalidIndex() ) + { + GetParent()->OnCommand( m_buttonMap[idx].cmd.String() ); + } + else + { + // Disable input before forwarding the message + // so it doesn't feed back here again. + SetKeyBoardInputEnabled( false ); + GetParent()->OnKeyCodeTyped( code ); + SetKeyBoardInputEnabled( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CControllerMap::CControllerMap( vgui::Panel *parent, const char *name ) : BaseClass( parent, name ) +{ + m_buttonMap.SetLessFunc( lessFunc ); +} + +//----------------------------------------------------------------------------- +// Purpose: sets up the button/command bindings +//----------------------------------------------------------------------------- +void CControllerMap::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + // loop through all the data adding items to the menu + for (KeyValues *dat = inResourceData->GetFirstSubKey(); dat != NULL; dat = dat->GetNextKey()) + { + if ( !Q_stricmp( dat->GetName(), "button" ) ) + { + const char *buttonName = dat->GetString( "name", "" ); + int keycode = StringToButtonCode( buttonName ); + if ( keycode != -1 ) + { + button_t b; + b.cmd = CUtlSymbol( dat->GetString( "command", "" ) ); + + // text and icon are optional - their existence means this button + // should be displayed in the footer panel. + const char *helpText = dat->GetString( "text", NULL ); + if ( helpText ) + { + b.text = CUtlSymbol( helpText ); + b.icon = CUtlSymbol( dat->GetString( "icon", NULL ) ); + } + + m_buttonMap.Insert( keycode, b ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: gets the help text for a binding, if it exists +//----------------------------------------------------------------------------- +const char *CControllerMap::GetBindingText( int idx ) +{ + CUtlSymbol s = m_buttonMap[idx].text; + if ( s.IsValid() ) + { + return s.String(); + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the icon for a binding, if it exists +//----------------------------------------------------------------------------- +const char *CControllerMap::GetBindingIcon( int idx ) +{ + CUtlSymbol s = m_buttonMap[idx].icon; + if ( s.IsValid() ) + { + return s.String(); + } + return NULL; +} + +DECLARE_BUILD_FACTORY( CControllerMap ); + diff --git a/vgui2/vgui_controls/DirectorySelectDialog.cpp b/vgui2/vgui_controls/DirectorySelectDialog.cpp new file mode 100644 index 0000000..0e9eac5 --- /dev/null +++ b/vgui2/vgui_controls/DirectorySelectDialog.cpp @@ -0,0 +1,594 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#define PROTECTED_THINGS_DISABLE + +#include <vgui_controls/Button.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/DirectorySelectDialog.h> +#include <vgui_controls/TreeView.h> +#include <vgui_controls/ImageList.h> +#include <vgui_controls/MessageBox.h> +#include <vgui/Cursor.h> +#include <KeyValues.h> +#include <vgui/IInput.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <filesystem.h> + +#ifdef WIN32 +#include <direct.h> +#include <stdio.h> +#include <io.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DirectoryTreeView::DirectoryTreeView(DirectorySelectDialog *parent, const char *name) : TreeView(parent, name) +{ + m_pParent = parent; +} + +void DirectoryTreeView::GenerateChildrenOfNode(int itemIndex) +{ + m_pParent->GenerateChildrenOfDirectoryNode(itemIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: Used to prompt the user to create a directory +//----------------------------------------------------------------------------- +class CreateDirectoryDialog : public Frame +{ + DECLARE_CLASS_SIMPLE(CreateDirectoryDialog, Frame); + +public: + CreateDirectoryDialog(Panel *parent, const char *defaultCreateDirName) : BaseClass(parent, NULL) + { + SetSize(320, 100); + SetSizeable(false); + SetTitle("Choose directory name", false); + MoveToCenterOfScreen(); + + m_pOKButton = new Button(this, "OKButton", "#vgui_ok"); + m_pCancelButton = new Button(this, "OKButton", "#vgui_cancel"); + m_pNameEntry = new TextEntry(this, "NameEntry"); + + m_pOKButton->SetCommand("OK"); + m_pCancelButton->SetCommand("Close"); + m_pNameEntry->SetText(defaultCreateDirName); + m_pNameEntry->RequestFocus(); + m_pNameEntry->SelectAllText(true); + + // If some other window was hogging the input focus, then we have to hog it or else we'll never get input. + m_PrevAppFocusPanel = vgui::input()->GetAppModalSurface(); + if ( m_PrevAppFocusPanel ) + vgui::input()->SetAppModalSurface( GetVPanel() ); + } + + ~CreateDirectoryDialog() + { + if ( m_PrevAppFocusPanel ) + vgui::input()->SetAppModalSurface( m_PrevAppFocusPanel ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + m_pNameEntry->SetBounds(24, 32, GetWide() - 48, 24); + m_pOKButton->SetBounds(GetWide() - 176, 64, 72, 24); + m_pCancelButton->SetBounds(GetWide() - 94, 64, 72, 24); + } + + virtual void OnCommand(const char *command) + { + if (!stricmp(command, "OK")) + { + PostActionSignal(new KeyValues("CreateDirectory", "dir", GetControlString("NameEntry"))); + Close(); + } + else + { + BaseClass::OnCommand(command); + } + } + + virtual void OnClose() + { + BaseClass::OnClose(); + MarkForDeletion(); + } + +private: + vgui::Button *m_pOKButton; + vgui::Button *m_pCancelButton; + vgui::TextEntry *m_pNameEntry; + vgui::VPANEL m_PrevAppFocusPanel; +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +DirectorySelectDialog::DirectorySelectDialog(vgui::Panel *parent, const char *title) : Frame(parent, NULL) +{ + SetTitle(title, true); + SetSize(320, 360); + SetMinimumSize(300, 240); + m_szCurrentDir[0] = 0; + m_szDefaultCreateDirName[0] = 0; + + m_pDirTree = new DirectoryTreeView(this, "DirTree"); + m_pDriveCombo = new ComboBox(this, "DriveCombo", 6, false); + m_pCancelButton = new Button(this, "CancelButton", "#VGui_Cancel"); + m_pSelectButton = new Button(this, "SelectButton", "#VGui_Select"); + m_pCreateButton = new Button(this, "CreateButton", "#VGui_CreateFolder"); + m_pCancelButton->SetCommand("Cancel"); + m_pSelectButton->SetCommand("Select"); + m_pCreateButton->SetCommand("Create"); +} + +//----------------------------------------------------------------------------- +// Purpose: lays out controls +//----------------------------------------------------------------------------- +void DirectorySelectDialog::PerformLayout() +{ + BaseClass::PerformLayout(); + + // lay out all the controls + m_pDriveCombo->SetBounds(24, 30, GetWide() - 48, 24); + m_pDirTree->SetBounds(24, 64, GetWide() - 48, GetTall() - 128); + + m_pCreateButton->SetBounds(24, GetTall() - 48, 104, 24); + m_pSelectButton->SetBounds(GetWide() - 172, GetTall() - 48, 72, 24); + m_pCancelButton->SetBounds(GetWide() - 96, GetTall() - 48, 72, 24); +} + +//----------------------------------------------------------------------------- +// Purpose: lays out controls +//----------------------------------------------------------------------------- +void DirectorySelectDialog::ApplySchemeSettings(IScheme *pScheme) +{ + ImageList *imageList = new ImageList(false); + imageList->AddImage(scheme()->GetImage("Resource/icon_folder", false)); + imageList->AddImage(scheme()->GetImage("Resource/icon_folder_selected", false)); + m_pDirTree->SetImageList(imageList, true); + + BaseClass::ApplySchemeSettings(pScheme); +} + +//----------------------------------------------------------------------------- +// Purpose: Move the start string forward until we hit a slash and return the +// the first character past the trailing slash +//----------------------------------------------------------------------------- +inline const char *MoveToNextSubDir( const char *pStart, int *nCount ) +{ + int nMoved = 0; + + // Move past pre-pended slash + if ( pStart[nMoved] == '\\' ) + { + nMoved++; + } + + // Move past the current block of text until we've hit the next path seperator (or end) + while ( pStart[nMoved] != '\\' && pStart[nMoved] != '\0' ) + { + nMoved++; + } + + // Move past trailing slash + if ( pStart[nMoved] == '\\' ) + { + nMoved++; + } + + // Give back a count if they've supplied a pointer + if ( nCount != NULL ) + { + *nCount = nMoved; + } + + // The beginning of the next string, past slash + return (pStart+nMoved); +} + +//----------------------------------------------------------------------------- +// Purpose: Walk through our directory structure given a path as our guide, while expanding +// and populating the nodes of the tree view to match +// Input : *path - path (with drive letter) to show +//----------------------------------------------------------------------------- +void DirectorySelectDialog::ExpandTreeToPath( const char *lpszPath, bool bSelectFinalDirectory /*= true*/ ) +{ + // Make sure our slashes are correct! + char workPath[MAX_PATH]; + Q_strncpy( workPath, lpszPath, sizeof(workPath) ); + Q_FixSlashes( workPath ); + + // Set us to the work drive + SetStartDirectory( workPath ); + + // Check that the path is valid + if ( workPath[0] == '\0' || DoesDirectoryHaveSubdirectories( m_szCurrentDrive, "" ) == false ) + { + // Failing, start in C: + SetStartDirectory( "C:\\" ); + } + + // Start at the root of our tree + int nItemIndex = m_pDirTree->GetRootItemIndex(); + + // Move past the drive letter to the first subdir + int nPathPos = 0; + const char *lpszSubDirName = MoveToNextSubDir( workPath, &nPathPos ); + const char *lpszLastSubDirName = NULL; + int nPathIncr = 0; + char subDirName[MAX_PATH]; + + // While there are subdirectory names present, expand and populate the tree with their subdirectories + while ( lpszSubDirName[0] != '\0' ) + { + // Move our string pointer forward while keeping where our last subdir started off + lpszLastSubDirName = lpszSubDirName; + lpszSubDirName = MoveToNextSubDir( lpszSubDirName, &nPathIncr ); + + // Get the span between the last subdir and the new one + Q_StrLeft( lpszLastSubDirName, nPathIncr, subDirName, sizeof(subDirName) ); + Q_StripTrailingSlash( subDirName ); + + // Increment where we are in the string for use later + nPathPos += nPathIncr; + + // Run through the list and expand to our currently selected directory + for ( int i = 0; i < m_pDirTree->GetNumChildren( nItemIndex ); i++ ) + { + // Get the child and data for it + int nChild = m_pDirTree->GetChild( nItemIndex, i ); + KeyValues *pValues = m_pDirTree->GetItemData( nChild ); + + // See if this matches + if ( Q_stricmp( pValues->GetString( "Text" ), subDirName ) == 0 ) + { + // This is the new root item + nItemIndex = nChild; + + // Get the full path (starting from the drive letter) up to our current subdir + Q_strncpy( subDirName, workPath, nPathPos ); + Q_AppendSlash( subDirName, sizeof(subDirName) ); + + // Expand the tree node and populate its subdirs for our next iteration + ExpandTreeNode( subDirName, nItemIndex ); + break; + } + } + } + + // Select our last directory if we've been asked to (and it's valid) + if ( bSelectFinalDirectory && m_pDirTree->IsItemIDValid( nItemIndex ) ) + { + // If we don't call this once before selecting an item, the tree will not be properly expanded + // before it calculates how to show the selected item in the view + PerformLayout(); + + // Select that item + m_pDirTree->AddSelectedItem( nItemIndex, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets where it should start searching +//----------------------------------------------------------------------------- +void DirectorySelectDialog::SetStartDirectory(const char *path) +{ + strncpy(m_szCurrentDir, path, sizeof(m_szCurrentDir)); + strncpy(m_szCurrentDrive, path, sizeof(m_szCurrentDrive)); + m_szCurrentDrive[sizeof(m_szCurrentDrive) - 1] = 0; + char *firstSlash = strstr(m_szCurrentDrive, "\\"); + if (firstSlash) + { + firstSlash[1] = 0; + } + + BuildDirTree(); + BuildDriveChoices(); + + // update state of create directory button + int selectedIndex = m_pDirTree->GetFirstSelectedItem(); + if (m_pDirTree->IsItemIDValid(selectedIndex)) + { + m_pCreateButton->SetEnabled(true); + } + else + { + m_pCreateButton->SetEnabled(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets what name should show up by default in the create directory dialog +//----------------------------------------------------------------------------- +void DirectorySelectDialog::SetDefaultCreateDirectoryName(const char *defaultCreateDirName) +{ + strncpy(m_szDefaultCreateDirName, defaultCreateDirName, sizeof(m_szDefaultCreateDirName)); + m_szDefaultCreateDirName[sizeof(m_szDefaultCreateDirName) - 1] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: opens the dialog +//----------------------------------------------------------------------------- +void DirectorySelectDialog::DoModal() +{ + input()->SetAppModalSurface(GetVPanel()); + BaseClass::Activate(); + MoveToCenterOfScreen(); +} + +//----------------------------------------------------------------------------- +// Purpose: Builds drive choices +//----------------------------------------------------------------------------- +void DirectorySelectDialog::BuildDriveChoices() +{ + m_pDriveCombo->DeleteAllItems(); + + char drives[256] = { 0 }; + int len = system()->GetAvailableDrives(drives, sizeof(drives)); + char *pBuf = drives; + KeyValues *kv = new KeyValues("drive"); + for (int i = 0; i < len / 4; i++) + { + kv->SetString("drive", pBuf); + int itemID = m_pDriveCombo->AddItem(pBuf, kv); + if (!stricmp(pBuf, m_szCurrentDrive)) + { + m_pDriveCombo->ActivateItem(itemID); + } + + pBuf += 4; + } + kv->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: Builds the base tree directory +//----------------------------------------------------------------------------- +void DirectorySelectDialog::BuildDirTree() +{ + // clear current tree + m_pDirTree->RemoveAll(); + + // add in a root + int rootIndex = m_pDirTree->AddItem(new KeyValues("root", "Text", m_szCurrentDrive), -1); + + // build first level of the tree + ExpandTreeNode(m_szCurrentDrive, rootIndex); + + // start the root expanded + m_pDirTree->ExpandItem(rootIndex, true); +} + +//----------------------------------------------------------------------------- +// Purpose: expands a path +//----------------------------------------------------------------------------- +void DirectorySelectDialog::ExpandTreeNode(const char *path, int parentNodeIndex) +{ + // set the small wait cursor + surface()->SetCursor(dc_waitarrow); + + // get all the subfolders of the current drive + char searchString[512]; + sprintf(searchString, "%s*.*", path); + + FileFindHandle_t h; + const char *pFileName = g_pFullFileSystem->FindFirstEx( searchString, NULL, &h ); + for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( h ) ) + { + if ( !Q_stricmp( pFileName, ".." ) || !Q_stricmp( pFileName, "." ) ) + continue; + + KeyValues *kv = new KeyValues("item"); + kv->SetString("Text", pFileName); + // set the folder image + kv->SetInt("Image", 1); + kv->SetInt("SelectedImage", 1); + kv->SetInt("Expand", DoesDirectoryHaveSubdirectories(path, pFileName)); + m_pDirTree->AddItem(kv, parentNodeIndex); + } + g_pFullFileSystem->FindClose( h ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool DirectorySelectDialog::DoesDirectoryHaveSubdirectories(const char *path, const char *dir) +{ + char searchString[512]; + sprintf(searchString, "%s%s\\*.*", path, dir); + + FileFindHandle_t h; + const char *pFileName = g_pFullFileSystem->FindFirstEx( searchString, NULL, &h ); + for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( h ) ) + { + char szFullPath[ MAX_PATH ]; + Q_snprintf( szFullPath, sizeof(szFullPath), "%s\\%s", path, pFileName ); + Q_FixSlashes( szFullPath ); + if ( g_pFullFileSystem->IsDirectory( szFullPath ) ) + { + g_pFullFileSystem->FindClose( h ); + return true; + } + } + g_pFullFileSystem->FindClose( h ); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Generates the children for the specified node +//----------------------------------------------------------------------------- +void DirectorySelectDialog::GenerateChildrenOfDirectoryNode(int nodeIndex) +{ + // generate path + char path[512]; + GenerateFullPathForNode(nodeIndex, path, sizeof(path)); + + // expand out + ExpandTreeNode(path, nodeIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: creates the full path for a node +//----------------------------------------------------------------------------- +void DirectorySelectDialog::GenerateFullPathForNode(int nodeIndex, char *path, int pathBufferSize) +{ + // get all the nodes + CUtlLinkedList<int, int> nodes; + nodes.AddToTail(nodeIndex); + int parentIndex = nodeIndex; + while (1) + { + parentIndex = m_pDirTree->GetItemParent(parentIndex); + if (parentIndex == -1) + break; + nodes.AddToHead(parentIndex); + } + + // walk the nodes, adding to the path + path[0] = 0; + bool bFirst = true; + FOR_EACH_LL( nodes, i ) + { + KeyValues *kv = m_pDirTree->GetItemData( nodes[i] ); + strcat(path, kv->GetString("Text")); + + if (!bFirst) + { + strcat(path, "\\"); + } + bFirst = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles combo box changes +//----------------------------------------------------------------------------- +void DirectorySelectDialog::OnTextChanged() +{ + KeyValues *kv = m_pDriveCombo->GetActiveItemUserData(); + if (!kv) + return; + const char *newDrive = kv->GetString("drive"); + if (stricmp(newDrive, m_szCurrentDrive)) + { + // drive changed, reset + SetStartDirectory(newDrive); + } +} + +//----------------------------------------------------------------------------- +// Purpose: creates a directory +//----------------------------------------------------------------------------- +void DirectorySelectDialog::OnCreateDirectory(const char *dir) +{ + int selectedIndex = m_pDirTree->GetFirstSelectedItem(); + if (m_pDirTree->IsItemIDValid(selectedIndex)) + { + char fullPath[512]; + GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath)); + + // create the new directory underneath + strcat(fullPath, dir); + if (_mkdir(fullPath) == 0) + { + // add new path to tree view + KeyValues *kv = new KeyValues("item"); + kv->SetString("Text", dir); + // set the folder image + kv->SetInt("Image", 1); + kv->SetInt("SelectedImage", 1); + int itemID = m_pDirTree->AddItem(kv, selectedIndex); + + // select the item + m_pDirTree->AddSelectedItem( itemID, true ); + } + else + { + // print error message + MessageBox *box = new MessageBox("#vgui_CreateDirectoryFail_Title", "#vgui_CreateDirectoryFail_Info"); + box->DoModal(this); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: dialog closes +//----------------------------------------------------------------------------- +void DirectorySelectDialog::OnClose() +{ + BaseClass::OnClose(); + MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: handles button commands +//----------------------------------------------------------------------------- +void DirectorySelectDialog::OnCommand(const char *command) +{ + if (!stricmp(command, "Cancel")) + { + Close(); + } + else if (!stricmp(command, "Select")) + { + // path selected + int selectedIndex = m_pDirTree->GetFirstSelectedItem(); + if (m_pDirTree->IsItemIDValid(selectedIndex)) + { + char fullPath[512]; + GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath)); + PostActionSignal(new KeyValues("DirectorySelected", "dir", fullPath)); + Close(); + } + } + else if (!stricmp(command, "Create")) + { + int selectedIndex = m_pDirTree->GetFirstSelectedItem(); + if (m_pDirTree->IsItemIDValid(selectedIndex)) + { + CreateDirectoryDialog *dlg = new CreateDirectoryDialog(this, m_szDefaultCreateDirName); + dlg->AddActionSignalTarget(this); + dlg->Activate(); + } + } + else + { + BaseClass::OnCommand(command); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Update the text in the combo +//----------------------------------------------------------------------------- +void DirectorySelectDialog::OnTreeViewItemSelected() +{ + int selectedIndex = m_pDirTree->GetFirstSelectedItem(); + if (!m_pDirTree->IsItemIDValid(selectedIndex)) + { + m_pCreateButton->SetEnabled(false); + return; + } + m_pCreateButton->SetEnabled(true); + + // build the string + char fullPath[512]; + GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath)); + + int itemID = m_pDriveCombo->GetActiveItem(); + m_pDriveCombo->UpdateItem(itemID, fullPath, NULL); + m_pDriveCombo->SetText(fullPath); +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/Divider.cpp b/vgui2/vgui_controls/Divider.cpp new file mode 100644 index 0000000..b705d6d --- /dev/null +++ b/vgui2/vgui_controls/Divider.cpp @@ -0,0 +1,41 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/IScheme.h> + +#include <vgui_controls/Divider.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +DECLARE_BUILD_FACTORY( Divider ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Divider::Divider(Panel *parent, const char *name) : Panel(parent, name) +{ + SetSize(128, 2); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Divider::~Divider() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: sets up the border as the line to draw as a divider +//----------------------------------------------------------------------------- +void Divider::ApplySchemeSettings(IScheme *pScheme) +{ + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); + BaseClass::ApplySchemeSettings(pScheme); +} diff --git a/vgui2/vgui_controls/EditablePanel.cpp b/vgui2/vgui_controls/EditablePanel.cpp new file mode 100644 index 0000000..670d4db --- /dev/null +++ b/vgui2/vgui_controls/EditablePanel.cpp @@ -0,0 +1,1072 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include <vgui/IPanel.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/ILocalize.h> +#include <KeyValues.h> +#include "vgui/IVGui.h" + +#include <vgui_controls/BuildGroup.h> +#include <vgui_controls/BuildModeDialog.h> +#include <vgui_controls/EditablePanel.h> + +// these includes are all for the virtual contruction factory Dialog::CreateControlByName() +#include <vgui_controls/Button.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/CheckButton.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/Menu.h> +#include <vgui_controls/MenuItem.h> +#include <vgui_controls/MessageBox.h> +#include <vgui_controls/ProgressBar.h> +#include <vgui_controls/RadioButton.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/ToggleButton.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/AnimatingImagePanel.h> +#include <vgui_controls/Divider.h> +#include <vgui_controls/URLLabel.h> +#include <vgui_controls/RichText.h> +#include <vgui_controls/BitmapImagePanel.h> + +#include "filesystem.h" +#include "fmtstr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( EditablePanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +#pragma warning( disable : 4355 ) + +EditablePanel::EditablePanel(Panel *parent, const char *panelName) : Panel(parent, panelName), m_NavGroup(this) +{ + _buildGroup = new BuildGroup(this, this); + m_pszConfigName = NULL; + m_iConfigID = 0; + m_pDialogVariables = NULL; + m_bShouldSkipAutoResize = false; + + // add ourselves to the build group + SetBuildGroup(GetBuildGroup()); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +EditablePanel::EditablePanel(Panel *parent, const char *panelName, HScheme hScheme) : Panel(parent, panelName, hScheme), m_NavGroup(this) +{ + _buildGroup = new BuildGroup(this, this); + m_pszConfigName = NULL; + m_iConfigID = 0; + m_pDialogVariables = NULL; + m_bShouldSkipAutoResize = false; + + // add ourselves to the build group + SetBuildGroup(GetBuildGroup()); +} + +#pragma warning( default : 4355 ) + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +EditablePanel::~EditablePanel() +{ + delete [] m_pszConfigName; + delete _buildGroup; + + if (m_pDialogVariables) + { + m_pDialogVariables->deleteThis(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a child is added to the panel. +//----------------------------------------------------------------------------- +void EditablePanel::OnChildAdded(VPANEL child) +{ + BaseClass::OnChildAdded(child); + + // add only if we're in the same module + Panel *panel = ipanel()->GetPanel(child, GetModuleName()); + if (panel) + { + panel->SetBuildGroup(_buildGroup); + panel->AddActionSignalTarget(this); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void EditablePanel::OnKeyCodePressed( KeyCode code ) +{ + static ConVarRef vgui_nav_lock_default_button( "vgui_nav_lock_default_button" ); + if ( !vgui_nav_lock_default_button.IsValid() || vgui_nav_lock_default_button.GetInt() == 0 ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + // check for a default button + VPANEL panel = GetFocusNavGroup().GetCurrentDefaultButton(); + if ( panel && !IsConsoleStylePanel() ) + { + switch ( nButtonCode ) + { + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + case KEY_XSTICK2_UP: + case KEY_UP: + case STEAMCONTROLLER_DPAD_UP: + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + case KEY_XSTICK2_DOWN: + case KEY_DOWN: + case STEAMCONTROLLER_DPAD_DOWN: + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case KEY_XSTICK2_LEFT: + case KEY_LEFT: + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case KEY_XSTICK2_RIGHT: + case KEY_RIGHT: + case KEY_XBUTTON_B: + case STEAMCONTROLLER_B: + // Navigating menus + vgui_nav_lock_default_button.SetValue( 1 ); + PostMessage( panel, new KeyValues( "KeyCodePressed", "code", code ) ); + return; + + case KEY_XBUTTON_A: + case STEAMCONTROLLER_A: + case KEY_ENTER: + if ( ipanel()->IsVisible( panel ) && ipanel()->IsEnabled( panel ) ) + { + // Activate the button + PostMessage( panel, new KeyValues( "Hotkey" ) ); + return; + } + } + } + } + + if ( !m_PassUnhandledInput ) + return; + + // Nothing to do with the button + BaseClass::OnKeyCodePressed( code ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Callback for when the panel size has been changed +//----------------------------------------------------------------------------- +void EditablePanel::OnSizeChanged(int wide, int tall) +{ + BaseClass::OnSizeChanged(wide, tall); + InvalidateLayout(); + + for (int i = 0; i < GetChildCount(); i++) + { + // perform auto-layout on the child panel + Panel *child = GetChild(i); + if ( !child ) + continue; + + int x, y, w, h; + child->GetBounds( x, y, w, h ); + + int px, py; + child->GetPinOffset( px, py ); + + int ox, oy; + child->GetResizeOffset( ox, oy ); + + int ex; + int ey; + + AutoResize_e resize = child->GetAutoResize(); + bool bResizeHoriz = ( resize == AUTORESIZE_RIGHT || resize == AUTORESIZE_DOWNANDRIGHT ); + bool bResizeVert = ( resize == AUTORESIZE_DOWN || resize == AUTORESIZE_DOWNANDRIGHT ); + + // The correct version of this code would say: + // if ( resize != AUTORESIZE_NO ) + // but we're very close to shipping and this causes artifacts in other vgui panels that now + // depend on this bug. So, I've added m_bShouldSkipAutoResize, which defaults to false but can + // be set using "skip_autoresize" in a .res file + if ( !m_bShouldSkipAutoResize ) + { + PinCorner_e pinCorner = child->GetPinCorner(); + if ( pinCorner == PIN_TOPRIGHT || pinCorner == PIN_BOTTOMRIGHT ) + { + // move along with the right edge + ex = wide + px; + x = bResizeHoriz ? ox : ex - w; + } + else + { + x = px; + ex = bResizeHoriz ? wide + ox : px + w; + } + + if ( pinCorner == PIN_BOTTOMLEFT || pinCorner == PIN_BOTTOMRIGHT ) + { + // move along with the right edge + ey = tall + py; + y = bResizeVert ? oy : ey - h; + } + else + { + y = py; + ey = bResizeVert ? tall + oy : py + h; + } + + // Clamp.. + if ( ex < x ) + { + ex = x; + } + if ( ey < y ) + { + ey = y; + } + + child->SetBounds( x, y, ex - x, ey - y ); + child->InvalidateLayout(); + } + } + Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void EditablePanel::OnCurrentDefaultButtonSet( VPANEL defaultButton ) +{ + m_NavGroup.SetCurrentDefaultButton( defaultButton, false ); + + // forward the message up + if (GetVParent()) + { + KeyValues *msg = new KeyValues("CurrentDefaultButtonSet"); + msg->SetInt("button", ivgui()->PanelToHandle( defaultButton ) ); + PostMessage(GetVParent(), msg); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void EditablePanel::OnDefaultButtonSet( VPANEL defaultButton ) +{ + Panel *panel = ipanel()->GetPanel( defaultButton, GetModuleName() ); + + m_NavGroup.SetDefaultButton(panel); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void EditablePanel::OnFindDefaultButton() +{ + if (m_NavGroup.GetDefaultButton()) + { + m_NavGroup.SetCurrentDefaultButton(m_NavGroup.GetDefaultButton()); + } + else + { + if (GetVParent()) + { + PostMessage(GetVParent(), new KeyValues("FindDefaultButton")); + } + } +} + +struct leaf_t +{ + short x, y, wide, tall; + unsigned char split; // 0 no split; 1 x-axis, 2 y-axis + bool filled; // true if this is already filled + short splitpos; // place of split + + leaf_t *left; + leaf_t *right; +}; + +leaf_t g_Leaves[256]; +int g_iNextLeaf; + +inline leaf_t *AllocLeaf() +{ + Assert(g_iNextLeaf < 255); + + return &g_Leaves[g_iNextLeaf++]; +} + +void AddSolidToTree(leaf_t *leaf, int x, int y, int wide, int tall) +{ + // clip to this leaf + if (x < leaf->x) + { + wide -= (leaf->x - x); + if (wide < 1) + return; + x = leaf->x; + } + if (y < leaf->y) + { + tall -= (leaf->y - y); + if (tall < 1) + return; + y = leaf->y; + } + if (x + wide > leaf->x + leaf->wide) + { + wide -= ((x + wide) - (leaf->x + leaf->wide)); + if (wide < 1) + return; + } + if (y + tall > leaf->y + leaf->tall) + { + tall -= ((y + tall) - (leaf->y + leaf->tall)); + if (tall < 1) + return; + } + + // the rect should now be completely within the leaf + if (leaf->split == 1) + { + // see if it is to the left or the right of the split + if (x < leaf->splitpos) + { + // it's to the left + AddSolidToTree(leaf->left, x, y, wide, tall); + } + else if (x + wide > leaf->splitpos) + { + // it's to the right + AddSolidToTree(leaf->right, x, y, wide, tall); + } + } + else if (leaf->split == 2) + { + // check y + // see if it is to the left (above) or the right (below) of the split + if (y < leaf->splitpos) + { + // it's above + AddSolidToTree(leaf->left, x, y, wide, tall); + } + else if (y + tall > leaf->splitpos) + { + // it's below + AddSolidToTree(leaf->right, x, y, wide, tall); + } + } + else + { + // this leaf is unsplit, make the first split against the first edge we find + if (x > leaf->x) + { + // split the left side of the rect + leaf->split = 1; + leaf->splitpos = (short)x; + + // create 2 new leaves + leaf_t *left = AllocLeaf(); + leaf_t *right = AllocLeaf(); + memset(left, 0, sizeof(leaf_t)); + memset(right, 0, sizeof(leaf_t)); + leaf->left = left; + leaf->right = right; + + left->x = leaf->x; + left->y = leaf->y; + left->wide = (short)(leaf->splitpos - leaf->x); + left->tall = leaf->tall; + + right->x = leaf->splitpos; + right->y = leaf->y; + right->wide = (short)(leaf->wide - left->wide); + right->tall = leaf->tall; + + // split the right leaf by the current rect + AddSolidToTree(leaf->right, x, y, wide, tall); + } + else if (y > leaf->y) + { + // split the top edge + leaf->split = 2; + leaf->splitpos = (short)y; + + // create 2 new leaves (facing to the east) + leaf_t *left = AllocLeaf(); + leaf_t *right = AllocLeaf(); + memset(left, 0, sizeof(leaf_t)); + memset(right, 0, sizeof(leaf_t)); + leaf->left = left; + leaf->right = right; + + left->x = leaf->x; + left->y = leaf->y; + left->wide = leaf->wide; + left->tall = (short)(y - leaf->y); + + right->x = leaf->x; + right->y = leaf->splitpos; + right->wide = leaf->wide; + right->tall = (short)(leaf->tall + leaf->y - right->y); + + // split the right leaf by the current rect + AddSolidToTree(leaf->right, x, y, wide, tall); + } + else if (x + wide < leaf->x + leaf->wide) + { + // split the right edge + leaf->split = 1; + leaf->splitpos = (short)(x + wide); + + // create 2 new leaves + leaf_t *left = AllocLeaf(); + leaf_t *right = AllocLeaf(); + memset(left, 0, sizeof(leaf_t)); + memset(right, 0, sizeof(leaf_t)); + leaf->left = left; + leaf->right = right; + + left->x = leaf->x; + left->y = leaf->y; + left->wide = (short)(leaf->splitpos - leaf->x); + left->tall = leaf->tall; + + right->x = leaf->splitpos; + right->y = leaf->y; + right->wide = (short)(leaf->wide - left->wide); + right->tall = leaf->tall; + + // split the left leaf by the current rect + AddSolidToTree(leaf->left, x, y, wide, tall); + } + else if (y + tall < leaf->y + leaf->tall) + { + // split the bottom edge + leaf->split = 2; + leaf->splitpos = (short)(y + tall); + + // create 2 new leaves (facing to the east) + leaf_t *left = AllocLeaf(); + leaf_t *right = AllocLeaf(); + memset(left, 0, sizeof(leaf_t)); + memset(right, 0, sizeof(leaf_t)); + leaf->left = left; + leaf->right = right; + + left->x = leaf->x; + left->y = leaf->y; + left->wide = leaf->wide; + left->tall = (short)(leaf->splitpos - leaf->y); + + right->x = leaf->x; + right->y = leaf->splitpos; + right->wide = leaf->wide; + right->tall = (short)(leaf->tall - left->tall); + + // split the left leaf by the current rect + AddSolidToTree(leaf->left, x, y, wide, tall); + } + else + { + // this is the exact same rect! don't draw this leaf + leaf->filled = true; + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fills the panel background, clipping if possible +//----------------------------------------------------------------------------- +void EditablePanel::PaintBackground() +{ + BaseClass::PaintBackground(); + return; + +/* + test code, using a screenspace bsp tree to reduce overdraw in vgui + not yet fully functional + +// test: fill background with obnoxious color to show holes +// surface()->DrawSetColor(Color(255, 0, 0, 255)); +// surface()->DrawFilledRect(0, 0, GetWide(), GetTall()); +// return; + + // reset the leaf memory + g_iNextLeaf = 0; + + leaf_t *headNode = AllocLeaf(); + memset(headNode, 0, sizeof(leaf_t)); + + headNode->wide = (short)GetWide(); + headNode->tall = (short)GetTall(); + + // split the leaf by the first child + for (int i = 0; i < GetChildCount(); i++) + { + Panel *child = GetChild(i); + if (child->IsOpaque()) + { + int x, y, wide, tall; + child->GetBounds(x, y, wide, tall); + + // ignore small children + if (wide + tall < 100) + continue; + + AddSolidToTree(headNode, x, y, wide, tall); + } + } + + // walk the built tree, painting the background + Color col = GetBgColor(); + surface()->DrawSetColor(col); + for (i = 0; i < g_iNextLeaf; i++) + { + leaf_t *leaf = g_Leaves + i; + if (leaf->splitpos || leaf->filled) + continue; + surface()->DrawFilledRect(leaf->x, leaf->y, leaf->x + leaf->wide, leaf->y + leaf->tall); + } +*/ +} + +//----------------------------------------------------------------------------- +// Purpose: Activates the build mode dialog for editing panels. +//----------------------------------------------------------------------------- +void EditablePanel::ActivateBuildMode() +{ + _buildGroup->SetEnabled(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Loads panel settings from a resource file. +//----------------------------------------------------------------------------- +void EditablePanel::LoadControlSettings(const char *resourceName, const char *pathID, KeyValues *pKeyValues, KeyValues *pConditions) +{ +#if defined( DBGFLAG_ASSERT ) && !defined(OSX) && !defined(LINUX) + // Since nobody wants to fix this assert, I'm making it a Msg instead: + // editablepanel.cpp (535) : Resource file "resource\DebugOptionsPanel.res" not found on disk! + // AssertMsg( g_pFullFileSystem->FileExists( resourceName ), CFmtStr( "Resource file \"%s\" not found on disk!", resourceName ).Access() ); + if ( !g_pFullFileSystem->FileExists( resourceName ) ) + { + Msg( "Resource file \"%s\" not found on disk!", resourceName ); + } +#endif + _buildGroup->LoadControlSettings(resourceName, pathID, pKeyValues, pConditions); + ForceSubPanelsToUpdateWithNewDialogVariables(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: registers a file in the list of control settings, so the vgui dialog can choose between them to edit +//----------------------------------------------------------------------------- +void EditablePanel::RegisterControlSettingsFile(const char *resourceName, const char *pathID) +{ + _buildGroup->RegisterControlSettingsFile(resourceName, pathID); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the name of this dialog so it can be saved in the user config area +//----------------------------------------------------------------------------- +void EditablePanel::LoadUserConfig(const char *configName, int dialogID) +{ + KeyValues *data = system()->GetUserConfigFileData(configName, dialogID); + + delete [] m_pszConfigName; + int len = Q_strlen(configName) + 1; + m_pszConfigName = new char[ len ]; + Q_strncpy(m_pszConfigName, configName, len ); + m_iConfigID = dialogID; + + // apply our user config settings (this will recurse through our children) + if (data) + { + ApplyUserConfigSettings(data); + } +} + +//----------------------------------------------------------------------------- +// Purpose: saves all the settings to the document +//----------------------------------------------------------------------------- +void EditablePanel::SaveUserConfig() +{ + if (m_pszConfigName) + { + KeyValues *data = system()->GetUserConfigFileData(m_pszConfigName, m_iConfigID); + + // get our user config settings (this will recurse through our children) + if (data) + { + GetUserConfigSettings(data); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: combines both of the above, LoadControlSettings & LoadUserConfig +//----------------------------------------------------------------------------- +void EditablePanel::LoadControlSettingsAndUserConfig(const char *dialogResourceName, int dialogID) +{ + LoadControlSettings(dialogResourceName); + LoadUserConfig(dialogResourceName, dialogID); +} + +//----------------------------------------------------------------------------- +// Purpose: applies the user config settings to all the children +//----------------------------------------------------------------------------- +void EditablePanel::ApplyUserConfigSettings(KeyValues *userConfig) +{ + for (int i = 0; i < GetChildCount(); i++) + { + Panel *child = GetChild(i); + if (child->HasUserConfigSettings()) + { + const char *name = child->GetName(); + if (name && *name) + { + child->ApplyUserConfigSettings(userConfig->FindKey(name, true)); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: gets all the children's user config settings +//----------------------------------------------------------------------------- +void EditablePanel::GetUserConfigSettings(KeyValues *userConfig) +{ + for (int i = 0; i < GetChildCount(); i++) + { + Panel *child = GetChild(i); + if (child->HasUserConfigSettings()) + { + const char *name = child->GetName(); + if (name && *name) + { + child->GetUserConfigSettings(userConfig->FindKey(name, true)); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Save user config settings +//----------------------------------------------------------------------------- +void EditablePanel::OnClose() +{ + SaveUserConfig(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle information requests +//----------------------------------------------------------------------------- +bool EditablePanel::RequestInfo(KeyValues *data) +{ + if (!stricmp(data->GetName(), "BuildDialog")) + { + // a build dialog is being requested, give it one + // a bit hacky, but this is a case where vgui.dll needs to reach out + data->SetPtr("PanelPtr", new BuildModeDialog( (BuildGroup *)data->GetPtr("BuildGroupPtr"))); + return true; + } + else if (!stricmp(data->GetName(), "ControlFactory")) + { + Panel *newPanel = CreateControlByName(data->GetString("ControlName")); + if (newPanel) + { + data->SetPtr("PanelPtr", newPanel); + return true; + } + } + return BaseClass::RequestInfo(data); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the buildgroup that this panel is part of. +// Input : +// Output : BuildGroup +//----------------------------------------------------------------------------- +BuildGroup *EditablePanel::GetBuildGroup() +{ + return _buildGroup; +} + +//----------------------------------------------------------------------------- +// Purpose: Return a pointer to the nav group +// Output : FocusNavGroup +//----------------------------------------------------------------------------- +FocusNavGroup &EditablePanel::GetFocusNavGroup() +{ + return m_NavGroup; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool EditablePanel::RequestFocusNext(VPANEL panel) +{ + bool bRet = m_NavGroup.RequestFocusNext(panel); + if ( IsPC() && !bRet && IsConsoleStylePanel() ) + { + NavigateDown(); + } + return bRet; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool EditablePanel::RequestFocusPrev(VPANEL panel) +{ + bool bRet = m_NavGroup.RequestFocusPrev(panel); + if ( IsPC() && !bRet && IsConsoleStylePanel() ) + { + NavigateUp(); + } + return bRet; +} + +//----------------------------------------------------------------------------- +// Purpose: Delegates focus to a sub panel +// Input : direction - the direction in which focus travelled to arrive at this panel; forward = 1, back = -1 +//----------------------------------------------------------------------------- +void EditablePanel::RequestFocus(int direction) +{ + // we must be a sub panel for this to be called + // delegate focus + if (direction == 1) + { + RequestFocusNext(NULL); + } + else if (direction == -1) + { + RequestFocusPrev(NULL); + } + else + { + BaseClass::RequestFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Pass the focus down onto the last used panel +//----------------------------------------------------------------------------- +void EditablePanel::OnSetFocus() +{ + Panel *focus = m_NavGroup.GetCurrentFocus(); + if (focus && focus != this) + { + focus->RequestFocus(); + } + else + { + focus = m_NavGroup.GetDefaultPanel(); + if (focus) + { + focus->RequestFocus(); + focus->OnSetFocus(); + } + } + + BaseClass::OnSetFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the resource file is loaded to set up the panel state +// Input : *inResourceData - +//----------------------------------------------------------------------------- +void EditablePanel::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + + _buildGroup->ApplySettings(inResourceData); + + m_bShouldSkipAutoResize = inResourceData->GetBool( "skip_autoresize", false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Update focus info for navigation +//----------------------------------------------------------------------------- +void EditablePanel::OnRequestFocus(VPANEL subFocus, VPANEL defaultPanel) +{ + if (!ipanel()->IsPopup(subFocus)) + { + defaultPanel = m_NavGroup.SetCurrentFocus(subFocus, defaultPanel); + } + BaseClass::OnRequestFocus(GetVPanel(), defaultPanel); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the panel that currently has keyfocus +//----------------------------------------------------------------------------- +VPANEL EditablePanel::GetCurrentKeyFocus() +{ + Panel *focus = m_NavGroup.GetCurrentFocus(); + if (focus == this) + return NULL; + + if (focus) + { + if (focus->IsPopup()) + return BaseClass::GetCurrentKeyFocus(); + + // chain down the editpanel hierarchy + VPANEL subFocus = focus->GetCurrentKeyFocus(); + if (subFocus) + return subFocus; + + // hit a leaf panel, return that + return focus->GetVPanel(); + } + return BaseClass::GetCurrentKeyFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the panel with the specified hotkey +//----------------------------------------------------------------------------- +Panel *EditablePanel::HasHotkey(wchar_t key) +{ + if( !IsVisible() || !IsEnabled()) // not visible, so can't respond to a hot key + { + return NULL; + } + + for (int i = 0; i < GetChildCount(); i++) + { + Panel *hot = GetChild(i)->HasHotkey(key); + if (hot && hot->IsVisible() && hot->IsEnabled()) + { + return hot; + } + } + + return NULL; + +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to setting enabled state of control +//----------------------------------------------------------------------------- +void EditablePanel::SetControlEnabled(const char *controlName, bool enabled) +{ + Panel *control = FindChildByName(controlName); + if (control) + { + control->SetEnabled(enabled); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to setting visibility state of control +//----------------------------------------------------------------------------- +void EditablePanel::SetControlVisible(const char *controlName, bool visible, bool bRecurseDown /*= false*/ ) +{ + Panel *control = FindChildByName(controlName, bRecurseDown); + if (control) + { + control->SetVisible(visible); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to set data in child controls +//----------------------------------------------------------------------------- +void EditablePanel::SetControlString(const char *controlName, const char *string) +{ + Panel *control = FindChildByName(controlName); + if (control) + { + if (string[0] == '#') + { + const wchar_t *wszText = g_pVGuiLocalize->Find(string); + if (wszText) + { + PostMessage(control, new KeyValues("SetText", "text", wszText)); + } + } + else + { + PostMessage(control, new KeyValues("SetText", "text", string)); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to set data in child controls +//----------------------------------------------------------------------------- +void EditablePanel::SetControlString(const char *controlName, const wchar_t *string) +{ + Panel *control = FindChildByName(controlName); + if (control) + { + PostMessage(control, new KeyValues("SetText", "text", string)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to set data in child controls +//----------------------------------------------------------------------------- +void EditablePanel::SetControlInt(const char *controlName, int state) +{ + Panel *control = FindChildByName(controlName); + if (control) + { + PostMessage(control, new KeyValues("SetState", "state", state)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to get data in child controls +//----------------------------------------------------------------------------- +int EditablePanel::GetControlInt(const char *controlName, int defaultState) +{ + Panel *control = FindChildByName(controlName); + if (control) + { + KeyValues *data = new KeyValues("GetState"); + if (control->RequestInfo(data)) + { + int state = data->GetInt("state", defaultState); + data->deleteThis(); + return state; + } + } + return defaultState; +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to get data in child controls +//----------------------------------------------------------------------------- +const char *EditablePanel::GetControlString(const char *controlName, const char *defaultString) +{ + static char buf[512]; + GetControlString(controlName, buf, sizeof(buf) - 1, defaultString); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut function to get data in child controls +//----------------------------------------------------------------------------- +void EditablePanel::GetControlString(const char *controlName, char *buf, int bufSize, const char *defaultString) +{ + Panel *control = FindChildByName(controlName); + KeyValues *data = new KeyValues("GetText"); + if (control && control->RequestInfo(data)) + { + Q_strncpy(buf, data->GetString("text", defaultString), bufSize); + } + else + { + // no value found, copy in default text + Q_strncpy(buf, defaultString, bufSize); + } + + // ensure null termination of string + buf[bufSize - 1] = 0; + + // free + data->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: localization variables (used in constructing UI strings) +//----------------------------------------------------------------------------- +void EditablePanel::SetDialogVariable(const char *varName, const char *value) +{ + GetDialogVariables()->SetString(varName, value); + ForceSubPanelsToUpdateWithNewDialogVariables(); +} + +//----------------------------------------------------------------------------- +// Purpose: localization variables (used in constructing UI strings) +//----------------------------------------------------------------------------- +void EditablePanel::SetDialogVariable(const char *varName, const wchar_t *value) +{ + GetDialogVariables()->SetWString(varName, value); + ForceSubPanelsToUpdateWithNewDialogVariables(); +} + +//----------------------------------------------------------------------------- +// Purpose: localization variables (used in constructing UI strings) +//----------------------------------------------------------------------------- +void EditablePanel::SetDialogVariable(const char *varName, int value) +{ + GetDialogVariables()->SetInt(varName, value); + ForceSubPanelsToUpdateWithNewDialogVariables(); +} + +//----------------------------------------------------------------------------- +// Purpose: localization variables (used in constructing UI strings) +//----------------------------------------------------------------------------- +void EditablePanel::SetDialogVariable(const char *varName, float value) +{ + GetDialogVariables()->SetFloat(varName, value); + ForceSubPanelsToUpdateWithNewDialogVariables(); +} + +//----------------------------------------------------------------------------- +// Purpose: redraws child panels with new localization vars +//----------------------------------------------------------------------------- +void EditablePanel::ForceSubPanelsToUpdateWithNewDialogVariables() +{ + if (m_pDialogVariables) + { + ipanel()->SendMessage(GetVPanel(), m_pDialogVariables, GetVPanel()); + for (int i = 0; i < ipanel()->GetChildCount(GetVPanel()); i++) + { + ipanel()->SendMessage(ipanel()->GetChild(GetVPanel(), i), m_pDialogVariables, GetVPanel()); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: lazy creation of localization vars object +//----------------------------------------------------------------------------- +KeyValues *EditablePanel::GetDialogVariables() +{ + if (m_pDialogVariables) + return m_pDialogVariables; + + m_pDialogVariables = new KeyValues("DialogVariables"); + return m_pDialogVariables; +} + +//----------------------------------------------------------------------------- +// Purpose: Virtual factory for control creation +//----------------------------------------------------------------------------- +Panel *EditablePanel::CreateControlByName(const char *controlName) +{ + Panel *fromFactory = CBuildFactoryHelper::InstancePanel( controlName ); + if ( fromFactory ) + { + return fromFactory; + } + + return NULL; +} diff --git a/vgui2/vgui_controls/ExpandButton.cpp b/vgui2/vgui_controls/ExpandButton.cpp new file mode 100644 index 0000000..a4489d0 --- /dev/null +++ b/vgui2/vgui_controls/ExpandButton.cpp @@ -0,0 +1,114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include <stdarg.h> +#include <stdio.h> + +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <KeyValues.h> + +#include <vgui_controls/Image.h> +#include <vgui_controls/ExpandButton.h> +#include <vgui_controls/TextImage.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( ExpandButton ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ExpandButton::ExpandButton( Panel *parent, const char *panelName ) : ToggleButton( parent, panelName, "" ) +{ + m_bExpandable = true; + m_hFont = INVALID_FONT; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +ExpandButton::~ExpandButton() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpandButton::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + m_Color = GetSchemeColor( "ExpandButton.Color", pScheme ); + m_hFont = pScheme->GetFont("Marlett", IsProportional() ); + + // don't draw a background + SetPaintBackgroundEnabled(false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +IBorder *ExpandButton::GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) +{ + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Expand the button +//----------------------------------------------------------------------------- +void ExpandButton::SetSelected(bool state) +{ + if ( m_bExpandable && ( state != IsSelected() ) ) + { + // send a message saying we've been checked + KeyValues *msg = new KeyValues("Expanded", "state", (int)state); + PostActionSignal(msg); + + BaseClass::SetSelected(state); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: sets whether or not the state of the check can be changed +//----------------------------------------------------------------------------- +void ExpandButton::SetExpandable(bool state) +{ + m_bExpandable = state; + Repaint(); +} + + +void ExpandButton::Paint() +{ + surface()->DrawSetTextFont( m_hFont ); + + wchar_t code = IsSelected( ) ? L'6' : L'4'; + wchar_t pString[2] = { code, 0 }; + + // draw selected check + int tw, th, w, h; + GetSize( w, h ); + surface()->GetTextSize( m_hFont, pString, tw, th ); + surface()->DrawSetTextColor( m_Color ); + surface()->DrawSetTextPos( ( w - tw ) / 2, ( h - th ) / 2 ); + surface()->DrawUnicodeChar( code ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpandButton::OnExpanded(Panel *panel) +{ +} diff --git a/vgui2/vgui_controls/FileOpenDialog.cpp b/vgui2/vgui_controls/FileOpenDialog.cpp new file mode 100644 index 0000000..ebe5eb0 --- /dev/null +++ b/vgui2/vgui_controls/FileOpenDialog.cpp @@ -0,0 +1,1701 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Implementation of vgui generic open file dialog +// +// $NoKeywords: $ +//===========================================================================// + + +#define PROTECTED_THINGS_DISABLE + +#if !defined( _X360 ) && defined( WIN32 ) +#include "winlite.h" +#include <shellapi.h> +#elif defined( POSIX ) +#include <stdlib.h> +#define _stat stat +#define _wcsnicmp wcsncmp +#elif defined( _X360 ) +#else +#error +#endif + +#undef GetCurrentDirectory +#include "filesystem.h" +#include <sys/stat.h> + +#include "tier1/utldict.h" +#include "tier1/utlstring.h" + +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <KeyValues.h> +#include <vgui/IVGui.h> +#include <vgui/ILocalize.h> +#include <vgui/IInput.h> + +#include <vgui_controls/FileOpenDialog.h> + +#include <vgui_controls/Button.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/InputDialog.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/ListPanel.h> +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/ImageList.h> +#include <vgui_controls/MenuItem.h> +#include <vgui_controls/Tooltip.h> + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#undef GetCurrentDirectory +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +static int s_nLastSortColumn = 0; + +static int ListFileNameSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + NOTE_UNUSED( pPanel ); + + bool dir1 = item1.kv->GetInt("directory") == 1; + bool dir2 = item2.kv->GetInt("directory") == 1; + + // if they're both not directories of files, return if dir1 is a directory (before files) + if (dir1 != dir2) + { + return dir1 ? -1 : 1; + } + + const char *string1 = item1.kv->GetString("text"); + const char *string2 = item2.kv->GetString("text"); + + // YWB: Mimic windows behavior where filenames starting with numbers are sorted based on numeric part + int num1 = Q_atoi( string1 ); + int num2 = Q_atoi( string2 ); + + if ( num1 != 0 && + num2 != 0 ) + { + if ( num1 < num2 ) + return -1; + else if ( num1 > num2 ) + return 1; + } + + // Push numbers before everything else + if ( num1 != 0 ) + { + return -1; + } + + // Push numbers before everything else + if ( num2 != 0 ) + { + return 1; + } + + return Q_stricmp( string1, string2 ); +} + +static int ListBaseStringSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName ) +{ + bool dir1 = item1.kv->GetInt("directory") == 1; + bool dir2 = item2.kv->GetInt("directory") == 1; + + // if they're both not directories of files, return if dir1 is a directory (before files) + if (dir1 != dir2) + { + return -1; + } + + const char *string1 = item1.kv->GetString(fieldName); + const char *string2 = item2.kv->GetString(fieldName); + int cval = Q_stricmp(string1, string2); + if ( cval == 0 ) + { + // Use filename to break ties + return ListFileNameSortFunc( pPanel, item1, item2 ); + } + + return cval; +} + +static int ListBaseIntegerSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName ) +{ + bool dir1 = item1.kv->GetInt("directory") == 1; + bool dir2 = item2.kv->GetInt("directory") == 1; + + // if they're both not directories of files, return if dir1 is a directory (before files) + if (dir1 != dir2) + { + return -1; + } + + int i1 = item1.kv->GetInt(fieldName); + int i2 = item2.kv->GetInt(fieldName); + if ( i1 == i2 ) + { + // Use filename to break ties + return ListFileNameSortFunc( pPanel, item1, item2 ); + } + + return ( i1 < i2 ) ? -1 : 1; +} + +static int ListBaseInteger64SortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *lowfield, char const *highfield ) +{ + bool dir1 = item1.kv->GetInt("directory") == 1; + bool dir2 = item2.kv->GetInt("directory") == 1; + + // if they're both not directories of files, return if dir1 is a directory (before files) + if (dir1 != dir2) + { + return dir1 ? -1 : 1; + } + + uint32 l1 = item1.kv->GetInt(lowfield); + uint32 h1 = item1.kv->GetInt(highfield); + uint32 l2 = item2.kv->GetInt(lowfield); + uint32 h2 = item2.kv->GetInt(highfield); + uint64 i1 = (uint64)( (uint64)l1 | ( (uint64)h1 << 32 ) ); + uint64 i2 = (uint64)( (uint64)l2 | ( (uint64)h2 << 32 ) ); + + if ( i1 == i2 ) + { + // Use filename to break ties + return ListFileNameSortFunc( pPanel, item1, item2 ); + } + + return ( i1 < i2 ) ? -1 : 1; +} + + +static int ListFileSizeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + return ListBaseIntegerSortFunc( pPanel, item1, item2, "filesizeint" ); +} + +static int ListFileModifiedSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + // NOTE: Backward order to get most recent files first + return ListBaseInteger64SortFunc( pPanel, item2, item1, "modifiedint_low", "modifiedint_high" ); +} +static int ListFileCreatedSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + // NOTE: Backward order to get most recent files first + return ListBaseInteger64SortFunc( pPanel, item2, item1, "createdint_low", "createdint_high" ); +} +static int ListFileAttributesSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + return ListBaseStringSortFunc( pPanel, item1, item2, "attributes" ); +} +static int ListFileTypeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + return ListBaseStringSortFunc( pPanel, item1, item2, "type" ); +} + + + +namespace vgui +{ + +class FileCompletionMenu : public Menu +{ +public: + FileCompletionMenu(Panel *parent, const char *panelName) : Menu(parent, panelName) + { + } + + // override it so it doesn't request focus + virtual void SetVisible(bool state) + { + Panel::SetVisible(state); + } + +}; + + +//----------------------------------------------------------------------------- +// File completion edit text entry +//----------------------------------------------------------------------------- +class FileCompletionEdit : public TextEntry +{ + DECLARE_CLASS_SIMPLE( FileCompletionEdit, TextEntry ); + +public: + FileCompletionEdit(Panel *parent); + ~FileCompletionEdit(); + + int AddItem(const char *itemText, KeyValues *userData); + int AddItem(const wchar_t *itemText, KeyValues *userData); + void DeleteAllItems(); + int GetItemCount(); + int GetItemIDFromRow(int row); + int GetRowFromItemID(int itemID); + virtual void PerformLayout(); + void OnSetText(const wchar_t *newtext); + virtual void OnKillFocus(); + void HideMenu(void); + void ShowMenu(void); + virtual void OnKeyCodeTyped(KeyCode code); + MESSAGE_FUNC_INT( OnMenuItemHighlight, "MenuItemHighlight", itemID ); + +private: + FileCompletionMenu *m_pDropDown; +}; + + + +FileCompletionEdit::FileCompletionEdit(Panel *parent) : TextEntry(parent, NULL) +{ + m_pDropDown = new FileCompletionMenu(this, NULL); + m_pDropDown->AddActionSignalTarget(this); +} + +FileCompletionEdit::~FileCompletionEdit() +{ + delete m_pDropDown; +} + +int FileCompletionEdit::AddItem(const char *itemText, KeyValues *userData) +{ + // when the menu item is selected it will send the custom message "SetText" + return m_pDropDown->AddMenuItem(itemText, new KeyValues("SetText", "text", itemText), this, userData); +} +int FileCompletionEdit::AddItem(const wchar_t *itemText, KeyValues *userData) +{ + // add the element to the menu + // when the menu item is selected it will send the custom message "SetText" + KeyValues *kv = new KeyValues("SetText"); + kv->SetWString("text", itemText); + + // get an ansi version for the menuitem name + char ansi[128]; + g_pVGuiLocalize->ConvertUnicodeToANSI(itemText, ansi, sizeof(ansi)); + return m_pDropDown->AddMenuItem(ansi, kv, this, userData); +} + +void FileCompletionEdit::DeleteAllItems() +{ + m_pDropDown->DeleteAllItems(); +} + +int FileCompletionEdit::GetItemCount() +{ + return m_pDropDown->GetItemCount(); +} + +int FileCompletionEdit::GetItemIDFromRow(int row) +{ + // valid from [0, GetItemCount) + return m_pDropDown->GetMenuID(row); +} + +int FileCompletionEdit::GetRowFromItemID(int itemID) +{ + int i; + for (i=0;i<GetItemCount();i++) + { + if (m_pDropDown->GetMenuID(i) == itemID) + return i; + } + return -1; +} + +void FileCompletionEdit::PerformLayout() +{ + BaseClass::PerformLayout(); + + m_pDropDown->PositionRelativeToPanel( this, Menu::DOWN, 0 ); + + // reset the width of the drop down menu to be the width of this edit box + m_pDropDown->SetFixedWidth(GetWide()); + m_pDropDown->ForceCalculateWidth(); +} + +void FileCompletionEdit::OnSetText(const wchar_t *newtext) +{ + // see if the combobox text has changed, and if so, post a message detailing the new text + wchar_t wbuf[255]; + GetText( wbuf, 254 ); + + if ( wcscmp(wbuf, newtext) ) + { + // text has changed + SetText(newtext); + + // fire off that things have changed + PostActionSignal(new KeyValues("TextChanged", "text", newtext)); + Repaint(); + } +} + +void FileCompletionEdit::OnKillFocus() +{ + HideMenu(); + BaseClass::OnKillFocus(); +} + +void FileCompletionEdit::HideMenu(void) +{ + // hide the menu + m_pDropDown->SetVisible(false); +} + +void FileCompletionEdit::ShowMenu(void) +{ + // reset the dropdown's position + m_pDropDown->InvalidateLayout(); + + // make sure we're at the top of the draw order (and therefore our children as well) + // this important to make sure the menu will be drawn in the foreground + MoveToFront(); + + // reset the drop down + m_pDropDown->ClearCurrentlyHighlightedItem(); + + // limit it to only 6 + if (m_pDropDown->GetItemCount() > 6) + { + m_pDropDown->SetNumberOfVisibleItems(6); + } + else + { + m_pDropDown->SetNumberOfVisibleItems(m_pDropDown->GetItemCount()); + } + // show the menu + m_pDropDown->SetVisible(true); + + Repaint(); +} + +void FileCompletionEdit::OnKeyCodeTyped(KeyCode code) +{ + if ( code == KEY_DOWN ) + { + if (m_pDropDown->GetItemCount() > 0) + { + int menuID = m_pDropDown->GetCurrentlyHighlightedItem(); + int row = -1; + if ( menuID == -1 ) + { + row = m_pDropDown->GetItemCount() - 1; + } + else + { + row = GetRowFromItemID(menuID); + } + row++; + if (row == m_pDropDown->GetItemCount()) + { + row = 0; + } + menuID = GetItemIDFromRow(row); + m_pDropDown->SetCurrentlyHighlightedItem(menuID); + return; + } + } + else if ( code == KEY_UP ) + { + if (m_pDropDown->GetItemCount() > 0) + { + int menuID = m_pDropDown->GetCurrentlyHighlightedItem(); + int row = -1; + if ( menuID == -1 ) + { + row = 0; + } + else + { + row = GetRowFromItemID(menuID); + } + row--; + if ( row < 0 ) + { + row = m_pDropDown->GetItemCount() - 1; + } + menuID = GetItemIDFromRow(row); + m_pDropDown->SetCurrentlyHighlightedItem(menuID); + return; + } + } + else if ( code == KEY_ESCAPE ) + { + if ( m_pDropDown->IsVisible() ) + { + HideMenu(); + return; + } + } + BaseClass::OnKeyCodeTyped(code); + return; +} + +void FileCompletionEdit::OnMenuItemHighlight( int itemID ) +{ + char wbuf[80]; + if ( m_pDropDown->IsValidMenuID(itemID) ) + { + m_pDropDown->GetMenuItem(itemID)->GetText(wbuf, 80); + } + else + { + wbuf[0] = 0; + } + SetText(wbuf); + RequestFocus(); + GotoTextEnd(); +} + + +} // namespace vgui + + +//----------------------------------------------------------------------------- +// Dictionary of start dir contexts +//----------------------------------------------------------------------------- +static CUtlDict< CUtlString, unsigned short > s_StartDirContexts; + +struct ColumnInfo_t +{ + char const *columnName; + char const *columnText; + int startingWidth; + int minWidth; + int maxWidth; + int flags; + SortFunc *pfnSort; + Label::Alignment alignment; +}; + +static ColumnInfo_t g_ColInfo[] = +{ + { "text", "#FileOpenDialog_Col_Name", 175, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileNameSortFunc , Label::a_west }, + { "filesize", "#FileOpenDialog_Col_Size", 100, 20, 10000, 0, &ListFileSizeSortFunc , Label::a_east }, + { "type", "#FileOpenDialog_Col_Type", 150, 20, 10000, 0, &ListFileTypeSortFunc , Label::a_west }, + { "modified", "#FileOpenDialog_Col_DateModified", 125, 20, 10000, 0, &ListFileModifiedSortFunc , Label::a_west }, +// { "created", "#FileOpenDialog_Col_DateCreated", 125, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileCreatedSortFunc , Label::a_west }, + { "attributes", "#FileOpenDialog_Col_Attributes", 50, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileAttributesSortFunc , Label::a_west }, +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +FileOpenDialog::FileOpenDialog(Panel *parent, const char *title, bool bOpenOnly, KeyValues* pContextKeyValues ) : + Frame( parent, "FileOpenDialog" ) +{ + m_DialogType = bOpenOnly ? FOD_OPEN : FOD_SAVE; + Init( title, pContextKeyValues ); +} + + +FileOpenDialog::FileOpenDialog( Panel *parent, const char *title, FileOpenDialogType_t type, KeyValues *pContextKeyValues ) : + Frame( parent, "FileOpenDialog" ) +{ + m_DialogType = type; + Init( title, pContextKeyValues ); +} + +void FileOpenDialog::Init( const char *title, KeyValues *pContextKeyValues ) +{ + m_bFileSelected = false; + SetTitle(title, true); + SetMinimizeButtonVisible(false); + +#ifdef POSIX + Q_strncpy(m_szLastPath, "/", sizeof( m_szLastPath ) ); +#else + Q_strncpy(m_szLastPath, "c:\\", sizeof( m_szLastPath ) ); +#endif + + m_pContextKeyValues = pContextKeyValues; + + // Get the list of available drives and put them in a menu here. + // Start with the directory we are in. + m_pFullPathEdit = new ComboBox(this, "FullPathEdit", 6, false); + m_pFullPathEdit->GetTooltip()->SetTooltipFormatToSingleLine(); + + // list panel + m_pFileList = new ListPanel(this, "FileList"); + for ( int i = 0; i < ARRAYSIZE( g_ColInfo ); ++i ) + { + const ColumnInfo_t& info = g_ColInfo[ i ]; + + m_pFileList->AddColumnHeader( i, info.columnName, info.columnText, info.startingWidth, info.minWidth, info.maxWidth, info.flags ); + m_pFileList->SetSortFunc( i, info.pfnSort ); + m_pFileList->SetColumnTextAlignment( i, info.alignment ); + } + + m_pFileList->SetSortColumn( s_nLastSortColumn ); + m_pFileList->SetMultiselectEnabled( false ); + + // file name edit box + m_pFileNameEdit = new FileCompletionEdit(this); + m_pFileNameEdit->AddActionSignalTarget(this); + + m_pFileTypeCombo = new ComboBox( this, "FileTypeCombo", 6, false ); + + switch ( m_DialogType ) + { + case FOD_OPEN: + m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Open", this ); + break; + case FOD_SAVE: + m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Save", this ); + break; + case FOD_SELECT_DIRECTORY: + m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Select", this ); + m_pFileTypeCombo->SetVisible( false ); + break; + } + + m_pCancelButton = new Button( this, "CancelButton", "#FileOpenDialog_Cancel", this ); + m_pFolderUpButton = new Button( this, "FolderUpButton", "", this ); + m_pFolderUpButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_Up" ); + m_pNewFolderButton = new Button( this, "NewFolderButton", "", this ); + m_pNewFolderButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_NewFolder" ); + m_pOpenInExplorerButton = new Button( this, "OpenInExplorerButton", "", this ); + +#if defined ( OSX ) + m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInFinderButton" ); +#elif defined ( POSIX ) + m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInDesktopManagerButton" ); +#else // Assume Windows / Explorer + m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInExplorerButton" ); +#endif + + Label *lookIn = new Label( this, "LookInLabel", "#FileOpenDialog_Look_in" ); + Label *fileName = new Label( this, "FileNameLabel", + ( m_DialogType != FOD_SELECT_DIRECTORY ) ? "#FileOpenDialog_File_name" : "#FileOpenDialog_Directory_Name" ); + + m_pFolderIcon = new ImagePanel(NULL, "FolderIcon"); + + // set up the control's initial positions + SetSize( 600, 260 ); + + int nFileEditLeftSide = ( m_DialogType != FOD_SELECT_DIRECTORY ) ? 84 : 100; + int nFileNameWidth = ( m_DialogType != FOD_SELECT_DIRECTORY ) ? 72 : 82; + + m_pFullPathEdit->SetBounds(67, 32, 310, 24); + m_pFolderUpButton->SetBounds(362, 32, 24, 24); + m_pNewFolderButton->SetBounds(392, 32, 24, 24); + m_pOpenInExplorerButton->SetBounds(332, 32, 24, 24); + m_pFileList->SetBounds(10, 60, 406, 130); + m_pFileNameEdit->SetBounds( nFileEditLeftSide, 194, 238, 24); + m_pFileTypeCombo->SetBounds( nFileEditLeftSide, 224, 238, 24); + m_pOpenButton->SetBounds(336, 194, 74, 24); + m_pCancelButton->SetBounds(336, 224, 74, 24); + lookIn->SetBounds(10, 32, 55, 24); + fileName->SetBounds(10, 194, nFileNameWidth, 24); + + // set autolayout parameters + m_pFullPathEdit->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_RIGHT, 67, 32, -100, 0 ); + m_pFileNameEdit->SetAutoResize( Panel::PIN_BOTTOMLEFT, Panel::AUTORESIZE_RIGHT, nFileEditLeftSide, -42, -104, 0 ); + m_pFileTypeCombo->SetAutoResize( Panel::PIN_BOTTOMLEFT, Panel::AUTORESIZE_RIGHT, nFileEditLeftSide, -12, -104, 0 ); + m_pFileList->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_DOWNANDRIGHT, 10, 60, -10, -70 ); + + m_pFolderUpButton->SetPinCorner( Panel::PIN_TOPRIGHT, -40, 32 ); + m_pNewFolderButton->SetPinCorner( Panel::PIN_TOPRIGHT, -10, 32 ); + m_pOpenInExplorerButton->SetPinCorner( Panel::PIN_TOPRIGHT, -70, 32 ); + m_pOpenButton->SetPinCorner( Panel::PIN_BOTTOMRIGHT, -16, -42 ); + m_pCancelButton->SetPinCorner( Panel::PIN_BOTTOMRIGHT, -16, -12 ); + lookIn->SetPinCorner( Panel::PIN_TOPLEFT, 10, 32 ); + fileName->SetPinCorner( Panel::PIN_BOTTOMLEFT, 10, -42 ); + + // label settings + lookIn->SetContentAlignment(Label::a_west); + fileName->SetContentAlignment(Label::a_west); + + lookIn->SetAssociatedControl(m_pFullPathEdit); + fileName->SetAssociatedControl(m_pFileNameEdit); + + if ( m_DialogType != FOD_SELECT_DIRECTORY ) + { + Label *fileType = new Label(this, "FileTypeLabel", "#FileOpenDialog_File_type"); + fileType->SetBounds(10, 224, 72, 24); + fileType->SetPinCorner( Panel::PIN_BOTTOMLEFT, 10, -12 ); + fileType->SetContentAlignment(Label::a_west); + fileType->SetAssociatedControl( m_pFileTypeCombo ); + } + + // set tab positions + GetFocusNavGroup().SetDefaultButton(m_pOpenButton); + + m_pFileNameEdit->SetTabPosition(1); + m_pFileTypeCombo->SetTabPosition(2); + m_pOpenButton->SetTabPosition(3); + m_pCancelButton->SetTabPosition(4); + m_pFullPathEdit->SetTabPosition(5); + m_pFileList->SetTabPosition(6); + + m_pOpenButton->SetCommand( ( m_DialogType != FOD_SELECT_DIRECTORY ) ? new KeyValues( "OnOpen" ) : new KeyValues( "SelectFolder" ) ); + m_pCancelButton->SetCommand( "CloseModal" ); + m_pFolderUpButton->SetCommand( new KeyValues( "OnFolderUp" ) ); + m_pNewFolderButton->SetCommand( new KeyValues( "OnNewFolder" ) ); + m_pOpenInExplorerButton->SetCommand( new KeyValues( "OpenInExplorer" ) ); + + SetSize( 600, 384 ); + + m_nStartDirContext = s_StartDirContexts.InvalidIndex(); + + // Set our starting path to the current directory + char pLocalPath[255]; + g_pFullFileSystem->GetCurrentDirectory( pLocalPath , 255 ); + if ( !pLocalPath[0] || ( IsOSX() && V_strlen(pLocalPath) <= 2 ) ) + { + const char *pszHomeDir = getenv( "HOME" ); + V_strcpy_safe( pLocalPath, pszHomeDir ); + } + + SetStartDirectory( pLocalPath ); + + // Because these call through virtual functions, we can't issue them in the constructor, so we post a message to ourselves instead!! + PostMessage( GetVPanel(), new KeyValues( "PopulateFileList" ) ); + PostMessage( GetVPanel(), new KeyValues( "PopulateDriveList" ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +FileOpenDialog::~FileOpenDialog() +{ + s_nLastSortColumn = m_pFileList->GetSortColumn(); + if ( m_pContextKeyValues ) + { + m_pContextKeyValues->deleteThis(); + m_pContextKeyValues = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Apply scheme settings +//----------------------------------------------------------------------------- +void FileOpenDialog::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + m_pFolderIcon->SetImage(scheme()->GetImage("resource/icon_folder", false)); + m_pFolderUpButton->AddImage(scheme()->GetImage("resource/icon_folderup", false), -3); + m_pNewFolderButton->AddImage( scheme()->GetImage("resource/icon_newfolder", false), -3 ); + m_pOpenInExplorerButton->AddImage( scheme()->GetImage("resource/icon_play_once", false), -3 ); + + ImageList *imageList = new ImageList(false); + imageList->AddImage(scheme()->GetImage("resource/icon_file", false)); + imageList->AddImage(scheme()->GetImage("resource/icon_folder", false)); + imageList->AddImage(scheme()->GetImage("resource/icon_folder_selected", false)); + + m_pFileList->SetImageList(imageList, true); +} + + +//----------------------------------------------------------------------------- +// Prevent default button ('select') from getting triggered +// when selecting directories. Instead, open the directory +//----------------------------------------------------------------------------- +void FileOpenDialog::OnKeyCodeTyped(KeyCode code) +{ + if ( m_DialogType == FOD_SELECT_DIRECTORY && code == KEY_ENTER ) + { + OnOpen(); + } + else + { + BaseClass::OnKeyCodeTyped( code ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void FileOpenDialog::PopulateDriveList() +{ + char fullpath[MAX_PATH * 4]; + char subDirPath[MAX_PATH * 4]; + GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH); + Q_strncpy(subDirPath, fullpath, sizeof( subDirPath ) ); + + m_pFullPathEdit->DeleteAllItems(); + +#ifdef WIN32 + // populate the drive list + char buf[512]; + int len = system()->GetAvailableDrives(buf, 512); + char *pBuf = buf; + for (int i=0; i < len / 4; i++) + { + m_pFullPathEdit->AddItem(pBuf, NULL); + + // is this our drive - add all subdirectories + if (!_strnicmp(pBuf, fullpath, 2)) + { + int indent = 0; + char *pData = fullpath; + while (*pData) + { + if ( *pData == CORRECT_PATH_SEPARATOR ) + { + if (indent > 0) + { + memset(subDirPath, ' ', indent); + memcpy(subDirPath+indent, fullpath, pData-fullpath); + subDirPath[indent+pData-fullpath] = 0; + + m_pFullPathEdit->AddItem(subDirPath, NULL); + } + indent += 2; + } + pData++; + } + } + pBuf += 4; + } +#else + m_pFullPathEdit->AddItem("/", NULL); + + char *pData = fullpath; + int indent = 0; + while (*pData) + { + if (*pData == '/' && ( pData[1] != '\0' ) ) + { + if (indent > 0) + { + memset(subDirPath, ' ', indent); + memcpy(subDirPath+indent, fullpath, pData-fullpath); + subDirPath[indent+pData-fullpath] = 0; + + m_pFullPathEdit->AddItem(subDirPath, NULL); + } + indent += 2; + } + pData++; + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Delete self on close +//----------------------------------------------------------------------------- +void FileOpenDialog::OnClose() +{ + s_nLastSortColumn = m_pFileList->GetSortColumn(); + if ( !m_bFileSelected ) + { + KeyValues *pKeyValues = new KeyValues( "FileSelectionCancelled" ); + PostActionSignal( pKeyValues ); + m_bFileSelected = true; + } + + m_pFileNameEdit->SetText(""); + m_pFileNameEdit->HideMenu(); + + if ( vgui::input()->GetAppModalSurface() == GetVPanel() ) + { + input()->SetAppModalSurface(NULL); + } + + BaseClass::OnClose(); +} + +void FileOpenDialog::OnFolderUp() +{ + MoveUpFolder(); + OnOpen(); +} + +void FileOpenDialog::OnInputCompleted( KeyValues *data ) +{ + if ( m_hInputDialog.Get() ) + { + delete m_hInputDialog.Get(); + } + + input()->SetAppModalSurface( m_SaveModal ); + m_SaveModal = 0; + + NewFolder( data->GetString( "text" ) ); + OnOpen(); +} + +void FileOpenDialog::OnInputCanceled() +{ + input()->SetAppModalSurface( m_SaveModal ); + m_SaveModal = 0; +} + +void FileOpenDialog::OnNewFolder() +{ + if ( m_hInputDialog.Get() ) + delete m_hInputDialog.Get(); + + m_hInputDialog = new InputDialog( this, "#FileOpenDialog_NewFolder_InputTitle", "#FileOpenDialog_NewFolderPrompt", "#FileOpenDialog_NewFolder_DefaultName" ); + if ( m_hInputDialog.Get() ) + { + m_SaveModal = input()->GetAppModalSurface(); + + KeyValues *pContextKeyValues = new KeyValues( "NewFolder" ); + m_hInputDialog->SetSmallCaption( true ); + m_hInputDialog->SetMultiline( false ); + m_hInputDialog->DoModal( pContextKeyValues ); + } +} + + +//----------------------------------------------------------------------------- +// Opens the current file/folder in explorer +//----------------------------------------------------------------------------- +void FileOpenDialog::OnOpenInExplorer() +{ + char pCurrentDirectory[MAX_PATH]; + GetCurrentDirectory( pCurrentDirectory, sizeof(pCurrentDirectory) ); +#if !defined( _X360 ) && defined( WIN32 ) + ShellExecute( NULL, NULL, pCurrentDirectory, NULL, NULL, SW_SHOWNORMAL ); +#elif defined( OSX ) + char szCmd[ MAX_PATH * 2]; + Q_snprintf( szCmd, sizeof(szCmd), "/usr/bin/open \"%s\"", pCurrentDirectory ); + ::system( szCmd ); +#elif defined( LINUX ) + char szCmd[ MAX_PATH * 2 ]; + Q_snprintf( szCmd, sizeof(szCmd), "xdg-open \"%s\" &", pCurrentDirectory ); + ::system( szCmd ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Handle for button commands +//----------------------------------------------------------------------------- +void FileOpenDialog::OnCommand(const char *command) +{ + if (!stricmp(command, "Cancel")) + { + Close(); + } + else + { + BaseClass::OnCommand(command); + } +} + + +//----------------------------------------------------------------------------- +// Sets the start directory context (and resets the start directory in the process) +//----------------------------------------------------------------------------- +void FileOpenDialog::SetStartDirectoryContext( const char *pStartDirContext, const char *pDefaultDir ) +{ + bool bUseCurrentDirectory = true; + if ( pStartDirContext ) + { + m_nStartDirContext = s_StartDirContexts.Find( pStartDirContext ); + if ( m_nStartDirContext == s_StartDirContexts.InvalidIndex() ) + { + m_nStartDirContext = s_StartDirContexts.Insert( pStartDirContext, pDefaultDir ); + bUseCurrentDirectory = ( pDefaultDir == NULL ); + } + else + { + bUseCurrentDirectory = false; + } + } + else + { + m_nStartDirContext = s_StartDirContexts.InvalidIndex(); + } + + if ( !bUseCurrentDirectory ) + { + SetStartDirectory( s_StartDirContexts[m_nStartDirContext].Get() ); + } + else + { + // Set our starting path to the current directory + char pLocalPath[255]; + g_pFullFileSystem->GetCurrentDirectory( pLocalPath, 255 ); + SetStartDirectory( pLocalPath ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Set the starting directory of the file search. +//----------------------------------------------------------------------------- +void FileOpenDialog::SetStartDirectory( const char *dir ) +{ + m_pFullPathEdit->SetText(dir); + + // ensure it's validity + ValidatePath(); + + // Store this in the start directory list + if ( m_nStartDirContext != s_StartDirContexts.InvalidIndex() ) + { + char pDirBuf[MAX_PATH]; + GetCurrentDirectory( pDirBuf, sizeof(pDirBuf) ); + s_StartDirContexts[ m_nStartDirContext ] = pDirBuf; + } + + PopulateDriveList(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add filters for the drop down combo box +//----------------------------------------------------------------------------- +void FileOpenDialog::AddFilter( const char *filter, const char *filterName, bool bActive, const char *pFilterInfo ) +{ + KeyValues *kv = new KeyValues("item"); + kv->SetString( "filter", filter ); + kv->SetString( "filterinfo", pFilterInfo ); + int itemID = m_pFileTypeCombo->AddItem(filterName, kv); + if ( bActive ) + { + m_pFileTypeCombo->ActivateItem(itemID); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate the dialog +//----------------------------------------------------------------------------- +void FileOpenDialog::DoModal( bool bUnused ) +{ + m_bFileSelected = false; + m_pFileNameEdit->RequestFocus(); + BaseClass::DoModal(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the directory this is currently in +//----------------------------------------------------------------------------- +void FileOpenDialog::GetCurrentDirectory(char *buf, int bufSize) +{ + // get the text from the text entry + m_pFullPathEdit->GetText(buf, bufSize); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the last selected file name +//----------------------------------------------------------------------------- +void FileOpenDialog::GetSelectedFileName(char *buf, int bufSize) +{ + m_pFileNameEdit->GetText(buf, bufSize); +} + + +//----------------------------------------------------------------------------- +// Creates a new folder +//----------------------------------------------------------------------------- +void FileOpenDialog::NewFolder( char const *folderName ) +{ + char pCurrentDirectory[MAX_PATH]; + GetCurrentDirectory( pCurrentDirectory, sizeof(pCurrentDirectory) ); + + char pFullPath[MAX_PATH]; + char pNewFolderName[MAX_PATH]; + Q_strncpy( pNewFolderName, folderName, sizeof(pNewFolderName) ); + int i = 2; + do + { + Q_MakeAbsolutePath( pFullPath, sizeof(pFullPath), pNewFolderName, pCurrentDirectory ); + if ( !g_pFullFileSystem->FileExists( pFullPath, NULL ) && + !g_pFullFileSystem->IsDirectory( pFullPath, NULL ) ) + { + g_pFullFileSystem->CreateDirHierarchy( pFullPath, NULL ); + m_pFileNameEdit->SetText( pNewFolderName ); + return; + } + + Q_snprintf( pNewFolderName, sizeof(pNewFolderName), "%s%d", folderName, i ); + ++i; + } while ( i <= 999 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Move the directory structure up +//----------------------------------------------------------------------------- +void FileOpenDialog::MoveUpFolder() +{ + char fullpath[MAX_PATH * 4]; + GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH); + + Q_StripLastDir( fullpath, sizeof( fullpath ) ); + // append a trailing slash + Q_AppendSlash( fullpath, sizeof( fullpath ) ); + + SetStartDirectory(fullpath); + PopulateFileList(); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Validate that the current path is valid +//----------------------------------------------------------------------------- +void FileOpenDialog::ValidatePath() +{ + char fullpath[MAX_PATH * 4]; + GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH); + Q_RemoveDotSlashes( fullpath ); + + // when statting a directory on Windows, you want to include + // the terminal slash exactly when you are statting a root + // directory. PKMN. +#ifdef _WIN32 + if ( Q_strlen( fullpath ) != 3 ) + { + Q_StripTrailingSlash( fullpath ); + } +#endif + // cleanup the path, we format tabs into the list to make it pretty in the UI + Q_StripPrecedingAndTrailingWhitespace( fullpath ); + + struct _stat buf; + if ( ( 0 == _stat( fullpath, &buf ) ) && + ( 0 != ( buf.st_mode & S_IFDIR ) ) ) + { + Q_AppendSlash( fullpath, sizeof( fullpath ) ); + Q_strncpy(m_szLastPath, fullpath, sizeof(m_szLastPath)); + } + else + { + // failed to load file, use the previously successful path + } + + m_pFullPathEdit->SetText(m_szLastPath); + m_pFullPathEdit->GetTooltip()->SetText(m_szLastPath); +} + +#ifdef WIN32 +const char *GetAttributesAsString( DWORD dwAttributes ) +{ + static char out[ 256 ]; + out[ 0 ] = 0; + if ( dwAttributes & FILE_ATTRIBUTE_ARCHIVE ) + { + Q_strncat( out, "A", sizeof( out ), COPY_ALL_CHARACTERS ); + } + if ( dwAttributes & FILE_ATTRIBUTE_COMPRESSED ) + { + Q_strncat( out, "C", sizeof( out ), COPY_ALL_CHARACTERS ); + } + if ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + Q_strncat( out, "D", sizeof( out ), COPY_ALL_CHARACTERS ); + } + if ( dwAttributes & FILE_ATTRIBUTE_HIDDEN ) + { + Q_strncat( out, "H", sizeof( out ), COPY_ALL_CHARACTERS ); + } + if ( dwAttributes & FILE_ATTRIBUTE_READONLY ) + { + Q_strncat( out, "R", sizeof( out ), COPY_ALL_CHARACTERS ); + } + if ( dwAttributes & FILE_ATTRIBUTE_SYSTEM ) + { + Q_strncat( out, "S", sizeof( out ), COPY_ALL_CHARACTERS ); + } + if ( dwAttributes & FILE_ATTRIBUTE_TEMPORARY ) + { + Q_strncat( out, "T", sizeof( out ), COPY_ALL_CHARACTERS ); + } + return out; +} + +const char *GetFileTimetamp( FILETIME ft ) +{ + SYSTEMTIME local; + FILETIME localFileTime; + FileTimeToLocalFileTime( &ft, &localFileTime ); + FileTimeToSystemTime( &localFileTime, &local ); + + static char out[ 256 ]; + + bool am = true; + WORD hour = local.wHour; + if ( hour >= 12 ) + { + am = false; + // 12:42 pm displays as 12:42 pm + // 13:42 pm displays as 1:42 pm + if ( hour > 12 ) + { + hour -= 12; + } + } + Q_snprintf( out, sizeof( out ), "%d/%02d/%04d %d:%02d %s", + local.wMonth, + local.wDay, + local.wYear, + hour, + local.wMinute, + am ? "AM" : "PM" // TODO: Localize this? + ); + return out; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Fill the filelist with the names of all the files in the current directory +//----------------------------------------------------------------------------- +#define MAX_FILTER_LENGTH 255 +void FileOpenDialog::PopulateFileList() +{ + // clear the current list + m_pFileList->DeleteAllItems(); + + FileFindHandle_t findHandle; + char pszFileModified[64]; + + // get the current directory + char currentDir[MAX_PATH * 4]; + char dir[MAX_PATH * 4]; + char filterList[MAX_FILTER_LENGTH+1]; + GetCurrentDirectory(currentDir, sizeof(dir)); + + KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData(); + if (combokv) + { + Q_strncpy(filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH); + } + else + { + // add wildcard for search + Q_strncpy(filterList, "*\0", MAX_FILTER_LENGTH); + } + + + char *filterPtr = filterList; + KeyValues *kv = new KeyValues("item"); + + if ( m_DialogType != FOD_SELECT_DIRECTORY ) + { + while ((filterPtr != NULL) && (*filterPtr != 0)) + { + // parse the next filter in the list. + char curFilter[MAX_FILTER_LENGTH]; + curFilter[0] = 0; + int i = 0; + while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' '))) + { + ++filterPtr; + } + while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' ')) + { + curFilter[i++] = *(filterPtr++); + } + curFilter[i] = 0; + + if (curFilter[0] == 0) + { + break; + } + + Q_snprintf( dir, MAX_PATH*4, "%s%s", currentDir, curFilter ); + + // Open the directory and walk it, loading files + const char *pszFileName = g_pFullFileSystem->FindFirst( dir, &findHandle ); + while ( pszFileName ) + { + if ( !g_pFullFileSystem->FindIsDirectory( findHandle ) + || !IsOSX() + || ( IsOSX() && g_pFullFileSystem->FindIsDirectory( findHandle ) && Q_stristr( pszFileName, ".app" ) ) ) + { + char pFullPath[MAX_PATH]; + Q_snprintf( pFullPath, MAX_PATH, "%s%s", currentDir, pszFileName ); + + // add the file to the list + kv->SetString( "text", pszFileName ); + + kv->SetInt( "image", 1 ); + + IImage *image = surface()->GetIconImageForFullPath( pFullPath ); + + if ( image ) + { + kv->SetPtr( "iconImage", (void *)image ); + } + + kv->SetInt("imageSelected", 1); + kv->SetInt("directory", 0); + + kv->SetString( "filesize", Q_pretifymem( g_pFullFileSystem->Size( pFullPath ), 0, true ) ); + Q_FixSlashes( pFullPath ); + wchar_t fileType[ 80 ]; + g_pFullFileSystem->GetFileTypeForFullPath( pFullPath, fileType, sizeof( fileType ) ); + kv->SetWString( "type", fileType ); + + kv->SetString( "attributes", g_pFullFileSystem->IsFileWritable( pFullPath )? "" : "R" ); + + long fileModified = g_pFullFileSystem->GetFileTime( pFullPath ); + g_pFullFileSystem->FileTimeToString( pszFileModified, sizeof( pszFileModified ), fileModified ); + kv->SetString( "modified", pszFileModified ); + +// kv->SetString( "created", GetFileTimetamp( findData.ftCreationTime ) ); + + m_pFileList->AddItem(kv, 0, false, false); + } + + pszFileName = g_pFullFileSystem->FindNext( findHandle ); + } + g_pFullFileSystem->FindClose( findHandle ); + } + } + + // find all the directories + GetCurrentDirectory( dir, sizeof(dir) ); + Q_strncat(dir, "*", sizeof( dir ), COPY_ALL_CHARACTERS); + + const char *pszFileName = g_pFullFileSystem->FindFirst( dir, &findHandle ); + while ( pszFileName ) + { + if ( pszFileName[0] != '.' && g_pFullFileSystem->FindIsDirectory( findHandle ) + && ( !IsOSX() || ( IsOSX() && !Q_stristr( pszFileName, ".app" ) ) ) ) + { + char pFullPath[MAX_PATH]; + Q_snprintf( pFullPath, MAX_PATH, "%s%s", currentDir, pszFileName ); + + kv->SetString("text", pszFileName ); + kv->SetPtr( "iconImage", (void *)NULL ); + kv->SetInt("image", 2); + kv->SetInt("imageSelected", 3); + kv->SetInt("directory", 1); + + kv->SetString( "filesize", "" ); + kv->SetString( "type", "#FileOpenDialog_FileType_Folder" ); + + kv->SetString( "attributes", g_pFullFileSystem->IsFileWritable( pFullPath )? "" : "R" ); + + long fileModified = g_pFullFileSystem->GetFileTime( pFullPath ); + g_pFullFileSystem->FileTimeToString( pszFileModified, sizeof( pszFileModified ), fileModified ); + kv->SetString( "modified", pszFileModified ); + +// kv->SetString( "created", GetFileTimetamp( findData.ftCreationTime ) ); + + m_pFileList->AddItem( kv, 0, false, false ); + } + + pszFileName = g_pFullFileSystem->FindNext( findHandle ); + } + g_pFullFileSystem->FindClose( findHandle ); + + kv->deleteThis(); + m_pFileList->SortList(); +} + + +//----------------------------------------------------------------------------- +// Does the specified extension match something in the filter list? +//----------------------------------------------------------------------------- +bool FileOpenDialog::ExtensionMatchesFilter( const char *pExt ) +{ + KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData(); + if ( !combokv ) + return true; + + char filterList[MAX_FILTER_LENGTH+1]; + Q_strncpy( filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH ); + + char *filterPtr = filterList; + while ((filterPtr != NULL) && (*filterPtr != 0)) + { + // parse the next filter in the list. + char curFilter[MAX_FILTER_LENGTH]; + curFilter[0] = 0; + int i = 0; + while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' '))) + { + ++filterPtr; + } + while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' ')) + { + curFilter[i++] = *(filterPtr++); + } + curFilter[i] = 0; + + if (curFilter[0] == 0) + break; + + if ( !Q_stricmp( curFilter, "*" ) || !Q_stricmp( curFilter, "*.*" ) ) + return true; + + // FIXME: This isn't exactly right, but tough cookies; + // it assumes the first two characters of the filter are *. + Assert( curFilter[0] == '*' && curFilter[1] == '.' ); + if ( !Q_stricmp( &curFilter[2], pExt ) ) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Choose the first non *.* filter in the filter list +//----------------------------------------------------------------------------- +void FileOpenDialog::ChooseExtension( char *pExt, int nBufLen ) +{ + pExt[0] = 0; + + KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData(); + if ( !combokv ) + return; + + char filterList[MAX_FILTER_LENGTH+1]; + Q_strncpy( filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH ); + + char *filterPtr = filterList; + while ((filterPtr != NULL) && (*filterPtr != 0)) + { + // parse the next filter in the list. + char curFilter[MAX_FILTER_LENGTH]; + curFilter[0] = 0; + int i = 0; + while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' '))) + { + ++filterPtr; + } + while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' ')) + { + curFilter[i++] = *(filterPtr++); + } + curFilter[i] = 0; + + if (curFilter[0] == 0) + break; + + if ( !Q_stricmp( curFilter, "*" ) || !Q_stricmp( curFilter, "*.*" ) ) + continue; + + // FIXME: This isn't exactly right, but tough cookies; + // it assumes the first two characters of the filter are *. + Assert( curFilter[0] == '*' && curFilter[1] == '.' ); + Q_strncpy( pExt, &curFilter[1], nBufLen ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Saves the file to the start dir context +//----------------------------------------------------------------------------- +void FileOpenDialog::SaveFileToStartDirContext( const char *pFullPath ) +{ + if ( m_nStartDirContext == s_StartDirContexts.InvalidIndex() ) + return; + + char pPath[MAX_PATH]; + pPath[0] = 0; + Q_ExtractFilePath( pFullPath, pPath, sizeof(pPath) ); + s_StartDirContexts[ m_nStartDirContext ] = pPath; +} + + +//----------------------------------------------------------------------------- +// Posts a file selected message +//----------------------------------------------------------------------------- +void FileOpenDialog::PostFileSelectedMessage( const char *pFileName ) +{ + m_bFileSelected = true; + + // open the file! + KeyValues *pKeyValues = new KeyValues( "FileSelected", "fullpath", pFileName ); + KeyValues *pFilterKeys = m_pFileTypeCombo->GetActiveItemUserData(); + const char *pFilterInfo = pFilterKeys ? pFilterKeys->GetString( "filterinfo", NULL ) : NULL; + if ( pFilterInfo ) + { + pKeyValues->SetString( "filterinfo", pFilterInfo ); + } + if ( m_pContextKeyValues ) + { + pKeyValues->AddSubKey( m_pContextKeyValues ); + m_pContextKeyValues = NULL; + } + PostActionSignal( pKeyValues ); + CloseModal(); +} + + +//----------------------------------------------------------------------------- +// Selects the current folder +//----------------------------------------------------------------------------- +void FileOpenDialog::OnSelectFolder() +{ + ValidatePath(); + + // construct a file path + char pFileName[MAX_PATH]; + GetSelectedFileName( pFileName, sizeof( pFileName ) ); + + Q_StripTrailingSlash( pFileName ); + + if ( !stricmp(pFileName, "..") ) + { + MoveUpFolder(); + + // clear the name text + m_pFileNameEdit->SetText(""); + return; + } + + if ( !stricmp(pFileName, ".") ) + { + // clear the name text + m_pFileNameEdit->SetText(""); + return; + } + + // Compute the full path + char pFullPath[MAX_PATH * 4]; + if ( !Q_IsAbsolutePath( pFileName ) ) + { + GetCurrentDirectory(pFullPath, sizeof(pFullPath) - MAX_PATH); + strcat( pFullPath, pFileName ); + if ( !pFileName[0] ) + { + Q_StripTrailingSlash( pFullPath ); + } + } + else + { + Q_strncpy( pFullPath, pFileName, sizeof(pFullPath) ); + } + + if ( g_pFullFileSystem->FileExists( pFullPath ) ) + { + // open the file! + SaveFileToStartDirContext( pFullPath ); + PostFileSelectedMessage( pFullPath ); + return; + } + + PopulateDriveList(); + PopulateFileList(); + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handle the open button being pressed +// checks on what has changed and acts accordingly +//----------------------------------------------------------------------------- +void FileOpenDialog::OnOpen() +{ + ValidatePath(); + + // construct a file path + char pFileName[MAX_PATH]; + GetSelectedFileName( pFileName, sizeof( pFileName ) ); + + int nLen = Q_strlen( pFileName ); + bool bSpecifiedDirectory = ( pFileName[nLen-1] == '/' || pFileName[nLen-1] == '\\' ) && (!IsOSX() || ( IsOSX() && !Q_stristr( pFileName, ".app" ) ) ); + Q_StripTrailingSlash( pFileName ); + + if ( !stricmp(pFileName, "..") ) + { + MoveUpFolder(); + + // clear the name text + m_pFileNameEdit->SetText(""); + return; + } + + if ( !stricmp(pFileName, ".") ) + { + // clear the name text + m_pFileNameEdit->SetText(""); + return; + } + + // Compute the full path + char pFullPath[MAX_PATH * 4]; + if ( !Q_IsAbsolutePath( pFileName ) ) + { + GetCurrentDirectory(pFullPath, sizeof(pFullPath) - MAX_PATH); + Q_AppendSlash( pFullPath, sizeof( pFullPath ) ); + strcat(pFullPath, pFileName); + if ( !pFileName[0] ) + { + Q_StripTrailingSlash( pFullPath ); + } + } + else + { + Q_strncpy( pFullPath, pFileName, sizeof(pFullPath) ); + } + + Q_StripTrailingSlash( pFullPath ); + + // when statting a directory on Windows, you want to include + // the terminal slash exactly when you are statting a root + // directory. PKMN. +#ifdef _WIN32 + if ( Q_strlen( pFullPath ) == 2 ) + { + Q_AppendSlash( pFullPath, Q_ARRAYSIZE( pFullPath ) ); + } +#endif + + + // If the name specified is a directory, then change directory + if ( g_pFullFileSystem->IsDirectory( pFullPath, NULL ) && + ( !IsOSX() || ( IsOSX() && !Q_stristr( pFullPath, ".app" ) ) ) ) + { + // it's a directory; change to the specified directory + if ( !bSpecifiedDirectory ) + { + Q_AppendSlash( pFullPath, Q_ARRAYSIZE( pFullPath ) ); + } + SetStartDirectory( pFullPath ); + + // clear the name text + m_pFileNameEdit->SetText(""); + m_pFileNameEdit->HideMenu(); + + PopulateDriveList(); + PopulateFileList(); + InvalidateLayout(); + return; + } + else if ( bSpecifiedDirectory ) + { + PopulateDriveList(); + PopulateFileList(); + InvalidateLayout(); + return; + } + + // Append suffix of the first filter that isn't *.* + char extension[512]; + Q_ExtractFileExtension( pFullPath, extension, sizeof(extension) ); + if ( !ExtensionMatchesFilter( extension ) ) + { + ChooseExtension( extension, sizeof(extension) ); + Q_SetExtension( pFullPath, extension, sizeof(pFullPath) ); + } + + if ( g_pFullFileSystem->FileExists( pFullPath ) ) + { + // open the file! + SaveFileToStartDirContext( pFullPath ); + PostFileSelectedMessage( pFullPath ); + return; + } + + // file not found + if ( ( m_DialogType == FOD_SAVE ) && pFileName[0] ) + { + // open the file! + SaveFileToStartDirContext( pFullPath ); + PostFileSelectedMessage( pFullPath ); + return; + } + + PopulateDriveList(); + PopulateFileList(); + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: using the file edit box as a prefix, create a menu of all possible files +//----------------------------------------------------------------------------- +void FileOpenDialog::PopulateFileNameCompletion() +{ + char buf[80]; + m_pFileNameEdit->GetText(buf, 80); + wchar_t wbuf[80]; + m_pFileNameEdit->GetText(wbuf, 80); + int bufLen = wcslen(wbuf); + + // delete all items before we check if there's even a string + m_pFileNameEdit->DeleteAllItems(); + + // no string at all - don't show even bother showing it + if (bufLen == 0) + { + m_pFileNameEdit->HideMenu(); + return; + } + + // what files use current string as a prefix? + int nCount = m_pFileList->GetItemCount(); + int i; + for ( i = 0 ; i < nCount ; i++ ) + { + KeyValues *kv = m_pFileList->GetItem(m_pFileList->GetItemIDFromRow(i)); + const wchar_t *wszString = kv->GetWString("text"); + if ( !_wcsnicmp(wbuf, wszString, bufLen) ) + { + m_pFileNameEdit->AddItem(wszString, NULL); + } + } + + // if there are any items - show the menu + if ( m_pFileNameEdit->GetItemCount() > 0 ) + { + m_pFileNameEdit->ShowMenu(); + } + else + { + m_pFileNameEdit->HideMenu(); + } + + m_pFileNameEdit->InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle an item in the list being selected +//----------------------------------------------------------------------------- +void FileOpenDialog::OnItemSelected() +{ + // make sure only one item is selected + if (m_pFileList->GetSelectedItemsCount() != 1) + { + m_pFileNameEdit->SetText(""); + } + else + { + // put the file name into the text edit box + KeyValues *data = m_pFileList->GetItem(m_pFileList->GetSelectedItem(0)); + m_pFileNameEdit->SetText(data->GetString("text")); + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle an item in the Drive combo box being selected +//----------------------------------------------------------------------------- +void FileOpenDialog::OnTextChanged(KeyValues *kv) +{ + Panel *pPanel = (Panel *) kv->GetPtr("panel", NULL); + + // first check which control had its text changed! + if (pPanel == m_pFullPathEdit) + { + m_pFileNameEdit->HideMenu(); + m_pFileNameEdit->SetText(""); + OnOpen(); + } + else if (pPanel == m_pFileNameEdit) + { + PopulateFileNameCompletion(); + } + else if (pPanel == m_pFileTypeCombo) + { + m_pFileNameEdit->HideMenu(); + PopulateFileList(); + } +} diff --git a/vgui2/vgui_controls/FileOpenStateMachine.cpp b/vgui2/vgui_controls/FileOpenStateMachine.cpp new file mode 100644 index 0000000..c611c11 --- /dev/null +++ b/vgui2/vgui_controls/FileOpenStateMachine.cpp @@ -0,0 +1,497 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// This is a helper class designed to help with the chains of modal dialogs +// encountered when trying to open or save a particular file +// +//============================================================================= + +#include "vgui_controls/FileOpenStateMachine.h" +#include "tier1/KeyValues.h" +#include "vgui_controls/FileOpenDialog.h" +#include "vgui_controls/MessageBox.h" +#include "vgui_controls/perforcefilelistframe.h" +#include "vgui_controls/savedocumentquery.h" +#include "filesystem.h" +#include "p4lib/ip4.h" +#include "tier2/tier2.h" +#include "tier0/icommandline.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +using namespace vgui; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +FileOpenStateMachine::FileOpenStateMachine( vgui::Panel *pParent, IFileOpenStateMachineClient *pClient ) : BaseClass( pParent, "FileOpenStateMachine" ) +{ + m_pClient = pClient; + m_CompletionState = SUCCESSFUL; + m_CurrentState = STATE_NONE; + m_pContextKeyValues = NULL; + SetVisible( false ); +} + +FileOpenStateMachine::~FileOpenStateMachine() +{ + CleanUpContextKeyValues(); +} + + +//----------------------------------------------------------------------------- +// Cleans up keyvalues +//----------------------------------------------------------------------------- +void FileOpenStateMachine::CleanUpContextKeyValues() +{ + if ( m_pContextKeyValues ) + { + m_pContextKeyValues->deleteThis(); + m_pContextKeyValues = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Returns the state machine completion state +//----------------------------------------------------------------------------- +FileOpenStateMachine::CompletionState_t FileOpenStateMachine::GetCompletionState() +{ + return m_CompletionState; +} + + +//----------------------------------------------------------------------------- +// Utility to set the completion state +//----------------------------------------------------------------------------- +void FileOpenStateMachine::SetCompletionState( FileOpenStateMachine::CompletionState_t state ) +{ + m_CompletionState = state; + if ( m_CompletionState == IN_PROGRESS ) + return; + + m_CurrentState = STATE_NONE; + + KeyValues *kv = new KeyValues( "FileStateMachineFinished" ); + kv->SetInt( "completionState", m_CompletionState ); + kv->SetInt( "wroteFile", m_bWroteFile ); + kv->SetString( "fullPath", m_FileName.Get() ); + kv->SetString( "fileType", m_bIsOpeningFile ? m_OpenFileType.Get() : m_SaveFileType.Get() ); + if ( m_pContextKeyValues ) + { + kv->AddSubKey( m_pContextKeyValues ); + m_pContextKeyValues = NULL; + } + PostActionSignal( kv ); +} + + +//----------------------------------------------------------------------------- +// Called by the message box in OverwriteFileDialog +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OnOverwriteFile( ) +{ + CheckOutDialog( ); +} + +void FileOpenStateMachine::OnCancelOverwriteFile( ) +{ + SetCompletionState( FILE_NOT_OVERWRITTEN ); +} + + +//----------------------------------------------------------------------------- +// Shows the overwrite existing file dialog +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OverwriteFileDialog( ) +{ + if ( !g_pFullFileSystem->FileExists( m_FileName ) ) + { + CheckOutDialog( ); + return; + } + + m_CurrentState = STATE_SHOWING_OVERWRITE_DIALOG; + + char pBuf[1024]; + Q_snprintf( pBuf, sizeof(pBuf), "File already exists. Overwrite it?\n\n\"%s\"\n", m_FileName.Get() ); + vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Overwrite Existing File?", pBuf, GetParent() ); + pMessageBox->AddActionSignalTarget( this ); + pMessageBox->SetOKButtonVisible( true ); + pMessageBox->SetOKButtonText( "Yes" ); + pMessageBox->SetCancelButtonVisible( true ); + pMessageBox->SetCancelButtonText( "No" ); + pMessageBox->SetCloseButtonVisible( false ); + pMessageBox->SetCommand( new KeyValues( "OverwriteFile" ) ); + pMessageBox->SetCancelCommand( new KeyValues( "CancelOverwriteFile" ) ); + pMessageBox->DoModal(); +} + + +//----------------------------------------------------------------------------- +// Used to open a particular file in perforce, and deal with all the lovely dialogs +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OnFileSelectionCancelled() +{ + if ( m_CurrentState == STATE_SHOWING_SAVE_DIALOG ) + { + SetCompletionState( FILE_SAVE_NAME_NOT_SPECIFIED ); + return; + } + + if ( m_CurrentState == STATE_SHOWING_OPEN_DIALOG ) + { + SetCompletionState( FILE_OPEN_NAME_NOT_SPECIFIED ); + return; + } + + Assert(0); +} + + +//----------------------------------------------------------------------------- +// Used to open a particular file in perforce, and deal with all the lovely dialogs +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OnFileSelected( KeyValues *pKeyValues ) +{ + if ( m_CurrentState == STATE_SHOWING_SAVE_DIALOG ) + { + m_FileName = pKeyValues->GetString( "fullpath" ); + const char *pFilterInfo = pKeyValues->GetString( "filterinfo" ); + if ( pFilterInfo ) + { + m_SaveFileType = pFilterInfo; + } + OverwriteFileDialog(); + return; + } + + if ( m_CurrentState == STATE_SHOWING_OPEN_DIALOG ) + { + m_FileName = pKeyValues->GetString( "fullpath" ); + const char *pFilterInfo = pKeyValues->GetString( "filterinfo" ); + if ( pFilterInfo ) + { + m_OpenFileType = pFilterInfo; + } + ReadFile( ); + return; + } + + Assert(0); +} + + +//----------------------------------------------------------------------------- +// Writes the file out +//----------------------------------------------------------------------------- +void FileOpenStateMachine::WriteFile() +{ + m_CurrentState = STATE_WRITING_FILE; + if ( !m_pClient->OnWriteFileToDisk( m_FileName, m_SaveFileType, m_pContextKeyValues ) ) + { + SetCompletionState( ERROR_WRITING_FILE ); + return; + } + + m_bWroteFile = true; + if ( m_bShowPerforceDialogs ) + { + m_CurrentState = STATE_SHOWING_PERFORCE_ADD_DIALOG; + ShowPerforceQuery( GetParent(), m_FileName, this, NULL, PERFORCE_ACTION_FILE_ADD ); + return; + } + + if ( !m_bIsOpeningFile ) + { + SetCompletionState( SUCCESSFUL ); + return; + } + + OpenFileDialog(); +} + + +//----------------------------------------------------------------------------- +// Called by the message box in MakeFileWriteableDialog +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OnMakeFileWriteable( ) +{ + if ( !g_pFullFileSystem->SetFileWritable( m_FileName, true ) ) + { + SetCompletionState( ERROR_MAKING_FILE_WRITEABLE ); + return; + } + + WriteFile(); +} + +void FileOpenStateMachine::OnCancelMakeFileWriteable( ) +{ + SetCompletionState( FILE_NOT_MADE_WRITEABLE ); +} + + +//----------------------------------------------------------------------------- +// Shows the make file writeable dialog +//----------------------------------------------------------------------------- +void FileOpenStateMachine::MakeFileWriteableDialog( ) +{ + // If the file is writeable, write it! + if ( !g_pFullFileSystem->FileExists( m_FileName ) || g_pFullFileSystem->IsFileWritable( m_FileName ) ) + { + WriteFile(); + return; + } + + // If it's in perforce, and not checked out, then we must abort. + bool bIsInPerforce = p4->IsFileInPerforce( m_FileName ); + bool bIsOpened = ( p4->GetFileState( m_FileName ) != P4FILE_UNOPENED ); + if ( bIsInPerforce && !bIsOpened ) + { + SetCompletionState( FILE_NOT_CHECKED_OUT ); + return; + } + + m_CurrentState = STATE_SHOWING_MAKE_FILE_WRITEABLE_DIALOG; + + char pBuf[1024]; + Q_snprintf( pBuf, sizeof(pBuf), "Encountered read-only file. Should it be made writeable?\n\n\"%s\"\n", m_FileName.Get() ); + vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Make File Writeable?", pBuf, GetParent() ); + pMessageBox->AddActionSignalTarget( this ); + pMessageBox->SetOKButtonVisible( true ); + pMessageBox->SetOKButtonText( "Yes" ); + pMessageBox->SetCancelButtonVisible( true ); + pMessageBox->SetCancelButtonText( "No" ); + pMessageBox->SetCloseButtonVisible( false ); + pMessageBox->SetCommand( new KeyValues( "MakeFileWriteable" ) ); + pMessageBox->SetCancelCommand( new KeyValues( "CancelMakeFileWriteable" ) ); + pMessageBox->DoModal(); +} + + +//----------------------------------------------------------------------------- +// Called when ShowPerforceQuery completes +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OnPerforceQueryCompleted( KeyValues *pKeyValues ) +{ + if ( m_CurrentState == STATE_SHOWING_CHECK_OUT_DIALOG ) + { + if ( pKeyValues->GetInt( "operationPerformed" ) == 0 ) + { + SetCompletionState( FILE_NOT_CHECKED_OUT ); + return; + } + + MakeFileWriteableDialog(); + return; + } + + if ( m_CurrentState == STATE_SHOWING_PERFORCE_ADD_DIALOG ) + { + if ( !m_bIsOpeningFile ) + { + SetCompletionState( SUCCESSFUL ); + return; + } + + OpenFileDialog(); + return; + } + + Assert(0); +} + + +//----------------------------------------------------------------------------- +// Used to open a particular file in perforce, and deal with all the lovely dialogs +//----------------------------------------------------------------------------- +void FileOpenStateMachine::CheckOutDialog( ) +{ + if ( m_bShowPerforceDialogs ) + { + m_CurrentState = STATE_SHOWING_CHECK_OUT_DIALOG; + ShowPerforceQuery( GetParent(), m_FileName, this, NULL, PERFORCE_ACTION_FILE_EDIT ); + return; + } + + WriteFile(); +} + + +//----------------------------------------------------------------------------- +// These 3 messages come from the savedocumentquery dialog +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OnSaveFile() +{ + if ( !m_FileName[0] || !Q_IsAbsolutePath( m_FileName ) ) + { + m_CurrentState = STATE_SHOWING_SAVE_DIALOG; + + FileOpenDialog *pDialog = new FileOpenDialog( GetParent(), "Save As", false ); + m_pClient->SetupFileOpenDialog( pDialog, false, m_SaveFileType, m_pContextKeyValues ); + pDialog->SetDeleteSelfOnClose( true ); + pDialog->AddActionSignalTarget( this ); + pDialog->DoModal( ); + return; + } + + CheckOutDialog( ); +} + +void FileOpenStateMachine::OnMarkNotDirty() +{ + if ( !m_bIsOpeningFile ) + { + SetCompletionState( SUCCESSFUL ); + return; + } + + // Jump right to opening the file + OpenFileDialog( ); +} + +void FileOpenStateMachine::OnCancelSaveDocument() +{ + SetCompletionState( FILE_SAVE_CANCELLED ); +} + + +//----------------------------------------------------------------------------- +// Show the save document query dialog +//----------------------------------------------------------------------------- +void FileOpenStateMachine::ShowSaveQuery( ) +{ + m_CurrentState = STATE_SHOWING_SAVE_DIRTY_FILE_DIALOG; + ShowSaveDocumentQuery( GetParent(), m_FileName, m_SaveFileType, 0, this, NULL ); +} + + +//----------------------------------------------------------------------------- +// Used to save a specified file, and deal with all the lovely dialogs +//----------------------------------------------------------------------------- +void FileOpenStateMachine::SaveFile( KeyValues *pContextKeyValues, const char *pFileName, const char *pFileType, int nFlags ) +{ + CleanUpContextKeyValues(); + SetCompletionState( IN_PROGRESS ); + m_pContextKeyValues = pContextKeyValues; + m_FileName = pFileName; + m_SaveFileType = pFileType; + m_OpenFileType = NULL; + m_OpenFileName = NULL; + + // Clear the P4 dialog flag for SDK users and licensees without Perforce + if ( CommandLine()->FindParm( "-nop4" ) ) + { + nFlags &= ~FOSM_SHOW_PERFORCE_DIALOGS; + } + + m_bShowPerforceDialogs = ( nFlags & FOSM_SHOW_PERFORCE_DIALOGS ) != 0; + m_bShowSaveQuery = ( nFlags & FOSM_SHOW_SAVE_QUERY ) != 0; + m_bIsOpeningFile = false; + m_bWroteFile = false; + + if ( m_bShowSaveQuery ) + { + ShowSaveQuery(); + return; + } + + OnSaveFile(); +} + + +//----------------------------------------------------------------------------- +// Reads the file in +//----------------------------------------------------------------------------- +void FileOpenStateMachine::ReadFile() +{ + m_CurrentState = STATE_READING_FILE; + if ( !m_pClient->OnReadFileFromDisk( m_FileName, m_OpenFileType, m_pContextKeyValues ) ) + { + SetCompletionState( ERROR_READING_FILE ); + return; + } + + SetCompletionState( SUCCESSFUL ); +} + + +//----------------------------------------------------------------------------- +// Shows the open file dialog +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OpenFileDialog( ) +{ + m_CurrentState = STATE_SHOWING_OPEN_DIALOG; + + if ( m_OpenFileName.IsEmpty() ) + { + FileOpenDialog *pDialog = new FileOpenDialog( GetParent(), "Open", true ); + m_pClient->SetupFileOpenDialog( pDialog, true, m_OpenFileType, m_pContextKeyValues ); + pDialog->SetDeleteSelfOnClose( true ); + pDialog->AddActionSignalTarget( this ); + pDialog->DoModal( ); + } + else + { + m_FileName = m_OpenFileName; + ReadFile(); + } +} + + +//----------------------------------------------------------------------------- +// Opens a file, saves an existing one if necessary +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OpenFile( const char *pOpenFileType, KeyValues *pContextKeyValues, const char *pSaveFileName, const char *pSaveFileType, int nFlags ) +{ + CleanUpContextKeyValues(); + SetCompletionState( IN_PROGRESS ); + m_pContextKeyValues = pContextKeyValues; + m_FileName = pSaveFileName; + m_SaveFileType = pSaveFileType; + m_OpenFileType = pOpenFileType; + m_OpenFileName = NULL; + m_bShowPerforceDialogs = ( nFlags & FOSM_SHOW_PERFORCE_DIALOGS ) != 0; + m_bShowSaveQuery = ( nFlags & FOSM_SHOW_SAVE_QUERY ) != 0; + m_bIsOpeningFile = true; + m_bWroteFile = false; + + if ( m_bShowSaveQuery ) + { + ShowSaveQuery(); + return; + } + + OpenFileDialog(); +} + + +//----------------------------------------------------------------------------- +// Version of OpenFile that skips browsing for a particular file to open +//----------------------------------------------------------------------------- +void FileOpenStateMachine::OpenFile( const char *pOpenFileName, const char *pOpenFileType, KeyValues *pContextKeyValues, const char *pSaveFileName, const char *pSaveFileType, int nFlags ) +{ + CleanUpContextKeyValues(); + SetCompletionState( IN_PROGRESS ); + m_pContextKeyValues = pContextKeyValues; + m_FileName = pSaveFileName; + m_SaveFileType = pSaveFileType; + m_OpenFileType = pOpenFileType; + m_bShowPerforceDialogs = ( nFlags & FOSM_SHOW_PERFORCE_DIALOGS ) != 0; + m_bShowSaveQuery = ( nFlags & FOSM_SHOW_SAVE_QUERY ) != 0; + m_bIsOpeningFile = true; + m_bWroteFile = false; + m_OpenFileName = pOpenFileName; + if ( m_bShowSaveQuery ) + { + ShowSaveQuery(); + return; + } + + OpenFileDialog(); +} + + diff --git a/vgui2/vgui_controls/FocusNavGroup.cpp b/vgui2/vgui_controls/FocusNavGroup.cpp new file mode 100644 index 0000000..8bfb807 --- /dev/null +++ b/vgui2/vgui_controls/FocusNavGroup.cpp @@ -0,0 +1,433 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> + +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> +#include <vgui/IPanel.h> +#include <vgui/VGUI.h> +#include <KeyValues.h> +#include <tier0/dbg.h> + +#include <vgui_controls/Controls.h> +#include <vgui_controls/FocusNavGroup.h> +#include <vgui_controls/Panel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : *panel - parent panel +//----------------------------------------------------------------------------- +FocusNavGroup::FocusNavGroup(Panel *panel) : _mainPanel(panel) +{ + _currentFocus = NULL; + _topLevelFocus = false; + _defaultButton = NULL; + _currentDefaultButton = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +FocusNavGroup::~FocusNavGroup() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the focus to the previous panel in the tab order +// Input : *panel - panel currently with focus +//----------------------------------------------------------------------------- +bool FocusNavGroup::RequestFocusPrev(VPANEL panel) +{ + if(panel==0) + return false; + + _currentFocus = NULL; + int newPosition = 9999999; + if (panel) + { + newPosition = ipanel()->GetTabPosition(panel); + } + + bool bFound = false; + bool bRepeat = true; + Panel *best = NULL; + while (1) + { + newPosition--; + if (newPosition > 0) + { + int bestPosition = 0; + + // look for the next tab position + for (int i = 0; i < _mainPanel->GetChildCount(); i++) + { + Panel *child = _mainPanel->GetChild(i); + if (child && child->IsVisible() && child->IsEnabled() && child->GetTabPosition()) + { + int tabPosition = child->GetTabPosition(); + if (tabPosition == newPosition) + { + // we've found the right tab + best = child; + bestPosition = newPosition; + + // don't loop anymore since we've found the correct panel + break; + } + else if (tabPosition < newPosition && tabPosition > bestPosition) + { + // record the match since this is the closest so far + bestPosition = tabPosition; + best = child; + } + } + } + + if (!bRepeat) + break; + + if (best) + break; + } + else + { + // reset new position for next loop + newPosition = 9999999; + } + + // haven't found an item + + if (!_topLevelFocus) + { + // check to see if we should push the focus request up + if (_mainPanel->GetVParent() && _mainPanel->GetVParent() != surface()->GetEmbeddedPanel()) + { + // we're not a top level panel, so forward up the request instead of looping + if (ipanel()->RequestFocusPrev(_mainPanel->GetVParent(), _mainPanel->GetVPanel())) + { + bFound = true; + SetCurrentDefaultButton(NULL); + break; + } + } + } + + // not found an item, loop back + newPosition = 9999999; + bRepeat = false; + } + + if (best) + { + _currentFocus = best->GetVPanel(); + best->RequestFocus(-1); + bFound = true; + + if (!CanButtonBeDefault(best->GetVPanel())) + { + if (_defaultButton) + { + SetCurrentDefaultButton(_defaultButton); + } + else + { + SetCurrentDefaultButton(NULL); + + // we need to ask the parent to set its default button + if (_mainPanel->GetVParent()) + { + ivgui()->PostMessage(_mainPanel->GetVParent(), new KeyValues("FindDefaultButton"), NULL); + } + } + } + else + { + SetCurrentDefaultButton(best->GetVPanel()); + } + } + return bFound; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the focus to the previous panel in the tab order +// Input : *panel - panel currently with focus +//----------------------------------------------------------------------------- +bool FocusNavGroup::RequestFocusNext(VPANEL panel) +{ + // basic recursion guard, in case user has set up a bad focus hierarchy + static int stack_depth = 0; + stack_depth++; + + _currentFocus = NULL; + int newPosition = 0; + if (panel) + { + newPosition = ipanel()->GetTabPosition(panel); + } + + bool bFound = false; + bool bRepeat = true; + Panel *best = NULL; + while (1) + { + newPosition++; + int bestPosition = 999999; + + // look for the next tab position + for (int i = 0; i < _mainPanel->GetChildCount(); i++) + { + Panel *child = _mainPanel->GetChild(i); + if ( !child ) + continue; + + if (child && child->IsVisible() && child->IsEnabled() && child->GetTabPosition()) + { + int tabPosition = child->GetTabPosition(); + if (tabPosition == newPosition) + { + // we've found the right tab + best = child; + bestPosition = newPosition; + + // don't loop anymore since we've found the correct panel + break; + } + else if (tabPosition > newPosition && tabPosition < bestPosition) + { + // record the match since this is the closest so far + bestPosition = tabPosition; + best = child; + } + } + } + + if (!bRepeat) + break; + + if (best) + break; + + // haven't found an item + + // check to see if we should push the focus request up + if (!_topLevelFocus) + { + if (_mainPanel->GetVParent() && _mainPanel->GetVParent() != surface()->GetEmbeddedPanel()) + { + // we're not a top level panel, so forward up the request instead of looping + if (stack_depth < 15) + { + if (ipanel()->RequestFocusNext(_mainPanel->GetVParent(), _mainPanel->GetVPanel())) + { + bFound = true; + SetCurrentDefaultButton(NULL); + break; + } + + // if we find one then we break, otherwise we loop + } + } + } + + // loop back + newPosition = 0; + bRepeat = false; + } + + if (best) + { + _currentFocus = best->GetVPanel(); + best->RequestFocus(1); + bFound = true; + + if (!CanButtonBeDefault(best->GetVPanel())) + { + if (_defaultButton) + { + SetCurrentDefaultButton(_defaultButton); + } + else + { + SetCurrentDefaultButton(NULL); + + // we need to ask the parent to set its default button + if (_mainPanel->GetVParent()) + { + ivgui()->PostMessage(_mainPanel->GetVParent(), new KeyValues("FindDefaultButton"), NULL); + } + } + } + else + { + SetCurrentDefaultButton(best->GetVPanel()); + } + } + + stack_depth--; + return bFound; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the panel that owns this FocusNavGroup to be the root in the focus traversal heirarchy +//----------------------------------------------------------------------------- +void FocusNavGroup::SetFocusTopLevel(bool state) +{ + _topLevelFocus = state; +} + +//----------------------------------------------------------------------------- +// Purpose: sets panel which receives input when ENTER is hit +//----------------------------------------------------------------------------- +void FocusNavGroup::SetDefaultButton(Panel *panel) +{ + VPANEL vpanel = panel ? panel->GetVPanel() : NULL; + if ( vpanel == _defaultButton.Get() ) + return; + +// Assert(CanButtonBeDefault(vpanel)); + + _defaultButton = vpanel; + SetCurrentDefaultButton(_defaultButton); +} + +//----------------------------------------------------------------------------- +// Purpose: sets panel which receives input when ENTER is hit +//----------------------------------------------------------------------------- +void FocusNavGroup::SetCurrentDefaultButton(VPANEL panel, bool sendCurrentDefaultButtonMessage) +{ + if (panel == _currentDefaultButton.Get()) + return; + + if ( sendCurrentDefaultButtonMessage && _currentDefaultButton.Get() != 0) + { + ivgui()->PostMessage(_currentDefaultButton, new KeyValues("SetAsCurrentDefaultButton", "state", 0), NULL); + } + + _currentDefaultButton = panel; + + if ( sendCurrentDefaultButtonMessage && _currentDefaultButton.Get() != 0) + { + ivgui()->PostMessage(_currentDefaultButton, new KeyValues("SetAsCurrentDefaultButton", "state", 1), NULL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets panel which receives input when ENTER is hit +//----------------------------------------------------------------------------- +VPANEL FocusNavGroup::GetCurrentDefaultButton() +{ + return _currentDefaultButton; +} + +//----------------------------------------------------------------------------- +// Purpose: sets panel which receives input when ENTER is hit +//----------------------------------------------------------------------------- +VPANEL FocusNavGroup::GetDefaultButton() +{ + return _defaultButton; +} + +//----------------------------------------------------------------------------- +// Purpose: finds the panel which is activated by the specified key +// Input : code - the keycode of the hotkey +// Output : Panel * - NULL if no panel found +//----------------------------------------------------------------------------- +Panel *FocusNavGroup::FindPanelByHotkey(wchar_t key) +{ + for (int i = 0; i < _mainPanel->GetChildCount(); i++) + { + Panel *child = _mainPanel->GetChild(i); + if ( !child ) + continue; + + Panel *hot = child->HasHotkey(key); + if (hot && hot->IsVisible() && hot->IsEnabled()) + { + return hot; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel *FocusNavGroup::GetDefaultPanel() +{ + for (int i = 0; i < _mainPanel->GetChildCount(); i++) + { + Panel *child = _mainPanel->GetChild(i); + if ( !child ) + continue; + + if (child->GetTabPosition() == 1) + { + return child; + } + } + + return NULL; // no specific panel set +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel *FocusNavGroup::GetCurrentFocus() +{ + return _currentFocus ? ipanel()->GetPanel(_currentFocus, vgui::GetControlsModuleName()) : NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the current focus +//----------------------------------------------------------------------------- +VPANEL FocusNavGroup::SetCurrentFocus(VPANEL focus, VPANEL defaultPanel) +{ + _currentFocus = focus; + + // if we haven't found a default panel yet, let's see if we know of one + if (defaultPanel == 0) + { + // can this focus itself by the default + if (CanButtonBeDefault(focus)) + { + defaultPanel = focus; + } + else if (_defaultButton) // do we know of a default button + { + defaultPanel = _defaultButton; + } + } + + SetCurrentDefaultButton(defaultPanel); + return defaultPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the specified panel can be the default +//----------------------------------------------------------------------------- +bool FocusNavGroup::CanButtonBeDefault(VPANEL panel) +{ + if( panel == 0 ) + return false; + + KeyValues *data = new KeyValues("CanBeDefaultButton"); + + bool bResult = false; + if (ipanel()->RequestInfo(panel, data)) + { + bResult = (data->GetInt("result") == 1); + } + data->deleteThis(); + return bResult; +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/Frame.cpp b/vgui2/vgui_controls/Frame.cpp new file mode 100644 index 0000000..35b5d76 --- /dev/null +++ b/vgui2/vgui_controls/Frame.cpp @@ -0,0 +1,2396 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include <assert.h> +#include <math.h> // for ceil() +#define PROTECTED_THINGS_DISABLE + +#include "tier1/utlstring.h" +#include "vgui/Cursor.h" +#include "vgui/MouseCode.h" +#include "vgui/IBorder.h" +#include "vgui/IInput.h" +#include "vgui/ILocalize.h" +#include "vgui/IPanel.h" +#include "vgui/ISurface.h" +#include "vgui/IScheme.h" +#include "vgui/KeyCode.h" + +#include "vgui_controls/AnimationController.h" +#include "vgui_controls/Controls.h" +#include "vgui_controls/Frame.h" +#include "vgui_controls/Button.h" +#include "vgui_controls/Menu.h" +#include "vgui_controls/MenuButton.h" +#include "vgui_controls/TextImage.h" + +#include "KeyValues.h" + +#include <stdio.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +static const int DEFAULT_SNAP_RANGE = 10; // number of pixels distance before the frame will snap to an edge +static const int CAPTION_TITLE_BORDER = 7; +static const int CAPTION_TITLE_BORDER_SMALL = 0; + +namespace +{ + //----------------------------------------------------------------------------- + // Purpose: Invisible panel to handle dragging/resizing frames + //----------------------------------------------------------------------------- + class GripPanel : public Panel + { + public: + GripPanel(Frame *dragFrame, const char *name, int xdir, int ydir) : Panel(dragFrame, name) + { + _frame = dragFrame; + _dragging = false; + _dragMultX = xdir; + _dragMultY = ydir; + SetPaintEnabled(false); + SetPaintBackgroundEnabled(false); + SetPaintBorderEnabled(false); + m_iSnapRange = DEFAULT_SNAP_RANGE; + + if (xdir == 1 && ydir == 1) + { + // bottom-right grip gets an image + SetPaintEnabled(true); + SetPaintBackgroundEnabled(true); + } + + SetBlockDragChaining( true ); + } + + // Purpose- handle window resizing + // Input- dx, dy, the offet of the mouse pointer from where we started dragging + virtual void moved(int dx, int dy) + { + if (!_frame->IsSizeable()) + return; + + // Start off with x, y at the coords of where we started to drag + int newX = _dragOrgPos[0], newY =_dragOrgPos[1]; + // Start off with width and tall equal from window when we started to drag + int newWide = _dragOrgSize[0], newTall = _dragOrgSize[1]; + + // get window's minimum size + int minWide, minTall; + _frame->GetMinimumSize( minWide, minTall); + + // Handle width resizing + newWide += (dx * _dragMultX); + // Handle the position of the corner x position + if (_dragMultX == -1) + { + // only move if we are not at the minimum + // if we are at min we have to force the proper offset (dx) + if (newWide < minWide) + { + dx=_dragOrgSize[0]-minWide; + } + newX += dx; // move window to its new position + } + + // Handle height resizing + newTall += (dy * _dragMultY); + // Handle position of corner y position + if (_dragMultY == -1) + { + if (newTall < minTall) + { + dy=_dragOrgSize[1]-minTall; + } + newY += dy; + } + + if ( _frame->GetClipToParent() ) + { + // If any coordinate is out of range, snap it back + if ( newX < 0 ) + newX = 0; + if ( newY < 0 ) + newY = 0; + + int sx, sy; + surface()->GetScreenSize( sx, sy ); + + int w, h; + _frame->GetSize( w, h ); + if ( newX + w > sx ) + { + newX = sx - w; + } + if ( newY + h > sy ) + { + newY = sy - h; + } + } + + // set new position + _frame->SetPos(newX, newY); + // set the new size + // if window is below min size it will automatically pop to min size + _frame->SetSize(newWide, newTall); + _frame->InvalidateLayout(); + _frame->Repaint(); + } + + void OnCursorMoved(int x, int y) + { + if (!_dragging) + return; + + if (!input()->IsMouseDown(MOUSE_LEFT)) + { + // for some reason we're marked as dragging when the mouse is released + // trigger a release + OnMouseReleased(MOUSE_LEFT); + return; + } + + input()->GetCursorPos(x, y); + moved((x - _dragStart[0]), ( y - _dragStart[1])); + _frame->Repaint(); + } + + void OnMousePressed(MouseCode code) + { + if (code == MOUSE_LEFT) + { + _dragging=true; + int x,y; + input()->GetCursorPos(x,y); + _dragStart[0]=x; + _dragStart[1]=y; + _frame->GetPos(_dragOrgPos[0],_dragOrgPos[1]); + _frame->GetSize(_dragOrgSize[0],_dragOrgSize[1]); + input()->SetMouseCapture(GetVPanel()); + + // if a child doesn't have focus, get it for ourselves + VPANEL focus = input()->GetFocus(); + if (!focus || !ipanel()->HasParent(focus, _frame->GetVPanel())) + { + _frame->RequestFocus(); + } + _frame->Repaint(); + } + else + { + GetParent()->OnMousePressed(code); + } + } + + void OnMouseDoublePressed(MouseCode code) + { + GetParent()->OnMouseDoublePressed(code); + } + + void Paint() + { + // draw the grab handle in the bottom right of the frame + surface()->DrawSetTextFont(_marlettFont); + surface()->DrawSetTextPos(0, 0); + + // thin highlight lines + surface()->DrawSetTextColor(GetFgColor()); + surface()->DrawUnicodeChar('p'); + } + + void PaintBackground() + { + // draw the grab handle in the bottom right of the frame + surface()->DrawSetTextFont(_marlettFont); + surface()->DrawSetTextPos(0, 0); + + // thick shadow lines + surface()->DrawSetTextColor(GetBgColor()); + surface()->DrawUnicodeChar('o'); + } + + void OnMouseReleased(MouseCode code) + { + _dragging = false; + input()->SetMouseCapture(NULL); + } + + void OnMouseCaptureLost() + { + Panel::OnMouseCaptureLost(); + _dragging = false; + } + + void ApplySchemeSettings(IScheme *pScheme) + { + Panel::ApplySchemeSettings(pScheme); + bool isSmall = ((Frame *)GetParent())->IsSmallCaption(); + + _marlettFont = pScheme->GetFont( isSmall ? "MarlettSmall" : "Marlett", IsProportional()); + SetFgColor(GetSchemeColor("FrameGrip.Color1", pScheme)); + SetBgColor(GetSchemeColor("FrameGrip.Color2", pScheme)); + + const char *snapRange = pScheme->GetResourceString("Frame.AutoSnapRange"); + if (snapRange && *snapRange) + { + m_iSnapRange = atoi(snapRange); + } + } + + protected: + Frame *_frame; + int _dragMultX; + int _dragMultY; + bool _dragging; + int _dragOrgPos[2]; + int _dragOrgSize[2]; + int _dragStart[2]; + int m_iSnapRange; + HFont _marlettFont; + }; + + //----------------------------------------------------------------------------- + // Purpose: Handles caption grip input for moving dialogs around + //----------------------------------------------------------------------------- + class CaptionGripPanel : public GripPanel + { + public: + CaptionGripPanel(Frame* frame, const char *name) : GripPanel(frame, name, 0, 0) + { + } + + void moved(int dx, int dy) + { + if (!_frame->IsMoveable()) + return; + + int newX = _dragOrgPos[0] + dx; + int newY = _dragOrgPos[1] + dy; + + if (m_iSnapRange) + { + // first check docking to desktop + int wx, wy, ww, wt; + surface()->GetWorkspaceBounds(wx, wy, ww, wt); + getInsideSnapPosition(wx, wy, ww, wt, newX, newY); + + // now lets check all windows and see if we snap to those + // root panel + VPANEL root = surface()->GetEmbeddedPanel(); + // cycle through panels + // look for panels that are visible and are popups that we can dock to + for (int i = 0; i < ipanel()->GetChildCount(root); ++i) + { + VPANEL child = ipanel()->GetChild(root, i); + tryToDock (child, newX, newY); + } + } + + if ( _frame->GetClipToParent() ) + { + // If any coordinate is out of range, snap it back + if ( newX < 0 ) + newX = 0; + if ( newY < 0 ) + newY = 0; + + int sx, sy; + surface()->GetScreenSize( sx, sy ); + + int w, h; + _frame->GetSize( w, h ); + if ( newX + w > sx ) + { + newX = sx - w; + } + if ( newY + h > sy ) + { + newY = sy - h; + } + } + + _frame->SetPos(newX, newY); + + } + + void tryToDock(VPANEL window, int &newX, int & newY) + { + // bail if child is this window + if ( window == _frame->GetVPanel()) + return; + + int cx, cy, cw, ct; + if ( (ipanel()->IsVisible(window)) && (ipanel()->IsPopup(window)) ) + { + // position + ipanel()->GetAbsPos(window, cx, cy); + // dimensions + ipanel()->GetSize(window, cw, ct); + bool snapped = getOutsideSnapPosition (cx, cy, cw, ct, newX, newY); + if (snapped) + { + // if we snapped, we're done with this path + // dont try to snap to kids + return; + } + } + + // check all children + for (int i = 0; i < ipanel()->GetChildCount(window); ++i) + { + VPANEL child = ipanel()->GetChild(window, i); + tryToDock(child, newX, newY); + } + + } + + // Purpose: To calculate the windows new x,y position if it snaps + // Will snap to the INSIDE of a window (eg desktop sides + // Input: boundX boundY, position of candidate window we are seeing if we snap to + // boundWide, boundTall, width and height of window we are seeing if we snap to + // Output: snapToX, snapToY new coords for window, unchanged if we dont snap + // Returns true if we snapped, false if we did not snap. + bool getInsideSnapPosition(int boundX, int boundY, int boundWide, int boundTall, + int &snapToX, int &snapToY) + { + + int wide, tall; + _frame->GetSize(wide, tall); + Assert (wide > 0); + Assert (tall > 0); + + bool snapped=false; + if (abs(snapToX - boundX) < m_iSnapRange) + { + snapToX = boundX; + snapped=true; + } + else if (abs((snapToX + wide) - (boundX + boundWide)) < m_iSnapRange) + { + snapToX = boundX + boundWide - wide; + snapped=true; + } + + if (abs(snapToY - boundY) < m_iSnapRange) + { + snapToY = boundY; + snapped=true; + } + else if (abs((snapToY + tall) - (boundY + boundTall)) < m_iSnapRange) + { + snapToY = boundY + boundTall - tall; + snapped=true; + } + return snapped; + + } + + // Purpose: To calculate the windows new x,y position if it snaps + // Will snap to the OUTSIDE edges of a window (i.e. will stick peers together + // Input: left, top, position of candidate window we are seeing if we snap to + // boundWide, boundTall, width and height of window we are seeing if we snap to + // Output: snapToX, snapToY new coords for window, unchanged if we dont snap + // Returns true if we snapped, false if we did not snap. + bool getOutsideSnapPosition(int left, int top, int boundWide, int boundTall, + int &snapToX, int &snapToY) + { + Assert (boundWide >= 0); + Assert (boundTall >= 0); + + bool snapped=false; + + int right=left+boundWide; + int bottom=top+boundTall; + + int wide, tall; + _frame->GetSize(wide, tall); + Assert (wide > 0); + Assert (tall > 0); + + // we now see if we are going to be able to snap to a window side, and not + // just snap to the "open air" + // want to make it so that if any part of the window can dock to the candidate, it will + + // is this window horizontally snappable to the candidate + bool horizSnappable=( + // top of window is in range + ((snapToY > top) && (snapToY < bottom)) + // bottom of window is in range + || ((snapToY+tall > top) && (snapToY+tall < bottom)) + // window is just plain bigger than the window we wanna dock to + || ((snapToY < top) && (snapToY+tall > bottom)) ); + + + // is this window vertically snappable to the candidate + bool vertSnappable= ( + // left of window is in range + ((snapToX > left) && (snapToX < right)) + // right of window is in range + || ((snapToX+wide > left) && (snapToX+wide < right)) + // window is just plain bigger than the window we wanna dock to + || ((snapToX < left) && (snapToX+wide > right)) ); + + // if neither, might as well bail + if ( !(horizSnappable || vertSnappable) ) + return false; + + //if we're within the snap threshold then snap + if ( (snapToX <= (right+m_iSnapRange)) && + (snapToX >= (right-m_iSnapRange)) ) + { + if (horizSnappable) + { + //disallow "open air" snaps + snapped=true; + snapToX = right; + } + } + else if ((snapToX + wide) >= (left-m_iSnapRange) && + (snapToX + wide) <= (left+m_iSnapRange)) + { + if (horizSnappable) + { + snapped=true; + snapToX = left-wide; + } + } + + if ( (snapToY <= (bottom+m_iSnapRange)) && + (snapToY >= (bottom-m_iSnapRange)) ) + { + if (vertSnappable) + { + snapped=true; + snapToY = bottom; + } + } + else if ((snapToY + tall) <= (top+m_iSnapRange) && + (snapToY + tall) >= (top-m_iSnapRange)) + { + if (vertSnappable) + { + snapped=true; + snapToY = top-tall; + } + } + return snapped; + } + }; + +} + +namespace vgui +{ + //----------------------------------------------------------------------------- + // Purpose: overrides normal button drawing to use different colors & borders + //----------------------------------------------------------------------------- + class FrameButton : public Button + { + private: + IBorder *_brightBorder, *_depressedBorder, *_disabledBorder; + Color _enabledFgColor, _enabledBgColor; + Color _disabledFgColor, _disabledBgColor; + bool _disabledLook; + + public: + + static int GetButtonSide( Frame *pFrame ) + { + if ( pFrame->IsSmallCaption() ) + { + return 12; + } + + return 18; + } + + + FrameButton(Panel *parent, const char *name, const char *text) : Button(parent, name, text) + { + SetSize( FrameButton::GetButtonSide( (Frame *)parent ), FrameButton::GetButtonSide( (Frame *)parent ) ); + _brightBorder = NULL; + _depressedBorder = NULL; + _disabledBorder = NULL; + _disabledLook = true; + SetContentAlignment(Label::a_northwest); + SetTextInset(2, 1); + SetBlockDragChaining( true ); + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + Button::ApplySchemeSettings(pScheme); + + _enabledFgColor = GetSchemeColor("FrameTitleButton.FgColor", pScheme); + _enabledBgColor = GetSchemeColor("FrameTitleButton.BgColor", pScheme); + + _disabledFgColor = GetSchemeColor("FrameTitleButton.DisabledFgColor", pScheme); + _disabledBgColor = GetSchemeColor("FrameTitleButton.DisabledBgColor", pScheme); + + _brightBorder = pScheme->GetBorder("TitleButtonBorder"); + _depressedBorder = pScheme->GetBorder("TitleButtonDepressedBorder"); + _disabledBorder = pScheme->GetBorder("TitleButtonDisabledBorder"); + + SetDisabledLook(_disabledLook); + } + + virtual IBorder *GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) + { + if (_disabledLook) + { + return _disabledBorder; + } + + if (depressed) + { + return _depressedBorder; + } + + return _brightBorder; + } + + virtual void SetDisabledLook(bool state) + { + _disabledLook = state; + if (!_disabledLook) + { + SetDefaultColor(_enabledFgColor, _enabledBgColor); + SetArmedColor(_enabledFgColor, _enabledBgColor); + SetDepressedColor(_enabledFgColor, _enabledBgColor); + } + else + { + // setup disabled colors + SetDefaultColor(_disabledFgColor, _disabledBgColor); + SetArmedColor(_disabledFgColor, _disabledBgColor); + SetDepressedColor(_disabledFgColor, _disabledBgColor); + } + } + + virtual void PerformLayout() + { + Button::PerformLayout(); + Repaint(); + } + + // Don't request focus. + // This will keep items in the listpanel selected. + virtual void OnMousePressed(MouseCode code) + { + if (!IsEnabled()) + return; + + if (!IsMouseClickEnabled(code)) + return; + + if (IsUseCaptureMouseEnabled()) + { + { + SetSelected(true); + Repaint(); + } + + // lock mouse input to going to this button + input()->SetMouseCapture(GetVPanel()); + } + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: icon button +//----------------------------------------------------------------------------- +class FrameSystemButton : public MenuButton +{ + DECLARE_CLASS_SIMPLE( FrameSystemButton, MenuButton ); + +private: + IImage *_enabled, *_disabled; + Color _enCol, _disCol; + bool _respond; + CUtlString m_EnabledImage; + CUtlString m_DisabledImage; + +public: + FrameSystemButton(Panel *parent, const char *panelName) : MenuButton(parent, panelName, "") + { + _disabled = _enabled = NULL; + _respond = true; + SetEnabled(false); + // This menu will open if we use the left or right mouse button + SetMouseClickEnabled( MOUSE_RIGHT, true ); + SetBlockDragChaining( true ); + } + + void SetImages( const char *pEnabledImage, const char *pDisabledImage = NULL ) + { + m_EnabledImage = pEnabledImage; + m_DisabledImage = pDisabledImage ? pDisabledImage : pEnabledImage; + } + + void GetImageSize( int &w, int &h ) + { + w = h = 0; + + int tw = 0, th = 0; + if ( _enabled ) + { + _enabled->GetSize( w, h ); + } + if ( _disabled ) + { + _disabled->GetSize( tw, th ); + } + if ( tw > w ) + { + w = tw; + } + if ( th > h ) + { + h = th; + } + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + BaseClass::ApplySchemeSettings(pScheme); + + _enCol = GetSchemeColor("FrameSystemButton.FgColor", pScheme); + _disCol = GetSchemeColor("FrameSystemButton.BgColor", pScheme); + + const char *pEnabledImage = m_EnabledImage.Length() ? m_EnabledImage.Get() : + pScheme->GetResourceString( "FrameSystemButton.Icon" ); + const char *pDisabledImage = m_DisabledImage.Length() ? m_DisabledImage.Get() : + pScheme->GetResourceString( "FrameSystemButton.DisabledIcon" ); + _enabled = scheme()->GetImage( pEnabledImage, false); + _disabled = scheme()->GetImage( pDisabledImage, false); + + SetTextInset(0, 0); + + // get our iconic image + SetEnabled(IsEnabled()); + } + + virtual IBorder *GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) + { + return NULL; + } + + virtual void SetEnabled(bool state) + { + Button::SetEnabled(state); + + if (IsEnabled()) + { + if ( _enabled ) + { + SetImageAtIndex(0, _enabled, 0); + } + SetBgColor(_enCol); + SetDefaultColor(_enCol, _enCol); + SetArmedColor(_enCol, _enCol); + SetDepressedColor(_enCol, _enCol); + } + else + { + if ( _disabled ) + { + SetImageAtIndex(0, _disabled, 0); + } + SetBgColor(_disCol); + SetDefaultColor(_disCol, _disCol); + SetArmedColor(_disCol, _disCol); + SetDepressedColor(_disCol, _disCol); + } + } + + void SetResponsive(bool state) + { + _respond = state; + } + + virtual void OnMousePressed(MouseCode code) + { + // button may look enabled but not be responsive + if (!_respond) + return; + + BaseClass::OnMousePressed(code); + } + + virtual void OnMouseDoublePressed(MouseCode code) + { + // button may look enabled but not be responsive + if (!_respond) + return; + + // only close if left is double pressed + if (code == MOUSE_LEFT) + { + // double click on the icon closes the window + // But only if the menu contains a 'close' item + vgui::Menu *pMenu = GetMenu(); + if ( pMenu && pMenu->FindChildByName("Close") ) + { + PostMessage(GetVParent(), new KeyValues("CloseFrameButtonPressed")); + } + } + } + +}; + +} // namespace vgui +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Frame::Frame(Panel *parent, const char *panelName, bool showTaskbarIcon /*=true*/, bool bPopup /*=true*/ ) : EditablePanel(parent, panelName) +{ + // frames start invisible, to avoid having window flicker in on taskbar + SetVisible(false); + if ( bPopup ) + { + MakePopup(showTaskbarIcon); + } + + m_hPreviousModal = 0; + + _title=null; + _moveable=true; + _sizeable=true; + m_bHasFocus=false; + _flashWindow=false; + _drawTitleBar = true; + m_bPreviouslyVisible = false; + m_bFadingOut = false; + m_bDisableFadeEffect = false; + m_flTransitionEffectTime = 0.0f; + m_flFocusTransitionEffectTime = 0.0f; + m_bDeleteSelfOnClose = false; + m_iClientInsetX = 5; + m_iClientInsetY = 5; + m_iClientInsetXOverridden = false; + m_iTitleTextInsetX = 28; + m_bClipToParent = false; + m_bSmallCaption = false; + m_bChainKeysToParent = false; + m_bPrimed = false; + m_hCustomTitleFont = INVALID_FONT; + + SetTitle("#Frame_Untitled", parent ? false : true); + + // add ourselves to the build group + SetBuildGroup(GetBuildGroup()); + + SetMinimumSize(128,66); + + GetFocusNavGroup().SetFocusTopLevel(true); + +#if !defined( _X360 ) + _sysMenu = NULL; + + // add dragging grips + _topGrip = new GripPanel(this, "frame_topGrip", 0, -1); + _bottomGrip = new GripPanel(this, "frame_bottomGrip", 0, 1); + _leftGrip = new GripPanel(this, "frame_leftGrip", -1, 0); + _rightGrip = new GripPanel(this, "frame_rightGrip", 1, 0); + _topLeftGrip = new GripPanel(this, "frame_tlGrip", -1, -1); + _topRightGrip = new GripPanel(this, "frame_trGrip", 1, -1); + _bottomLeftGrip = new GripPanel(this, "frame_blGrip", -1, 1); + _bottomRightGrip = new GripPanel(this, "frame_brGrip", 1, 1); + _captionGrip = new CaptionGripPanel(this, "frame_caption" ); + _captionGrip->SetCursor(dc_arrow); + + _minimizeButton = new FrameButton(this, "frame_minimize","0"); + _minimizeButton->AddActionSignalTarget(this); + _minimizeButton->SetCommand(new KeyValues("Minimize")); + + _maximizeButton = new FrameButton(this, "frame_maximize", "1"); + //!! no maximize handler implemented yet, so leave maximize button disabled + SetMaximizeButtonVisible(false); + + char str[] = { 0x6F, 0 }; + _minimizeToSysTrayButton = new FrameButton(this, "frame_mintosystray", str); + _minimizeToSysTrayButton->SetCommand("MinimizeToSysTray"); + SetMinimizeToSysTrayButtonVisible(false); + + _closeButton = new FrameButton(this, "frame_close", "r"); + _closeButton->AddActionSignalTarget(this); + _closeButton->SetCommand(new KeyValues("CloseFrameButtonPressed")); + + if (!surface()->SupportsFeature(ISurface::FRAME_MINIMIZE_MAXIMIZE)) + { + SetMinimizeButtonVisible(false); + SetMaximizeButtonVisible(false); + } + + if (parent) + { + // vgui doesn't support subwindow minimization + SetMinimizeButtonVisible(false); + SetMaximizeButtonVisible(false); + } + + _menuButton = new FrameSystemButton(this, "frame_menu"); + _menuButton->SetMenu(GetSysMenu()); +#endif + + SetupResizeCursors(); + + REGISTER_COLOR_AS_OVERRIDABLE( m_InFocusBgColor, "infocus_bgcolor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( m_OutOfFocusBgColor, "outoffocus_bgcolor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _titleBarBgColor, "titlebarbgcolor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _titleBarDisabledBgColor, "titlebardisabledbgcolor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _titleBarFgColor, "titlebarfgcolor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _titleBarDisabledFgColor, "titlebardisabledfgcolor_override" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Frame::~Frame() +{ + if ( input()->GetAppModalSurface() == GetVPanel() ) + { + vgui::input()->ReleaseAppModalSurface(); + if ( m_hPreviousModal != 0 ) + { + vgui::input()->SetAppModalSurface( m_hPreviousModal ); + m_hPreviousModal = 0; + } + } + +#if !defined( _X360 ) + delete _topGrip; + delete _bottomGrip; + delete _leftGrip; + delete _rightGrip; + delete _topLeftGrip; + delete _topRightGrip; + delete _bottomLeftGrip; + delete _bottomRightGrip; + delete _captionGrip; + delete _minimizeButton; + delete _maximizeButton; + delete _closeButton; + delete _menuButton; + delete _minimizeToSysTrayButton; +#endif + delete _title; +} + +//----------------------------------------------------------------------------- +// Purpose: Setup the grips on the edges of the panel to resize it. +//----------------------------------------------------------------------------- +void Frame::SetupResizeCursors() +{ +#if !defined( _X360 ) + if (IsSizeable()) + { + _topGrip->SetCursor(dc_sizens); + _bottomGrip->SetCursor(dc_sizens); + _leftGrip->SetCursor(dc_sizewe); + _rightGrip->SetCursor(dc_sizewe); + _topLeftGrip->SetCursor(dc_sizenwse); + _topRightGrip->SetCursor(dc_sizenesw); + _bottomLeftGrip->SetCursor(dc_sizenesw); + _bottomRightGrip->SetCursor(dc_sizenwse); + + _bottomRightGrip->SetPaintEnabled(true); + _bottomRightGrip->SetPaintBackgroundEnabled(true); + } + else + { + // not resizable, so just use the default cursor + _topGrip->SetCursor(dc_arrow); + _bottomGrip->SetCursor(dc_arrow); + _leftGrip->SetCursor(dc_arrow); + _rightGrip->SetCursor(dc_arrow); + _topLeftGrip->SetCursor(dc_arrow); + _topRightGrip->SetCursor(dc_arrow); + _bottomLeftGrip->SetCursor(dc_arrow); + _bottomRightGrip->SetCursor(dc_arrow); + + _bottomRightGrip->SetPaintEnabled(false); + _bottomRightGrip->SetPaintBackgroundEnabled(false); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Bring the frame to the front and requests focus, ensures it's not minimized +//----------------------------------------------------------------------------- +void Frame::Activate() +{ + MoveToFront(); + if ( IsKeyBoardInputEnabled() ) + { + RequestFocus(); + } + SetVisible(true); + SetEnabled(true); + if (m_bFadingOut) + { + // we were fading out, make sure to fade back in + m_bFadingOut = false; + m_bPreviouslyVisible = false; + } + + surface()->SetMinimized(GetVPanel(), false); +} + + +//----------------------------------------------------------------------------- +// Sets up, cleans up modal dialogs +//----------------------------------------------------------------------------- +void Frame::DoModal( ) +{ + // move to the middle of the screen + MoveToCenterOfScreen(); + InvalidateLayout(); + Activate(); + m_hPreviousModal = vgui::input()->GetAppModalSurface(); + vgui::input()->SetAppModalSurface( GetVPanel() ); +} + + +//----------------------------------------------------------------------------- +// Closes a modal dialog +//----------------------------------------------------------------------------- +void Frame::CloseModal() +{ + vgui::input()->ReleaseAppModalSurface(); + if ( m_hPreviousModal != 0 ) + { + vgui::input()->SetAppModalSurface( m_hPreviousModal ); + m_hPreviousModal = 0; + } + PostMessage( this, new KeyValues("Close") ); +} + + +//----------------------------------------------------------------------------- +// Purpose: activates the dialog +// if dialog is not currently visible it starts it minimized and flashing in the taskbar +//----------------------------------------------------------------------------- +void Frame::ActivateMinimized() +{ + if ( ( IsVisible() && !IsMinimized() ) || !surface()->SupportsFeature( ISurface::FRAME_MINIMIZE_MAXIMIZE ) ) + { + Activate(); + } + else + { + ipanel()->MoveToBack(GetVPanel()); + surface()->SetMinimized(GetVPanel(), true); + SetVisible(true); + SetEnabled(true); + if (m_bFadingOut) + { + // we were fading out, make sure to fade back in + m_bFadingOut = false; + m_bPreviouslyVisible = false; + } + FlashWindow(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the dialog is currently minimized +//----------------------------------------------------------------------------- +bool Frame::IsMinimized() +{ + return surface()->IsMinimized(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: Center the dialog on the screen +//----------------------------------------------------------------------------- +void Frame::MoveToCenterOfScreen() +{ + int wx, wy, ww, wt; + surface()->GetWorkspaceBounds(wx, wy, ww, wt); + SetPos((ww - GetWide()) / 2, (wt - GetTall()) / 2); +} + + +void Frame::LayoutProportional( FrameButton *bt ) +{ + float scale = 1.0; + + if( IsProportional() ) + { + int screenW, screenH; + surface()->GetScreenSize( screenW, screenH ); + + int proW,proH; + surface()->GetProportionalBase( proW, proH ); + + scale = ( (float)( screenH ) / (float)( proH ) ); + } + + bt->SetSize( (int)( FrameButton::GetButtonSide( this ) * scale ), (int)( FrameButton::GetButtonSide( this ) * scale ) ); + bt->SetTextInset( (int)( ceil( 2 * scale ) ), (int) ( ceil(1 * scale ) ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: per-frame thinking, used for transition effects +// only gets called if the Frame is visible +//----------------------------------------------------------------------------- +void Frame::OnThink() +{ + BaseClass::OnThink(); + + // check for transition effects + if (IsVisible() && m_flTransitionEffectTime > 0 && ( !m_bDisableFadeEffect )) + { + if (m_bFadingOut) + { + // we're fading out, see if we're done so we can fully hide the window + if (GetAlpha() < ( IsX360() ? 64 : 1 )) + { + FinishClose(); + } + } + else if (!m_bPreviouslyVisible) + { + // need to fade-in + m_bPreviouslyVisible = true; + + // fade in + if (IsX360()) + { + SetAlpha(64); + } + else + { + SetAlpha(0); + } + GetAnimationController()->RunAnimationCommand(this, "alpha", 255.0f, 0.0f, m_flTransitionEffectTime, AnimationController::INTERPOLATOR_LINEAR); + } + } + + // check for focus changes + bool hasFocus = false; + + if (input()) + { + VPANEL focus = input()->GetFocus(); + if (focus && ipanel()->HasParent(focus, GetVPanel())) + { + if ( input()->GetAppModalSurface() == 0 || + input()->GetAppModalSurface() == GetVPanel() ) + { + hasFocus = true; + } + } + } + if (hasFocus != m_bHasFocus) + { + // Because vgui focus is message based, and focus gets reset to NULL when a focused panel is deleted, we defer the flashing/transition + // animation for an extra frame in case something is deleted, a message is sent, and then we become the focused panel again on the + // next frame + if ( !m_bPrimed ) + { + m_bPrimed = true; + return; + } + m_bPrimed = false; + m_bHasFocus = hasFocus; + OnFrameFocusChanged(m_bHasFocus); + } + else + { + m_bPrimed = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the frame focus changes +//----------------------------------------------------------------------------- +void Frame::OnFrameFocusChanged(bool bHasFocus) +{ +#if !defined( _X360 ) + // enable/disable the frame buttons + _minimizeButton->SetDisabledLook(!bHasFocus); + _maximizeButton->SetDisabledLook(!bHasFocus); + _closeButton->SetDisabledLook(!bHasFocus); + _minimizeToSysTrayButton->SetDisabledLook(!bHasFocus); + _menuButton->SetEnabled(bHasFocus); + _minimizeButton->InvalidateLayout(); + _maximizeButton->InvalidateLayout(); + _minimizeToSysTrayButton->InvalidateLayout(); + _closeButton->InvalidateLayout(); + _menuButton->InvalidateLayout(); +#endif + + if (bHasFocus) + { + _title->SetColor(_titleBarFgColor); + } + else + { + _title->SetColor(_titleBarDisabledFgColor); + } + + // set our background color + if (bHasFocus) + { + if (m_flFocusTransitionEffectTime && ( !m_bDisableFadeEffect )) + { + GetAnimationController()->RunAnimationCommand(this, "BgColor", m_InFocusBgColor, 0.0f, m_bDisableFadeEffect ? 0.0f : m_flTransitionEffectTime, AnimationController::INTERPOLATOR_LINEAR); + } + else + { + SetBgColor(m_InFocusBgColor); + } + } + else + { + if (m_flFocusTransitionEffectTime && ( !m_bDisableFadeEffect )) + { + GetAnimationController()->RunAnimationCommand(this, "BgColor", m_OutOfFocusBgColor, 0.0f, m_bDisableFadeEffect ? 0.0f : m_flTransitionEffectTime, AnimationController::INTERPOLATOR_LINEAR); + } + else + { + SetBgColor(m_OutOfFocusBgColor); + } + } + + // Stop flashing when we get focus + if (bHasFocus && _flashWindow) + { + FlashWindowStop(); + } +} + +int Frame::GetDraggerSize() +{ + const int DRAGGER_SIZE = 5; + if ( m_bSmallCaption ) + { + return 3; + } + + return DRAGGER_SIZE; +} + +int Frame::GetCornerSize() +{ + const int CORNER_SIZE = 8; + if ( m_bSmallCaption ) + { + return 6; + } + + return CORNER_SIZE; +} + +int Frame::GetBottomRightSize() +{ + const int BOTTOMRIGHTSIZE = 18; + if ( m_bSmallCaption ) + { + return 12; + } + + return BOTTOMRIGHTSIZE; +} + +int Frame::GetCaptionHeight() +{ + const int CAPTIONHEIGHT = 23; + if ( m_bSmallCaption ) + { + return 12; + } + return CAPTIONHEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the position of all items +//----------------------------------------------------------------------------- +void Frame::PerformLayout() +{ + // chain back + BaseClass::PerformLayout(); + + // move everything into place + int wide, tall; + GetSize(wide, tall); + +#if !defined( _X360 ) + int DRAGGER_SIZE = GetDraggerSize(); + int CORNER_SIZE = GetCornerSize(); + int CORNER_SIZE2 = CORNER_SIZE * 2; + int BOTTOMRIGHTSIZE = GetBottomRightSize(); + + _topGrip->SetBounds(CORNER_SIZE, 0, wide - CORNER_SIZE2, DRAGGER_SIZE); + _leftGrip->SetBounds(0, CORNER_SIZE, DRAGGER_SIZE, tall - CORNER_SIZE2); + _topLeftGrip->SetBounds(0, 0, CORNER_SIZE, CORNER_SIZE); + _topRightGrip->SetBounds(wide - CORNER_SIZE, 0, CORNER_SIZE, CORNER_SIZE); + _bottomLeftGrip->SetBounds(0, tall - CORNER_SIZE, CORNER_SIZE, CORNER_SIZE); + + // make the bottom-right grip larger + _bottomGrip->SetBounds(CORNER_SIZE, tall - DRAGGER_SIZE, wide - (CORNER_SIZE + BOTTOMRIGHTSIZE), DRAGGER_SIZE); + _rightGrip->SetBounds(wide - DRAGGER_SIZE, CORNER_SIZE, DRAGGER_SIZE, tall - (CORNER_SIZE + BOTTOMRIGHTSIZE)); + + _bottomRightGrip->SetBounds(wide - BOTTOMRIGHTSIZE, tall - BOTTOMRIGHTSIZE, BOTTOMRIGHTSIZE, BOTTOMRIGHTSIZE); + + _captionGrip->SetSize(wide-10,GetCaptionHeight()); + + _topGrip->MoveToFront(); + _bottomGrip->MoveToFront(); + _leftGrip->MoveToFront(); + _rightGrip->MoveToFront(); + _topLeftGrip->MoveToFront(); + _topRightGrip->MoveToFront(); + _bottomLeftGrip->MoveToFront(); + _bottomRightGrip->MoveToFront(); + + _maximizeButton->MoveToFront(); + _menuButton->MoveToFront(); + _minimizeButton->MoveToFront(); + _minimizeToSysTrayButton->MoveToFront(); + _menuButton->SetBounds(5+2, 5+3, GetCaptionHeight()-5, GetCaptionHeight()-5); +#endif + + float scale = 1; + if (IsProportional()) + { + int screenW, screenH; + surface()->GetScreenSize( screenW, screenH ); + + int proW,proH; + surface()->GetProportionalBase( proW, proH ); + + scale = ( (float)( screenH ) / (float)( proH ) ); + } + +#if !defined( _X360 ) + int offset_start = (int)( 20 * scale ); + int offset = offset_start; + + int top_border_offset = (int) ( ( 5+3 ) * scale ); + if ( m_bSmallCaption ) + { + top_border_offset = (int) ( ( 3 ) * scale ); + } + + int side_border_offset = (int) ( 5 * scale ); + // push the buttons against the east side + if (_closeButton->IsVisible()) + { + _closeButton->SetPos((wide-side_border_offset)-offset,top_border_offset); + offset += offset_start; + LayoutProportional( _closeButton ); + + } + if (_minimizeToSysTrayButton->IsVisible()) + { + _minimizeToSysTrayButton->SetPos((wide-side_border_offset)-offset,top_border_offset); + offset += offset_start; + LayoutProportional( _minimizeToSysTrayButton ); + } + if (_maximizeButton->IsVisible()) + { + _maximizeButton->SetPos((wide-side_border_offset)-offset,top_border_offset); + offset += offset_start; + LayoutProportional( _maximizeButton ); + } + if (_minimizeButton->IsVisible()) + { + _minimizeButton->SetPos((wide-side_border_offset)-offset,top_border_offset); + offset += offset_start; + LayoutProportional( _minimizeButton ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text in the title bar. +//----------------------------------------------------------------------------- +void Frame::SetTitle(const char *title, bool surfaceTitle) +{ + if (!_title) + { + _title = new TextImage( "" ); + } + + Assert(title); + _title->SetText(title); + + // see if the combobox text has changed, and if so, post a message detailing the new text + const char *newTitle = title; + + // check if the new text is a localized string, if so undo it + wchar_t unicodeText[128]; + unicodeText[0] = 0; + if (*newTitle == '#') + { + // try lookup in localization tables + StringIndex_t unlocalizedTextSymbol = g_pVGuiLocalize->FindIndex(newTitle + 1); + if (unlocalizedTextSymbol != INVALID_LOCALIZE_STRING_INDEX) + { + // we have a new text value + wcsncpy( unicodeText, g_pVGuiLocalize->GetValueByIndex(unlocalizedTextSymbol), sizeof( unicodeText) / sizeof(wchar_t) ); + } + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode( newTitle, unicodeText, sizeof(unicodeText) ); + } + + if (surfaceTitle) + { + surface()->SetTitle(GetVPanel(), unicodeText); + } + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the unicode text in the title bar +//----------------------------------------------------------------------------- +void Frame::SetTitle(const wchar_t *title, bool surfaceTitle) +{ + if (!_title) + { + _title = new TextImage( "" ); + } + _title->SetText(title); + if (surfaceTitle) + { + surface()->SetTitle(GetVPanel(), title); + } + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text in the title bar. +//----------------------------------------------------------------------------- +void Frame::InternalSetTitle(const char *title) +{ + SetTitle(title, true); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the movability of the panel +//----------------------------------------------------------------------------- +void Frame::SetMoveable(bool state) +{ + _moveable=state; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the resizability of the panel +//----------------------------------------------------------------------------- +void Frame::SetSizeable(bool state) +{ + _sizeable=state; + + SetupResizeCursors(); +} + +// When moving via caption, don't let any part of window go outside parent's bounds +void Frame::SetClipToParent( bool state ) +{ + m_bClipToParent = state; +} + +bool Frame::GetClipToParent() const +{ + return m_bClipToParent; +} + +//----------------------------------------------------------------------------- +// Purpose: Check the movability of the panel +//----------------------------------------------------------------------------- +bool Frame::IsMoveable() +{ + return _moveable; +} + +//----------------------------------------------------------------------------- +// Purpose: Check the resizability of the panel +//----------------------------------------------------------------------------- +bool Frame::IsSizeable() +{ + return _sizeable; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the size of the panel inside the frame edges. +//----------------------------------------------------------------------------- +void Frame::GetClientArea(int &x, int &y, int &wide, int &tall) +{ + x = m_iClientInsetX; + + GetSize(wide, tall); + + if (_drawTitleBar) + { + int captionTall = surface()->GetFontTall(_title->GetFont()); + + int border = m_bSmallCaption ? CAPTION_TITLE_BORDER_SMALL : CAPTION_TITLE_BORDER; + int yinset = m_bSmallCaption ? 0 : m_iClientInsetY; + + yinset += m_iTitleTextInsetYOverride; + + y = yinset + captionTall + border + 1; + tall = (tall - yinset) - y; + } + + if ( m_bSmallCaption ) + { + tall -= 5; + } + + wide = (wide - m_iClientInsetX) - x; +} + +// +//----------------------------------------------------------------------------- +// Purpose: applies user configuration settings +//----------------------------------------------------------------------------- +void Frame::ApplyUserConfigSettings(KeyValues *userConfig) +{ + // calculate defaults + int wx, wy, ww, wt; + vgui::surface()->GetWorkspaceBounds(wx, wy, ww, wt); + + int x, y, wide, tall; + GetBounds(x, y, wide, tall); + bool bNoSettings = false; + if (_moveable) + { + // check to see if anything is set + if (!userConfig->FindKey("xpos", false)) + { + bNoSettings = true; + } + + // get the user config position + // default to where we're currently at + x = userConfig->GetInt("xpos", x); + y = userConfig->GetInt("ypos", y); + } + if (_sizeable) + { + wide = userConfig->GetInt("wide", wide); + tall = userConfig->GetInt("tall", tall); + + // Make sure it's no larger than the workspace + if ( wide > ww ) + { + wide = ww; + } + if ( tall > wt ) + { + tall = wt; + } + } + + // see if the dialog has a place on the screen it wants to start + if (bNoSettings && GetDefaultScreenPosition(x, y, wide, tall)) + { + bNoSettings = false; + } + + // make sure it conforms to the minimum size of the dialog + int minWide, minTall; + GetMinimumSize(minWide, minTall); + if (wide < minWide) + { + wide = minWide; + } + if (tall < minTall) + { + tall = minTall; + } + + // make sure it's on the screen + if (x + wide > ww) + { + x = wx + ww - wide; + } + if (y + tall > wt) + { + y = wy + wt - tall; + } + + if (x < wx) + { + x = wx; + } + if (y < wy) + { + y = wy; + } + + SetBounds(x, y, wide, tall); + + if (bNoSettings) + { + // since nothing was set, default our position to the middle of the screen + MoveToCenterOfScreen(); + } + + BaseClass::ApplyUserConfigSettings(userConfig); +} + +//----------------------------------------------------------------------------- +// Purpose: returns user config settings for this control +//----------------------------------------------------------------------------- +void Frame::GetUserConfigSettings(KeyValues *userConfig) +{ + if (_moveable) + { + int x, y; + GetPos(x, y); + userConfig->SetInt("xpos", x); + userConfig->SetInt("ypos", y); + } + if (_sizeable) + { + int w, t; + GetSize(w, t); + userConfig->SetInt("wide", w); + userConfig->SetInt("tall", t); + } + + BaseClass::GetUserConfigSettings(userConfig); +} + +//----------------------------------------------------------------------------- +// Purpose: optimization, return true if this control has any user config settings +//----------------------------------------------------------------------------- +bool Frame::HasUserConfigSettings() +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the default position and size on the screen to appear the first time (defaults to centered) +//----------------------------------------------------------------------------- +bool Frame::GetDefaultScreenPosition(int &x, int &y, int &wide, int &tall) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: draws title bar +//----------------------------------------------------------------------------- +void Frame::PaintBackground() +{ + // take the panel with focus and check up tree for this panel + // if you find it, than some child of you has the focus, so + // you should be focused + Color titleColor = _titleBarDisabledBgColor; + if (m_bHasFocus) + { + titleColor = _titleBarBgColor; + } + + BaseClass::PaintBackground(); + + if (_drawTitleBar) + { + int wide = GetWide(); + int tall = surface()->GetFontTall(_title->GetFont()); + + // caption + surface()->DrawSetColor(titleColor); + int inset = m_bSmallCaption ? 3 : 5; + int captionHeight = m_bSmallCaption ? 14: 28; + + surface()->DrawFilledRect(inset, inset, wide - inset, captionHeight ); + + if (_title) + { + int nTitleX = m_iTitleTextInsetXOverride ? m_iTitleTextInsetXOverride : m_iTitleTextInsetX; + int nTitleWidth = wide - 72; +#if !defined( _X360 ) + if ( _menuButton && _menuButton->IsVisible() ) + { + int mw, mh; + _menuButton->GetImageSize( mw, mh ); + nTitleX += mw; + nTitleWidth -= mw; + } +#endif + int nTitleY; + if ( m_iTitleTextInsetYOverride ) + { + nTitleY = m_iTitleTextInsetYOverride; + } + else + { + nTitleY = m_bSmallCaption ? 2 : 9; + } + _title->SetPos( nTitleX, nTitleY ); + _title->SetSize( nTitleWidth, tall); + _title->Paint(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Frame::ApplySchemeSettings(IScheme *pScheme) +{ + // always chain back + BaseClass::ApplySchemeSettings(pScheme); + + SetOverridableColor( &_titleBarFgColor, GetSchemeColor("FrameTitleBar.TextColor", pScheme) ); + SetOverridableColor( &_titleBarBgColor, GetSchemeColor("FrameTitleBar.BgColor", pScheme) ); + SetOverridableColor( &_titleBarDisabledFgColor, GetSchemeColor("FrameTitleBar.DisabledTextColor", pScheme) ); + SetOverridableColor( &_titleBarDisabledBgColor, GetSchemeColor("FrameTitleBar.DisabledBgColor", pScheme) ); + + const char *font = NULL; + if ( m_bSmallCaption ) + { + font = pScheme->GetResourceString("FrameTitleBar.SmallFont"); + } + else + { + font = pScheme->GetResourceString("FrameTitleBar.Font"); + } + + HFont titlefont; + if ( m_hCustomTitleFont ) + { + titlefont = m_hCustomTitleFont; + } + else + { + titlefont = pScheme->GetFont((font && *font) ? font : "Default", IsProportional()); + } + + _title->SetFont( titlefont ); + _title->ResizeImageToContent(); + +#if !defined( _X360 ) + HFont marfont = (HFont)0; + if ( m_bSmallCaption ) + { + marfont = pScheme->GetFont( "MarlettSmall", IsProportional() ); + } + else + { + marfont = pScheme->GetFont( "Marlett", IsProportional() ); + } + + _minimizeButton->SetFont(marfont); + _maximizeButton->SetFont(marfont); + _minimizeToSysTrayButton->SetFont(marfont); + _closeButton->SetFont(marfont); +#endif + + m_flTransitionEffectTime = atof(pScheme->GetResourceString("Frame.TransitionEffectTime")); + m_flFocusTransitionEffectTime = atof(pScheme->GetResourceString("Frame.FocusTransitionEffectTime")); + + SetOverridableColor( &m_InFocusBgColor, pScheme->GetColor("Frame.BgColor", GetBgColor()) ); + SetOverridableColor( &m_OutOfFocusBgColor, pScheme->GetColor("Frame.OutOfFocusBgColor", m_InFocusBgColor) ); + + const char *resourceString = pScheme->GetResourceString("Frame.ClientInsetX"); + if ( resourceString ) + { + m_iClientInsetX = atoi(resourceString); + } + resourceString = pScheme->GetResourceString("Frame.ClientInsetY"); + if ( resourceString ) + { + m_iClientInsetY = atoi(resourceString); + } + resourceString = pScheme->GetResourceString("Frame.TitleTextInsetX"); + if ( resourceString ) + { + m_iTitleTextInsetX = atoi(resourceString); + } + + SetBgColor(m_InFocusBgColor); + SetBorder(pScheme->GetBorder("FrameBorder")); + + OnFrameFocusChanged( m_bHasFocus ); +} + +// Disables the fade-in/out-effect even if configured in the scheme settings +void Frame::DisableFadeEffect( void ) +{ + m_flFocusTransitionEffectTime = 0.f; + m_flTransitionEffectTime = 0.f; +} + +void Frame::SetFadeEffectDisableOverride( bool disabled ) +{ + m_bDisableFadeEffect = disabled; +} + +//----------------------------------------------------------------------------- +// Purpose: Apply settings loaded from a resource file +//----------------------------------------------------------------------------- +void Frame::ApplySettings(KeyValues *inResourceData) +{ + // Don't change the frame's visibility, remove that setting from the config data + inResourceData->SetInt("visible", -1); + BaseClass::ApplySettings(inResourceData); + + SetCloseButtonVisible( inResourceData->GetBool( "setclosebuttonvisible", true ) ); + + if( !inResourceData->GetInt("settitlebarvisible", 1 ) ) // if "title" is "0" then don't draw the title bar + { + SetTitleBarVisible( false ); + } + + // set the title + const char *title = inResourceData->GetString("title", ""); + if (title && *title) + { + SetTitle(title, true); + } + + const char *titlefont = inResourceData->GetString("title_font", ""); + if ( titlefont && titlefont[0] ) + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + if ( pScheme ) + { + m_hCustomTitleFont = pScheme->GetFont( titlefont ); + } + } + + KeyValues *pKV = inResourceData->FindKey( "clientinsetx_override", false ); + if ( pKV ) + { + m_iClientInsetX = pKV->GetInt(); + m_iClientInsetXOverridden = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Apply settings loaded from a resource file +//----------------------------------------------------------------------------- +void Frame::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + outResourceData->SetInt("settitlebarvisible", _drawTitleBar ); + + if (_title) + { + char buf[256]; + _title->GetUnlocalizedText( buf, 255 ); + if (buf[0]) + { + outResourceData->SetString("title", buf); + } + } + + if ( m_iClientInsetXOverridden ) + { + outResourceData->SetInt( "clientinsetx_override", m_iClientInsetX ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns a description of the settings possible for a frame +//----------------------------------------------------------------------------- +const char *Frame::GetDescription() +{ + static char buf[512]; + Q_snprintf(buf, sizeof(buf), "%s, string title", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: Go invisible when a close message is recieved. +//----------------------------------------------------------------------------- +void Frame::OnClose() +{ + // if we're modal, release that before we hide the window else the wrong window will get focus + if (input()->GetAppModalSurface() == GetVPanel()) + { + input()->ReleaseAppModalSurface(); + if ( m_hPreviousModal != 0 ) + { + vgui::input()->SetAppModalSurface( m_hPreviousModal ); + m_hPreviousModal = 0; + } + } + + BaseClass::OnClose(); + + if (m_flTransitionEffectTime && !m_bDisableFadeEffect) + { + // begin the hide transition effect + GetAnimationController()->RunAnimationCommand(this, "alpha", 0.0f, 0.0f, m_flTransitionEffectTime, AnimationController::INTERPOLATOR_LINEAR); + m_bFadingOut = true; + // move us to the back of the draw order (so that fading out over the top of other dialogs doesn't look wierd) + surface()->MovePopupToBack(GetVPanel()); + } + else + { + // hide us immediately + FinishClose(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Close button in frame pressed +//----------------------------------------------------------------------------- +void Frame::OnCloseFrameButtonPressed() +{ + OnCommand("Close"); +} + +//----------------------------------------------------------------------------- +// Purpose: Command handling +//----------------------------------------------------------------------------- +void Frame::OnCommand(const char *command) +{ + if (!stricmp(command, "Close")) + { + Close(); + } + else if (!stricmp(command, "CloseModal")) + { + CloseModal(); + } + else if (!stricmp(command, "Minimize")) + { + OnMinimize(); + } + else if (!stricmp(command, "MinimizeToSysTray")) + { + OnMinimizeToSysTray(); + } + else + { + BaseClass::OnCommand(command); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the system menu +//----------------------------------------------------------------------------- +Menu *Frame::GetSysMenu() +{ +#if !defined( _X360 ) + if (!_sysMenu) + { + _sysMenu = new Menu(this, NULL); + _sysMenu->SetVisible(false); + _sysMenu->AddActionSignalTarget(this); + + _sysMenu->AddMenuItem("Minimize", "#SysMenu_Minimize", "Minimize", this); + _sysMenu->AddMenuItem("Maximize", "#SysMenu_Maximize", "Maximize", this); + _sysMenu->AddMenuItem("Close", "#SysMenu_Close", "Close", this); + + // check for enabling/disabling menu items + // this might have to be done at other times as well. + Panel *menuItem = _sysMenu->FindChildByName("Minimize"); + if (menuItem) + { + menuItem->SetEnabled(_minimizeButton->IsVisible()); + } + menuItem = _sysMenu->FindChildByName("Maximize"); + if (menuItem) + { + menuItem->SetEnabled(_maximizeButton->IsVisible()); + } + menuItem = _sysMenu->FindChildByName("Close"); + if (menuItem) + { + menuItem->SetEnabled(_closeButton->IsVisible()); + } + } + + return _sysMenu; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Set the system menu +//----------------------------------------------------------------------------- +void Frame::SetSysMenu(Menu *menu) +{ +#if !defined( _X360 ) + if (menu == _sysMenu) + return; + + _sysMenu->MarkForDeletion(); + _sysMenu = menu; + + _menuButton->SetMenu(_sysMenu); +#endif +} + + +//----------------------------------------------------------------------------- +// Set the system menu images +//----------------------------------------------------------------------------- +void Frame::SetImages( const char *pEnabledImage, const char *pDisabledImage ) +{ +#if !defined( _X360 ) + _menuButton->SetImages( pEnabledImage, pDisabledImage ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Close the window +//----------------------------------------------------------------------------- +void Frame::Close() +{ + OnClose(); +} + +//----------------------------------------------------------------------------- +// Purpose: Finishes closing the dialog +//----------------------------------------------------------------------------- +void Frame::FinishClose() +{ + SetVisible(false); + m_bPreviouslyVisible = false; + m_bFadingOut = false; + + OnFinishedClose(); + + if (m_bDeleteSelfOnClose) + { + // Must be last because if vgui is not running then this will call delete this!!! + MarkForDeletion(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Frame::OnFinishedClose() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Minimize the window on the taskbar. +//----------------------------------------------------------------------------- +void Frame::OnMinimize() +{ + surface()->SetMinimized(GetVPanel(), true); +} + +//----------------------------------------------------------------------------- +// Purpose: Does nothing by default +//----------------------------------------------------------------------------- +void Frame::OnMinimizeToSysTray() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to mouse presses +//----------------------------------------------------------------------------- +void Frame::OnMousePressed(MouseCode code) +{ + if (!IsBuildGroupEnabled()) + { + // if a child doesn't have focus, get it for ourselves + VPANEL focus = input()->GetFocus(); + if (!focus || !ipanel()->HasParent(focus, GetVPanel())) + { + RequestFocus(); + } + } + + BaseClass::OnMousePressed(code); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle visibility of the system menu button +//----------------------------------------------------------------------------- +void Frame::SetMenuButtonVisible(bool state) +{ +#if !defined( _X360 ) + _menuButton->SetVisible(state); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle respond of the system menu button +// it will look enabled or disabled in response to the title bar +// but may not activate. +//----------------------------------------------------------------------------- +void Frame::SetMenuButtonResponsive(bool state) +{ +#if !defined( _X360 ) + _menuButton->SetResponsive(state); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle visibility of the minimize button +//----------------------------------------------------------------------------- +void Frame::SetMinimizeButtonVisible(bool state) +{ +#if !defined( _X360 ) + _minimizeButton->SetVisible(state); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle visibility of the maximize button +//----------------------------------------------------------------------------- +void Frame::SetMaximizeButtonVisible(bool state) +{ +#if !defined( _X360 ) + _maximizeButton->SetVisible(state); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Toggles visibility of the minimize-to-systray icon (defaults to false) +//----------------------------------------------------------------------------- +void Frame::SetMinimizeToSysTrayButtonVisible(bool state) +{ +#if !defined( _X360 ) + _minimizeToSysTrayButton->SetVisible(state); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle visibility of the close button +//----------------------------------------------------------------------------- +void Frame::SetCloseButtonVisible(bool state) +{ +#if !defined( _X360 ) + _closeButton->SetVisible(state); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: soaks up any remaining messages +//----------------------------------------------------------------------------- +void Frame::OnKeyCodeReleased(KeyCode code) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: soaks up any remaining messages +//----------------------------------------------------------------------------- +void Frame::OnKeyFocusTicked() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Toggles window flash state on a timer +//----------------------------------------------------------------------------- +void Frame::InternalFlashWindow() +{ + if (_flashWindow) + { + // toggle icon flashing + _nextFlashState = true; + surface()->FlashWindow(GetVPanel(), _nextFlashState); + _nextFlashState = !_nextFlashState; + + PostMessage(this, new KeyValues("FlashWindow"), 1.8f); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds the child to the focus nav group +//----------------------------------------------------------------------------- +void Frame::OnChildAdded(VPANEL child) +{ + BaseClass::OnChildAdded(child); +} + +//----------------------------------------------------------------------------- +// Purpose: Flash the window system tray button until the frame gets focus +//----------------------------------------------------------------------------- +void Frame::FlashWindow() +{ + _flashWindow = true; + _nextFlashState = true; + + InternalFlashWindow(); +} + +//----------------------------------------------------------------------------- +// Purpose: Stops any window flashing +//----------------------------------------------------------------------------- +void Frame::FlashWindowStop() +{ + surface()->FlashWindow(GetVPanel(), false); + _flashWindow = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: load the control settings - should be done after all the children are added to the dialog +//----------------------------------------------------------------------------- +void Frame::LoadControlSettings( const char *dialogResourceName, const char *pathID, KeyValues *pPreloadedKeyValues, KeyValues *pConditions ) +{ + BaseClass::LoadControlSettings( dialogResourceName, pathID, pPreloadedKeyValues, pConditions ); + + // set the focus on the default control + Panel *defaultFocus = GetFocusNavGroup().GetDefaultPanel(); + if (defaultFocus) + { + defaultFocus->RequestFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Checks for ctrl+shift+b hits to enter build mode +// Activates any hotkeys / default buttons +// Swallows any unhandled input +//----------------------------------------------------------------------------- +void Frame::OnKeyCodeTyped(KeyCode code) +{ + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + if ( IsX360() ) + { + vgui::Panel *pMap = FindChildByName( "ControllerMap" ); + if ( pMap && pMap->IsKeyBoardInputEnabled() ) + { + pMap->OnKeyCodeTyped( code ); + return; + } + } + + if ( ctrl && shift && alt && code == KEY_B) + { + // enable build mode + ActivateBuildMode(); + } + else if (ctrl && shift && alt && code == KEY_R) + { + // reload the scheme + VPANEL top = surface()->GetEmbeddedPanel(); + if (top) + { + // reload the data file + scheme()->ReloadSchemes(); + + Panel *panel = ipanel()->GetPanel(top, GetModuleName()); + if (panel) + { + // make the top-level panel reload it's scheme, it will chain down to all the child panels + panel->InvalidateLayout(false, true); + } + } + } + else if (alt && code == KEY_F4) + { + // user has hit the close + PostMessage(this, new KeyValues("CloseFrameButtonPressed")); + } + else if (code == KEY_ENTER) + { + // check for a default button + VPANEL panel = GetFocusNavGroup().GetCurrentDefaultButton(); + if (panel && ipanel()->IsVisible( panel ) && ipanel()->IsEnabled( panel )) + { + // Activate the button + PostMessage(panel, new KeyValues("Hotkey")); + } + } + else if ( code == KEY_ESCAPE && + surface()->SupportsFeature(ISurface::ESCAPE_KEY) && + input()->GetAppModalSurface() == GetVPanel() ) + { + // ESC cancels, unless we're in the engine - in the engine ESC flips between the UI and the game + CloseModal(); + } + // Usually don't chain back as Frames are the end of the line for key presses, unless + // m_bChainKeysToParent is set + else if ( m_bChainKeysToParent ) + { + BaseClass::OnKeyCodeTyped( code ); + } + else + { + input()->OnKeyCodeUnhandled( (int)code ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: If true, then OnKeyCodeTyped messages continue up past the Frame +// Input : state - +//----------------------------------------------------------------------------- +void Frame::SetChainKeysToParent( bool state ) +{ + m_bChainKeysToParent = state; +} + +//----------------------------------------------------------------------------- +// Purpose: If true, then OnKeyCodeTyped messages continue up past the Frame +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Frame::CanChainKeysToParent() const +{ + return m_bChainKeysToParent; +} + +//----------------------------------------------------------------------------- +// Purpose: Checks for ctrl+shift+b hits to enter build mode +// Activates any hotkeys / default buttons +// Swallows any unhandled input +//----------------------------------------------------------------------------- +void Frame::OnKeyTyped(wchar_t unichar) +{ + Panel *panel = GetFocusNavGroup().FindPanelByHotkey(unichar); + if (panel) + { + // tell the panel to Activate + PostMessage(panel, new KeyValues("Hotkey")); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets all title bar controls +//----------------------------------------------------------------------------- +void Frame::SetTitleBarVisible( bool state ) +{ + _drawTitleBar = state; + SetMenuButtonVisible(state); + SetMinimizeButtonVisible(state); + SetMaximizeButtonVisible(state); + SetCloseButtonVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the frame to delete itself on close +//----------------------------------------------------------------------------- +void Frame::SetDeleteSelfOnClose( bool state ) +{ + m_bDeleteSelfOnClose = state; +} + +//----------------------------------------------------------------------------- +// Purpose: updates localized text +//----------------------------------------------------------------------------- +void Frame::OnDialogVariablesChanged( KeyValues *dialogVariables ) +{ + StringIndex_t index = _title->GetUnlocalizedTextSymbol(); + if (index != INVALID_LOCALIZE_STRING_INDEX) + { + // reconstruct the string from the variables + wchar_t buf[1024]; + g_pVGuiLocalize->ConstructString(buf, sizeof(buf), index, dialogVariables); + SetTitle(buf, true); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles staying on screen when the screen size changes +//----------------------------------------------------------------------------- +void Frame::OnScreenSizeChanged(int iOldWide, int iOldTall) +{ + BaseClass::OnScreenSizeChanged(iOldWide, iOldTall); + + if (IsProportional()) + return; + + // make sure we're completely on screen + int iNewWide, iNewTall; + surface()->GetScreenSize(iNewWide, iNewTall); + + int x, y, wide, tall; + GetBounds(x, y, wide, tall); + + // make sure the bottom-right corner is on the screen first + if (x + wide > iNewWide) + { + x = iNewWide - wide; + } + if (y + tall > iNewTall) + { + y = iNewTall - tall; + } + + // make sure the top-left is visible + x = max( 0, x ); + y = max( 0, y ); + + // apply + SetPos(x, y); +} + +//----------------------------------------------------------------------------- +// Purpose: For supporting thin caption bars +// Input : state - +//----------------------------------------------------------------------------- +void Frame::SetSmallCaption( bool state ) +{ + m_bSmallCaption = state; + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Frame::IsSmallCaption() const +{ + return m_bSmallCaption; +} + + +//----------------------------------------------------------------------------- +// Purpose: Static method to place a frame under the cursor +//----------------------------------------------------------------------------- +void Frame::PlaceUnderCursor( ) +{ + // get cursor position, this is local to this text edit window + int cursorX, cursorY; + input()->GetCursorPos( cursorX, cursorY ); + + // relayout the menu immediately so that we know it's size + InvalidateLayout(true); + int w, h; + GetSize( w, h ); + + // work out where the cursor is and therefore the best place to put the frame + int sw, sh; + surface()->GetScreenSize( sw, sh ); + + // Try to center it first + int x, y; + x = cursorX - ( w / 2 ); + y = cursorY - ( h / 2 ); + + // Clamp to various sides + if ( x + w > sw ) + { + x = sw - w; + } + if ( y + h > sh ) + { + y = sh - h; + } + if ( x < 0 ) + { + x = 0; + } + if ( y < 0 ) + { + y = 0; + } + + SetPos( x, y ); +} diff --git a/vgui2/vgui_controls/GraphPanel.cpp b/vgui2/vgui_controls/GraphPanel.cpp new file mode 100644 index 0000000..3e558dd --- /dev/null +++ b/vgui2/vgui_controls/GraphPanel.cpp @@ -0,0 +1,308 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <math.h> + +#include <vgui_controls/GraphPanel.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( GraphPanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +GraphPanel::GraphPanel(Panel *parent, const char *name) : BaseClass(parent, name) +{ + m_flDomainSize = 100.0f; + m_flLowRange = 0.0f; + m_flHighRange = 1.0f; + m_bUseDynamicRange = true; + m_flMinDomainSize = 0.0f; + m_flMaxDomainSize = 0.0f; + m_bMaxDomainSizeSet = false; + + // rendering, need to pull these from scheme/res file + m_iGraphBarWidth = 2; + m_iGraphBarGapWidth = 2; +} + +//----------------------------------------------------------------------------- +// Purpose: domain settings (x-axis settings) +//----------------------------------------------------------------------------- +void GraphPanel::SetDisplayDomainSize(float size) +{ + m_flDomainSize = size; + + // set the max domain size if it hasn't been set yet + if (!m_bMaxDomainSizeSet) + { + SetMaxDomainSize(size); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets the smallest domain that will be displayed +//----------------------------------------------------------------------------- +void GraphPanel::SetMinDomainSize(float size) +{ + m_flMinDomainSize = size; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the samples to keep +//----------------------------------------------------------------------------- +void GraphPanel::SetMaxDomainSize(float size) +{ + m_flMaxDomainSize = size; + m_bMaxDomainSizeSet = true; +} + +//----------------------------------------------------------------------------- +// Purpose: range settings (y-axis settings) +//----------------------------------------------------------------------------- +void GraphPanel::SetUseFixedRange(float lowRange, float highRange) +{ + m_bUseDynamicRange = false; + m_flLowRange = lowRange; + m_flHighRange = highRange; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the graph to dynamically determine the range +//----------------------------------------------------------------------------- +void GraphPanel::SetUseDynamicRange(float *rangeList, int numRanges) +{ + m_bUseDynamicRange = true; + m_RangeList.CopyArray(rangeList, numRanges); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the currently displayed range +//----------------------------------------------------------------------------- +void GraphPanel::GetDisplayedRange(float &lowRange, float &highRange) +{ + lowRange = m_flLowRange; + highRange = m_flHighRange; +} + +//----------------------------------------------------------------------------- +// Purpose: adds an item to the end of the list +//----------------------------------------------------------------------------- +void GraphPanel::AddItem(float sampleEnd, float sampleValue) +{ + if (m_Samples.Count() && m_Samples[m_Samples.Tail()].value == sampleValue) + { + // collapse identical samples + m_Samples[m_Samples.Tail()].sampleEnd = sampleEnd; + } + else + { + // add to the end of the samples list + Sample_t item; + item.value = sampleValue; + item.sampleEnd = sampleEnd; + m_Samples.AddToTail(item); + } + + // see if this frees up any samples past the end + if (m_bMaxDomainSizeSet) + { + float freePoint = sampleEnd - m_flMaxDomainSize; + while (m_Samples[m_Samples.Head()].sampleEnd < freePoint) + { + m_Samples.Remove(m_Samples.Head()); + } + } + +/* + // see the max number of samples necessary to display this information reasonably precisely + static const int MAX_LIKELY_GRAPH_WIDTH = 800; + int maxSamplesNeeded = 2 * MAX_LIKELY_GRAPH_WIDTH / (m_iGraphBarWidth + m_iGraphBarGapWidth); + if (m_Samples.Count() > 2) + { + // see if we can collapse some items + float highestSample = m_Samples[m_Samples.Tail()].sampleEnd; + + // iterate the items + // always keep the head around so we have something to go against + int sampleIndex = m_Samples.Next(m_Samples.Head()); + int nextSampleIndex = m_Samples.Next(sampleIndex); + + while (m_Samples.IsInList(nextSampleIndex)) + { + // calculate what sampling precision is actually needed to display this data + float distanceFromEnd = highestSample - m_Samples[sampleIndex].sampleEnd; + +// if (distanceFromEnd < m_flDomainSize) +// break; + + //!! this calculation is very incorrect + float minNeededSampleSize = distanceFromEnd / (m_flMinDomainSize * maxSamplesNeeded); + float sampleSize = m_Samples[nextSampleIndex].sampleEnd - m_Samples[sampleIndex].sampleEnd; + + if (sampleSize < minNeededSampleSize) + { + // collapse the item into the next index + m_Samples[nextSampleIndex].value = 0.5f * (m_Samples[nextSampleIndex].value + m_Samples[sampleIndex].value); + + // remove the item from the list + m_Samples.Remove(sampleIndex); + + // move to the next item + sampleIndex = nextSampleIndex; + nextSampleIndex = m_Samples.Next(sampleIndex); + } + else + { + // this item didn't need collapsing, so assume the next item won't + break; + } + } + } +*/ + + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns number of items that can be displayed +//----------------------------------------------------------------------------- +int GraphPanel::GetVisibleItemCount() +{ + return GetWide() / (m_iGraphBarWidth + m_iGraphBarGapWidth); +} + +//----------------------------------------------------------------------------- +// Purpose: lays out the graph +//----------------------------------------------------------------------------- +void GraphPanel::PerformLayout() +{ + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: draws the graph +//----------------------------------------------------------------------------- +void GraphPanel::Paint() +{ + if (!m_Samples.Count()) + return; + + // walk from right to left drawing the resampled data + int sampleIndex = m_Samples.Tail(); + int x = GetWide() - (m_iGraphBarWidth + m_iGraphBarGapWidth); + + // calculate how big each sample should be + float sampleSize = m_flDomainSize / GetVisibleItemCount(); + + // calculate where in the domain we start resampling + float resampleStart = m_Samples[sampleIndex].sampleEnd - sampleSize; + // always resample from a sample point that is a multiple of the sampleSize + resampleStart -= (float)fmod(resampleStart, sampleSize); + + // bar size multiplier + float barSizeMultiplier = GetTall() / (m_flHighRange - m_flLowRange); + + // set render color + surface()->DrawSetColor(GetFgColor()); + + // recalculate the sample range for dynamic resizing + float flMinValue = m_Samples[m_Samples.Head()].value; + float flMaxValue = m_Samples[m_Samples.Head()].value; + + // iterate the bars to draw + while (x > 0 && m_Samples.IsInList(sampleIndex)) + { + // move back the drawing point + x -= (m_iGraphBarWidth + m_iGraphBarGapWidth); + + // collect the samples + float value = 0.0f; + float maxValue = 0.0f; + int samplesTouched = 0; + int prevSampleIndex = m_Samples.Previous(sampleIndex); + while (m_Samples.IsInList(prevSampleIndex)) + { + // take the value + value += m_Samples[sampleIndex].value; + samplesTouched++; + + // do some work to calculate the sample range + if (m_Samples[sampleIndex].value < flMinValue) + { + flMinValue = m_Samples[sampleIndex].value; + } + if (m_Samples[sampleIndex].value > flMaxValue) + { + flMaxValue = m_Samples[sampleIndex].value; + } + if (m_Samples[sampleIndex].value > maxValue) + { + maxValue = m_Samples[sampleIndex].value; + } + + if (resampleStart < m_Samples[prevSampleIndex].sampleEnd) + { + // we're out of the sampling range, we need to move on to the next sample + sampleIndex = prevSampleIndex; + prevSampleIndex = m_Samples.Previous(sampleIndex); + } + else + { + // we're done with this resample + // move back the resample start + resampleStart -= sampleSize; + // draw the current item + break; + } + } + + // draw the item + // show the max value in the sample, not the average + int size = (int)(maxValue * barSizeMultiplier); +// int size = (int)((value * barSizeMultiplier) / samplesTouched); + surface()->DrawFilledRect(x, GetTall() - size, x + m_iGraphBarWidth, GetTall()); + } + + // calculate our final range (for use next frame) + if (m_bUseDynamicRange) + { + flMinValue = 0; + + // find the range that fits + for (int i = 0; i < m_RangeList.Count(); i++) + { + if (m_RangeList[i] > flMaxValue) + { + flMaxValue = m_RangeList[i]; + break; + } + } + + m_flLowRange = flMinValue; + m_flHighRange = flMaxValue; + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets up colors +//----------------------------------------------------------------------------- +void GraphPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("GraphPanel.FgColor", pScheme)); + SetBgColor(GetSchemeColor("GraphPanel.BgColor", pScheme)); + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); +} diff --git a/vgui2/vgui_controls/HTML.cpp b/vgui2/vgui_controls/HTML.cpp new file mode 100644 index 0000000..515794f --- /dev/null +++ b/vgui2/vgui_controls/HTML.cpp @@ -0,0 +1,1794 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// This class is a message box that has two buttons, ok and cancel instead of +// just the ok button of a message box. We use a message box class for the ok button +// and implement another button here. +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/pch_vgui_controls.h" +#include <vgui_controls/EditablePanel.h> +#include <vgui_controls/Menu.h> +#include <vgui_controls/MessageBox.h> + +#include "filesystem.h" +#include "../vgui2/src/vgui_key_translation.h" + +#undef PostMessage +#undef MessageBox + +#include "OfflineMode.h" + +// memdbgon must be the last include file in a .cpp file +#include "tier0/memdbgon.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: A simple passthrough panel to render the border onto the HTML widget +//----------------------------------------------------------------------------- +class HTMLInterior : public Panel +{ + DECLARE_CLASS_SIMPLE( HTMLInterior, Panel ); +public: + HTMLInterior( HTML *parent ) : BaseClass( parent, "HTMLInterior" ) + { + m_pHTML = parent; + SetPaintBackgroundEnabled( false ); + SetKeyBoardInputEnabled( false ); + SetMouseInputEnabled( false ); + } + +private: + HTML *m_pHTML; +}; + +//----------------------------------------------------------------------------- +// Purpose: container class for any external popup windows the browser requests +//----------------------------------------------------------------------------- +class HTMLPopup : public vgui::Frame +{ + DECLARE_CLASS_SIMPLE( HTMLPopup, vgui::Frame ); + class PopupHTML : public vgui::HTML + { + DECLARE_CLASS_SIMPLE( PopupHTML, vgui::HTML ); + public: + PopupHTML( Frame *parent, const char *pchName, bool allowJavaScript , bool bPopupWindow ) : HTML( parent, pchName, allowJavaScript, bPopupWindow ) { m_pParent = parent; } + + virtual void OnSetHTMLTitle( const char *pchTitle ) + { + BaseClass::OnSetHTMLTitle( pchTitle ); + m_pParent->SetTitle( pchTitle, true ); + } + + private: + Frame *m_pParent; + }; +public: + HTMLPopup( Panel *parent, const char *pchURL, const char *pchTitle ) : Frame( NULL, "HtmlPopup", true ) + { + m_pHTML = new PopupHTML( this, "htmlpopupchild", true, true ); + m_pHTML->OpenURL( pchURL, NULL, false ); + SetTitle( pchTitle, true ); + } + + ~HTMLPopup() + { + } + + enum + { + vert_inset = 40, + horiz_inset = 6 + }; + + void PerformLayout() + { + BaseClass::PerformLayout(); + int wide, tall; + GetSize( wide, tall ); + m_pHTML->SetPos( horiz_inset, vert_inset ); + m_pHTML->SetSize( wide - horiz_inset*2, tall - vert_inset*2 ); + } + + void SetBounds( int x, int y, int wide, int tall ) + { + BaseClass::SetBounds( x, y, wide + horiz_inset*2, tall + vert_inset*2 ); + } + + MESSAGE_FUNC( OnCloseWindow, "OnCloseWindow" ) + { + Close(); + } +private: + PopupHTML *m_pHTML; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +HTML::HTML(Panel *parent, const char *name, bool allowJavaScript, bool bPopupWindow) : Panel(parent, name), +m_NeedsPaint( this, &HTML::BrowserNeedsPaint ), +m_StartRequest( this, &HTML::BrowserStartRequest ), +m_URLChanged( this, &HTML::BrowserURLChanged ), +m_FinishedRequest( this, &HTML::BrowserFinishedRequest ), +m_LinkInNewTab( this, &HTML::BrowserOpenNewTab ), +m_ChangeTitle( this, &HTML::BrowserSetHTMLTitle ), +m_NewWindow( this, &HTML::BrowserPopupHTMLWindow ), +m_FileLoadDialog( this, &HTML::BrowserFileLoadDialog ), +m_SearchResults( this, &HTML::BrowserSearchResults ), +m_CloseBrowser( this, &HTML::BrowserClose ), +m_HorizScroll( this, &HTML::BrowserHorizontalScrollBarSizeResponse ), +m_VertScroll( this, &HTML::BrowserVerticalScrollBarSizeResponse ), +m_LinkAtPosResp( this, &HTML::BrowserLinkAtPositionResponse ), +m_JSAlert( this, &HTML::BrowserJSAlert ), +m_JSConfirm( this, &HTML::BrowserJSConfirm ), +m_CanGoBackForward( this, &HTML::BrowserCanGoBackandForward ), +m_SetCursor( this, &HTML::BrowserSetCursor ), +m_StatusText( this, &HTML::BrowserStatusText ), +m_ShowTooltip( this, &HTML::BrowserShowToolTip ), +m_UpdateTooltip( this, &HTML::BrowserUpdateToolTip ), +m_HideTooltip( this, &HTML::BrowserHideToolTip ) +{ + m_iHTMLTextureID = 0; + m_bCanGoBack = false; + m_bCanGoForward = false; + m_bInFind = false; + m_bRequestingDragURL = false; + m_bRequestingCopyLink = false; + m_flZoom = 100.0f; + m_bNeedsFullTextureUpload = false; + + m_pInteriorPanel = new HTMLInterior( this ); + SetPostChildPaintEnabled( true ); + + m_unBrowserHandle = INVALID_HTMLBROWSER; + m_SteamAPIContext.Init(); + if ( m_SteamAPIContext.SteamHTMLSurface() ) + { + m_SteamAPIContext.SteamHTMLSurface()->Init(); + SteamAPICall_t hSteamAPICall = m_SteamAPIContext.SteamHTMLSurface()->CreateBrowser( surface()->GetWebkitHTMLUserAgentString(), NULL ); + m_SteamCallResultBrowserReady.Set( hSteamAPICall, this, &HTML::OnBrowserReady ); + } + else + { + Warning("Unable to access SteamHTMLSurface"); + } + m_iScrollBorderX=m_iScrollBorderY=0; + m_bScrollBarEnabled = true; + m_bContextMenuEnabled = true; + m_bNewWindowsOnly = false; + m_iMouseX = m_iMouseY = 0; + m_iDragStartX = m_iDragStartY = 0; + m_nViewSourceAllowedIndex = -1; + m_iWideLastHTMLSize = m_iTalLastHTMLSize = 0; + + _hbar = new ScrollBar(this, "HorizScrollBar", false); + _hbar->SetVisible(false); + _hbar->AddActionSignalTarget(this); + + _vbar = new ScrollBar(this, "VertScrollBar", true); + _vbar->SetVisible(false); + _vbar->AddActionSignalTarget(this); + + m_pFindBar = new HTML::CHTMLFindBar( this ); + m_pFindBar->SetZPos( 2 ); + m_pFindBar->SetVisible( false ); + + m_pContextMenu = new Menu( this, "contextmenu" ); + m_pContextMenu->AddMenuItem( "#vgui_HTMLBack", new KeyValues( "Command", "command", "back" ), this ); + m_pContextMenu->AddMenuItem( "#vgui_HTMLForward", new KeyValues( "Command", "command", "forward" ), this ); + m_pContextMenu->AddMenuItem( "#vgui_HTMLReload", new KeyValues( "Command", "command", "reload" ), this ); + m_pContextMenu->AddMenuItem( "#vgui_HTMLStop", new KeyValues( "Command", "command", "stop" ), this ); + m_pContextMenu->AddSeparator(); + m_pContextMenu->AddMenuItem( "#vgui_HTMLCopyUrl", new KeyValues( "Command", "command", "copyurl" ), this ); + m_iCopyLinkMenuItemID = m_pContextMenu->AddMenuItem( "#vgui_HTMLCopyLink", new KeyValues( "Command", "command", "copylink" ), this ); + m_pContextMenu->AddMenuItem( "#TextEntry_Copy", new KeyValues( "Command", "command", "copy" ), this ); + m_pContextMenu->AddMenuItem( "#TextEntry_Paste", new KeyValues( "Command", "command", "paste" ), this ); + m_pContextMenu->AddSeparator(); + m_nViewSourceAllowedIndex = m_pContextMenu->AddMenuItem( "#vgui_HTMLViewSource", new KeyValues( "Command", "command", "viewsource" ), this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser is ready to show pages +//----------------------------------------------------------------------------- +void HTML::OnBrowserReady( HTML_BrowserReady_t *pBrowserReady, bool bIOFailure ) +{ + m_unBrowserHandle = pBrowserReady->unBrowserHandle; + BrowserResize(); + + if (!m_sPendingURLLoad.IsEmpty()) + { + PostURL( m_sPendingURLLoad, m_sPendingPostData, false ); + m_sPendingURLLoad.Clear(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +HTML::~HTML() +{ + m_pContextMenu->MarkForDeletion(); + + if ( m_SteamAPIContext.SteamHTMLSurface() ) + { + m_SteamAPIContext.SteamHTMLSurface()->RemoveBrowser( m_unBrowserHandle ); + } + + FOR_EACH_VEC( m_vecHCursor, i ) + { + // BR FIXME! +// surface()->DeleteCursor( m_vecHCursor[i].m_Cursor ); + } + m_vecHCursor.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handle message to change our cursor +//----------------------------------------------------------------------------- +void HTML::OnSetCursorVGUI( int cursor ) +{ + SetCursor( (HCursor)cursor ); +} + +//----------------------------------------------------------------------------- +// Purpose: sets up colors/fonts/borders +//----------------------------------------------------------------------------- +void HTML::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + BrowserResize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: overrides panel class, paints a texture of the HTML window as a background +//----------------------------------------------------------------------------- +void HTML::Paint() +{ + //VPROF_BUDGET( "HTML::Paint()", VPROF_BUDGETGROUP_OTHER_VGUI ); + BaseClass::Paint(); + + if ( m_iHTMLTextureID != 0 ) + { + surface()->DrawSetTexture( m_iHTMLTextureID ); + int tw = 0, tt = 0; + surface()->DrawSetColor( Color( 255, 255, 255, 255 ) ); + GetSize( tw, tt ); + surface()->DrawTexturedRect( 0, 0, tw, tt ); + } + + // If we have scrollbars, we need to draw the bg color under them, since the browser + // bitmap is a checkerboard under them, and they are transparent in the in-game client + if ( m_iScrollBorderX > 0 || m_iScrollBorderY > 0 ) + { + int w, h; + GetSize( w, h ); + IBorder *border = GetBorder(); + int left = 0, top = 0, right = 0, bottom = 0; + if ( border ) + { + border->GetInset( left, top, right, bottom ); + } + surface()->DrawSetColor( GetBgColor() ); + if ( m_iScrollBorderX ) + { + surface()->DrawFilledRect( w-m_iScrollBorderX - right, top, w, h - bottom ); + } + if ( m_iScrollBorderY ) + { + surface()->DrawFilledRect( left, h-m_iScrollBorderY - bottom, w-m_iScrollBorderX - right, h ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: causes a repaint when the layout changes +//----------------------------------------------------------------------------- +void HTML::PerformLayout() +{ + BaseClass::PerformLayout(); + Repaint(); + int vbarInset = _vbar->IsVisible() ? _vbar->GetWide() : 0; + int maxw = GetWide() - vbarInset; + m_pInteriorPanel->SetBounds( 0, 0, maxw, GetTall() ); + + IScheme *pClientScheme = vgui::scheme()->GetIScheme( vgui::scheme()->GetScheme( "ClientScheme" ) ); + + int iSearchInsetY = 5; + int iSearchInsetX = 5; + int iSearchTall = 24; + int iSearchWide = 150; + const char *resourceString = pClientScheme->GetResourceString( "HTML.SearchInsetY"); + if ( resourceString ) + { + iSearchInsetY = atoi(resourceString); + } + resourceString = pClientScheme->GetResourceString( "HTML.SearchInsetX"); + if ( resourceString ) + { + iSearchInsetX = atoi(resourceString); + } + resourceString = pClientScheme->GetResourceString( "HTML.SearchTall"); + if ( resourceString ) + { + iSearchTall = atoi(resourceString); + } + resourceString = pClientScheme->GetResourceString( "HTML.SearchWide"); + if ( resourceString ) + { + iSearchWide = atoi(resourceString); + } + + m_pFindBar->SetBounds( GetWide() - iSearchWide - iSearchInsetX - vbarInset, m_pFindBar->BIsHidden() ? -1*iSearchTall-5: iSearchInsetY, iSearchWide, iSearchTall ); +} + + +//----------------------------------------------------------------------------- +// Purpose: updates the underlying HTML surface widgets position +//----------------------------------------------------------------------------- +void HTML::OnMove() +{ + BaseClass::OnMove(); + + // tell cef where we are on the screen so plugins can correctly render + int nPanelAbsX, nPanelAbsY; + ipanel()->GetAbsPos( GetVPanel(), nPanelAbsX, nPanelAbsY ); +} + + +//----------------------------------------------------------------------------- +// Purpose: opens the URL, will accept any URL that IE accepts +//----------------------------------------------------------------------------- +void HTML::OpenURL(const char *URL, const char *postData, bool force) +{ + PostURL( URL, postData, force ); +} + +//----------------------------------------------------------------------------- +// Purpose: opens the URL, will accept any URL that IE accepts +//----------------------------------------------------------------------------- +void HTML::PostURL(const char *URL, const char *pchPostData, bool force) +{ + if ( m_unBrowserHandle == INVALID_HTMLBROWSER ) + { + m_sPendingURLLoad = URL; + m_sPendingPostData = pchPostData; + return; + } + + if ( IsSteamInOfflineMode() && !force ) + { + const char *baseDir = getenv("HTML_OFFLINE_DIR"); + if ( baseDir ) + { + // get the app we need to run + char htmlLocation[_MAX_PATH]; + char otherName[128]; + char fileLocation[_MAX_PATH]; + + if ( ! g_pFullFileSystem->FileExists( baseDir ) ) + { + Q_snprintf( otherName, sizeof(otherName), "%senglish.html", OFFLINE_FILE ); + baseDir = otherName; + } + g_pFullFileSystem->GetLocalCopy( baseDir ); // put this file on disk for IE to load + + g_pFullFileSystem->GetLocalPath( baseDir, fileLocation, sizeof(fileLocation) ); + Q_snprintf(htmlLocation, sizeof(htmlLocation), "file://%s", fileLocation); + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->LoadURL( m_unBrowserHandle, htmlLocation, NULL ); + } + else + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->LoadURL( m_unBrowserHandle, URL, NULL ); + } + } + else + { + if ( pchPostData && Q_strlen(pchPostData) > 0 ) + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->LoadURL( m_unBrowserHandle, URL, pchPostData ); + + } + else + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->LoadURL( m_unBrowserHandle, URL, NULL ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: opens the URL, will accept any URL that IE accepts +//----------------------------------------------------------------------------- +bool HTML::StopLoading() +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->StopLoad( m_unBrowserHandle ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: refreshes the current page +//----------------------------------------------------------------------------- +bool HTML::Refresh() +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->Reload( m_unBrowserHandle ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Tells the browser control to go back +//----------------------------------------------------------------------------- +void HTML::GoBack() +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->GoBack( m_unBrowserHandle ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Tells the browser control to go forward +//----------------------------------------------------------------------------- +void HTML::GoForward() +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->GoForward( m_unBrowserHandle ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Checks if the browser can go back further +//----------------------------------------------------------------------------- +bool HTML::BCanGoBack() +{ + return m_bCanGoBack; +} + + +//----------------------------------------------------------------------------- +// Purpose: Checks if the browser can go forward further +//----------------------------------------------------------------------------- +bool HTML::BCanGoFoward() +{ + return m_bCanGoForward; +} + + +//----------------------------------------------------------------------------- +// Purpose: handle resizing +//----------------------------------------------------------------------------- +void HTML::OnSizeChanged(int wide,int tall) +{ + BaseClass::OnSizeChanged(wide,tall); + UpdateSizeAndScrollBars(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Run javascript in the page +//----------------------------------------------------------------------------- +void HTML::RunJavascript( const char *pchScript ) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->ExecuteJavascript( m_unBrowserHandle, pchScript ); +} + + + + +//----------------------------------------------------------------------------- +// Purpose: helper to convert UI mouse codes to CEF ones +//----------------------------------------------------------------------------- +ISteamHTMLSurface::EHTMLMouseButton ConvertMouseCodeToCEFCode( MouseCode code ) +{ + switch( code ) + { + default: + case MOUSE_LEFT: + return ISteamHTMLSurface::eHTMLMouseButton_Left; + break; + case MOUSE_RIGHT: + return ISteamHTMLSurface::eHTMLMouseButton_Right; + break; + case MOUSE_MIDDLE: + return ISteamHTMLSurface::eHTMLMouseButton_Middle; + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: passes mouse clicks to the control +//----------------------------------------------------------------------------- +void HTML::OnMousePressed(MouseCode code) +{ + m_sDragURL = NULL; + + // mouse4 = back button + if ( code == MOUSE_4 ) + { + PostActionSignal( new KeyValues( "HTMLBackRequested" ) ); + return; + } + if ( code == MOUSE_5 ) + { + PostActionSignal( new KeyValues( "HTMLForwardRequested" ) ); + return; + } + + + if ( code == MOUSE_RIGHT && m_bContextMenuEnabled ) + { + GetLinkAtPosition( m_iMouseX, m_iMouseY ); + Menu::PlaceContextMenu( this, m_pContextMenu ); + return; + } + + // ask for the focus to come to this window + RequestFocus(); + + // now tell the browser about the click + // ignore right clicks if context menu has been disabled + if ( code != MOUSE_RIGHT ) + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->MouseDown( m_unBrowserHandle, ConvertMouseCodeToCEFCode( code ) ); + } + + if ( code == MOUSE_LEFT ) + { + input()->GetCursorPos( m_iDragStartX, m_iDragStartY ); + int htmlx, htmly; + ipanel()->GetAbsPos( GetVPanel(), htmlx, htmly ); + + GetLinkAtPosition( m_iDragStartX - htmlx, m_iDragStartY - htmly ); + + m_bRequestingDragURL = true; + // make sure we get notified when the mouse gets released + if ( !m_sDragURL.IsEmpty() ) + { + input()->SetMouseCapture( GetVPanel() ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: passes mouse up events +//----------------------------------------------------------------------------- +void HTML::OnMouseReleased(MouseCode code) +{ + if ( code == MOUSE_LEFT ) + { + input()->SetMouseCapture( NULL ); + input()->SetCursorOveride( 0 ); + + if ( !m_sDragURL.IsEmpty() && input()->GetMouseOver() != GetVPanel() && input()->GetMouseOver() ) + { + // post the text as a drag drop to the target panel + KeyValuesAD kv( "DragDrop" ); + if ( ipanel()->RequestInfo( input()->GetMouseOver(), kv ) + && kv->GetPtr( "AcceptPanel" ) != NULL ) + { + VPANEL vpanel = (VPANEL)kv->GetPtr( "AcceptPanel" ); + ivgui()->PostMessage( vpanel, new KeyValues( "DragDrop", "text", m_sDragURL.Get() ), GetVPanel() ); + } + } + m_sDragURL = NULL; + } + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->MouseUp( m_unBrowserHandle, ConvertMouseCodeToCEFCode( code ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: keeps track of where the cursor is +//----------------------------------------------------------------------------- +void HTML::OnCursorMoved(int x,int y) +{ + // Only do this when we are over the current panel + if ( vgui::input()->GetMouseOver() == GetVPanel() ) + { + m_iMouseX = x; + m_iMouseY = y; + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->MouseMove( m_unBrowserHandle, m_iMouseX, m_iMouseY ); + } + else if ( !m_sDragURL.IsEmpty() ) + { + if ( !input()->GetMouseOver() ) + { + // we're not over any vgui window, switch to the OS implementation of drag/drop + // BR FIXME +// surface()->StartDragDropText( m_sDragURL ); + m_sDragURL = NULL; + } + } + + if ( !m_sDragURL.IsEmpty() && !input()->GetCursorOveride() ) + { + // if we've dragged far enough (in global coordinates), set to use the drag cursor + int gx, gy; + input()->GetCursorPos( gx, gy ); + if ( abs(m_iDragStartX-gx) + abs(m_iDragStartY-gy) > 3 ) + { +// input()->SetCursorOveride( dc_alias ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: passes double click events to the browser +//----------------------------------------------------------------------------- +void HTML::OnMouseDoublePressed(MouseCode code) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->MouseDoubleClick( m_unBrowserHandle, ConvertMouseCodeToCEFCode( code ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: return the bitmask of any modifier keys that are currently down +//----------------------------------------------------------------------------- +int GetKeyModifiers() +{ + // Any time a key is pressed reset modifier list as well + int nModifierCodes = 0; + if (vgui::input()->IsKeyDown( KEY_LCONTROL ) || vgui::input()->IsKeyDown( KEY_RCONTROL )) + nModifierCodes |= ISteamHTMLSurface::k_eHTMLKeyModifier_CtrlDown; + + if (vgui::input()->IsKeyDown( KEY_LALT ) || vgui::input()->IsKeyDown( KEY_RALT )) + nModifierCodes |= ISteamHTMLSurface::k_eHTMLKeyModifier_AltDown; + + if (vgui::input()->IsKeyDown( KEY_LSHIFT ) || vgui::input()->IsKeyDown( KEY_RSHIFT )) + nModifierCodes |= ISteamHTMLSurface::k_eHTMLKeyModifier_ShiftDown; + +#ifdef OSX + // for now pipe through the cmd-key to be like the control key so we get copy/paste + if (vgui::input()->IsKeyDown( KEY_LWIN ) || vgui::input()->IsKeyDown( KEY_RWIN )) + nModifierCodes |= ISteamHTMLSurface::k_eHTMLKeyModifier_CtrlDown; +#endif + + return nModifierCodes; +} + + +//----------------------------------------------------------------------------- +// Purpose: passes key presses to the browser (we don't current do this) +//----------------------------------------------------------------------------- +void HTML::OnKeyTyped(wchar_t unichar) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->KeyChar( m_unBrowserHandle, unichar, (ISteamHTMLSurface::EHTMLKeyModifiers)GetKeyModifiers() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: pop up the find dialog +//----------------------------------------------------------------------------- +void HTML::ShowFindDialog() +{ + IScheme *pClientScheme = vgui::scheme()->GetIScheme( vgui::scheme()->GetScheme( "ClientScheme" ) ); + if ( !pClientScheme ) + return; + + m_pFindBar->SetVisible( true ); + m_pFindBar->RequestFocus(); + m_pFindBar->SetText( "" ); + m_pFindBar->HideCountLabel(); + m_pFindBar->SetHidden( false ); + int x = 0, y = 0, h = 0, w = 0; + m_pFindBar->GetBounds( x, y, w, h ); + m_pFindBar->SetPos( x, -1*h ); + int iSearchInsetY = 0; + const char *resourceString = pClientScheme->GetResourceString( "HTML.SearchInsetY"); + if ( resourceString ) + { + iSearchInsetY = atoi(resourceString); + } + float flAnimationTime = 0.0f; + resourceString = pClientScheme->GetResourceString( "HTML.SearchAnimationTime"); + if ( resourceString ) + { + flAnimationTime = atof(resourceString); + } + + GetAnimationController()->RunAnimationCommand( m_pFindBar, "ypos", iSearchInsetY, 0.0f, flAnimationTime, AnimationController::INTERPOLATOR_LINEAR ); +} + + +//----------------------------------------------------------------------------- +// Purpose: hide the find dialog +//----------------------------------------------------------------------------- +void HTML::HideFindDialog() +{ + IScheme *pClientScheme = vgui::scheme()->GetIScheme( vgui::scheme()->GetScheme( "ClientScheme" ) ); + if ( !pClientScheme ) + return; + + int x = 0, y = 0, h = 0, w = 0; + m_pFindBar->GetBounds( x, y, w, h ); + float flAnimationTime = 0.0f; + const char *resourceString = pClientScheme->GetResourceString( "HTML.SearchAnimationTime"); + if ( resourceString ) + { + flAnimationTime = atof(resourceString); + } + + GetAnimationController()->RunAnimationCommand( m_pFindBar, "ypos", -1*h-5, 0.0f, flAnimationTime, AnimationController::INTERPOLATOR_LINEAR ); + m_pFindBar->SetHidden( true ); + StopFind(); +} + + +//----------------------------------------------------------------------------- +// Purpose: is the find dialog visible? +//----------------------------------------------------------------------------- +bool HTML::FindDialogVisible() +{ + return m_pFindBar->IsVisible() && !m_pFindBar->BIsHidden(); +} + + +//----------------------------------------------------------------------------- +// Purpose: passes key presses to the browser +//----------------------------------------------------------------------------- +void HTML::OnKeyCodeTyped(KeyCode code) +{ + switch( code ) + { + case KEY_PAGEDOWN: + { + int val = _vbar->GetValue(); + val += 200; + _vbar->SetValue(val); + break; + } + case KEY_PAGEUP: + { + int val = _vbar->GetValue(); + val -= 200; + _vbar->SetValue(val); + break; + } + case KEY_F5: + { + Refresh(); + break; + } + case KEY_F: + { + if ( (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ) + || ( IsOSX() && ( input()->IsKeyDown(KEY_LWIN) || input()->IsKeyDown(KEY_RWIN) ) ) ) + { + if ( !FindDialogVisible() ) + { + ShowFindDialog(); + } + else + { + HideFindDialog(); + } + break; + } + } + case KEY_ESCAPE: + { + if ( FindDialogVisible() ) + { + HideFindDialog(); + break; + } + } + case KEY_TAB: + { + if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ) + { + // pass control-tab to parent (through baseclass) + BaseClass::OnKeyTyped( code ); + return; + } + break; + } + } + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->KeyDown( m_unBrowserHandle, KeyCode_VGUIToVirtualKey(code), (ISteamHTMLSurface::EHTMLKeyModifiers)GetKeyModifiers() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void HTML::OnKeyCodeReleased(KeyCode code) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->KeyUp( m_unBrowserHandle, KeyCode_VGUIToVirtualKey( code ), (ISteamHTMLSurface::EHTMLKeyModifiers)GetKeyModifiers() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: scrolls the vertical scroll bar on a web page +//----------------------------------------------------------------------------- +void HTML::OnMouseWheeled(int delta) +{ + if (_vbar ) + { + int val = _vbar->GetValue(); + val -= (delta * 100.0/3.0 ); // 100 for every 3 lines matches chromes code + _vbar->SetValue(val); + } + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->MouseWheel( m_unBrowserHandle, delta* 100.0/3.0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Inserts a custom URL handler +//----------------------------------------------------------------------------- +void HTML::AddCustomURLHandler(const char *customProtocolName, vgui::Panel *target) +{ + int index = m_CustomURLHandlers.AddToTail(); + m_CustomURLHandlers[index].hPanel = target; + Q_strncpy(m_CustomURLHandlers[index].url, customProtocolName, sizeof(m_CustomURLHandlers[index].url)); +} + + +//----------------------------------------------------------------------------- +// Purpose: shared code for sizing the HTML surface window +//----------------------------------------------------------------------------- +void HTML::BrowserResize() +{ + if (m_unBrowserHandle == INVALID_HTMLBROWSER) + return; + + int w,h; + GetSize( w, h ); + int right = 0, bottom = 0; + // TODO::STYLE + /* + IAppearance *pAppearance = GetAppearance(); + int left = 0, top = 0; + if ( pAppearance ) + { + pAppearance->GetInset( left, top, right, bottom ); + } + */ + + + if ( m_iWideLastHTMLSize != ( w - m_iScrollBorderX - right ) || m_iTalLastHTMLSize != ( h - m_iScrollBorderY - bottom ) ) + { + m_iWideLastHTMLSize = w - m_iScrollBorderX - right; + m_iTalLastHTMLSize = h - m_iScrollBorderY - bottom; + if ( m_iTalLastHTMLSize <= 0 ) + { + SetTall( 64 ); + m_iTalLastHTMLSize = 64 - bottom; + } + + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->SetSize( m_unBrowserHandle, m_iWideLastHTMLSize, m_iTalLastHTMLSize ); + } + + + // webkit forgets the scroll offset when you resize (it saves the scroll in a DC and a resize throws away the DC) + // so just tell it after the resize + int scrollV = _vbar->GetValue(); + int scrollH = _hbar->GetValue(); + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->SetHorizontalScroll( m_unBrowserHandle, scrollH ); + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->SetVerticalScroll( m_unBrowserHandle, scrollV ); + } + +} + + +//----------------------------------------------------------------------------- +// Purpose: when a slider moves causes the IE images to re-render itself +//----------------------------------------------------------------------------- +void HTML::OnSliderMoved() +{ + if(_hbar->IsVisible()) + { + int scrollX = _hbar->GetValue(); + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->SetHorizontalScroll( m_unBrowserHandle, scrollX ); + } + + if(_vbar->IsVisible()) + { + int scrollY=_vbar->GetValue(); + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->SetVerticalScroll( m_unBrowserHandle, scrollY ); + } + + // post a message that the slider has moved + PostActionSignal( new KeyValues( "HTMLSliderMoved" ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +bool HTML::IsScrolledToBottom() +{ + if ( !_vbar->IsVisible() ) + return true; + + return m_scrollVertical.m_nScroll >= m_scrollVertical.m_nMax; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +bool HTML::IsScrollbarVisible() +{ + return _vbar->IsVisible(); +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void HTML::SetScrollbarsEnabled(bool state) +{ + m_bScrollBarEnabled = state; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void HTML::SetContextMenuEnabled(bool state) +{ + m_bContextMenuEnabled = state; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void HTML::SetViewSourceEnabled(bool state) +{ + m_pContextMenu->SetItemVisible( m_nViewSourceAllowedIndex, state ); +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void HTML::NewWindowsOnly( bool state ) +{ + m_bNewWindowsOnly = state; +} + + +//----------------------------------------------------------------------------- +// Purpose: called when our children have finished painting +//----------------------------------------------------------------------------- +void HTML::PostChildPaint() +{ + BaseClass::PostChildPaint(); + // TODO::STYLE + //m_pInteriorPanel->SetPaintAppearanceEnabled( true ); // turn painting back on so the IE hwnd can render this border +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a custom header to all requests +//----------------------------------------------------------------------------- +void HTML::AddHeader( const char *pchHeader, const char *pchValue ) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->AddHeader( m_unBrowserHandle, pchHeader, pchValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void HTML::OnSetFocus() +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->SetKeyFocus( m_unBrowserHandle, true ); + + BaseClass::OnSetFocus(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void HTML::OnKillFocus() +{ + BaseClass::OnKillFocus(); + + // Don't clear the actual html focus if a context menu is what took focus + if ( m_pContextMenu->HasFocus() ) + return; + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->SetKeyFocus( m_unBrowserHandle, false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: webkit is telling us to use this cursor type +//----------------------------------------------------------------------------- +void HTML::OnCommand( const char *pchCommand ) +{ + if ( !Q_stricmp( pchCommand, "back" ) ) + { + PostActionSignal( new KeyValues( "HTMLBackRequested" ) ); + } + else if ( !Q_stricmp( pchCommand, "forward" ) ) + { + PostActionSignal( new KeyValues( "HTMLForwardRequested" ) ); + } + else if ( !Q_stricmp( pchCommand, "reload" ) ) + { + Refresh(); + } + else if ( !Q_stricmp( pchCommand, "stop" ) ) + { + StopLoading(); + } + else if ( !Q_stricmp( pchCommand, "viewsource" ) ) + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->ViewSource( m_unBrowserHandle ); + } + else if ( !Q_stricmp( pchCommand, "copy" ) ) + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->CopyToClipboard( m_unBrowserHandle ); + } + else if ( !Q_stricmp( pchCommand, "paste" ) ) + { + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->PasteFromClipboard( m_unBrowserHandle ); + } + else if ( !Q_stricmp( pchCommand, "copyurl" ) ) + { + system()->SetClipboardText( m_sCurrentURL, m_sCurrentURL.Length() ); + } + else if ( !Q_stricmp( pchCommand, "copylink" ) ) + { + int x, y; + m_pContextMenu->GetPos( x, y ); + int htmlx, htmly; + ipanel()->GetAbsPos( GetVPanel(), htmlx, htmly ); + + m_bRequestingCopyLink = true; + GetLinkAtPosition( x - htmlx, y - htmly ); + } + else + BaseClass::OnCommand( pchCommand ); + +} + + +//----------------------------------------------------------------------------- +// Purpose: the control wants us to ask the user what file to load +//----------------------------------------------------------------------------- +void HTML::OnFileSelected( const char *pchSelectedFile ) +{ + const char *ppchSelectedFiles[] = { pchSelectedFile, NULL }; + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->FileLoadDialogResponse( m_unBrowserHandle , ppchSelectedFiles ); + + m_hFileOpenDialog->Close(); +} + +//----------------------------------------------------------------------------- +// Purpose: called when the user dismissed the file dialog with no selection +//----------------------------------------------------------------------------- +void HTML::OnFileSelectionCancelled() +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->FileLoadDialogResponse( m_unBrowserHandle, NULL ); + + m_hFileOpenDialog->Close(); +} + +//----------------------------------------------------------------------------- +// Purpose: find any text on the html page with this sub string +//----------------------------------------------------------------------------- +void HTML::Find( const char *pchSubStr ) +{ + m_bInFind = false; + if ( m_sLastSearchString == pchSubStr ) // same string as last time, lets fine next + m_bInFind = true; + + m_sLastSearchString = pchSubStr; + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->Find( m_unBrowserHandle, pchSubStr, m_bInFind, false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: find any text on the html page with this sub string +//----------------------------------------------------------------------------- +void HTML::FindPrevious() +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->Find( m_unBrowserHandle, m_sLastSearchString, m_bInFind, true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: find any text on the html page with this sub string +//----------------------------------------------------------------------------- +void HTML::FindNext() +{ + Find( m_sLastSearchString ); +} + + +//----------------------------------------------------------------------------- +// Purpose: stop an outstanding find request +//----------------------------------------------------------------------------- +void HTML::StopFind( ) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->StopFind( m_unBrowserHandle ); + m_bInFind = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: input handler +//----------------------------------------------------------------------------- +void HTML::OnEditNewLine( Panel *pPanel ) +{ + OnTextChanged( pPanel ); +} + + +//-----------------------h------------------------------------------------------ +// Purpose: input handler +//----------------------------------------------------------------------------- +void HTML::OnTextChanged( Panel *pPanel ) +{ + char rgchText[2048]; + m_pFindBar->GetText( rgchText, sizeof( rgchText ) ); + Find( rgchText ); +} + +//----------------------------------------------------------------------------- +// Purpose: helper class for the find bar +//----------------------------------------------------------------------------- +HTML::CHTMLFindBar::CHTMLFindBar( HTML *parent ) : EditablePanel( parent, "FindBar" ) +{ + m_pParent = parent; + m_bHidden = false; + m_pFindBar = new TextEntry( this, "FindEntry" ); + m_pFindBar->AddActionSignalTarget( parent ); + m_pFindBar->SendNewLine( true ); + m_pFindCountLabel = new Label( this, "FindCount", "" ); + m_pFindCountLabel->SetVisible( false ); + + if ( g_pFullFileSystem->FileExists( "resource/layout/htmlfindbar.layout" ) ) + { + LoadControlSettings( "resource/layout/htmlfindbar.layout" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: button input into the find bar +//----------------------------------------------------------------------------- +void HTML::CHTMLFindBar::OnCommand( const char *pchCmd ) +{ + if ( !Q_stricmp( pchCmd, "close" ) ) + { + m_pParent->HideFindDialog(); + } + else if ( !Q_stricmp( pchCmd, "previous" ) ) + { + m_pParent->FindPrevious(); + } + else if ( !Q_stricmp( pchCmd, "next" ) ) + { + m_pParent->FindNext(); + } + else + BaseClass::OnCommand( pchCmd ); + +} + + +//----------------------------------------------------------------------------- +// Purpose: we have a new texture to update +//----------------------------------------------------------------------------- +void HTML::BrowserNeedsPaint( HTML_NeedsPaint_t *pCallback ) +{ + int tw = 0, tt = 0; + if ( m_iHTMLTextureID != 0 ) + { + tw = m_allocedTextureWidth; + tt = m_allocedTextureHeight; + } + + if ( m_iHTMLTextureID != 0 && ( ( _vbar->IsVisible() && pCallback->unScrollY > 0 && abs( (int)pCallback->unScrollY - m_scrollVertical.m_nScroll) > 5 ) || ( _hbar->IsVisible() && pCallback->unScrollX > 0 && abs( (int)pCallback->unScrollX - m_scrollHorizontal.m_nScroll ) > 5 ) ) ) + { + m_bNeedsFullTextureUpload = true; + return; + } + + // update the vgui texture + if ( m_bNeedsFullTextureUpload || m_iHTMLTextureID == 0 || tw != (int)pCallback->unWide || tt != (int)pCallback->unTall ) + { + m_bNeedsFullTextureUpload = false; + if ( m_iHTMLTextureID != 0 ) + surface()->DeleteTextureByID( m_iHTMLTextureID ); + + // if the dimensions changed we also need to re-create the texture ID to support the overlay properly (it won't resize a texture on the fly, this is the only control that needs + // to so lets have a tiny bit more code here to support that) + m_iHTMLTextureID = surface()->CreateNewTextureID( true ); + surface()->DrawSetTextureRGBAEx( m_iHTMLTextureID, (const unsigned char *)pCallback->pBGRA, pCallback->unWide, pCallback->unTall, IMAGE_FORMAT_BGRA8888 );// BR FIXME - this call seems to shift by some number of pixels? + m_allocedTextureWidth = pCallback->unWide; + m_allocedTextureHeight = pCallback->unTall; + } + else if ( (int)pCallback->unUpdateWide > 0 && (int)pCallback->unUpdateTall > 0 ) + { + // same size texture, just bits changing in it, lets twiddle + surface()->DrawUpdateRegionTextureRGBA( m_iHTMLTextureID, pCallback->unUpdateX, pCallback->unUpdateY, (const unsigned char *)pCallback->pBGRA, pCallback->unUpdateWide, pCallback->unUpdateTall, IMAGE_FORMAT_BGRA8888 ); + } + else + { + surface()->DrawSetTextureRGBAEx( m_iHTMLTextureID, (const unsigned char *)pCallback->pBGRA,pCallback->unWide, pCallback->unTall, IMAGE_FORMAT_BGRA8888 ); + } + + // need a paint next time + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: browser wants to start loading this url, do we let it? +//----------------------------------------------------------------------------- +bool HTML::OnStartRequest( const char *url, const char *target, const char *pchPostData, bool bIsRedirect ) +{ + if ( !url || !Q_stricmp( url, "about:blank") ) + return true ; // this is just webkit loading a new frames contents inside an existing page + + HideFindDialog(); + // see if we have a custom handler for this + bool bURLHandled = false; + for (int i = 0; i < m_CustomURLHandlers.Count(); i++) + { + if (!Q_strnicmp(m_CustomURLHandlers[i].url,url, Q_strlen(m_CustomURLHandlers[i].url))) + { + // we have a custom handler + Panel *targetPanel = m_CustomURLHandlers[i].hPanel; + if (targetPanel) + { + PostMessage(targetPanel, new KeyValues("CustomURL", "url", m_CustomURLHandlers[i].url ) ); + } + + bURLHandled = true; + } + } + + if (bURLHandled) + return false; + + if ( m_bNewWindowsOnly && bIsRedirect ) + { + if ( target && ( !Q_stricmp( target, "_blank" ) || !Q_stricmp( target, "_new" ) ) ) // only allow NEW windows (_blank ones) + { + return true; + } + else + { + return false; + } + } + + if ( target && !Q_strlen( target ) ) + { + m_sCurrentURL = url; + + KeyValues *pMessage = new KeyValues( "OnURLChanged" ); + pMessage->SetString( "url", url ); + pMessage->SetString( "postdata", pchPostData ); + pMessage->SetInt( "isredirect", bIsRedirect ? 1 : 0 ); + + PostActionSignal( pMessage ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: callback from cef thread, load a url please +//----------------------------------------------------------------------------- +void HTML::BrowserStartRequest( HTML_StartRequest_t *pCmd ) +{ + bool bRes = OnStartRequest( pCmd->pchURL, pCmd->pchTarget, pCmd->pchPostData, pCmd->bIsRedirect ); + + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->AllowStartRequest( m_unBrowserHandle, bRes ); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser went to a new url +//----------------------------------------------------------------------------- +void HTML::BrowserURLChanged( HTML_URLChanged_t *pCmd ) +{ + m_sCurrentURL = pCmd->pchURL; + + KeyValues *pMessage = new KeyValues( "OnURLChanged" ); + pMessage->SetString( "url", pCmd->pchURL ); + pMessage->SetString( "postdata", pCmd->pchPostData ); + pMessage->SetInt( "isredirect", pCmd->bIsRedirect ? 1 : 0 ); + + PostActionSignal( pMessage ); + + OnURLChanged( m_sCurrentURL, pCmd->pchPostData, pCmd->bIsRedirect ); +} + + +//----------------------------------------------------------------------------- +// Purpose: finished loading this page +//----------------------------------------------------------------------------- +void HTML::BrowserFinishedRequest( HTML_FinishedRequest_t *pCmd ) +{ + PostActionSignal( new KeyValues( "OnFinishRequest", "url", pCmd->pchURL ) ); + if ( pCmd->pchPageTitle && pCmd->pchPageTitle[0] ) + PostActionSignal( new KeyValues( "PageTitleChange", "title", pCmd->pchPageTitle ) ); + + CUtlMap < CUtlString, CUtlString > mapHeaders; + mapHeaders.SetLessFunc( UtlStringLessFunc ); + // headers are no longer reported on loads + + OnFinishRequest( pCmd->pchURL, pCmd->pchPageTitle, mapHeaders ); +} + +//----------------------------------------------------------------------------- +// Purpose: browser wants to open a new tab +//----------------------------------------------------------------------------- +void HTML::BrowserOpenNewTab( HTML_OpenLinkInNewTab_t *pCmd ) +{ + (pCmd); + // Not supported by default, if a child class overrides us and knows how to handle tabs, then it can do this. +} + +//----------------------------------------------------------------------------- +// Purpose: display a new html window +//----------------------------------------------------------------------------- +void HTML::BrowserPopupHTMLWindow( HTML_NewWindow_t *pCmd ) +{ + HTMLPopup *p = new HTMLPopup( this, pCmd->pchURL, "" ); + int wide = pCmd->unWide; + int tall = pCmd->unTall; + if ( wide == 0 || tall == 0 ) + { + wide = MAX( 640, GetWide() ); + tall = MAX( 480, GetTall() ); + } + + p->SetBounds( pCmd->unX, pCmd->unY, wide, tall ); + p->SetDeleteSelfOnClose( true ); + if ( pCmd->unX == 0 || pCmd->unY == 0 ) + p->MoveToCenterOfScreen(); + p->Activate(); + +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us the page title +//----------------------------------------------------------------------------- +void HTML::BrowserSetHTMLTitle( HTML_ChangedTitle_t *pCmd ) +{ + PostMessage( GetParent(), new KeyValues( "OnSetHTMLTitle", "title", pCmd->pchTitle ) ); + OnSetHTMLTitle( pCmd->pchTitle ); +} + + +//----------------------------------------------------------------------------- +// Purpose: status bar details +//----------------------------------------------------------------------------- +void HTML::BrowserStatusText( HTML_StatusText_t *pCmd ) +{ + PostActionSignal( new KeyValues( "OnSetStatusText", "status", pCmd->pchMsg ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us to use this cursor +//----------------------------------------------------------------------------- +void HTML::BrowserSetCursor( HTML_SetCursor_t *pCmd ) +{ + vgui::CursorCode cursor = dc_last; + + switch ( pCmd->eMouseCursor ) + { + case ISteamHTMLSurface::dc_user: + cursor = dc_user; + break; + case ISteamHTMLSurface::dc_none: + cursor = dc_none; + break; + default: + case ISteamHTMLSurface::dc_arrow: + cursor = dc_arrow; + break; + case ISteamHTMLSurface::dc_ibeam: + cursor = dc_ibeam; + break; + case ISteamHTMLSurface::dc_hourglass: + cursor = dc_hourglass; + break; + case ISteamHTMLSurface::dc_waitarrow: + cursor = dc_waitarrow; + break; + case ISteamHTMLSurface::dc_crosshair: + cursor = dc_crosshair; + break; + case ISteamHTMLSurface::dc_up: + cursor = dc_up; + break; + /*case ISteamHTMLSurface::dc_sizenw: + cursor = dc_sizenw; + break; + case ISteamHTMLSurface::dc_sizese: + cursor = dc_sizese; + break; + case ISteamHTMLSurface::dc_sizene: + cursor = dc_sizene; + break; + case ISteamHTMLSurface::dc_sizesw: + cursor = dc_sizesw; + break; + case ISteamHTMLSurface::dc_sizew: + cursor = dc_sizew; + break; + case ISteamHTMLSurface::dc_sizee: + cursor = dc_sizee; + break; + case ISteamHTMLSurface::dc_sizen: + cursor = dc_sizen; + break; + case ISteamHTMLSurface::dc_sizes: + cursor = dc_sizes; + break;*/ + case ISteamHTMLSurface::dc_sizewe: + cursor = dc_sizewe; + break; + case ISteamHTMLSurface::dc_sizens: + cursor = dc_sizens; + break; + case ISteamHTMLSurface::dc_sizeall: + cursor = dc_sizeall; + break; + case ISteamHTMLSurface::dc_no: + cursor = dc_no; + break; + case ISteamHTMLSurface::dc_hand: + cursor = dc_hand; + break; + case ISteamHTMLSurface::dc_blank: + cursor = dc_blank; + break; +/* case ISteamHTMLSurface::dc_middle_pan: + cursor = dc_middle_pan; + break; + case ISteamHTMLSurface::dc_north_pan: + cursor = dc_north_pan; + break; + case ISteamHTMLSurface::dc_north_east_pan: + cursor = dc_north_east_pan; + break; + case ISteamHTMLSurface::dc_east_pan: + cursor = dc_east_pan; + break; + case ISteamHTMLSurface::dc_south_east_pan: + cursor = dc_south_east_pan; + break; + case ISteamHTMLSurface::dc_south_pan: + cursor = dc_south_pan; + break; + case ISteamHTMLSurface::dc_south_west_pan: + cursor = dc_south_west_pan; + break; + case ISteamHTMLSurface::dc_west_pan: + cursor = dc_west_pan; + break; + case ISteamHTMLSurface::dc_north_west_pan: + cursor = dc_north_west_pan; + break; + case ISteamHTMLSurface::dc_alias: + cursor = dc_alias; + break; + case ISteamHTMLSurface::dc_cell: + cursor = dc_cell; + break; + case ISteamHTMLSurface::dc_colresize: + cursor = dc_colresize; + break; + case ISteamHTMLSurface::dc_copycur: + cursor = dc_copycur; + break; + case ISteamHTMLSurface::dc_verticaltext: + cursor = dc_verticaltext; + break; + case ISteamHTMLSurface::dc_rowresize: + cursor = dc_rowresize; + break; + case ISteamHTMLSurface::dc_zoomin: + cursor = dc_zoomin; + break; + case ISteamHTMLSurface::dc_zoomout: + cursor = dc_zoomout; + break; + case ISteamHTMLSurface::dc_custom: + cursor = dc_custom; + break; + case ISteamHTMLSurface::dc_help: + cursor = dc_help; + break;*/ + + } + + if ( cursor >= dc_last ) + { + cursor = dc_arrow; + } + + SetCursor( cursor ); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling to show the file loading dialog +//----------------------------------------------------------------------------- +void HTML::BrowserFileLoadDialog( HTML_FileOpenDialog_t *pCmd ) +{ + // couldn't access an OS-specific dialog, use the internal one + if ( m_hFileOpenDialog.Get() ) + { + delete m_hFileOpenDialog.Get(); + m_hFileOpenDialog = NULL; + } + m_hFileOpenDialog = new FileOpenDialog( this, pCmd->pchTitle, true ); + m_hFileOpenDialog->SetStartDirectory( pCmd->pchInitialFile ); + m_hFileOpenDialog->AddActionSignalTarget( this ); + m_hFileOpenDialog->SetAutoDelete( true ); + m_hFileOpenDialog->DoModal(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser asking to show a tooltip +//----------------------------------------------------------------------------- +void HTML::BrowserShowToolTip( HTML_ShowToolTip_t *pCmd ) +{ +/* + BR FIXME + Tooltip *tip = GetTooltip(); + tip->SetText( pCmd->text().c_str() ); + tip->SetTooltipFormatToMultiLine(); + tip->SetTooltipDelayMS( 250 ); + tip->SetMaxToolTipWidth( MAX( 200, GetWide()/2 ) ); + tip->ShowTooltip( this ); + */ + +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us to update tool tip text +//----------------------------------------------------------------------------- +void HTML::BrowserUpdateToolTip( HTML_UpdateToolTip_t *pCmd ) +{ +// GetTooltip()->SetText( pCmd->text().c_str() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling that it is done with the tip +//----------------------------------------------------------------------------- +void HTML::BrowserHideToolTip( HTML_HideToolTip_t *pCmd ) +{ +// GetTooltip()->HideTooltip(); +// DeleteToolTip(); +} + + +//----------------------------------------------------------------------------- +// Purpose: callback when performing a search +//----------------------------------------------------------------------------- +void HTML::BrowserSearchResults( HTML_SearchResults_t *pCmd ) +{ + if ( pCmd->unResults == 0 ) + m_pFindBar->HideCountLabel(); + else + m_pFindBar->ShowCountLabel(); + + if ( pCmd->unResults > 0 ) + m_pFindBar->SetDialogVariable( "findcount", (int)pCmd->unResults ); + if ( pCmd->unCurrentMatch > 0 ) + m_pFindBar->SetDialogVariable( "findactive", (int)pCmd->unCurrentMatch ); + m_pFindBar->InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us it had a close requested +//----------------------------------------------------------------------------- +void HTML::BrowserClose( HTML_CloseBrowser_t *pCmd ) +{ + PostActionSignal( new KeyValues( "OnCloseWindow" ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us the size of the horizontal scrollbars +//----------------------------------------------------------------------------- +void HTML::BrowserHorizontalScrollBarSizeResponse( HTML_HorizontalScroll_t *pCmd ) +{ + ScrollData_t scrollHorizontal; + scrollHorizontal.m_nScroll = pCmd->unScrollCurrent; + scrollHorizontal.m_nMax = pCmd->unScrollMax; + scrollHorizontal.m_bVisible = pCmd->bVisible; + scrollHorizontal.m_flZoom = pCmd->flPageScale; + + if ( scrollHorizontal != m_scrollHorizontal ) + { + m_scrollHorizontal = scrollHorizontal; + UpdateSizeAndScrollBars(); + m_bNeedsFullTextureUpload = true; + } + else + m_scrollHorizontal = scrollHorizontal; +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us the size of the vertical scrollbars +//----------------------------------------------------------------------------- +void HTML::BrowserVerticalScrollBarSizeResponse( HTML_VerticalScroll_t *pCmd ) +{ + ScrollData_t scrollVertical; + scrollVertical.m_nScroll = pCmd->unScrollCurrent; + scrollVertical.m_nMax = pCmd->unScrollMax; + scrollVertical.m_bVisible = pCmd->bVisible; + scrollVertical.m_flZoom = pCmd->flPageScale; + + if ( scrollVertical != m_scrollVertical ) + { + m_scrollVertical = scrollVertical; + UpdateSizeAndScrollBars(); + m_bNeedsFullTextureUpload = true; + } + else + m_scrollVertical = scrollVertical; +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us what is at this location on the page +//----------------------------------------------------------------------------- +void HTML::BrowserLinkAtPositionResponse( HTML_LinkAtPosition_t *pCmd ) +{ + m_LinkAtPos.m_sURL = pCmd->pchURL; + m_LinkAtPos.m_nX = pCmd->x; + m_LinkAtPos.m_nY = pCmd->y; + + m_pContextMenu->SetItemVisible( m_iCopyLinkMenuItemID, !m_LinkAtPos.m_sURL.IsEmpty() ? true : false ); + if ( m_bRequestingDragURL ) + { + m_bRequestingDragURL = false; + m_sDragURL = m_LinkAtPos.m_sURL; + // make sure we get notified when the mouse gets released + if ( !m_sDragURL.IsEmpty() ) + { + input()->SetMouseCapture( GetVPanel() ); + } + } + + if ( m_bRequestingCopyLink ) + { + m_bRequestingCopyLink = false; + if ( !m_LinkAtPos.m_sURL.IsEmpty() ) + system()->SetClipboardText( m_LinkAtPos.m_sURL, m_LinkAtPos.m_sURL.Length() ); + else + system()->SetClipboardText( "", 1 ); + } + + OnLinkAtPosition( m_LinkAtPos.m_sURL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us to pop a javascript alert dialog +//----------------------------------------------------------------------------- +void HTML::BrowserJSAlert( HTML_JSAlert_t *pCmd ) +{ + MessageBox *pDlg = new MessageBox( m_sCurrentURL, (const char *)pCmd->pchMessage, this ); + pDlg->AddActionSignalTarget( this ); + pDlg->SetCommand( new KeyValues( "DismissJSDialog", "result", false ) ); + pDlg->DoModal(); +} + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us to pop a js confirm dialog +//----------------------------------------------------------------------------- +void HTML::BrowserJSConfirm( HTML_JSConfirm_t *pCmd ) +{ + QueryBox *pDlg = new QueryBox( m_sCurrentURL, (const char *)pCmd->pchMessage, this ); + pDlg->AddActionSignalTarget( this ); + pDlg->SetOKCommand( new KeyValues( "DismissJSDialog", "result", true ) ); + pDlg->SetCancelCommand( new KeyValues( "DismissJSDialog", "result", false ) ); + pDlg->DoModal(); +} + + +//----------------------------------------------------------------------------- +// Purpose: got an answer from the dialog, tell cef +//----------------------------------------------------------------------------- +void HTML::DismissJSDialog( int bResult ) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->JSDialogResponse( m_unBrowserHandle, bResult ); +}; + + +//----------------------------------------------------------------------------- +// Purpose: browser telling us the state of back and forward buttons +//----------------------------------------------------------------------------- +void HTML::BrowserCanGoBackandForward( HTML_CanGoBackAndForward_t *pCmd ) +{ + m_bCanGoBack = pCmd->bCanGoBack; + m_bCanGoForward = pCmd->bCanGoForward; +} + + +//----------------------------------------------------------------------------- +// Purpose: ask the browser for what is at this x,y +//----------------------------------------------------------------------------- +void HTML::GetLinkAtPosition( int x, int y ) +{ + if (m_SteamAPIContext.SteamHTMLSurface()) + m_SteamAPIContext.SteamHTMLSurface()->GetLinkAtPosition( m_unBrowserHandle, x, y ); +} + + +//----------------------------------------------------------------------------- +// Purpose: update the size of the browser itself and scrollbars it shows +//----------------------------------------------------------------------------- +void HTML::UpdateSizeAndScrollBars() +{ + BrowserResize(); + InvalidateLayout(); +} + + diff --git a/vgui2/vgui_controls/Image.cpp b/vgui2/vgui_controls/Image.cpp new file mode 100644 index 0000000..30af296 --- /dev/null +++ b/vgui2/vgui_controls/Image.cpp @@ -0,0 +1,282 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <Color.h> +#include <vgui/IPanel.h> +#include <vgui/ISurface.h> + +#include <vgui_controls/Image.h> +#include <vgui_controls/Controls.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Conctructor. Start with default position and default color. +//----------------------------------------------------------------------------- +Image::Image() +{ + SetPos(0,0); + SetSize(0,0); + SetColor(Color(255,255,255,255)); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Image::~Image() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Set the position of the image, you need to reset this every time you +// call Paint() +//----------------------------------------------------------------------------- +void Image::SetPos(int x,int y) +{ + _pos[0]=x; + _pos[1]=y; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the position of the image +//----------------------------------------------------------------------------- +void Image::GetPos(int& x,int& y) +{ + x=_pos[0]; + y=_pos[1]; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the size of the image +//----------------------------------------------------------------------------- +void Image::GetSize(int &wide, int &tall) +{ + wide = _size[0]; + tall = _size[1]; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the size of the image contents (by default the set size) +//----------------------------------------------------------------------------- +void Image::GetContentSize(int &wide, int &tall) +{ + GetSize(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the size of the image +//----------------------------------------------------------------------------- +void Image::SetSize(int wide, int tall) +{ + _size[0]=wide; + _size[1]=tall; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the draw color using a Color struct. +//----------------------------------------------------------------------------- +void Image::DrawSetColor(Color col) +{ + surface()->DrawSetColor(col[0], col[1], col[2], col[3]); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the draw color using RGBA ints +//----------------------------------------------------------------------------- +void Image::DrawSetColor(int r,int g,int b,int a) +{ + surface()->DrawSetColor(r,g,b,a); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a filled rectangle +//----------------------------------------------------------------------------- +void Image::DrawFilledRect(int x0,int y0,int x1,int y1) +{ + x0+=_pos[0]; + y0+=_pos[1]; + x1+=_pos[0]; + y1+=_pos[1]; + surface()->DrawFilledRect(x0,y0,x1,y1); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw an outlined rectangle +//----------------------------------------------------------------------------- +void Image::DrawOutlinedRect(int x0,int y0,int x1,int y1) +{ + x0+=_pos[0]; + y0+=_pos[1]; + x1+=_pos[0]; + y1+=_pos[1]; + surface()->DrawOutlinedRect(x0,y0,x1,y1); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a line between two points +//----------------------------------------------------------------------------- +void Image::DrawLine(int x0,int y0,int x1,int y1) +{ + x0+=_pos[0]; + y0+=_pos[1]; + x1+=_pos[0]; + y1+=_pos[1]; + surface()->DrawLine(x0,y0,x1,y1); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a line between a list of 'numPoints' points +//----------------------------------------------------------------------------- +void Image::DrawPolyLine(int *px, int *py, int numPoints) +{ + // update the positions to be relative to this panel + for(int i=0;i<numPoints;i++) + { + px[i] += _pos[0]; + py[i] += _pos[1]; + } + + surface()->DrawPolyLine(px, py, numPoints); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the font +//----------------------------------------------------------------------------- +void Image::DrawSetTextFont(HFont font) +{ + surface()->DrawSetTextFont(font); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text color using a color struct +//----------------------------------------------------------------------------- +void Image::DrawSetTextColor(Color sc) +{ + surface()->DrawSetTextColor(sc[0], sc[1], sc[2], sc[3]); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text color useing RGBA ints +//----------------------------------------------------------------------------- +void Image::DrawSetTextColor(int r,int g,int b,int a) +{ + surface()->DrawSetTextColor(r,g,b,a); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text position +//----------------------------------------------------------------------------- +void Image::DrawSetTextPos(int x,int y) +{ + x+=_pos[0]; + y+=_pos[1]; + surface()->DrawSetTextPos(x,y); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a text string +//----------------------------------------------------------------------------- +void Image::DrawPrintText(const wchar_t *str,int strlen) +{ + surface()->DrawPrintText(str, strlen); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a text string at the given coords. +//----------------------------------------------------------------------------- +void Image::DrawPrintText(int x, int y, const wchar_t *str, int strlen) +{ + x += _pos[0]; + y += _pos[1]; + + surface()->DrawSetTextPos(x, y); + surface()->DrawPrintText(str, strlen); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a character +//----------------------------------------------------------------------------- +void Image::DrawPrintChar(wchar_t ch) +{ + surface()->DrawUnicodeChar(ch); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a character at the given coords +//----------------------------------------------------------------------------- +void Image::DrawPrintChar(int x, int y, wchar_t ch) +{ + x+=_pos[0]; + y+=_pos[1]; + + surface()->DrawSetTextPos(x, y); + surface()->DrawUnicodeChar(ch); +} + +//----------------------------------------------------------------------------- +// Purpose: Set a texture +//----------------------------------------------------------------------------- +void Image::DrawSetTexture(int id) +{ + surface()->DrawSetTexture(id); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a rectangle filled with the current texture +//----------------------------------------------------------------------------- +void Image::DrawTexturedRect(int x0,int y0,int x1,int y1) +{ + surface()->DrawTexturedRect(x0,y0,x1,y1); +} + +//----------------------------------------------------------------------------- +// Purpose: Paint the contents of the image on screen. +// You must call this explicitly each frame. +//----------------------------------------------------------------------------- +void Image::Paint() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Set the current color using a color struct +//----------------------------------------------------------------------------- +void Image::SetColor(Color color) +{ + _color=color; + DrawSetTextColor(color); // now update the device context underneath us :) +} + +//----------------------------------------------------------------------------- +// Purpose: Get the current color as a color struct +//----------------------------------------------------------------------------- +Color Image::GetColor() +{ + return _color; +} + +bool Image::Evict() +{ + return false; +} + +int Image::GetNumFrames() +{ + return 0; +} + +void Image::SetFrame( int nFrame ) +{ +} + +HTexture Image::GetID() +{ + return 0; +} + diff --git a/vgui2/vgui_controls/ImageList.cpp b/vgui2/vgui_controls/ImageList.cpp new file mode 100644 index 0000000..dbf8e88 --- /dev/null +++ b/vgui2/vgui_controls/ImageList.cpp @@ -0,0 +1,106 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/VGUI.h> +#include <Color.h> + +#include <vgui_controls/ImageList.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: blank image, intentially draws nothing +//----------------------------------------------------------------------------- +class BlankImage : public IImage +{ +public: + virtual void Paint() {} + virtual void SetPos(int x, int y) {} + virtual void GetContentSize(int &wide, int &tall) { wide = 0; tall = 0; } + virtual void GetSize(int &wide, int &tall) { wide = 0; tall = 0; } + virtual void SetSize(int wide, int tall) {} + virtual void SetColor(Color col) {} + virtual bool Evict() { return false; } + virtual int GetNumFrames() { return 0; } + virtual void SetFrame( int nFrame ) {} + virtual HTexture GetID() { return 0; } + virtual void SetRotation( int iRotation ) { return; }; +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ImageList::ImageList(bool deleteImagesWhenDone) +{ + m_bDeleteImagesWhenDone = deleteImagesWhenDone; + AddImage(new BlankImage()); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +ImageList::~ImageList() +{ + if (m_bDeleteImagesWhenDone) + { + // delete all the images, except for the first image (which is always the blank image) + for (int i = 1; i < m_Images.Count(); i++) + { + delete m_Images[i]; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: adds a new image to the list, returning the index it was placed at +//----------------------------------------------------------------------------- +int ImageList::AddImage(vgui::IImage *image) +{ + return m_Images.AddToTail(image); +} + +//----------------------------------------------------------------------------- +// Purpose: sets an image at a specified index, growing and adding NULL images if necessary +//----------------------------------------------------------------------------- +void ImageList::SetImageAtIndex(int index, vgui::IImage *image) +{ + // allocate more images if necessary + while (m_Images.Count() <= index) + { + m_Images.AddToTail(NULL); + } + + m_Images[index] = image; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the number of images +//----------------------------------------------------------------------------- +int ImageList::GetImageCount() +{ + return m_Images.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: gets an image, imageIndex is of range [0, GetImageCount) +//----------------------------------------------------------------------------- +vgui::IImage *ImageList::GetImage(int imageIndex) +{ + return m_Images[imageIndex]; +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if an index is valid +//----------------------------------------------------------------------------- +bool ImageList::IsValidIndex(int imageIndex) +{ + return m_Images.IsValidIndex(imageIndex); +} + diff --git a/vgui2/vgui_controls/ImagePanel.cpp b/vgui2/vgui_controls/ImagePanel.cpp new file mode 100644 index 0000000..97ceb43 --- /dev/null +++ b/vgui2/vgui_controls/ImagePanel.cpp @@ -0,0 +1,475 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> + +#include <vgui/IBorder.h> +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <vgui/IBorder.h> +#include <KeyValues.h> + +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/Image.h> +#include <vgui_controls/Controls.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( ImagePanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ImagePanel::ImagePanel(Panel *parent, const char *name) : Panel(parent, name) +{ + m_pImage = NULL; + m_pszImageName = NULL; + m_pszFillColorName = NULL; + m_pszDrawColorName = NULL; // HPE addition + m_bCenterImage = false; + m_bScaleImage = false; + m_bTileImage = false; + m_bTileHorizontally = false; + m_bTileVertically = false; + m_bPositionImage = true; + m_fScaleAmount = 0.0f; + m_FillColor = Color(0, 0, 0, 0); + m_DrawColor = Color(255,255,255,255); + m_iRotation = ROTATED_UNROTATED; + + SetImage( m_pImage ); + + REGISTER_COLOR_AS_OVERRIDABLE( m_FillColor, "fillcolor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( m_DrawColor, "drawcolor_override" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ImagePanel::~ImagePanel() +{ + delete [] m_pszImageName; + delete [] m_pszFillColorName; + delete [] m_pszDrawColorName; // HPE addition +} + +//----------------------------------------------------------------------------- +// Purpose: handles size changing +//----------------------------------------------------------------------------- +void ImagePanel::OnSizeChanged(int newWide, int newTall) +{ + BaseClass::OnSizeChanged(newWide, newTall); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ImagePanel::SetImage(IImage *image) +{ + m_pImage = image; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets an image by file name +//----------------------------------------------------------------------------- +void ImagePanel::SetImage(const char *imageName) +{ + if ( imageName && m_pszImageName && V_stricmp( imageName, m_pszImageName ) == 0 ) + return; + + int len = Q_strlen(imageName) + 1; + delete [] m_pszImageName; + m_pszImageName = new char[ len ]; + Q_strncpy(m_pszImageName, imageName, len ); + InvalidateLayout(false, true); // force applyschemesettings to run +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +IImage *ImagePanel::GetImage() +{ + return m_pImage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color ImagePanel::GetDrawColor( void ) +{ + return m_DrawColor; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ImagePanel::SetDrawColor( Color drawColor ) +{ + m_DrawColor = drawColor; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ImagePanel::PaintBackground() +{ + if (m_FillColor[3] > 0) + { + // draw the specified fill color + int wide, tall; + GetSize(wide, tall); + surface()->DrawSetColor(m_FillColor); + surface()->DrawFilledRect(0, 0, wide, tall); + } + if ( m_pImage ) + { + //============================================================================= + // HPE_BEGIN: + // [pfreese] Color should be always set from GetDrawColor(), not just when + // scaling is true (see previous code) + //============================================================================= + + // surface()->DrawSetColor( 255, 255, 255, GetAlpha() ); + m_pImage->SetColor( GetDrawColor() ); + m_pImage->SetRotation( m_iRotation ); + + //============================================================================= + // HPE_END + //============================================================================= + + if ( m_bPositionImage ) + { + if ( m_bCenterImage ) + { + int wide, tall; + GetSize(wide, tall); + + int imageWide, imageTall; + m_pImage->GetSize( imageWide, imageTall ); + + if ( m_bScaleImage && m_fScaleAmount > 0.0f ) + { + imageWide = static_cast<int>( static_cast<float>(imageWide) * m_fScaleAmount ); + imageTall = static_cast<int>( static_cast<float>(imageTall) * m_fScaleAmount ); + } + + m_pImage->SetPos( (wide - imageWide) / 2, (tall - imageTall) / 2 ); + } + else + { + m_pImage->SetPos(0, 0); + } + } + + if (m_bScaleImage) + { + // Image size is stored in the bitmap, so temporarily set its size + // to our panel size and then restore after we draw it. + + int imageWide, imageTall; + m_pImage->GetSize( imageWide, imageTall ); + + if ( m_fScaleAmount > 0.0f ) + { + float wide, tall; + wide = static_cast<float>(imageWide) * m_fScaleAmount; + tall = static_cast<float>(imageTall) * m_fScaleAmount; + m_pImage->SetSize( static_cast<int>(wide), static_cast<int>(tall) ); + } + else + { + int wide, tall; + GetSize( wide, tall ); + m_pImage->SetSize( wide, tall ); + } + + m_pImage->Paint(); + + m_pImage->SetSize( imageWide, imageTall ); + } + else if ( m_bTileImage || m_bTileHorizontally || m_bTileVertically ) + { + int wide, tall; + GetSize(wide, tall); + int imageWide, imageTall; + m_pImage->GetSize( imageWide, imageTall ); + + int y = 0; + while ( y < tall ) + { + int x = 0; + while (x < wide) + { + m_pImage->SetPos(x,y); + m_pImage->Paint(); + + x += imageWide; + + if ( !m_bTileHorizontally ) + break; + } + + y += imageTall; + + if ( !m_bTileVertically ) + break; + } + + if ( m_bPositionImage ) + { + m_pImage->SetPos(0, 0); + } + } + else + { + m_pImage->SetColor( GetDrawColor() ); + m_pImage->Paint(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets control settings for editing +//----------------------------------------------------------------------------- +void ImagePanel::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + if (m_pszImageName) + { + outResourceData->SetString("image", m_pszImageName); + } + if (m_pszFillColorName) + { + outResourceData->SetString("fillcolor", m_pszFillColorName); + } + + //============================================================================= + // HPE_BEGIN: + // [pfreese] Added support for specifying drawcolor + //============================================================================= + if (m_pszDrawColorName) + { + outResourceData->SetString("drawcolor", m_pszDrawColorName); + } + //============================================================================= + // HPE_END + //============================================================================= + + if (GetBorder()) + { + outResourceData->SetString("border", GetBorder()->GetName()); + } + + outResourceData->GetInt("positionImage", m_bPositionImage ); + outResourceData->SetInt("scaleImage", m_bScaleImage); + outResourceData->SetFloat("scaleAmount", m_fScaleAmount); + outResourceData->SetInt("tileImage", m_bTileImage); + outResourceData->SetInt("tileHorizontally", m_bTileHorizontally); + outResourceData->SetInt("tileVertically", m_bTileVertically); +} + +//----------------------------------------------------------------------------- +// Purpose: Applies designer settings from res file +//----------------------------------------------------------------------------- +void ImagePanel::ApplySettings(KeyValues *inResourceData) +{ + delete [] m_pszImageName; + delete [] m_pszFillColorName; + delete [] m_pszDrawColorName; // HPE addition + m_pszImageName = NULL; + m_pszFillColorName = NULL; + m_pszDrawColorName = NULL; // HPE addition + + m_bPositionImage = inResourceData->GetInt("positionImage", 1); + m_bScaleImage = inResourceData->GetInt("scaleImage", 0); + m_fScaleAmount = inResourceData->GetFloat("scaleAmount", 0.0f); + m_bTileImage = inResourceData->GetInt("tileImage", 0); + m_bTileHorizontally = inResourceData->GetInt("tileHorizontally", m_bTileImage); + m_bTileVertically = inResourceData->GetInt("tileVertically", m_bTileImage); + m_iRotation = inResourceData->GetInt( "rotation", ROTATED_UNROTATED ); + const char *imageName = inResourceData->GetString("image", ""); + if ( *imageName ) + { + SetImage( imageName ); + } + + const char *pszFillColor = inResourceData->GetString("fillcolor", ""); + if (*pszFillColor) + { + int r = 0, g = 0, b = 0, a = 255; + int len = Q_strlen(pszFillColor) + 1; + m_pszFillColorName = new char[ len ]; + Q_strncpy( m_pszFillColorName, pszFillColor, len ); + + if (sscanf(pszFillColor, "%d %d %d %d", &r, &g, &b, &a) >= 3) + { + // it's a direct color + m_FillColor = Color(r, g, b, a); + } + else + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + m_FillColor = pScheme->GetColor(pszFillColor, Color(0, 0, 0, 0)); + } + } + + //============================================================================= + // HPE_BEGIN: + // [pfreese] Added support for specifying drawcolor + //============================================================================= + const char *pszDrawColor = inResourceData->GetString("drawcolor", ""); + if (*pszDrawColor) + { + int r = 255, g = 255, b = 255, a = 255; + int len = Q_strlen(pszDrawColor) + 1; + m_pszDrawColorName = new char[ len ]; + Q_strncpy( m_pszDrawColorName, pszDrawColor, len ); + + if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3) + { + // it's a direct color + m_DrawColor = Color(r, g, b, a); + } + else + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + m_DrawColor = pScheme->GetColor(pszDrawColor, Color(255, 255, 255, 255)); + } + } + //============================================================================= + // HPE_END + //============================================================================= + + const char *pszBorder = inResourceData->GetString("border", ""); + if (*pszBorder) + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + SetBorder(pScheme->GetBorder(pszBorder)); + } + + BaseClass::ApplySettings(inResourceData); +} + +//----------------------------------------------------------------------------- +// Purpose: load the image, this is done just before this control is displayed +//----------------------------------------------------------------------------- +void ImagePanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings(pScheme); + if ( m_pszImageName && strlen( m_pszImageName ) > 0 ) + { + SetImage(scheme()->GetImage(m_pszImageName, m_bScaleImage)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Describes editing details +//----------------------------------------------------------------------------- +const char *ImagePanel::GetDescription() +{ + static char buf[1024]; + _snprintf(buf, sizeof(buf), "%s, string image, string border, string fillcolor, bool scaleImage", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: sets whether or not the image should scale to fit the size of the ImagePanel (defaults to false) +//----------------------------------------------------------------------------- +void ImagePanel::SetShouldScaleImage( bool state ) +{ + m_bScaleImage = state; +} + +//----------------------------------------------------------------------------- +// Purpose: gets whether or not the image should be scaled to fit the size of the ImagePanel +//----------------------------------------------------------------------------- +bool ImagePanel::GetShouldScaleImage() +{ + return m_bScaleImage; +} + +//----------------------------------------------------------------------------- +// Purpose: used in conjunction with setting that the image should scale and defines an absolute scale amount +//----------------------------------------------------------------------------- +void ImagePanel::SetScaleAmount( float scale ) +{ + m_fScaleAmount = scale; +} + +float ImagePanel::GetScaleAmount( void ) +{ + return m_fScaleAmount; +} + +//----------------------------------------------------------------------------- +// Purpose: set the color to fill with, if no Image is specified +//----------------------------------------------------------------------------- +void ImagePanel::SetFillColor( Color col ) +{ + m_FillColor = col; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +Color ImagePanel::GetFillColor() +{ + return m_FillColor; +} + +char *ImagePanel::GetImageName() +{ + return m_pszImageName; +} + +bool ImagePanel::EvictImage() +{ + if ( !m_pImage ) + { + // nothing to do + return false; + } + + if ( !scheme()->DeleteImage( m_pszImageName ) ) + { + // no eviction occured, could have an outstanding reference + return false; + } + + // clear out our cached concept of it + // as it may change + // the next SetImage() will re-establish + m_pImage = NULL; + delete [] m_pszImageName; + m_pszImageName = NULL; + + return true; +} + +int ImagePanel::GetNumFrames() +{ + if ( !m_pImage ) + { + return 0; + } + + return m_pImage->GetNumFrames(); +} + +void ImagePanel::SetFrame( int nFrame ) +{ + if ( !m_pImage ) + { + return; + } + + return m_pImage->SetFrame( nFrame ); +} diff --git a/vgui2/vgui_controls/InputDialog.cpp b/vgui2/vgui_controls/InputDialog.cpp new file mode 100644 index 0000000..ca7cf70 --- /dev/null +++ b/vgui2/vgui_controls/InputDialog.cpp @@ -0,0 +1,236 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <vgui_controls/InputDialog.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/TextEntry.h> +#include "tier1/KeyValues.h" +#include "vgui/IInput.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +BaseInputDialog::BaseInputDialog( vgui::Panel *parent, const char *title ) : + BaseClass( parent, NULL ) +{ + m_pContextKeyValues = NULL; + + SetDeleteSelfOnClose( true ); + SetTitle(title, true); + SetSize(320, 180); + SetSizeable( false ); + + m_pCancelButton = new Button(this, "CancelButton", "#VGui_Cancel"); + m_pOKButton = new Button(this, "OKButton", "#VGui_OK"); + m_pCancelButton->SetCommand("Cancel"); + m_pOKButton->SetCommand("OK"); + m_pOKButton->SetAsDefaultButton( true ); + + if ( parent ) + { + AddActionSignalTarget( parent ); + } +} + +BaseInputDialog::~BaseInputDialog() +{ + CleanUpContextKeyValues(); +} + +//----------------------------------------------------------------------------- +// Purpose: Cleans up the keyvalues +//----------------------------------------------------------------------------- +void BaseInputDialog::CleanUpContextKeyValues() +{ + if ( m_pContextKeyValues ) + { + m_pContextKeyValues->deleteThis(); + m_pContextKeyValues = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void BaseInputDialog::DoModal( KeyValues *pContextKeyValues ) +{ + CleanUpContextKeyValues(); + m_pContextKeyValues = pContextKeyValues; + BaseClass::DoModal(); +} + +//----------------------------------------------------------------------------- +// Purpose: lays out controls +//----------------------------------------------------------------------------- +void BaseInputDialog::PerformLayout() +{ + BaseClass::PerformLayout(); + + int w, h; + GetSize( w, h ); + + // lay out all the controls + int topy = IsSmallCaption() ? 15 : 30; + int halfw = w / 2; + + PerformLayout( 12, topy, w - 24, h - 100 ); + + m_pOKButton->SetBounds( halfw - 84, h - 30, 72, 24 ); + m_pCancelButton->SetBounds( halfw + 12, h - 30, 72, 24 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: handles button commands +//----------------------------------------------------------------------------- +void BaseInputDialog::OnCommand(const char *command) +{ + KeyValues *kv = NULL; + if ( !stricmp( command, "OK" ) ) + { + kv = new KeyValues( "InputCompleted" ); + kv->SetPtr( "dialog", this ); + } + else if ( !stricmp( command, "Cancel" ) ) + { + kv = new KeyValues( "InputCanceled" ); + } + else + { + BaseClass::OnCommand( command ); + return; + } + + if ( m_pContextKeyValues ) + { + kv->AddSubKey( m_pContextKeyValues ); + m_pContextKeyValues = NULL; + } + PostActionSignal( kv ); + CloseModal(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Utility dialog, used to ask yes/no questions of the user +//----------------------------------------------------------------------------- +InputMessageBox::InputMessageBox( vgui::Panel *parent, const char *title, char const *prompt ) +: BaseClass( parent, title ) +{ + SetSize( 320, 120 ); + + m_pPrompt = new Label( this, "Prompt", prompt ); +} + +InputMessageBox::~InputMessageBox() +{ +} + +void InputMessageBox::PerformLayout( int x, int y, int w, int h ) +{ + m_pPrompt->SetBounds( x, y, w, 24 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +InputDialog::InputDialog(vgui::Panel *parent, const char *title, char const *prompt, char const *defaultValue /*=""*/ ) : + BaseClass(parent, title) +{ + SetSize( 320, 120 ); + + m_pPrompt = new Label( this, "Prompt", prompt ); + + m_pInput = new TextEntry( this, "Text" ); + m_pInput->SetText( defaultValue ); + m_pInput->SelectAllText( true ); + m_pInput->RequestFocus(); +} + + +InputDialog::~InputDialog() +{ +} + + +//----------------------------------------------------------------------------- +// Sets the dialog to be multiline +//----------------------------------------------------------------------------- +void InputDialog::SetMultiline( bool state ) +{ + m_pInput->SetMultiline( state ); + m_pInput->SetCatchEnterKey( state ); +} + + +//----------------------------------------------------------------------------- +// Allow numeric input only +//----------------------------------------------------------------------------- +void InputDialog::AllowNumericInputOnly( bool bOnlyNumeric ) +{ + if ( m_pInput ) + { + m_pInput->SetAllowNumericInputOnly( bOnlyNumeric ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: lays out controls +//----------------------------------------------------------------------------- +void InputDialog::PerformLayout( int x, int y, int w, int h ) +{ + m_pPrompt->SetBounds( x, y, w, 24 ); + m_pInput ->SetBounds( x, y + 30, w, m_pInput->IsMultiline() ? h - 30 : 24 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: handles button commands +//----------------------------------------------------------------------------- +void InputDialog::OnCommand(const char *command) +{ + // overriding OnCommand for backwards compatability + // it'd be nice at some point to find all uses of InputDialog and just use BaseInputDialog's OnCommand + + if (!stricmp(command, "OK")) + { + int nTextLength = m_pInput->GetTextLength() + 1; + char* txt = (char*)_alloca( nTextLength * sizeof(char) ); + m_pInput->GetText( txt, nTextLength ); + KeyValues *kv = new KeyValues( "InputCompleted", "text", txt ); + if ( m_pContextKeyValues ) + { + kv->AddSubKey( m_pContextKeyValues ); + m_pContextKeyValues = NULL; + } + PostActionSignal( kv ); + CloseModal(); + } + else if (!stricmp(command, "Cancel")) + { + KeyValues *kv = new KeyValues( "InputCanceled" ); + if ( m_pContextKeyValues ) + { + kv->AddSubKey( m_pContextKeyValues ); + m_pContextKeyValues = NULL; + } + PostActionSignal( kv ); + CloseModal(); + } + else + { + BaseClass::OnCommand(command); + } +} diff --git a/vgui2/vgui_controls/KeyBindingHelpDialog.cpp b/vgui2/vgui_controls/KeyBindingHelpDialog.cpp new file mode 100644 index 0000000..79bc902 --- /dev/null +++ b/vgui2/vgui_controls/KeyBindingHelpDialog.cpp @@ -0,0 +1,355 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "vgui_controls/KeyBindingHelpDialog.h" +#include "vgui_controls/ListPanel.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" +#include "vgui/ILocalize.h" +#include "vgui/IInput.h" +#include "vgui/ISystem.h" +#include "KeyValues.h" +#include "vgui/Cursor.h" +#include "tier1/utldict.h" +#include "vgui_controls/KeyBoardEditorDialog.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +using namespace vgui; + +// If the user holds the key bound to help down for this long, then the dialog will stay on automatically +#define KB_HELP_CONTINUE_SHOWING_TIME 1.0 + +static bool BindingLessFunc( KeyValues * const & lhs, KeyValues * const &rhs ) +{ + KeyValues *p1, *p2; + + p1 = const_cast< KeyValues * >( lhs ); + p2 = const_cast< KeyValues * >( rhs ); + return ( Q_stricmp( p1->GetString( "Action" ), p2->GetString( "Action" ) ) < 0 ) ? true : false; +} + +CKeyBindingHelpDialog::CKeyBindingHelpDialog( Panel *parent, Panel *panelToView, KeyBindingContextHandle_t handle, KeyCode code, int modifiers ) + : BaseClass( parent, "KeyBindingHelpDialog" ), + m_Handle( handle ), + m_KeyCode( code ), + m_Modifiers( modifiers ), + m_bPermanent( false ) +{ + Assert( panelToView ); + m_hPanel = panelToView; + + m_pList = new ListPanel( this, "KeyBindings" ); + m_pList->SetIgnoreDoubleClick( true ); + m_pList->AddColumnHeader(0, "Action", "#KBEditorBindingName", 175, 0); + m_pList->AddColumnHeader(1, "Binding", "#KBEditorBinding", 175, 0); + m_pList->AddColumnHeader(2, "Description", "#KBEditorDescription", 300, 0); + + LoadControlSettings( "resource/KeyBindingHelpDialog.res" ); + + if ( panelToView && panelToView->GetName() && panelToView->GetName()[0] ) + { + SetTitle( panelToView->GetName(), true ); + } + else + { + SetTitle( "#KBHelpDialogTitle", true ); + } + + SetSmallCaption( true ); + SetMinimumSize( 400, 400 ); + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetSizeable( true ); + SetMoveable( true ); + SetMenuButtonVisible( false ); + + SetVisible( true ); + + MoveToCenterOfScreen(); + + PopulateList(); + + m_flShowTime = system()->GetCurrentTime(); + ivgui()->AddTickSignal( GetVPanel(), 0 ); + + input()->SetAppModalSurface( GetVPanel() ); +} + +CKeyBindingHelpDialog::~CKeyBindingHelpDialog() +{ + if ( input()->GetAppModalSurface() == GetVPanel() ) + { + input()->SetAppModalSurface( 0 ); + } +} + +void CKeyBindingHelpDialog::OnTick() +{ + BaseClass::OnTick(); + + bool keyStillDown = IsHelpKeyStillBeingHeld(); + + double curtime = system()->GetCurrentTime(); + double elapsed = curtime - m_flShowTime; + // After a second of holding the key, releasing the key will close the dialog + if ( elapsed > KB_HELP_CONTINUE_SHOWING_TIME ) + { + if ( !keyStillDown ) + { + MarkForDeletion(); + return; + } + } + // Otherwise, if they tapped the key within a second and now have released... + else if ( !keyStillDown ) + { + // Continue showing dialog indefinitely + ivgui()->RemoveTickSignal( GetVPanel() ); + m_bPermanent = true; + } +} + +// The key originally bound to help was pressed +void CKeyBindingHelpDialog::HelpKeyPressed() +{ + // Don't kill while editor is being shown... + if ( m_hKeyBindingsEditor.Get() ) + return; + + if ( m_bPermanent ) + { + MarkForDeletion(); + } +} + +bool CKeyBindingHelpDialog::IsHelpKeyStillBeingHeld() +{ + bool keyDown = input()->IsKeyDown( m_KeyCode ); + if ( !keyDown ) + return false; + + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + int modifiers = 0; + if ( shift ) + { + modifiers |= MODIFIER_SHIFT; + } + if ( ctrl ) + { + modifiers |= MODIFIER_CONTROL; + } + if ( alt ) + { + modifiers |= MODIFIER_ALT; + } + + if ( modifiers != m_Modifiers ) + { + return false; + } + + return true; +} + +void CKeyBindingHelpDialog::OnCommand( char const *cmd ) +{ + if ( !Q_stricmp( cmd, "OK" ) || + !Q_stricmp( cmd, "cancel" ) || + !Q_stricmp( cmd, "Close" ) ) + { + MarkForDeletion(); + } + else if ( !Q_stricmp( cmd, "edit" ) ) + { + // Show the keybindings edit dialog + if ( m_hKeyBindingsEditor.Get() ) + { + delete m_hKeyBindingsEditor.Get(); + } + + // Don't delete panel if H key is released... + + m_hKeyBindingsEditor = new CKeyBoardEditorDialog( this, m_hPanel, m_Handle ); + m_hKeyBindingsEditor->DoModal(); + + ivgui()->RemoveTickSignal( GetVPanel() ); + m_bPermanent = true; + } + else + { + BaseClass::OnCommand( cmd ); + } +} + +void CKeyBindingHelpDialog::OnKeyCodeTyped(vgui::KeyCode code) +{ + BaseClass::OnKeyCodeTyped( code ); +} + +void CKeyBindingHelpDialog::GetMappingList( Panel *panel, CUtlVector< PanelKeyBindingMap * >& maps ) +{ + PanelKeyBindingMap *map = panel->GetKBMap(); + while ( map ) + { + maps.AddToTail( map ); + map = map->baseMap; + } +} + + +void CKeyBindingHelpDialog::AnsiText( char const *token, char *out, size_t buflen ) +{ + out[ 0 ] = 0; + + wchar_t *str = g_pVGuiLocalize->Find( token ); + if ( !str ) + { + Q_strncpy( out, token, buflen ); + } + else + { + g_pVGuiLocalize->ConvertUnicodeToANSI( str, out, buflen ); + } +} + +struct ListInfo_t +{ + PanelKeyBindingMap *m_pMap; + Panel *m_pPanel; +}; + +void CKeyBindingHelpDialog::PopulateList() +{ + m_pList->DeleteAllItems(); + + int i, j; + + CUtlVector< ListInfo_t > maps; + vgui::Panel *pPanel = m_hPanel; + while ( pPanel->IsKeyBindingChainToParentAllowed() ) + { + PanelKeyBindingMap *map = pPanel->GetKBMap(); + while ( map ) + { + int k; + int c = maps.Count(); + for ( k = 0; k < c; ++k ) + { + if ( maps[k].m_pMap == map ) + break; + } + if ( k == c ) + { + int iMap = maps.AddToTail( ); + maps[iMap].m_pMap = map; + maps[iMap].m_pPanel = pPanel; + } + map = map->baseMap; + } + + pPanel = pPanel->GetParent(); + if ( !pPanel ) + break; + } + + CUtlRBTree< KeyValues *, int > sorted( 0, 0, BindingLessFunc ); + + // add header item + int c = maps.Count(); + for ( i = 0; i < c; ++i ) + { + PanelKeyBindingMap *m = maps[ i ].m_pMap; + pPanel = maps[i].m_pPanel; + Assert( m ); + + int bindings = m->boundkeys.Count(); + for ( j = 0; j < bindings; ++j ) + { + BoundKey_t *kbMap = &m->boundkeys[ j ]; + Assert( kbMap ); + + // Create a new: blank item + KeyValues *item = new KeyValues( "Item" ); + + // Fill in data + char loc[ 128 ]; + Q_snprintf( loc, sizeof( loc ), "#%s", kbMap->bindingname ); + + char ansi[ 256 ]; + AnsiText( loc, ansi, sizeof( ansi ) ); + + item->SetString( "Action", ansi ); + item->SetWString( "Binding", Panel::KeyCodeModifiersToDisplayString( (KeyCode)kbMap->keycode, kbMap->modifiers ) ); + + // Find the binding + KeyBindingMap_t *bindingMap = pPanel->LookupBinding( kbMap->bindingname ); + if ( bindingMap && + bindingMap->helpstring ) + { + AnsiText( bindingMap->helpstring, ansi, sizeof( ansi ) ); + item->SetString( "Description", ansi ); + } + + item->SetPtr( "Item", kbMap ); + + sorted.Insert( item ); + } + + // Now try and find any "unbound" keys... + int mappings = m->entries.Count(); + for ( j = 0; j < mappings; ++j ) + { + KeyBindingMap_t *kbMap = &m->entries[ j ]; + + // See if it's bound + CUtlVector< BoundKey_t * > list; + pPanel->LookupBoundKeys( kbMap->bindingname, list ); + if ( list.Count() > 0 ) + continue; + + // Not bound, add a placeholder entry + // Create a new: blank item + KeyValues *item = new KeyValues( "Item" ); + + // fill in data + char loc[ 128 ]; + Q_snprintf( loc, sizeof( loc ), "#%s", kbMap->bindingname ); + + char ansi[ 256 ]; + AnsiText( loc, ansi, sizeof( ansi ) ); + + item->SetString( "Action", ansi ); + item->SetWString( "Binding", L"" ); + if ( kbMap->helpstring ) + { + AnsiText( kbMap->helpstring, ansi, sizeof( ansi ) ); + item->SetString( "Description", ansi ); + } + + item->SetPtr( "Unbound", kbMap ); + + sorted.Insert( item ); + } + } + + for ( j = sorted.FirstInorder() ; j != sorted.InvalidIndex(); j = sorted.NextInorder( j ) ) + { + KeyValues *item = sorted[ j ]; + + // Add to list + m_pList->AddItem( item, 0, false, false ); + + item->deleteThis(); + } + + sorted.RemoveAll(); +} diff --git a/vgui2/vgui_controls/KeyBoardEditorDialog.cpp b/vgui2/vgui_controls/KeyBoardEditorDialog.cpp new file mode 100644 index 0000000..c177ffa --- /dev/null +++ b/vgui2/vgui_controls/KeyBoardEditorDialog.cpp @@ -0,0 +1,849 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "vgui_controls/KeyBoardEditorDialog.h" +#include "vgui_controls/ListPanel.h" +#include "vgui_controls/Button.h" +#include "vgui_controls/TextEntry.h" +#include "vgui/ISurface.h" +#include "vgui/IInput.h" +#include "vgui/IVGui.h" +#include "vgui/ILocalize.h" +#include "KeyValues.h" +#include "vgui/Cursor.h" +#include "tier1/utldict.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +using namespace vgui; + +static char *CopyString( const char *in ) +{ + if ( !in ) + return NULL; + + int len = strlen( in ); + char *n = new char[ len + 1 ]; + Q_strncpy( n, in, len + 1 ); + return n; +} + +CKeyBoardEditorPage::SaveMapping_t::SaveMapping_t() : map( 0 ) +{ +} + +CKeyBoardEditorPage::SaveMapping_t::SaveMapping_t( const SaveMapping_t& src ) +{ + map = src.map; + current = src.current; + original = src.original; +} + +//----------------------------------------------------------------------------- +// Purpose: Special list subclass to handle drawing of trap mode prompt on top of +// lists client area +//----------------------------------------------------------------------------- +class VControlsListPanel : public ListPanel +{ + DECLARE_CLASS_SIMPLE( VControlsListPanel, ListPanel ); + +public: + // Construction + VControlsListPanel( vgui::Panel *parent, const char *listName ); + virtual ~VControlsListPanel(); + + // Start/end capturing + virtual void StartCaptureMode(vgui::HCursor hCursor = NULL); + virtual void EndCaptureMode(vgui::HCursor hCursor = NULL); + virtual bool IsCapturing(); + + // Set which item should be associated with the prompt + virtual void SetItemOfInterest(int itemID); + virtual int GetItemOfInterest(); + + virtual void OnMousePressed(vgui::MouseCode code); + virtual void OnMouseDoublePressed(vgui::MouseCode code); + + KEYBINDING_FUNC( clearbinding, KEY_DELETE, 0, OnClearBinding, 0, 0 ); + +private: + void ApplySchemeSettings(vgui::IScheme *pScheme ); + + // Are we showing the prompt? + bool m_bCaptureMode; + // If so, where? + int m_nClickRow; + // Font to use for showing the prompt + vgui::HFont m_hFont; + // panel used to edit + class CInlineEditPanel *m_pInlineEditPanel; + int m_iMouseX, m_iMouseY; +}; + +//----------------------------------------------------------------------------- +// Purpose: panel used for inline editing of key bindings +//----------------------------------------------------------------------------- +class CInlineEditPanel : public vgui::Panel +{ + DECLARE_CLASS_SIMPLE( CInlineEditPanel, vgui::Panel ); + +public: + CInlineEditPanel() : vgui::Panel(NULL, "InlineEditPanel") + { + } + + virtual void Paint() + { + int wide, tall; + GetSize(wide, tall); + + // Draw a white rectangle around that cell + vgui::surface()->DrawSetColor( 63, 63, 63, 255 ); + vgui::surface()->DrawFilledRect( 0, 0, wide, tall ); + + vgui::surface()->DrawSetColor( 0, 255, 0, 255 ); + vgui::surface()->DrawOutlinedRect( 0, 0, wide, tall ); + } + + virtual void OnKeyCodeTyped(KeyCode code) + { + // forward up + if (GetParent()) + { + GetParent()->OnKeyCodeTyped(code); + } + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + Panel::ApplySchemeSettings(pScheme); + SetBorder(pScheme->GetBorder("DepressedButtonBorder")); + } + + void OnMousePressed(vgui::MouseCode code) + { + // forward up mouse pressed messages to be handled by the key options + if (GetParent()) + { + GetParent()->OnMousePressed(code); + } + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Construction +//----------------------------------------------------------------------------- +VControlsListPanel::VControlsListPanel( vgui::Panel *parent, const char *listName ) : BaseClass( parent, listName ) +{ + m_bCaptureMode = false; + m_nClickRow = 0; + m_pInlineEditPanel = new CInlineEditPanel(); + m_hFont = INVALID_FONT; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +VControlsListPanel::~VControlsListPanel() +{ + m_pInlineEditPanel->MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void VControlsListPanel::ApplySchemeSettings(IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + m_hFont = pScheme->GetFont("DefaultVerySmall", IsProportional() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Start capture prompt display +//----------------------------------------------------------------------------- +void VControlsListPanel::StartCaptureMode( HCursor hCursor ) +{ + m_bCaptureMode = true; + EnterEditMode(m_nClickRow, 1, m_pInlineEditPanel); + input()->SetMouseFocus(m_pInlineEditPanel->GetVPanel()); + input()->SetMouseCapture(m_pInlineEditPanel->GetVPanel()); + + if (hCursor) + { + m_pInlineEditPanel->SetCursor(hCursor); + + // save off the cursor position so we can restore it + vgui::input()->GetCursorPos( m_iMouseX, m_iMouseY ); + } +} + +void VControlsListPanel::OnClearBinding() +{ + if ( m_bCaptureMode ) + return; + + if ( GetItemOfInterest() < 0 ) + return; + + PostMessage( GetParent()->GetVPanel(), new KeyValues( "ClearBinding", "item", GetItemOfInterest() ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Finish capture prompt display +//----------------------------------------------------------------------------- +void VControlsListPanel::EndCaptureMode( HCursor hCursor ) +{ + m_bCaptureMode = false; + input()->SetMouseCapture(NULL); + LeaveEditMode(); + RequestFocus(); + input()->SetMouseFocus(GetVPanel()); + if (hCursor) + { + m_pInlineEditPanel->SetCursor(hCursor); + surface()->SetCursor(hCursor); + if ( hCursor != dc_none ) + { + vgui::input()->SetCursorPos ( m_iMouseX, m_iMouseY ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set active row column +//----------------------------------------------------------------------------- +void VControlsListPanel::SetItemOfInterest(int itemID) +{ + m_nClickRow = itemID; +} + +//----------------------------------------------------------------------------- +// Purpose: Retrieve row, column of interest +//----------------------------------------------------------------------------- +int VControlsListPanel::GetItemOfInterest() +{ + return m_nClickRow; +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if we're currently waiting to capture a key +//----------------------------------------------------------------------------- +bool VControlsListPanel::IsCapturing( void ) +{ + return m_bCaptureMode; +} + +//----------------------------------------------------------------------------- +// Purpose: Forwards mouse pressed message up to keyboard page when in capture +//----------------------------------------------------------------------------- +void VControlsListPanel::OnMousePressed(vgui::MouseCode code) +{ + if (IsCapturing()) + { + // forward up mouse pressed messages to be handled by the key options + if (GetParent()) + { + GetParent()->OnMousePressed(code); + } + } + else + { + BaseClass::OnMousePressed(code); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: input handler +//----------------------------------------------------------------------------- +void VControlsListPanel::OnMouseDoublePressed( vgui::MouseCode code ) +{ + int c = GetSelectedItemsCount(); + if ( c > 0 ) + { + // enter capture mode + OnKeyCodeTyped(KEY_ENTER); + } + else + { + BaseClass::OnMouseDoublePressed(code); + } +} + +CKeyBoardEditorPage::CKeyBoardEditorPage( Panel *parent, Panel *panelToEdit, KeyBindingContextHandle_t handle ) + : BaseClass( parent, "KeyBoardEditorPage" ), + m_pPanel( panelToEdit ), + m_Handle( handle ) +{ + Assert( m_pPanel ); + + m_pList = new VControlsListPanel( this, "KeyBindings" ); + m_pList->SetIgnoreDoubleClick( true ); + m_pList->AddColumnHeader(0, "Action", "#KBEditorBindingName", 175, 0); + m_pList->AddColumnHeader(1, "Binding", "#KBEditorBinding", 175, 0); + m_pList->AddColumnHeader(2, "Description", "#KBEditorDescription", 300, 0); + + LoadControlSettings( "resource/KeyBoardEditorPage.res" ); + + SaveMappings(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +CKeyBoardEditorPage::~CKeyBoardEditorPage() +{ + int c = m_Save.Count(); + for ( int i = 0 ; i < c; ++i ) + { + delete m_Save[ i ]; + } + m_Save.RemoveAll(); +} + +void CKeyBoardEditorPage::ApplySchemeSettings( IScheme *scheme ) +{ + BaseClass::ApplySchemeSettings( scheme ); + PopulateList(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void CKeyBoardEditorPage::SaveMappings() +{ + Assert( m_Save.Count() == 0 ); + + CUtlVector< PanelKeyBindingMap * > maps; + GetMappingList( m_pPanel, maps ); + + // add header item + int c = maps.Count(); + for ( int i = 0; i < c; ++i ) + { + PanelKeyBindingMap *m = maps[ i ]; + SaveMapping_t *sm = new SaveMapping_t; + sm->map = m; + sm->current = m->boundkeys; + sm->original = m->boundkeys; + m_Save.AddToTail( sm ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void CKeyBoardEditorPage::UpdateCurrentMappings() +{ + int c = m_Save.Count(); + for ( int i = 0 ; i < c; ++i ) + { + PanelKeyBindingMap *m = m_Save[ i ]->map; + Assert( m ); + m_Save[ i ]->current = m->boundkeys; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void CKeyBoardEditorPage::RestoreMappings() +{ + int c = m_Save.Count(); + for ( int i = 0; i < c; ++i ) + { + SaveMapping_t *sm = m_Save[ i ]; + sm->current = sm->original; + } +} + +void CKeyBoardEditorPage::ApplyMappings() +{ + int c = m_Save.Count(); + for ( int i = 0; i < c; ++i ) + { + SaveMapping_t *sm = m_Save[ i ]; + sm->map->boundkeys = sm->current; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: User clicked on item: remember where last active row/column was +//----------------------------------------------------------------------------- +void CKeyBoardEditorPage::ItemSelected() +{ + int c = m_pList->GetSelectedItemsCount(); + if ( c > 0 ) + { + m_pList->SetItemOfInterest( m_pList->GetSelectedItem( 0 ) ); + } +} + +void CKeyBoardEditorPage::BindKey( KeyCode code ) +{ + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + int modifiers = 0; + if ( shift ) + { + modifiers |= MODIFIER_SHIFT; + } + if ( ctrl ) + { + modifiers |= MODIFIER_CONTROL; + } + if ( alt ) + { + modifiers |= MODIFIER_ALT; + } + + int r = m_pList->GetItemOfInterest(); + + // Retrieve clicked row and column + m_pList->EndCaptureMode(dc_arrow); + + // Find item for this row + KeyValues *item = m_pList->GetItem(r); + if ( item ) + { + BoundKey_t *kbMap = reinterpret_cast< BoundKey_t * >( item->GetPtr( "Item", 0 ) ); + if ( kbMap ) + { + KeyBindingMap_t *binding = m_pPanel->LookupBindingByKeyCode( code, modifiers ); + if ( binding && Q_stricmp( kbMap->bindingname, binding->bindingname ) ) + { + // Key is already rebound!!! + Warning( "Can't bind to '%S', key is already bound to '%s'\n", + Panel::KeyCodeToDisplayString( code ), binding->bindingname ); + return; + } + + kbMap->keycode = code; + kbMap->modifiers = modifiers; + + PopulateList(); + } + + KeyBindingMap_t *bindingMap = reinterpret_cast< KeyBindingMap_t * >( item->GetPtr( "Unbound", 0 ) ); + if ( bindingMap ) + { + KeyBindingMap_t *binding = m_pPanel->LookupBindingByKeyCode( code, modifiers ); + if ( binding && Q_stricmp( bindingMap->bindingname, binding->bindingname ) ) + { + // Key is already rebound!!! + Warning( "Can't bind to '%S', key is already bound to '%s'\n", + Panel::KeyCodeToDisplayString( code ), binding->bindingname ); + return; + } + + // Need to add to current entries + m_pPanel->AddKeyBinding( bindingMap->bindingname, code, modifiers ); + UpdateCurrentMappings(); + PopulateList(); + } + } +} + +void CKeyBoardEditorPage::OnPageHide() +{ + if ( m_pList->IsCapturing() ) + { + // Cancel capturing + m_pList->EndCaptureMode(dc_arrow); + } +} + +//----------------------------------------------------------------------------- +// Purpose: binds double-clicking or hitting enter in the keybind list to changing the key +//----------------------------------------------------------------------------- +void CKeyBoardEditorPage::OnKeyCodeTyped(vgui::KeyCode code) +{ + switch ( code ) + { + case KEY_ENTER: + { + if ( !m_pList->IsCapturing() ) + { + OnCommand( "ChangeKey" ); + } + else + { + BindKey( code ); + } + } + break; + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_LALT: + case KEY_RALT: + case KEY_LCONTROL: + case KEY_RCONTROL: + { + // Swallow these + break; + } + break; + default: + { + if ( m_pList->IsCapturing() ) + { + BindKey( code ); + } + else + { + BaseClass::OnKeyCodeTyped(code); + } + } + } +} + +void CKeyBoardEditorPage::OnCommand( char const *cmd ) +{ + if ( !m_pList->IsCapturing() && !Q_stricmp( cmd, "ChangeKey" ) ) + { + m_pList->StartCaptureMode(dc_blank); + } + else + { + BaseClass::OnCommand( cmd ); + } +} + +void CKeyBoardEditorPage::OnSaveChanges() +{ + ApplyMappings(); +} + +void CKeyBoardEditorPage::OnRevert() +{ + RestoreMappings(); + PopulateList(); +} + +void CKeyBoardEditorPage::OnUseDefaults() +{ + m_pPanel->RevertKeyBindingsToDefault(); + UpdateCurrentMappings(); + PopulateList(); +} + +void CKeyBoardEditorPage::GetMappingList( Panel *panel, CUtlVector< PanelKeyBindingMap * >& maps ) +{ + PanelKeyBindingMap *map = panel->GetKBMap(); + while ( map ) + { + maps.AddToTail( map ); + map = map->baseMap; + } +} + +static bool BindingLessFunc( KeyValues * const & lhs, KeyValues * const &rhs ) +{ + KeyValues *p1, *p2; + + p1 = const_cast< KeyValues * >( lhs ); + p2 = const_cast< KeyValues * >( rhs ); + return ( Q_stricmp( p1->GetString( "Action" ), p2->GetString( "Action" ) ) < 0 ) ? true : false; +} + +void CKeyBoardEditorPage::AnsiText( char const *token, char *out, size_t buflen ) +{ + out[ 0 ] = 0; + + wchar_t *str = g_pVGuiLocalize->Find( token ); + if ( !str ) + { + Q_strncpy( out, token, buflen ); + } + else + { + g_pVGuiLocalize->ConvertUnicodeToANSI( str, out, buflen ); + } +} + +void CKeyBoardEditorPage::PopulateList() +{ + m_pList->DeleteAllItems(); + + int i, j; + + CUtlRBTree< KeyValues *, int > sorted( 0, 0, BindingLessFunc ); + + // add header item + int c = m_Save.Count(); + for ( i = 0; i < c; ++i ) + { + SaveMapping_t* sm = m_Save[ i ]; + + PanelKeyBindingMap *m = sm->map; + Assert( m ); + + int bindings = sm->current.Count(); + for ( j = 0; j < bindings; ++j ) + { + BoundKey_t *kbMap = &sm->current[ j ]; + Assert( kbMap ); + + // Create a new: blank item + KeyValues *item = new KeyValues( "Item" ); + + // Fill in data + char loc[ 128 ]; + Q_snprintf( loc, sizeof( loc ), "#%s", kbMap->bindingname ); + + char ansi[ 256 ]; + AnsiText( loc, ansi, sizeof( ansi ) ); + + item->SetString( "Action", ansi ); + item->SetWString( "Binding", Panel::KeyCodeModifiersToDisplayString( (KeyCode)kbMap->keycode, kbMap->modifiers ) ); + + // Find the binding + KeyBindingMap_t *bindingMap = m_pPanel->LookupBinding( kbMap->bindingname ); + if ( bindingMap && + bindingMap->helpstring ) + { + AnsiText( bindingMap->helpstring, ansi, sizeof( ansi ) ); + item->SetString( "Description", ansi); + } + + item->SetPtr( "Item", kbMap ); + + sorted.Insert( item ); + } + + // Now try and find any "unbound" keys... + int mappings = m->entries.Count(); + for ( j = 0; j < mappings; ++j ) + { + KeyBindingMap_t *kbMap = &m->entries[ j ]; + + // See if it's bound + CUtlVector< BoundKey_t * > list; + m_pPanel->LookupBoundKeys( kbMap->bindingname, list ); + if ( list.Count() > 0 ) + continue; + + // Not bound, add a placeholder entry + // Create a new: blank item + KeyValues *item = new KeyValues( "Item" ); + + // fill in data + char loc[ 128 ]; + Q_snprintf( loc, sizeof( loc ), "#%s", kbMap->bindingname ); + + char ansi[ 256 ]; + AnsiText( loc, ansi, sizeof( ansi ) ); + + item->SetString( "Action", ansi ); + item->SetWString( "Binding", L"" ); + if ( kbMap->helpstring ) + { + AnsiText( kbMap->helpstring, ansi, sizeof( ansi ) ); + item->SetString( "Description", ansi ); + } + + item->SetPtr( "Unbound", kbMap ); + + sorted.Insert( item ); + } + } + + for ( j = sorted.FirstInorder() ; j != sorted.InvalidIndex(); j = sorted.NextInorder( j ) ) + { + KeyValues *item = sorted[ j ]; + + // Add to list + m_pList->AddItem( item, 0, false, false ); + + item->deleteThis(); + } + + sorted.RemoveAll(); +} + +void CKeyBoardEditorPage::OnClearBinding( int item ) +{ + // Find item for this row + KeyValues *kv = m_pList->GetItem(item ); + if ( !kv ) + { + return; + } + + BoundKey_t *kbMap = reinterpret_cast< BoundKey_t * >( kv->GetPtr( "Item", 0 ) ); + if ( !kbMap ) + { + return; + } + + kbMap->keycode = KEY_NONE; + kbMap->modifiers = 0; + + PopulateList(); +} + +CKeyBoardEditorSheet::CKeyBoardEditorSheet( Panel *parent, Panel *panelToEdit, KeyBindingContextHandle_t handle ) + : BaseClass( parent, "KeyBoardEditorSheet" ), + m_bSaveToExternalFile( false ), + m_Handle( handle ), + m_SaveFileName( UTL_INVAL_SYMBOL ), + m_SaveFilePathID( UTL_INVAL_SYMBOL ) +{ + m_hPanel = panelToEdit; + + SetSmallTabs( true ); + + // Create this sheet and add the subcontrols + CKeyBoardEditorPage *active = NULL; + + int subCount = Panel::GetPanelsWithKeyBindingsCount( handle ); + for ( int i = 0; i < subCount; ++i ) + { + Panel *p = Panel::GetPanelWithKeyBindings( handle, i ); + if ( !p ) + continue; + + // Don't display panels with no keymappings + if ( p->GetKeyMappingCount() == 0 ) + continue; + + CKeyBoardEditorPage *newPage = new CKeyBoardEditorPage( this, p, handle ); + AddPage( newPage, p->GetName() ); + if ( p == panelToEdit ) + { + active = newPage; + } + } + + if ( active ) + { + SetActivePage( active ); + } + + LoadControlSettings( "resource/KeyBoardEditorSheet.res" ); +} + +void CKeyBoardEditorSheet::SetKeybindingsSaveFile( char const *filename, char const *pathID /*= 0*/ ) +{ + Assert( filename ); + m_bSaveToExternalFile = true; + m_SaveFileName = filename; + if ( pathID != NULL ) + { + m_SaveFilePathID = pathID; + } + else + { + m_SaveFilePathID = UTL_INVAL_SYMBOL; + } +} + +void CKeyBoardEditorSheet::OnSaveChanges() +{ + int c = GetNumPages(); + for ( int i = 0 ; i < c; ++i ) + { + CKeyBoardEditorPage *page = static_cast< CKeyBoardEditorPage * >( GetPage( i ) ); + page->OnSaveChanges(); + } + + if ( m_bSaveToExternalFile ) + { + m_hPanel->SaveKeyBindingsToFile( m_Handle, m_SaveFileName.String(), m_SaveFilePathID.IsValid() ? m_SaveFilePathID.String() : NULL ); + } + else + { + m_hPanel->SaveKeyBindings( m_Handle ); + } +} + +void CKeyBoardEditorSheet::OnRevert() +{ + int c = GetNumPages(); + for ( int i = 0 ; i < c; ++i ) + { + CKeyBoardEditorPage *page = static_cast< CKeyBoardEditorPage * >( GetPage( i ) ); + page->OnRevert(); + } +} + +void CKeyBoardEditorSheet::OnUseDefaults() +{ + int c = GetNumPages(); + for ( int i = 0 ; i < c; ++i ) + { + CKeyBoardEditorPage *page = static_cast< CKeyBoardEditorPage * >( GetPage( i ) ); + page->OnUseDefaults(); + } +} + +CKeyBoardEditorDialog::CKeyBoardEditorDialog( Panel *parent, Panel *panelToEdit, KeyBindingContextHandle_t handle ) + : BaseClass( parent, "KeyBoardEditorDialog" ) +{ + m_pSave = new Button( this, "Save", "#KBEditorSave", this, "save" ); + m_pCancel = new Button( this, "Cancel", "#KBEditorCancel", this, "cancel" ); + m_pRevert = new Button( this, "Revert", "#KBEditorRevert", this, "revert" ); + m_pUseDefaults = new Button( this, "Defaults", "#KBEditorUseDefaults", this, "defaults" ); + + m_pKBEditor = new CKeyBoardEditorSheet( this, panelToEdit, handle ); + + LoadControlSettings( "resource/KeyBoardEditorDialog.res" ); + + SetTitle( "#KBEditorTitle", true ); + + SetSmallCaption( true ); + SetMinimumSize( 640, 200 ); + SetMinimizeButtonVisible( false ); + SetMaximizeButtonVisible( false ); + SetSizeable( true ); + SetMoveable( true ); + SetMenuButtonVisible( false ); + + SetVisible( true ); + + MoveToCenterOfScreen(); +} + +void CKeyBoardEditorDialog::OnCommand( char const *cmd ) +{ + if ( !Q_stricmp( cmd, "save" ) ) + { + m_pKBEditor->OnSaveChanges(); + MarkForDeletion(); + } + else if ( !Q_stricmp( cmd, "cancel" ) || + !Q_stricmp( cmd, "Close" ) ) + { + m_pKBEditor->OnRevert(); + MarkForDeletion(); + } + else if ( !Q_stricmp( cmd, "revert" ) ) + { + m_pKBEditor->OnRevert(); + } + else if ( !Q_stricmp( cmd, "defaults" ) ) + { + m_pKBEditor->OnUseDefaults(); + } + else + { + BaseClass::OnCommand( cmd ); + } +} + +void CKeyBoardEditorDialog::SetKeybindingsSaveFile( char const *filename, char const *pathID /*= 0*/ ) +{ + m_pKBEditor->SetKeybindingsSaveFile( filename, pathID ); +} diff --git a/vgui2/vgui_controls/KeyRepeat.cpp b/vgui2/vgui_controls/KeyRepeat.cpp new file mode 100644 index 0000000..1c5fcd0 --- /dev/null +++ b/vgui2/vgui_controls/KeyRepeat.cpp @@ -0,0 +1,98 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/pch_vgui_controls.h" +#include <vgui_controls/KeyRepeat.h> + +// memdbgon must be the last include file in a .cpp file +#include "tier0/memdbgon.h" + +using namespace vgui; + +vgui::KeyCode g_iCodesForAliases[FM_NUM_KEYREPEAT_ALIASES] = +{ + KEY_XBUTTON_UP, + KEY_XBUTTON_DOWN, + KEY_XBUTTON_LEFT, + KEY_XBUTTON_RIGHT, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CKeyRepeatHandler::KeyDown( vgui::KeyCode code ) +{ + int iIndex = GetIndexForCode(code); + if ( iIndex == -1 ) + return; + + if ( m_bAliasDown[ iIndex ] ) + return; + + Reset(); + m_bAliasDown[ iIndex ] = true; + m_flNextKeyRepeat = system()->GetCurrentTime() + 0.4; + m_bHaveKeyDown = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CKeyRepeatHandler::KeyUp( vgui::KeyCode code ) +{ + int iIndex = GetIndexForCode(code); + if ( iIndex == -1 ) + return; + + m_bAliasDown[ GetIndexForCode(code) ] = false; + + m_bHaveKeyDown = false; + for ( int i = 0; i < FM_NUM_KEYREPEAT_ALIASES; i++ ) + { + if ( m_bAliasDown[i] ) + { + m_bHaveKeyDown = true; + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +vgui::KeyCode CKeyRepeatHandler::KeyRepeated( void ) +{ + if ( IsPC() ) + return BUTTON_CODE_NONE; + + if ( !m_bHaveKeyDown ) + return BUTTON_CODE_NONE; + + if ( m_flNextKeyRepeat < system()->GetCurrentTime() ) + { + for ( int i = 0; i < FM_NUM_KEYREPEAT_ALIASES; i++ ) + { + if ( m_bAliasDown[i] ) + { + m_flNextKeyRepeat = system()->GetCurrentTime() + m_flRepeatTimes[i]; + return g_iCodesForAliases[i]; + } + } + } + + return BUTTON_CODE_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CKeyRepeatHandler::SetKeyRepeatTime( vgui::KeyCode code, float flRepeat ) +{ + int iIndex = GetIndexForCode(code); + Assert( iIndex != -1 ); + m_flRepeatTimes[ iIndex ] = flRepeat; +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/Label.cpp b/vgui2/vgui_controls/Label.cpp new file mode 100644 index 0000000..2178f39 --- /dev/null +++ b/vgui2/vgui_controls/Label.cpp @@ -0,0 +1,1392 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <ctype.h> + +#include <vgui/IInput.h> +#include <vgui/ILocalize.h> +#include <vgui/IPanel.h> +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <KeyValues.h> + +#include <vgui_controls/Label.h> +#include <vgui_controls/Image.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/Controls.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( Label, Label ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Label::Label(Panel *parent, const char *panelName, const char *text) : BaseClass(parent, panelName) +{ + Init(); + + _textImage = new TextImage(text); + _textImage->SetColor(Color(0, 0, 0, 0)); + SetText(text); + _textImageIndex = AddImage(_textImage, 0); + + REGISTER_COLOR_AS_OVERRIDABLE( _disabledFgColor2, "disabledfgcolor2_override" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Label::Label(Panel *parent, const char *panelName, const wchar_t *wszText) : BaseClass(parent, panelName) +{ + Init(); + + _textImage = new TextImage(wszText); + _textImage->SetColor(Color(0, 0, 0, 0)); + SetText(wszText); + _textImageIndex = AddImage(_textImage, 0); + + REGISTER_COLOR_AS_OVERRIDABLE( _disabledFgColor2, "disabledfgcolor2_override" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Label::~Label() +{ + delete _textImage; + delete [] _associateName; + delete [] _fontOverrideName; +} + +//----------------------------------------------------------------------------- +// Purpose: Construct the label +//----------------------------------------------------------------------------- +void Label::Init() +{ + _contentAlignment = a_west; + _textColorState = CS_NORMAL; + _textInset[0] = 0; + _textInset[1] = 0; + _hotkey = 0; + _associate = NULL; + _associateName = NULL; + _fontOverrideName = NULL; + m_bWrap = false; + m_bCenterWrap = false; + m_bAutoWideToContents = false; + m_bUseProportionalInsets = false; + m_bAutoWideDirty = false; + +// SetPaintBackgroundEnabled(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Set whether the text is displayed bright or dull +//----------------------------------------------------------------------------- +void Label::SetTextColorState(EColorState state) +{ + if (_textColorState != state) + { + _textColorState = state; + InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the full size of the contained content +//----------------------------------------------------------------------------- +void Label::GetContentSize(int &wide, int &tall) +{ + if( GetFont() == INVALID_FONT ) // we haven't loaded our font yet, so load it now + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + if ( pScheme ) + { + SetFont( pScheme->GetFont( "Default", IsProportional() ) ); + } + } + + + int tx0, ty0, tx1, ty1; + ComputeAlignment(tx0, ty0, tx1, ty1); + + // the +8 is padding to the content size + // the code which uses it should really set that itself; + // however a lot of existing code relies on this + wide = (tx1 - tx0) + _textInset[0]; + + // get the size of the text image and remove it + int iWide, iTall; + _textImage->GetSize(iWide, iTall); + wide -= iWide; + // get the full, untruncated (no elipsis) size of the text image. + _textImage->GetContentSize(iWide, iTall); + wide += iWide; + + // addin the image offsets as well + for (int i=0; i < _imageDar.Size(); i++) + wide += _imageDar[i].offset; + + tall = max((ty1 - ty0) + _textInset[1], iTall); +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate the keyboard key that is a hotkey +//----------------------------------------------------------------------------- +wchar_t Label::CalculateHotkey(const char *text) +{ + for (const char *ch = text; *ch != 0; ch++) + { + if (*ch == '&') + { + // get the next character + ch++; + + if (*ch == '&') + { + // just an & + continue; + } + else if (*ch == 0) + { + break; + } + else if (V_isalnum(*ch)) + { + // found the hotkey + return (wchar_t)tolower(*ch); + } + } + } + + return '\0'; +} + +wchar_t Label::CalculateHotkey(const wchar_t *text) +{ + if( text ) + { + for (const wchar_t *ch = text; *ch != 0; ch++) + { + if (*ch == '&') + { + // get the next character + ch++; + + if (*ch == '&') + { + // just an & + continue; + } + else if (*ch == 0) + { + break; + } + else if (iswalnum(*ch)) + { + // found the hotkey + return (wchar_t)towlower(*ch); + } + } + } + } + + return '\0'; +} + +//----------------------------------------------------------------------------- +// Purpose: Check if this label has a hotkey that is the key passed in. +//----------------------------------------------------------------------------- +Panel *Label::HasHotkey(wchar_t key) +{ +#ifdef VGUI_HOTKEYS_ENABLED + if ( iswalnum( key ) ) + key = towlower( key ); + + if (_hotkey == key) + return this; +#endif + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the hotkey for this label +//----------------------------------------------------------------------------- +void Label::SetHotkey(wchar_t ch) +{ + _hotkey = ch; +} + +wchar_t Label::GetHotKey() +{ + return _hotkey; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle a hotkey by passing on focus to associate +//----------------------------------------------------------------------------- +void Label::OnHotkeyPressed() +{ + // we can't accept focus, but if we are associated to a control give it to that + if (_associate.Get() && !IsBuildModeActive()) + { + _associate->RequestFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Redirect mouse pressed to giving focus to associate +//----------------------------------------------------------------------------- +void Label::OnMousePressed(MouseCode code) +{ + if (_associate.Get() && !IsBuildModeActive()) + { + _associate->RequestFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the text in the label +//----------------------------------------------------------------------------- +void Label::GetText(char *textOut, int bufferLen) +{ + _textImage->GetText(textOut, bufferLen); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the text in the label +//----------------------------------------------------------------------------- +void Label::GetText(wchar_t *textOut, int bufLenInBytes) +{ + _textImage->GetText(textOut, bufLenInBytes); +} + +//----------------------------------------------------------------------------- +// Purpose: Take the string and looks it up in the localization file +// to convert it to unicode +// Setting the text will not set the size of the label. +// Set the size explicitly or use setToContent() +//----------------------------------------------------------------------------- +void Label::SetText(const char *text) +{ + // if set to null, just make blank + if (!text) + { + text = ""; + } + + // let the text image do the translation itself + _textImage->SetText(text); + + if( text[0] == '#' ) + { + SetHotkey(CalculateHotkey(g_pVGuiLocalize->Find(text))); + } + else + { + SetHotkey(CalculateHotkey(text)); + } + + m_bAutoWideDirty = m_bAutoWideToContents; + + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set unicode text directly +//----------------------------------------------------------------------------- +void Label::SetText(const wchar_t *unicodeString, bool bClearUnlocalizedSymbol) +{ + m_bAutoWideDirty = m_bAutoWideToContents; + + if ( unicodeString && _textImage->GetUText() && !Q_wcscmp(unicodeString,_textImage->GetUText()) ) + return; + + _textImage->SetText(unicodeString, bClearUnlocalizedSymbol); + +//!! need to calculate hotkey from translated string + SetHotkey(CalculateHotkey(unicodeString)); + + InvalidateLayout(); // possible that the textimage needs to expand + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: updates localized text +//----------------------------------------------------------------------------- +void Label::OnDialogVariablesChanged(KeyValues *dialogVariables ) +{ + StringIndex_t index = _textImage->GetUnlocalizedTextSymbol(); + if (index != INVALID_LOCALIZE_STRING_INDEX) + { + // reconstruct the string from the variables + wchar_t buf[1024]; + g_pVGuiLocalize->ConstructString(buf, sizeof(buf), index, dialogVariables); + SetText(buf); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Additional offset at the Start of the text (from whichever side it is aligned) +//----------------------------------------------------------------------------- +void Label::SetTextInset(int xInset, int yInset) +{ + _textInset[0] = xInset; + _textInset[1] = yInset; + + int wide, tall; + GetSize( wide, tall); + _textImage->SetDrawWidth(wide - _textInset[0]); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Label::GetTextInset(int *xInset, int *yInset ) +{ + if ( xInset ) + { + *xInset = _textInset[0]; + } + if ( yInset ) + { + *yInset = _textInset[1]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the enabled state +//----------------------------------------------------------------------------- +void Label::SetEnabled(bool state) +{ + Panel::SetEnabled(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Calculates where in the panel the content resides +// Input : &tx0 - [out] position of the content +// &ty0 - +// &tx1 - +// &ty1 - +// Note: horizontal alignment is west if the image dar has +// more than one image in it, this is because we use image sizes +// to determine layout in classes for example, Menu. +//----------------------------------------------------------------------------- +void Label::ComputeAlignment(int &tx0, int &ty0, int &tx1, int &ty1) +{ + int wide, tall; + GetPaintSize(wide, tall); + int tWide,tTall; + + // text bounding box + tx0 = 0; + ty0 = 0; + + // loop through all the images and calculate the complete bounds + int maxX = 0, maxY = 0; + + int actualXAlignment = _contentAlignment; + for (int i = 0; i < _imageDar.Count(); i++) + { + TImageInfo &imageInfo = _imageDar[i]; + IImage *image = imageInfo.image; + if (!image) + continue; // skip over null images + + // add up the bounds + int iWide, iTall; + image->GetSize(iWide, iTall); + if (iWide > wide) // if the image is larger than the label just do a west alignment + actualXAlignment = Label::a_west; + + // get the max height + maxY = max(maxY, iTall); + maxX += iWide; + + // add the offset to x + maxX += imageInfo.offset; + } + + tWide = maxX; + tTall = maxY; + + // x align text + switch (actualXAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + tx0 = 0; + break; + } + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + tx0 = (wide - tWide) / 2; + break; + } + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + tx0 = wide - tWide; + break; + } + } + + // y align text + switch (_contentAlignment) + { + //top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + ty0 = 0; + break; + } + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + ty0 = (tall - tTall) / 2; + break; + } + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + ty0 = tall - tTall; + break; + } + } + + tx1 = tx0 + tWide; + ty1 = ty0 + tTall; +} + +//----------------------------------------------------------------------------- +// Purpose: overridden main drawing function for the panel +//----------------------------------------------------------------------------- +void Label::Paint() +{ + int tx0, ty0, tx1, ty1; + ComputeAlignment(tx0, ty0, tx1, ty1); + + // calculate who our associate is if we haven't already + if (_associateName) + { + SetAssociatedControl(FindSiblingByName(_associateName)); + delete [] _associateName; + _associateName = NULL; + } + + int labelWide, labelTall; + GetSize(labelWide, labelTall); + int x = tx0, y = _textInset[1] + ty0; + int imageYPos = 0; // a place to save the y offset for when we draw the disable version of the image + + // draw the set of images + for (int i = 0; i < _imageDar.Count(); i++) + { + TImageInfo &imageInfo = _imageDar[i]; + IImage *image = imageInfo.image; + if (!image) + continue; // skip over null images + + // add the offset to x + x += imageInfo.offset; + + // if this is the text image then add its inset to the left or from the right + if (i == _textImageIndex) + { + switch ( _contentAlignment ) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x += _textInset[0]; + break; + } + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x -= _textInset[0]; + break; + } + } + } + + // see if the image is in a fixed position + if (imageInfo.xpos >= 0) + { + x = imageInfo.xpos; + } + + // draw + imageYPos = y; + image->SetPos(x, y); + + // fix up y for center-aligned text + if (_contentAlignment == Label::a_west || _contentAlignment == Label::a_center || _contentAlignment == Label::a_east) + { + int iw, it; + image->GetSize(iw, it); + if (it < (ty1 - ty0)) + { + imageYPos = ((ty1 - ty0) - it) / 2 + y; + image->SetPos(x, ((ty1 - ty0) - it) / 2 + y); + } + } + + // don't resize the image unless its too big + if (imageInfo.width >= 0) + { + int w, t; + image->GetSize(w, t); + if (w > imageInfo.width) + { + image->SetSize(imageInfo.width, t); + } + } + + // if it's the basic text image then draw specially + if (image == _textImage) + { + if (IsEnabled()) + { + if (_associate.Get() && ipanel()->HasParent(input()->GetFocus(), _associate->GetVPanel())) + { + _textImage->SetColor(_associateColor); + } + else + { + _textImage->SetColor(GetFgColor()); + } + + _textImage->Paint(); + } + else + { + // draw disabled version, with embossed look + // offset image + _textImage->SetPos(x + 1, imageYPos + 1); + _textImage->SetColor(_disabledFgColor1); + _textImage->Paint(); + + surface()->DrawFlushText(); + + // overlayed image + _textImage->SetPos(x, imageYPos); + _textImage->SetColor(_disabledFgColor2); + _textImage->Paint(); + } + } + else + { + image->Paint(); + } + + // add the image size to x + int wide, tall; + image->GetSize(wide, tall); + x += wide; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Helper function, draws a simple line with dashing parameters +//----------------------------------------------------------------------------- +void Label::DrawDashedLine(int x0, int y0, int x1, int y1, int dashLen, int gapLen) +{ + // work out which way the line goes + if ((x1 - x0) > (y1 - y0)) + { + // x direction line + while (1) + { + if (x0 + dashLen > x1) + { + // draw partial + surface()->DrawFilledRect(x0, y0, x1, y1); + } + else + { + surface()->DrawFilledRect(x0, y0, x0 + dashLen, y1); + } + + x0 += dashLen; + + if (x0 + gapLen > x1) + break; + + x0 += gapLen; + } + } + else + { + // y direction + while (1) + { + if (y0 + dashLen > y1) + { + // draw partial + surface()->DrawFilledRect(x0, y0, x1, y1); + } + else + { + surface()->DrawFilledRect(x0, y0, x1, y0 + dashLen); + } + + y0 += dashLen; + + if (y0 + gapLen > y1) + break; + + y0 += gapLen; + } + } +} + +void Label::SetContentAlignment(Alignment alignment) +{ + _contentAlignment=alignment; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Size the width of the label to its contents - only works from in ApplySchemeSettings or PerformLayout() +//----------------------------------------------------------------------------- +void Label::SizeToContents() +{ + int wide, tall; + GetContentSize(wide, tall); + + SetSize(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the font the text is drawn in +//----------------------------------------------------------------------------- +void Label::SetFont(HFont font) +{ + _textImage->SetFont(font); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Resond to resizing of the panel +//----------------------------------------------------------------------------- +void Label::OnSizeChanged(int wide, int tall) +{ + InvalidateLayout(); + Panel::OnSizeChanged(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the font the textImage is drawn in. +//----------------------------------------------------------------------------- +HFont Label::GetFont() +{ + return _textImage->GetFont(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the foreground color of the Label +//----------------------------------------------------------------------------- +void Label::SetFgColor(Color color) +{ + if (!(GetFgColor() == color)) + { + BaseClass::SetFgColor(color); + _textImage->SetColor(color); + Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the foreground color of the Label +//----------------------------------------------------------------------------- +Color Label::GetFgColor() +{ + Color clr = Panel::GetFgColor(); + return clr; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the foreground color 1 color of the Label +//----------------------------------------------------------------------------- +void Label::SetDisabledFgColor1(Color color) +{ + _disabledFgColor1 = color; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Label::SetDisabledFgColor2(Color color) +{ + _disabledFgColor2 = color; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color Label::GetDisabledFgColor1() +{ + return _disabledFgColor1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color Label::GetDisabledFgColor2() +{ + return _disabledFgColor2; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TextImage *Label::GetTextImage() +{ + return _textImage; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Label::RequestInfo(KeyValues *outputData) +{ + if (!stricmp(outputData->GetName(), "GetText")) + { + wchar_t wbuf[256]; + _textImage->GetText(wbuf, 255); + outputData->SetWString("text", wbuf); + return true; + } + + return Panel::RequestInfo(outputData); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the text from the message +//----------------------------------------------------------------------------- +void Label::OnSetText(KeyValues *params) +{ + KeyValues *pkvText = params->FindKey("text", false); + if (!pkvText) + return; + + if (pkvText->GetDataType() == KeyValues::TYPE_STRING) + { + SetText(pkvText->GetString()); + } + else if (pkvText->GetDataType() == KeyValues::TYPE_WSTRING) + { + SetText(pkvText->GetWString()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add an image to the list +// returns the index the image was placed in +//----------------------------------------------------------------------------- +int Label::AddImage(IImage *image, int offset) +{ + int newImage = _imageDar.AddToTail(); + _imageDar[newImage].image = image; + _imageDar[newImage].offset = (short)offset; + _imageDar[newImage].xpos = -1; + _imageDar[newImage].width = -1; + InvalidateLayout(); + return newImage; +} + +//----------------------------------------------------------------------------- +// Purpose: removes all images from the list +// user is responsible for the memory +//----------------------------------------------------------------------------- +void Label::ClearImages() +{ + _imageDar.RemoveAll(); + _textImageIndex = -1; +} + +void Label::ResetToSimpleTextImage() +{ + ClearImages(); + _textImageIndex = AddImage(_textImage, 0); +} + + +//----------------------------------------------------------------------------- +// Purpose: Multiple image handling +// Images are drawn from left to right across the label, ordered by index +// By default there is a TextImage in position 0 +// Set the contents of an IImage in the IImage array. +//----------------------------------------------------------------------------- +void Label::SetImageAtIndex(int index, IImage *image, int offset) +{ + EnsureImageCapacity(index); +// Assert( image ); + + if ( _imageDar[index].image != image || _imageDar[index].offset != offset) + { + _imageDar[index].image = image; + _imageDar[index].offset = (short)offset; + InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get an IImage in the IImage array. +//----------------------------------------------------------------------------- +IImage *Label::GetImageAtIndex(int index) +{ + if ( _imageDar.IsValidIndex( index ) ) + return _imageDar[index].image; + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of images in the array. +//----------------------------------------------------------------------------- +int Label::GetImageCount() +{ + return _imageDar.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move where the default text image is within the image array +// (it starts in position 0) +// Input : newIndex - +// Output : int - the index the default text image was previously in +//----------------------------------------------------------------------------- +int Label::SetTextImageIndex(int newIndex) +{ + if (newIndex == _textImageIndex) + return _textImageIndex; + + EnsureImageCapacity(newIndex); + + int oldIndex = _textImageIndex; + if ( _textImageIndex >= 0 ) + { + _imageDar[_textImageIndex].image = NULL; + } + if (newIndex > -1) + { + _imageDar[newIndex].image = _textImage; + } + _textImageIndex = newIndex; + return oldIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Ensure that the maxIndex will be a valid index +//----------------------------------------------------------------------------- +void Label::EnsureImageCapacity(int maxIndex) +{ + while (_imageDar.Size() <= maxIndex) + { + AddImage(NULL, 0); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the offset in pixels before the image +//----------------------------------------------------------------------------- +void Label::SetImagePreOffset(int index, int preOffset) +{ + if (_imageDar.IsValidIndex(index) && _imageDar[index].offset != preOffset) + { + _imageDar[index].offset = (short)preOffset; + InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: fixes the layout bounds of the image within the label +//----------------------------------------------------------------------------- +void Label::SetImageBounds(int index, int x, int width) +{ + _imageDar[index].xpos = (short)x; + _imageDar[index].width = (short)width; +} + +//----------------------------------------------------------------------------- +// Purpose: Labels can be associated with controls, and alter behaviour based on the associates behaviour +// If the associate is disabled, so are we +// If the associate has focus, we may alter how we draw +// If we get a hotkey press or focus message, we forward the focus to the associate +//----------------------------------------------------------------------------- +void Label::SetAssociatedControl(Panel *control) +{ + if (control != this) + { + _associate = control; + } + else + { + // don't let the associate ever be set to be ourself + _associate = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called after a panel requests focus to fix up the whole chain +//----------------------------------------------------------------------------- +void Label::OnRequestFocus(VPANEL subFocus, VPANEL defaultPanel) +{ + if (_associate.Get() && subFocus == GetVPanel() && !IsBuildModeActive()) + { + // we've received focus; pass the focus onto the associate instead + _associate->RequestFocus(); + } + else + { + BaseClass::OnRequestFocus(subFocus, defaultPanel); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets custom settings from the scheme file +//----------------------------------------------------------------------------- +void Label::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + if (_fontOverrideName) + { + // use the custom specified font since we have one set + SetFont(pScheme->GetFont(_fontOverrideName, IsProportional())); + } + if ( GetFont() == INVALID_FONT ) + { + SetFont( pScheme->GetFont( "Default", IsProportional() ) ); + } + + if ( m_bWrap || m_bCenterWrap ) + { + //tell it how big it is + int wide, tall; + Panel::GetSize(wide, tall); + wide -= _textInset[0]; // take inset into account + _textImage->SetSize(wide, tall); + + _textImage->RecalculateNewLinePositions(); + } + else + { + // if you don't set the size of the image, many, many buttons will break - we might want to look into fixing this all over the place later + int wide, tall; + _textImage->GetContentSize(wide, tall); + _textImage->SetSize(wide, tall); + } + + if ( m_bAutoWideToContents ) + { + m_bAutoWideDirty = true; + HandleAutoSizing(); + } + + // clear out any the images, since they will have been invalidated + for (int i = 0; i < _imageDar.Count(); i++) + { + IImage *image = _imageDar[i].image; + if (!image) + continue; // skip over null images + + if (i == _textImageIndex) + continue; + + _imageDar[i].image = NULL; + } + + SetDisabledFgColor1(GetSchemeColor("Label.DisabledFgColor1", pScheme)); + SetDisabledFgColor2(GetSchemeColor("Label.DisabledFgColor2", pScheme)); + SetBgColor(GetSchemeColor("Label.BgColor", pScheme)); + + switch (_textColorState) + { + case CS_DULL: + SetFgColor(GetSchemeColor("Label.TextDullColor", pScheme)); + break; + case CS_BRIGHT: + SetFgColor(GetSchemeColor("Label.TextBrightColor", pScheme)); + break; + case CS_NORMAL: + default: + SetFgColor(GetSchemeColor("Label.TextColor", pScheme)); + break; + } + + _associateColor = GetSchemeColor("Label.SelectedTextColor", pScheme); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Label::GetSettings( KeyValues *outResourceData ) +{ + // panel settings + Panel::GetSettings( outResourceData ); + + // label settings + char buf[256]; + _textImage->GetUnlocalizedText( buf, 255 ); + if (!strnicmp(buf, "#var_", 5)) + { + // strip off the variable prepender on save + outResourceData->SetString( "labelText", buf + 5 ); + } + else + { + outResourceData->SetString( "labelText", buf ); + } + + const char *alignmentString = ""; + switch ( _contentAlignment ) + { + case a_northwest: alignmentString = "north-west"; break; + case a_north: alignmentString = "north"; break; + case a_northeast: alignmentString = "north-east"; break; + case a_center: alignmentString = "center"; break; + case a_east: alignmentString = "east"; break; + case a_southwest: alignmentString = "south-west"; break; + case a_south: alignmentString = "south"; break; + case a_southeast: alignmentString = "south-east"; break; + case a_west: + default: alignmentString = "west"; break; + } + + outResourceData->SetString( "textAlignment", alignmentString ); + + if (_associate) + { + outResourceData->SetString("associate", _associate->GetName()); + } + + outResourceData->SetInt("dulltext", (int)(_textColorState == CS_DULL)); + outResourceData->SetInt("brighttext", (int)(_textColorState == CS_BRIGHT)); + + if (_fontOverrideName) + { + outResourceData->SetString("font", _fontOverrideName); + } + + outResourceData->SetInt("wrap", ( m_bWrap ? 1 : 0 )); + outResourceData->SetInt("centerwrap", ( m_bCenterWrap ? 1 : 0 )); + + if ( m_bUseProportionalInsets ) + { + outResourceData->SetInt("textinsetx", scheme()->GetProportionalNormalizedValueEx( GetScheme(), _textInset[0] ) ); + outResourceData->SetInt("textinsety", _textInset[1]); + } + else + { + outResourceData->SetInt("textinsetx", _textInset[0]); + outResourceData->SetInt("textinsety", _textInset[1]); + } + outResourceData->SetInt("auto_wide_tocontents", ( m_bAutoWideToContents ? 1 : 0 )); + outResourceData->SetInt("use_proportional_insets", ( m_bUseProportionalInsets ? 1 : 0 )); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Label::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + // label settings + const char *labelText = inResourceData->GetString( "labelText", NULL ); + if ( labelText ) + { + if (labelText[0] == '%' && labelText[strlen(labelText) - 1] == '%') + { + // it's a variable, set it to be a special variable localized string + wchar_t unicodeVar[256]; + g_pVGuiLocalize->ConvertANSIToUnicode(labelText, unicodeVar, sizeof(unicodeVar)); + + char var[256]; + _snprintf(var, sizeof(var), "#var_%s", labelText); + g_pVGuiLocalize->AddString(var + 1, unicodeVar, ""); + SetText(var); + } + else + { + SetText(labelText); + } + } + + // text alignment + const char *alignmentString = inResourceData->GetString( "textAlignment", "" ); + int align = -1; + + if ( !stricmp(alignmentString, "north-west") ) + { + align = a_northwest; + } + else if ( !stricmp(alignmentString, "north") ) + { + align = a_north; + } + else if ( !stricmp(alignmentString, "north-east") ) + { + align = a_northeast; + } + else if ( !stricmp(alignmentString, "west") ) + { + align = a_west; + } + else if ( !stricmp(alignmentString, "center") ) + { + align = a_center; + } + else if ( !stricmp(alignmentString, "east") ) + { + align = a_east; + } + else if ( !stricmp(alignmentString, "south-west") ) + { + align = a_southwest; + } + else if ( !stricmp(alignmentString, "south") ) + { + align = a_south; + } + else if ( !stricmp(alignmentString, "south-east") ) + { + align = a_southeast; + } + + if ( align != -1 ) + { + SetContentAlignment( (Alignment)align ); + } + + // the control we are to be associated with may not have been created yet, + // so keep a pointer to it's name and calculate it when we can + const char *associateName = inResourceData->GetString("associate", ""); + if (associateName[0] != 0) + { + int len = Q_strlen(associateName) + 1; + _associateName = new char[ len ]; + Q_strncpy( _associateName, associateName, len ); + } + + if (inResourceData->GetInt("dulltext", 0) == 1) + { + SetTextColorState(CS_DULL); + } + else if (inResourceData->GetInt("brighttext", 0) == 1) + { + SetTextColorState(CS_BRIGHT); + } + else + { + SetTextColorState(CS_NORMAL); + } + + // font settings + const char *overrideFont = inResourceData->GetString("font", ""); + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + + if (*overrideFont) + { + delete [] _fontOverrideName; + int len = Q_strlen(overrideFont) + 1; + _fontOverrideName = new char[ len ]; + Q_strncpy(_fontOverrideName, overrideFont, len ); + SetFont(pScheme->GetFont(_fontOverrideName, IsProportional())); + } + else if (_fontOverrideName) + { + delete [] _fontOverrideName; + _fontOverrideName = NULL; + SetFont(pScheme->GetFont("Default", IsProportional())); + } + + bool bWrapText = inResourceData->GetInt("centerwrap", 0) > 0; + SetCenterWrap( bWrapText ); + + m_bAutoWideToContents = inResourceData->GetInt("auto_wide_tocontents", 0) > 0; + + bWrapText = inResourceData->GetInt("wrap", 0) > 0; + SetWrap( bWrapText ); + + int inset_x = inResourceData->GetInt("textinsetx", _textInset[0]); + int inset_y = inResourceData->GetInt("textinsety", _textInset[1]); + // Had to play it safe and add a new key for backwards compatibility + m_bUseProportionalInsets = inResourceData->GetInt("use_proportional_insets", 0) > 0; + if ( m_bUseProportionalInsets ) + { + inset_x = scheme()->GetProportionalScaledValueEx( GetScheme(), inset_x ); + } + + SetTextInset( inset_x, inset_y ); + + bool bAllCaps = inResourceData->GetInt("allcaps", 0) > 0; + SetAllCaps( bAllCaps ); + + InvalidateLayout(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a description of the label string +//----------------------------------------------------------------------------- +const char *Label::GetDescription( void ) +{ + static char buf[1024]; + Q_snprintf(buf, sizeof(buf), "%s, string labelText, string associate, alignment textAlignment, int wrap, int dulltext, int brighttext, string font", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: If a label has images in _imageDar, the size +// must take those into account as well as the textImage part +// Textimage part will shrink ONLY if there is not enough room. +//----------------------------------------------------------------------------- +void Label::PerformLayout() +{ + int wide, tall; + Panel::GetSize(wide, tall); + wide -= _textInset[0]; // take inset into account + + // if we just have a textImage, this is trivial. + if (_imageDar.Count() == 1 && _textImageIndex == 0) + { + if ( m_bWrap || m_bCenterWrap ) + { + int twide, ttall; + _textImage->GetContentSize(twide, ttall); + _textImage->SetSize(wide, ttall); + } + else + { + int twide, ttall; + _textImage->GetContentSize(twide, ttall); + + // tell the textImage how much space we have to draw in + if ( wide < twide) + _textImage->SetSize(wide, ttall); + else + _textImage->SetSize(twide, ttall); + } + + HandleAutoSizing(); + + HandleAutoSizing(); + + return; + } + + // assume the images in the dar cannot be resized, and if + // the images + the textimage are too wide we shring the textimage part + if (_textImageIndex < 0) + return; + + // get the size of the images + int widthOfImages = 0; + for (int i = 0; i < _imageDar.Count(); i++) + { + TImageInfo &imageInfo = _imageDar[i]; + IImage *image = imageInfo.image; + if (!image) + continue; // skip over null images + + if (i == _textImageIndex) + continue; + + // add up the bounds + int iWide, iTall; + image->GetSize(iWide, iTall); + widthOfImages += iWide; + } + + // so this is how much room we have to draw the textimage part + int spaceAvail = wide - widthOfImages; + + // if we have no space at all just leave everything as is. + if (spaceAvail < 0) + return; + + int twide, ttall; + _textImage->GetSize (twide, ttall); + // tell the textImage how much space we have to draw in + _textImage->SetSize(spaceAvail, ttall); + + HandleAutoSizing(); +} + +void Label::SetWrap( bool bWrap ) +{ + m_bWrap = bWrap; + _textImage->SetWrap( m_bWrap ); + + InvalidateLayout(); +} + +void Label::SetCenterWrap( bool bWrap ) +{ + m_bCenterWrap = bWrap; + _textImage->SetCenterWrap( m_bCenterWrap ); + + InvalidateLayout(); +} + +void Label::SetAllCaps( bool bAllCaps ) +{ + m_bAllCaps = bAllCaps; + _textImage->SetAllCaps( m_bAllCaps ); + + InvalidateLayout(); +} + +void Label::HandleAutoSizing( void ) +{ + if ( m_bAutoWideDirty ) + { + m_bAutoWideDirty = false; + + // Only change our width to match our content + int wide, tall; + GetContentSize(wide, tall); + SetSize(wide, GetTall()); + } +} + + + diff --git a/vgui2/vgui_controls/ListPanel.cpp b/vgui2/vgui_controls/ListPanel.cpp new file mode 100644 index 0000000..4c1c1c5 --- /dev/null +++ b/vgui2/vgui_controls/ListPanel.cpp @@ -0,0 +1,3287 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> + +#define PROTECTED_THINGS_DISABLE + +#include <vgui/Cursor.h> +#include <vgui/IInput.h> +#include <vgui/ILocalize.h> +#include <vgui/IPanel.h> +#include <vgui/IScheme.h> +#include <vgui/ISystem.h> +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> + +#include <vgui_controls/Button.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/ImageList.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/ListPanel.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/Menu.h> +#include <vgui_controls/Tooltip.h> + +// memdbgon must be the last include file in a .cpp file +#include "tier0/memdbgon.h" + +using namespace vgui; + +enum +{ + WINDOW_BORDER_WIDTH=2 // the width of the window's border +}; + + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef clamp +#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) ) +#endif + +//----------------------------------------------------------------------------- +// +// Button at the top of columns used to re-sort +// +//----------------------------------------------------------------------------- +class ColumnButton : public Button +{ +public: + ColumnButton(vgui::Panel *parent, const char *name, const char *text); + + // Inherited from Button + virtual void ApplySchemeSettings(IScheme *pScheme); + virtual void OnMousePressed(MouseCode code); + + void OpenColumnChoiceMenu(); +}; + +ColumnButton::ColumnButton(vgui::Panel *parent, const char *name, const char *text) : Button(parent, name, text) +{ + SetBlockDragChaining( true ); +} + +void ColumnButton::ApplySchemeSettings(IScheme *pScheme) +{ + Button::ApplySchemeSettings(pScheme); + + SetContentAlignment(Label::a_west); + SetFont(pScheme->GetFont("DefaultSmall", IsProportional())); +} + +// Don't request focus. +// This will keep items in the listpanel selected. +void ColumnButton::OnMousePressed(MouseCode code) +{ + if (!IsEnabled()) + return; + + if (code == MOUSE_RIGHT) + { + OpenColumnChoiceMenu(); + return; + } + + if (!IsMouseClickEnabled(code)) + return; + + if (IsUseCaptureMouseEnabled()) + { + { + SetSelected(true); + Repaint(); + } + + // lock mouse input to going to this button + input()->SetMouseCapture(GetVPanel()); + } +} + +void ColumnButton::OpenColumnChoiceMenu() +{ + CallParentFunction(new KeyValues("OpenColumnChoiceMenu")); +} + + +//----------------------------------------------------------------------------- +// +// Purpose: Handles resizing of columns +// +//----------------------------------------------------------------------------- +class Dragger : public Panel +{ +public: + Dragger(int column); + + // Inherited from Panel + virtual void OnMousePressed(MouseCode code); + virtual void OnMouseDoublePressed(MouseCode code); + virtual void OnMouseReleased(MouseCode code); + virtual void OnCursorMoved(int x, int y); + virtual void SetMovable(bool state); + +private: + int m_iDragger; + bool m_bDragging; + int m_iDragPos; + bool m_bMovable; // whether this dragger is movable using mouse or not +}; + + +Dragger::Dragger(int column) +{ + m_iDragger = column; + SetPaintBackgroundEnabled(false); + SetPaintEnabled(false); + SetPaintBorderEnabled(false); + SetCursor(dc_sizewe); + m_bDragging = false; + m_bMovable = true; // movable by default + m_iDragPos = 0; + SetBlockDragChaining( true ); +} + +void Dragger::OnMousePressed(MouseCode code) +{ + if (m_bMovable) + { + input()->SetMouseCapture(GetVPanel()); + + int x, y; + input()->GetCursorPos(x, y); + m_iDragPos = x; + m_bDragging = true; + } +} + +void Dragger::OnMouseDoublePressed(MouseCode code) +{ + if (m_bMovable) + { + // resize the column to the size of it's contents + PostMessage(GetParent(), new KeyValues("ResizeColumnToContents", "column", m_iDragger)); + } +} + +void Dragger::OnMouseReleased(MouseCode code) +{ + if (m_bMovable) + { + input()->SetMouseCapture(NULL); + m_bDragging = false; + } +} + +void Dragger::OnCursorMoved(int x, int y) +{ + if (m_bDragging) + { + input()->GetCursorPos(x, y); + KeyValues *msg = new KeyValues("ColumnResized"); + msg->SetInt("column", m_iDragger); + msg->SetInt("delta", x - m_iDragPos); + m_iDragPos = x; + if (GetVParent()) + { + ivgui()->PostMessage(GetVParent(), msg, GetVPanel()); + } + } +} + +void Dragger::SetMovable(bool state) +{ + m_bMovable = state; + // disable cursor change if the dragger is not movable + if( IsVisible() ) + { + if (state) + { + // if its not movable we stick with the default arrow + // if parent windows Start getting fancy cursors we should probably retrive a parent + // cursor and set it to that + SetCursor(dc_sizewe); + } + else + { + SetCursor(dc_arrow); + } + } +} + + + +namespace vgui +{ +// optimized for sorting +class FastSortListPanelItem : public ListPanelItem +{ +public: + // index into accessing item to sort + CUtlVector<int> m_SortedTreeIndexes; + + // visibility flag (for quick hide/filter) + bool visible; + + // precalculated sort orders + int primarySortIndexValue; + int secondarySortIndexValue; +}; +} + +static ListPanel *s_pCurrentSortingListPanel = NULL; +static const char *s_pCurrentSortingColumn = NULL; +static bool s_currentSortingColumnTypeIsText = false; + +static SortFunc *s_pSortFunc = NULL; +static bool s_bSortAscending = true; +static SortFunc *s_pSortFuncSecondary = NULL; +static bool s_bSortAscendingSecondary = true; + + +//----------------------------------------------------------------------------- +// Purpose: Basic sort function, for use in qsort +//----------------------------------------------------------------------------- +static int __cdecl AscendingSortFunc(const void *elem1, const void *elem2) +{ + int itemID1 = *((int *) elem1); + int itemID2 = *((int *) elem2); + + // convert the item index into the ListPanelItem pointers + vgui::ListPanelItem *p1, *p2; + p1 = s_pCurrentSortingListPanel->GetItemData(itemID1); + p2 = s_pCurrentSortingListPanel->GetItemData(itemID2); + + int result = s_pSortFunc( s_pCurrentSortingListPanel, *p1, *p2 ); + if (result == 0) + { + // use the secondary sort functino + result = s_pSortFuncSecondary( s_pCurrentSortingListPanel, *p1, *p2 ); + + if (!s_bSortAscendingSecondary) + { + result = -result; + } + + if (result == 0) + { + // sort by the pointers to make sure we get consistent results + if (p1 > p2) + { + result = 1; + } + else + { + result = -1; + } + } + } + else + { + // flip result if not doing an ascending sort + if (!s_bSortAscending) + { + result = -result; + } + } + + return result; +} + + +//----------------------------------------------------------------------------- +// Purpose: Default column sorting function, puts things in alpabetical order +// If images are the same returns 1, else 0 +//----------------------------------------------------------------------------- +static int __cdecl DefaultSortFunc( + ListPanel *pPanel, + const ListPanelItem &item1, + const ListPanelItem &item2 ) +{ + const vgui::ListPanelItem *p1 = &item1; + const vgui::ListPanelItem *p2 = &item2; + + if ( !p1 || !p2 ) // No meaningful comparison + { + return 0; + } + + const char *col = s_pCurrentSortingColumn; + if (s_currentSortingColumnTypeIsText) // textImage column + { + if (p1->kv->FindKey(col, true)->GetDataType() == KeyValues::TYPE_INT) + { + // compare ints + int s1 = p1->kv->GetInt(col, 0); + int s2 = p2->kv->GetInt(col, 0); + + if (s1 < s2) + { + return -1; + } + else if (s1 > s2) + { + return 1; + } + return 0; + } + else + { + // compare as string + const char *s1 = p1->kv->GetString(col, ""); + const char *s2 = p2->kv->GetString(col, ""); + + return Q_stricmp(s1, s2); + } + } + else // its an imagePanel column + { + const ImagePanel *s1 = (const ImagePanel *)p1->kv->GetPtr(col, NULL); + const ImagePanel *s2 = (const ImagePanel *)p2->kv->GetPtr(col, NULL); + + if (s1 < s2) + { + return -1; + } + else if (s1 > s2) + { + return 1; + } + return 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Sorts items by comparing precalculated list values +//----------------------------------------------------------------------------- +static int __cdecl FastSortFunc( + ListPanel *pPanel, + const ListPanelItem &item1, + const ListPanelItem &item2 ) +{ + const vgui::FastSortListPanelItem *p1 = (vgui::FastSortListPanelItem *)&item1; + const vgui::FastSortListPanelItem *p2 = (vgui::FastSortListPanelItem *)&item2; + + Assert(p1 && p2); + + // compare the precalculated indices + if (p1->primarySortIndexValue < p2->primarySortIndexValue) + { + return 1; + } + else if (p1->primarySortIndexValue > p2->primarySortIndexValue) + { + return -1; + + } + + // they're equal, compare the secondary indices + if (p1->secondarySortIndexValue < p2->secondarySortIndexValue) + { + return 1; + } + else if (p1->secondarySortIndexValue > p2->secondarySortIndexValue) + { + return -1; + + } + + // still equal; just compare the pointers (so we get deterministic results) + return (p1 < p2) ? 1 : -1; +} + +static int s_iDuplicateIndex = 1; + +//----------------------------------------------------------------------------- +// Purpose: sorting function used in the column index redblack tree +//----------------------------------------------------------------------------- +bool ListPanel::RBTreeLessFunc(vgui::ListPanel::IndexItem_t &item1, vgui::ListPanel::IndexItem_t &item2) +{ + int result = s_pSortFunc( s_pCurrentSortingListPanel, *item1.dataItem, *item2.dataItem); + if (result == 0) + { + // they're the same value, set their duplicate index to reflect that + if (item1.duplicateIndex) + { + item2.duplicateIndex = item1.duplicateIndex; + } + else if (item2.duplicateIndex) + { + item1.duplicateIndex = item2.duplicateIndex; + } + else + { + item1.duplicateIndex = item2.duplicateIndex = s_iDuplicateIndex++; + } + } + return (result > 0); +} + + +DECLARE_BUILD_FACTORY( ListPanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ListPanel::ListPanel(Panel *parent, const char *panelName) : BaseClass(parent, panelName) +{ + m_bIgnoreDoubleClick = false; + m_bMultiselectEnabled = true; + m_iEditModeItemID = 0; + m_iEditModeColumn = 0; + + m_iHeaderHeight = 20; + m_iRowHeight = 20; + m_bCanSelectIndividualCells = false; + m_iSelectedColumn = -1; + m_bAllowUserAddDeleteColumns = false; + + m_hbar = new ScrollBar(this, "HorizScrollBar", false); + m_hbar->AddActionSignalTarget(this); + m_hbar->SetVisible(false); + m_vbar = new ScrollBar(this, "VertScrollBar", true); + m_vbar->SetVisible(false); + m_vbar->AddActionSignalTarget(this); + + m_pLabel = new Label(this, NULL, ""); + m_pLabel->SetVisible(false); + m_pLabel->SetPaintBackgroundEnabled(false); + m_pLabel->SetContentAlignment(Label::a_west); + + m_pTextImage = new TextImage( "" ); + m_pImagePanel = new ImagePanel(NULL, "ListImage"); + m_pImagePanel->SetAutoDelete(false); + + m_iSortColumn = -1; + m_iSortColumnSecondary = -1; + m_bSortAscending = true; + m_bSortAscendingSecondary = true; + + m_lastBarWidth = 0; + m_iColumnDraggerMoved = -1; + m_bNeedsSort = false; + m_LastItemSelected = -1; + + m_pImageList = NULL; + m_bDeleteImageListWhenDone = false; + m_pEmptyListText = new TextImage(""); + + m_nUserConfigFileVersion = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ListPanel::~ListPanel() +{ + // free data from table + RemoveAll(); + + // free column headers + unsigned char i; + for ( i = m_ColumnsData.Head(); i != m_ColumnsData.InvalidIndex(); i= m_ColumnsData.Next( i ) ) + { + m_ColumnsData[i].m_pHeader->MarkForDeletion(); + m_ColumnsData[i].m_pResizer->MarkForDeletion(); + } + m_ColumnsData.RemoveAll(); + + delete m_pTextImage; + delete m_pImagePanel; + delete m_vbar; + + if ( m_bDeleteImageListWhenDone ) + { + delete m_pImageList; + } + + delete m_pEmptyListText; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetImageList(ImageList *imageList, bool deleteImageListWhenDone) +{ + // get rid of existing list image if there's one and we're supposed to get rid of it + if ( m_pImageList && m_bDeleteImageListWhenDone ) + { + delete m_pImageList; + } + + m_bDeleteImageListWhenDone = deleteImageListWhenDone; + m_pImageList = imageList; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetColumnHeaderHeight( int height ) +{ + m_iHeaderHeight = height; +} + +//----------------------------------------------------------------------------- +// Purpose: adds a column header. +// this->FindChildByName(columnHeaderName) can be used to retrieve a pointer to a header panel by name +// +// if minWidth and maxWidth are BOTH NOTRESIZABLE or RESIZABLE +// the min and max size will be calculated automatically for you with that attribute +// columns are resizable by default +// if min and max size are specified column is resizable +// +// A small note on passing numbers for minWidth and maxWidth, +// If the initial window size is larger than the sum of the original widths of the columns, +// you can wind up with the columns "snapping" to size after the first window focus +// This is because the dxPerBar being calculated in PerformLayout() +// is making resizable bounded headers exceed thier maxWidths at the Start. +// Solution is to either put in support for redistributing the extra dx being truncated and +// therefore added to the last column on window opening, which is what causes the snapping. +// OR to +// ensure the difference between the starting sum of widths is not too much smaller/bigger +// than the starting window size so the starting dx doesn't cause snapping to occur. +// The easiest thing is to simply set it so your column widths add up to the starting size of the window on opening. +// +// Another note: Always give bounds for the last column you add or make it not resizable. +// +// Columns can have text headers or images for headers (e.g. password icon) +//----------------------------------------------------------------------------- +void ListPanel::AddColumnHeader(int index, const char *columnName, const char *columnText, int width, int columnFlags) +{ + if (columnFlags & COLUMN_FIXEDSIZE && !(columnFlags & COLUMN_RESIZEWITHWINDOW)) + { + // for fixed size columns, set the min & max widths to be the same as the initial width + AddColumnHeader( index, columnName, columnText, width, width, width, columnFlags); + } + else + { + AddColumnHeader( index, columnName, columnText, width, 20, 10000, columnFlags); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a new column +//----------------------------------------------------------------------------- +void ListPanel::AddColumnHeader(int index, const char *columnName, const char *columnText, int width, int minWidth, int maxWidth, int columnFlags) +{ + Assert (minWidth <= width); + Assert (maxWidth >= width); + + // get our permanent index + unsigned char columnDataIndex = m_ColumnsData.AddToTail(); + + // put this index on the tail, so all item's m_SortedTreeIndexes have a consistent mapping + m_ColumnsHistory.AddToTail(columnDataIndex); + + // put this column in the right place visually + m_CurrentColumns.InsertBefore(index, columnDataIndex); + + // create the actual column object + column_t &column = m_ColumnsData[columnDataIndex]; + + // create the column header button + Button *pButton = SETUP_PANEL(new ColumnButton(this, columnName, columnText)); // the cell rendering mucks with the button visibility during the solvetraverse loop, + //so force applyschemesettings to make sure its run + pButton->SetSize(width, 24); + pButton->AddActionSignalTarget(this); + pButton->SetContentAlignment(Label::a_west); + pButton->SetTextInset(5, 0); + + column.m_pHeader = pButton; + column.m_iMinWidth = minWidth; + column.m_iMaxWidth = maxWidth; + column.m_bResizesWithWindow = columnFlags & COLUMN_RESIZEWITHWINDOW; + column.m_bTypeIsText = !(columnFlags & COLUMN_IMAGE); + column.m_bHidden = false; + column.m_bUnhidable = (columnFlags & COLUMN_UNHIDABLE); + column.m_nContentAlignment = Label::a_west; + + Dragger *dragger = new Dragger(index); + dragger->SetParent(this); + dragger->AddActionSignalTarget(this); + dragger->MoveToFront(); + if (minWidth == maxWidth || (columnFlags & COLUMN_FIXEDSIZE)) + { + // not resizable so disable the slider + dragger->SetMovable(false); + } + column.m_pResizer = dragger; + + // add default sort function + column.m_pSortFunc = NULL; + + // Set the SortedTree less than func to the generic RBTreeLessThanFunc + m_ColumnsData[columnDataIndex].m_SortedTree.SetLessFunc((IndexRBTree_t::LessFunc_t)RBTreeLessFunc); + + // go through all the headers and make sure their Command has the right column ID + ResetColumnHeaderCommands(); + + // create the new data index + ResortColumnRBTree(index); + + // ensure scroll bar is topmost compared to column headers + m_vbar->MoveToFront(); + + // fix up our visibility + SetColumnVisible(index, !(columnFlags & COLUMN_HIDDEN)); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Recreates a column's RB Sorted Tree +//----------------------------------------------------------------------------- +void ListPanel::ResortColumnRBTree(int col) +{ + Assert(m_CurrentColumns.IsValidIndex(col)); + + unsigned char dataColumnIndex = m_CurrentColumns[col]; + int columnHistoryIndex = m_ColumnsHistory.Find(dataColumnIndex); + column_t &column = m_ColumnsData[dataColumnIndex]; + + IndexRBTree_t &rbtree = column.m_SortedTree; + + // remove all elements - we're going to create from scratch + rbtree.RemoveAll(); + + s_pCurrentSortingListPanel = this; + s_currentSortingColumnTypeIsText = column.m_bTypeIsText; // type of data in the column + SortFunc *sortFunc = column.m_pSortFunc; + if ( !sortFunc ) + { + sortFunc = DefaultSortFunc; + } + s_pSortFunc = sortFunc; + s_bSortAscending = true; + s_pSortFuncSecondary = NULL; + + // sort all current data items for this column + FOR_EACH_LL( m_DataItems, i ) + { + IndexItem_t item; + item.dataItem = m_DataItems[i]; + item.duplicateIndex = 0; + + FastSortListPanelItem *dataItem = (FastSortListPanelItem*) m_DataItems[i]; + + // if this item doesn't already have a SortedTreeIndex for this column, + // if can only be because this is the brand new column, so add it to the SortedTreeIndexes + if (dataItem->m_SortedTreeIndexes.Count() == m_ColumnsHistory.Count() - 1 && + columnHistoryIndex == m_ColumnsHistory.Count() - 1) + { + dataItem->m_SortedTreeIndexes.AddToTail(); + } + + Assert( dataItem->m_SortedTreeIndexes.IsValidIndex(columnHistoryIndex) ); + + dataItem->m_SortedTreeIndexes[columnHistoryIndex] = rbtree.Insert(item); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the "SetSortColumn" command for each column - in case columns were added or removed +//----------------------------------------------------------------------------- +void ListPanel::ResetColumnHeaderCommands() +{ + int i; + for ( i = 0 ; i < m_CurrentColumns.Count() ; i++ ) + { + Button *pButton = m_ColumnsData[m_CurrentColumns[i]].m_pHeader; + pButton->SetCommand(new KeyValues("SetSortColumn", "column", i)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the header text for a particular column. +//----------------------------------------------------------------------------- +void ListPanel::SetColumnHeaderText(int col, const char *text) +{ + m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetText(text); +} +void ListPanel::SetColumnHeaderText(int col, wchar_t *text) +{ + m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetText(text); +} + +void ListPanel::SetColumnTextAlignment( int col, int align ) +{ + m_ColumnsData[m_CurrentColumns[col]].m_nContentAlignment = align; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the column header to have an image instead of text +//----------------------------------------------------------------------------- +void ListPanel::SetColumnHeaderImage(int column, int imageListIndex) +{ + Assert(m_pImageList); + m_ColumnsData[m_CurrentColumns[column]].m_pHeader->SetTextImageIndex(-1); + m_ColumnsData[m_CurrentColumns[column]].m_pHeader->SetImageAtIndex(0, m_pImageList->GetImage(imageListIndex), 0); +} + +//----------------------------------------------------------------------------- +// Purpose: associates a tooltip with the column header +//----------------------------------------------------------------------------- +void ListPanel::SetColumnHeaderTooltip(int column, const char *tooltipText) +{ + m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetText(tooltipText); + m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetTooltipFormatToSingleLine(); + m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetTooltipDelay(0); +} + +int ListPanel::GetNumColumnHeaders() const +{ + return m_CurrentColumns.Count(); +} + +bool ListPanel::GetColumnHeaderText( int index, char *pOut, int maxLen ) +{ + if ( index < m_CurrentColumns.Count() ) + { + m_ColumnsData[m_CurrentColumns[index]].m_pHeader->GetText( pOut, maxLen ); + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetColumnSortable(int col, bool sortable) +{ + if (sortable) + { + m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetCommand(new KeyValues("SetSortColumn", "column", col)); + } + else + { + m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetCommand((const char *)NULL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Changes the visibility of a column +//----------------------------------------------------------------------------- +void ListPanel::SetColumnVisible(int col, bool visible) +{ + column_t &column = m_ColumnsData[m_CurrentColumns[col]]; + bool bHidden = !visible; + if (column.m_bHidden == bHidden) + return; + + if (column.m_bUnhidable) + return; + + column.m_bHidden = bHidden; + if (bHidden) + { + column.m_pHeader->SetVisible(false); + column.m_pResizer->SetVisible(false); + } + else + { + column.m_pHeader->SetVisible(true); + column.m_pResizer->SetVisible(true); + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::RemoveColumn(int col) +{ + if ( !m_CurrentColumns.IsValidIndex( col ) ) + return; + + // find the appropriate column data + unsigned char columnDataIndex = m_CurrentColumns[col]; + + // remove it from the current columns + m_CurrentColumns.Remove(col); + + // zero out this entry in m_ColumnsHistory + unsigned char i; + for ( i = 0 ; i < m_ColumnsHistory.Count() ; i++ ) + { + if ( m_ColumnsHistory[i] == columnDataIndex ) + { + m_ColumnsHistory[i] = m_ColumnsData.InvalidIndex(); + break; + } + } + Assert( i != m_ColumnsHistory.Count() ); + + // delete and remove the column data + m_ColumnsData[columnDataIndex].m_SortedTree.RemoveAll(); + m_ColumnsData[columnDataIndex].m_pHeader->MarkForDeletion(); + m_ColumnsData[columnDataIndex].m_pResizer->MarkForDeletion(); + m_ColumnsData.Remove(columnDataIndex); + + ResetColumnHeaderCommands(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the index of a column by column->GetName() +//----------------------------------------------------------------------------- +int ListPanel::FindColumn(const char *columnName) +{ + for (int i = 0; i < m_CurrentColumns.Count(); i++) + { + if (!stricmp(columnName, m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetName())) + { + return i; + } + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: adds an item to the view +// data->GetName() is used to uniquely identify an item +// data sub items are matched against column header name to be used in the table +//----------------------------------------------------------------------------- +int ListPanel::AddItem( const KeyValues *item, unsigned int userData, bool bScrollToItem, bool bSortOnAdd) +{ + FastSortListPanelItem *newitem = new FastSortListPanelItem; + newitem->kv = item->MakeCopy(); + newitem->userData = userData; + newitem->m_pDragData = NULL; + newitem->m_bImage = newitem->kv->GetInt( "image" ) != 0 ? true : false; + newitem->m_nImageIndex = newitem->kv->GetInt( "image" ); + newitem->m_nImageIndexSelected = newitem->kv->GetInt( "imageSelected" ); + newitem->m_pIcon = reinterpret_cast< IImage * >( newitem->kv->GetPtr( "iconImage" ) ); + + int itemID = m_DataItems.AddToTail(newitem); + int displayRow = m_VisibleItems.AddToTail(itemID); + newitem->visible = true; + + // put the item in each column's sorted Tree Index + IndexItem(itemID); + + if ( bSortOnAdd ) + { + m_bNeedsSort = true; + } + + InvalidateLayout(); + + if ( bScrollToItem ) + { + // scroll to last item + m_vbar->SetValue(displayRow); + } + return itemID; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetUserData( int itemID, unsigned int userData ) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + m_DataItems[itemID]->userData = userData; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the first itemID with a matching userData +//----------------------------------------------------------------------------- +int ListPanel::GetItemIDFromUserData( unsigned int userData ) +{ + FOR_EACH_LL( m_DataItems, itemID ) + { + if (m_DataItems[itemID]->userData == userData) + return itemID; + } + // not found + return InvalidItemID(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListPanel::GetItemCount( void ) +{ + return m_VisibleItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: gets the item ID of an item by name (data->GetName()) +//----------------------------------------------------------------------------- +int ListPanel::GetItem(const char *itemName) +{ + FOR_EACH_LL( m_DataItems, i ) + { + if (!stricmp(m_DataItems[i]->kv->GetName(), itemName)) + { + return i; + } + } + + // failure + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: returns pointer to data the itemID holds +//----------------------------------------------------------------------------- +KeyValues *ListPanel::GetItem(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return NULL; + + return m_DataItems[itemID]->kv; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListPanel::GetItemCurrentRow(int itemID) +{ + return m_VisibleItems.Find(itemID); +} + + +//----------------------------------------------------------------------------- +// Attaches drag data to a particular item +//----------------------------------------------------------------------------- +void ListPanel::SetItemDragData( int itemID, const KeyValues *data ) +{ + ListPanelItem *pItem = m_DataItems[ itemID ]; + if ( pItem->m_pDragData ) + { + pItem->m_pDragData->deleteThis(); + } + pItem->m_pDragData = data->MakeCopy(); +} + + +//----------------------------------------------------------------------------- +// Attaches drag data to a particular item +//----------------------------------------------------------------------------- +void ListPanel::OnCreateDragData( KeyValues *msg ) +{ + int nCount = GetSelectedItemsCount(); + if ( nCount == 0 ) + return; + + for ( int i = 0; i < nCount; ++i ) + { + int nItemID = GetSelectedItem( i ); + + KeyValues *pDragData = m_DataItems[ nItemID ]->m_pDragData; + if ( pDragData ) + { + KeyValues *pDragDataCopy = pDragData->MakeCopy(); + msg->AddSubKey( pDragDataCopy ); + } + } + + // Add the keys of the last item directly into the root also + int nLastItemID = GetSelectedItem( nCount - 1 ); + KeyValues *pLastItemDrag = m_DataItems[ nLastItemID ]->m_pDragData; + if ( pLastItemDrag ) + { + pLastItemDrag->CopySubkeys( msg ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListPanel::GetItemIDFromRow(int currentRow) +{ + if (!m_VisibleItems.IsValidIndex(currentRow)) + return -1; + + return m_VisibleItems[currentRow]; +} + + +int ListPanel::FirstItem() const +{ + return m_DataItems.Head(); +} + + +int ListPanel::NextItem( int iItem ) const +{ + return m_DataItems.Next( iItem ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListPanel::InvalidItemID() const +{ + return m_DataItems.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ListPanel::IsValidItemID(int itemID) +{ + return m_DataItems.IsValidIndex(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ListPanelItem *ListPanel::GetItemData( int itemID ) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return NULL; + + return m_DataItems[ itemID ]; +} + +//----------------------------------------------------------------------------- +// Purpose: returns user data for itemID +//----------------------------------------------------------------------------- +unsigned int ListPanel::GetItemUserData(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return 0; + + return m_DataItems[itemID]->userData; +} + + +//----------------------------------------------------------------------------- +// Purpose: updates the view with any changes to the data +// Input : itemID - index to update +//----------------------------------------------------------------------------- +void ListPanel::ApplyItemChanges(int itemID) +{ + // reindex the item and then redraw + IndexItem(itemID); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds the item into the column indexes +//----------------------------------------------------------------------------- +void ListPanel::IndexItem(int itemID) +{ + FastSortListPanelItem *newitem = (FastSortListPanelItem*) m_DataItems[itemID]; + + // remove the item from the indexes and re-add + int maxCount = min(m_ColumnsHistory.Count(), newitem->m_SortedTreeIndexes.Count()); + for (int i = 0; i < maxCount; i++) + { + IndexRBTree_t &rbtree = m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree; + rbtree.RemoveAt(newitem->m_SortedTreeIndexes[i]); + } + + // make sure it's all free + newitem->m_SortedTreeIndexes.RemoveAll(); + + // reserve one index per historical column - pad it out + newitem->m_SortedTreeIndexes.AddMultipleToTail(m_ColumnsHistory.Count()); + + // set the current sorting list (since the insert will need to sort) + s_pCurrentSortingListPanel = this; + + // add the item into the RB tree for each column + for (int i = 0; i < m_ColumnsHistory.Count(); i++) + { + // skip over any removed columns + if ( m_ColumnsHistory[i] == m_ColumnsData.InvalidIndex() ) + continue; + + column_t &column = m_ColumnsData[m_ColumnsHistory[i]]; + + IndexItem_t item; + item.dataItem = newitem; + item.duplicateIndex = 0; + + IndexRBTree_t &rbtree = column.m_SortedTree; + + // setup sort state + s_pCurrentSortingListPanel = this; + s_pCurrentSortingColumn = column.m_pHeader->GetName(); // name of current column for sorting + s_currentSortingColumnTypeIsText = column.m_bTypeIsText; // type of data in the column + + SortFunc *sortFunc = column.m_pSortFunc; + if (!sortFunc) + { + sortFunc = DefaultSortFunc; + } + s_pSortFunc = sortFunc; + s_bSortAscending = true; + s_pSortFuncSecondary = NULL; + + // insert index + newitem->m_SortedTreeIndexes[i] = rbtree.Insert(item); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::RereadAllItems() +{ + //!! need to make this more efficient + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Cleans up allocations associated with a particular item +//----------------------------------------------------------------------------- +void ListPanel::CleanupItem( FastSortListPanelItem *data ) +{ + if ( data ) + { + if (data->kv) + { + data->kv->deleteThis(); + } + if (data->m_pDragData) + { + data->m_pDragData->deleteThis(); + } + delete data; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Removes an item at the specified item +//----------------------------------------------------------------------------- +void ListPanel::RemoveItem(int itemID) +{ +#ifdef _X360 + bool renavigate = false; + if(HasFocus()) + { + for(int i = 0; i < GetSelectedItemsCount(); ++i) + { + if(itemID == GetSelectedItem(i)) + { + renavigate = true; + break; + } + } + } +#endif + + FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID]; + if (!data) + return; + + // remove from column sorted indexes + int i; + for ( i = 0; i < m_ColumnsHistory.Count(); i++ ) + { + if ( m_ColumnsHistory[i] == m_ColumnsData.InvalidIndex()) + continue; + + IndexRBTree_t &rbtree = m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree; + rbtree.RemoveAt(data->m_SortedTreeIndexes[i]); + } + + // remove from selection + m_SelectedItems.FindAndRemove(itemID); + PostActionSignal( new KeyValues("ItemDeselected") ); + + // remove from visible items + m_VisibleItems.FindAndRemove(itemID); + + // remove from data + m_DataItems.Remove(itemID); + CleanupItem( data ); + InvalidateLayout(); + +#ifdef _X360 + if(renavigate) + { + NavigateTo(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: clears and deletes all the memory used by the data items +//----------------------------------------------------------------------------- +void ListPanel::RemoveAll() +{ + // remove all sort indexes + for (int i = 0; i < m_ColumnsHistory.Count(); i++) + { + m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree.RemoveAll(); + } + + FOR_EACH_LL( m_DataItems, index ) + { + FastSortListPanelItem *pItem = m_DataItems[index]; + CleanupItem( pItem ); + } + + m_DataItems.RemoveAll(); + m_VisibleItems.RemoveAll(); + ClearSelectedItems(); + + InvalidateLayout(); + +#ifdef _X360 + if(HasFocus()) + { + NavigateTo(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: obselete, use RemoveAll(); +//----------------------------------------------------------------------------- +void ListPanel::DeleteAllItems() +{ + RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::ResetScrollBar() +{ + // delete and reallocate to besure the scroll bar's + // information is correct. + delete m_vbar; + m_vbar = new ScrollBar(this, "VertScrollBar", true); + m_vbar->SetVisible(false); + m_vbar->AddActionSignalTarget(this); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the count of selected rows +//----------------------------------------------------------------------------- +int ListPanel::GetSelectedItemsCount() +{ + return m_SelectedItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the selected item by selection index +// Input : selectionIndex - valid in range [0, GetNumSelectedRows) +// Output : int - itemID +//----------------------------------------------------------------------------- +int ListPanel::GetSelectedItem(int selectionIndex) +{ + if ( m_SelectedItems.IsValidIndex(selectionIndex)) + return m_SelectedItems[selectionIndex]; + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListPanel::GetSelectedColumn() +{ + return m_iSelectedColumn; +} + + +//----------------------------------------------------------------------------- +// Purpose: Clears all selected rows +//----------------------------------------------------------------------------- +void ListPanel::ClearSelectedItems() +{ + int nPrevCount = m_SelectedItems.Count(); + m_SelectedItems.RemoveAll(); + if ( nPrevCount > 0 ) + { + PostActionSignal( new KeyValues("ItemDeselected") ); + } + m_LastItemSelected = -1; + m_iSelectedColumn = -1; +} + + +//----------------------------------------------------------------------------- +bool ListPanel::IsItemSelected( int itemID ) +{ + return m_DataItems.IsValidIndex( itemID ) && m_SelectedItems.HasElement( itemID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::AddSelectedItem( int itemID ) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + Assert( !m_SelectedItems.HasElement( itemID ) ); + + m_LastItemSelected = itemID; + m_SelectedItems.AddToTail( itemID ); + PostActionSignal( new KeyValues("ItemSelected") ); + Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetSingleSelectedItem( int itemID ) +{ + ClearSelectedItems(); + AddSelectedItem(itemID); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetSelectedCell(int itemID, int col) +{ + if ( !m_bCanSelectIndividualCells ) + { + SetSingleSelectedItem(itemID); + return; + } + + // make sure it's a valid cell + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + if ( !m_CurrentColumns.IsValidIndex(col) ) + return; + + SetSingleSelectedItem( itemID ); + m_iSelectedColumn = col; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the data held by a specific cell +//----------------------------------------------------------------------------- +void ListPanel::GetCellText(int itemID, int col, wchar_t *wbuffer, int bufferSizeInBytes) +{ + if ( !wbuffer || !bufferSizeInBytes ) + return; + + wcscpy( wbuffer, L"" ); + + KeyValues *itemData = GetItem( itemID ); + if ( !itemData ) + { + return; + } + + // Look up column header + if ( col < 0 || col >= m_CurrentColumns.Count() ) + { + return; + } + + const char *key = m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetName(); + if ( !key || !key[ 0 ] ) + { + return; + } + + char const *val = itemData->GetString( key, "" ); + if ( !val || !key[ 0 ] ) + return; + + const wchar_t *wval = NULL; + + if ( val[ 0 ] == '#' ) + { + StringIndex_t si = g_pVGuiLocalize->FindIndex( val + 1 ); + if ( si != INVALID_LOCALIZE_STRING_INDEX ) + { + wval = g_pVGuiLocalize->GetValueByIndex( si ); + } + } + + if ( !wval ) + { + wval = itemData->GetWString( key, L"" ); + } + + wcsncpy( wbuffer, wval, bufferSizeInBytes/sizeof(wchar_t) ); + wbuffer[ (bufferSizeInBytes/sizeof(wchar_t)) - 1 ] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the data held by a specific cell +//----------------------------------------------------------------------------- +IImage *ListPanel::GetCellImage(int itemID, int col) //, ImagePanel *&buffer) +{ +// if ( !buffer ) +// return; + + KeyValues *itemData = GetItem( itemID ); + if ( !itemData ) + { + return NULL; + } + + // Look up column header + if ( col < 0 || col >= m_CurrentColumns.Count() ) + { + return NULL; + } + + const char *key = m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetName(); + if ( !key || !key[ 0 ] ) + { + return NULL; + } + + if ( !m_pImageList ) + { + return NULL; + } + + int imageIndex = itemData->GetInt( key, 0 ); + if ( m_pImageList->IsValidIndex(imageIndex) ) + { + if ( imageIndex > 0 ) + { + return m_pImageList->GetImage(imageIndex); + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the panel to use to render a cell +//----------------------------------------------------------------------------- +Panel *ListPanel::GetCellRenderer(int itemID, int col) +{ + Assert( m_pTextImage ); + Assert( m_pImagePanel ); + + column_t &column = m_ColumnsData[ m_CurrentColumns[col] ]; + + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + + m_pLabel->SetContentAlignment( (Label::Alignment)column.m_nContentAlignment ); + + if ( column.m_bTypeIsText ) + { + wchar_t tempText[ 256 ]; + + // Grab cell text + GetCellText( itemID, col, tempText, 256 ); + KeyValues *item = GetItem( itemID ); + m_pTextImage->SetText(tempText); + int cw, tall; + m_pTextImage->GetContentSize(cw, tall); + + // set cell size + Panel *header = column.m_pHeader; + int wide = header->GetWide(); + m_pTextImage->SetSize( min( cw, wide - 5 ), tall); + + m_pLabel->SetTextImageIndex( 0 ); + m_pLabel->SetImageAtIndex(0, m_pTextImage, 3); + + bool selected = false; + if ( m_SelectedItems.HasElement(itemID) && ( !m_bCanSelectIndividualCells || col == m_iSelectedColumn ) ) + { + selected = true; + VPANEL focus = input()->GetFocus(); + // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected + if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))) + { + m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedBgColor", pScheme)); + // selection + } + else + { + m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedOutOfFocusBgColor", pScheme)); + } + + if ( item->IsEmpty("cellcolor") == false ) + { + m_pTextImage->SetColor( item->GetColor( "cellcolor" ) ); + } + else if ( item->GetInt("disabled", 0) == 0 ) + { + m_pTextImage->SetColor(m_SelectionFgColor); + } + else + { + m_pTextImage->SetColor(m_DisabledSelectionFgColor); + } + + m_pLabel->SetPaintBackgroundEnabled(true); + } + else + { + if ( item->IsEmpty("cellcolor") == false ) + { + m_pTextImage->SetColor( item->GetColor( "cellcolor" ) ); + } + else if ( item->GetInt("disabled", 0) == 0 ) + { + m_pTextImage->SetColor(m_LabelFgColor); + } + else + { + m_pTextImage->SetColor(m_DisabledColor); + } + m_pLabel->SetPaintBackgroundEnabled(false); + } + + FastSortListPanelItem *listItem = m_DataItems[ itemID ]; + if ( col == 0 && + listItem->m_bImage && m_pImageList ) + { + IImage *pImage = NULL; + if ( listItem->m_pIcon ) + { + pImage = listItem->m_pIcon; + } + else + { + int imageIndex = selected ? listItem->m_nImageIndexSelected : listItem->m_nImageIndex; + if ( m_pImageList->IsValidIndex(imageIndex) ) + { + pImage = m_pImageList->GetImage(imageIndex); + } + } + + if ( pImage ) + { + m_pLabel->SetTextImageIndex( 1 ); + m_pLabel->SetImageAtIndex(0, pImage, 0); + m_pLabel->SetImageAtIndex(1, m_pTextImage, 3); + } + } + + return m_pLabel; + } + else // if its an Image Panel + { + if ( m_SelectedItems.HasElement(itemID) && ( !m_bCanSelectIndividualCells || col == m_iSelectedColumn ) ) + { + VPANEL focus = input()->GetFocus(); + // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected + if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))) + { + m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedBgColor", pScheme)); + // selection + } + else + { + m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedOutOfFocusBgColor", pScheme)); + } + // selection + m_pLabel->SetPaintBackgroundEnabled(true); + } + else + { + m_pLabel->SetPaintBackgroundEnabled(false); + } + + IImage *pIImage = GetCellImage(itemID, col); + m_pLabel->SetImageAtIndex(0, pIImage, 0); + + return m_pLabel; + } +} + +//----------------------------------------------------------------------------- +// Purpose: relayouts out the panel after any internal changes +//----------------------------------------------------------------------------- +void ListPanel::PerformLayout() +{ + if ( m_CurrentColumns.Count() == 0 ) + return; + + if (m_bNeedsSort) + { + SortList(); + } + + int rowsperpage = (int) GetRowsPerPage(); + + // count the number of visible items + int visibleItemCount = m_VisibleItems.Count(); + + //!! need to make it recalculate scroll positions + m_vbar->SetVisible(true); + m_vbar->SetEnabled(false); + m_vbar->SetRangeWindow( rowsperpage ); + m_vbar->SetRange( 0, visibleItemCount); + m_vbar->SetButtonPressedScrollValue( 1 ); + + int wide, tall; + GetSize( wide, tall ); + m_vbar->SetPos(wide - (m_vbar->GetWide()+WINDOW_BORDER_WIDTH), 0); + m_vbar->SetSize(m_vbar->GetWide(), tall - 2); + m_vbar->InvalidateLayout(); + + int buttonMaxXPos = wide - (m_vbar->GetWide()+WINDOW_BORDER_WIDTH); + + int nColumns = m_CurrentColumns.Count(); + // number of bars that can be resized + int numToResize=0; + if (m_iColumnDraggerMoved != -1) // we're resizing in response to a column dragger + { + numToResize = 1; // only one column will change size, the one we dragged + } + else // we're resizing in response to a window resize + { + for (int i = 0; i < nColumns; i++) + { + if ( m_ColumnsData[m_CurrentColumns[i]].m_bResizesWithWindow // column is resizable in response to window + && !m_ColumnsData[m_CurrentColumns[i]].m_bHidden) + { + numToResize++; + } + } + } + + int dxPerBar; // zero on window first opening + + // location of the last column resizer + int oldSizeX = 0, oldSizeY = 0; + int lastColumnIndex = nColumns-1; + for (int i = nColumns-1; i >= 0; --i) + { + if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden) + { + m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetPos(oldSizeX, oldSizeY); + lastColumnIndex = i; + break; + } + } + + bool bForceShrink = false; + if ( numToResize == 0 ) + { + // make sure we've got enough to be within minwidth + int minWidth=0; + for (int i = 0; i < nColumns; i++) + { + if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden) + { + minWidth += m_ColumnsData[m_CurrentColumns[i]].m_iMinWidth; + } + } + + // if all the minimum widths cannot fit in the space given, then we will shrink ALL columns an equal amount + if (minWidth > buttonMaxXPos) + { + int dx = buttonMaxXPos - minWidth; + dxPerBar=(int)((float)dx/(float)nColumns); + bForceShrink = true; + } + else + { + dxPerBar = 0; + } + m_lastBarWidth = buttonMaxXPos; + + } + else if ( oldSizeX != 0 ) // make sure this isnt the first time we opened the window + { + int dx = buttonMaxXPos - m_lastBarWidth; // this is how much we grew or shrank. + + // see how many bars we have and now much each should grow/shrink + dxPerBar=(int)((float)dx/(float)numToResize); + m_lastBarWidth = buttonMaxXPos; + } + else // this is the first time we've opened the window, make sure all our colums fit! resize if needed + { + int startingBarWidth=0; + for (int i = 0; i < nColumns; i++) + { + if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden) + { + startingBarWidth += m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetWide(); + } + } + int dx = buttonMaxXPos - startingBarWidth; // this is how much we grew or shrank. + // see how many bars we have and now much each should grow/shrink + dxPerBar=(int)((float)dx/(float)numToResize); + m_lastBarWidth = buttonMaxXPos; + } + + // Make sure nothing is smaller than minwidth to start with or else we'll get into trouble below. + for ( int i=0; i < nColumns; i++ ) + { + column_t &column = m_ColumnsData[m_CurrentColumns[i]]; + Panel *header = column.m_pHeader; + if ( header->GetWide() < column.m_iMinWidth ) + header->SetWide( column.m_iMinWidth ); + } + + // This was a while(1) loop and we hit an infinite loop case, so now we max out the # of times it can loop. + for ( int iLoopSanityCheck=0; iLoopSanityCheck < 1000; iLoopSanityCheck++ ) + { + // try and place headers as is - before we have to force items to be minimum width + int x = -1; + int i; + for ( i = 0; i < nColumns; i++) + { + column_t &column = m_ColumnsData[m_CurrentColumns[i]]; + Panel *header = column.m_pHeader; + if (column.m_bHidden) + { + header->SetVisible(false); + continue; + } + + header->SetPos(x, 0); + header->SetVisible(true); + + // if we couldn't fit this column - then we need to force items to be minimum width + if ( x+column.m_iMinWidth >= buttonMaxXPos && !bForceShrink ) + { + break; + } + + int hWide = header->GetWide(); + + // calculate the column's width + // make it so the last column always attaches to the scroll bar + if ( i == lastColumnIndex ) + { + hWide = buttonMaxXPos-x; + } + else if (i == m_iColumnDraggerMoved ) // column resizing using dragger + { + hWide += dxPerBar; // adjust width of column + } + else if ( m_iColumnDraggerMoved == -1 ) // window is resizing + { + // either this column is allowed to resize OR we are forcing it because we're shrinking all columns + if ( column.m_bResizesWithWindow || bForceShrink ) + { + Assert ( column.m_iMinWidth <= column.m_iMaxWidth ); + hWide += dxPerBar; // adjust width of column + } + } + + // enforce column mins and max's - unless we're FORCING it to shrink + if ( hWide < column.m_iMinWidth && !bForceShrink ) + { + hWide = column.m_iMinWidth; // adjust width of column + } + else if ( hWide > column.m_iMaxWidth ) + { + hWide = column.m_iMaxWidth; + } + + header->SetSize(hWide, m_vbar->GetWide()); + x += hWide; + + // set the resizers + Panel *sizer = column.m_pResizer; + if ( i == lastColumnIndex ) + { + sizer->SetVisible(false); + } + else + { + sizer->SetVisible(true); + } + sizer->MoveToFront(); + sizer->SetPos(x - 4, 0); + sizer->SetSize(8, m_vbar->GetWide()); + } + + // we made it all the way through + if ( i == nColumns ) + break; + + // we do this AFTER trying first, to let as many columns as possible try and get to their + // desired width before we forcing the minimum width on them + + // get the total desired width of all the columns + int totalDesiredWidth = 0; + for ( i = 0 ; i < nColumns ; i++ ) + { + if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden) + { + Panel *pHeader = m_ColumnsData[m_CurrentColumns[i]].m_pHeader; + totalDesiredWidth += pHeader->GetWide(); + } + } + + // shrink from the most right column to minimum width until we can fit them all + Assert(totalDesiredWidth > buttonMaxXPos); + for ( i = nColumns-1; i >= 0 ; i--) + { + column_t &column = m_ColumnsData[m_CurrentColumns[i]]; + if (!column.m_bHidden) + { + Panel *pHeader = column.m_pHeader; + + totalDesiredWidth -= pHeader->GetWide(); + if ( totalDesiredWidth + column.m_iMinWidth <= buttonMaxXPos ) + { + int newWidth = buttonMaxXPos - totalDesiredWidth; + pHeader->SetSize( newWidth, m_vbar->GetWide() ); + break; + } + + totalDesiredWidth += column.m_iMinWidth; + pHeader->SetSize(column.m_iMinWidth, m_vbar->GetWide()); + } + } + // If we don't allow this to shrink, then as we resize, it can get stuck in an infinite loop. + dxPerBar -= 5; + if ( dxPerBar < 0 ) + dxPerBar = 0; + + if ( i == -1 ) + { + break; + } + } + + // setup edit mode + if ( m_hEditModePanel.Get() ) + { + m_iTableStartX = 0; + m_iTableStartY = m_iHeaderHeight + 1; + + int nTotalRows = m_VisibleItems.Count(); + int nRowsPerPage = GetRowsPerPage(); + + // find the first visible item to display + int nStartItem = 0; + if (nRowsPerPage <= nTotalRows) + { + nStartItem = m_vbar->GetValue(); + } + + bool bDone = false; + int drawcount = 0; + for (int i = nStartItem; i < nTotalRows && !bDone; i++) + { + int x = 0; + if (!m_VisibleItems.IsValidIndex(i)) + continue; + + int itemID = m_VisibleItems[i]; + + // iterate the columns + for (int j = 0; j < m_CurrentColumns.Count(); j++) + { + Panel *header = m_ColumnsData[m_CurrentColumns[j]].m_pHeader; + + if (!header->IsVisible()) + continue; + + wide = header->GetWide(); + + if ( itemID == m_iEditModeItemID && + j == m_iEditModeColumn ) + { + + m_hEditModePanel->SetPos( x + m_iTableStartX + 2, (drawcount * m_iRowHeight) + m_iTableStartY); + m_hEditModePanel->SetSize( wide, m_iRowHeight - 1 ); + + bDone = true; + } + + x += wide; + } + + drawcount++; + } + } + + Repaint(); + m_iColumnDraggerMoved = -1; // reset to invalid column + + m_iHeaderHeight = m_ColumnsData[0].m_pHeader ? m_ColumnsData[0].m_pHeader->GetTall() : m_iHeaderHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::OnSizeChanged(int wide, int tall) +{ + BaseClass::OnSizeChanged(wide, tall); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Renders the cells +//----------------------------------------------------------------------------- +void ListPanel::Paint() +{ + if (m_bNeedsSort) + { + SortList(); + } + + // draw selection areas if any + int wide, tall; + GetSize( wide, tall ); + + m_iTableStartX = 0; + m_iTableStartY = m_iHeaderHeight + 1; + + int nTotalRows = m_VisibleItems.Count(); + int nRowsPerPage = GetRowsPerPage(); + + // find the first visible item to display + int nStartItem = 0; + if (nRowsPerPage <= nTotalRows) + { + nStartItem = m_vbar->GetValue(); + } + + int vbarInset = m_vbar->IsVisible() ? m_vbar->GetWide() : 0; + int maxw = wide - vbarInset - 8; + +// debug timing functions +// double startTime, endTime; +// startTime = system()->GetCurrentTime(); + + // iterate through and draw each cell + bool bDone = false; + int drawcount = 0; + for (int i = nStartItem; i < nTotalRows && !bDone; i++) + { + int x = 0; + if (!m_VisibleItems.IsValidIndex(i)) + continue; + + int itemID = m_VisibleItems[i]; + + // iterate the columns + for (int j = 0; j < m_CurrentColumns.Count(); j++) + { + Panel *header = m_ColumnsData[m_CurrentColumns[j]].m_pHeader; + Panel *render = GetCellRenderer(itemID, j); + + if (!header->IsVisible()) + continue; + + int hWide = header->GetWide(); + + if (render) + { + // setup render panel + if (render->GetVParent() != GetVPanel()) + { + render->SetParent(GetVPanel()); + } + if (!render->IsVisible()) + { + render->SetVisible(true); + } + int xpos = x + m_iTableStartX + 2; + + render->SetPos( xpos, (drawcount * m_iRowHeight) + m_iTableStartY); + + int right = min( xpos + hWide, maxw ); + int usew = right - xpos; + render->SetSize( usew, m_iRowHeight - 1 ); + + // mark the panel to draw immediately (since it will probably be recycled to draw other cells) + render->Repaint(); + surface()->SolveTraverse(render->GetVPanel()); + int x0, y0, x1, y1; + render->GetClipRect(x0, y0, x1, y1); + if ((y1 - y0) < (m_iRowHeight - 3)) + { + bDone = true; + break; + } + surface()->PaintTraverse(render->GetVPanel()); + } + /* + // work in progress, optimized paint for text + else + { + // just paint it ourselves + char tempText[256]; + // Grab cell text + GetCellText(i, j, tempText, sizeof(tempText)); + surface()->DrawSetTextPos(x + m_iTableStartX + 2, (drawcount * m_iRowHeight) + m_iTableStartY); + + for (const char *pText = tempText; *pText != 0; pText++) + { + surface()->DrawUnicodeChar((wchar_t)*pText); + } + } + */ + + x += hWide; + } + + drawcount++; + } + + m_pLabel->SetVisible(false); + + // if the list is empty, draw some help text + if (m_VisibleItems.Count() < 1 && m_pEmptyListText) + { + m_pEmptyListText->SetPos(m_iTableStartX + 8, m_iTableStartY + 4); + m_pEmptyListText->SetSize(wide - 8, m_iRowHeight); + m_pEmptyListText->Paint(); + } + +// endTime = system()->GetCurrentTime(); +// ivgui()->DPrintf2("ListPanel::Paint() (%.3f sec)\n", (float)(endTime - startTime)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::PaintBackground() +{ + BaseClass::PaintBackground(); +} + + +//----------------------------------------------------------------------------- +// Handles multiselect +//----------------------------------------------------------------------------- +void ListPanel::HandleMultiSelection( int itemID, int row, int column ) +{ + // deal with 'multiple' row selection + + // convert the last item selected to a row so we can multiply select by rows NOT items + int lastSelectedRow = (m_LastItemSelected != -1) ? m_VisibleItems.Find( m_LastItemSelected ) : row; + int startRow, endRow; + if ( row < lastSelectedRow ) + { + startRow = row; + endRow = lastSelectedRow; + } + else + { + startRow = lastSelectedRow; + endRow = row; + } + + // clear the selection if neither control key was down - we are going to readd ALL selected items + // in case the user changed the 'direction' of the shift add + if ( !input()->IsKeyDown(KEY_LCONTROL) && !input()->IsKeyDown(KEY_RCONTROL) ) + { + ClearSelectedItems(); + } + + // add any items that we haven't added + for (int i = startRow; i <= endRow; i++) + { + // get the item indexes for these rows + int selectedItemID = m_VisibleItems[i]; + if ( !m_SelectedItems.HasElement(selectedItemID) ) + { + AddSelectedItem( selectedItemID ); + } + } +} + + +//----------------------------------------------------------------------------- +// Handles multiselect +//----------------------------------------------------------------------------- +void ListPanel::HandleAddSelection( int itemID, int row, int column ) +{ + // dealing with row selection + if ( m_SelectedItems.HasElement( itemID ) ) + { + // this row is already selected, remove + m_SelectedItems.FindAndRemove( itemID ); + PostActionSignal( new KeyValues("ItemDeselected") ); + m_LastItemSelected = itemID; + } + else + { + // add the row to the selection + AddSelectedItem( itemID ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::UpdateSelection( MouseCode code, int x, int y, int row, int column ) +{ + // make sure we're clicking on a real item + if ( row < 0 || row >= m_VisibleItems.Count() ) + { + ClearSelectedItems(); + return; + } + + int itemID = m_VisibleItems[ row ]; + + // if we've right-clicked on a selection, don't change the selection + if ( code == MOUSE_RIGHT && m_SelectedItems.HasElement( itemID ) ) + return; + + if ( m_bCanSelectIndividualCells ) + { + if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ) + { + // we're ctrl selecting the same cell, clear it + if ( ( m_LastItemSelected == itemID ) && ( m_iSelectedColumn == column ) && ( m_SelectedItems.Count() == 1 ) ) + { + ClearSelectedItems(); + } + else + { + SetSelectedCell( itemID, column ); + } + } + else + { + SetSelectedCell( itemID, column ); + } + return; + } + + if ( !m_bMultiselectEnabled ) + { + SetSingleSelectedItem( itemID ); + return; + } + + if ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) ) + { + // check for multi-select + HandleMultiSelection( itemID, row, column ); + } + else if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ) + { + // check for row-add select + HandleAddSelection( itemID, row, column ); + } + else + { + // no CTRL or SHIFT keys + // reset the selection Start point +// if ( ( m_LastItemSelected != itemID ) || ( m_SelectedItems.Count() > 1 ) ) + { + SetSingleSelectedItem( itemID ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::OnMousePressed( MouseCode code ) +{ + if (code == MOUSE_LEFT || code == MOUSE_RIGHT) + { + if ( m_VisibleItems.Count() > 0 ) + { + // determine where we were pressed + int x, y, row, column; + input()->GetCursorPos(x, y); + GetCellAtPos(x, y, row, column); + + UpdateSelection( code, x, y, row, column ); + } + + // get the key focus + RequestFocus(); + } + + // check for context menu open + if (code == MOUSE_RIGHT) + { + if ( m_SelectedItems.Count() > 0 ) + { + PostActionSignal( new KeyValues("OpenContextMenu", "itemID", m_SelectedItems[0] )); + } + else + { + // post it, but with the invalid row + PostActionSignal( new KeyValues("OpenContextMenu", "itemID", -1 )); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list according to the mouse wheel movement +//----------------------------------------------------------------------------- +void ListPanel::OnMouseWheeled(int delta) +{ + if (m_hEditModePanel.Get()) + { + // ignore mouse wheel in edit mode, forward right up to parent + CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); + return; + } + + int val = m_vbar->GetValue(); + val -= (delta * 3); + m_vbar->SetValue(val); +} + +//----------------------------------------------------------------------------- +// Purpose: Double-click act like the the item under the mouse was selected +// and then the enter key hit +//----------------------------------------------------------------------------- +void ListPanel::OnMouseDoublePressed(MouseCode code) +{ + if (code == MOUSE_LEFT) + { + // select the item + OnMousePressed(code); + + // post up an enter key being hit if anything was selected + if (GetSelectedItemsCount() > 0 && !m_bIgnoreDoubleClick ) + { + OnKeyCodeTyped(KEY_ENTER); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef _X360 +void ListPanel::OnKeyCodePressed(KeyCode code) +{ + int nTotalRows = m_VisibleItems.Count(); + int nTotalColumns = m_CurrentColumns.Count(); + if ( nTotalRows == 0 ) + return; + + // calculate info for adjusting scrolling + int nStartItem = GetStartItem(); + int nRowsPerPage = (int)GetRowsPerPage(); + + int nSelectedRow = 0; + if ( m_DataItems.IsValidIndex( m_LastItemSelected ) ) + { + nSelectedRow = m_VisibleItems.Find( m_LastItemSelected ); + } + int nSelectedColumn = m_iSelectedColumn; + + switch(code) + { + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + case KEY_XSTICK2_UP: + if(GetItemCount() < 1 || nSelectedRow == nStartItem) + { + ClearSelectedItems(); + BaseClass::OnKeyCodePressed(code); + return; + } + else + { + nSelectedRow -= 1; + } + break; + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + case KEY_XSTICK2_DOWN: + { + int itemId = GetSelectedItem(0); + if(itemId != -1 && GetItemCurrentRow(itemId) == (nTotalRows - 1)) + { + ClearSelectedItems(); + BaseClass::OnKeyCodePressed(code); + return; + } + else + { + nSelectedRow += 1; + } + } + break; + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case KEY_XSTICK2_LEFT: + if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) ) + { + nSelectedColumn--; + if (nSelectedColumn < 0) + { + nSelectedColumn = 0; + } + break; + } + break; + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case KEY_XSTICK2_RIGHT: + if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) ) + { + nSelectedColumn++; + if (nSelectedColumn >= nTotalColumns) + { + nSelectedColumn = nTotalColumns - 1; + } + break; + } + break; + case KEY_XBUTTON_A: + PostActionSignal( new KeyValues("ListPanelItemChosen", "itemID", m_SelectedItems[0] )); + break; + default: + BaseClass::OnKeyCodePressed(code); + break; + } + + // make sure newly selected item is a valid range + nSelectedRow = clamp(nSelectedRow, 0, nTotalRows - 1); + + int row = m_VisibleItems[ nSelectedRow ]; + + // This will select the cell if in single select mode, or the row in multiselect mode + if ( ( row != m_LastItemSelected ) || ( nSelectedColumn != m_iSelectedColumn ) || ( m_SelectedItems.Count() > 1 ) ) + { + SetSelectedCell( row, nSelectedColumn ); + } + + // move the newly selected item to within the visible range + if ( nRowsPerPage < nTotalRows ) + { + int nStartItem = m_vbar->GetValue(); + if ( nSelectedRow < nStartItem ) + { + // move the list back to match + m_vbar->SetValue( nSelectedRow ); + } + else if ( nSelectedRow >= nStartItem + nRowsPerPage ) + { + // move list forward to match + m_vbar->SetValue( nSelectedRow - nRowsPerPage + 1); + } + } + + // redraw + InvalidateLayout(); +} + +#else + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::OnKeyCodePressed(KeyCode code) +{ + if (m_hEditModePanel.Get()) + { + // ignore arrow keys in edit mode + // forward right up to parent so that tab focus change doesn't occur + CallParentFunction(new KeyValues("KeyCodePressed", "code", code)); + return; + } + + int nTotalRows = m_VisibleItems.Count(); + int nTotalColumns = m_CurrentColumns.Count(); + if ( nTotalRows == 0 ) + { + BaseClass::OnKeyCodePressed(code); + return; + } + + // calculate info for adjusting scrolling + int nStartItem = GetStartItem(); + int nRowsPerPage = (int)GetRowsPerPage(); + + int nSelectedRow = 0; + if ( m_DataItems.IsValidIndex( m_LastItemSelected ) ) + { + nSelectedRow = m_VisibleItems.Find( m_LastItemSelected ); + } + int nSelectedColumn = m_iSelectedColumn; + + switch (code) + { + case KEY_HOME: + nSelectedRow = 0; + break; + + case KEY_END: + nSelectedRow = nTotalRows - 1; + break; + + case KEY_PAGEUP: + if (nSelectedRow <= nStartItem) + { + // move up a page + nSelectedRow -= (nRowsPerPage - 1); + } + else + { + // move to the top of the current page + nSelectedRow = nStartItem; + } + break; + + case KEY_PAGEDOWN: + if (nSelectedRow >= (nStartItem + nRowsPerPage-1)) + { + // move down a page + nSelectedRow += (nRowsPerPage - 1); + } + else + { + // move to the bottom of the current page + nSelectedRow = nStartItem + (nRowsPerPage - 1); + } + break; + + case KEY_UP: + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + case KEY_XSTICK2_UP: + case STEAMCONTROLLER_DPAD_UP: + if ( nTotalRows > 0 ) + { + nSelectedRow--; + break; + } + // fall through + + case KEY_DOWN: + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + case KEY_XSTICK2_DOWN: + case STEAMCONTROLLER_DPAD_DOWN: + if ( nTotalRows > 0 ) + { + nSelectedRow++; + break; + } + // fall through + + case KEY_LEFT: + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case KEY_XSTICK2_LEFT: + if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) ) + { + nSelectedColumn--; + if (nSelectedColumn < 0) + { + nSelectedColumn = 0; + } + break; + } + // fall through + + case KEY_RIGHT: + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case KEY_XSTICK2_RIGHT: + if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) ) + { + nSelectedColumn++; + if (nSelectedColumn >= nTotalColumns) + { + nSelectedColumn = nTotalColumns - 1; + } + break; + } + // fall through + + default: + // chain back + BaseClass::OnKeyCodePressed(code); + return; + }; + + // make sure newly selected item is a valid range + nSelectedRow = clamp(nSelectedRow, 0, nTotalRows - 1); + + int row = m_VisibleItems[ nSelectedRow ]; + + // This will select the cell if in single select mode, or the row in multiselect mode + if ( ( row != m_LastItemSelected ) || ( nSelectedColumn != m_iSelectedColumn ) || ( m_SelectedItems.Count() > 1 ) ) + { + SetSelectedCell( row, nSelectedColumn ); + } + + // move the newly selected item to within the visible range + if ( nRowsPerPage < nTotalRows ) + { + nStartItem = m_vbar->GetValue(); + if ( nSelectedRow < nStartItem ) + { + // move the list back to match + m_vbar->SetValue( nSelectedRow ); + } + else if ( nSelectedRow >= nStartItem + nRowsPerPage ) + { + // move list forward to match + m_vbar->SetValue( nSelectedRow - nRowsPerPage + 1); + } + } + + // redraw + InvalidateLayout(); +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ListPanel::GetCellBounds( int row, int col, int& x, int& y, int& wide, int& tall ) +{ + if ( col < 0 || col >= m_CurrentColumns.Count() ) + return false; + + if ( row < 0 || row >= m_VisibleItems.Count() ) + return false; + + // Is row on screen? + int startitem = GetStartItem(); + if ( row < startitem || row >= ( startitem + GetRowsPerPage() ) ) + return false; + + y = m_iTableStartY; + y += ( row - startitem ) * m_iRowHeight; + tall = m_iRowHeight; + + // Compute column cell + x = m_iTableStartX; + // walk columns + int c = 0; + while ( c < col) + { + x += m_ColumnsData[m_CurrentColumns[c]].m_pHeader->GetWide(); + c++; + } + wide = m_ColumnsData[m_CurrentColumns[c]].m_pHeader->GetWide(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if any found, row and column are filled out +//----------------------------------------------------------------------------- +bool ListPanel::GetCellAtPos(int x, int y, int &row, int &col) +{ + // convert to local + ScreenToLocal(x, y); + + // move to Start of table + x -= m_iTableStartX; + y -= m_iTableStartY; + + int startitem = GetStartItem(); + // make sure it's still in valid area + if ( x >= 0 && y >= 0 ) + { + // walk the rows (for when row height is independant each row) + // NOTE: if we do height independent rows, we will need to change GetCellBounds as well + for ( row = startitem ; row < m_VisibleItems.Count() ; row++ ) + { + if ( y < ( ( ( row - startitem ) + 1 ) * m_iRowHeight ) ) + break; + } + + // walk columns + int startx = 0; + for ( col = 0 ; col < m_CurrentColumns.Count() ; col++ ) + { + startx += m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetWide(); + + if ( x < startx ) + break; + } + + // make sure we're not out of range + if ( ! ( row == m_VisibleItems.Count() || col == m_CurrentColumns.Count() ) ) + { + return true; + } + } + + // out-of-bounds + row = col = -1; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::ApplySchemeSettings(IScheme *pScheme) +{ + // force label to apply scheme settings now so we can override it + m_pLabel->InvalidateLayout(true); + + BaseClass::ApplySchemeSettings(pScheme); + + SetBgColor(GetSchemeColor("ListPanel.BgColor", pScheme)); + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); + + m_pLabel->SetBgColor(GetSchemeColor("ListPanel.BgColor", pScheme)); + + m_LabelFgColor = GetSchemeColor("ListPanel.TextColor", pScheme); + m_DisabledColor = GetSchemeColor("ListPanel.DisabledTextColor", m_LabelFgColor, pScheme); + m_SelectionFgColor = GetSchemeColor("ListPanel.SelectedTextColor", m_LabelFgColor, pScheme); + m_DisabledSelectionFgColor = GetSchemeColor("ListPanel.DisabledSelectedTextColor", m_LabelFgColor, pScheme); + + m_pEmptyListText->SetColor(GetSchemeColor("ListPanel.EmptyListInfoTextColor", pScheme)); + + SetFont( pScheme->GetFont("Default", IsProportional() ) ); + m_pEmptyListText->SetFont( pScheme->GetFont( "Default", IsProportional() ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetSortFunc(int col, SortFunc *func) +{ + Assert(col < m_CurrentColumns.Count()); + unsigned char dataColumnIndex = m_CurrentColumns[col]; + + if ( !m_ColumnsData[dataColumnIndex].m_bTypeIsText && func != NULL) + { + m_ColumnsData[dataColumnIndex].m_pHeader->SetMouseClickEnabled(MOUSE_LEFT, 1); + } + + m_ColumnsData[dataColumnIndex].m_pSortFunc = func; + + // resort this column according to new sort func + ResortColumnRBTree(col); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetSortColumn(int column) +{ + m_iSortColumn = column; +} + +int ListPanel::GetSortColumn() const +{ + return m_iSortColumn; +} + +void ListPanel::SetSortColumnEx( int iPrimarySortColumn, int iSecondarySortColumn, bool bSortAscending ) +{ + m_iSortColumn = iPrimarySortColumn; + m_iSortColumnSecondary = iSecondarySortColumn; + m_bSortAscending = bSortAscending; +} + +void ListPanel::GetSortColumnEx( int &iPrimarySortColumn, int &iSecondarySortColumn, bool &bSortAscending ) const +{ + iPrimarySortColumn = m_iSortColumn; + iSecondarySortColumn = m_iSortColumnSecondary; + bSortAscending = m_bSortAscending; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SortList( void ) +{ + m_bNeedsSort = false; + + if ( m_VisibleItems.Count() <= 1 ) + { + return; + } + + // check if the last selected item is on the screen - if so, we should try to maintain it on screen + int startItem = GetStartItem(); + int rowsperpage = (int) GetRowsPerPage(); + int screenPosition = -1; + if ( m_LastItemSelected != -1 && m_SelectedItems.Count() > 0 ) + { + int selectedItemRow = m_VisibleItems.Find(m_LastItemSelected); + if ( selectedItemRow >= startItem && selectedItemRow <= ( startItem + rowsperpage ) ) + { + screenPosition = selectedItemRow - startItem; + } + } + + // get the required sorting functions + s_pCurrentSortingListPanel = this; + + // setup globals for use in qsort + s_pSortFunc = FastSortFunc; + s_bSortAscending = m_bSortAscending; + s_pSortFuncSecondary = FastSortFunc; + s_bSortAscendingSecondary = m_bSortAscendingSecondary; + + // walk the tree and set up the current indices + if (m_CurrentColumns.IsValidIndex(m_iSortColumn)) + { + IndexRBTree_t &rbtree = m_ColumnsData[m_CurrentColumns[m_iSortColumn]].m_SortedTree; + unsigned int index = rbtree.FirstInorder(); + unsigned int lastIndex = rbtree.LastInorder(); + int prevDuplicateIndex = 0; + int sortValue = 1; + while (1) + { + FastSortListPanelItem *dataItem = (FastSortListPanelItem*) rbtree[index].dataItem; + if (dataItem->visible) + { + // only increment the sort value if we're a different token from the previous + if (!prevDuplicateIndex || prevDuplicateIndex != rbtree[index].duplicateIndex) + { + sortValue++; + } + dataItem->primarySortIndexValue = sortValue; + prevDuplicateIndex = rbtree[index].duplicateIndex; + } + + if (index == lastIndex) + break; + + index = rbtree.NextInorder(index); + } + } + + // setup secondary indices + if (m_CurrentColumns.IsValidIndex(m_iSortColumnSecondary)) + { + IndexRBTree_t &rbtree = m_ColumnsData[m_CurrentColumns[m_iSortColumnSecondary]].m_SortedTree; + unsigned int index = rbtree.FirstInorder(); + unsigned int lastIndex = rbtree.LastInorder(); + int sortValue = 1; + int prevDuplicateIndex = 0; + while (1) + { + FastSortListPanelItem *dataItem = (FastSortListPanelItem*) rbtree[index].dataItem; + if (dataItem->visible) + { + // only increment the sort value if we're a different token from the previous + if (!prevDuplicateIndex || prevDuplicateIndex != rbtree[index].duplicateIndex) + { + sortValue++; + } + dataItem->secondarySortIndexValue = sortValue; + + prevDuplicateIndex = rbtree[index].duplicateIndex; + } + + if (index == lastIndex) + break; + + index = rbtree.NextInorder(index); + } + } + + // quick sort the list + qsort(m_VisibleItems.Base(), (size_t) m_VisibleItems.Count(), (size_t) sizeof(int), AscendingSortFunc); + + if ( screenPosition != -1 ) + { + int selectedItemRow = m_VisibleItems.Find(m_LastItemSelected); + + // if we can put the last selected item in exactly the same spot, put it there, otherwise + // we need to be at the top of the list + if (selectedItemRow > screenPosition) + { + m_vbar->SetValue(selectedItemRow - screenPosition); + } + else + { + m_vbar->SetValue(0); + } + } + + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::SetFont(HFont font) +{ + Assert( font ); + if ( !font ) + return; + + m_pTextImage->SetFont(font); + m_iRowHeight = surface()->GetFontTall(font) + 2; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListPanel::OnSliderMoved() +{ + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : deltax - deltas from current position +//----------------------------------------------------------------------------- +void ListPanel::OnColumnResized(int col, int delta) +{ + m_iColumnDraggerMoved = col; + + column_t& column = m_ColumnsData[m_CurrentColumns[col]]; + + Panel *header = column.m_pHeader; + int wide, tall; + header->GetSize(wide, tall); + + + wide += delta; + + // enforce minimum sizes for the header + if ( wide < column.m_iMinWidth ) + { + wide = column.m_iMinWidth; + } + // enforce maximum sizes for the header + if ( wide > column.m_iMaxWidth ) + { + wide = column.m_iMaxWidth; + } + + // make sure we have enough space for the columns to our right + int panelWide, panelTall; + GetSize( panelWide, panelTall ); + int x, y; + header->GetPos(x, y); + int restColumnsMinWidth = 0; + int i; + for ( i = col+1 ; i < m_CurrentColumns.Count() ; i++ ) + { + column_t& nextCol = m_ColumnsData[m_CurrentColumns[i]]; + restColumnsMinWidth += nextCol.m_iMinWidth; + } + panelWide -= ( x + restColumnsMinWidth + m_vbar->GetWide() + WINDOW_BORDER_WIDTH ); + if ( wide > panelWide ) + { + wide = panelWide; + } + + header->SetSize(wide, tall); + + // the adjacent header will be moved automatically in PerformLayout() + header->InvalidateLayout(); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets which column we should sort with +//----------------------------------------------------------------------------- +void ListPanel::OnSetSortColumn(int column) +{ + // if it's the primary column already, flip the sort direction + if (m_iSortColumn == column) + { + m_bSortAscending = !m_bSortAscending; + } + else + { + // switching sort columns, keep the old one as the secondary sort + m_iSortColumnSecondary = m_iSortColumn; + m_bSortAscendingSecondary = m_bSortAscending; + } + + SetSortColumn(column); + + SortList(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets whether the item is visible or not +//----------------------------------------------------------------------------- +void ListPanel::SetItemVisible(int itemID, bool state) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID]; + if (data->visible == state) + return; + + m_bNeedsSort = true; + + data->visible = state; + if (data->visible) + { + // add back to end of list + m_VisibleItems.AddToTail(itemID); + } + else + { + // remove from selection if it is there. + if (m_SelectedItems.HasElement(itemID)) + { + m_SelectedItems.FindAndRemove(itemID); + PostActionSignal( new KeyValues("ItemDeselected") ); + } + + // remove from data + m_VisibleItems.FindAndRemove(itemID); + + InvalidateLayout(); + } +} + + +//----------------------------------------------------------------------------- +// Is the item visible? +//----------------------------------------------------------------------------- +bool ListPanel::IsItemVisible( int itemID ) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return false; + + FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID]; + return data->visible; +} + + +//----------------------------------------------------------------------------- +// Purpose: sets whether the item is disabled or not (effects item color) +//----------------------------------------------------------------------------- +void ListPanel::SetItemDisabled(int itemID, bool state) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + m_DataItems[itemID]->kv->SetInt( "disabled", state ); +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate number of rows per page +//----------------------------------------------------------------------------- +float ListPanel::GetRowsPerPage() +{ + float rowsperpage = (float)( GetTall() - m_iHeaderHeight ) / (float)m_iRowHeight; + return rowsperpage; +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate the item we should Start on +//----------------------------------------------------------------------------- +int ListPanel::GetStartItem() +{ + // if rowsperpage < total number of rows + if ( GetRowsPerPage() < (float) m_VisibleItems.Count() ) + { + return m_vbar->GetValue(); + } + return 0; // otherwise Start at top +} + +//----------------------------------------------------------------------------- +// Purpose: whether or not to select specific cells (off by default) +//----------------------------------------------------------------------------- +void ListPanel::SetSelectIndividualCells(bool state) +{ + m_bCanSelectIndividualCells = state; +} + + +//----------------------------------------------------------------------------- +// whether or not multiple cells/rows can be selected +//----------------------------------------------------------------------------- +void ListPanel::SetMultiselectEnabled( bool bState ) +{ + m_bMultiselectEnabled = bState; +} + +bool ListPanel::IsMultiselectEnabled() const +{ + return m_bMultiselectEnabled; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the text which is displayed when the list is empty +//----------------------------------------------------------------------------- +void ListPanel::SetEmptyListText(const char *text) +{ + m_pEmptyListText->SetText(text); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the text which is displayed when the list is empty +//----------------------------------------------------------------------------- +void ListPanel::SetEmptyListText(const wchar_t *text) +{ + m_pEmptyListText->SetText(text); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: opens the content menu +//----------------------------------------------------------------------------- +void ListPanel::OpenColumnChoiceMenu() +{ + if (!m_bAllowUserAddDeleteColumns) + return; + + Menu *menu = new Menu(this, "ContextMenu"); + + int x, y; + input()->GetCursorPos(x, y); + menu->SetPos(x, y); + + // add all the column choices to the menu + for ( int i = 0 ; i < m_CurrentColumns.Count() ; i++ ) + { + column_t &column = m_ColumnsData[m_CurrentColumns[i]]; + + char name[128]; + column.m_pHeader->GetText(name, sizeof(name)); + int itemID = menu->AddCheckableMenuItem(name, new KeyValues("ToggleColumnVisible", "col", m_CurrentColumns[i]), this); + menu->SetMenuItemChecked(itemID, !column.m_bHidden); + + if (column.m_bUnhidable) + { + menu->SetItemEnabled(itemID, false); + } + } + + menu->SetVisible(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Resizes a column +//----------------------------------------------------------------------------- +void ListPanel::ResizeColumnToContents(int column) +{ + // iterate all the items in the column, getting the size of each + column_t &col = m_ColumnsData[m_CurrentColumns[column]]; + + if (!col.m_bTypeIsText) + return; + + // start with the size of the column text + int wide = 0, minRequiredWidth = 0, tall = 0; + col.m_pHeader->GetContentSize( minRequiredWidth, tall ); + + // iterate every item + for (int i = 0; i < m_VisibleItems.Count(); i++) + { + if (!m_VisibleItems.IsValidIndex(i)) + continue; + + // get the cell + int itemID = m_VisibleItems[i]; + + // get the text + wchar_t tempText[ 256 ]; + GetCellText( itemID, column, tempText, 256 ); + m_pTextImage->SetText(tempText); + + m_pTextImage->GetContentSize(wide, tall); + + if ( wide > minRequiredWidth ) + { + minRequiredWidth = wide; + } + } + + // Introduce a slight buffer between columns + minRequiredWidth += 4; + + // call the resize + col.m_pHeader->GetSize(wide, tall); + OnColumnResized(column, minRequiredWidth - wide); +} + +//----------------------------------------------------------------------------- +// Purpose: Changes the visibilty of a column +//----------------------------------------------------------------------------- +void ListPanel::OnToggleColumnVisible(int col) +{ + if (!m_CurrentColumns.IsValidIndex(col)) + return; + + // toggle the state of the column + column_t &column = m_ColumnsData[m_CurrentColumns[col]]; + SetColumnVisible(col, column.m_bHidden); +} + +//----------------------------------------------------------------------------- +// Purpose: sets user settings +//----------------------------------------------------------------------------- +void ListPanel::ApplyUserConfigSettings(KeyValues *userConfig) +{ + // Check for version mismatch, then don't load settings. (Just revert to the defaults.) + int version = userConfig->GetInt( "configVersion", 1 ); + if ( version != m_nUserConfigFileVersion ) + { + return; + } + + // We save/restore m_lastBarWidth because all of the column widths are saved relative to that size. + // If we don't save it, you can run into this case: + // - Window width is 500, load sizes setup relative to a 1000-width window + // - Set window size to 1000 + // - In PerformLayout, it thinks the window has grown by 500 (since m_lastBarWidth is 500 and new window width is 1000) + // so it pushes out any COLUMN_RESIZEWITHWINDOW columns to their max extent and shrinks everything else to its min extent. + m_lastBarWidth = userConfig->GetInt( "lastBarWidth", 0 ); + + // read which columns are hidden + for ( int i = 0; i < m_CurrentColumns.Count(); i++ ) + { + char name[64]; + _snprintf(name, sizeof(name), "%d_hidden", i); + + int hidden = userConfig->GetInt(name, -1); + if (hidden == 0) + { + SetColumnVisible(i, true); + } + else if (hidden == 1) + { + SetColumnVisible(i, false); + } + + _snprintf(name, sizeof(name), "%d_width", i); + int nWidth = userConfig->GetInt( name, -1 ); + if ( nWidth >= 0 ) + { + column_t &column = m_ColumnsData[m_CurrentColumns[i]]; + column.m_pHeader->SetWide( nWidth ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns user config settings for this control +//----------------------------------------------------------------------------- +void ListPanel::GetUserConfigSettings(KeyValues *userConfig) +{ + if ( m_nUserConfigFileVersion != 1 ) + { + userConfig->SetInt( "configVersion", m_nUserConfigFileVersion ); + } + + userConfig->SetInt( "lastBarWidth", m_lastBarWidth ); + + // save which columns are hidden + for ( int i = 0 ; i < m_CurrentColumns.Count() ; i++ ) + { + column_t &column = m_ColumnsData[m_CurrentColumns[i]]; + + char name[64]; + _snprintf(name, sizeof(name), "%d_hidden", i); + userConfig->SetInt(name, column.m_bHidden ? 1 : 0); + + _snprintf(name, sizeof(name), "%d_width", i); + userConfig->SetInt( name, column.m_pHeader->GetWide() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: optimization, return true if this control has any user config settings +//----------------------------------------------------------------------------- +bool ListPanel::HasUserConfigSettings() +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void ListPanel::SetAllowUserModificationOfColumns(bool allowed) +{ + m_bAllowUserAddDeleteColumns = allowed; +} + +void ListPanel::SetIgnoreDoubleClick( bool state ) +{ + m_bIgnoreDoubleClick = state; +} + +//----------------------------------------------------------------------------- +// Purpose: set up a field for editing +//----------------------------------------------------------------------------- +void ListPanel::EnterEditMode(int itemID, int column, vgui::Panel *editPanel) +{ + m_hEditModePanel = editPanel; + m_iEditModeItemID = itemID; + m_iEditModeColumn = column; + editPanel->SetParent(this); + editPanel->SetVisible(true); + editPanel->RequestFocus(); + editPanel->MoveToFront(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: leaves editing mode +//----------------------------------------------------------------------------- +void ListPanel::LeaveEditMode() +{ + if (m_hEditModePanel.Get()) + { + m_hEditModePanel->SetVisible(false); + m_hEditModePanel->SetParent((Panel *)NULL); + m_hEditModePanel = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if we are currently in inline editing mode +//----------------------------------------------------------------------------- +bool ListPanel::IsInEditMode() +{ + return (m_hEditModePanel.Get() != NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef _X360 +void ListPanel::NavigateTo() +{ + BaseClass::NavigateTo(); + // attempt to select the first item in the list when we get focus + if(GetItemCount()) + { + SetSingleSelectedItem(FirstItem()); + } + else // if we have no items, change focus + { + if(!NavigateDown()) + { + NavigateUp(); + } + } +} +#endif diff --git a/vgui2/vgui_controls/ListViewPanel.cpp b/vgui2/vgui_controls/ListViewPanel.cpp new file mode 100644 index 0000000..59f05ab --- /dev/null +++ b/vgui2/vgui_controls/ListViewPanel.cpp @@ -0,0 +1,1082 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> +#include <ctype.h> + +#include <vgui/MouseCode.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/ILocalize.h> + +#include <vgui_controls/Controls.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/ImageList.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/ListViewPanel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +enum +{ + WINDOW_BORDER_WIDTH=2 // the width of the window's border +}; + +namespace vgui +{ +class ListViewItem : public Label +{ + DECLARE_CLASS_SIMPLE( ListViewItem, Label ); + +public: + ListViewItem(Panel *parent) : Label(parent, NULL, "") + { + m_pListViewPanel = (ListViewPanel*) parent; + m_pData = NULL; + m_bSelected = false; + SetPaintBackgroundEnabled(true); + } + + ~ListViewItem() + { + if (m_pData) + { + m_pData->deleteThis(); + m_pData = NULL; + } + } + + void SetData(const KeyValues *data) + { + if (m_pData) + { + m_pData->deleteThis(); + } + m_pData = data->MakeCopy(); + } + + virtual void OnMousePressed( MouseCode code) + { + m_pListViewPanel->OnItemMousePressed(this, code); + } + + virtual void OnMouseDoublePressed( MouseCode code) + { + // double press should only select the item + m_pListViewPanel->OnItemMouseDoublePressed(this, code); + } + + KeyValues *GetData() + { + return m_pData; + } + + void SetSelected(bool bSelected) + { + if (bSelected == m_bSelected) + return; + + m_bSelected = bSelected; + if (bSelected) + { + RequestFocus(); + } + + UpdateImage(); + InvalidateLayout(); + Repaint(); + } + + virtual void PerformLayout() + { + TextImage *textImage = GetTextImage(); + if (m_bSelected) + { + VPANEL focus = input()->GetFocus(); + // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected + if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))) + { + textImage->SetColor(m_ArmedFgColor2); + } + else + { + textImage->SetColor(m_FgColor2); + } + } + else + { + textImage->SetColor(GetFgColor()); + } + BaseClass::PerformLayout(); + Repaint(); + } + + virtual void PaintBackground() + { + int wide, tall; + GetSize(wide, tall); + + if ( m_bSelected ) + { + VPANEL focus = input()->GetFocus(); + // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected + if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))) + { + surface()->DrawSetColor(m_ArmedBgColor); + } + else + { + surface()->DrawSetColor(m_SelectionBG2Color); + } + } + else + { + surface()->DrawSetColor(GetBgColor()); + } + surface()->DrawFilledRect(0, 0, wide, tall); + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + BaseClass::ApplySchemeSettings(pScheme); + + m_ArmedFgColor2 = GetSchemeColor("ListPanel.SelectedTextColor", pScheme); + m_ArmedBgColor = GetSchemeColor("ListPanel.SelectedBgColor", pScheme); + + m_FgColor1 = GetSchemeColor("ListPanel.TextColor", pScheme); + m_FgColor2 = GetSchemeColor("ListPanel.SelectedTextColor", pScheme); + + m_BgColor = GetSchemeColor("ListPanel.BgColor", GetBgColor(), pScheme); + m_BgColor = GetSchemeColor("ListPanel.TextBgColor", m_BgColor, pScheme); + m_SelectionBG2Color = GetSchemeColor("ListPanel.SelectedOutOfFocusBgColor", pScheme); + SetBgColor(m_BgColor); + SetFgColor(m_FgColor1); + + UpdateImage(); + } + + void UpdateImage() + { + if ( m_pListViewPanel->m_pImageList ) + { + int imageIndex = 0; + if ( m_bSelected ) + { + imageIndex = m_pData->GetInt("imageSelected", 0); + } + if ( imageIndex == 0 ) + { + imageIndex = m_pData->GetInt("image", 0); + } + if ( m_pListViewPanel->m_pImageList->IsValidIndex(imageIndex) ) + { + SetImageAtIndex(0, m_pListViewPanel->m_pImageList->GetImage(imageIndex), 0); + } + else + { + // use the default + SetImageAtIndex(0, m_pListViewPanel->m_pImageList->GetImage(1), 0); + } + SizeToContents(); + InvalidateLayout(); + } + } + +private: + + Color m_FgColor1; + Color m_FgColor2; + Color m_BgColor; + Color m_ArmedFgColor2; + Color m_ArmedBgColor; + Color m_SelectionBG2Color; + + //IBorder *_keyFocusBorder; // maybe in the future when I'm the 'active' but not selected item, I'll have a border + + KeyValues *m_pData; + ListViewPanel *m_pListViewPanel; + bool m_bSelected; +}; +} + +static bool DefaultSortFunc(KeyValues *kv1, KeyValues *kv2) +{ + const char *string1 = kv1->GetString("text"); + const char *string2 = kv2->GetString("text"); + return Q_stricmp(string1, string2) < 0; +} + +DECLARE_BUILD_FACTORY( ListViewPanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ListViewPanel::ListViewPanel(Panel *parent, const char *panelName) : Panel(parent, panelName) +{ + m_iRowHeight = 20; + m_bNeedsSort = false; + m_hFont = NULL; + m_pImageList = NULL; + m_bDeleteImageListWhenDone = false; + m_pSortFunc = DefaultSortFunc; + m_ShiftStartItemID = -1; + + m_hbar = new ScrollBar(this, "HorizScrollBar", false); + m_hbar->AddActionSignalTarget(this); + m_hbar->SetVisible(false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ListViewPanel::~ListViewPanel() +{ + DeleteAllItems(); + + delete m_hbar; + + if ( m_bDeleteImageListWhenDone ) + { + delete m_pImageList; + m_pImageList = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListViewPanel::AddItem(const KeyValues *data, bool bScrollToItem, bool bSortOnAdd) +{ + ListViewItem *pNewItem = new ListViewItem(this); + pNewItem->SetData(data); + if (m_hFont) + { + pNewItem->SetFont(m_hFont); + } + int itemID = m_DataItems.AddToTail(pNewItem); + ApplyItemChanges(itemID); + m_SortedItems.AddToTail(itemID); + + if ( bSortOnAdd ) + { + m_bNeedsSort = true; + } + + InvalidateLayout(); + + if ( bScrollToItem ) + { + ScrollToItem(itemID); + } + + return itemID; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::ScrollToItem(int itemID) +{ + if (!m_hbar->IsVisible()) + { + return; + } + int val = m_hbar->GetValue(); + + int wide, tall; + GetSize( wide, tall ); + + int maxWidth = GetItemsMaxWidth(); + int maxColVisible = wide / maxWidth; + int itemsPerCol = GetItemsPerColumn(); + + int itemIndex = m_SortedItems.Find(itemID); + int desiredCol = itemIndex / itemsPerCol; + if (desiredCol < val || desiredCol >= (val + maxColVisible) ) + { + m_hbar->SetValue(desiredCol); + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListViewPanel::GetItemCount() +{ + return m_DataItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *ListViewPanel::GetItem(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return NULL; + + return m_DataItems[itemID]->GetData(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get ItemID from position in panel - valid from [0, GetItemCount) +//----------------------------------------------------------------------------- +int ListViewPanel::GetItemIDFromPos(int iPos) +{ + if ( m_SortedItems.IsValidIndex(iPos) ) + { + return m_SortedItems[iPos]; + } + else + { + return m_DataItems.InvalidIndex(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::ApplyItemChanges(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + KeyValues *kv = m_DataItems[itemID]->GetData(); + ListViewItem *pLabel = m_DataItems[itemID]; + + pLabel->SetText(kv->GetString("text")); + pLabel->SetTextImageIndex(1); + pLabel->SetImagePreOffset(1, 5); + + TextImage *pTextImage = pLabel->GetTextImage(); + pTextImage->ResizeImageToContent(); + + pLabel->UpdateImage(); + pLabel->SizeToContents(); + pLabel->InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::RemoveItem(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + m_DataItems[itemID]->MarkForDeletion(); + + // mark the keyValues for deletion + m_DataItems.Remove(itemID); + m_SortedItems.FindAndRemove(itemID); + m_SelectedItems.FindAndRemove(itemID); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::DeleteAllItems() +{ + FOR_EACH_LL( m_DataItems, index ) + { + m_DataItems[index]->MarkForDeletion(); + } + m_DataItems.RemoveAll(); + m_SortedItems.RemoveAll(); + m_SelectedItems.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListViewPanel::InvalidItemID() +{ + return m_DataItems.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ListViewPanel::IsValidItemID(int itemID) +{ + return m_DataItems.IsValidIndex(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::SetSortFunc(ListViewSortFunc_t func) +{ + if ( func ) + { + m_pSortFunc = func; + SortList(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::SortList() +{ + m_SortedItems.RemoveAll(); + + // find all the items in this section + for( int i = m_DataItems.Head(); i != m_DataItems.InvalidIndex(); i = m_DataItems.Next( i ) ) + { + // insert the items sorted + if (m_pSortFunc) + { + int insertionPoint; + for (insertionPoint = 0; insertionPoint < m_SortedItems.Count(); insertionPoint++) + { + if ( m_pSortFunc(m_DataItems[i]->GetData(), m_DataItems[m_SortedItems[insertionPoint]]->GetData() ) ) + break; + } + + if (insertionPoint == m_SortedItems.Count()) + { + m_SortedItems.AddToTail(i); + } + else + { + m_SortedItems.InsertBefore(insertionPoint, i); + } + } + else + { + // just add to the end + m_SortedItems.AddToTail(i); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::SetImageList(ImageList *imageList, bool deleteImageListWhenDone) +{ + // get rid of existing list image if there's one and we're supposed to get rid of it + if ( m_pImageList && m_bDeleteImageListWhenDone ) + { + delete m_pImageList; + m_pImageList = NULL; + } + + m_bDeleteImageListWhenDone = deleteImageListWhenDone; + m_pImageList = imageList; + + FOR_EACH_LL( m_DataItems, i ) + { + m_DataItems[i]->UpdateImage(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::SetFont(HFont font) +{ + Assert( font ); + if ( !font ) + return; + + m_hFont = font; + m_iRowHeight = surface()->GetFontTall(font) + 1; + + FOR_EACH_LL( m_DataItems, i ) + { + m_DataItems[i]->SetFont(m_hFont); + TextImage *pTextImage = m_DataItems[i]->GetTextImage(); + pTextImage->ResizeImageToContent(); + m_DataItems[i]->SizeToContents(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListViewPanel::GetSelectedItemsCount() +{ + return m_SelectedItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListViewPanel::GetSelectedItem(int selectionIndex) +{ + if ( m_SelectedItems.IsValidIndex(selectionIndex) ) + return m_SelectedItems[selectionIndex]; + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::ClearSelectedItems() +{ + int i; + for (i = 0 ; i < m_SelectedItems.Count(); i++) + { + if ( m_DataItems.IsValidIndex(m_SelectedItems[i]) ) + { + m_DataItems[m_SelectedItems[i]]->SetSelected(false); + } + } + m_SelectedItems.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::AddSelectedItem(int itemID) +{ + if ( m_SelectedItems.Find(itemID) == -1 ) + { + m_SelectedItems.AddToTail(itemID); + m_DataItems[itemID]->SetSelected(true); + m_LastSelectedItemID = itemID; + m_ShiftStartItemID = itemID; + PostActionSignal(new KeyValues("ListViewItemSelected")); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::SetSingleSelectedItem(int itemID) +{ + ClearSelectedItems(); + AddSelectedItem(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnMouseWheeled(int delta) +{ + int val = m_hbar->GetValue(); + val -= delta; + m_hbar->SetValue(val); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnSizeChanged(int wide, int tall) +{ + BaseClass::OnSizeChanged(wide, tall); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListViewPanel::GetItemsMaxWidth() +{ + int maxWidth = 0; + FOR_EACH_LL( m_DataItems, i ) + { + int labelWide, labelTall; + m_DataItems[i]->GetSize(labelWide, labelTall); + if (labelWide > maxWidth) + { + maxWidth = labelWide + 25; + } + } + return maxWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::PerformLayout() +{ + if (m_bNeedsSort) + { + SortList(); + } + + if ( m_DataItems.Count() == 0 ) + return; + + int wide, tall; + GetSize(wide, tall); + + int maxWidth = GetItemsMaxWidth(); + if (maxWidth < 24) + { + maxWidth = 24; + } + int maxColVisible = wide / maxWidth; + + m_hbar->SetVisible(false); + int itemsPerCol = GetItemsPerColumn(); + if (itemsPerCol < 1) + { + itemsPerCol = 1; + } + int cols = ( GetItemCount() + (itemsPerCol - 1) ) / itemsPerCol; + + int startItem = 0; + if ( cols > maxColVisible) + { + m_hbar->SetVisible(true); + + // recalulate # per column now that we've made the hbar visible + itemsPerCol = GetItemsPerColumn(); + cols = ( GetItemCount() + (itemsPerCol - 1) ) / (itemsPerCol > 0 ? itemsPerCol : 1 ); + + m_hbar->SetEnabled(false); + m_hbar->SetRangeWindow( maxColVisible ); + m_hbar->SetRange( 0, cols); + m_hbar->SetButtonPressedScrollValue( 1 ); + + m_hbar->SetPos(0, tall - (m_hbar->GetTall()+WINDOW_BORDER_WIDTH)); + m_hbar->SetSize(wide - (WINDOW_BORDER_WIDTH*2), m_hbar->GetTall()); + m_hbar->InvalidateLayout(); + + int val = m_hbar->GetValue(); + startItem += val*itemsPerCol; + } + else + { + m_hbar->SetVisible(false); + } + int lastItemVisible = startItem + (( maxColVisible + 1 )* itemsPerCol) - 1; + + int itemsThisCol = 0; + int x = 0; + int y = 0; + int i; + for ( i = 0 ; i < m_SortedItems.Count() ; i++ ) + { + if ( i >= startItem && i <= lastItemVisible ) + { + m_DataItems[ m_SortedItems[i] ]->SetVisible(true); + m_DataItems[ m_SortedItems[i] ]->SetPos(x, y); + itemsThisCol++; + if ( itemsThisCol == itemsPerCol ) + { + y = 0; + x += maxWidth; + itemsThisCol = 0; + } + else + { + y += m_iRowHeight; + } + } + else + { + m_DataItems[ m_SortedItems[i] ]->SetVisible(false); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::Paint() +{ + BaseClass::Paint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetBgColor(GetSchemeColor("ListPanel.BgColor", pScheme)); + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); + + m_LabelFgColor = GetSchemeColor("ListPanel.TextColor", pScheme); + m_SelectionFgColor = GetSchemeColor("ListPanel.SelectedTextColor", m_LabelFgColor, pScheme); + + m_hFont = pScheme->GetFont("Default", IsProportional()); + SetFont(m_hFont); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnMousePressed( MouseCode code) +{ + if (code == MOUSE_LEFT || code == MOUSE_RIGHT) + { + ClearSelectedItems(); + RequestFocus(); + } + // check for context menu open + if (code == MOUSE_RIGHT) + { + // post it, but with the invalid row + PostActionSignal(new KeyValues("OpenContextMenu", "itemID", -1)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnShiftSelect(int itemID) +{ + // if we dont' have a valid selected ItemID - then we just choose the first item + if ( !m_DataItems.IsValidIndex(m_ShiftStartItemID) ) + { + m_ShiftStartItemID = m_DataItems.Head(); + } + + // find out if the just pressed item is "earlier" or is the 'last selected item' + int lowerPos = -1, upperPos = -1; + int i; + for ( i = 0 ; i < m_SortedItems.Count() ; i++ ) + { + if ( m_SortedItems[i] == itemID ) + { + lowerPos = i; + upperPos = m_SortedItems.Find(m_ShiftStartItemID); + break; + } + else if ( m_SortedItems[i] == m_ShiftStartItemID ) + { + lowerPos = m_SortedItems.Find(m_ShiftStartItemID); + upperPos = i; + break; + } + } + assert(lowerPos <= upperPos); + if ( !input()->IsKeyDown(KEY_LCONTROL) && !input()->IsKeyDown(KEY_RCONTROL) ) + { + ClearSelectedItems(); + } + + for ( i = lowerPos ; i <= upperPos ; i ++) + { + // do not use AddSelectedItem because we don't want to switch the shiftStartItemID + m_DataItems[ m_SortedItems[i] ]->SetSelected(true); + m_SelectedItems.AddToTail(m_SortedItems[i]); + m_LastSelectedItemID = itemID; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnItemMousePressed(ListViewItem* pItem, MouseCode code) +{ + int itemID = m_DataItems.Find(pItem); + if (!m_DataItems.IsValidIndex(itemID)) + return; + + // check for context menu open + if (code == MOUSE_RIGHT) + { + // if this is a new item - unselect everything else + if ( m_SelectedItems.Find(itemID) == -1) + { + ClearSelectedItems(); + AddSelectedItem(itemID); + } + + PostActionSignal(new KeyValues("OpenContextMenu", "itemID", itemID)); + } + else + { + if ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) ) + { + OnShiftSelect(itemID); + } + else if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ) + { + if ( m_SelectedItems.Find(itemID) != -1) + { + m_SelectedItems.FindAndRemove(itemID); + pItem->SetSelected(false); + + // manually select these since we 'last' clicked on these items + m_ShiftStartItemID = itemID; + m_LastSelectedItemID = itemID; + m_DataItems[itemID]->RequestFocus(); + } + else + { + AddSelectedItem(itemID); + } + } + else + { + ClearSelectedItems(); + AddSelectedItem(itemID); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnMouseDoublePressed( MouseCode code) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnItemMouseDoublePressed(ListViewItem* pItem, MouseCode code) +{ + if (code == MOUSE_LEFT) + { + OnKeyCodeTyped(KEY_ENTER); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::FinishKeyPress(int itemID) +{ + if ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) ) + { + OnShiftSelect(itemID); + } + else if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) ) + { + m_DataItems[itemID]->RequestFocus(); + m_LastSelectedItemID = itemID; + } + else + { + SetSingleSelectedItem(itemID); + } + ScrollToItem(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnKeyCodeTyped( KeyCode code ) +{ + if ( m_DataItems.Count() == 0 ) + return; + + switch (code) + { + case KEY_HOME: + { + if (m_SortedItems.Count() > 0) + { + int itemID = m_SortedItems[0]; + FinishKeyPress(itemID); + } + break; + } + case KEY_END: + { + if (m_DataItems.Count() > 0) + { + int itemID = m_SortedItems[ m_SortedItems.Count() - 1 ]; + FinishKeyPress(itemID); + } + break; + } + + case KEY_UP: + { + int itemPos = m_SortedItems.Find( m_LastSelectedItemID ); + itemPos--; + if (itemPos < 0) + itemPos = 0; + + FinishKeyPress(m_SortedItems[itemPos]); + break; + } + + case KEY_DOWN: + { + int itemPos = m_SortedItems.Find( m_LastSelectedItemID ); + itemPos++; + if (itemPos >= m_DataItems.Count()) + itemPos = m_DataItems.Count() - 1; + + FinishKeyPress(m_SortedItems[itemPos]); + break; + } + + case KEY_LEFT: + { + int itemPos = m_SortedItems.Find( m_LastSelectedItemID ); + itemPos -= GetItemsPerColumn(); + if (itemPos < 0) + { + itemPos = 0; + } + FinishKeyPress(m_SortedItems[itemPos]); + break; + } + + case KEY_RIGHT: + { + int itemPos = m_SortedItems.Find( m_LastSelectedItemID ); + itemPos += GetItemsPerColumn(); + if (itemPos >= m_SortedItems.Count()) + { + itemPos = m_SortedItems.Count() - 1; + } + FinishKeyPress(m_SortedItems[itemPos]); + break; + } + + case KEY_PAGEUP: + { + int wide, tall; + GetSize(wide, tall); + + int maxWidth = GetItemsMaxWidth(); + if (maxWidth == 0) + { + maxWidth = wide; + } + int maxColVisible = wide / maxWidth; + int delta = maxColVisible * GetItemsPerColumn(); + + int itemPos = m_SortedItems.Find( m_LastSelectedItemID ); + itemPos -= delta; + if (itemPos < 0) + { + itemPos = 0; + } + + FinishKeyPress(m_SortedItems[itemPos]); + break; + } + case KEY_PAGEDOWN: + { + int wide, tall; + GetSize(wide, tall); + + int maxWidth = GetItemsMaxWidth(); + if (maxWidth == 0) + { + maxWidth = wide; + } + int maxColVisible = wide / maxWidth; + int delta = maxColVisible * GetItemsPerColumn(); + + int itemPos = m_SortedItems.Find( m_LastSelectedItemID ); + itemPos += delta; + if (itemPos >= m_SortedItems.Count()) + { + itemPos = m_SortedItems.Count() - 1; + } + + FinishKeyPress(m_SortedItems[itemPos]); + break; + } + default: + { + BaseClass::OnKeyCodeTyped(code); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnKeyTyped(wchar_t unichar) +{ + if (!iswcntrl(unichar)) + { + wchar_t uniString[2]; + uniString[0] = unichar; + uniString[1] = 0; + + char buf[2]; + g_pVGuiLocalize->ConvertUnicodeToANSI(uniString, buf, sizeof(buf)); + + int i; + int itemPos = m_SortedItems.Find(m_LastSelectedItemID); + if ( m_SortedItems.IsValidIndex(itemPos)) + { + itemPos++; + // start from the item AFTER our last selected Item and go to end + for ( i = itemPos ; i != m_SortedItems.Count(); i++) + { + KeyValues *kv = m_DataItems[ m_SortedItems[i] ]->GetData(); + const char *pszText = kv->GetString("text"); + if (!strnicmp(pszText, buf, 1)) + { + // select the next of this letter + SetSingleSelectedItem(m_SortedItems[i]); + ScrollToItem(m_SortedItems[i]); + return; + } + } + // if the after this item we couldn't fine an item with the same letter, fall through and just start from the beginning of list to the last selected item + } + + for ( i = 0 ; i < m_SortedItems.Count() ; i++ ) + { + // we've gone all the way around - break - if we had a valid index, this is one more that last selectedItem, if not it's an illegal index + if ( i == itemPos) + break; + + KeyValues *kv = m_DataItems[ m_SortedItems[i] ]->GetData(); + const char *pszText = kv->GetString("text"); + if (!strnicmp(pszText, buf, 1)) + { + SetSingleSelectedItem(m_SortedItems[i]); + ScrollToItem(m_SortedItems[i]); + return; + } + } + } + else + BaseClass::OnKeyTyped(unichar); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ListViewPanel::OnSliderMoved() +{ + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int ListViewPanel::GetItemsPerColumn() +{ + int wide, tall; + GetSize(wide, tall); + + if ( m_hbar->IsVisible() ) + { + tall -= m_hbar->GetTall(); + } + + return tall / m_iRowHeight; // should round down +} + diff --git a/vgui2/vgui_controls/Menu.cpp b/vgui2/vgui_controls/Menu.cpp new file mode 100644 index 0000000..5853635 --- /dev/null +++ b/vgui2/vgui_controls/Menu.cpp @@ -0,0 +1,2751 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/pch_vgui_controls.h" + +// memdbgon must be the last include file in a .cpp file +#include "tier0/memdbgon.h" +#define MENU_SEPARATOR_HEIGHT 3 + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: divider line in a menu +//----------------------------------------------------------------------------- +class vgui::MenuSeparator : public Panel +{ +public: + DECLARE_CLASS_SIMPLE( MenuSeparator, Panel ); + + MenuSeparator( Panel *parent, char const *panelName ) : + BaseClass( parent, panelName ) + { + SetPaintEnabled( true ); + SetPaintBackgroundEnabled( true ); + SetPaintBorderEnabled( false ); + } + + virtual void Paint() + { + int w, h; + GetSize( w, h ); + + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawFilledRect( 4, 1, w-1, 2 ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + SetFgColor( pScheme->GetColor( "Menu.SeparatorColor", Color( 142, 142, 142, 255 ) ) ); + SetBgColor( pScheme->GetColor( "Menu.BgColor", Color( 0, 0, 0, 255 ) ) ); + } +}; + +DECLARE_BUILD_FACTORY( Menu ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Menu::Menu(Panel *parent, const char *panelName) : Panel(parent, panelName) +{ + m_Alignment = Label::a_west; + m_iFixedWidth = 0; + m_iMinimumWidth = 0; + m_iNumVisibleLines = -1; // No limit + m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex(); + m_pScroller = new ScrollBar(this, "MenuScrollBar", true); + m_pScroller->SetVisible(false); + m_pScroller->AddActionSignalTarget(this); + _sizedForScrollBar = false; + SetZPos(1); + SetVisible(false); + MakePopup(false); + SetParent(parent); + _recalculateWidth = true; + m_bUseMenuManager = true; + m_iInputMode = MOUSE; + m_iCheckImageWidth = 0; + m_iActivatedItem = 0; + + m_bUseFallbackFont = false; + m_hFallbackItemFont = INVALID_FONT; + + if (IsProportional()) + { + m_iMenuItemHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_MENU_ITEM_HEIGHT ); + } + else + { + m_iMenuItemHeight = DEFAULT_MENU_ITEM_HEIGHT; + } + m_hItemFont = INVALID_FONT; + + + m_eTypeAheadMode = COMPAT_MODE; + m_szTypeAheadBuf[0] = '\0'; + m_iNumTypeAheadChars = 0; + m_fLastTypeAheadTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Menu::~Menu() +{ + delete m_pScroller; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all menu items from the menu. +//----------------------------------------------------------------------------- +void Menu::DeleteAllItems() +{ + FOR_EACH_LL( m_MenuItems, i ) + { + m_MenuItems[i]->MarkForDeletion(); + } + + m_MenuItems.RemoveAll(); + m_SortedItems.RemoveAll(); + m_VisibleSortedItems.RemoveAll(); + m_Separators.RemoveAll(); + int c = m_SeparatorPanels.Count(); + for ( int i = 0 ; i < c; ++i ) + { + m_SeparatorPanels[ i ]->MarkForDeletion(); + } + m_SeparatorPanels.RemoveAll(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( MenuItem *panel ) +{ + panel->SetParent( this ); + MEM_ALLOC_CREDIT(); + int itemID = m_MenuItems.AddToTail( panel ); + m_SortedItems.AddToTail(itemID); + InvalidateLayout(false); + _recalculateWidth = true; + panel->SetContentAlignment( m_Alignment ); + if ( INVALID_FONT != m_hItemFont ) + { + panel->SetFont( m_hItemFont ); + } + if ( m_bUseFallbackFont && INVALID_FONT != m_hFallbackItemFont ) + { + Label *l = panel; + TextImage *ti = l->GetTextImage(); + if ( ti ) + { + ti->SetUseFallbackFont( m_bUseFallbackFont, m_hFallbackItemFont ); + } + } + + if ( panel->GetHotKey() ) + { + SetTypeAheadMode( HOT_KEY_MODE ); + } + + return itemID; +} + + +//----------------------------------------------------------------------------- +// Remove a single item +//----------------------------------------------------------------------------- +void Menu::DeleteItem( int itemID ) +{ + // FIXME: This doesn't work with separator panels yet + Assert( m_SeparatorPanels.Count() == 0 ); + + m_MenuItems[itemID]->MarkForDeletion(); + m_MenuItems.Remove( itemID ); + + m_SortedItems.FindAndRemove( itemID ); + m_VisibleSortedItems.FindAndRemove( itemID ); + + InvalidateLayout(false); + _recalculateWidth = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *item - MenuItem +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *userData - any user data associated with this menu item +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItemCharCommand(MenuItem *item, const char *command, Panel *target, const KeyValues *userData) +{ + item->SetCommand(command); + item->AddActionSignalTarget( target ); + item->SetUserData(userData); + return AddMenuItem( item ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItemKeyValuesCommand( MenuItem *item, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + item->SetCommand(message); + item->AddActionSignalTarget(target); + item->SetUserData(userData); + return AddMenuItem(item); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, command, target, userData ) ; +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText ); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText ); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, message, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, Panel *target , const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, command, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, message, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, cascadeMenu ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, cascadeMenu ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + return AddCascadingMenuItem( itemText, itemText, command, target, cascadeMenu, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem( this, itemName, itemText, cascadeMenu); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem( this, itemName, wszItemText, cascadeMenu); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + return AddCascadingMenuItem(itemText, itemText, message, target, cascadeMenu, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + return AddCascadingMenuItem(itemText, itemText, target, cascadeMenu, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the values of a menu item at the specified index +// Input : index - the index of this item entry +// *message - pointer to the message to send when the item is selected +//----------------------------------------------------------------------------- +void Menu::UpdateMenuItem(int itemID, const char *itemText, KeyValues *message, const KeyValues *userData) +{ + Assert( m_MenuItems.IsValidIndex(itemID) ); + if ( m_MenuItems.IsValidIndex(itemID) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem) + { + menuItem->SetText(itemText); + menuItem->SetCommand(message); + if(userData) + { + menuItem->SetUserData(userData); + } + } + } + _recalculateWidth = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the values of a menu item at the specified index +//----------------------------------------------------------------------------- +void Menu::UpdateMenuItem(int itemID, const wchar_t *wszItemText, KeyValues *message, const KeyValues *userData) +{ + Assert( m_MenuItems.IsValidIndex(itemID) ); + if ( m_MenuItems.IsValidIndex(itemID) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem) + { + menuItem->SetText(wszItemText); + menuItem->SetCommand(message); + if(userData) + { + menuItem->SetUserData(userData); + } + } + } + _recalculateWidth = true; +} + + +//----------------------------------------------------------------------------- +// Sets the content alignment of all items in the menu +//----------------------------------------------------------------------------- +void Menu::SetContentAlignment( Label::Alignment alignment ) +{ + if ( m_Alignment != alignment ) + { + m_Alignment = alignment; + + // Change the alignment of existing menu items + int nCount = m_MenuItems.Count(); + for ( int i = 0; i < nCount; ++i ) + { + m_MenuItems[i]->SetContentAlignment( alignment ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Locks down a specific width +//----------------------------------------------------------------------------- +void Menu::SetFixedWidth(int width) +{ + // the padding makes it so the menu has the label padding on each side of the menu. + // makes the menu items look centered. + m_iFixedWidth = width; + InvalidateLayout(false); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the height of each menu item +//----------------------------------------------------------------------------- +void Menu::SetMenuItemHeight(int itemHeight) +{ + m_iMenuItemHeight = itemHeight; +} + +int Menu::GetMenuItemHeight() const +{ + return m_iMenuItemHeight; +} + +int Menu::CountVisibleItems() +{ + int count = 0; + int c = m_SortedItems.Count(); + for ( int i = 0 ; i < c; ++i ) + { + if ( m_MenuItems[ m_SortedItems[ i ] ]->IsVisible() ) + ++count; + } + return count; +} + +void Menu::ComputeWorkspaceSize( int& workWide, int& workTall ) +{ + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + int workX, workY; + surface()->GetWorkspaceBounds(workX, workY, workWide, workTall); + workTall -= 20; + workTall -= itop; + workTall -= ibottom; +} + +// Assumes relative coords in screenspace +void Menu::PositionRelativeToPanel( Panel *relative, MenuDirection_e direction, int nAdditionalYOffset /*=0*/, bool showMenu /*=false*/ ) +{ + Assert( relative ); + int rx, ry, rw, rh; + relative->GetBounds( rx, ry, rw, rh ); + relative->LocalToScreen( rx, ry ); + + if ( direction == CURSOR ) + { + // force the menu to appear where the mouse button was pressed + input()->GetCursorPos(rx, ry); + rw = rh = 0; + } + else if ( direction == ALIGN_WITH_PARENT && relative->GetVParent() ) + { + rx = 0, ry = 0; + relative->ParentLocalToScreen(rx, ry); + rx -= 1; // take border into account + ry += rh + nAdditionalYOffset; + rw = rh = 0; + } + else + { + rx = 0, ry = 0; + relative->LocalToScreen(rx, ry); + } + + int workWide, workTall; + ComputeWorkspaceSize( workWide, workTall ); + + // Final pos + int x = 0, y = 0; + + int mWide, mTall; + GetSize( mWide, mTall ); + + switch( direction ) + { + case Menu::UP: // Menu prefers to open upward + { + x = rx; + int topOfReference = ry; + y = topOfReference - mTall; + if ( y < 0 ) + { + int bottomOfReference = ry + rh + 1; + int remainingPixels = workTall - bottomOfReference; + + // Can't fit on bottom, either, move to side + if ( mTall >= remainingPixels ) + { + y = workTall - mTall; + x = rx + rw; + // Try and place it to the left of the button + if ( x + mWide > workWide ) + { + x = rx - mWide; + } + } + else + { + // Room at bottom + y = bottomOfReference; + } + } + } + break; + // Everyone else aligns downward... + default: + case Menu::LEFT: + case Menu::RIGHT: + case Menu::DOWN: + { + x = rx; + int bottomOfReference = ry + rh + 1; + y = bottomOfReference; + if ( bottomOfReference + mTall >= workTall ) + { + // See if there's run straight above + if ( mTall >= ry ) // No room, try and push menu to right or left + { + y = workTall - mTall; + x = rx + rw; + // Try and place it to the left of the button + if ( x + mWide > workWide ) + { + x = rx - mWide; + } + } + else + { + // Room at top + y = ry - mTall; + } + } + } + break; + } + + // Check left rightness + if ( x + mWide > workWide ) + { + x = workWide - mWide; + Assert( x >= 0 ); // yikes!!! + } + else if ( x < 0 ) + { + x = 0; + } + + SetPos( x, y ); + if ( showMenu ) + { + SetVisible( true ); + } +} + +int Menu::ComputeFullMenuHeightWithInsets() +{ + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + int separatorHeight = 3; + + // add up the size of all the child panels + // move the child panels to the correct place in the menu + int totalTall = itop + ibottom; + int i; + for ( i = 0 ; i < m_SortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos() + { + int itemId = m_SortedItems[i]; + + MenuItem *child = m_MenuItems[ itemId ]; + Assert( child ); + if ( !child ) + continue; + // These should all be visible at this point + if ( !child->IsVisible() ) + continue; + + totalTall += m_iMenuItemHeight; + + // Add a separator if needed... + int sepIndex = m_Separators.Find( itemId ); + if ( sepIndex != m_Separators.InvalidIndex() ) + { + totalTall += separatorHeight; + } + } + + return totalTall; +} + +//----------------------------------------------------------------------------- +// Purpose: Reformat according to the new layout +//----------------------------------------------------------------------------- +void Menu::PerformLayout() +{ + MenuItem *parent = GetParentMenuItem(); + bool cascading = parent != NULL ? true : false; + + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + int workWide, workTall; + + ComputeWorkspaceSize( workWide, workTall ); + + int fullHeightWouldRequire = ComputeFullMenuHeightWithInsets(); + + bool bNeedScrollbar = fullHeightWouldRequire >= workTall; + + int maxVisibleItems = CountVisibleItems(); + + if ( m_iNumVisibleLines > 0 && + maxVisibleItems > m_iNumVisibleLines ) + { + bNeedScrollbar = true; + maxVisibleItems = m_iNumVisibleLines; + } + + // if we have a scroll bar + if ( bNeedScrollbar ) + { + // add it to the display + AddScrollBar(); + + // This fills in m_VisibleSortedItems as needed + MakeItemsVisibleInScrollRange( m_iNumVisibleLines, min( fullHeightWouldRequire, workTall ) ); + } + else + { + RemoveScrollBar(); + // Make everything visible + m_VisibleSortedItems.RemoveAll(); + int i; + int c = m_SortedItems.Count(); + for ( i = 0; i < c; ++i ) + { + int itemID = m_SortedItems[ i ]; + MenuItem *child = m_MenuItems[ itemID ]; + if ( !child || !child->IsVisible() ) + continue; + + m_VisibleSortedItems.AddToTail( itemID ); + } + + // Hide the separators, the needed ones will be readded below + c = m_SeparatorPanels.Count(); + for ( i = 0; i < c; ++i ) + { + if ( m_SeparatorPanels[ i ] ) + { + m_SeparatorPanels[ i ]->SetVisible( false ); + } + } + } + + // get the appropriate menu border + LayoutMenuBorder(); + + int trueW = GetWide(); + if ( bNeedScrollbar ) + { + trueW -= m_pScroller->GetWide(); + } + int separatorHeight = MENU_SEPARATOR_HEIGHT; + + // add up the size of all the child panels + // move the child panels to the correct place in the menu + int menuTall = 0; + int totalTall = itop + ibottom; + int i; + for ( i = 0 ; i < m_VisibleSortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos() + { + int itemId = m_VisibleSortedItems[i]; + + MenuItem *child = m_MenuItems[ itemId ]; + Assert( child ); + if ( !child ) + continue; + // These should all be visible at this point + if ( !child->IsVisible() ) + continue; + + if ( totalTall >= workTall ) + break; + + if ( INVALID_FONT != m_hItemFont ) + { + child->SetFont( m_hItemFont ); + } + + // take into account inset + child->SetPos (0, menuTall); + child->SetTall( m_iMenuItemHeight ); // Width is set in a second pass + menuTall += m_iMenuItemHeight; + totalTall += m_iMenuItemHeight; + + // this will make all the menuitems line up in a column with space for the checks to the left. + if ( ( !child->IsCheckable() ) && ( m_iCheckImageWidth > 0 ) ) + { + // Non checkable items have to move over + child->SetTextInset( m_iCheckImageWidth, 0 ); + } + else if ( child->IsCheckable() ) + { + child->SetTextInset(0, 0); //TODO: for some reason I can't comment this out. + } + + // Add a separator if needed... + int sepIndex = m_Separators.Find( itemId ); + if ( sepIndex != m_Separators.InvalidIndex() ) + { + MenuSeparator *sep = m_SeparatorPanels[ sepIndex ]; + Assert( sep ); + sep->SetVisible( true ); + sep->SetBounds( 0, menuTall, trueW, separatorHeight ); + menuTall += separatorHeight; + totalTall += separatorHeight; + } + } + + if (!m_iFixedWidth) + { + _recalculateWidth = true; + CalculateWidth(); + } + else if (m_iFixedWidth) + { + _menuWide = m_iFixedWidth; + // fixed width menus include the scroll bar in their width. + if (_sizedForScrollBar) + { + _menuWide -= m_pScroller->GetWide(); + } + } + + SizeMenuItems(); + + int extraWidth = 0; + if (_sizedForScrollBar) + { + extraWidth = m_pScroller->GetWide(); + } + + int mwide = _menuWide + extraWidth; + if ( mwide > workWide ) + { + mwide = workWide; + } + int mtall = menuTall + itop + ibottom; + if ( mtall > workTall ) + { + // Shouldn't happen + mtall = workTall; + } + + // set the new size of the menu + SetSize( mwide, mtall ); + + // move the menu to the correct position if it is a cascading menu. + if ( cascading ) + { + // move the menu to the correct position if it is a cascading menu. + PositionCascadingMenu(); + } + + // set up scroll bar as appropriate + if ( m_pScroller->IsVisible() ) + { + LayoutScrollBar(); + } + + FOR_EACH_LL( m_MenuItems, j ) + { + m_MenuItems[j]->InvalidateLayout(); // cause each menu item to redo its apply settings now we have sized ourselves + } + + Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Force the menu to work out how wide it should be +//----------------------------------------------------------------------------- +void Menu::ForceCalculateWidth() +{ + _recalculateWidth = true; + CalculateWidth(); + PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out how wide the menu should be if the menu is not fixed width +//----------------------------------------------------------------------------- +void Menu::CalculateWidth() +{ + if (!_recalculateWidth) + return; + + _menuWide = 0; + if (!m_iFixedWidth) + { + // find the biggest menu item + FOR_EACH_LL( m_MenuItems, i ) + { + int wide, tall; + m_MenuItems[i]->GetContentSize(wide, tall); + if (wide > _menuWide - Label::Content) + { + _menuWide = wide + Label::Content; + } + } + } + + // enfoce a minimumWidth + if (_menuWide < m_iMinimumWidth) + { + _menuWide = m_iMinimumWidth; + } + + _recalculateWidth = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Set up the scroll bar attributes,size and location. +//----------------------------------------------------------------------------- +void Menu::LayoutScrollBar() +{ + //!! need to make it recalculate scroll positions + m_pScroller->SetEnabled(false); + m_pScroller->SetRangeWindow( m_VisibleSortedItems.Count() ); + m_pScroller->SetRange( 0, CountVisibleItems() ); + m_pScroller->SetButtonPressedScrollValue( 1 ); + + int wide, tall; + GetSize (wide, tall); + + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + // with a scroll bar we take off the inset + wide -= iright; + + m_pScroller->SetPos(wide - m_pScroller->GetWide(), 1); + + // scrollbar is inside the menu's borders. + m_pScroller->SetSize(m_pScroller->GetWide(), tall - ibottom - itop); + +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out where to open menu if it is a cascading menu +//----------------------------------------------------------------------------- +void Menu::PositionCascadingMenu() +{ + Assert(GetVParent()); + int parentX, parentY, parentWide, parentTall; + // move the menu to the correct place below the menuItem + ipanel()->GetSize(GetVParent(), parentWide, parentTall); + ipanel()->GetPos(GetVParent(), parentX, parentY); + + parentX += parentWide, parentY = 0; + + ParentLocalToScreen(parentX, parentY); + + SetPos(parentX, parentY); + + // for cascading menus, + // make sure we're on the screen + int workX, workY, workWide, workTall, x, y, wide, tall; + GetBounds(x, y, wide, tall); + surface()->GetWorkspaceBounds(workX, workY, workWide, workTall); + + if (x + wide > workX + workWide) + { + // we're off the right, move the menu to the left side + // orignalX - width of the parentmenuitem - width of this menu. + // add 2 pixels to offset one pixel onto the parent menu. + x -= (parentWide + wide); + x -= 2; + } + else + { + // alignment move it in the amount of the insets. + x += 1; + } + + if ( y + tall > workY + workTall ) + { + int lastWorkY = workY + workTall; + int pixelsOffBottom = ( y + tall ) - lastWorkY; + + y -= pixelsOffBottom; + y -= 2; + } + else + { + y -= 1; + } + SetPos(x, y); + + MoveToFront(); +} + +//----------------------------------------------------------------------------- +// Purpose: Size the menu items so they are the width of the menu. +// Also size the menu items with cascading menus so the arrow fits in there. +//----------------------------------------------------------------------------- +void Menu::SizeMenuItems() +{ + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + // assign the sizes of all the menu item panels + FOR_EACH_LL( m_MenuItems, i ) + { + MenuItem *child = m_MenuItems[i]; + if (child ) + { + // labels do thier own sizing. this will size the label to the width of the menu, + // this will put the cascading menu arrow on the right side automatically. + child->SetWide(_menuWide - ileft - iright); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Makes menu items visible in relation to where the scroll bar is +//----------------------------------------------------------------------------- +void Menu::MakeItemsVisibleInScrollRange( int maxVisibleItems, int nNumPixelsAvailable ) +{ + // Detach all items from tree + int i; + FOR_EACH_LL( m_MenuItems, item ) + { + m_MenuItems[ item ]->SetBounds( 0, 0, 0, 0 ); + } + for ( i = 0; i < m_SeparatorPanels.Count(); ++i ) + { + m_SeparatorPanels[ i ]->SetVisible( false ); + } + + m_VisibleSortedItems.RemoveAll(); + + int tall = 0; + + int startItem = m_pScroller->GetValue(); + Assert( startItem >= 0 ); + do + { + if ( startItem >= m_SortedItems.Count() ) + break; + + int itemId = m_SortedItems[ startItem ]; + + if ( !m_MenuItems[ itemId ]->IsVisible() ) + { + ++startItem; + continue; + } + + int itemHeight = m_iMenuItemHeight; + int sepIndex = m_Separators.Find( itemId ); + if ( sepIndex != m_Separators.InvalidIndex() ) + { + itemHeight += MENU_SEPARATOR_HEIGHT; + } + + if ( tall + itemHeight > nNumPixelsAvailable ) + break; + + // Too many items + if ( maxVisibleItems > 0 ) + { + if ( m_VisibleSortedItems.Count() >= maxVisibleItems ) + break; + } + + tall += itemHeight; + // Re-attach this one + m_VisibleSortedItems.AddToTail( itemId ); + ++startItem; + } + while ( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the approproate menu border +//----------------------------------------------------------------------------- +void Menu::LayoutMenuBorder() +{ + IBorder *menuBorder; + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + + menuBorder = pScheme->GetBorder("MenuBorder"); + + if ( menuBorder ) + { + SetBorder(menuBorder); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a black border on the right side of the menu items +//----------------------------------------------------------------------------- +void Menu::Paint() +{ + if ( m_pScroller->IsVisible() ) + { + // draw black bar + int wide, tall; + GetSize (wide, tall); + surface()->DrawSetColor(_borderDark); + if( IsProportional() ) + { + surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall); + } + else + { + surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: sets the max number of items visible (scrollbar appears with more) +// Input : numItems - +//----------------------------------------------------------------------------- +void Menu::SetNumberOfVisibleItems( int numItems ) +{ + m_iNumVisibleLines = numItems; + InvalidateLayout(false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::EnableUseMenuManager( bool bUseMenuManager ) +{ + m_bUseMenuManager = bUseMenuManager; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +MenuItem *Menu::GetMenuItem(int itemID) +{ + if ( !m_MenuItems.IsValidIndex(itemID) ) + return NULL; + + return m_MenuItems[itemID]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Menu::IsValidMenuID(int itemID) +{ + return m_MenuItems.IsValidIndex(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetInvalidMenuID() +{ + return m_MenuItems.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: When a menuItem is selected, close cascading menus +// if the menuItem selected has a cascading menu attached, we +// want to keep that one open so skip it. +// Passing NULL will close all cascading menus. +//----------------------------------------------------------------------------- +void Menu::CloseOtherMenus(MenuItem *item) +{ + FOR_EACH_LL( m_MenuItems, i ) + { + if (m_MenuItems[i] == item) + continue; + + m_MenuItems[i]->CloseCascadeMenu(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to string commands. +//----------------------------------------------------------------------------- +void Menu::OnCommand( const char *command ) +{ + // forward on the message + PostActionSignal(new KeyValues("Command", "command", command)); + + Panel::OnCommand(command); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses, Activate shortcuts +//----------------------------------------------------------------------------- +void Menu::OnKeyCodeTyped(KeyCode keycode) +{ + vgui::KeyCode code = GetBaseButtonCode( keycode ); + + // Don't allow key inputs when disabled! + if ( !IsEnabled() ) + return; + + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + if (alt) + { + BaseClass::OnKeyCodeTyped( keycode ); + // Ignore alt when in combobox mode + if (m_eTypeAheadMode != TYPE_AHEAD_MODE) + { + PostActionSignal(new KeyValues("MenuClose")); + } + } + + switch (code) + { + case KEY_ESCAPE: + case KEY_XBUTTON_B: + case STEAMCONTROLLER_B: + { + // hide the menu on ESC + SetVisible(false); + break; + } + // arrow keys scroll through items on the list. + // they should also scroll the scroll bar if needed + case KEY_UP: + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + case STEAMCONTROLLER_DPAD_UP: + { + MoveAlongMenuItemList(MENU_UP, 0); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); // chain up + } + break; + } + case KEY_DOWN: + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + case STEAMCONTROLLER_DPAD_DOWN: + { + MoveAlongMenuItemList(MENU_DOWN, 0); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); // chain up + } + break; + } + // for now left and right arrows just open or close submenus if they are there. + case KEY_RIGHT: + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case STEAMCONTROLLER_DPAD_RIGHT: + { + // make sure a menuItem is currently selected + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + if (m_MenuItems[m_iCurrentlySelectedItemID]->HasMenu()) + { + ActivateItem(m_iCurrentlySelectedItemID); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); + } + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); + } + break; + } + case KEY_LEFT: + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case STEAMCONTROLLER_DPAD_LEFT: + { + // if our parent is a menu item then we are a submenu so close us. + if (GetParentMenuItem()) + { + SetVisible(false); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); + } + break; + } + case KEY_ENTER: + case KEY_XBUTTON_A: + case STEAMCONTROLLER_A: + { + // make sure a menuItem is currently selected + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + ActivateItem(m_iCurrentlySelectedItemID); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); // chain up + } + break; + } + + case KEY_PAGEUP: + { + if ( m_iNumVisibleLines > 1 ) + { + if ( m_iCurrentlySelectedItemID < m_iNumVisibleLines ) + { + MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 ); + } + else + { + MoveAlongMenuItemList(MENU_UP * m_iNumVisibleLines - 1, 0); + } + } + else + { + MoveAlongMenuItemList(MENU_UP, 0); + } + + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + + + case KEY_PAGEDOWN: + { + if ( m_iNumVisibleLines > 1 ) + { + if ( m_iCurrentlySelectedItemID + m_iNumVisibleLines >= GetItemCount() ) + { + MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0); + } + else + { + MoveAlongMenuItemList(MENU_DOWN * m_iNumVisibleLines - 1, 0); + } + } + else + { + MoveAlongMenuItemList(MENU_DOWN, 0); + } + + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + + case KEY_HOME: + { + MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 ); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + + + case KEY_END: + { + MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + } + + // don't chain back +} + +void Menu::OnHotKey(wchar_t unichar) +{ + // iterate the menu items looking for one with the matching hotkey + FOR_EACH_LL( m_MenuItems, i ) + { + MenuItem *panel = m_MenuItems[i]; + if (panel->IsVisible()) + { + Panel *hot = panel->HasHotkey(unichar); + if (hot) + { + // post a message to the menuitem telling it it's hotkey was pressed + PostMessage(hot, new KeyValues("Hotkey")); + return; + } + // if the menuitem is a cascading menuitem and it is open, check its hotkeys too + Menu *cascadingMenu = panel->GetMenu(); + if (cascadingMenu && cascadingMenu->IsVisible()) + { + cascadingMenu->OnKeyTyped(unichar); + } + } + } +} + +void Menu::OnTypeAhead(wchar_t unichar) +{ + // Don't do anything if the menu is empty since there cannot be a selected item. + if ( m_MenuItems.Count() <= 0) + return; + + // expire the type ahead buffer after 0.5 seconds + double tCurrentTime = Sys_FloatTime(); + if ( (tCurrentTime - m_fLastTypeAheadTime) > 0.5f ) + { + m_iNumTypeAheadChars = 0; + m_szTypeAheadBuf[0] = '\0'; + } + m_fLastTypeAheadTime = tCurrentTime; + + // add current character to the type ahead buffer + if ( m_iNumTypeAheadChars+1 < TYPEAHEAD_BUFSIZE ) + { + m_szTypeAheadBuf[m_iNumTypeAheadChars++] = unichar; + } + + int itemToSelect = m_iCurrentlySelectedItemID; + if ( itemToSelect < 0 || itemToSelect >= m_MenuItems.Count()) + { + itemToSelect = 0; + } + + int i = itemToSelect; + do + { + wchar_t menuItemName[255]; + m_MenuItems[i]->GetText(menuItemName, 254); + + // This is supposed to be case insensitive but we don't have a portable case + // insensitive wide-character routine. + if ( wcsncmp( m_szTypeAheadBuf, menuItemName, m_iNumTypeAheadChars) == 0 ) + { + itemToSelect = i; + break; + } + + i = (i+1) % m_MenuItems.Count(); + } while ( i != itemToSelect ); + + if ( itemToSelect >= 0 ) + { + SetCurrentlyHighlightedItem( itemToSelect ); + InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses, Activate shortcuts +// Input : code - +//----------------------------------------------------------------------------- +void Menu::OnKeyTyped(wchar_t unichar) +{ + if (! unichar) + { + return; + } + + switch( m_eTypeAheadMode ) + { + case HOT_KEY_MODE: + OnHotKey(unichar); + return; + + case TYPE_AHEAD_MODE: + OnTypeAhead(unichar); + return; + + case COMPAT_MODE: + default: + break; + } + + int itemToSelect = m_iCurrentlySelectedItemID; + if ( itemToSelect < 0 ) + { + itemToSelect = 0; + } + + int i; + wchar_t menuItemName[255]; + + i = itemToSelect + 1; + if ( i >= m_MenuItems.Count() ) + { + i = 0; + } + + while ( i != itemToSelect ) + { + m_MenuItems[i]->GetText(menuItemName, 254); + + if ( tolower( unichar ) == tolower( menuItemName[0] ) ) + { + itemToSelect = i; + break; + } + + i++; + if ( i >= m_MenuItems.Count() ) + { + i = 0; + } + } + + if ( itemToSelect >= 0 ) + { + SetCurrentlyHighlightedItem( itemToSelect ); + InvalidateLayout(); + } + + // don't chain back +} + + +void Menu::SetTypeAheadMode(MenuTypeAheadMode mode) +{ + m_eTypeAheadMode = mode; +} + +int Menu::GetTypeAheadMode() +{ + return m_eTypeAheadMode; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle the mouse wheel event, scroll the selection +//----------------------------------------------------------------------------- +void Menu::OnMouseWheeled(int delta) +{ + if (!m_pScroller->IsVisible()) + return; + + int val = m_pScroller->GetValue(); + val -= delta; + + m_pScroller->SetValue(val); + + // moving the slider redraws the scrollbar, + // and so we should redraw the menu since the + // menu draws the black border to the right of the scrollbar. + InvalidateLayout(); + + // don't chain back +} + + +//----------------------------------------------------------------------------- +// Purpose: Lose focus, hide menu +//----------------------------------------------------------------------------- +void Menu::OnKillFocus() +{ + // check to see if it's a child taking it + if (!input()->GetFocus() || !ipanel()->HasParent(input()->GetFocus(), GetVPanel())) + { + // if we don't accept keyboard input, then we have to ignore the killfocus if it's not actually being stolen + if (!IsKeyBoardInputEnabled() && !input()->GetFocus()) + return; + + // get the parent of this menu. + MenuItem *item = GetParentMenuItem(); + // if the parent is a menu item, this menu is a cascading menu + // if the panel that is getting focus is the parent menu, don't close this menu. + if ( (item) && (input()->GetFocus() == item->GetVParent()) ) + { + // if we are in mouse mode and we clicked on the menuitem that + // triggers the cascading menu, leave it open. + if (m_iInputMode == MOUSE) + { + // return the focus to the cascading menu. + MoveToFront(); + return; + } + } + + // forward the message to the parent. + PostActionSignal(new KeyValues("MenuClose")); + + // hide this menu + SetVisible(false); + } + +} + +namespace vgui +{ + +class CMenuManager +{ +public: + void AddMenu( Menu *m ) + { + if ( !m ) + return; + + int c = m_Menus.Count(); + for ( int i = 0 ; i < c; ++i ) + { + if ( m_Menus[ i ].Get() == m ) + return; + } + + DHANDLE< Menu > h; + h = m; + m_Menus.AddToTail( h ); + } + + void RemoveMenu( Menu *m ) + { + if ( !m ) + return; + + int c = m_Menus.Count(); + for ( int i = c - 1 ; i >= 0; --i ) + { + if ( m_Menus[ i ].Get() == m ) + { + m_Menus.Remove( i ); + return; + } + } + } + + void OnInternalMousePressed( Panel *other, MouseCode code ) + { + int c = m_Menus.Count(); + if ( !c ) + return; + + int x, y; + input()->GetCursorPos( x, y ); + + bool mouseInsideMenuRelatedPanel = false; + + for ( int i = c - 1; i >= 0 ; --i ) + { + Menu *m = m_Menus[ i ].Get(); + if ( !m ) + { + m_Menus.Remove( i ); + continue; + } + + // See if the mouse is within a menu + if ( IsWithinMenuOrRelative( m, x, y ) ) + { + mouseInsideMenuRelatedPanel = true; + } + } + + if ( mouseInsideMenuRelatedPanel ) + { + return; + } + + AbortMenus(); + } + + void AbortMenus() + { + // Close all of the menus + int c = m_Menus.Count(); + for ( int i = c - 1; i >= 0 ; --i ) + { + Menu *m = m_Menus[ i ].Get(); + if ( !m ) + { + continue; + } + + m_Menus.Remove( i ); + + // Force it to close + m->SetVisible( false ); + } + + m_Menus.RemoveAll(); + } + + bool IsWithinMenuOrRelative( Panel *panel, int x, int y ) + { + VPANEL topMost = panel->IsWithinTraverse( x, y, true ); + if ( topMost ) + { + // It's over the menu + if ( topMost == panel->GetVPanel() ) + { + return true; + } + + // It's over something which is parented to the menu (i.e., a menu item) + if ( ipanel()->HasParent( topMost, panel->GetVPanel() ) ) + { + return true; + } + } + + if ( panel->GetParent() ) + { + Panel *parent = panel->GetParent(); + + topMost = parent->IsWithinTraverse( x, y, true ); + + if ( topMost ) + { + if ( topMost == parent->GetVPanel() ) + { + return true; + } + + /* + // NOTE: this check used to not cast to MenuButton, but it seems wrong to me + // since if the mouse is over another child of the parent panel to the menu then + // the menu stays visible. I think this is bogus. + Panel *pTopMost = ipanel()->GetPanel(topMost, GetControlsModuleName()); + + if ( pTopMost && + ipanel()->HasParent( topMost, parent->GetVPanel() ) && + dynamic_cast< MenuButton * >( pTopMost ) ) + { + Msg( "topMost %s has parent %s\n", + ipanel()->GetName( topMost ), + parent->GetName() ); + + return true; + } + */ + } + } + + return false; + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, char *pchName ) + { + validator.Push( "CMenuManager", this, pchName ); + m_Menus.Validate( validator, "m_Menus" ); + validator.Pop(); + } +#endif + +private: + + // List of visible menus + CUtlVector< DHANDLE< Menu > > m_Menus; +}; + + +// Singleton helper class +static CMenuManager g_MenuMgr; + +void ValidateMenuGlobals( CValidator &validator ) +{ +#ifdef DBGFLAG_VALIDATE + g_MenuMgr.Validate( validator, "g_MenuMgr" ); +#endif +} + +} // end namespace vgui + +//----------------------------------------------------------------------------- +// Purpose: Static method called on mouse released to see if Menu objects should be aborted +// Input : *other - +// code - +//----------------------------------------------------------------------------- +void Menu::OnInternalMousePressed( Panel *other, MouseCode code ) +{ + g_MenuMgr.OnInternalMousePressed( other, code ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set visibility of menu and its children as appropriate. +//----------------------------------------------------------------------------- +void Menu::SetVisible(bool state) +{ + if (state == IsVisible()) + return; + + if ( state == false ) + { + PostActionSignal(new KeyValues("MenuClose")); + CloseOtherMenus(NULL); + + SetCurrentlySelectedItem(-1); + + g_MenuMgr.RemoveMenu( this ); + } + else if ( state == true ) + { + MoveToFront(); + RequestFocus(); + + // Add to menu manager? + if ( m_bUseMenuManager ) + { + g_MenuMgr.AddMenu( this ); + } + } + + // must be after movetofront() + BaseClass::SetVisible(state); + _sizedForScrollBar = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("Menu.TextColor", pScheme)); + SetBgColor(GetSchemeColor("Menu.BgColor", pScheme)); + + _borderDark = pScheme->GetColor("BorderDark", Color(255, 255, 255, 0)); + + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i]->IsCheckable() ) + { + int wide, tall; + m_MenuItems[i]->GetCheckImageSize( wide, tall ); + + m_iCheckImageWidth = max ( m_iCheckImageWidth, wide ); + } + } + _recalculateWidth = true; + CalculateWidth(); + + InvalidateLayout(); +} + +void Menu::SetBgColor( Color newColor ) +{ + BaseClass::SetBgColor( newColor ); + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i]->HasMenu() ) + { + m_MenuItems[i]->GetMenu()->SetBgColor( newColor ); + } + } +} + +void Menu::SetFgColor( Color newColor ) +{ + BaseClass::SetFgColor( newColor ); + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i]->HasMenu() ) + { + m_MenuItems[i]->GetMenu()->SetFgColor( newColor ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::SetBorder(class IBorder *border) +{ + Panel::SetBorder(border); +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a MenuItem that is this menus parent, if it has one +//----------------------------------------------------------------------------- +MenuItem *Menu::GetParentMenuItem() +{ + return dynamic_cast<MenuItem *>(GetParent()); +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the menu when an item has been selected +//----------------------------------------------------------------------------- +void Menu::OnMenuItemSelected(Panel *panel) +{ + SetVisible(false); + m_pScroller->SetVisible(false); + + // chain this message up through the hierarchy so + // all the parent menus will close + + // get the parent of this menu. + MenuItem *item = GetParentMenuItem(); + // if the parent is a menu item, this menu is a cascading menu + if (item) + { + // get the parent of the menuitem. it should be a menu. + Menu *parentMenu = item->GetParentMenu(); + if (parentMenu) + { + // send the message to this parent menu + KeyValues *kv = new KeyValues("MenuItemSelected"); + kv->SetPtr("panel", panel); + ivgui()->PostMessage(parentMenu->GetVPanel(), kv, GetVPanel()); + } + } + + bool activeItemSet = false; + + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i] == panel ) + { + activeItemSet = true; + m_iActivatedItem = i; + break; + } + } + if( !activeItemSet ) + { + FOR_EACH_LL( m_MenuItems, i ) + { + if(m_MenuItems[i]->HasMenu() ) + { + /* + // GetActiveItem needs to return -1 or similar if it hasn't been set... + if( m_MenuItems[i]->GetActiveItem() ) + { + m_iActivatedItem = m_MenuItems[i]->GetActiveItem(); + }*/ + } + } + } + + // also pass it to the parent so they can respond if they like + if (GetVParent()) + { + KeyValues *kv = new KeyValues("MenuItemSelected"); + kv->SetPtr("panel", panel); + + ivgui()->PostMessage(GetVParent(), kv, GetVPanel()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetActiveItem() +{ + return m_iActivatedItem; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *Menu::GetItemUserData(int itemID) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem && menuItem->IsEnabled()) + { + return menuItem->GetUserData(); + } + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void Menu::GetItemText(int itemID, wchar_t *text, int bufLenInBytes) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + if (menuItem) + { + menuItem->GetText(text, bufLenInBytes); + return; + } + } + text[0] = 0; +} + +void Menu::GetItemText(int itemID, char *text, int bufLenInBytes) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + if (menuItem) + { + menuItem->GetText( text, bufLenInBytes ); + return; + } + } + text[0] = 0; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Activate the n'th item in the menu list, as if that menu item had been selected by the user +//----------------------------------------------------------------------------- +void Menu::ActivateItem(int itemID) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem && menuItem->IsEnabled()) + { + menuItem->FireActionSignal(); + m_iActivatedItem = itemID; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::SilentActivateItem(int itemID) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem && menuItem->IsEnabled()) + { + m_iActivatedItem = itemID; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::ActivateItemByRow(int row) +{ + if (m_SortedItems.IsValidIndex(row)) + { + ActivateItem(m_SortedItems[row]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of items currently in the menu list +//----------------------------------------------------------------------------- +int Menu::GetItemCount() const +{ + return m_MenuItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetMenuID(int index) +{ + if ( !m_SortedItems.IsValidIndex(index) ) + return m_MenuItems.InvalidIndex(); + + return m_SortedItems[index]; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of items currently visible in the menu list +//----------------------------------------------------------------------------- +int Menu::GetCurrentlyVisibleItemsCount() +{ + if (m_MenuItems.Count() < m_iNumVisibleLines) + { + int cMenuItems = 0; + FOR_EACH_LL(m_MenuItems, i) + { + if (m_MenuItems[i]->IsVisible()) + { + ++cMenuItems; + } + } + + return cMenuItems; + } + return m_iNumVisibleLines; +} + +//----------------------------------------------------------------------------- +// Purpose: Enables/disables choices in the list +// itemText - string name of item in the list +// state - true enables, false disables +//----------------------------------------------------------------------------- +void Menu::SetItemEnabled(const char *itemName, bool state) +{ + FOR_EACH_LL( m_MenuItems, i ) + { + if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0) + { + m_MenuItems[i]->SetEnabled(state); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Enables/disables choices in the list +//----------------------------------------------------------------------------- +void Menu::SetItemEnabled(int itemID, bool state) +{ + if ( !m_MenuItems.IsValidIndex(itemID) ) + return; + + m_MenuItems[itemID]->SetEnabled(state); +} + +//----------------------------------------------------------------------------- +// Purpose: shows/hides choices in the list +//----------------------------------------------------------------------------- +void Menu::SetItemVisible(const char *itemName, bool state) +{ + FOR_EACH_LL( m_MenuItems, i ) + { + if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0) + { + m_MenuItems[i]->SetVisible(state); + InvalidateLayout(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: shows/hides choices in the list +//----------------------------------------------------------------------------- +void Menu::SetItemVisible(int itemID, bool state) +{ + if ( !m_MenuItems.IsValidIndex(itemID) ) + return; + + m_MenuItems[itemID]->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Make the scroll bar visible and narrow the menu +// also make items visible or invisible in the list as appropriate +//----------------------------------------------------------------------------- +void Menu::AddScrollBar() +{ + m_pScroller->SetVisible(true); + _sizedForScrollBar = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Make the scroll bar invisible and widen the menu +//----------------------------------------------------------------------------- +void Menu::RemoveScrollBar() +{ + m_pScroller->SetVisible(false); + _sizedForScrollBar = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Invalidate layout if the slider is moved so items scroll +//----------------------------------------------------------------------------- +void Menu::OnSliderMoved() +{ + CloseOtherMenus(NULL); // close any cascading menus + + // Invalidate so we redraw the menu! + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle into mouse mode. +//----------------------------------------------------------------------------- +void Menu::OnCursorMoved(int x, int y) +{ + m_iInputMode = MOUSE; + + // chain up + CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); + //RequestFocus(); + //InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle into keyboard mode. +//----------------------------------------------------------------------------- +void Menu::OnKeyCodePressed(KeyCode code) +{ + m_iInputMode = KEYBOARD; + // send the message to this parent in case this is a cascading menu + if (GetVParent()) + { + ivgui()->PostMessage(GetVParent(), new KeyValues("KeyModeSet"), GetVPanel()); + } + + BaseClass::OnKeyCodePressed( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the item currently highlighted in the menu by ptr +//----------------------------------------------------------------------------- +void Menu::SetCurrentlySelectedItem(MenuItem *item) +{ + int itemNum = -1; + // find it in our list of menuitems + FOR_EACH_LL( m_MenuItems, i ) + { + MenuItem *child = m_MenuItems[i]; + if (child == item) + { + itemNum = i; + break; + } + } + Assert( itemNum >= 0 ); + + SetCurrentlySelectedItem(itemNum); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::ClearCurrentlyHighlightedItem() +{ + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem(); + } + m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the item currently highlighted in the menu by index +//----------------------------------------------------------------------------- +void Menu::SetCurrentlySelectedItem(int itemID) +{ + // dont deselect if its the same item + if (itemID == m_iCurrentlySelectedItemID) + return; + + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem(); + } + + PostActionSignal(new KeyValues("MenuItemHighlight", "itemID", itemID)); + m_iCurrentlySelectedItemID = itemID; +} + +//----------------------------------------------------------------------------- +// This will set the item to be currenly selected and highlight it +// will not open cascading menu. This was added for comboboxes +// to have the combobox item highlighted in the menu when they open the +// dropdown. +//----------------------------------------------------------------------------- +void Menu::SetCurrentlyHighlightedItem(int itemID) +{ + SetCurrentlySelectedItem(itemID); + int row = m_SortedItems.Find(itemID); + // If we have no items, then row will be -1. The dev console, for example... + Assert( ( m_SortedItems.Count() == 0 ) || ( row != -1 ) ); + if ( row == -1 ) + return; + + // if there is a scroll bar, and we scroll off lets move it. + if ( m_pScroller->IsVisible() ) + { + // now if we are off the scroll bar, it means we moved the scroll bar + // by hand or set the item off the list + // so just snap the scroll bar straight to the item. + if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1 ) || + ( row < m_pScroller->GetValue() ) ) + { + if ( !m_pScroller->IsVisible() ) + return; + + m_pScroller->SetValue(row); + } + } + + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + if ( !m_MenuItems[m_iCurrentlySelectedItemID]->IsArmed() ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetCurrentlyHighlightedItem() +{ + return m_iCurrentlySelectedItemID; +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to cursor entering a menuItem. +//----------------------------------------------------------------------------- +void Menu::OnCursorEnteredMenuItem(int VPanel) +{ + VPANEL menuItem = (VPANEL)VPanel; + // if we are in mouse mode + if (m_iInputMode == MOUSE) + { + MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName())); + // arm the menu + item->ArmItem(); + SetCurrentlySelectedItem(item); + + // open the cascading menu if there is one. + if ( item->HasMenu() ) + { + // open the cascading menu if there is one. + item->OpenCascadeMenu(); + ActivateItem( m_iCurrentlySelectedItemID ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to cursor exiting a menuItem +//----------------------------------------------------------------------------- +void Menu::OnCursorExitedMenuItem(int VPanel) +{ + VPANEL menuItem = (VPANEL)VPanel; + // only care if we are in mouse mode + if (m_iInputMode == MOUSE) + { + MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName())); + // unhighlight the item. + // note menuItems with cascading menus will stay lit. + item->DisarmItem(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Move up or down one in the list of items in the menu +// Direction is MENU_UP or MENU_DOWN +//----------------------------------------------------------------------------- +void Menu::MoveAlongMenuItemList(int direction, int loopCount) +{ + // Early out if no menu items to scroll through + if (m_MenuItems.Count() <= 0) + return; + + int itemID = m_iCurrentlySelectedItemID; + int row = m_SortedItems.Find(itemID); + row += direction; + + if ( row > m_SortedItems.Count() - 1 ) + { + if ( m_pScroller->IsVisible() ) + { + // stop at bottom of scrolled list + row = m_SortedItems.Count() - 1; + } + else + { + // if no scroll bar we circle around + row = 0; + } + } + else if (row < 0) + { + if ( m_pScroller->IsVisible() ) + { + // stop at top of scrolled list + row = m_pScroller->GetValue(); + } + else + { + // if no scroll bar circle around + row = m_SortedItems.Count()-1; + } + } + + // if there is a scroll bar, and we scroll off lets move it. + if ( m_pScroller->IsVisible() ) + { + if ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) + { + int val = m_pScroller->GetValue(); + val -= -direction; + + m_pScroller->SetValue(val); + + // moving the slider redraws the scrollbar, + // and so we should redraw the menu since the + // menu draws the black border to the right of the scrollbar. + InvalidateLayout(); + } + else if ( row < m_pScroller->GetValue() ) + { + int val = m_pScroller->GetValue(); + val -= -direction; + + m_pScroller->SetValue(val); + + // moving the slider redraws the scrollbar, + // and so we should redraw the menu since the + // menu draws the black border to the right of the scrollbar. + InvalidateLayout(); + } + + // now if we are still off the scroll bar, it means we moved the scroll bar + // by hand and created a situation in which we moved an item down, but the + // scroll bar is already too far down and should scroll up or vice versa + // so just snap the scroll bar straight to the item. + if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) || + ( row < m_pScroller->GetValue() ) ) + { + m_pScroller->SetValue(row); + } + } + + // switch it back to an itemID from row + if ( m_SortedItems.IsValidIndex( row ) ) + { + SetCurrentlySelectedItem( m_SortedItems[row] ); + } + + // don't allow us to loop around more than once + if (loopCount < m_MenuItems.Count()) + { + // see if the text is empty, if so skip + wchar_t text[256]; + m_MenuItems[m_iCurrentlySelectedItemID]->GetText(text, 255); + if (text[0] == 0 || !m_MenuItems[m_iCurrentlySelectedItemID]->IsVisible()) + { + // menu item is empty, keep moving along + MoveAlongMenuItemList(direction, loopCount + 1); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return which type of events the menu is currently interested in +// MenuItems need to know because behaviour is different depending on mode. +//----------------------------------------------------------------------------- +int Menu::GetMenuMode() +{ + return m_iInputMode; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the menu to key mode if a child menu goes into keymode +// This mode change has to be chained up through the menu heirarchy +// so cascading menus will work when you do a bunch of stuff in keymode +// in high level menus and then switch to keymode in lower level menus. +//----------------------------------------------------------------------------- +void Menu::OnKeyModeSet() +{ + m_iInputMode = KEYBOARD; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the checked state of a menuItem +//----------------------------------------------------------------------------- +void Menu::SetMenuItemChecked(int itemID, bool state) +{ + m_MenuItems[itemID]->SetChecked(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if item is checked. +//----------------------------------------------------------------------------- +bool Menu::IsChecked(int itemID) +{ + return m_MenuItems[itemID]->IsChecked(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the minmum width the menu has to be. This +// is useful if you have a menu that is sized to the largest item in it +// but you don't want the menu to be thinner than the menu button +//----------------------------------------------------------------------------- +void Menu::SetMinimumWidth(int width) +{ + m_iMinimumWidth = width; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the minmum width the menu +//----------------------------------------------------------------------------- +int Menu::GetMinimumWidth() +{ + return m_iMinimumWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void Menu::AddSeparator() +{ + int lastID = m_MenuItems.Count() - 1; + m_Separators.AddToTail( lastID ); + m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) ); +} + +void Menu::AddSeparatorAfterItem( int itemID ) +{ + Assert( m_MenuItems.IsValidIndex( itemID ) ); + m_Separators.AddToTail( itemID ); + m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) ); +} + +void Menu::MoveMenuItem( int itemID, int moveBeforeThisItemID ) +{ + int c = m_SortedItems.Count(); + int i; + for ( i = 0; i < c; ++i ) + { + if ( m_SortedItems[i] == itemID ) + { + m_SortedItems.Remove( i ); + break; + } + } + + // Didn't find it + if ( i >= c ) + { + return; + } + + // Now find insert pos + c = m_SortedItems.Count(); + for ( i = 0; i < c; ++i ) + { + if ( m_SortedItems[i] == moveBeforeThisItemID ) + { + m_SortedItems.InsertBefore( i, itemID ); + break; + } + } +} + +void Menu::SetFont( HFont font ) +{ + m_hItemFont = font; + if ( font ) + { + m_iMenuItemHeight = surface()->GetFontTall( font ) + 2; + } + InvalidateLayout(); +} + + +void Menu::SetCurrentKeyBinding( int itemID, char const *hotkey ) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + menuItem->SetCurrentKeyBinding( hotkey ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Static method to display a context menu +// Input : *parent - +// *menu - +//----------------------------------------------------------------------------- +void Menu::PlaceContextMenu( Panel *parent, Menu *menu ) +{ + Assert( parent ); + Assert( menu ); + if ( !menu || !parent ) + return; + + menu->SetVisible(false); + menu->SetParent( parent ); + menu->AddActionSignalTarget( parent ); + + // get cursor position, this is local to this text edit window + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + + menu->SetVisible(true); + + // relayout the menu immediately so that we know it's size + menu->InvalidateLayout(true); + int menuWide, menuTall; + menu->GetSize(menuWide, menuTall); + + // work out where the cursor is and therefore the best place to put the menu + int wide, tall; + surface()->GetScreenSize(wide, tall); + + if (wide - menuWide > cursorX) + { + // menu hanging right + if (tall - menuTall > cursorY) + { + // menu hanging down + menu->SetPos(cursorX, cursorY); + } + else + { + // menu hanging up + menu->SetPos(cursorX, cursorY - menuTall); + } + } + else + { + // menu hanging left + if (tall - menuTall > cursorY) + { + // menu hanging down + menu->SetPos(cursorX - menuWide, cursorY); + } + else + { + // menu hanging up + menu->SetPos(cursorX - menuWide, cursorY - menuTall); + } + } + + menu->RequestFocus(); +} + +void Menu::SetUseFallbackFont( bool bState, HFont hFallback ) +{ + m_hFallbackItemFont = hFallback; + m_bUseFallbackFont = bState; +} + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Run a global validation pass on all of our data structures and memory +// allocations. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void Menu::Validate( CValidator &validator, char *pchName ) +{ + validator.Push( "vgui::Menu", this, pchName ); + + m_MenuItems.Validate( validator, "m_MenuItems" ); + m_SortedItems.Validate( validator, "m_SortedItems" ); + + BaseClass::Validate( validator, "vgui::Menu" ); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + + +MenuBuilder::MenuBuilder( Menu *pMenu, Panel *pActionTarget ) + : m_pMenu( pMenu ) + , m_pActionTarget( pActionTarget ) + , m_pszLastCategory( NULL ) +{} + +MenuItem* MenuBuilder::AddMenuItem( const char *pszButtonText, const char *pszCommand, const char *pszCategoryName ) +{ + AddSepratorIfNeeded( pszCategoryName ); + return m_pMenu->GetMenuItem( m_pMenu->AddMenuItem( pszButtonText, pszCommand, m_pActionTarget ) ); +} + +MenuItem* MenuBuilder::AddMenuItem( const char *pszButtonText, KeyValues *kvUserData, const char *pszCategoryName ) +{ + AddSepratorIfNeeded( pszCategoryName ); + return m_pMenu->GetMenuItem( m_pMenu->AddMenuItem( pszButtonText, kvUserData, m_pActionTarget ) ); +} + +MenuItem* MenuBuilder::AddCascadingMenuItem( const char *pszButtonText, Menu *pSubMenu, const char *pszCategoryName ) +{ + AddSepratorIfNeeded( pszCategoryName ); + return m_pMenu->GetMenuItem( m_pMenu->AddCascadingMenuItem( pszButtonText, m_pActionTarget, pSubMenu ) ); +} + +void MenuBuilder::AddSepratorIfNeeded( const char *pszCategoryName ) +{ + // Add a separator if the categories are different + if ( m_pszLastCategory && V_stricmp( pszCategoryName, m_pszLastCategory ) != 0 ) + { + m_pMenu->AddSeparator(); + } + + m_pszLastCategory = pszCategoryName; +} diff --git a/vgui2/vgui_controls/MenuBar.cpp b/vgui2/vgui_controls/MenuBar.cpp new file mode 100644 index 0000000..43ae560 --- /dev/null +++ b/vgui2/vgui_controls/MenuBar.cpp @@ -0,0 +1,252 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/IScheme.h> +#include <vgui/IBorder.h> +#include <vgui/ISurface.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> + +#include <vgui_controls/MenuBar.h> +#include <vgui_controls/MenuButton.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/Controls.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + + +enum +{ + MENUBARINDENT = 4, // indent from top and bottom of panel. +}; + +DECLARE_BUILD_FACTORY( MenuBar ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +MenuBar::MenuBar(Panel *parent, const char *panelName) : + Panel(parent, panelName), + m_nRightEdge( 0 ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +MenuBar::~MenuBar() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void MenuBar::AddButton(MenuButton *button) +{ + button->SetParent(this); + button->AddActionSignalTarget(this); + m_pMenuButtons.AddToTail(button); +} + +//----------------------------------------------------------------------------- +// This will add the menu to the menu bar +//----------------------------------------------------------------------------- +void MenuBar::AddMenu( const char *pButtonName, Menu *pMenu ) +{ + MenuButton *pMenuButton = new MenuButton(this, pButtonName, pButtonName); + pMenuButton->SetMenu(pMenu); + AddButton(pMenuButton); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses, Activate shortcuts +//----------------------------------------------------------------------------- +void MenuBar::OnKeyCodeTyped(KeyCode code) +{ + switch(code) + { + case KEY_RIGHT: + { + // iterate the menu items looking for one that is open + // if we find one open, open the one to the right + for (int i = 0; i < m_pMenuButtons.Count() - 1; i++) + { + MenuButton *panel = m_pMenuButtons[i]; + if (panel->IsDepressed()) + { + m_pMenuButtons[i]->DoClick(); + m_pMenuButtons[i+1]->DoClick(); + break; + } + } + break; + } + case KEY_LEFT: + { + // iterate the menu items looking for one that is open + // if we find one open, open the one to the left + for (int i = 1; i < m_pMenuButtons.Count(); i++) + { + MenuButton *panel = m_pMenuButtons[i]; + if (panel->IsDepressed()) + { + m_pMenuButtons[i]->DoClick(); + m_pMenuButtons[i-1]->DoClick(); + break; + } + } + break; + } + default: + { + break; + } + } + + // don't chain back +} + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses, Activate shortcuts +// Input : code - +//----------------------------------------------------------------------------- +void MenuBar::OnKeyTyped(wchar_t unichar) +{ + if (unichar) + { + // iterate the menu items looking for one with the matching hotkey + for (int i = 0; i < m_pMenuButtons.Count(); i++) + { + MenuButton *panel = m_pMenuButtons[i]; + if (panel->IsVisible()) + { + Panel *hot = panel->HasHotkey(unichar); + if (hot) + { + // post a message to the menuitem telling it it's hotkey was pressed + PostMessage(hot, new KeyValues("Hotkey")); + return; + } + } + } + } + + // don't chain back +} + +void MenuBar::Paint() +{ + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + for ( int i = 0; i < m_pMenuButtons.Count(); i++) + { + if (!m_pMenuButtons[i]->IsArmed()) + m_pMenuButtons[i]->SetDefaultBorder(NULL); + else + { + m_pMenuButtons[i]->SetDefaultBorder(pScheme->GetBorder( "ButtonBorder")); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Message map +//----------------------------------------------------------------------------- +void MenuBar::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + // get the borders we need + SetBorder(pScheme->GetBorder("ButtonBorder")); + + // get the background color + SetBgColor(pScheme->GetColor( "MenuBar.BgColor", GetBgColor() )); + +} + + + +//----------------------------------------------------------------------------- +// Purpose: Reformat according to the new layout +//----------------------------------------------------------------------------- +void MenuBar::PerformLayout() +{ + int nBarWidth, nBarHeight; + GetSize( nBarWidth, nBarHeight ); + + // Now position + resize all buttons + int x = MENUBARINDENT; + for ( int i = 0; i < m_pMenuButtons.Count(); ++i ) + { + int nWide, nTall; + + m_pMenuButtons[i]->GetContentSize(nWide, nTall); + m_pMenuButtons[i]->SetPos( x, MENUBARINDENT ); + m_pMenuButtons[i]->SetSize( nWide + Label::Content, nBarHeight - 2 * MENUBARINDENT ); + + x += nWide + MENUBARINDENT; + } + + m_nRightEdge = x; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the size of the menus in the bar (so other children can be added to menu bar) +// Input : w - +// int&h - +//----------------------------------------------------------------------------- +void MenuBar::GetContentSize( int& w, int&h ) +{ + w = m_nRightEdge + 2; + h = GetTall(); +} + +//----------------------------------------------------------------------------- +// Purpose: Message map +//----------------------------------------------------------------------------- +void MenuBar::OnMenuClose() +{ + RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Message map +//----------------------------------------------------------------------------- +void MenuBar::OnCursorEnteredMenuButton(int VPanel) +{ + VPANEL menuButton = (VPANEL)VPanel; + // see if we had a menu open + for ( int i = 0; i < m_pMenuButtons.Count(); i++) + { + // one of our buttons was pressed. + if (m_pMenuButtons[i]->IsDepressed()) + { + int oldbutton = i; + // now see if menuButton is one of ours. + for ( int j = 0; j < m_pMenuButtons.Count(); j++) + { + MenuButton *button = static_cast<MenuButton *>(ipanel()->GetPanel(menuButton, GetModuleName())); + // it is one of ours. + if ( button == m_pMenuButtons[j]) + { + // if its a different button than the one we already had open, + if (j != oldbutton) + { + // close this menu and open the one we just entered + m_pMenuButtons[oldbutton]->DoClick(); + button->DoClick(); + } + } + } + } + } +} diff --git a/vgui2/vgui_controls/MenuButton.cpp b/vgui2/vgui_controls/MenuButton.cpp new file mode 100644 index 0000000..19b35ae --- /dev/null +++ b/vgui2/vgui_controls/MenuButton.cpp @@ -0,0 +1,351 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#define PROTECTED_THINGS_DISABLE + +#include <vgui/IPanel.h> +#include <vgui/IInput.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> +#include <vgui/IVGui.h> + +#include <vgui_controls/Controls.h> +#include <vgui_controls/MenuButton.h> +#include <vgui_controls/Menu.h> +#include <vgui_controls/TextImage.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( MenuButton, MenuButton ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +MenuButton::MenuButton(Panel *parent, const char *panelName, const char *text) : Button(parent, panelName, text) +{ + m_pMenu = NULL; + m_iDirection = Menu::DOWN; + m_pDropMenuImage = NULL; + m_nImageIndex = -1; + _openOffsetY = 0; + m_bDropMenuButtonStyle = true; // set to true so SetDropMenuButtonStyle() forces real init. + + SetDropMenuButtonStyle( false ); + SetUseCaptureMouse( false ); + SetButtonActivationType( ACTIVATE_ONPRESSED ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +MenuButton::~MenuButton() +{ + delete m_pDropMenuImage; +} + +//----------------------------------------------------------------------------- +// Purpose: attaches a menu to the menu button +//----------------------------------------------------------------------------- +void MenuButton::SetMenu(Menu *menu) +{ + m_pMenu = menu; + + if (menu) + { + m_pMenu->SetVisible(false); + m_pMenu->AddActionSignalTarget(this); + m_pMenu->SetParent(this); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Never draw a focus border +//----------------------------------------------------------------------------- +void MenuButton::DrawFocusBorder(int tx0, int ty0, int tx1, int ty1) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the direction from the menu button the menu should open +//----------------------------------------------------------------------------- +void MenuButton::SetOpenDirection(Menu::MenuDirection_e direction) +{ + m_iDirection = direction; +} + + +//----------------------------------------------------------------------------- +// Purpose: hides the menu +//----------------------------------------------------------------------------- +void MenuButton::HideMenu(void) +{ + if (!m_pMenu) + return; + + // hide the menu + m_pMenu->SetVisible(false); + + // unstick the button + BaseClass::ForceDepressed(false); + Repaint(); + + OnHideMenu(m_pMenu); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when the menu button loses focus; hides the menu +//----------------------------------------------------------------------------- +void MenuButton::OnKillFocus( KeyValues *pParams ) +{ + VPANEL hPanel = (VPANEL)pParams->GetPtr( "newPanel" ); + if ( m_pMenu && !m_pMenu->HasFocus() && hPanel != m_pMenu->GetVPanel() ) + { + HideMenu(); + } + BaseClass::OnKillFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the menu is closed +//----------------------------------------------------------------------------- +void MenuButton::OnMenuClose() +{ + HideMenu(); + PostActionSignal(new KeyValues("MenuClose")); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the offset from where menu would normally be placed +// Only is used if menu is ALIGN_WITH_PARENT +//----------------------------------------------------------------------------- +void MenuButton::SetOpenOffsetY(int yOffset) +{ + _openOffsetY = yOffset; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool MenuButton::CanBeDefaultButton(void) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Handles hotkey accesses +//----------------------------------------------------------------------------- +void MenuButton::DoClick() +{ + if ( IsDropMenuButtonStyle() && + m_pDropMenuImage ) + { + int mx, my; + // force the menu to appear where the mouse button was pressed + input()->GetCursorPos( mx, my ); + ScreenToLocal( mx, my ); + + int contentW, contentH; + m_pDropMenuImage->GetContentSize( contentW, contentH ); + int drawX = GetWide() - contentW - 2; + if ( mx <= drawX || !OnCheckMenuItemCount() ) + { + // Treat it like a "regular" button click + BaseClass::DoClick(); + return; + } + } + + if ( !m_pMenu ) + { + return; + } + + // menu is already visible, hide the menu + if (m_pMenu->IsVisible()) + { + HideMenu(); + return; + } + + // do nothing if menu is not enabled + if (!m_pMenu->IsEnabled()) + { + return; + } + // force the menu to compute required width/height + m_pMenu->PerformLayout(); + + // Now position it so it can fit in the workspace + m_pMenu->PositionRelativeToPanel(this, m_iDirection, _openOffsetY ); + + // make sure we're at the top of the draw order (and therefore our children as well) + MoveToFront(); + + // notify + OnShowMenu(m_pMenu); + + // keep the button depressed + BaseClass::ForceDepressed(true); + + // show the menu + m_pMenu->SetVisible(true); + + // bring to focus + m_pMenu->RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void MenuButton::OnKeyCodeTyped(KeyCode code) +{ + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + if (!shift && !ctrl && !alt) + { + switch (code) + { + case KEY_ENTER: + { + if ( !IsDropMenuButtonStyle() ) + { + DoClick(); + } + break; + } + } + } + BaseClass::OnKeyCodeTyped(code); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void MenuButton::OnCursorEntered() +{ + Button::OnCursorEntered(); + // post a message to the parent menu. + // forward the message on to the parent of this menu. + KeyValues *msg = new KeyValues ("CursorEnteredMenuButton"); + // tell the parent this menuitem is the one that was entered so it can open the menu if it wants + msg->SetInt("VPanel", GetVPanel()); + ivgui()->PostMessage(GetVParent(), msg, NULL); +} + +// This style is like the IE "back" button where the left side acts like a regular button, the the right side has a little +// combo box dropdown indicator and presents and submenu +void MenuButton::SetDropMenuButtonStyle( bool state ) +{ + bool changed = m_bDropMenuButtonStyle != state; + m_bDropMenuButtonStyle = state; + if ( !changed ) + return; + + if ( state ) + { + m_pDropMenuImage = new TextImage( "u" ); + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + m_pDropMenuImage->SetFont(pScheme->GetFont("Marlett", IsProportional())); + // m_pDropMenuImage->SetContentAlignment(Label::a_west); + // m_pDropMenuImage->SetTextInset(3, 0); + m_nImageIndex = AddImage( m_pDropMenuImage, 0 ); + } + else + { + ResetToSimpleTextImage(); + delete m_pDropMenuImage; + m_pDropMenuImage = NULL; + m_nImageIndex = -1; + } +} + +void MenuButton::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_pDropMenuImage ) + { + SetImageAtIndex( 1, m_pDropMenuImage, 0 ); + } +} + + +void MenuButton::PerformLayout() +{ + BaseClass::PerformLayout(); + if ( !IsDropMenuButtonStyle() ) + return; + + Assert( m_nImageIndex >= 0 ); + if ( m_nImageIndex < 0 || !m_pDropMenuImage ) + return; + + int w, h; + GetSize( w, h ); + + int contentW, contentH; + m_pDropMenuImage->ResizeImageToContent(); + m_pDropMenuImage->GetContentSize( contentW, contentH ); + + SetImageBounds( m_nImageIndex, w - contentW - 2, contentW ); +} + +bool MenuButton::IsDropMenuButtonStyle() const +{ + return m_bDropMenuButtonStyle; +} + +void MenuButton::Paint(void) +{ + BaseClass::Paint(); + + if ( !IsDropMenuButtonStyle() ) + return; + + int contentW, contentH; + m_pDropMenuImage->GetContentSize( contentW, contentH ); + m_pDropMenuImage->SetColor( IsEnabled() ? GetButtonFgColor() : GetDisabledFgColor1() ); + + int drawX = GetWide() - contentW - 2; + + surface()->DrawSetColor( IsEnabled() ? GetButtonFgColor() : GetDisabledFgColor1() ); + surface()->DrawFilledRect( drawX, 3, drawX + 1, GetTall() - 3 ); +} + +void MenuButton::OnCursorMoved( int x, int y ) +{ + BaseClass::OnCursorMoved( x, y ); + + if ( !IsDropMenuButtonStyle() ) + return; + + int contentW, contentH; + m_pDropMenuImage->GetContentSize( contentW, contentH ); + int drawX = GetWide() - contentW - 2; + if ( x <= drawX || !OnCheckMenuItemCount() ) + { + SetButtonActivationType(ACTIVATE_ONPRESSEDANDRELEASED); + SetUseCaptureMouse(true); + } + else + { + SetButtonActivationType(ACTIVATE_ONPRESSED); + SetUseCaptureMouse(false); + } +} + +Menu *MenuButton::GetMenu() +{ + Assert( m_pMenu ); + return m_pMenu; +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/MenuItem.cpp b/vgui2/vgui_controls/MenuItem.cpp new file mode 100644 index 0000000..209ec36 --- /dev/null +++ b/vgui2/vgui_controls/MenuItem.cpp @@ -0,0 +1,647 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/IScheme.h> +#include <vgui/IVGui.h> +#include "vgui/ISurface.h" +#include <KeyValues.h> + +#include <vgui_controls/Controls.h> +#include <vgui_controls/Menu.h> +#include <vgui_controls/MenuItem.h> +#include <vgui_controls/TextImage.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Check box image +//----------------------------------------------------------------------------- +class MenuItemCheckImage : public TextImage +{ +public: + MenuItemCheckImage(MenuItem *item) : TextImage( "g" ) + { + _menuItem = item; + + SetSize(20, 13); + } + + virtual void Paint() + { + DrawSetTextFont(GetFont()); + + // draw background + DrawSetTextColor(_menuItem->GetBgColor()); + DrawPrintChar(0, 0, 'g'); + + // draw check + if (_menuItem->IsChecked()) + { + if (_menuItem->IsEnabled()) + { + DrawSetTextColor(_menuItem->GetButtonFgColor()); + DrawPrintChar(0, 2, 'a'); + } + else if (!_menuItem->IsEnabled()) + { + // draw disabled version, with embossed look + // offset image + DrawSetTextColor(_menuItem->GetDisabledFgColor1()); + DrawPrintChar(1, 3, 'a'); + + // overlayed image + DrawSetTextColor(_menuItem->GetDisabledFgColor2()); + DrawPrintChar(0, 2, 'a'); + } + } + } + +private: + MenuItem *_menuItem; +}; + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( MenuItem, MenuItem ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input: parent - the parent of this menu item, usually a menu +// text - the name of the menu item as it appears in the menu +// cascadeMenu - if this item triggers the opening of a cascading menu +// provide a pointer to it. +// MenuItems cannot be both checkable and trigger a cascade menu. +//----------------------------------------------------------------------------- +MenuItem::MenuItem(Menu *parent, const char *panelName, const char *text, Menu *cascadeMenu, bool checkable) : Button(parent, panelName, text) +{ + m_pCascadeMenu = cascadeMenu; + m_bCheckable = checkable; + SetButtonActivationType(ACTIVATE_ONRELEASED); + m_pUserData = NULL; + m_pCurrentKeyBinding = NULL; + + // only one arg should be passed in. + Assert (!(cascadeMenu && checkable)); + + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input: parent - the parent of this menu item, usually a menu +// text - the name of the menu item as it appears in the menu +// cascadeMenu - if this item triggers the opening of a cascading menu +// provide a pointer to it. +// MenuItems cannot be both checkable and trigger a cascade menu. +//----------------------------------------------------------------------------- +MenuItem::MenuItem(Menu *parent, const char *panelName, const wchar_t *wszText, Menu *cascadeMenu, bool checkable) : Button(parent, panelName, wszText) +{ + m_pCascadeMenu = cascadeMenu; + m_bCheckable = checkable; + SetButtonActivationType(ACTIVATE_ONRELEASED); + m_pUserData = NULL; + m_pCurrentKeyBinding = NULL; + + // only one arg should be passed in. + Assert (!(cascadeMenu && checkable)); + + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +MenuItem::~MenuItem() +{ + delete m_pCascadeMenu; + delete m_pCascadeArrow; + delete m_pCheck; + if (m_pUserData) + { + m_pUserData->deleteThis(); + } + delete m_pCurrentKeyBinding; +} + +//----------------------------------------------------------------------------- +// Purpose: Basic initializer +//----------------------------------------------------------------------------- +void MenuItem::Init( void ) +{ + m_pCascadeArrow = NULL; + m_pCheck = NULL; + + if (m_pCascadeMenu) + { + m_pCascadeMenu->SetParent(this); + m_pCascadeArrow = new TextImage("4"); // this makes a right pointing arrow. + + m_pCascadeMenu->AddActionSignalTarget(this); + } + else if (m_bCheckable) + { + // move the text image over so we have room for the check + SetTextImageIndex(1); + m_pCheck = new MenuItemCheckImage(this); + SetImageAtIndex(0, m_pCheck, CHECK_INSET); + SetChecked(false); + } + + SetButtonBorderEnabled( false ); + SetUseCaptureMouse( false ); + SetContentAlignment( Label::a_west ); + + // note menus handle all the sizing of menuItem panels +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Menu *MenuItem::GetParentMenu() +{ + return (Menu *)GetParent(); +} + +//----------------------------------------------------------------------------- +// Purpose: Layout the Textimage and the Arrow part of the menuItem +//----------------------------------------------------------------------------- +void MenuItem::PerformLayout() +{ + Button::PerformLayout(); + // make the arrow image match the button layout. + // this will make it brighten and dim like the menu buttons. + if (m_pCascadeArrow) + { + m_pCascadeArrow->SetColor(GetButtonFgColor()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Close the cascading menu if we have one. +//----------------------------------------------------------------------------- +void MenuItem::CloseCascadeMenu() +{ + if (m_pCascadeMenu) + { + if (m_pCascadeMenu->IsVisible()) + { + m_pCascadeMenu->SetVisible(false); + } + // disarm even if menu wasn't visible! + SetArmed(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle cursor moving in a menuItem. +//----------------------------------------------------------------------------- +void MenuItem::OnCursorMoved(int x, int y) +{ + // if menu is in keymode and we moved the mouse + // highlight this item + if (GetParentMenu()->GetMenuMode() == Menu::KEYBOARD) + { + OnCursorEntered(); + } + + // chain up to parent + CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse cursor entering a menuItem. +//----------------------------------------------------------------------------- +void MenuItem::OnCursorEntered() +{ + // post a message to the parent menu. + // forward the message on to the parent of this menu. + KeyValues *msg = new KeyValues ("CursorEnteredMenuItem"); + // tell the parent this menuitem is the one that was entered so it can highlight it + msg->SetInt("VPanel", GetVPanel()); + + ivgui()->PostMessage(GetVParent(), msg, NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse cursor exiting a menuItem. +//----------------------------------------------------------------------------- +void MenuItem::OnCursorExited() +{ + // post a message to the parent menu. + // forward the message on to the parent of this menu. + KeyValues *msg = new KeyValues ("CursorExitedMenuItem"); + // tell the parent this menuitem is the one that was entered so it can unhighlight it + msg->SetInt("VPanel", GetVPanel()); + + ivgui()->PostMessage(GetVParent(), msg, NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse cursor exiting a menuItem. +//----------------------------------------------------------------------------- +void MenuItem::OnKeyCodeReleased(KeyCode code) +{ + if (GetParentMenu()->GetMenuMode() == Menu::KEYBOARD && m_pCascadeMenu) + { + return; + } + // only disarm if we are not opening a cascading menu using keys. + Button::OnKeyCodeReleased(code); +} + +//----------------------------------------------------------------------------- +// Purpose: Highlight a menu item +// Menu item buttons highlight if disabled, but won't activate. +//----------------------------------------------------------------------------- +void MenuItem::ArmItem() +{ + // close all other menus + GetParentMenu()->CloseOtherMenus(this); + // arm the menuItem. + Button::SetArmed(true); + + // When you have a submenu with no scroll bar the menu + // border will not be drawn correctly. This fixes it. + Menu *parent = GetParentMenu(); + if ( parent ) + { + parent->ForceCalculateWidth(); + } + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Unhighlight a menu item +//----------------------------------------------------------------------------- +void MenuItem::DisarmItem() +{ + // normal behaviour is that the button becomes unarmed + // do not unarm if there is a cascading menu. CloseCascadeMenu handles this. + // and the menu handles it since we close at different times depending + // on whether menu is handling mouse or key events. + if (!m_pCascadeMenu) + { + Button::OnCursorExited(); + } + + // When you have a submenu with no scroll bar the menu + // border will not be drawn correctly. This fixes it. + Menu *parent = GetParentMenu(); + if ( parent ) + { + parent->ForceCalculateWidth(); + } + Repaint(); +} + +bool MenuItem::IsItemArmed() +{ + return Button::IsArmed(); +} + +//----------------------------------------------------------------------------- +// Purpose: Pass kill focus events up to parent, This will tell all panels +// in the hierarchy to hide themselves, and enables cascading menus to +// all disappear on selecting an item at the end of the tree. +//----------------------------------------------------------------------------- +void MenuItem::OnKillFocus() +{ + GetParentMenu()->OnKillFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: fire the menu item as if it has been selected and +// Tell the owner that it is closing +//----------------------------------------------------------------------------- +void MenuItem::FireActionSignal() +{ + // cascading menus items don't trigger the parent menu to disappear + // (they trigger the cascading menu to open/close when cursor is moved over/off them) + if (!m_pCascadeMenu) + { + KeyValues *kv = new KeyValues("MenuItemSelected"); + kv->SetPtr("panel", this); + ivgui()->PostMessage(GetVParent(), kv, GetVPanel()); + + // ivgui()->PostMessage(GetVParent(), new KeyValues("MenuItemSelected"), GetVPanel()); + Button::FireActionSignal(); + // toggle the check next to the item if it is checkable + if (m_bCheckable) + { + SetChecked( !m_bChecked ); + } + } + else + { + // if we are in keyboard mode, open the child menu. + if (GetParentMenu()->GetMenuMode() == Menu::KEYBOARD) + { + OpenCascadeMenu(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Opens the cascading menu. +//----------------------------------------------------------------------------- +void MenuItem::OpenCascadeMenu() +{ + if (m_pCascadeMenu) + { + // perform layout on menu, this way it will open in the right spot + // if the window's been moved + m_pCascadeMenu->PerformLayout(); + m_pCascadeMenu->SetVisible(true); + m_pCascadeMenu->MoveToFront(); + } +} + +//----------------------------------------------------------------------------- +// Purpse: Return true if this item triggers a cascading menu +//----------------------------------------------------------------------------- +bool MenuItem::HasMenu() +{ + return (m_pCascadeMenu != NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: Apply the resource scheme to the menu. +//----------------------------------------------------------------------------- +void MenuItem::ApplySchemeSettings(IScheme *pScheme) +{ + // chain back first + Button::ApplySchemeSettings(pScheme); + + // get color settings + SetDefaultColor(GetSchemeColor("Menu.TextColor", GetFgColor(), pScheme), GetSchemeColor("Menu.BgColor", GetBgColor(), pScheme)); + SetArmedColor(GetSchemeColor("Menu.ArmedTextColor", GetFgColor(), pScheme), GetSchemeColor("Menu.ArmedBgColor", GetBgColor(), pScheme)); + SetDepressedColor(GetSchemeColor("Menu.ArmedTextColor", GetFgColor(), pScheme), GetSchemeColor("Menu.ArmedBgColor", GetBgColor(), pScheme)); + + SetTextInset(atoi(pScheme->GetResourceString("Menu.TextInset")), 0); + + // reload images since applyschemesettings in label wipes them out. + if ( m_pCascadeArrow ) + { + m_pCascadeArrow->SetFont(pScheme->GetFont("Marlett", IsProportional() )); + m_pCascadeArrow->ResizeImageToContent(); + AddImage(m_pCascadeArrow, 0); + } + else if (m_bCheckable) + { + ( static_cast<MenuItemCheckImage *>(m_pCheck) )->SetFont( pScheme->GetFont("Marlett", IsProportional())); + SetImageAtIndex(0, m_pCheck, CHECK_INSET); + ( static_cast<MenuItemCheckImage *>(m_pCheck) )->ResizeImageToContent(); + } + + if ( m_pCurrentKeyBinding ) + { + m_pCurrentKeyBinding->SetFont(pScheme->GetFont("Default", IsProportional() )); + m_pCurrentKeyBinding->ResizeImageToContent(); + } + + // Have the menu redo the layout + // Get the parent to resize + Menu * parent = GetParentMenu(); + if ( parent ) + { + parent->ForceCalculateWidth(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Return the size of the text portion of the label. +// for normal menu items this is the same as the label size, but for +// cascading menus it gives you the size of the text portion only, without +// the arrow. +//----------------------------------------------------------------------------- +void MenuItem::GetTextImageSize(int &wide, int &tall) +{ + GetTextImage()->GetSize(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the size of the text portion of the label. +// For normal menu items this is the same as the label size, but for +// cascading menus it sizes textImage portion only, without +// the arrow. +//----------------------------------------------------------------------------- +void MenuItem::SetTextImageSize(int wide, int tall) +{ + GetTextImage()->SetSize(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the size of the arrow portion of the label. +// If the menuItem is not a cascading menu, 0 is returned. +//----------------------------------------------------------------------------- +void MenuItem::GetArrowImageSize(int &wide, int &tall) +{ + wide = 0, tall = 0; + if (m_pCascadeArrow) + { + m_pCascadeArrow->GetSize(wide, tall); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the size of the check portion of the label. +//----------------------------------------------------------------------------- +void MenuItem::GetCheckImageSize(int &wide, int &tall) +{ + wide = 0, tall = 0; + if (m_pCheck) + { + // resize the image to the contents size + ( static_cast<MenuItemCheckImage *>(m_pCheck) )->ResizeImageToContent(); + m_pCheck->GetSize(wide, tall); + + // include the inset for the check, since nobody but us know about the inset + wide += CHECK_INSET; + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return a the menu that this menuItem contains +// This is useful when the parent menu's commands must be +// sent through all menus that are open as well (like hotkeys) +//----------------------------------------------------------------------------- +Menu *MenuItem::GetMenu() +{ + return m_pCascadeMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the border style for the button. Menu items have no border so +// return null. +//----------------------------------------------------------------------------- +IBorder *MenuItem::GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the menu to key mode if a child menu goes into keymode +//----------------------------------------------------------------------------- +void MenuItem::OnKeyModeSet() +{ + // send the message to this parent in case this is a cascading menu + ivgui()->PostMessage(GetVParent(), new KeyValues("KeyModeSet"), GetVPanel()); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return if this menuitem is checkable or not +// This is used by menus to perform the layout properly. +//----------------------------------------------------------------------------- +bool MenuItem::IsCheckable() +{ + return m_bCheckable; +} + +//----------------------------------------------------------------------------- +// Purpose: Return if this menuitem is checked or not +//----------------------------------------------------------------------------- +bool MenuItem::IsChecked() +{ + return m_bChecked; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the checked state of a checkable menuitem +// Does nothing if item is not checkable +//----------------------------------------------------------------------------- +void MenuItem::SetChecked(bool state) +{ + if (m_bCheckable) + { + m_bChecked = state; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool MenuItem::CanBeDefaultButton(void) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *MenuItem::GetUserData() +{ + if ( HasMenu() ) + { + return m_pCascadeMenu->GetItemUserData( m_pCascadeMenu->GetActiveItem() ); + } + else + { + return m_pUserData; + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets the user data +//----------------------------------------------------------------------------- +void MenuItem::SetUserData(const KeyValues *kv) +{ + if (m_pUserData) + { + m_pUserData->deleteThis(); + m_pUserData = NULL; + } + + if ( kv ) + { + m_pUserData = kv->MakeCopy(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Passing in NULL removes this object +// Input : *keyName - +//----------------------------------------------------------------------------- +void MenuItem::SetCurrentKeyBinding( char const *keyName ) +{ + if ( !keyName ) + { + delete m_pCurrentKeyBinding; + m_pCurrentKeyBinding = NULL; + return; + } + + if ( !m_pCurrentKeyBinding ) + { + m_pCurrentKeyBinding = new TextImage( keyName ); + } + else + { + char curtext[ 256 ]; + m_pCurrentKeyBinding->GetText( curtext, sizeof( curtext ) ); + if ( !Q_strcmp( curtext, keyName ) ) + return; + + m_pCurrentKeyBinding->SetText( keyName ); + } + + InvalidateLayout( false, true ); +} + +#define KEYBINDING_INSET 5 + +void MenuItem::Paint() +{ + BaseClass::Paint(); + if ( !m_pCurrentKeyBinding ) + return; + + int w, h; + GetSize( w, h ); + int iw, ih; + m_pCurrentKeyBinding->GetSize( iw, ih ); + + int x = w - iw - KEYBINDING_INSET; + int y = ( h - ih ) / 2; + + if ( IsEnabled() ) + { + m_pCurrentKeyBinding->SetPos( x, y ); + m_pCurrentKeyBinding->SetColor( GetButtonFgColor() ); + m_pCurrentKeyBinding->Paint(); + } + else + { + m_pCurrentKeyBinding->SetPos( x + 1 , y + 1 ); + m_pCurrentKeyBinding->SetColor( GetDisabledFgColor1() ); + m_pCurrentKeyBinding->Paint(); + + surface()->DrawFlushText(); + + m_pCurrentKeyBinding->SetPos( x, y ); + m_pCurrentKeyBinding->SetColor( GetDisabledFgColor2() ); + m_pCurrentKeyBinding->Paint(); + } +} + +void MenuItem::GetContentSize( int& cw, int &ch ) +{ + BaseClass::GetContentSize( cw, ch ); + if ( !m_pCurrentKeyBinding ) + return; + + int iw, ih; + m_pCurrentKeyBinding->GetSize( iw, ih ); + + cw += iw + KEYBINDING_INSET; + ch = max( ch, ih ); +} diff --git a/vgui2/vgui_controls/MessageBox.cpp b/vgui2/vgui_controls/MessageBox.cpp new file mode 100644 index 0000000..e825e6b --- /dev/null +++ b/vgui2/vgui_controls/MessageBox.cpp @@ -0,0 +1,395 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/ISurface.h> +#include <KeyValues.h> +#include <vgui/IInput.h> + +#include <vgui_controls/Button.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/MessageBox.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +vgui::Panel *MessageBox_Factory() +{ + return new MessageBox("MessageBox", "MessageBoxText"); +} + +DECLARE_BUILD_FACTORY_CUSTOM( MessageBox, MessageBox_Factory ); +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +MessageBox::MessageBox(const char *title, const char *text, Panel *parent) : Frame(parent, NULL, false) +{ + SetTitle(title, true); + m_pMessageLabel = new Label(this, NULL, text); + + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +MessageBox::MessageBox(const wchar_t *wszTitle, const wchar_t *wszText, Panel *parent) : Frame(parent, NULL, false) +{ + SetTitle(wszTitle, true); + m_pMessageLabel = new Label(this, NULL, wszText); + + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor Helper +//----------------------------------------------------------------------------- +void MessageBox::Init() +{ + SetDeleteSelfOnClose(true); + m_pFrameOver = NULL; + m_bShowMessageBoxOverCursor = false; + + SetMenuButtonResponsive(false); + SetMinimizeButtonVisible(false); + SetCloseButtonVisible(false); + SetSizeable(false); + + m_pOkButton = new Button(this, NULL, "#MessageBox_OK"); + m_pOkButton->SetCommand( "OnOk" ); + m_pOkButton->AddActionSignalTarget(this); + + m_pCancelButton = new Button(this, NULL, "#MessageBox_Cancel"); + m_pCancelButton->SetCommand( "OnCancel" ); + m_pCancelButton->AddActionSignalTarget(this); + m_pCancelButton->SetVisible( false ); + + m_OkCommand = m_CancelCommand = NULL; + m_bNoAutoClose = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +MessageBox::~MessageBox() +{ + if ( m_OkCommand ) + { + m_OkCommand->deleteThis(); + } + if ( m_CancelCommand ) + { + m_CancelCommand->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Shows the message box over the cursor +//----------------------------------------------------------------------------- +void MessageBox::ShowMessageBoxOverCursor( bool bEnable ) +{ + m_bShowMessageBoxOverCursor = bEnable; +} + + +//----------------------------------------------------------------------------- +// Purpose: size the message label properly +//----------------------------------------------------------------------------- +void MessageBox::OnCommand( const char *pCommand ) +{ + if ( vgui::input()->GetAppModalSurface() == GetVPanel() ) + { + vgui::input()->ReleaseAppModalSurface(); + } + + if ( !Q_stricmp( pCommand, "OnOk" ) ) + { + if ( m_OkCommand ) + { + PostActionSignal(m_OkCommand->MakeCopy()); + } + } + else if ( !Q_stricmp( pCommand, "OnCancel" ) ) + { + if ( m_CancelCommand ) + { + PostActionSignal(m_CancelCommand->MakeCopy()); + } + } + + if ( !m_bNoAutoClose ) + { + OnShutdownRequest(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: size the message label properly +//----------------------------------------------------------------------------- +void MessageBox::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + int wide, tall; + m_pMessageLabel->GetContentSize(wide, tall); + m_pMessageLabel->SetSize(wide, tall); + + wide += 100; + tall += 100; + SetSize(wide, tall); + + if ( m_bShowMessageBoxOverCursor ) + { + PlaceUnderCursor(); + return; + } + + // move to the middle of the screen + if ( m_pFrameOver ) + { + int frameX, frameY; + int frameWide, frameTall; + m_pFrameOver->GetPos(frameX, frameY); + m_pFrameOver->GetSize(frameWide, frameTall); + + SetPos((frameWide - wide) / 2 + frameX, (frameTall - tall) / 2 + frameY); + } + else + { + int swide, stall; + surface()->GetScreenSize(swide, stall); + // put the dialog in the middle of the screen + SetPos((swide - wide) / 2, (stall - tall) / 2); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Put the message box into a modal state +// Does not suspend execution - use addActionSignal to get return value +//----------------------------------------------------------------------------- +void MessageBox::DoModal(Frame* pFrameOver) +{ + ShowWindow(pFrameOver); +/* + // move to the middle of the screen + // get the screen size + int wide, tall; + // get our dialog size + GetSize(wide, tall); + + if (pFrameOver) + { + int frameX, frameY; + int frameWide, frameTall; + pFrameOver->GetPos(frameX, frameY); + pFrameOver->GetSize(frameWide, frameTall); + + SetPos((frameWide - wide) / 2 + frameX, (frameTall - tall) / 2 + frameY); + } + else + { + int swide, stall; + surface()->GetScreenSize(swide, stall); + // put the dialog in the middle of the screen + SetPos((swide - wide) / 2, (stall - tall) / 2); + } + + SetVisible( true ); + SetEnabled( true ); + MoveToFront(); + + if (m_pOkButton->IsVisible()) + m_pOkButton->RequestFocus(); + else // handle message boxes with no button + RequestFocus(); +*/ + input()->SetAppModalSurface(GetVPanel()); +} + +void MessageBox::ShowWindow(Frame *pFrameOver) +{ + m_pFrameOver = pFrameOver; + + SetVisible( true ); + SetEnabled( true ); + MoveToFront(); + + if ( m_pOkButton->IsVisible() ) + { + m_pOkButton->RequestFocus(); + } + else // handle message boxes with no button + { + RequestFocus(); + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Put the text and OK buttons in correct place +//----------------------------------------------------------------------------- +void MessageBox::PerformLayout() +{ + int x, y, wide, tall; + GetClientArea(x, y, wide, tall); + wide += x; + tall += y; + + int boxWidth, boxTall; + GetSize(boxWidth, boxTall); + + int oldWide, oldTall; + m_pOkButton->GetSize(oldWide, oldTall); + + int btnWide, btnTall; + m_pOkButton->GetContentSize(btnWide, btnTall); + btnWide = max(oldWide, btnWide + 10); + btnTall = max(oldTall, btnTall + 10); + m_pOkButton->SetSize(btnWide, btnTall); + + int btnWide2 = 0, btnTall2 = 0; + if ( m_pCancelButton->IsVisible() ) + { + m_pCancelButton->GetSize(oldWide, oldTall); + + m_pCancelButton->GetContentSize(btnWide2, btnTall2); + btnWide2 = max(oldWide, btnWide2 + 10); + btnTall2 = max(oldTall, btnTall2 + 10); + m_pCancelButton->SetSize(btnWide2, btnTall2); + } + + boxWidth = max(boxWidth, m_pMessageLabel->GetWide() + 100); + boxWidth = max(boxWidth, (btnWide + btnWide2) * 2 + 30); + SetSize(boxWidth, boxTall); + + GetSize(boxWidth, boxTall); + + m_pMessageLabel->SetPos((wide/2)-(m_pMessageLabel->GetWide()/2) + x, y + 5 ); + if ( !m_pCancelButton->IsVisible() ) + { + m_pOkButton->SetPos((wide/2)-(m_pOkButton->GetWide()/2) + x, tall - m_pOkButton->GetTall() - 15); + } + else + { + m_pOkButton->SetPos((wide/4)-(m_pOkButton->GetWide()/2) + x, tall - m_pOkButton->GetTall() - 15); + m_pCancelButton->SetPos((3*wide/4)-(m_pOkButton->GetWide()/2) + x, tall - m_pOkButton->GetTall() - 15); + } + + BaseClass::PerformLayout(); + GetSize(boxWidth, boxTall); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a string command to be sent when the OK button is pressed. +//----------------------------------------------------------------------------- +void MessageBox::SetCommand(const char *command) +{ + if (m_OkCommand) + { + m_OkCommand->deleteThis(); + } + m_OkCommand = new KeyValues("Command", "command", command); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the command +//----------------------------------------------------------------------------- +void MessageBox::SetCommand(KeyValues *command) +{ + if (m_OkCommand) + { + m_OkCommand->deleteThis(); + } + m_OkCommand = command; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void MessageBox::OnShutdownRequest() +{ + // Shutdown the dialog + PostMessage(this, new KeyValues("Close")); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the visibility of the OK button. +//----------------------------------------------------------------------------- +void MessageBox::SetOKButtonVisible(bool state) +{ + m_pOkButton->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the Text on the OK button +//----------------------------------------------------------------------------- +void MessageBox::SetOKButtonText(const char *buttonText) +{ + m_pOkButton->SetText(buttonText); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the Text on the OK button +//----------------------------------------------------------------------------- +void MessageBox::SetOKButtonText(const wchar_t *wszButtonText) +{ + m_pOkButton->SetText(wszButtonText); + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Cancel button (off by default) +//----------------------------------------------------------------------------- +void MessageBox::SetCancelButtonVisible(bool state) +{ + m_pCancelButton->SetVisible(state); + InvalidateLayout(); +} + +void MessageBox::SetCancelButtonText(const char *buttonText) +{ + m_pCancelButton->SetText(buttonText); + InvalidateLayout(); +} + +void MessageBox::SetCancelButtonText(const wchar_t *wszButtonText) +{ + m_pCancelButton->SetText(wszButtonText); + InvalidateLayout(); +} + +void MessageBox::SetCancelCommand( KeyValues *command ) +{ + if (m_CancelCommand) + { + m_CancelCommand->deleteThis(); + } + m_CancelCommand = command; +} + + +//----------------------------------------------------------------------------- +// Purpose: Toggles visibility of the close box. +//----------------------------------------------------------------------------- +void MessageBox::DisableCloseButton(bool state) +{ + BaseClass::SetCloseButtonVisible(state); + m_bNoAutoClose = true; +} diff --git a/vgui2/vgui_controls/MessageDialog.cpp b/vgui2/vgui_controls/MessageDialog.cpp new file mode 100644 index 0000000..ee8262f --- /dev/null +++ b/vgui2/vgui_controls/MessageDialog.cpp @@ -0,0 +1,361 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "vgui_controls/MessageDialog.h" +#include "vgui/ILocalize.h" +#include "vgui/ISurface.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// CMessageDialog +//----------------------------------------------------------------------------- +CMessageDialog::CMessageDialog( vgui::Panel *pParent, const uint nType, const char *pTitle, const char *pMsg, const char *pCmdA, const char *pCmdB, vgui::Panel *pCreator, bool bShowActivity ) + : BaseClass( pParent, "MessageDialog" ) +{ + SetSize( 500, 200 ); + SetDeleteSelfOnClose( true ); + SetTitleBarVisible( false ); + SetCloseButtonVisible( false ); + SetSizeable( false ); + + m_pControlSettings = NULL; + m_pCreator = pCreator ? pCreator : pParent; + + m_nType = nType; + m_pTitle = new vgui::Label( this, "TitleLabel", pTitle ); + m_pMsg = new vgui::Label( this, "MessageLabel", pMsg ); + m_pAnimatingPanel = new vgui::AnimatingImagePanel( this, "AnimatingPanel" ); + + m_bShowActivity = bShowActivity; + + if ( nType & MD_SIMPLEFRAME ) + { + SetPaintBackgroundEnabled( true ); + m_pBackground = NULL; + } + else + { + m_pBackground = new vgui::ImagePanel( this, "Background" ); + if ( nType & MD_WARNING ) + { + m_pBackground->SetName( "WarningBackground" ); + } + else if ( nType & MD_ERROR ) + { + m_pBackground->SetName( "ErrorBackground" ); + } + } + + Q_memset( m_pCommands, 0, sizeof( m_pCommands ) ); + Q_memset( m_Buttons, 0, sizeof( m_Buttons ) ); + + if ( pCmdA ) + { + const int len = Q_strlen( pCmdA ) + 1; + m_pCommands[BTN_A] = (char*)malloc( len ); + Q_strncpy( m_pCommands[BTN_A], pCmdA, len ); + } + + if ( pCmdB ) + { + const int len = Q_strlen( pCmdB ) + 1; + m_pCommands[BTN_B] = (char*)malloc( len ); + Q_strncpy( m_pCommands[BTN_B], pCmdB, len ); + } + + // invalid until pressed + m_ButtonPressed = BTN_INVALID; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CMessageDialog::~CMessageDialog() +{ + if ( m_ButtonPressed != BTN_INVALID && ( m_nType & MD_COMMANDAFTERCLOSE ) ) + { + // caller wants the command sent after closure + m_pCreator->OnCommand( m_pCommands[m_ButtonPressed] ); + } + else if ( m_nType & MD_COMMANDONFORCECLOSE ) + { + // caller wants the command sent after closure + m_pCreator->OnCommand( m_pCommands[BTN_A] ); + } + + for ( int i = 0; i < MAX_BUTTONS; ++i ) + { + free( m_pCommands[i] ); + m_pCommands[i] = NULL; + + delete m_Buttons[i].pIcon; + delete m_Buttons[i].pText; + } + + delete m_pTitle; + m_pTitle = NULL; + + delete m_pMsg; + m_pMsg = NULL; + + delete m_pBackground; + m_pBackground = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the keyvalues to pass to LoadControlSettings() +//----------------------------------------------------------------------------- +void CMessageDialog::SetControlSettingsKeys( KeyValues *pKeys ) +{ + m_pControlSettings = pKeys; +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new button +//----------------------------------------------------------------------------- +void CMessageDialog::CreateButtonLabel( ButtonLabel_s *pButton, const char *pIcon, const char *pText ) +{ + pButton->nWide = m_ButtonIconLabelSpace; + pButton->bCreated = true; + + pButton->pIcon = new vgui::Label( this, "icon", pIcon ); + SETUP_PANEL( pButton->pIcon ); + pButton->pIcon->SetFont( m_hButtonFont ); + pButton->pIcon->SizeToContents(); + pButton->nWide += pButton->pIcon->GetWide(); + + pButton->pText = new vgui::Label( this, "text", pText ); + SETUP_PANEL( pButton->pText ); + pButton->pText->SetFont( m_hTextFont ); + pButton->pText->SizeToContents(); + pButton->pText->SetFgColor( m_ButtonTextColor ); + pButton->nWide += pButton->pText->GetWide(); +} + +//----------------------------------------------------------------------------- +// Purpose: Create and arrange the panel button labels according to the dialog type +//----------------------------------------------------------------------------- +void CMessageDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings( "resource/UI/MessageDialog.res", "GAME", m_pControlSettings ); + + m_hButtonFont = pScheme->GetFont( "GameUIButtons" ); + m_hTextFont = pScheme->GetFont( "MenuLarge" ); + + if ( m_nType & MD_OK ) + { + CreateButtonLabel( &m_Buttons[BTN_A], "#GameUI_Icons_A_BUTTON", "#GameUI_OK" ); + } + else if ( m_nType & MD_CANCEL ) + { + CreateButtonLabel( &m_Buttons[BTN_B], "#GameUI_Icons_B_BUTTON", "#GameUI_Cancel" ); + } + else if ( m_nType & MD_OKCANCEL ) + { + CreateButtonLabel( &m_Buttons[BTN_A], "#GameUI_Icons_A_BUTTON", "#GameUI_OK" ); + CreateButtonLabel( &m_Buttons[BTN_B], "#GameUI_Icons_B_BUTTON", "#GameUI_Cancel" ); + } + else if ( m_nType & MD_YESNO ) + { + CreateButtonLabel( &m_Buttons[BTN_A], "#GameUI_Icons_A_BUTTON", "#GameUI_Yes" ); + CreateButtonLabel( &m_Buttons[BTN_B], "#GameUI_Icons_B_BUTTON", "#GameUI_No" ); + } + + // count the buttons and add up their widths + int cButtons = 0; + int nTotalWide = 0; + for ( int i = 0; i < MAX_BUTTONS; ++i ) + { + if ( m_Buttons[i].bCreated ) + { + ++cButtons; + nTotalWide += m_Buttons[i].nWide; + } + } + + // make sure text and icons are center-aligned vertically with each other + int nButtonTall = vgui::surface()->GetFontTall( m_hButtonFont ); + int nTextTall = vgui::surface()->GetFontTall( m_hTextFont ); + int nVerticalAdjust = ( nButtonTall - nTextTall ) / 2; + + // position the buttons with even horizontal spacing + int xpos = 0; + int ypos = GetTall() - max( nButtonTall, nTextTall ) - m_ButtonMargin; + int nSpacing = ( GetWide() - nTotalWide ) / ( cButtons + 1 ); + for ( int i = 0; i < MAX_BUTTONS; ++i ) + { + if ( m_Buttons[i].bCreated ) + { + xpos += nSpacing; + m_Buttons[i].pIcon->SetPos( xpos, ypos ); + xpos += m_Buttons[i].pIcon->GetWide() + m_ButtonIconLabelSpace; + m_Buttons[i].pText->SetPos( xpos, ypos + nVerticalAdjust ); + xpos += m_Buttons[i].pText->GetWide(); + } + } + + m_clrNotSimpleBG = pScheme->GetColor( "MessageDialog.MatchmakingBG", Color( 200, 184, 151, 255 ) ); + m_clrNotSimpleBGBlack = pScheme->GetColor( "MessageDialog.MatchmakingBGBlack", Color( 52, 48, 55, 255 ) ); + + if ( !m_bShowActivity ) + { + if ( m_pAnimatingPanel ) + { + if ( m_pAnimatingPanel->IsVisible() ) + { + + m_pAnimatingPanel->SetVisible( false ); + } + + m_pAnimatingPanel->StopAnimation(); + } + } + else + { + if ( m_pAnimatingPanel ) + { + if ( !m_pAnimatingPanel->IsVisible() ) + { + m_pAnimatingPanel->SetVisible( true ); + } + + m_pAnimatingPanel->StartAnimation(); + } + } + + MoveToCenterOfScreen(); + + if ( m_bShowActivity && m_ActivityIndent ) + { + // If we're animating, we push our text label in, and reduce its width + int iX,iY,iW,iH; + m_pMsg->GetBounds( iX, iY, iW, iH ); + m_pMsg->SetBounds( iX + m_ActivityIndent, iY, max(0,iW-m_ActivityIndent), iH ); + } + + // Invalidate the scheme on our message label so that it recalculates + // its line breaks in case it was resized when we loaded our .res file. + m_pMsg->InvalidateLayout( false, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create and arrange the panel button labels according to the dialog type +//----------------------------------------------------------------------------- +void CMessageDialog::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + m_pTitle->SetFgColor( inResourceData->GetColor( "titlecolor" ) ); + + m_pMsg->SetFgColor( inResourceData->GetColor( "messagecolor" ) ); + + m_ButtonTextColor = inResourceData->GetColor( "buttontextcolor" ); + + m_FooterTall = inResourceData->GetInt( "footer_tall", 0 ); + m_ButtonMargin = inResourceData->GetInt( "button_margin", 25 ); + m_ButtonIconLabelSpace = inResourceData->GetInt( "button_separator", 10 ); + m_ActivityIndent = inResourceData->GetInt( "activity_indent", 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +uint CMessageDialog::GetType( void ) +{ + return m_nType; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMessageDialog::DoCommand( int button ) +{ + if ( button == BTN_INVALID || ( m_nType & MD_COMMANDONFORCECLOSE ) ) + { + return; + } + + if ( m_pCommands[button] ) + { + m_ButtonPressed = button; + if ( !( m_nType & MD_COMMANDAFTERCLOSE ) ) + { + // caller wants the command sent before closure + m_pCreator->OnCommand( m_pCommands[m_ButtonPressed] ); + } + m_pCreator->OnCommand( "ReleaseModalWindow" ); + Close(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMessageDialog::OnKeyCodePressed( vgui::KeyCode code ) +{ + if ( m_ButtonPressed != BTN_INVALID || GetAlpha() != 255 ) + { + // inhibit any further key activity or during transitions + return; + } + + switch ( GetBaseButtonCode( code ) ) + { + case KEY_XBUTTON_A: + case STEAMCONTROLLER_A: + DoCommand( BTN_A ); + break; + + case KEY_XBUTTON_B: + case STEAMCONTROLLER_B: + DoCommand( BTN_B ); + break; + + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMessageDialog::PaintBackground( void ) +{ + int wide, tall; + GetSize( wide, tall ); + + if ( !( m_nType & MD_SIMPLEFRAME ) ) + { + int nAlpha = GetAlpha(); + + m_clrNotSimpleBG[3] = nAlpha; + m_clrNotSimpleBGBlack[3] = nAlpha; + + DrawBox( 0, 0, wide, tall, m_clrNotSimpleBGBlack, 1.0f ); + DrawBox( 0, 0, wide, tall - m_FooterTall, m_clrNotSimpleBG, 1.0f ); + + return; + } + + Color col = GetBgColor(); + DrawBox( 0, 0, wide, tall, col, 1.0f ); + + // offset the inset by title + int titleX, titleY, titleWide, titleTall; + m_pTitle->GetBounds( titleX, titleY, titleWide, titleTall ); + int y = titleY + titleTall; + + // draw an inset + Color darkColor; + darkColor.SetColor( 0.70f * (float)col.r(), 0.70f * (float)col.g(), 0.70f * (float)col.b(), col.a() ); + vgui::surface()->DrawSetColor( darkColor ); + vgui::surface()->DrawFilledRect( 8, y, wide - 8, tall - 8 ); +} diff --git a/vgui2/vgui_controls/Panel.cpp b/vgui2/vgui_controls/Panel.cpp new file mode 100644 index 0000000..eda828c --- /dev/null +++ b/vgui2/vgui_controls/Panel.cpp @@ -0,0 +1,8924 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include <stdio.h> +#include <assert.h> +#include <utlvector.h> +#include <vstdlib/IKeyValuesSystem.h> +#include <ctype.h> // isdigit() + +#include <materialsystem/imaterial.h> + +#include <vgui/IBorder.h> +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/ILocalize.h> +#include <vgui/IVGui.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> + +#include <vgui_controls/Panel.h> +#include <vgui_controls/BuildGroup.h> +#include <vgui_controls/Tooltip.h> +#include <vgui_controls/PHandle.h> +#include <vgui_controls/Controls.h> +#include "vgui_controls/Menu.h" +#include "vgui_controls/MenuItem.h" + +#include "UtlSortVector.h" + +#include "tier1/utldict.h" +#include "tier1/utlbuffer.h" +#include "mempool.h" +#include "filesystem.h" +#include "tier0/icommandline.h" +#include "tier0/minidump.h" + +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +#define TRIPLE_PRESS_MSEC 300 + +const char *g_PinCornerStrings [] = +{ + "PIN_TOPLEFT", + "PIN_TOPRIGHT", + "PIN_BOTTOMLEFT", + "PIN_BOTTOMRIGHT", + + "PIN_CENTER_TOP", + "PIN_CENTER_RIGHT", + "PIN_CENTER_BOTTOM", + "PIN_CENTER_LEFT", +}; + +COMPILE_TIME_ASSERT( Panel::PIN_LAST == ARRAYSIZE( g_PinCornerStrings ) ); + +extern int GetBuildModeDialogCount(); + +static char *CopyString( const char *in ) +{ + if ( !in ) + return NULL; + + int len = strlen( in ); + char *n = new char[ len + 1 ]; + Q_strncpy( n, in, len + 1 ); + return n; +} + +#ifdef STAGING_ONLY +ConVar tf_strict_mouse_up_events( "tf_strict_mouse_up_events", "0", FCVAR_ARCHIVE, "Only allow Mouse-Release events to happens on panels we also Mouse-Downed in" ); +#endif + +// Temporary convar to help debug why the MvMVictoryMannUpPanel TabContainer is sometimes way off to the left. +ConVar tf_debug_tabcontainer( "tf_debug_tabcontainer", "0", FCVAR_HIDDEN, "Spew TabContainer dimensions." ); + +#if defined( VGUI_USEDRAGDROP ) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct vgui::DragDrop_t +{ + DragDrop_t() : + m_bDragEnabled( false ), + m_bShowDragHelper( true ), + m_bDropEnabled( false ), + m_bDragStarted( false ), + m_nDragStartTolerance( 8 ), + m_bDragging( false ), + m_lDropHoverTime( 0 ), + m_bDropMenuShown( false ), + m_bPreventChaining( false ) + { + m_nStartPos[ 0 ] = m_nStartPos[ 1 ] = 0; + m_nLastPos[ 0 ] = m_nLastPos[ 1 ] = 0; + } + + // Drag related data + bool m_bDragEnabled; + bool m_bShowDragHelper; + bool m_bDragging; + bool m_bDragStarted; + // How many pixels the dragged box must move before showing the outline rect... + int m_nDragStartTolerance; + int m_nStartPos[ 2 ]; + int m_nLastPos[ 2 ]; + CUtlVector< KeyValues * > m_DragData; + CUtlVector< PHandle > m_DragPanels; + + // Drop related data + bool m_bDropEnabled; + // A droppable panel can have a hover context menu, which will show up after m_flHoverContextTime of hovering + float m_flHoverContextTime; + + PHandle m_hCurrentDrop; + // Amount of time hovering over current drop target + long m_lDropHoverTime; + bool m_bDropMenuShown; + DHANDLE< Menu > m_hDropContextMenu; + + // Misc data + bool m_bPreventChaining; +}; + +//----------------------------------------------------------------------------- +// Purpose: Helper for painting to the full screen... +//----------------------------------------------------------------------------- +class CDragDropHelperPanel : public Panel +{ + DECLARE_CLASS_SIMPLE( CDragDropHelperPanel, Panel ); +public: + CDragDropHelperPanel(); + + virtual VPANEL IsWithinTraverse(int x, int y, bool traversePopups); + virtual void PostChildPaint(); + + void AddPanel( Panel *current ); + + void RemovePanel( Panel *search ); + +private: + struct DragHelperPanel_t + { + PHandle m_hPanel; + }; + + CUtlVector< DragHelperPanel_t > m_PaintList; +}; + +vgui::DHANDLE< CDragDropHelperPanel > s_DragDropHelper; +#endif + +#if defined( VGUI_USEKEYBINDINGMAPS ) + +BoundKey_t::BoundKey_t(): + isbuiltin( true ), + bindingname( 0 ), + keycode( KEY_NONE ), + modifiers( 0 ) +{ +} + +BoundKey_t::BoundKey_t( const BoundKey_t& src ) +{ + isbuiltin = src.isbuiltin; + bindingname = isbuiltin ? src.bindingname : CopyString( src.bindingname ); + keycode = src.keycode; + modifiers = src.modifiers; +} + +BoundKey_t& BoundKey_t::operator =( const BoundKey_t& src ) +{ + if ( this == &src ) + return *this; + isbuiltin = src.isbuiltin; + bindingname = isbuiltin ? src.bindingname : CopyString( src.bindingname ); + keycode = src.keycode; + modifiers = src.modifiers; + return *this; +} + + +BoundKey_t::~BoundKey_t() +{ + if ( !isbuiltin ) + { + delete[] bindingname; + } +} + +KeyBindingMap_t::KeyBindingMap_t() : + bindingname( 0 ), + func( 0 ), + helpstring( 0 ), + docstring( 0 ), + passive( false ) +{ +} + +KeyBindingMap_t::KeyBindingMap_t( const KeyBindingMap_t& src ) +{ + bindingname = src.bindingname; + helpstring = src.helpstring; + docstring = src.docstring; + + func = src.func; + passive = src.passive; +} + +KeyBindingMap_t::~KeyBindingMap_t() +{ +} + +class CKeyBindingsMgr +{ +public: + + CKeyBindingsMgr() : + m_Bindings( 0, 0, KeyBindingContextHandleLessFunc ), + m_nKeyBindingContexts( 0 ) + { + } + + struct KBContext_t + { + KBContext_t() : + m_KeyBindingsFile( UTL_INVAL_SYMBOL ), + m_KeyBindingsPathID( UTL_INVAL_SYMBOL ) + { + m_Handle = INVALID_KEYBINDINGCONTEXT_HANDLE; + } + + KBContext_t( const KBContext_t& src ) + { + m_Handle = src.m_Handle; + m_KeyBindingsFile = src.m_KeyBindingsFile; + m_KeyBindingsPathID = src.m_KeyBindingsPathID; + int c = src.m_Panels.Count(); + for ( int i = 0; i < c; ++i ) + { + m_Panels.AddToTail( src.m_Panels[ i ] ); + } + } + + KeyBindingContextHandle_t m_Handle; + CUtlSymbol m_KeyBindingsFile; + CUtlSymbol m_KeyBindingsPathID; + CUtlVector< Panel * > m_Panels; + }; + + static bool KeyBindingContextHandleLessFunc( const KBContext_t& lhs, const KBContext_t& rhs ) + { + return lhs.m_Handle < rhs.m_Handle; + } + + KeyBindingContextHandle_t CreateContext( char const *filename, char const *pathID ) + { + KBContext_t entry; + + entry.m_Handle = (KeyBindingContextHandle_t)++m_nKeyBindingContexts; + entry.m_KeyBindingsFile = filename; + if ( pathID ) + { + entry.m_KeyBindingsPathID = pathID; + } + else + { + entry.m_KeyBindingsPathID = UTL_INVAL_SYMBOL; + } + + m_Bindings.Insert( entry ); + + return entry.m_Handle; + } + + void AddPanelToContext( KeyBindingContextHandle_t handle, Panel *panel ) + { + if ( !panel->GetName() || !panel->GetName()[ 0 ] ) + { + Warning( "Can't add Keybindings Context for unnamed panels\n" ); + return; + } + + KBContext_t *entry = Find( handle ); + Assert( entry ); + if ( entry ) + { + int idx = entry->m_Panels.Find( panel ); + if ( idx == entry->m_Panels.InvalidIndex() ) + { + entry->m_Panels.AddToTail( panel ); + } + } + } + + void OnPanelDeleted( KeyBindingContextHandle_t handle, Panel *panel ) + { + KBContext_t *kb = Find( handle ); + if ( kb ) + { + kb->m_Panels.FindAndRemove( panel ); + } + } + + KBContext_t *Find( KeyBindingContextHandle_t handle ) + { + KBContext_t search; + search.m_Handle = handle; + int idx = m_Bindings.Find( search ); + if ( idx == m_Bindings.InvalidIndex() ) + { + return NULL; + } + return &m_Bindings[ idx ]; + } + + char const *GetKeyBindingsFile( KeyBindingContextHandle_t handle ) + { + KBContext_t *kb = Find( handle ); + if ( kb ) + { + return kb->m_KeyBindingsFile.String(); + } + Assert( 0 ); + return ""; + } + + char const *GetKeyBindingsFilePathID( KeyBindingContextHandle_t handle ) + { + KBContext_t *kb = Find( handle ); + if ( kb ) + { + return kb->m_KeyBindingsPathID.String(); + } + Assert( 0 ); + return NULL; + } + + int GetPanelsWithKeyBindingsCount( KeyBindingContextHandle_t handle ) + { + KBContext_t *kb = Find( handle ); + if ( kb ) + { + return kb->m_Panels.Count(); + } + Assert( 0 ); + return 0; + } + + //----------------------------------------------------------------------------- + // Purpose: static method + // Input : index - + // Output : Panel + //----------------------------------------------------------------------------- + Panel *GetPanelWithKeyBindings( KeyBindingContextHandle_t handle, int index ) + { + KBContext_t *kb = Find( handle ); + if ( kb ) + { + Assert( index >= 0 && index < kb->m_Panels.Count() ); + return kb->m_Panels[ index ]; + } + Assert( 0 ); + return 0; + } + + CUtlRBTree< KBContext_t, int > m_Bindings; + int m_nKeyBindingContexts; +}; + +static CKeyBindingsMgr g_KBMgr; + +//----------------------------------------------------------------------------- +// Purpose: Static method to allocate a context +// Input : - +// Output : KeyBindingContextHandle_t +//----------------------------------------------------------------------------- +KeyBindingContextHandle_t Panel::CreateKeyBindingsContext( char const *filename, char const *pathID /*=0*/ ) +{ + return g_KBMgr.CreateContext( filename, pathID ); +} + +COMPILE_TIME_ASSERT( ( MOUSE_MIDDLE - MOUSE_LEFT ) == 2 ); +Panel* Panel::m_sMousePressedPanels[] = { NULL, NULL, NULL }; + +//----------------------------------------------------------------------------- +// Purpose: static method +// Input : - +// Output : int +//----------------------------------------------------------------------------- +int Panel::GetPanelsWithKeyBindingsCount( KeyBindingContextHandle_t handle ) +{ + return g_KBMgr.GetPanelsWithKeyBindingsCount( handle ); +} + +//----------------------------------------------------------------------------- +// Purpose: static method +// Input : index - +// Output : Panel +//----------------------------------------------------------------------------- +Panel *Panel::GetPanelWithKeyBindings( KeyBindingContextHandle_t handle, int index ) +{ + return g_KBMgr.GetPanelWithKeyBindings( handle, index ); +} + + +//----------------------------------------------------------------------------- +// Returns the number of keybindings +//----------------------------------------------------------------------------- +int Panel::GetKeyMappingCount( ) +{ + int nCount = 0; + PanelKeyBindingMap *map = GetKBMap(); + while ( map ) + { + nCount += map->entries.Count(); + map = map->baseMap; + } + return nCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: static method. Reverts key bindings for all registered panels (panels with keybindings actually +// loaded from file +// Input : - +//----------------------------------------------------------------------------- +void Panel::RevertKeyBindings( KeyBindingContextHandle_t handle ) +{ + int c = GetPanelsWithKeyBindingsCount( handle ); + for ( int i = 0; i < c; ++i ) + { + Panel *kbPanel = GetPanelWithKeyBindings( handle, i ); + Assert( kbPanel ); + kbPanel->RevertKeyBindingsToDefault(); + } +} + +static void BufPrint( CUtlBuffer& buf, int level, char const *fmt, ... ) +{ + char string[ 2048 ]; + va_list argptr; + va_start( argptr, fmt ); + _vsnprintf( string, sizeof( string ) - 1, fmt, argptr ); + va_end( argptr ); + string[ sizeof( string ) - 1 ] = 0; + + while ( --level >= 0 ) + { + buf.Printf( " " ); + } + buf.Printf( "%s", string ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +//----------------------------------------------------------------------------- +void Panel::SaveKeyBindings( KeyBindingContextHandle_t handle ) +{ + char const *filename = g_KBMgr.GetKeyBindingsFile( handle ); + char const *pathID = g_KBMgr.GetKeyBindingsFilePathID( handle ); + + SaveKeyBindingsToFile( handle, filename, pathID ); +} + +//----------------------------------------------------------------------------- +// Purpose: static method. Saves key binding files out for all keybindings +// Input : - +//----------------------------------------------------------------------------- +void Panel::SaveKeyBindingsToFile( KeyBindingContextHandle_t handle, char const *filename, char const *pathID /*= 0*/ ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + BufPrint( buf, 0, "keybindings\n" ); + BufPrint( buf, 0, "{\n" ); + + int c = GetPanelsWithKeyBindingsCount( handle ); + for ( int i = 0; i < c; ++i ) + { + Panel *kbPanel = GetPanelWithKeyBindings( handle, i ); + Assert( kbPanel ); + if ( !kbPanel ) + continue; + + Assert( kbPanel->GetName() ); + Assert( kbPanel->GetName()[ 0 ] ); + + if ( !kbPanel->GetName() || !kbPanel->GetName()[ 0 ] ) + continue; + + BufPrint( buf, 1, "\"%s\"\n", kbPanel->GetName() ); + BufPrint( buf, 1, "{\n" ); + + kbPanel->SaveKeyBindingsToBuffer( 2, buf ); + + BufPrint( buf, 1, "}\n" ); + } + + BufPrint( buf, 0, "}\n" ); + + if ( g_pFullFileSystem->FileExists( filename, pathID ) && + !g_pFullFileSystem->IsFileWritable( filename, pathID ) ) + { + Warning( "Panel::SaveKeyBindings '%s' is read-only!!!\n", filename ); + } + + FileHandle_t h = g_pFullFileSystem->Open( filename, "wb", pathID ); + if ( FILESYSTEM_INVALID_HANDLE != h ) + { + g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), h ); + g_pFullFileSystem->Close( h ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// *panelOfInterest - +//----------------------------------------------------------------------------- +void Panel::LoadKeyBindingsForOnePanel( KeyBindingContextHandle_t handle, Panel *panelOfInterest ) +{ + if ( !panelOfInterest ) + return; + if ( !panelOfInterest->GetName() ) + return; + if ( !panelOfInterest->GetName()[ 0 ] ) + return; + + char const *filename = g_KBMgr.GetKeyBindingsFile( handle ); + char const *pathID = g_KBMgr.GetKeyBindingsFilePathID( handle ); + + KeyValues *kv = new KeyValues( "keybindings" ); + if ( kv->LoadFromFile( g_pFullFileSystem, filename, pathID ) ) + { + int c = GetPanelsWithKeyBindingsCount( handle ); + for ( int i = 0; i < c; ++i ) + { + Panel *kbPanel = GetPanelWithKeyBindings( handle, i ); + Assert( kbPanel ); + + char const *panelName = kbPanel->GetName(); + if ( !panelName ) + { + continue; + } + + if ( Q_stricmp( panelOfInterest->GetName(), panelName ) ) + continue; + + KeyValues *subKey = kv->FindKey( panelName, false ); + if ( !subKey ) + { + Warning( "Panel::ReloadKeyBindings: Can't find entry for panel '%s'\n", panelName ); + continue; + } + + kbPanel->ParseKeyBindings( subKey ); + } + } + kv->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: static method. Loads all key bindings again +// Input : - +//----------------------------------------------------------------------------- + +void Panel::ReloadKeyBindings( KeyBindingContextHandle_t handle ) +{ + char const *filename = g_KBMgr.GetKeyBindingsFile( handle ); + char const *pathID = g_KBMgr.GetKeyBindingsFilePathID( handle ); + + KeyValues *kv = new KeyValues( "keybindings" ); + if ( kv->LoadFromFile( g_pFullFileSystem, filename, pathID ) ) + { + int c = GetPanelsWithKeyBindingsCount( handle ); + for ( int i = 0; i < c; ++i ) + { + Panel *kbPanel = GetPanelWithKeyBindings( handle, i ); + Assert( kbPanel ); + + char const *panelName = kbPanel->GetName(); + if ( !panelName ) + { + continue; + } + + KeyValues *subKey = kv->FindKey( panelName, false ); + if ( !subKey ) + { + Warning( "Panel::ReloadKeyBindings: Can't find entry for panel '%s'\n", panelName ); + continue; + } + + kbPanel->ParseKeyBindings( subKey ); + } + } + kv->deleteThis(); +} +#endif // VGUI_USEKEYBINDINGMAPS + +DECLARE_BUILD_FACTORY( Panel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Panel::Panel() +{ + Init(0, 0, 64, 24); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Panel::Panel(Panel *parent) +{ + Init(0, 0, 64, 24); + SetParent(parent); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Panel::Panel(Panel *parent, const char *panelName) +{ + Init(0, 0, 64, 24); + SetName(panelName); + SetParent(parent); + SetBuildModeEditable(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Panel::Panel( Panel *parent, const char *panelName, HScheme scheme ) +{ + Init(0, 0, 64, 24); + SetName(panelName); + SetParent(parent); + SetBuildModeEditable(true); + SetScheme( scheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup +//----------------------------------------------------------------------------- +void Panel::Init( int x, int y, int wide, int tall ) +{ + _panelName = NULL; + _tooltipText = NULL; + _pinToSibling = NULL; + m_hMouseEventHandler = NULL; + _pinCornerToSibling = PIN_TOPLEFT; + _pinToSiblingCorner = PIN_TOPLEFT; + + // get ourselves an internal panel + _vpanel = ivgui()->AllocPanel(); + ipanel()->Init(_vpanel, this); + + SetPos(x, y); + SetSize(wide, tall); + _flags.SetFlag( NEEDS_LAYOUT | NEEDS_SCHEME_UPDATE | NEEDS_DEFAULT_SETTINGS_APPLIED ); + _flags.SetFlag( AUTODELETE_ENABLED | PAINT_BORDER_ENABLED | PAINT_BACKGROUND_ENABLED | PAINT_ENABLED ); +#if defined( VGUI_USEKEYBINDINGMAPS ) + _flags.SetFlag( ALLOW_CHAIN_KEYBINDING_TO_PARENT ); +#endif + m_nPinDeltaX = m_nPinDeltaY = 0; + m_nResizeDeltaX = m_nResizeDeltaY = 0; + _autoResizeDirection = AUTORESIZE_NO; + _pinCorner = PIN_TOPLEFT; + _cursor = dc_arrow; + _border = NULL; + _buildGroup = UTLHANDLE_INVALID; + _tabPosition = 0; + m_iScheme = 0; + m_bIsSilent = false; + m_bParentNeedsCursorMoveEvents = false; + + _buildModeFlags = 0; // not editable or deletable in buildmode dialog by default + + m_pTooltips = NULL; + m_bToolTipOverridden = false; + + m_flAlpha = 255.0f; + m_nPaintBackgroundType = 0; + + //============================================================================= + // HPE_BEGIN: + // [tj] Default to rounding all corners (for draw style 2) + //============================================================================= + m_roundedCorners = PANEL_ROUND_CORNER_ALL; + //============================================================================= + // HPE_END + //============================================================================= + + m_nBgTextureId1 = -1; + m_nBgTextureId2 = -1; + m_nBgTextureId3 = -1; + m_nBgTextureId4 = -1; +#if defined( VGUI_USEDRAGDROP ) + m_pDragDrop = new DragDrop_t; + +#endif + + m_lLastDoublePressTime = 0L; + +#if defined( VGUI_USEKEYBINDINGMAPS ) + m_hKeyBindingsContext = INVALID_KEYBINDINGCONTEXT_HANDLE; +#endif + + REGISTER_COLOR_AS_OVERRIDABLE( _fgColor, "fgcolor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _bgColor, "bgcolor_override" ); + + m_bIsConsoleStylePanel = false; + m_NavUp = NULL; + m_NavDown = NULL; + m_NavLeft = NULL; + m_NavRight = NULL; + m_NavToRelay = NULL; + m_NavActivate = NULL; + m_NavBack = NULL; + m_sNavUpName = NULL; + m_sNavDownName = NULL; + m_sNavLeftName = NULL; + m_sNavRightName = NULL; + m_sNavToRelayName = NULL; + m_sNavActivateName = NULL; + m_sNavBackName = NULL; + + m_PassUnhandledInput = true; + m_LastNavDirection = ND_NONE; + m_bWorldPositionCurrentFrame = false; + m_bForceStereoRenderToFrameBuffer = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Panel::~Panel() +{ + // @note Tom Bui: only cleanup if we've created it + if ( !m_bToolTipOverridden ) + { + if ( m_pTooltips ) + { + delete m_pTooltips; + } + } +#if defined( VGUI_USEKEYBINDINGMAPS ) + if ( IsValidKeyBindingsContext() ) + { + g_KBMgr.OnPanelDeleted( m_hKeyBindingsContext, this ); + } +#endif // VGUI_USEKEYBINDINGMAPS +#if defined( VGUI_USEDRAGDROP ) + if ( m_pDragDrop->m_bDragging ) + { + OnFinishDragging( false, (MouseCode)-1 ); + } +#endif // VGUI_USEDRAGDROP + + _flags.ClearFlag( AUTODELETE_ENABLED ); + _flags.SetFlag( MARKED_FOR_DELETION ); + + // remove panel from any list + SetParent((VPANEL)NULL); + + // Stop our children from pointing at us, and delete them if possible + while (ipanel()->GetChildCount(GetVPanel())) + { + VPANEL child = ipanel()->GetChild(GetVPanel(), 0); + if (ipanel()->IsAutoDeleteSet(child)) + { + ipanel()->DeletePanel(child); + } + else + { + ipanel()->SetParent(child, NULL); + } + } + + // delete VPanel + ivgui()->FreePanel(_vpanel); + // free our name + delete [] _panelName; + + if ( _tooltipText && _tooltipText[0] ) + { + delete [] _tooltipText; + } + + delete [] _pinToSibling; + + _vpanel = NULL; +#if defined( VGUI_USEDRAGDROP ) + delete m_pDragDrop; +#endif // VGUI_USEDRAGDROP + +#if defined( VGUI_PANEL_VERIFY_DELETES ) + // Zero out our vtbl pointer. This should hopefully help us catch bad guys using + // this panel after it has been deleted. + uintp *panel_vtbl = (uintp *)this; + *panel_vtbl = NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: fully construct this panel so its ready for use right now (i.e fonts loaded, colors set, default label text set, ...) +//----------------------------------------------------------------------------- +void Panel::MakeReadyForUse() +{ +// PerformApplySchemeSettings(); + UpdateSiblingPin(); + surface()->SolveTraverse( GetVPanel(), true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetName( const char *panelName ) +{ + // No change? + if ( _panelName && + panelName && + !Q_strcmp( _panelName, panelName ) ) + { + return; + } + + if (_panelName) + { + delete [] _panelName; + _panelName = NULL; + } + + if (panelName) + { + int len = Q_strlen(panelName) + 1; + _panelName = new char[ len ]; + Q_strncpy( _panelName, panelName, len ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns the given name of the panel +//----------------------------------------------------------------------------- +const char *Panel::GetName() +{ + if (_panelName) + return _panelName; + + return ""; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the name of the module that this instance of panel was compiled into +//----------------------------------------------------------------------------- +const char *Panel::GetModuleName() +{ + return vgui::GetControlsModuleName(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the classname of the panel (as specified in the panelmaps) +//----------------------------------------------------------------------------- +const char *Panel::GetClassName() +{ + // loop up the panel map name + PanelMessageMap *panelMap = GetMessageMap(); + if ( panelMap ) + { + return panelMap->pfnClassName(); + } + + return "Panel"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetPos(int x, int y) +{ + if ( !HushAsserts() ) + { + Assert( abs(x) < 32768 && abs(y) < 32768 ); + } + ipanel()->SetPos(GetVPanel(), x, y); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::GetPos(int &x, int &y) +{ + ipanel()->GetPos(GetVPanel(), x, y); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Panel::GetXPos() +{ + int x,y; + GetPos( x, y ); + return x; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Panel::GetYPos() +{ + int x,y; + GetPos( x, y ); + return y; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetSize(int wide, int tall) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s", __FUNCTION__, GetName() ); + Assert( abs(wide) < 32768 && abs(tall) < 32768 ); + ipanel()->SetSize(GetVPanel(), wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::GetSize(int &wide, int &tall) +{ + ipanel()->GetSize(GetVPanel(), wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetBounds(int x, int y, int wide, int tall) +{ + SetPos(x,y); + SetSize(wide,tall); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::GetBounds(int &x, int &y, int &wide, int &tall) +{ + GetPos(x, y); + GetSize(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: returns safe handle to parent +//----------------------------------------------------------------------------- +VPANEL Panel::GetVParent() +{ + if ( ipanel() ) + { + return ipanel()->GetParent(GetVPanel()); + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a controls version of a Panel pointer +//----------------------------------------------------------------------------- +Panel *Panel::GetParent() +{ + // get the parent and convert it to a Panel * + // this is OK, the hierarchy is guaranteed to be all from the same module, except for the root node + // the root node always returns NULL when a GetParent() is done so everything is OK + if ( ipanel() ) + { + VPANEL parent = ipanel()->GetParent(GetVPanel()); + if (parent) + { + Panel *pParent = ipanel()->GetPanel(parent, GetControlsModuleName()); + Assert(!pParent || !strcmp(pParent->GetModuleName(), GetControlsModuleName())); + return pParent; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Screen size change notification handler +//----------------------------------------------------------------------------- +void Panel::OnScreenSizeChanged(int nOldWide, int nOldTall) +{ + // post to all children + for (int i = 0; i < ipanel()->GetChildCount(GetVPanel()); i++) + { + VPANEL child = ipanel()->GetChild(GetVPanel(), i); + PostMessage(child, new KeyValues("OnScreenSizeChanged", "oldwide", nOldWide, "oldtall", nOldTall), NULL); + } + + // make any currently fullsize window stay fullsize + int x, y, wide, tall; + GetBounds(x, y, wide, tall); + int screenWide, screenTall; + surface()->GetScreenSize(screenWide, screenTall); + if (x == 0 && y == 0 && nOldWide == wide && tall == nOldTall) + { + // fullsize + surface()->GetScreenSize(wide, tall); + SetBounds(0, 0, wide, tall); + } + + // panel needs to re-get it's scheme settings + _flags.SetFlag( NEEDS_SCHEME_UPDATE ); + + // invalidate our settings + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetVisible(bool state) +{ + ipanel()->SetVisible(GetVPanel(), state); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Panel::IsVisible() +{ + if (ipanel()) + { + return ipanel()->IsVisible(GetVPanel()); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetEnabled(bool state) +{ + if (state != ipanel()->IsEnabled( GetVPanel())) + { + ipanel()->SetEnabled(GetVPanel(), state); + InvalidateLayout(false); + Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Panel::IsEnabled() +{ + return ipanel()->IsEnabled(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Panel::IsPopup() +{ + return ipanel()->IsPopup(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::Repaint() +{ + _flags.SetFlag( NEEDS_REPAINT ); + if (surface()) + { + surface()->Invalidate(GetVPanel()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::Think() +{ + if (IsVisible()) + { + // update any tooltips + if (m_pTooltips) + { + m_pTooltips->PerformLayout(); + } + if ( _flags.IsFlagSet( NEEDS_LAYOUT ) ) + { + InternalPerformLayout(); + } + } + + OnThink(); +} + +void Panel::OnChildSettingsApplied( KeyValues *pInResourceData, Panel *pChild ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s", __FUNCTION__, GetName() ); + + Panel* pParent = GetParent(); + if( pParent ) + { + pParent->OnChildSettingsApplied( pInResourceData, pChild ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::PaintTraverse( bool repaint, bool allowForce ) +{ + if ( m_bWorldPositionCurrentFrame ) + { + surface()->SolveTraverse( GetVPanel() ); + } + + if ( !IsVisible() ) + { + return; + } + + float oldAlphaMultiplier = surface()->DrawGetAlphaMultiplier(); + float newAlphaMultiplier = oldAlphaMultiplier * m_flAlpha * 1.0f/255.0f; + + if ( IsXbox() && !newAlphaMultiplier ) + { + // xbox optimization not suitable for pc + // xbox panels are compliant and can early out and not traverse their children + // when they have no opacity + return; + } + + if ( !repaint && + allowForce && + _flags.IsFlagSet( NEEDS_REPAINT ) ) + { + repaint = true; + _flags.ClearFlag( NEEDS_REPAINT ); + } + + VPANEL vpanel = GetVPanel(); + + bool bPushedViewport = false; + if( GetForceStereoRenderToFrameBuffer() ) + { + CMatRenderContextPtr pRenderContext( materials ); + if( pRenderContext->GetRenderTarget() ) + { + surface()->PushFullscreenViewport(); + bPushedViewport = true; + } + } + + int clipRect[4]; + ipanel()->GetClipRect( vpanel, clipRect[0], clipRect[1], clipRect[2], clipRect[3] ); + if ( ( clipRect[2] <= clipRect[0] ) || ( clipRect[3] <= clipRect[1] ) ) + { + repaint = false; + } + + // set global alpha + surface()->DrawSetAlphaMultiplier( newAlphaMultiplier ); + + bool bBorderPaintFirst = _border ? _border->PaintFirst() : false; + + // draw the border first if requested to + if ( bBorderPaintFirst && repaint && _flags.IsFlagSet( PAINT_BORDER_ENABLED ) && ( _border != null ) ) + { + // Paint the border over the background with no inset + surface()->PushMakeCurrent( vpanel, false ); + PaintBorder(); + surface()->PopMakeCurrent( vpanel ); + } + + if ( repaint ) + { + // draw the background with no inset + if ( _flags.IsFlagSet( PAINT_BACKGROUND_ENABLED ) ) + { + surface()->PushMakeCurrent( vpanel, false ); + PaintBackground(); + surface()->PopMakeCurrent( vpanel ); + } + + // draw the front of the panel with the inset + if ( _flags.IsFlagSet( PAINT_ENABLED ) ) + { + surface()->PushMakeCurrent( vpanel, true ); + Paint(); + surface()->PopMakeCurrent( vpanel ); + } + } + + // traverse and paint all our children + CUtlVector< VPANEL > &children = ipanel()->GetChildren( vpanel ); + int childCount = children.Count(); + for (int i = 0; i < childCount; i++) + { + VPANEL child = children[ i ]; + bool bVisible = ipanel()->IsVisible( child ); + + if ( surface()->ShouldPaintChildPanel( child ) ) + { + if ( bVisible ) + { + ipanel()->PaintTraverse( child, repaint, allowForce ); + } + } + else + { + // Invalidate the child panel so that it gets redrawn + surface()->Invalidate( child ); + + // keep traversing the tree, just don't allow anyone to paint after here + if ( bVisible ) + { + ipanel()->PaintTraverse( child, false, false ); + } + } + } + + // draw the border last + if ( repaint ) + { + if ( !bBorderPaintFirst && _flags.IsFlagSet( PAINT_BORDER_ENABLED ) && ( _border != null ) ) + { + // Paint the border over the background with no inset + surface()->PushMakeCurrent( vpanel, false ); + PaintBorder(); + surface()->PopMakeCurrent( vpanel ); + } + +#ifdef _DEBUG + // IsBuildGroupEnabled recurses up all the parents and ends up being very expensive as it wanders all over memory + if ( GetBuildModeDialogCount() && IsBuildGroupEnabled() ) //&& HasFocus() ) + { + // outline all selected panels + // outline all selected panels + CUtlVector<PHandle> *controlGroup = _buildGroup->GetControlGroup(); + for (int i=0; i < controlGroup->Size(); ++i) + { + surface()->PushMakeCurrent( ((*controlGroup)[i].Get())->GetVPanel(), false ); + ((*controlGroup)[i].Get())->PaintBuildOverlay(); + surface()->PopMakeCurrent( ((*controlGroup)[i].Get())->GetVPanel() ); + } + + _buildGroup->DrawRulers(); + } +#endif + + // All of our children have painted, etc, now allow painting in top of them + if ( _flags.IsFlagSet( POST_CHILD_PAINT_ENABLED ) ) + { + surface()->PushMakeCurrent( vpanel, false ); + PostChildPaint(); + surface()->PopMakeCurrent( vpanel ); + } + } + + surface()->DrawSetAlphaMultiplier( oldAlphaMultiplier ); + + surface()->SwapBuffers( vpanel ); + + if( bPushedViewport ) + { + surface()->PopFullscreenViewport(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::PaintBorder() +{ + _border->Paint(GetVPanel()); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::PaintBackground() +{ + int wide, tall; + GetSize( wide, tall ); + if ( m_SkipChild.Get() && m_SkipChild->IsVisible() ) + { + if ( GetPaintBackgroundType() == 2 ) + { + int cornerWide, cornerTall; + GetCornerTextureSize( cornerWide, cornerTall ); + + Color col = GetBgColor(); + DrawHollowBox( 0, 0, wide, tall, col, 1.0f ); + + wide -= 2 * cornerWide; + tall -= 2 * cornerTall; + + FillRectSkippingPanel( GetBgColor(), cornerWide, cornerTall, wide, tall, m_SkipChild.Get() ); + } + else + { + FillRectSkippingPanel( GetBgColor(), 0, 0, wide, tall, m_SkipChild.Get() ); + } + } + else + { + Color col = GetBgColor(); + + switch ( m_nPaintBackgroundType ) + { + default: + case 0: + { + surface()->DrawSetColor(col); + surface()->DrawFilledRect(0, 0, wide, tall); + } + break; + case 1: + { + DrawTexturedBox( 0, 0, wide, tall, col, 1.0f ); + } + break; + case 2: + { + DrawBox( 0, 0, wide, tall, col, 1.0f ); + } + break; + case 3: + { + DrawBoxFade( 0, 0, wide, tall, col, 1.0f, 255, 0, true ); + } + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::Paint() +{ + // empty on purpose + // PaintBackground is painted and default behavior is for Paint to do nothing +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::PostChildPaint() +{ + // Empty on purpose + // This is called if _postChildPaintEnabled is true and allows painting to + // continue on the surface after all of the panel's children have painted + // themselves. Allows drawing an overlay on top of the children, etc. +} + +//----------------------------------------------------------------------------- +// Purpose: Draws a black rectangle around the panel. +//----------------------------------------------------------------------------- +void Panel::PaintBuildOverlay() +{ + int wide,tall; + GetSize(wide,tall); + surface()->DrawSetColor(0, 0, 0, 255); + + surface()->DrawFilledRect(0,0,wide,2); //top + surface()->DrawFilledRect(0,tall-2,wide,tall); //bottom + surface()->DrawFilledRect(0,2,2,tall-2); //left + surface()->DrawFilledRect(wide-2,2,wide,tall-2); //right +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the panel's draw code will fully cover it's area +//----------------------------------------------------------------------------- +bool Panel::IsOpaque() +{ + // FIXME: Add code to account for the 'SkipChild' functionality in Frame + if ( IsVisible() && _flags.IsFlagSet( PAINT_BACKGROUND_ENABLED ) && ( _bgColor[3] == 255 ) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the settings are aligned to the right of the screen +//----------------------------------------------------------------------------- +bool Panel::IsRightAligned() +{ + return (_buildModeFlags & BUILDMODE_SAVE_XPOS_RIGHTALIGNED); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the settings are aligned to the bottom of the screen +//----------------------------------------------------------------------------- +bool Panel::IsBottomAligned() +{ + return (_buildModeFlags & BUILDMODE_SAVE_YPOS_BOTTOMALIGNED); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the parent +//----------------------------------------------------------------------------- +void Panel::SetParent(Panel *newParent) +{ + // Assert that the parent is from the same module as the child + // FIXME: !!! work out how to handle this properly! + // Assert(!newParent || !strcmp(newParent->GetModuleName(), GetControlsModuleName())); + + Panel* pCurrentParent = GetParent(); + if ( pCurrentParent ) + { + pCurrentParent->m_dictChidlren.Remove( GetName() ); + } + + if (newParent) + { + SetParent(newParent->GetVPanel()); + } + else + { + SetParent((VPANEL)NULL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetParent(VPANEL newParent) +{ + if (newParent) + { + ipanel()->SetParent(GetVPanel(), newParent); + } + else + { + ipanel()->SetParent(GetVPanel(), NULL); + } + + if (GetVParent() && !IsPopup()) + { + SetProportional(ipanel()->IsProportional(GetVParent())); + + // most of the time KBInput == parents kbinput + if (ipanel()->IsKeyBoardInputEnabled(GetVParent()) != IsKeyBoardInputEnabled()) + { + SetKeyBoardInputEnabled(ipanel()->IsKeyBoardInputEnabled(GetVParent())); + } + + if (ipanel()->IsMouseInputEnabled(GetVParent()) != IsMouseInputEnabled()) + { + SetMouseInputEnabled(ipanel()->IsMouseInputEnabled(GetVParent())); + } + } + + UpdateSiblingPin(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::OnChildAdded(VPANEL child) +{ + Assert( !_flags.IsFlagSet( IN_PERFORM_LAYOUT ) ); + Panel *pChild = ipanel()->GetPanel(child, GetControlsModuleName()); + if ( pChild ) + { + auto idx = m_dictChidlren.Insert( pChild->GetName() ); + m_dictChidlren[ idx ].Set( child ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: default message handler +//----------------------------------------------------------------------------- +void Panel::OnSizeChanged(int newWide, int newTall) +{ + InvalidateLayout(); // our size changed so force us to layout again +} + +//----------------------------------------------------------------------------- +// Purpose: sets Z ordering - lower numbers are always behind higher z's +//----------------------------------------------------------------------------- +void Panel::SetZPos(int z) +{ + ipanel()->SetZPos(GetVPanel(), z); +} + +//----------------------------------------------------------------------------- +// Purpose: sets Z ordering - lower numbers are always behind higher z's +//----------------------------------------------------------------------------- +int Panel::GetZPos() const +{ + return ( ipanel()->GetZPos( GetVPanel() ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: sets alpha modifier for panel and all child panels [0..255] +//----------------------------------------------------------------------------- +void Panel::SetAlpha(int alpha) +{ + m_flAlpha = alpha; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int Panel::GetAlpha() +{ + return (int)m_flAlpha; +} + +//----------------------------------------------------------------------------- +// Purpose: Moves the panel to the front of the z-order +//----------------------------------------------------------------------------- +void Panel::MoveToFront(void) +{ + // FIXME: only use ipanel() as per src branch? + if (IsPopup()) + { + surface()->BringToFront(GetVPanel()); + } + else + { + ipanel()->MoveToFront(GetVPanel()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates up the hierarchy looking for a particular parent +//----------------------------------------------------------------------------- +bool Panel::HasParent(VPANEL potentialParent) +{ + if (!potentialParent) + return false; + + return ipanel()->HasParent(GetVPanel(), potentialParent); +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the index of a child panel by string name +// Output : int - -1 if no panel of that name is found +//----------------------------------------------------------------------------- +int Panel::FindChildIndexByName(const char *childName) +{ + for (int i = 0; i < GetChildCount(); i++) + { + Panel *pChild = GetChild(i); + if (!pChild) + continue; + + if (!stricmp(pChild->GetName(), childName)) + { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a child panel by string name +// Output : Panel * - NULL if no panel of that name is found +//----------------------------------------------------------------------------- +Panel *Panel::FindChildByName(const char *childName, bool recurseDown) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s finding %s", __FUNCTION__, GetName(), childName ); + + auto idx = m_dictChidlren.Find( childName ); + if ( idx != m_dictChidlren.InvalidIndex() ) + { + Panel *pCachedChild = ipanel()->GetPanel( m_dictChidlren[ idx ], GetControlsModuleName() ); + + if ( !pCachedChild ) + { + m_dictChidlren.Remove( childName ); + } + else + { + return pCachedChild; + } + } + + for (int i = 0; i < GetChildCount(); i++) + { + Panel *pChild = GetChild(i); + if (!pChild) + continue; + + if (!V_stricmp(pChild->GetName(), childName)) + { + idx = m_dictChidlren.Insert( childName ); + m_dictChidlren[ idx ].Set( pChild->GetVPanel() ); + return pChild; + } + + if (recurseDown) + { + Panel *panel = pChild->FindChildByName(childName, recurseDown); + if ( panel ) + { + return panel; + } + } + } + + m_dictChidlren.Insert( childName ); // Defaults the handle to INVALID_PANEL + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a sibling panel by name +//----------------------------------------------------------------------------- +Panel *Panel::FindSiblingByName(const char *siblingName) +{ + if ( !GetVParent() ) + return NULL; + + int siblingCount = ipanel()->GetChildCount(GetVParent()); + for (int i = 0; i < siblingCount; i++) + { + VPANEL sibling = ipanel()->GetChild(GetVParent(), i); + Panel *panel = ipanel()->GetPanel(sibling, GetControlsModuleName()); + if (!stricmp(panel->GetName(), siblingName)) + { + return panel; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Dispatches immediately a message to the parent +//----------------------------------------------------------------------------- +void Panel::CallParentFunction(KeyValues *message) +{ + if (GetVParent()) + { + ipanel()->SendMessage(GetVParent(), message, GetVPanel()); + } + if (message) + { + message->deleteThis(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: if set to true, panel automatically frees itself when parent is deleted +//----------------------------------------------------------------------------- +void Panel::SetAutoDelete( bool state ) +{ + _flags.SetFlag( AUTODELETE_ENABLED, state ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Panel::IsAutoDeleteSet() +{ + return _flags.IsFlagSet( AUTODELETE_ENABLED ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Just calls 'delete this' +//----------------------------------------------------------------------------- +void Panel::DeletePanel() +{ + // Avoid re-entrancy + _flags.SetFlag( MARKED_FOR_DELETION ); + _flags.ClearFlag( AUTODELETE_ENABLED ); + delete this; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +HScheme Panel::GetScheme() +{ + if (m_iScheme) + { + return m_iScheme; // return our internal scheme + } + + if (GetVParent()) // recurse down the heirarchy + { + return ipanel()->GetScheme(GetVParent()); + } + + return scheme()->GetDefaultScheme(); +} + +//----------------------------------------------------------------------------- +// Purpose: set the scheme to render this panel with by name +//----------------------------------------------------------------------------- +void Panel::SetScheme(const char *tag) +{ + if (strlen(tag) > 0 && scheme()->GetScheme(tag)) // check the scheme exists + { + SetScheme(scheme()->GetScheme(tag)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: set the scheme to render this panel with +//----------------------------------------------------------------------------- +void Panel::SetScheme(HScheme scheme) +{ + if (scheme != m_iScheme) + { + m_iScheme = scheme; + + // This will cause the new scheme to be applied at a later point +// InvalidateLayout( false, true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the char of this panels hotkey +//----------------------------------------------------------------------------- +Panel *Panel::HasHotkey(wchar_t key) +{ + return NULL; +} + +#if defined( VGUI_USEDRAGDROP ) +static vgui::PHandle g_DragDropCapture; +#endif // VGUI_USEDRAGDROP + +void Panel::InternalCursorMoved(int x, int y) +{ +#if defined( VGUI_USEDRAGDROP ) + if ( g_DragDropCapture.Get() ) + { + bool started = g_DragDropCapture->GetDragDropInfo()->m_bDragStarted; + + g_DragDropCapture->OnContinueDragging(); + + if ( started ) + { + bool isEscapeKeyDown = input()->IsKeyDown( KEY_ESCAPE ); + if ( isEscapeKeyDown ) + { + g_DragDropCapture->OnFinishDragging( true, (MouseCode)-1, true ); + } + return; + } + } +#endif // VGUI_USEDRAGDROP + + if ( !ShouldHandleInputMessage() ) + return; + + if ( IsCursorNone() ) + return; + + if ( !IsMouseInputEnabled() ) + { + return; + } + + if (IsBuildGroupEnabled()) + { + if ( _buildGroup->CursorMoved(x, y, this) ) + { + return; + } + } + + if (m_pTooltips) + { + if ( _tooltipText ) + { + m_pTooltips->SetText( _tooltipText ); + } + m_pTooltips->ShowTooltip(this); + } + + ScreenToLocal(x, y); + + OnCursorMoved(x, y); +} + +void Panel::InternalCursorEntered() +{ + if (IsCursorNone() || !IsMouseInputEnabled()) + return; + + if (IsBuildGroupEnabled()) + return; + + if (m_pTooltips) + { + m_pTooltips->ResetDelay(); + + if ( _tooltipText ) + { + m_pTooltips->SetText( _tooltipText ); + } + m_pTooltips->ShowTooltip(this); + } + + OnCursorEntered(); +} + +void Panel::InternalCursorExited() +{ + if (IsCursorNone() || !IsMouseInputEnabled()) + return; + + if (IsBuildGroupEnabled()) + return; + + if (m_pTooltips) + { + m_pTooltips->HideTooltip(); + } + + OnCursorExited(); +} + +bool Panel::IsChildOfSurfaceModalPanel() +{ + VPANEL appModalPanel = input()->GetAppModalSurface(); + if ( !appModalPanel ) + return true; + + if ( ipanel()->HasParent( GetVPanel(), appModalPanel ) ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsChildOfModalSubTree() +{ + VPANEL subTree = input()->GetModalSubTree(); + if ( !subTree ) + return true; + + if ( HasParent( subTree ) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if message is being subverted due to modal subtree logic +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +static bool ShouldHandleInputMessage( VPANEL p ) +{ + // If there is not modal subtree, then always handle the msg + if ( !input()->GetModalSubTree() ) + return true; + + // What state are we in? + bool bChildOfModal = false; + VPANEL subTree = input()->GetModalSubTree(); + if ( !subTree ) + { + bChildOfModal = true; + } + else if ( ipanel()->HasParent( p, subTree ) ) + { + bChildOfModal = true; + } + + if ( input()->ShouldModalSubTreeReceiveMessages() ) + return bChildOfModal; + + return !bChildOfModal; +} + +bool Panel::ShouldHandleInputMessage() +{ + return ::ShouldHandleInputMessage( GetVPanel() ); +} + +void Panel::InternalMousePressed(int code) +{ + long curtime = system()->GetTimeMillis(); + if ( IsTriplePressAllowed() ) + { + long elapsed = curtime - m_lLastDoublePressTime; + if ( elapsed < TRIPLE_PRESS_MSEC ) + { + InternalMouseTriplePressed( code ); + return; + } + } + + // The menu system passively watches for mouse released messages so it + // can clear any open menus if the release is somewhere other than on a menu + Menu::OnInternalMousePressed( this, (MouseCode)code ); + + if ( !ShouldHandleInputMessage() ) + return; + + if ( IsCursorNone() ) + return; + + if ( !IsMouseInputEnabled()) + { +#if defined( VGUI_USEDRAGDROP ) + DragDropStartDragging(); +#endif + return; + } + + if (IsBuildGroupEnabled()) + { + if ( _buildGroup->MousePressed((MouseCode)code, this) ) + { + return; + } + } + +#ifdef STAGING_ONLY + // If holding CTRL + ALT, invalidate layout. For debugging purposes + if ( ( vgui::input()->IsKeyDown(KEY_LCONTROL) || vgui::input()->IsKeyDown(KEY_RCONTROL) ) + && ( vgui::input()->IsKeyDown(KEY_LALT) || vgui::input()->IsKeyDown(KEY_RALT) ) ) + { + InvalidateLayout( true, true ); + } +#endif + +#ifdef STAGING_ONLY + const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); + if ( !V_stricmp( pGameDir, "tf" ) ) + { + if ( code >= MOUSE_LEFT && code <= MOUSE_MIDDLE ) + { + m_sMousePressedPanels[ code - MOUSE_LEFT ] = this; + } + } +#endif + + Panel *pMouseHandler = m_hMouseEventHandler.Get(); + if ( pMouseHandler ) + { + pMouseHandler->OnMousePressed( (MouseCode)code ); + } + else + { + OnMousePressed( (MouseCode)code ); + } + +#if defined( VGUI_USEDRAGDROP ) + DragDropStartDragging(); +#endif +} + +void Panel::InternalMouseDoublePressed(int code) +{ + m_lLastDoublePressTime = system()->GetTimeMillis(); + + if ( !ShouldHandleInputMessage() ) + return; + + if ( IsCursorNone() ) + return; + + if ( !IsMouseInputEnabled()) + { + return; + } + + if (IsBuildGroupEnabled()) + { + if ( _buildGroup->MouseDoublePressed((MouseCode)code, this) ) + { + return; + } + } + + Panel *pMouseHandler = m_hMouseEventHandler.Get(); + if ( pMouseHandler ) + { + pMouseHandler->OnMouseDoublePressed( (MouseCode)code ); + } + else + { + OnMouseDoublePressed( (MouseCode)code ); + } +} + +#if defined( VGUI_USEDRAGDROP ) +void Panel::SetStartDragWhenMouseExitsPanel( bool state ) +{ + _flags.SetFlag( DRAG_REQUIRES_PANEL_EXIT, state ); +} + +bool Panel::IsStartDragWhenMouseExitsPanel() const +{ + return _flags.IsFlagSet( DRAG_REQUIRES_PANEL_EXIT ); +} +#endif // VGUI_USEDRAGDROP + +void Panel::SetTriplePressAllowed( bool state ) +{ + _flags.SetFlag( TRIPLE_PRESS_ALLOWED, state ); +} + +bool Panel::IsTriplePressAllowed() const +{ + return _flags.IsFlagSet( TRIPLE_PRESS_ALLOWED ); +} + +void Panel::InternalMouseTriplePressed( int code ) +{ + Assert( IsTriplePressAllowed() ); + m_lLastDoublePressTime = 0L; + + if ( !ShouldHandleInputMessage() ) + return; + + if ( IsCursorNone() ) + return; + + if ( !IsMouseInputEnabled()) + { +#if defined( VGUI_USEDRAGDROP ) + DragDropStartDragging(); +#endif + return; + } + + if (IsBuildGroupEnabled()) + { + return; + } + + OnMouseTriplePressed((MouseCode)code); +#if defined( VGUI_USEDRAGDROP ) + DragDropStartDragging(); +#endif +} + +void Panel::InternalMouseReleased(int code) +{ +#if defined( VGUI_USEDRAGDROP ) + if ( g_DragDropCapture.Get() ) + { + bool started = g_DragDropCapture->GetDragDropInfo()->m_bDragStarted; + g_DragDropCapture->OnFinishDragging( true, (MouseCode)code ); + if ( started ) + { + return; + } + } +#endif + + if ( !ShouldHandleInputMessage() ) + return; + + if ( IsCursorNone() ) + return; + + if ( !IsMouseInputEnabled()) + { + return; + } + + if (IsBuildGroupEnabled()) + { + if ( _buildGroup->MouseReleased((MouseCode)code, this) ) + { + return; + } + } + +#ifdef STAGING_ONLY + const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); + if ( tf_strict_mouse_up_events.GetBool() && !V_stricmp( pGameDir, "tf" ) ) + { + // Only allow mouse release events to go to panels that we also + // first clicked into + if ( code >= MOUSE_LEFT && code <= MOUSE_MIDDLE ) + { + const int nIndex = code - MOUSE_LEFT; + Panel* pPressedPanel = m_sMousePressedPanels[ nIndex ]; + m_sMousePressedPanels[ nIndex ] = NULL; // Clear out pressed panel + if ( pPressedPanel != this ) + { + OnMouseMismatchedRelease( (MouseCode)code, pPressedPanel ); + return; + } + } + } +#endif + + OnMouseReleased((MouseCode)code); +} + +void Panel::InternalMouseWheeled(int delta) +{ + if (IsBuildGroupEnabled() || !IsMouseInputEnabled()) + { + return; + } + + if ( !ShouldHandleInputMessage() ) + return; + + OnMouseWheeled(delta); +} + +void Panel::InternalKeyCodePressed(int code) +{ + if ( !ShouldHandleInputMessage() ) + return; + + if (IsKeyBoardInputEnabled()) + { + OnKeyCodePressed((KeyCode)code); + } + else + { + CallParentFunction(new KeyValues("KeyCodePressed", "code", code)); + } +} + +#if defined( VGUI_USEKEYBINDINGMAPS ) +//----------------------------------------------------------------------------- +// Purpose: +// Input : *bindingName - +// keycode - +// modifiers - +//----------------------------------------------------------------------------- +void Panel::AddKeyBinding( char const *bindingName, int keycode, int modifiers ) +{ + PanelKeyBindingMap *map = LookupMapForBinding( bindingName ); + if ( !map ) + { + Assert( 0 ); + return; + } + + BoundKey_t kb; + kb.isbuiltin = false; + kb.bindingname = CopyString( bindingName ); + kb.keycode = keycode; + kb.modifiers = modifiers; + + map->boundkeys.AddToTail( kb ); +} + +KeyBindingMap_t *Panel::LookupBinding( char const *bindingName ) +{ + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + int c = map->entries.Count(); + for( int i = 0; i < c ; ++i ) + { + KeyBindingMap_t *binding = &map->entries[ i ]; + if ( !Q_stricmp( binding->bindingname, bindingName ) ) + return binding; + } + + map = map->baseMap; + } + + return NULL; +} + +PanelKeyBindingMap *Panel::LookupMapForBinding( char const *bindingName ) +{ + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + int c = map->entries.Count(); + for( int i = 0; i < c ; ++i ) + { + KeyBindingMap_t *binding = &map->entries[ i ]; + if ( !Q_stricmp( binding->bindingname, bindingName ) ) + return map; + } + + map = map->baseMap; + } + + return NULL; +} + +KeyBindingMap_t *Panel::LookupBindingByKeyCode( KeyCode code, int modifiers ) +{ + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + int c = map->boundkeys.Count(); + for( int i = 0; i < c ; ++i ) + { + BoundKey_t *kb = &map->boundkeys[ i ]; + if ( kb->keycode == code && kb->modifiers == modifiers ) + { + KeyBindingMap_t *binding = LookupBinding( kb->bindingname ); + Assert( binding ); + if ( binding ) + { + return binding; + } + } + } + + map = map->baseMap; + } + + return NULL; +} + +BoundKey_t *Panel::LookupDefaultKey( char const *bindingName ) +{ + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + int c = map->defaultkeys.Count(); + for( int i = 0; i < c ; ++i ) + { + BoundKey_t *kb = &map->defaultkeys[ i ]; + if ( !Q_stricmp( kb->bindingname, bindingName ) ) + { + return kb; + } + } + + map = map->baseMap; + } + return NULL; +} + +void Panel::LookupBoundKeys( char const *bindingName, CUtlVector< BoundKey_t * >& list ) +{ + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + int c = map->boundkeys.Count(); + for( int i = 0; i < c ; ++i ) + { + BoundKey_t *kb = &map->boundkeys[ i ]; + if ( !Q_stricmp( kb->bindingname, bindingName ) ) + { + list.AddToTail( kb ); + } + } + + map = map->baseMap; + } +} + +void Panel::RevertKeyBindingsToDefault() +{ + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + map->boundkeys.RemoveAll(); + map->boundkeys = map->defaultkeys; + + map = map->baseMap; + } +} + +void Panel::RemoveAllKeyBindings() +{ + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + map->boundkeys.RemoveAll(); + map = map->baseMap; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void Panel::ReloadKeyBindings() +{ + RevertKeyBindingsToDefault(); + LoadKeyBindingsForOnePanel( GetKeyBindingsContext(), this ); +} + +#define MAKE_STRING( x ) #x +#define KEY_NAME( str, disp ) { KEY_##str, MAKE_STRING( KEY_##str ), disp } + +struct KeyNames_t +{ + KeyCode code; + char const *string; + char const *displaystring; +}; + +static KeyNames_t g_KeyNames[] = +{ +KEY_NAME( NONE, "None" ), +KEY_NAME( 0, "0" ), +KEY_NAME( 1, "1" ), +KEY_NAME( 2, "2" ), +KEY_NAME( 3, "3" ), +KEY_NAME( 4, "4" ), +KEY_NAME( 5, "5" ), +KEY_NAME( 6, "6" ), +KEY_NAME( 7, "7" ), +KEY_NAME( 8, "8" ), +KEY_NAME( 9, "9" ), +KEY_NAME( A, "A" ), +KEY_NAME( B, "B" ), +KEY_NAME( C, "C" ), +KEY_NAME( D, "D" ), +KEY_NAME( E, "E" ), +KEY_NAME( F, "F" ), +KEY_NAME( G, "G" ), +KEY_NAME( H, "H" ), +KEY_NAME( I, "I" ), +KEY_NAME( J, "J" ), +KEY_NAME( K, "K" ), +KEY_NAME( L, "L" ), +KEY_NAME( M, "M" ), +KEY_NAME( N, "N" ), +KEY_NAME( O, "O" ), +KEY_NAME( P, "P" ), +KEY_NAME( Q, "Q" ), +KEY_NAME( R, "R" ), +KEY_NAME( S, "S" ), +KEY_NAME( T, "T" ), +KEY_NAME( U, "U" ), +KEY_NAME( V, "V" ), +KEY_NAME( W, "W" ), +KEY_NAME( X, "X" ), +KEY_NAME( Y, "Y" ), +KEY_NAME( Z, "Z" ), +KEY_NAME( PAD_0, "Key Pad 0" ), +KEY_NAME( PAD_1, "Key Pad 1" ), +KEY_NAME( PAD_2, "Key Pad 2" ), +KEY_NAME( PAD_3, "Key Pad 3" ), +KEY_NAME( PAD_4, "Key Pad 4" ), +KEY_NAME( PAD_5, "Key Pad 5" ), +KEY_NAME( PAD_6, "Key Pad 6" ), +KEY_NAME( PAD_7, "Key Pad 7" ), +KEY_NAME( PAD_8, "Key Pad 8" ), +KEY_NAME( PAD_9, "Key Pad 9" ), +KEY_NAME( PAD_DIVIDE, "Key Pad /" ), +KEY_NAME( PAD_MULTIPLY, "Key Pad *" ), +KEY_NAME( PAD_MINUS, "Key Pad -" ), +KEY_NAME( PAD_PLUS, "Key Pad +" ), +KEY_NAME( PAD_ENTER, "Key Pad Enter" ), +KEY_NAME( PAD_DECIMAL, "Key Pad ." ), +KEY_NAME( LBRACKET, "[" ), +KEY_NAME( RBRACKET, "]" ), +KEY_NAME( SEMICOLON, "," ), +KEY_NAME( APOSTROPHE, "'" ), +KEY_NAME( BACKQUOTE, "`" ), +KEY_NAME( COMMA, "," ), +KEY_NAME( PERIOD, "." ), +KEY_NAME( SLASH, "/" ), +KEY_NAME( BACKSLASH, "\\" ), +KEY_NAME( MINUS, "-" ), +KEY_NAME( EQUAL, "=" ), +KEY_NAME( ENTER, "Enter" ), +KEY_NAME( SPACE, "Space" ), +KEY_NAME( BACKSPACE, "Backspace" ), +KEY_NAME( TAB, "Tab" ), +KEY_NAME( CAPSLOCK, "Caps Lock" ), +KEY_NAME( NUMLOCK, "Num Lock" ), +KEY_NAME( ESCAPE, "Escape" ), +KEY_NAME( SCROLLLOCK, "Scroll Lock" ), +KEY_NAME( INSERT, "Ins" ), +KEY_NAME( DELETE, "Del" ), +KEY_NAME( HOME, "Home" ), +KEY_NAME( END, "End" ), +KEY_NAME( PAGEUP, "PgUp" ), +KEY_NAME( PAGEDOWN, "PgDn" ), +KEY_NAME( BREAK, "Break" ), +KEY_NAME( LSHIFT, "Shift" ), +KEY_NAME( RSHIFT, "Shift" ), +KEY_NAME( LALT, "Alt" ), +KEY_NAME( RALT, "Alt" ), +KEY_NAME( LCONTROL, "Ctrl" ), +KEY_NAME( RCONTROL, "Ctrl" ), +KEY_NAME( LWIN, "Windows" ), +KEY_NAME( RWIN, "Windows" ), +KEY_NAME( APP, "App" ), +KEY_NAME( UP, "Up" ), +KEY_NAME( LEFT, "Left" ), +KEY_NAME( DOWN, "Down" ), +KEY_NAME( RIGHT, "Right" ), +KEY_NAME( F1, "F1" ), +KEY_NAME( F2, "F2" ), +KEY_NAME( F3, "F3" ), +KEY_NAME( F4, "F4" ), +KEY_NAME( F5, "F5" ), +KEY_NAME( F6, "F6" ), +KEY_NAME( F7, "F7" ), +KEY_NAME( F8, "F8" ), +KEY_NAME( F9, "F9" ), +KEY_NAME( F10, "F10" ), +KEY_NAME( F11, "F11" ), +KEY_NAME( F12, "F12" ), +KEY_NAME( CAPSLOCKTOGGLE, "Caps Lock Toggle" ), +KEY_NAME( NUMLOCKTOGGLE, "Num Lock Toggle" ), +KEY_NAME( SCROLLLOCKTOGGLE, "Scroll Lock Toggle" ), +}; + +char const *Panel::KeyCodeToString( KeyCode code ) +{ + int c = ARRAYSIZE( g_KeyNames ); + for ( int i = 0; i < c ; ++i ) + { + if ( g_KeyNames[ i ].code == code ) + return g_KeyNames[ i ].string; + } + + return ""; +} + +wchar_t const *Panel::KeyCodeToDisplayString( KeyCode code ) +{ + int c = ARRAYSIZE( g_KeyNames ); + for ( int i = 0; i < c ; ++i ) + { + if ( g_KeyNames[ i ].code == code ) + { + char const *str = g_KeyNames[ i ].displaystring; + wchar_t *wstr = g_pVGuiLocalize->Find( str ); + if ( wstr ) + { + return wstr; + } + + static wchar_t buf[ 64 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( str, buf, sizeof( buf ) ); + return buf; + } + } + + return L""; +} + +static void AddModifierToString( char const *modifiername, char *buf, size_t bufsize ) +{ + char add[ 32 ]; + if ( Q_strlen( buf ) > 0 ) + { + Q_snprintf( add, sizeof( add ), "+%s", modifiername ); + } + else + { + Q_strncpy( add, modifiername, sizeof( add ) ); + } + + Q_strncat( buf, add, bufsize, COPY_ALL_CHARACTERS ); + +} + +wchar_t const *Panel::KeyCodeModifiersToDisplayString( KeyCode code, int modifiers ) +{ + char sz[ 256 ]; + sz[ 0 ] = 0; + + if ( modifiers & MODIFIER_SHIFT ) + { + AddModifierToString( "Shift", sz, sizeof( sz ) ); + } + if ( modifiers & MODIFIER_CONTROL ) + { + AddModifierToString( "Ctrl", sz, sizeof( sz ) ); + } + if ( modifiers & MODIFIER_ALT ) + { + AddModifierToString( "Alt", sz, sizeof( sz ) ); + } + + if ( Q_strlen( sz ) > 0 ) + { + Q_strncat( sz, "+", sizeof( sz ), COPY_ALL_CHARACTERS ); + } + + static wchar_t unicode[ 256 ]; + V_swprintf_safe( unicode, L"%S%s", sz, Panel::KeyCodeToDisplayString( (KeyCode)code ) ); + return unicode; +} + +KeyCode Panel::StringToKeyCode( char const *str ) +{ + int c = ARRAYSIZE( g_KeyNames ); + for ( int i = 0; i < c ; ++i ) + { + if ( !Q_stricmp( str, g_KeyNames[ i ].string ) ) + return g_KeyNames[ i ].code; + } + + return KEY_NONE; +} + +static void WriteKeyBindingToBuffer( CUtlBuffer& buf, int level, const BoundKey_t& binding ) +{ + BufPrint( buf, level, "\"keycode\"\t\"%s\"\n", Panel::KeyCodeToString( (KeyCode)binding.keycode ) ); + if ( binding.modifiers & MODIFIER_SHIFT ) + { + BufPrint( buf, level, "\"shift\"\t\"1\"\n" ); + } + if ( binding.modifiers & MODIFIER_CONTROL ) + { + BufPrint( buf, level, "\"ctrl\"\t\"1\"\n" ); + } + if ( binding.modifiers & MODIFIER_ALT ) + { + BufPrint( buf, level, "\"alt\"\t\"1\"\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// *pathID - +//----------------------------------------------------------------------------- +void Panel::SaveKeyBindingsToBuffer( int level, CUtlBuffer& buf ) +{ + Assert( IsValidKeyBindingsContext() ); + + Assert( buf.IsText() ); + + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + int c = map->boundkeys.Count(); + for( int i = 0; i < c ; ++i ) + { + const BoundKey_t& binding = map->boundkeys[ i ]; + + // Spew to file + BufPrint( buf, level, "\"%s\"\n", binding.bindingname ); + BufPrint( buf, level, "{\n" ); + + WriteKeyBindingToBuffer( buf, level + 1, binding ); + + BufPrint( buf, level, "}\n" ); + } + + map = map->baseMap; + } +} + +bool Panel::ParseKeyBindings( KeyValues *kv ) +{ + Assert( IsValidKeyBindingsContext() ); + if ( !IsValidKeyBindingsContext() ) + return false; + + // To have KB the panel must have a name + Assert( GetName() && GetName()[ 0 ] ); + if ( !GetName() || !GetName()[ 0 ] ) + return false; + + bool success = false; + + g_KBMgr.AddPanelToContext( GetKeyBindingsContext(), this ); + + RemoveAllKeyBindings(); + + // Walk through bindings + for ( KeyValues *binding = kv->GetFirstSubKey(); binding != NULL; binding = binding->GetNextKey() ) + { + char const *bindingName = binding->GetName(); + if ( !bindingName || !bindingName[ 0 ] ) + continue; + + KeyBindingMap_t *b = LookupBinding( bindingName ); + if ( b ) + { + success = true; + const char *keycode = binding->GetString( "keycode", "" ); + int modifiers = 0; + if ( binding->GetInt( "shift", 0 ) != 0 ) + { + modifiers |= MODIFIER_SHIFT; + } + if ( binding->GetInt( "ctrl", 0 ) != 0 ) + { + modifiers |= MODIFIER_CONTROL; + } + if ( binding->GetInt( "alt", 0 ) != 0 ) + { + modifiers |= MODIFIER_ALT; + } + + KeyBindingMap_t *bound = LookupBindingByKeyCode( StringToKeyCode( keycode ), modifiers ); + if ( !bound ) + { + AddKeyBinding( bindingName, StringToKeyCode( keycode ), modifiers ); + } + } + else + { + Warning( "KeyBinding for panel '%s' contained unknown binding '%s'\n", GetName() ? GetName() : "???", bindingName ); + } + } + + // Now for each binding which is currently "unbound" to any key, use the default binding + PanelKeyBindingMap *map = GetKBMap(); + while( map ) + { + int c = map->entries.Count(); + for( int i = 0; i < c ; ++i ) + { + KeyBindingMap_t *binding = &map->entries[ i ]; + + // See if there is a bound key + CUtlVector< BoundKey_t * > list; + LookupBoundKeys( binding->bindingname, list ); + if ( list.Count() == 0 ) + { + // Assign the default binding to this key + BoundKey_t *defaultKey = LookupDefaultKey( binding->bindingname ); + if ( defaultKey ) + { + KeyBindingMap_t *alreadyBound = LookupBindingByKeyCode( (KeyCode)defaultKey->keycode, defaultKey->modifiers ); + if ( alreadyBound ) + { + Warning( "No binding for '%s', defautl key already bound to '%s'\n", binding->bindingname, alreadyBound->bindingname ); + } + else + { + AddKeyBinding( defaultKey->bindingname, defaultKey->keycode, defaultKey->modifiers ); + } + } + } + } + + map = map->baseMap; + } + + return success; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void Panel::SetKeyBindingsContext( KeyBindingContextHandle_t handle ) +{ + Assert( !IsValidKeyBindingsContext() || handle == GetKeyBindingsContext() ); + g_KBMgr.AddPanelToContext( handle, this ); + m_hKeyBindingsContext = handle; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : KeyBindingContextHandle_t +//----------------------------------------------------------------------------- +KeyBindingContextHandle_t Panel::GetKeyBindingsContext() const +{ + return m_hKeyBindingsContext; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsValidKeyBindingsContext() const +{ + return GetKeyBindingsContext() != INVALID_KEYBINDINGCONTEXT_HANDLE; +} + +char const *Panel::GetKeyBindingsFile() const +{ + Assert( IsValidKeyBindingsContext() ); + return g_KBMgr.GetKeyBindingsFile( GetKeyBindingsContext() ); +} + +char const *Panel::GetKeyBindingsFilePathID() const +{ + Assert( IsValidKeyBindingsContext() ); + return g_KBMgr.GetKeyBindingsFilePathID( GetKeyBindingsContext() ); +} + +void Panel::EditKeyBindings() +{ + Assert( 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set this to false to disallow IsKeyRebound chaining to GetParent() Panels... +// Input : state - +//----------------------------------------------------------------------------- +void Panel::SetAllowKeyBindingChainToParent( bool state ) +{ + _flags.SetFlag( ALLOW_CHAIN_KEYBINDING_TO_PARENT, state ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsKeyBindingChainToParentAllowed() const +{ + return _flags.IsFlagSet( ALLOW_CHAIN_KEYBINDING_TO_PARENT ); +} + +bool Panel::IsKeyOverridden( KeyCode code, int modifiers ) +{ + // By default assume all keys should pass through binding system + return false; +} + +bool Panel::IsKeyRebound( KeyCode code, int modifiers ) +{ + if ( IsKeyBoardInputEnabled() ) + { + KeyBindingMap_t* binding = LookupBindingByKeyCode( code, modifiers ); + // Only dispatch if we're part of the current modal subtree + if ( binding && IsChildOfSurfaceModalPanel() ) + { + // Found match, post message to panel + if ( binding->func ) + { + // dispatch the func + (this->*binding->func)(); + } + else + { + Assert( 0 ); + } + + if ( !binding->passive ) + { + // Exit this function... + return true; + } + } + } + + // Chain to parent + Panel* pParent = GetParent(); + if ( IsKeyBindingChainToParentAllowed() && pParent && !IsKeyOverridden( code, modifiers ) ) + return pParent->IsKeyRebound( code, modifiers ); + + // No suitable binding found + return false; +} + +static bool s_bSuppressRebindChecks = false; +#endif // VGUI_USEKEYBINDINGMAPS + +void Panel::InternalKeyCodeTyped( int code ) +{ + if ( !ShouldHandleInputMessage() ) + { + input()->OnKeyCodeUnhandled( code ); + return; + } + + if (IsKeyBoardInputEnabled()) + { + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + int modifiers = 0; + if ( shift ) + { + modifiers |= MODIFIER_SHIFT; + } + if ( ctrl ) + { + modifiers |= MODIFIER_CONTROL; + } + if ( alt ) + { + modifiers |= MODIFIER_ALT; + } + + // Things in build mode don't have accelerators + if (IsBuildGroupEnabled()) + { + _buildGroup->KeyCodeTyped((KeyCode)code, this); + return; + } + + if ( !s_bSuppressRebindChecks && IsKeyRebound( (KeyCode)code, modifiers ) ) + { + return; + } + + bool oldVal = s_bSuppressRebindChecks; + s_bSuppressRebindChecks = true; + OnKeyCodeTyped((KeyCode)code); + s_bSuppressRebindChecks = oldVal; + } + else + { + if ( GetVPanel() == surface()->GetEmbeddedPanel() ) + { + input()->OnKeyCodeUnhandled( code ); + } + CallParentFunction(new KeyValues("KeyCodeTyped", "code", code)); + } +} + +void Panel::InternalKeyTyped(int unichar) +{ + if ( !ShouldHandleInputMessage() ) + return; + + if (IsKeyBoardInputEnabled()) + { + if ( IsBuildGroupEnabled() ) + { + if ( _buildGroup->KeyTyped( (wchar_t)unichar, this ) ) + { + return; + } + } + + OnKeyTyped((wchar_t)unichar); + } + else + { + CallParentFunction(new KeyValues("KeyTyped", "unichar", unichar)); + } +} + +void Panel::InternalKeyCodeReleased(int code) +{ + if ( !ShouldHandleInputMessage() ) + return; + + if (IsKeyBoardInputEnabled()) + { + if (IsBuildGroupEnabled()) + { + if ( _buildGroup->KeyCodeReleased((KeyCode)code, this) ) + { + return; + } + } + + OnKeyCodeReleased((KeyCode)code); + } + else + { + CallParentFunction(new KeyValues("KeyCodeReleased", "code", code)); + } +} + +void Panel::InternalKeyFocusTicked() +{ + if (IsBuildGroupEnabled()) + return; + + OnKeyFocusTicked(); +} + +void Panel::InternalMouseFocusTicked() +{ + if (IsBuildGroupEnabled()) + { + // must repaint so the numbers will be accurate + if (_buildGroup->HasRulersOn()) + { + PaintTraverse(true); + } + return; + } + + // update cursor + InternalSetCursor(); + OnMouseFocusTicked(); +} + + +void Panel::InternalSetCursor() +{ + bool visible = IsVisible(); + + if (visible) + { +#if defined( VGUI_USEDRAGDROP ) + // Drag drop is overriding cursor? + if ( m_pDragDrop->m_bDragging || + g_DragDropCapture.Get() != NULL ) + return; +#endif + // chain up and make sure all our parents are also visible + VPANEL p = GetVParent(); + while (p) + { + visible &= ipanel()->IsVisible(p); + p = ipanel()->GetParent(p); + } + + // only change the cursor if this panel is visible, and if its part of the main VGUI tree + if (visible && HasParent(surface()->GetEmbeddedPanel())) + { + HCursor cursor = GetCursor(); + + if (IsBuildGroupEnabled()) + { + cursor = _buildGroup->GetCursor(this); + } + + if (input()->GetCursorOveride()) + { + cursor = input()->GetCursorOveride(); + } + + surface()->SetCursor(cursor); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame the panel is visible, designed to be overridden +//----------------------------------------------------------------------------- +void Panel::OnThink() +{ +#if defined( VGUI_USEDRAGDROP ) + if ( IsPC() && + m_pDragDrop->m_bDragEnabled && + m_pDragDrop->m_bDragging && + m_pDragDrop->m_bDragStarted ) + { + bool isEscapeKeyDown = input()->IsKeyDown( KEY_ESCAPE ); + if ( isEscapeKeyDown ) + { + OnContinueDragging(); + OnFinishDragging( true, (MouseCode)-1, true ); + return; + } + + if ( m_pDragDrop->m_hCurrentDrop != 0 ) + { + if ( !input()->IsMouseDown( MOUSE_LEFT ) ) + { + OnContinueDragging(); + OnFinishDragging( true, (MouseCode)-1 ); + return; + } + + // allow the cursor to change based upon things like changing keystate, etc. + surface()->SetCursor( m_pDragDrop->m_hCurrentDrop->GetDropCursor( m_pDragDrop->m_DragData ) ); + + if ( !m_pDragDrop->m_bDropMenuShown ) + { + // See if the hover time has gotten larger + float hoverSeconds = ( system()->GetTimeMillis() - m_pDragDrop->m_lDropHoverTime ) * 0.001f; + DragDrop_t *dropInfo = m_pDragDrop->m_hCurrentDrop->GetDragDropInfo(); + + if ( dropInfo->m_flHoverContextTime != 0.0f ) + { + if ( hoverSeconds >= dropInfo->m_flHoverContextTime ) + { + m_pDragDrop->m_bDropMenuShown = true; + + CUtlVector< KeyValues * > data; + + GetDragData( data ); + + int x, y; + input()->GetCursorPos( x, y ); + + if ( m_pDragDrop->m_hDropContextMenu.Get() ) + { + delete m_pDragDrop->m_hDropContextMenu.Get(); + } + + Menu *menu = new Menu( m_pDragDrop->m_hCurrentDrop.Get(), "DropContext" ); + + bool useMenu = m_pDragDrop->m_hCurrentDrop->GetDropContextMenu( menu, data ); + if ( useMenu ) + { + m_pDragDrop->m_hDropContextMenu = menu; + + menu->SetPos( x, y ); + menu->SetVisible( true ); + menu->MakePopup(); + surface()->MovePopupToFront( menu->GetVPanel() ); + if ( menu->GetItemCount() > 0 ) + { + int id = menu->GetMenuID( 0 ); + menu->SetCurrentlyHighlightedItem( id ); + MenuItem *item = menu->GetMenuItem( id ); + item->SetArmed( true ); + } + } + else + { + delete menu; + } + + m_pDragDrop->m_hCurrentDrop->OnDropContextHoverShow( data ); + } + } + } + } + } +#endif +} + +// input messages handlers (designed for override) +void Panel::OnCursorMoved(int x, int y) +{ + if( ParentNeedsCursorMoveEvents() ) + { + // figure out x and y in parent space + int thisX, thisY; + ipanel()->GetPos( GetVPanel(), thisX, thisY ); + CallParentFunction( new KeyValues( "OnCursorMoved", "x", x + thisX, "y", y + thisY ) ); + } +} + +void Panel::OnCursorEntered() +{ +} + +void Panel::OnCursorExited() +{ +} + +void Panel::OnMousePressed(MouseCode code) +{ +} + +void Panel::OnMouseDoublePressed(MouseCode code) +{ +} + +void Panel::OnMouseTriplePressed(MouseCode code) +{ +} + +void Panel::OnMouseReleased(MouseCode code) +{ +} + +void Panel::OnMouseMismatchedRelease( MouseCode code, Panel* pPressedPanel ) +{ +} + +void Panel::OnMouseWheeled(int delta) +{ + CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); +} + +// base implementation forwards Key messages to the Panel's parent - override to 'swallow' the input +void Panel::OnKeyCodePressed(KeyCode code) +{ + static ConVarRef vgui_nav_lock( "vgui_nav_lock" ); + + bool handled = false; + switch( GetBaseButtonCode( code ) ) + { + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + case KEY_XSTICK2_UP: + case KEY_UP: + case STEAMCONTROLLER_DPAD_UP: + if ( ( !vgui_nav_lock.IsValid() || vgui_nav_lock.GetInt() == 0 ) && NavigateUp() ) + { + vgui_nav_lock.SetValue( 1 ); + vgui::surface()->PlaySound( "UI/menu_focus.wav" ); + handled = true; + } + break; + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + case KEY_XSTICK2_DOWN: + case KEY_DOWN: + case STEAMCONTROLLER_DPAD_DOWN: + if ( ( !vgui_nav_lock.IsValid() || vgui_nav_lock.GetInt() == 0 ) && NavigateDown() ) + { + vgui_nav_lock.SetValue( 1 ); + vgui::surface()->PlaySound( "UI/menu_focus.wav" ); + handled = true; + } + break; + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case KEY_XSTICK2_LEFT: + case KEY_LEFT: + case STEAMCONTROLLER_DPAD_LEFT: + if ( ( !vgui_nav_lock.IsValid() || vgui_nav_lock.GetInt() == 0 ) && NavigateLeft() ) + { + vgui_nav_lock.SetValue( 1 ); + vgui::surface()->PlaySound( "UI/menu_focus.wav" ); + handled = true; + } + break; + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case KEY_XSTICK2_RIGHT: + case KEY_RIGHT: + case STEAMCONTROLLER_DPAD_RIGHT: + if ( ( !vgui_nav_lock.IsValid() || vgui_nav_lock.GetInt() == 0 ) && NavigateRight() ) + { + vgui_nav_lock.SetValue( 1 ); + vgui::surface()->PlaySound( "UI/menu_focus.wav" ); + handled = true; + } + break; + case KEY_XBUTTON_B: + case STEAMCONTROLLER_B: + if ( ( !vgui_nav_lock.IsValid() || vgui_nav_lock.GetInt() == 0 ) && NavigateBack() ) + { + vgui_nav_lock.SetValue( 1 ); + vgui::surface()->PlaySound( "UI/menu_focus.wav" ); + handled = true; + } + break; + } + + if( !handled && !m_PassUnhandledInput ) + return; + + CallParentFunction(new KeyValues("KeyCodePressed", "code", code)); +} + +void Panel::OnKeyCodeTyped(KeyCode keycode) +{ + vgui::KeyCode code = GetBaseButtonCode( keycode ); + + // handle focus change + if ( IsX360() || IsConsoleStylePanel() ) + { + // eat these typed codes, will get handled in OnKeyCodePressed + switch ( code ) + { + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + case KEY_XSTICK2_UP: + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + case KEY_XSTICK2_DOWN: + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case KEY_XSTICK2_LEFT: + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case KEY_XSTICK2_RIGHT: + case KEY_XBUTTON_A: + case KEY_XBUTTON_B: + case KEY_XBUTTON_X: + case KEY_XBUTTON_Y: + case KEY_XBUTTON_LEFT_SHOULDER: + case KEY_XBUTTON_RIGHT_SHOULDER: + case KEY_XBUTTON_BACK: + case KEY_XBUTTON_START: + case KEY_XBUTTON_STICK1: + case KEY_XBUTTON_STICK2: + case KEY_XBUTTON_LTRIGGER: + case KEY_XBUTTON_RTRIGGER: + case STEAMCONTROLLER_A: + case STEAMCONTROLLER_B: + + case KEY_UP: + case KEY_DOWN: + case KEY_LEFT: + case KEY_RIGHT: + case STEAMCONTROLLER_DPAD_UP: + case STEAMCONTROLLER_DPAD_DOWN: + case STEAMCONTROLLER_DPAD_LEFT: + case STEAMCONTROLLER_DPAD_RIGHT: + return; + } + + // legacy handling - need to re-enable for older apps? + /* + if ( code == KEY_XSTICK1_RIGHT || code == KEY_XBUTTON_RIGHT ) + { + RequestFocusNext(); + return; + } + else if ( code == KEY_XSTICK1_LEFT || code == KEY_XBUTTON_LEFT ) + { + RequestFocusPrev(); + return; + } + */ + } + + if (code == KEY_TAB) + { + bool bShiftDown = input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT); + + if ( IsConsoleStylePanel() ) + { + if ( bShiftDown ) + { + NavigateUp(); + } + else + { + NavigateDown(); + } + } + else + { + // if shift is down goto previous tab position, otherwise goto next + if ( bShiftDown ) + { + RequestFocusPrev(); + } + else + { + RequestFocusNext(); + } + } + } + else + { + // forward up + if ( GetVPanel() == surface()->GetEmbeddedPanel() ) + { + input()->OnKeyCodeUnhandled( keycode ); + } + CallParentFunction(new KeyValues("KeyCodeTyped", "code", keycode)); + } +} + +void Panel::OnKeyTyped(wchar_t unichar) +{ + CallParentFunction(new KeyValues("KeyTyped", "unichar", unichar)); +} + +void Panel::OnKeyCodeReleased(KeyCode code) +{ + CallParentFunction(new KeyValues("KeyCodeReleased", "code", code)); +} + +void Panel::OnKeyFocusTicked() +{ + CallParentFunction(new KeyValues("KeyFocusTicked")); +} + +void Panel::OnMouseFocusTicked() +{ + CallParentFunction(new KeyValues("OnMouseFocusTicked")); +} + +bool Panel::IsWithin(int x,int y) +{ + // check against our clip rect + int clipRect[4]; + ipanel()->GetClipRect(GetVPanel(), clipRect[0], clipRect[1], clipRect[2], clipRect[3]); + + if (x < clipRect[0]) + { + return false; + } + + if (y < clipRect[1]) + { + return false; + } + + if (x >= clipRect[2]) + { + return false; + } + + if (y >= clipRect[3]) + { + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: determines which is the topmost panel under the coordinates (x, y) +//----------------------------------------------------------------------------- +VPANEL Panel::IsWithinTraverse(int x, int y, bool traversePopups) +{ + // if this one is not visible, its children won't be either + // also if it doesn't want mouse input its children can't get it either + if (!IsVisible() || !IsMouseInputEnabled()) + return NULL; + + if (traversePopups) + { + // check popups first + int i; + CUtlVector< VPANEL > &children = ipanel()->GetChildren( GetVPanel() ); + int childCount = children.Count(); + for (i = childCount - 1; i >= 0; i--) + { + VPANEL panel = children[ i ]; + if (ipanel()->IsPopup(panel)) + { + panel = ipanel()->IsWithinTraverse(panel, x, y, true); + if (panel != null) + { + return panel; + } + } + } + + // check children recursive, if you find one, just return first one + // this checks in backwards order so the last child drawn for this panel is chosen which + // coincides to how it would be visibly displayed + for (i = childCount - 1; i >= 0; i--) + { + VPANEL panel = children[ i ]; + // we've already checked popups so ignore + if (!ipanel()->IsPopup(panel)) + { + panel = ipanel()->IsWithinTraverse(panel, x, y, true); + if (panel != 0) + { + return panel; + } + } + } + + // check ourself + if ( !IsMouseInputDisabledForThisPanel() && IsWithin(x, y) ) + { + return GetVPanel(); + } + } + else + { + // since we're not checking popups, it must be within us, so we can check ourself first + if (IsWithin(x, y)) + { + // check children recursive, if you find one, just return first one + // this checks in backwards order so the last child drawn for this panel is chosen which + // coincides to how it would be visibly displayed + CUtlVector< VPANEL > &children = ipanel()->GetChildren( GetVPanel() ); + int childCount = children.Count(); + for (int i = childCount - 1; i >= 0; i--) + { + VPANEL panel = children[ i ]; + // ignore popups + if (!ipanel()->IsPopup(panel)) + { + panel = ipanel()->IsWithinTraverse(panel, x, y, false); + if (panel != 0) + { + return panel; + } + } + } + + // not a child, must be us + if ( !IsMouseInputDisabledForThisPanel() ) + return GetVPanel(); + } + } + + return NULL; +} + +void Panel::LocalToScreen(int& x,int& y) +{ + int px, py; + ipanel()->GetAbsPos(GetVPanel(), px, py); + + x = x + px; + y = y + py; +} + +void Panel::ScreenToLocal(int& x,int& y) +{ + int px, py; + ipanel()->GetAbsPos(GetVPanel(), px, py); + + x = x - px; + y = y - py; +} + +void Panel::ParentLocalToScreen(int &x, int &y) +{ + int px, py; + ipanel()->GetAbsPos(GetVParent(), px, py); + + x = x + px; + y = y + py; +} + +void Panel::MakePopup(bool showTaskbarIcon,bool disabled) +{ + surface()->CreatePopup(GetVPanel(), false, showTaskbarIcon,disabled); +} + +void Panel::SetCursor(HCursor cursor) +{ + _cursor = cursor; +} + +HCursor Panel::GetCursor() +{ + return _cursor; +} + +void Panel::SetCursorAlwaysVisible( bool visible ) +{ + surface()->SetCursorAlwaysVisible( visible ); +} + +void Panel::SetMinimumSize(int wide,int tall) +{ + ipanel()->SetMinimumSize(GetVPanel(), wide, tall); +} + +void Panel::GetMinimumSize(int& wide,int &tall) +{ + ipanel()->GetMinimumSize(GetVPanel(), wide, tall); +} + +bool Panel::IsBuildModeEditable() +{ + return true; +} + +void Panel::SetBuildModeEditable(bool state) +{ + if (state) + { + _buildModeFlags |= BUILDMODE_EDITABLE; + } + else + { + _buildModeFlags &= ~BUILDMODE_EDITABLE; + } +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +bool Panel::IsBuildModeDeletable() +{ + return (_buildModeFlags & BUILDMODE_DELETABLE); +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void Panel::SetBuildModeDeletable(bool state) +{ + if (state) + { + _buildModeFlags |= BUILDMODE_DELETABLE; + } + else + { + _buildModeFlags &= ~BUILDMODE_DELETABLE; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Panel::IsBuildModeActive() +{ + return _buildGroup ? _buildGroup->IsEnabled() : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::GetClipRect(int& x0,int& y0,int& x1,int& y1) +{ + ipanel()->GetClipRect(GetVPanel(), x0, y0, x1, y1); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Panel::GetChildCount() +{ + if (ipanel()) + { + return ipanel()->GetChildCount(GetVPanel()); + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: returns a child by the specified index +//----------------------------------------------------------------------------- +Panel *Panel::GetChild(int index) +{ + // get the child and cast it to a panel + // this assumes that the child is from the same module as the this (precondition) + return ipanel()->GetPanel(ipanel()->GetChild(GetVPanel(), index), GetControlsModuleName()); +} + +CUtlVector< VPANEL > &Panel::GetChildren() +{ + return ipanel()->GetChildren(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: moves the key focus back +//----------------------------------------------------------------------------- +bool Panel::RequestFocusPrev(VPANEL panel) +{ + // chain to parent + if (GetVParent()) + { + return ipanel()->RequestFocusPrev(GetVParent(), GetVPanel()); + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Panel::RequestFocusNext(VPANEL panel) +{ + // chain to parent + if (GetVParent()) + { + return ipanel()->RequestFocusNext(GetVParent(), GetVPanel()); + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the panel to have the current sub focus +// Input : direction - the direction in which focus travelled to arrive at this panel; forward = 1, back = -1 +//----------------------------------------------------------------------------- +void Panel::RequestFocus(int direction) +{ + // NOTE: This doesn't make any sense if we don't have keyboard input enabled + // NOTE: Well, maybe it does if you have a steam controller... + // Assert( ( IsX360() || IsConsoleStylePanel() ) || IsKeyBoardInputEnabled() ); + // ivgui()->DPrintf2("RequestFocus(%s, %s)\n", GetName(), GetClassName()); + OnRequestFocus(GetVPanel(), NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: Called after a panel requests focus to fix up the whole chain +//----------------------------------------------------------------------------- +void Panel::OnRequestFocus(VPANEL subFocus, VPANEL defaultPanel) +{ + CallParentFunction(new KeyValues("OnRequestFocus", "subFocus", subFocus, "defaultPanel", defaultPanel)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +VPANEL Panel::GetCurrentKeyFocus() +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the panel has focus +//----------------------------------------------------------------------------- +bool Panel::HasFocus() +{ + if (input()->GetFocus() == GetVPanel()) + { + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetTabPosition(int position) +{ + _tabPosition = position; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Panel::GetTabPosition() +{ + return _tabPosition; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::InternalFocusChanged(bool lost) +{ + /* + //if focus is gained tell the focusNavGroup about it so its current can be correct + if( (!lost) && (_focusNavGroup!=null) ) + { + _focusNavGroup->setCurrentPanel(this); + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a panel loses it's mouse capture +//----------------------------------------------------------------------------- +void Panel::OnMouseCaptureLost() +{ + if (m_pTooltips) + { + m_pTooltips->ResetDelay(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::AddActionSignalTarget(Panel *messageTarget) +{ + HPanel target = ivgui()->PanelToHandle(messageTarget->GetVPanel()); + if (!_actionSignalTargetDar.HasElement(target)) + { + _actionSignalTargetDar.AddElement(target); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::AddActionSignalTarget(VPANEL messageTarget) +{ + HPanel target = ivgui()->PanelToHandle(messageTarget); + if (!_actionSignalTargetDar.HasElement(target)) + { + _actionSignalTargetDar.AddElement(target); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::RemoveActionSignalTarget(Panel *oldTarget) +{ + _actionSignalTargetDar.RemoveElement(ivgui()->PanelToHandle(oldTarget->GetVPanel())); +} + +//----------------------------------------------------------------------------- +// Purpose: Sends a message to all the panels that have requested action signals +//----------------------------------------------------------------------------- +void Panel::PostActionSignal( KeyValues *message ) +{ + if ( m_bIsSilent != true ) + { + // add who it was from the message + message->SetPtr("panel", this); + int i; + for (i = _actionSignalTargetDar.GetCount() - 1; i > 0; i--) + { + VPANEL panel = ivgui()->HandleToPanel(_actionSignalTargetDar[i]); + if (panel) + { + ivgui()->PostMessage(panel, message->MakeCopy(), GetVPanel()); + } + } + + // do this so we can save on one MakeCopy() call + if (i == 0) + { + VPANEL panel = ivgui()->HandleToPanel(_actionSignalTargetDar[i]); + if (panel) + { + ivgui()->PostMessage(panel, message, GetVPanel()); + return; + } + } + } + message->deleteThis(); +} + +void Panel::SetBorder(IBorder *border) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s", __FUNCTION__, GetName() ); + _border = border; + + if (border) + { + int x, y, x2, y2; + border->GetInset(x, y, x2, y2); + ipanel()->SetInset(GetVPanel(), x, y, x2, y2); + + // update our background type based on the bord + SetPaintBackgroundType(border->GetBackgroundType()); + } + else + { + ipanel()->SetInset(GetVPanel(), 0, 0, 0, 0); + } +} + +IBorder *Panel::GetBorder() +{ + return _border; +} + + +void Panel::SetPaintBorderEnabled(bool state) +{ + _flags.SetFlag( PAINT_BORDER_ENABLED, state ); +} + +void Panel::SetPaintBackgroundEnabled(bool state) +{ + _flags.SetFlag( PAINT_BACKGROUND_ENABLED, state ); +} + +void Panel::SetPaintBackgroundType( int type ) +{ + // HACK only 0 through 2 supported for now + m_nPaintBackgroundType = clamp( type, 0, 2 ); +} + +void Panel::SetPaintEnabled(bool state) +{ + _flags.SetFlag( PAINT_ENABLED, state ); +} + +void Panel::SetPostChildPaintEnabled(bool state) +{ + _flags.SetFlag( POST_CHILD_PAINT_ENABLED, state ); +} + +void Panel::GetInset(int& left,int& top,int& right,int& bottom) +{ + ipanel()->GetInset(GetVPanel(), left, top, right, bottom); +} + +void Panel::GetPaintSize(int& wide,int& tall) +{ + GetSize(wide, tall); + if (_border != null) + { + int left,top,right,bottom; + _border->GetInset(left,top,right,bottom); + + wide -= (left+right); + tall -= (top+bottom); + } +} + +int Panel::GetWide() +{ + int wide, tall; + ipanel()->GetSize(GetVPanel(), wide, tall); + return wide; +} + +void Panel::SetWide(int wide) +{ + ipanel()->SetSize(GetVPanel(), wide, GetTall()); +} + +int Panel::GetTall() +{ + int wide, tall; + ipanel()->GetSize(GetVPanel(), wide, tall); + return tall; +} + +void Panel::SetTall(int tall) +{ + ipanel()->SetSize(GetVPanel(), GetWide(), tall); +} + +void Panel::SetBuildGroup(BuildGroup* buildGroup) +{ + //TODO: remove from old group + + Assert(buildGroup != NULL); + + _buildGroup = buildGroup; + + _buildGroup->PanelAdded(this); +} + +bool Panel::IsBuildGroupEnabled() +{ + if ( !_buildGroup.IsValid() ) + return false; + + bool enabled = _buildGroup->IsEnabled(); + if ( enabled ) + return enabled; + + if ( GetParent() && GetParent()->IsBuildGroupEnabled() ) + return true; + + return false; +} + +void Panel::SetBgColor(Color color) +{ + _bgColor = color; +} + +void Panel::SetFgColor(Color color) +{ + _fgColor = color; +} + +Color Panel::GetBgColor() +{ + return _bgColor; +} + +Color Panel::GetFgColor() +{ + return _fgColor; +} + +void Panel::InternalPerformLayout() +{ + // Don't layout if we're still waiting for our scheme to be applied. + // At worst, it leads to crashes, at best it does work that we'll redo as soon as the scheme has been applied. + if ( _flags.IsFlagSet( NEEDS_SCHEME_UPDATE ) ) + return; + + _flags.SetFlag( IN_PERFORM_LAYOUT ); + // make sure the scheme has been applied + _flags.ClearFlag( NEEDS_LAYOUT ); + PerformLayout(); + _flags.ClearFlag( IN_PERFORM_LAYOUT ); +} + +void Panel::PerformLayout() +{ + // this should be overridden to relayout controls +} + +void Panel::InvalidateLayout( bool layoutNow, bool reloadScheme ) +{ + _flags.SetFlag( NEEDS_LAYOUT ); + + if (reloadScheme) + { + // make all our children reload the scheme + _flags.SetFlag( NEEDS_SCHEME_UPDATE ); + + for (int i = 0; i < GetChildCount(); i++) + { + vgui::Panel* panel = GetChild(i); + if( panel ) + { + panel->InvalidateLayout(layoutNow, true); + } + } + + PerformApplySchemeSettings(); + } + + if (layoutNow) + { + InternalPerformLayout(); + Repaint(); + } +} + +bool Panel::IsCursorNone() +{ + HCursor cursor = GetCursor(); + + if (!cursor) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the cursor is currently over the panel +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsCursorOver(void) +{ + int x, y; + input()->GetCursorPos(x, y); + return IsWithin(x, y); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a panel receives a command message from another panel +//----------------------------------------------------------------------------- +void Panel::OnCommand(const char *command) +{ + if ( !Q_stricmp( "performlayout", command ) ) + { + InvalidateLayout(); + } + else if ( !Q_stricmp( "reloadscheme", command ) ) + { + InvalidateLayout( false, true ); + } + else + { + // if noone else caught this, pass along to the listeners + // (this is useful for generic dialogs - otherwise, commands just get ignored) + KeyValues *msg = new KeyValues( command ); + PostActionSignal( msg ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: panel gained focus message +//----------------------------------------------------------------------------- +void Panel::OnSetFocus() +{ + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: panel lost focus message +//----------------------------------------------------------------------------- +void Panel::OnKillFocus() +{ + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the object up to be deleted next frame +//----------------------------------------------------------------------------- +void Panel::MarkForDeletion() +{ + if ( _flags.IsFlagSet( MARKED_FOR_DELETION ) ) + return; + + _flags.SetFlag( MARKED_FOR_DELETION ); + _flags.ClearFlag( AUTODELETE_ENABLED ); + + if (ivgui()->IsRunning()) + { + ivgui()->MarkPanelForDeletion(GetVPanel()); + } + // direct delete is never safe because even if ivgui is shutdown we manually do RunFrame() + // and we can enter here in a think traverse and then delete from underneath ourselves + /*else + { + delete this; + }*/ +} + +//----------------------------------------------------------------------------- +// Purpose: return true if this object require a perform layout +//----------------------------------------------------------------------------- +bool Panel::IsLayoutInvalid() +{ + return _flags.IsFlagSet( NEEDS_LAYOUT ); +} + + +//----------------------------------------------------------------------------- +// Sets the pin corner + resize mode for resizing panels +//----------------------------------------------------------------------------- +void Panel::SetAutoResize( PinCorner_e pinCorner, AutoResize_e resizeDir, + int nPinOffsetX, int nPinOffsetY, int nUnpinnedCornerOffsetX, int nUnpinnedCornerOffsetY ) +{ + _pinCorner = pinCorner; + _autoResizeDirection = resizeDir; + m_nPinDeltaX = nPinOffsetX; + m_nPinDeltaY = nPinOffsetY; + m_nResizeDeltaX = nUnpinnedCornerOffsetX; + m_nResizeDeltaY = nUnpinnedCornerOffsetY; +} + + +//----------------------------------------------------------------------------- +// Sets the pin corner for non-resizing panels +//----------------------------------------------------------------------------- +void Panel::SetPinCorner( PinCorner_e pinCorner, int nOffsetX, int nOffsetY ) +{ + _pinCorner = pinCorner; + _autoResizeDirection = AUTORESIZE_NO; + m_nPinDeltaX = nOffsetX; + m_nPinDeltaY = nOffsetY; + m_nResizeDeltaX = 0; + m_nResizeDeltaY = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +Panel::PinCorner_e Panel::GetPinCorner() +{ + return (PinCorner_e)_pinCorner; +} + + +//----------------------------------------------------------------------------- +// Gets the relative offset of the control from the pin corner +//----------------------------------------------------------------------------- +void Panel::GetPinOffset( int &dx, int &dy ) +{ + dx = m_nPinDeltaX; + dy = m_nPinDeltaY; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +Panel::AutoResize_e Panel::GetAutoResize() +{ + return (AutoResize_e)_autoResizeDirection; +} + + +//----------------------------------------------------------------------------- +// Gets the relative offset of the control from the pin corner +//----------------------------------------------------------------------------- +void Panel::GetResizeOffset( int &dx, int &dy ) +{ + dx = m_nResizeDeltaX; + dy = m_nResizeDeltaY; +} + +//----------------------------------------------------------------------------- +// Tells this panel that it should pin itself to the corner of a specified sibling panel +//----------------------------------------------------------------------------- +void Panel::PinToSibling( const char *pszSibling, PinCorner_e pinOurCorner, PinCorner_e pinSibling ) +{ + _pinCornerToSibling = pinOurCorner; + _pinToSiblingCorner = pinSibling; + + if ( m_pinSibling.Get() && _pinToSibling && pszSibling && !Q_strcmp( _pinToSibling, pszSibling ) ) + return; + + if (_pinToSibling) + { + delete [] _pinToSibling; + _pinToSibling = NULL; + } + + if (pszSibling) + { + int len = Q_strlen(pszSibling) + 1; + _pinToSibling = new char[ len ]; + Q_strncpy( _pinToSibling, pszSibling, len ); + } + m_pinSibling = NULL; + + UpdateSiblingPin(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::UpdateSiblingPin( void ) +{ + if ( !_pinToSibling ) + { + ipanel()->SetSiblingPin(GetVPanel(), NULL); + return; + } + + if ( !m_pinSibling.Get() ) + { + // Resolve our sibling now + m_pinSibling = FindSiblingByName( _pinToSibling ); + } + + if ( m_pinSibling.Get() ) + { + ipanel()->SetSiblingPin( GetVPanel(), m_pinSibling->GetVPanel(), _pinCornerToSibling, _pinToSiblingCorner ); + } + else + { + ipanel()->SetSiblingPin(GetVPanel(), NULL); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::ApplySchemeSettings(IScheme *pScheme) +{ + // get colors + SetFgColor(GetSchemeColor("Panel.FgColor", pScheme)); + SetBgColor(GetSchemeColor("Panel.BgColor", pScheme)); + +#if defined( VGUI_USEDRAGDROP ) + m_clrDragFrame = pScheme->GetColor("DragDrop.DragFrame", Color(255, 255, 255, 192)); + m_clrDropFrame = pScheme->GetColor("DragDrop.DropFrame", Color(150, 255, 150, 255)); + + m_infoFont = pScheme->GetFont( "DefaultVerySmall" ); +#endif + // mark us as no longer needing scheme settings applied + _flags.ClearFlag( NEEDS_SCHEME_UPDATE ); + + if ( IsBuildGroupEnabled() ) + { + _buildGroup->ApplySchemeSettings(pScheme); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if the panel needs it's scheme info setup +//----------------------------------------------------------------------------- +void Panel::PerformApplySchemeSettings() +{ + if ( _flags.IsFlagSet( NEEDS_DEFAULT_SETTINGS_APPLIED ) ) + { + InternalInitDefaultValues( GetAnimMap() ); + } + + if ( _flags.IsFlagSet( NEEDS_SCHEME_UPDATE ) ) + { + VPROF( "ApplySchemeSettings" ); + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + AssertOnce( pScheme ); + if ( pScheme ) // this should NEVER be null, but if it is bad things would happen in ApplySchemeSettings... + { + ApplySchemeSettings( pScheme ); + //_needsSchemeUpdate = false; + + ApplyOverridableColors(); + + UpdateSiblingPin(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Loads panel details related to autoresize from the resource info +//----------------------------------------------------------------------------- +#if defined( _DEBUG ) +static Panel *lastWarningParent = 0; +#endif + +void Panel::ApplyAutoResizeSettings(KeyValues *inResourceData) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s", __FUNCTION__, GetName() ); + + int x, y; + GetPos(x, y); + + int wide, tall; + GetSize( wide, tall ); + + AutoResize_e autoResize = (AutoResize_e)inResourceData->GetInt( "AutoResize", AUTORESIZE_NO ); + PinCorner_e pinCorner = (PinCorner_e)inResourceData->GetInt( "PinCorner", PIN_TOPLEFT ); + + // By default, measure unpinned corner for the offset + int pw = wide, pt = tall; + if ( GetParent() ) + { + GetParent()->GetSize( pw, pt ); +#if defined( _DEBUG ) + if ( pw == 64 && pt == 24 ) + { + if ( GetParent() != lastWarningParent ) + { + lastWarningParent = GetParent(); + Warning( "Resize parent (panel(%s) -> parent(%s)) not sized yet!!!\n", GetName(), GetParent()->GetName() ); + } + } +#endif + } + + int nPinnedCornerOffsetX = 0, nPinnedCornerOffsetY = 0; + int nUnpinnedCornerOffsetX = 0, nUnpinnedCornerOffsetY = 0; + switch( pinCorner ) + { + case PIN_TOPLEFT: + nPinnedCornerOffsetX = x; + nPinnedCornerOffsetY = y; + nUnpinnedCornerOffsetX = (x + wide) - pw; + nUnpinnedCornerOffsetY = (y + tall) - pt; + break; + + case PIN_TOPRIGHT: + nPinnedCornerOffsetX = (x + wide) - pw; + nPinnedCornerOffsetY = y; + nUnpinnedCornerOffsetX = x; + nUnpinnedCornerOffsetY = (y + tall) - pt; + break; + + case PIN_BOTTOMLEFT: + nPinnedCornerOffsetX = x; + nPinnedCornerOffsetY = (y + tall) - pt; + nUnpinnedCornerOffsetX = (x + wide) - pw; + nUnpinnedCornerOffsetY = y; + break; + + case PIN_BOTTOMRIGHT: + nPinnedCornerOffsetX = (x + wide) - pw; + nPinnedCornerOffsetY = (y + tall) - pt; + nUnpinnedCornerOffsetX = x; + nUnpinnedCornerOffsetY = y; + break; + } + + // Allow specific overrides in the resource file + if ( IsProportional() ) + { + if ( inResourceData->FindKey( "PinnedCornerOffsetX" ) ) + { + nPinnedCornerOffsetX = scheme()->GetProportionalScaledValueEx( GetScheme(), inResourceData->GetInt( "PinnedCornerOffsetX" ) ); + } + if ( inResourceData->FindKey( "PinnedCornerOffsetY" ) ) + { + nPinnedCornerOffsetY = scheme()->GetProportionalScaledValueEx( GetScheme(), inResourceData->GetInt( "PinnedCornerOffsetY" ) ); + } + if ( inResourceData->FindKey( "UnpinnedCornerOffsetX" ) ) + { + nUnpinnedCornerOffsetX = scheme()->GetProportionalScaledValueEx( GetScheme(), inResourceData->GetInt( "UnpinnedCornerOffsetX" ) ); + } + if ( inResourceData->FindKey( "UnpinnedCornerOffsetY" ) ) + { + nUnpinnedCornerOffsetY = scheme()->GetProportionalScaledValueEx( GetScheme(), inResourceData->GetInt( "UnpinnedCornerOffsetY" ) ); + } + } + else + { + nPinnedCornerOffsetX = inResourceData->GetInt( "PinnedCornerOffsetX", nPinnedCornerOffsetX ); + nPinnedCornerOffsetY = inResourceData->GetInt( "PinnedCornerOffsetY", nPinnedCornerOffsetY ); + nUnpinnedCornerOffsetX = inResourceData->GetInt( "UnpinnedCornerOffsetX", nUnpinnedCornerOffsetX ); + nUnpinnedCornerOffsetY = inResourceData->GetInt( "UnpinnedCornerOffsetY", nUnpinnedCornerOffsetY ); + } + + if ( autoResize == AUTORESIZE_NO ) + { + nUnpinnedCornerOffsetX = nUnpinnedCornerOffsetY = 0; + } + + SetAutoResize( pinCorner, autoResize, nPinnedCornerOffsetX, nPinnedCornerOffsetY, nUnpinnedCornerOffsetX, nUnpinnedCornerOffsetY ); +} + +ConVar panel_test_title_safe( "panel_test_title_safe", "0", FCVAR_CHEAT, "Test vgui panel positioning with title safe indentation" ); + + + + + +Panel::PinCorner_e GetPinCornerFromString( const char* pszCornerName ) +{ + if ( pszCornerName == NULL ) + { + return Panel::PIN_TOPLEFT; + } + + // Optimize for all the old entries of a single digit + if ( strlen( pszCornerName ) == 1 ) + { + return (Panel::PinCorner_e)atoi( pszCornerName ); + } + + for( int i=0; i<ARRAYSIZE( g_PinCornerStrings ); ++i ) + { + if ( !Q_stricmp( g_PinCornerStrings[i], pszCornerName ) ) + { + return (Panel::PinCorner_e)i; + } + } + + return Panel::PIN_TOPLEFT; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads panel details from the resource info +//----------------------------------------------------------------------------- +void Panel::ApplySettings(KeyValues *inResourceData) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s", __FUNCTION__, GetName() ); + + // First restore to default values + if ( _flags.IsFlagSet( NEEDS_DEFAULT_SETTINGS_APPLIED ) ) + { + InternalInitDefaultValues( GetAnimMap() ); + } + + // Let PanelAnimationVars auto-retrieve settings (we restore defaults above + // since a script might be missing certain values) + InternalApplySettings( GetAnimMap(), inResourceData ); + + // clear any alignment flags + _buildModeFlags &= ~( BUILDMODE_SAVE_XPOS_RIGHTALIGNED + | BUILDMODE_SAVE_XPOS_CENTERALIGNED + | BUILDMODE_SAVE_YPOS_BOTTOMALIGNED + | BUILDMODE_SAVE_YPOS_CENTERALIGNED + | BUILDMODE_SAVE_WIDE_FULL + | BUILDMODE_SAVE_TALL_FULL + | BUILDMODE_SAVE_PROPORTIONAL_TO_PARENT + | BUILDMODE_SAVE_WIDE_PROPORTIONAL + | BUILDMODE_SAVE_TALL_PROPORTIONAL + | BUILDMODE_SAVE_XPOS_PROPORTIONAL_SELF + | BUILDMODE_SAVE_YPOS_PROPORTIONAL_SELF + | BUILDMODE_SAVE_WIDE_PROPORTIONAL_TALL + | BUILDMODE_SAVE_TALL_PROPORTIONAL_WIDE + | BUILDMODE_SAVE_XPOS_PROPORTIONAL_PARENT + | BUILDMODE_SAVE_YPOS_PROPORTIONAL_PARENT + | BUILDMODE_SAVE_WIDE_PROPORTIONAL_SELF + | BUILDMODE_SAVE_TALL_PROPORTIONAL_SELF ); + + // get the position + int alignScreenWide, alignScreenTall; // screen dimensions used for pinning in splitscreen + surface()->GetScreenSize( alignScreenWide, alignScreenTall ); + + int screenWide = alignScreenWide; + int screenTall = alignScreenTall; + + // temporarily remove the override to get the fullscreen dimensions + if ( surface()->IsScreenSizeOverrideActive() ) + { + surface()->ForceScreenSizeOverride( false, 0, 0 ); + surface()->GetScreenSize( screenWide, screenTall ); + + // restore the override + surface()->ForceScreenSizeOverride( true, alignScreenWide, alignScreenTall ); + } + + int parentX = 0; + int parentY = 0; + + // flag to cause windows to get screenWide and screenTall from their parents, + // this allows children windows to use fill and right/bottom alignment even + // if their parent does not use the full screen. + if ( inResourceData->GetInt( "proportionalToParent", 0 ) == 1 ) + { + _buildModeFlags |= BUILDMODE_SAVE_PROPORTIONAL_TO_PARENT; + if ( GetParent() != NULL ) + { + GetParent()->GetBounds( parentX, parentY, alignScreenWide, alignScreenTall ); + } + } + + // size + int wide = ComputeWide( this, _buildModeFlags, inResourceData, alignScreenWide, alignScreenTall, false ); + int tall = ComputeTall( this, _buildModeFlags, inResourceData, alignScreenWide, alignScreenTall, false ); + + int x, y; + GetPos(x, y); + const char *xstr = inResourceData->GetString( "xpos", NULL ); + const char *ystr = inResourceData->GetString( "ypos", NULL ); + _buildModeFlags |= ComputePos( this, xstr, x, wide, alignScreenWide, true, OP_SET ); + _buildModeFlags |= ComputePos( this, ystr, y, tall, alignScreenTall, false, OP_SET ); + + + bool bUsesTitleSafeArea = false; + int titleSafeWide = 0; + int titleSafeTall = 0; + + Rect_t excludeEdgeFromTitleSafe; // if a side is set to != 0, don't title safe relative to that edge + excludeEdgeFromTitleSafe.x = 0; + excludeEdgeFromTitleSafe.y = 0; + excludeEdgeFromTitleSafe.width = 0; + excludeEdgeFromTitleSafe.height = 0; + + if ( IsX360() || panel_test_title_safe.GetBool() ) + { + // "usetitlesafe" "1" - required inner 90% + // "usetitlesafe" "2" - suggested inner 85% + + int iUseTitleSafeValue = 0; + if ( inResourceData->FindKey( "usetitlesafe" ) ) + { + iUseTitleSafeValue = inResourceData->GetInt( "usetitlesafe" ); + bUsesTitleSafeArea = ( iUseTitleSafeValue > 0 ); + } + + if( bUsesTitleSafeArea ) + { + titleSafeWide = screenWide * ( iUseTitleSafeValue == 1 ? 0.05f : 0.075f ); + titleSafeTall = screenTall * ( iUseTitleSafeValue == 1 ? 0.05f : 0.075f ); + + // Don't title safe internal boundaries for split screen viewports + int splitX = 0; + int splitY = 0; + vgui::surface()->OffsetAbsPos( splitX, splitY ); + + bool bHorizontalSplit = ( alignScreenTall != screenTall ); + bool bVerticalSplit = ( alignScreenWide != screenWide ); + + if ( bHorizontalSplit ) + { + // top or bottom? + if ( splitY != parentY ) + { + excludeEdgeFromTitleSafe.y = 1; + } + else + { + excludeEdgeFromTitleSafe.height = 1; + } + } + + if ( bVerticalSplit ) + { + // left or right + if ( splitX != parentX ) + { + excludeEdgeFromTitleSafe.x = 1; + } + else + { + excludeEdgeFromTitleSafe.width = 1; + } + } + + if ( _buildModeFlags & BUILDMODE_SAVE_XPOS_RIGHTALIGNED ) + { + if ( !excludeEdgeFromTitleSafe.width ) + { + x -= titleSafeWide; // right edge + } + } + else if (_buildModeFlags & BUILDMODE_SAVE_XPOS_CENTERALIGNED) + { + } + else if ( !excludeEdgeFromTitleSafe.x ) + { + x += titleSafeWide; // left edge + } + + if ( _buildModeFlags & BUILDMODE_SAVE_YPOS_BOTTOMALIGNED ) + { + if ( !excludeEdgeFromTitleSafe.height ) + { + y -= titleSafeTall; // bottom edge + } + } + else if (_buildModeFlags & BUILDMODE_SAVE_YPOS_CENTERALIGNED) + { + } + else if ( !excludeEdgeFromTitleSafe.y ) + { + y += titleSafeTall; // top edge + } + } + } + SetNavUp( inResourceData->GetString("navUp") ); + SetNavDown( inResourceData->GetString("navDown") ); + SetNavLeft( inResourceData->GetString("navLeft") ); + SetNavRight( inResourceData->GetString("navRight") ); + SetNavToRelay( inResourceData->GetString("navToRelay") ); + SetNavActivate( inResourceData->GetString("navActivate") ); + SetNavBack( inResourceData->GetString("navBack") ); + + SetPos(x, y); + + if (inResourceData->FindKey( "zpos" )) + { + SetZPos( inResourceData->GetInt( "zpos" ) ); + } + + if( bUsesTitleSafeArea ) + { + if ( _buildModeFlags & BUILDMODE_SAVE_WIDE_FULL ) + { + if ( !excludeEdgeFromTitleSafe.x ) + wide -= titleSafeWide; + + if ( !excludeEdgeFromTitleSafe.width ) + wide -= titleSafeWide; + } + + if ( _buildModeFlags & BUILDMODE_SAVE_TALL_FULL ) + { + if ( !excludeEdgeFromTitleSafe.y ) + tall -= titleSafeTall; + + if ( !excludeEdgeFromTitleSafe.height ) + tall -= titleSafeTall; + } + } + + SetSize( wide, tall ); + + // NOTE: This has to happen after pos + size is set + ApplyAutoResizeSettings( inResourceData ); + + // only get colors if we're ignoring the scheme + if (inResourceData->GetInt("IgnoreScheme", 0)) + { + PerformApplySchemeSettings(); + } + + // state + int state = inResourceData->GetInt("visible", 1); + if (state == 0) + { + SetVisible(false); + } + else if (state == 1) + { + SetVisible(true); + } + + SetEnabled( inResourceData->GetInt("enabled", true) ); + + bool bMouseEnabled = inResourceData->GetInt( "mouseinputenabled", true ); + if ( !bMouseEnabled ) + { + SetMouseInputEnabled( false ); + } + + // tab order + SetTabPosition(inResourceData->GetInt("tabPosition", 0)); + + const char *tooltip = inResourceData->GetString("tooltiptext", NULL); + if (tooltip && *tooltip) + { + GetTooltip()->SetText(tooltip); + } + + // paint background? + int nPaintBackground = inResourceData->GetInt("paintbackground", -1); + if (nPaintBackground >= 0) + { + SetPaintBackgroundEnabled( nPaintBackground != 0 ); + } + + // paint border? + int nPaintBorder = inResourceData->GetInt("paintborder", -1); + if (nPaintBorder >= 0) + { + SetPaintBorderEnabled( nPaintBorder != 0 ); + } + + // border? + const char *pBorder = inResourceData->GetString( "border", "" ); + if ( *pBorder ) + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + SetBorder( pScheme->GetBorder( pBorder ) ); + } + + // check to see if we have a new name assigned + const char *newName = inResourceData->GetString("fieldName", NULL); + if ( newName ) + { + // Only slam the name if the new one differs... + SetName(newName); + } + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s: Action signal", __FUNCTION__, GetName() ); + // Automatically add an action signal target if one is specified. This allows for + // nested child buttons to add their distant parents as action signal targets. + int nActionSignalLevel = inResourceData->GetInt( "actionsignallevel", -1 ); + if ( nActionSignalLevel != -1 ) + { + Panel *pActionSignalTarget = this; + while( nActionSignalLevel-- ) + { + pActionSignalTarget = pActionSignalTarget->GetParent(); + } + AddActionSignalTarget( pActionSignalTarget ); + } + + // check to see if we need to render to the frame buffer even if + // stereo mode is trying to render all of the ui to a render target + m_bForceStereoRenderToFrameBuffer = inResourceData->GetBool( "ForceStereoRenderToFrameBuffer", false ); + + //============================================================================= + // HPE_BEGIN: + // [pfreese] Support for reading rounded corner flags + //============================================================================= + int roundedCorners = inResourceData->GetInt( "RoundedCorners", -1 ); + if ( roundedCorners >= 0 ) + { + m_roundedCorners = roundedCorners; + } + //============================================================================= + // HPE_END + //============================================================================= + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s: Pin Sibling", __FUNCTION__, GetName() ); + const char *pszSiblingName = inResourceData->GetString("pin_to_sibling", NULL); + PinCorner_e pinOurCornerToSibling = GetPinCornerFromString( inResourceData->GetString( "pin_corner_to_sibling", NULL ) ); + PinCorner_e pinSiblingCorner = GetPinCornerFromString( inResourceData->GetString( "pin_to_sibling_corner", NULL ) ); + PinToSibling( pszSiblingName, pinOurCornerToSibling, pinSiblingCorner ); + + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s: Color overrides", __FUNCTION__, GetName() ); + // Allow overriding of colors. Used mostly by HUD elements, where scheme color usage is often undesired. + IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + for ( int i = 0; i < m_OverridableColorEntries.Count(); i++ ) + { + // Need to ensure the key exists, so we don't overwrite existing colors when it's not set. + if ( inResourceData->FindKey( m_OverridableColorEntries[i].m_pszScriptName, false ) ) + { + // Get the color as a string - test whether it is an actual color or a reference to a scheme color + const char *pColorStr = inResourceData->GetString( m_OverridableColorEntries[i].m_pszScriptName ); + Color &clrDest = m_OverridableColorEntries[i].m_colFromScript; + if ( pColorStr[0] == '.' || isdigit( pColorStr[0] ) ) + { + float r = 0.0f, g = 0.0f, b = 0.0f, a = 0.0f; + sscanf( pColorStr, "%f %f %f %f", &r, &g, &b, &a ); + clrDest[0] = (unsigned char)r; + clrDest[1] = (unsigned char)g; + clrDest[2] = (unsigned char)b; + clrDest[3] = (unsigned char)a; + } + else + { + // First character wasn't a digit or a decimal - do a scheme color lookup + clrDest = pScheme->GetColor( pColorStr, Color( 255, 255, 255, 255 ) ); + } + + (*m_OverridableColorEntries[i].m_pColor) = m_OverridableColorEntries[i].m_colFromScript; + m_OverridableColorEntries[i].m_bOverridden = true; + } + } + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s: Keyboard enabled", __FUNCTION__, GetName() ); + const char *pKeyboardInputEnabled = inResourceData->GetString( "keyboardinputenabled", NULL ); + if ( pKeyboardInputEnabled && pKeyboardInputEnabled[0] ) + { + SetKeyBoardInputEnabled( atoi( pKeyboardInputEnabled ) ); + } + + OnChildSettingsApplied( inResourceData, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Saves out a resource description of this panel +//----------------------------------------------------------------------------- +void Panel::GetSettings( KeyValues *outResourceData ) +{ + // control class name (so it can be recreated later if needed) + outResourceData->SetString( "ControlName", GetClassName() ); + + // name + outResourceData->SetString( "fieldName", _panelName ); + + // positioning + int screenWide, screenTall; + surface()->GetScreenSize(screenWide, screenTall); + int x, y; + GetPos( x, y ); + if ( IsProportional() ) + { + x = scheme()->GetProportionalNormalizedValueEx( GetScheme(), x ); + y = scheme()->GetProportionalNormalizedValueEx( GetScheme(), y ); + } + // correct for alignment + if (_buildModeFlags & BUILDMODE_SAVE_XPOS_RIGHTALIGNED) + { + x = screenWide - x; + char xstr[32]; + Q_snprintf(xstr, sizeof( xstr ), "r%d", x); + outResourceData->SetString( "xpos", xstr ); + } + else if (_buildModeFlags & BUILDMODE_SAVE_XPOS_CENTERALIGNED) + { + x = (screenWide / 2) + x; + char xstr[32]; + Q_snprintf(xstr, sizeof( xstr ), "c%d", x); + outResourceData->SetString( "xpos", xstr ); + } + else + { + outResourceData->SetInt( "xpos", x ); + } + if (_buildModeFlags & BUILDMODE_SAVE_YPOS_BOTTOMALIGNED) + { + y = screenTall - y; + char ystr[32]; + Q_snprintf(ystr, sizeof( ystr ), "r%d", y); + outResourceData->SetString( "ypos", ystr ); + } + else if (_buildModeFlags & BUILDMODE_SAVE_YPOS_CENTERALIGNED) + { + y = (screenTall / 2) + y; + char ystr[32]; + Q_snprintf(ystr, sizeof( ystr ), "c%d", y); + outResourceData->SetString( "ypos", ystr ); + } + else + { + outResourceData->SetInt( "ypos", y ); + } + if (m_pTooltips) + { + if (strlen(m_pTooltips->GetText()) > 0) + { + outResourceData->SetString("tooltiptext", m_pTooltips->GetText()); + } + } + int wide, tall; + GetSize( wide, tall ); + if ( IsProportional() ) + { + wide = scheme()->GetProportionalNormalizedValueEx( GetScheme(), wide ); + tall = scheme()->GetProportionalNormalizedValueEx( GetScheme(), tall ); + } + + int z = ipanel()->GetZPos(GetVPanel()); + if (z) + { + outResourceData->SetInt("zpos", z); + } + + // Correct for alignment + if (_buildModeFlags & BUILDMODE_SAVE_WIDE_FULL ) + { + wide = screenWide - wide; + char wstr[32]; + Q_snprintf(wstr, sizeof( wstr ), "f%d", wide); + outResourceData->SetString( "wide", wstr ); + } + else + { + outResourceData->SetInt( "wide", wide ); + } + outResourceData->SetInt( "tall", tall ); + + outResourceData->SetInt("AutoResize", GetAutoResize()); + outResourceData->SetInt("PinCorner", GetPinCorner()); + + //============================================================================= + // HPE_BEGIN: + // [pfreese] Support for writing out rounded corner flags + //============================================================================= + outResourceData->SetInt("RoundedCorners", m_roundedCorners); + //============================================================================= + // HPE_END + //============================================================================= + + outResourceData->SetString( "pin_to_sibling", _pinToSibling ); + outResourceData->SetInt("pin_corner_to_sibling", _pinCornerToSibling ); + outResourceData->SetInt("pin_to_sibling_corner", _pinToSiblingCorner ); + + + // state + outResourceData->SetInt( "visible", IsVisible() ); + outResourceData->SetInt( "enabled", IsEnabled() ); + + outResourceData->SetInt( "tabPosition", GetTabPosition() ); + + for ( int i = 0; i < m_OverridableColorEntries.Count(); i++ ) + { + if ( m_OverridableColorEntries[i].m_bOverridden ) + { + outResourceData->SetColor( m_OverridableColorEntries[i].m_pszScriptName, m_OverridableColorEntries[i].m_colFromScript ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: After applying settings, apply overridable colors. +// Done post apply settings, so that baseclass settings don't stomp +// the script specified override colors. +//----------------------------------------------------------------------------- +void Panel::ApplyOverridableColors( void ) +{ + for ( int i = 0; i < m_OverridableColorEntries.Count(); i++ ) + { + if ( m_OverridableColorEntries[i].m_bOverridden ) + { + (*m_OverridableColorEntries[i].m_pColor) = m_OverridableColorEntries[i].m_colFromScript; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetOverridableColor( Color *pColor, const Color &newColor ) +{ + for ( int i = 0; i < m_OverridableColorEntries.Count(); i++ ) + { + if ( m_OverridableColorEntries[i].m_bOverridden ) + { + if ( m_OverridableColorEntries[i].m_pColor == pColor ) + return; + } + } + + // Didn't find it, or it's not been overridden. + *pColor = newColor; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color Panel::GetSchemeColor(const char *keyName, IScheme *pScheme) +{ + return pScheme->GetColor(keyName, Color(255, 255, 255, 255)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color Panel::GetSchemeColor(const char *keyName, Color defaultColor, IScheme *pScheme) +{ + return pScheme->GetColor(keyName, defaultColor); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a string description of the panel fields for use in the UI +//----------------------------------------------------------------------------- +const char *Panel::GetDescription( void ) +{ + static const char *panelDescription = "string fieldName, int xpos, int ypos, int wide, int tall, bool visible, bool enabled, int tabPosition, corner pinCorner, autoresize autoResize, string tooltiptext"; + return panelDescription; +} + +//----------------------------------------------------------------------------- +// Purpose: user configuration settings +// this is used for any control details the user wants saved between sessions +// eg. dialog positions, last directory opened, list column width +//----------------------------------------------------------------------------- +void Panel::ApplyUserConfigSettings(KeyValues *userConfig) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: returns user config settings for this control +//----------------------------------------------------------------------------- +void Panel::GetUserConfigSettings(KeyValues *userConfig) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: optimization, return true if this control has any user config settings +//----------------------------------------------------------------------------- +bool Panel::HasUserConfigSettings() +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::InternalInvalidateLayout() +{ + InvalidateLayout(false, false); +} + +//----------------------------------------------------------------------------- +// Purpose: called whenever the panel moves +//----------------------------------------------------------------------------- +void Panel::OnMove() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::InternalMove() +{ + OnMove(); + for(int i=0;i<GetChildCount();i++) + { + // recursively apply to all children + GetChild(i)->OnMove(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: empty function +//----------------------------------------------------------------------------- +void Panel::OnTick() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: versioning +//----------------------------------------------------------------------------- +void *Panel::QueryInterface(EInterfaceID id) +{ + if (id == ICLIENTPANEL_STANDARD_INTERFACE) + { + return this; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Map all the base messages to functions +// ordering from most -> least used improves speed +//----------------------------------------------------------------------------- +MessageMapItem_t Panel::m_MessageMap[] = +{ + MAP_MESSAGE_INT( Panel, "RequestFocus", RequestFocus, "direction" ) +}; + +// IMPLEMENT_PANELMAP( Panel, NULL ) +PanelMap_t Panel::m_PanelMap = { Panel::m_MessageMap, ARRAYSIZE(Panel::m_MessageMap), "Panel", NULL }; +PanelMap_t *Panel::GetPanelMap( void ) { return &m_PanelMap; } + +//----------------------------------------------------------------------------- +// Purpose: !! Soon to replace existing prepare panel map +//----------------------------------------------------------------------------- +void PreparePanelMessageMap(PanelMessageMap *panelMap) +{ + // iterate through the class hierarchy message maps + while ( panelMap != NULL && !panelMap->processed ) + { + // hash message map strings into symbols + for (int i = 0; i < panelMap->entries.Count(); i++) + { + MessageMapItem_t *item = &panelMap->entries[i]; + + if (item->name) + { + item->nameSymbol = KeyValuesSystem()->GetSymbolForString(item->name); + } + else + { + item->nameSymbol = INVALID_KEY_SYMBOL; + } + if (item->firstParamName) + { + item->firstParamSymbol = KeyValuesSystem()->GetSymbolForString(item->firstParamName); + } + else + { + item->firstParamSymbol = INVALID_KEY_SYMBOL; + } + if (item->secondParamName) + { + item->secondParamSymbol = KeyValuesSystem()->GetSymbolForString(item->secondParamName); + } + else + { + item->secondParamSymbol = INVALID_KEY_SYMBOL; + } + } + + panelMap->processed = true; + panelMap = panelMap->baseMap; + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Handles a message +// Dispatches the message to a set of message maps +//----------------------------------------------------------------------------- +void Panel::OnMessage(const KeyValues *params, VPANEL ifromPanel) +{ + PanelMessageMap *panelMap = GetMessageMap(); + bool bFound = false; + int iMessageName = params->GetNameSymbol(); + + if ( !panelMap->processed ) + { + PreparePanelMessageMap( panelMap ); + } + + // iterate through the class hierarchy message maps + for ( ; panelMap != NULL && !bFound; panelMap = panelMap->baseMap ) + { +#if defined( _DEBUG ) +// char const *className = panelMap->pfnClassName(); +// NOTE_UNUSED( className ); +#endif + + // iterate all the entries in the panel map + for ( int i = 0; i < panelMap->entries.Count(); i++ ) + { + MessageMapItem_t *pMap = &panelMap->entries[i]; + + if (iMessageName == pMap->nameSymbol) + { + bFound = true; + + switch (pMap->numParams) + { + case 0: + { + (this->*(pMap->func))(); + break; + } + + case 1: + { + KeyValues *param1 = params->FindKey(pMap->firstParamSymbol); + if (!param1) + { + param1 = const_cast<KeyValues *>(params); + } + + switch ( pMap->firstParamType ) + { + case DATATYPE_INT: + typedef void (Panel::*MessageFunc_Int_t)(int); + (this->*((MessageFunc_Int_t)pMap->func))( param1->GetInt() ); + break; + + case DATATYPE_UINT64: + typedef void (Panel::*MessageFunc_Uin64_t)(uint64); + (this->*((MessageFunc_Uin64_t)pMap->func))( param1->GetUint64() ); + break; + + case DATATYPE_PTR: + typedef void (Panel::*MessageFunc_Ptr_t)( void * ); + (this->*((MessageFunc_Ptr_t)pMap->func))( param1->GetPtr() ); + break; + + case DATATYPE_HANDLE: + { + typedef void (Panel::*MessageFunc_VPANEL_t)( VPANEL ); + VPANEL vpanel = ivgui()->HandleToPanel( param1->GetInt() ); + (this->*((MessageFunc_VPANEL_t)pMap->func))( vpanel ); + } + break; + + case DATATYPE_FLOAT: + typedef void (Panel::*MessageFunc_Float_t)( float ); + (this->*((MessageFunc_Float_t)pMap->func))( param1->GetFloat() ); + break; + + case DATATYPE_CONSTCHARPTR: + typedef void (Panel::*MessageFunc_CharPtr_t)( const char * ); + (this->*((MessageFunc_CharPtr_t)pMap->func))( param1->GetString() ); + break; + + case DATATYPE_CONSTWCHARPTR: + typedef void (Panel::*MessageFunc_WCharPtr_t)( const wchar_t * ); + (this->*((MessageFunc_WCharPtr_t)pMap->func))( param1->GetWString() ); + break; + + case DATATYPE_KEYVALUES: + typedef void (Panel::*MessageFunc_KeyValues_t)(KeyValues *); + if ( pMap->firstParamName ) + { + (this->*((MessageFunc_KeyValues_t)pMap->func))( (KeyValues *)param1->GetPtr() ); + } + else + { + // no param set, so pass in the whole thing + (this->*((MessageFunc_KeyValues_t)pMap->func))( const_cast<KeyValues *>(params) ); + } + break; + + default: + Assert(!("No handler for vgui message function")); + break; + } + break; + } + + case 2: + { + KeyValues *param1 = params->FindKey(pMap->firstParamSymbol); + if (!param1) + { + param1 = const_cast<KeyValues *>(params); + } + KeyValues *param2 = params->FindKey(pMap->secondParamSymbol); + if (!param2) + { + param2 = const_cast<KeyValues *>(params); + } + + if ( (DATATYPE_INT == pMap->firstParamType) && (DATATYPE_INT == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_IntInt_t)(int, int); + (this->*((MessageFunc_IntInt_t)pMap->func))( param1->GetInt(), param2->GetInt() ); + } + else if ( (DATATYPE_PTR == pMap->firstParamType) && (DATATYPE_INT == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_PtrInt_t)(void *, int); + (this->*((MessageFunc_PtrInt_t)pMap->func))( param1->GetPtr(), param2->GetInt() ); + } + else if ( (DATATYPE_CONSTCHARPTR == pMap->firstParamType) && (DATATYPE_INT == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_ConstCharPtrInt_t)(const char *, int); + (this->*((MessageFunc_ConstCharPtrInt_t)pMap->func))( param1->GetString(), param2->GetInt() ); + } + else if ( (DATATYPE_CONSTCHARPTR == pMap->firstParamType) && (DATATYPE_CONSTCHARPTR == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_ConstCharPtrConstCharPtr_t)(const char *, const char *); + (this->*((MessageFunc_ConstCharPtrConstCharPtr_t)pMap->func))( param1->GetString(), param2->GetString() ); + } + else if ( (DATATYPE_INT == pMap->firstParamType) && (DATATYPE_CONSTCHARPTR == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_IntConstCharPtr_t)(int, const char *); + (this->*((MessageFunc_IntConstCharPtr_t)pMap->func))( param1->GetInt(), param2->GetString() ); + } + else if ( (DATATYPE_PTR == pMap->firstParamType) && (DATATYPE_CONSTCHARPTR == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_PtrConstCharPtr_t)(void *, const char *); + (this->*((MessageFunc_PtrConstCharPtr_t)pMap->func))( param1->GetPtr(), param2->GetString() ); + } + else if ( (DATATYPE_PTR == pMap->firstParamType) && (DATATYPE_CONSTWCHARPTR == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_PtrConstCharPtr_t)(void *, const wchar_t *); + (this->*((MessageFunc_PtrConstCharPtr_t)pMap->func))( param1->GetPtr(), param2->GetWString() ); + } + else if ( (DATATYPE_HANDLE == pMap->firstParamType) && (DATATYPE_CONSTCHARPTR == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_HandleConstCharPtr_t)(VPANEL, const char *); + VPANEL vp = ivgui()->HandleToPanel( param1->GetInt() ); + (this->*((MessageFunc_HandleConstCharPtr_t)pMap->func))( vp, param2->GetString() ); + } + else if ( (DATATYPE_HANDLE == pMap->firstParamType) && (DATATYPE_CONSTWCHARPTR == pMap->secondParamType) ) + { + typedef void (Panel::*MessageFunc_HandleConstCharPtr_t)(VPANEL, const wchar_t *); + VPANEL vp = ivgui()->HandleToPanel( param1->GetInt() ); + (this->*((MessageFunc_HandleConstCharPtr_t)pMap->func))( vp, param2->GetWString() ); + } + else + { + // the message isn't handled + ivgui()->DPrintf( "Message '%s', sent to '%s', has invalid parameter types\n", params->GetName(), GetName() ); + } + break; + } + + default: + Assert(!("Invalid number of parameters")); + break; + } + + // break the loop + bFound = true; + break; + } + } + } + + if (!bFound) + { + OnOldMessage(const_cast<KeyValues *>(params), ifromPanel); + } +} + +void Panel::OnOldMessage(KeyValues *params, VPANEL ifromPanel) +{ + bool bFound = false; + // message map dispatch + int iMessageName = params->GetNameSymbol(); + + PanelMap_t *panelMap = GetPanelMap(); + if ( !panelMap->processed ) + { + PreparePanelMap( panelMap ); + } + + // iterate through the class hierarchy message maps + for ( ; panelMap != NULL && !bFound; panelMap = panelMap->baseMap ) + { + MessageMapItem_t *pMessageMap = panelMap->dataDesc; + + for ( int i = 0; i < panelMap->dataNumFields; i++ ) + { + if (iMessageName == pMessageMap[i].nameSymbol) + { + // call the mapped function + switch ( pMessageMap[i].numParams ) + { + case 2: + if ( (DATATYPE_INT == pMessageMap[i].firstParamType) && (DATATYPE_INT == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_IntInt_t)(int, int); + (this->*((MessageFunc_IntInt_t)pMessageMap[i].func))( params->GetInt(pMessageMap[i].firstParamName), params->GetInt(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_PTR == pMessageMap[i].firstParamType) && (DATATYPE_INT == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_PtrInt_t)(void *, int); + (this->*((MessageFunc_PtrInt_t)pMessageMap[i].func))( params->GetPtr(pMessageMap[i].firstParamName), params->GetInt(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_CONSTCHARPTR == pMessageMap[i].firstParamType) && (DATATYPE_INT == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_ConstCharPtrInt_t)(const char *, int); + (this->*((MessageFunc_ConstCharPtrInt_t)pMessageMap[i].func))( params->GetString(pMessageMap[i].firstParamName), params->GetInt(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_CONSTCHARPTR == pMessageMap[i].firstParamType) && (DATATYPE_CONSTCHARPTR == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_ConstCharPtrConstCharPtr_t)(const char *, const char *); + (this->*((MessageFunc_ConstCharPtrConstCharPtr_t)pMessageMap[i].func))( params->GetString(pMessageMap[i].firstParamName), params->GetString(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_INT == pMessageMap[i].firstParamType) && (DATATYPE_CONSTCHARPTR == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_IntConstCharPtr_t)(int, const char *); + (this->*((MessageFunc_IntConstCharPtr_t)pMessageMap[i].func))( params->GetInt(pMessageMap[i].firstParamName), params->GetString(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_PTR == pMessageMap[i].firstParamType) && (DATATYPE_CONSTCHARPTR == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_PtrConstCharPtr_t)(void *, const char *); + (this->*((MessageFunc_PtrConstCharPtr_t)pMessageMap[i].func))( params->GetPtr(pMessageMap[i].firstParamName), params->GetString(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_PTR == pMessageMap[i].firstParamType) && (DATATYPE_CONSTWCHARPTR == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_PtrConstCharPtr_t)(void *, const wchar_t *); + (this->*((MessageFunc_PtrConstCharPtr_t)pMessageMap[i].func))( params->GetPtr(pMessageMap[i].firstParamName), params->GetWString(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_HANDLE == pMessageMap[i].firstParamType) && (DATATYPE_CONSTCHARPTR ==pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_HandleConstCharPtr_t)(VPANEL, const char *); + VPANEL vp = ivgui()->HandleToPanel( params->GetInt( pMessageMap[i].firstParamName ) ); + (this->*((MessageFunc_HandleConstCharPtr_t)pMessageMap[i].func))( vp, params->GetString(pMessageMap[i].secondParamName) ); + } + else if ( (DATATYPE_HANDLE == pMessageMap[i].firstParamType) && (DATATYPE_CONSTWCHARPTR == pMessageMap[i].secondParamType) ) + { + typedef void (Panel::*MessageFunc_HandleConstCharPtr_t)(VPANEL, const wchar_t *); + VPANEL vp = ivgui()->HandleToPanel( params->GetInt( pMessageMap[i].firstParamName ) ); + (this->*((MessageFunc_HandleConstCharPtr_t)pMessageMap[i].func))( vp, params->GetWString(pMessageMap[i].secondParamName) ); + } + else + { + // the message isn't handled + ivgui()->DPrintf( "Message '%s', sent to '%s', has invalid parameter types\n", params->GetName(), GetName() ); + } + break; + + case 1: + switch ( pMessageMap[i].firstParamType ) + { + case DATATYPE_BOOL: + typedef void (Panel::*MessageFunc_Bool_t)(bool); + (this->*((MessageFunc_Bool_t)pMessageMap[i].func))( (bool)params->GetInt(pMessageMap[i].firstParamName) ); + break; + + case DATATYPE_CONSTCHARPTR: + typedef void (Panel::*MessageFunc_ConstCharPtr_t)(const char *); + (this->*((MessageFunc_ConstCharPtr_t)pMessageMap[i].func))( (const char *)params->GetString(pMessageMap[i].firstParamName) ); + break; + + case DATATYPE_CONSTWCHARPTR: + typedef void (Panel::*MessageFunc_ConstCharPtr_t)(const char *); + (this->*((MessageFunc_ConstCharPtr_t)pMessageMap[i].func))( (const char *)params->GetWString(pMessageMap[i].firstParamName) ); + break; + + case DATATYPE_INT: + typedef void (Panel::*MessageFunc_Int_t)(int); + (this->*((MessageFunc_Int_t)pMessageMap[i].func))( params->GetInt(pMessageMap[i].firstParamName) ); + break; + + case DATATYPE_FLOAT: + typedef void (Panel::*MessageFunc_Float_t)(float); + (this->*((MessageFunc_Float_t)pMessageMap[i].func))( params->GetFloat(pMessageMap[i].firstParamName) ); + break; + + case DATATYPE_PTR: + typedef void (Panel::*MessageFunc_Ptr_t)(void *); + (this->*((MessageFunc_Ptr_t)pMessageMap[i].func))( (void *)params->GetPtr(pMessageMap[i].firstParamName) ); + break; + + case DATATYPE_HANDLE: + { + typedef void (Panel::*MessageFunc_Ptr_t)(void *); + VPANEL vp = ivgui()->HandleToPanel( params->GetInt( pMessageMap[i].firstParamName ) ); + Panel *panel = ipanel()->GetPanel( vp, GetModuleName() ); + (this->*((MessageFunc_Ptr_t)pMessageMap[i].func))( (void *)panel ); + } + break; + + case DATATYPE_KEYVALUES: + typedef void (Panel::*MessageFunc_KeyValues_t)(KeyValues *); + if ( pMessageMap[i].firstParamName ) + { + (this->*((MessageFunc_KeyValues_t)pMessageMap[i].func))( (KeyValues *)params->GetPtr(pMessageMap[i].firstParamName) ); + } + else + { + (this->*((MessageFunc_KeyValues_t)pMessageMap[i].func))( params ); + } + break; + + default: + // the message isn't handled + ivgui()->DPrintf( "Message '%s', sent to '%s', has an invalid parameter type\n", params->GetName(), GetName() ); + break; + } + + break; + + default: + (this->*(pMessageMap[i].func))(); + break; + }; + + // break the loop + bFound = true; + break; + } + } + } + + // message not handled + // debug code + if ( !bFound ) + { + static int s_bDebugMessages = -1; + if ( s_bDebugMessages == -1 ) + { + s_bDebugMessages = CommandLine()->FindParm( "-vguimessages" ) ? 1 : 0; + } + if ( s_bDebugMessages == 1 ) + { + ivgui()->DPrintf( "Message '%s' not handled by panel '%s'\n", params->GetName(), GetName() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Safe call to get info from child panel by name +//----------------------------------------------------------------------------- +bool Panel::RequestInfoFromChild(const char *childName, KeyValues *outputData) +{ + Panel *panel = FindChildByName(childName); + if (panel) + { + return panel->RequestInfo(outputData); + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Posts a message +//----------------------------------------------------------------------------- +void Panel::PostMessage(Panel *target, KeyValues *message, float delay) +{ + ivgui()->PostMessage(target->GetVPanel(), message, GetVPanel(), delay); +} + +void Panel::PostMessage(VPANEL target, KeyValues *message, float delaySeconds) +{ + ivgui()->PostMessage(target, message, GetVPanel(), delaySeconds); +} + +void Panel::PostMessageToAllSiblings( KeyValues *msg, float delaySeconds /*= 0.0f*/ ) +{ + VPANEL parent = GetVParent(); + if ( parent ) + { + VPANEL vpanel = GetVPanel(); + + CUtlVector< VPANEL > &children = ipanel()->GetChildren( parent ); + int nChildCount = children.Count(); + for ( int i = 0; i < nChildCount; ++i ) + { + VPANEL sibling = children[ i ]; + if ( sibling == vpanel ) + continue; + + if ( sibling ) + { + PostMessage( sibling, msg->MakeCopy(), delaySeconds ); + } + } + } + + msg->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: Safe call to post a message to a child by name +//----------------------------------------------------------------------------- +void Panel::PostMessageToChild(const char *childName, KeyValues *message) +{ + Panel *panel = FindChildByName(childName); + if (panel) + { + ivgui()->PostMessage(panel->GetVPanel(), message, GetVPanel()); + } + else + { + message->deleteThis(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Requests some information from the panel +// Look through the message map for the handler +//----------------------------------------------------------------------------- +bool Panel::RequestInfo( KeyValues *outputData ) +{ + if ( InternalRequestInfo( GetAnimMap(), outputData ) ) + { + return true; + } + + if (GetVParent()) + { + return ipanel()->RequestInfo(GetVParent(), outputData); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: sets a specified value in the control - inverse of RequestInfo +//----------------------------------------------------------------------------- +bool Panel::SetInfo(KeyValues *inputData) +{ + if ( InternalSetInfo( GetAnimMap(), inputData ) ) + { + return true; + } + + // doesn't chain to parent + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: change the panel's silent mode; if silent, the panel will not post +// any action signals +//----------------------------------------------------------------------------- + +void Panel::SetSilentMode( bool bSilent ) +{ + m_bIsSilent = bSilent; +} + +//----------------------------------------------------------------------------- +// Purpose: mouse events will be send to handler panel instead of this panel +//----------------------------------------------------------------------------- +void Panel::InstallMouseHandler( Panel *pHandler ) +{ + m_hMouseEventHandler = pHandler; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepares the hierarchy panel maps for use (with message maps etc) +//----------------------------------------------------------------------------- +void Panel::PreparePanelMap( PanelMap_t *panelMap ) +{ + // iterate through the class hierarchy message maps + while ( panelMap != NULL && !panelMap->processed ) + { + // fixup cross-dll boundary panel maps + if ( panelMap->baseMap == (PanelMap_t*)0x00000001 ) + { + panelMap->baseMap = &Panel::m_PanelMap; + } + + // hash message map strings into symbols + for (int i = 0; i < panelMap->dataNumFields; i++) + { + MessageMapItem_t *item = &panelMap->dataDesc[i]; + + if (item->name) + { + item->nameSymbol = KeyValuesSystem()->GetSymbolForString(item->name); + } + else + { + item->nameSymbol = INVALID_KEY_SYMBOL; + } + if (item->firstParamName) + { + item->firstParamSymbol = KeyValuesSystem()->GetSymbolForString(item->firstParamName); + } + else + { + item->firstParamSymbol = INVALID_KEY_SYMBOL; + } + if (item->secondParamName) + { + item->secondParamSymbol = KeyValuesSystem()->GetSymbolForString(item->secondParamName); + } + else + { + item->secondParamSymbol = INVALID_KEY_SYMBOL; + } + } + + panelMap->processed = true; + panelMap = panelMap->baseMap; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called to delete the panel +//----------------------------------------------------------------------------- +void Panel::OnDelete() +{ +#ifdef WIN32 + Assert( IsX360() || ( IsPC() && _heapchk() == _HEAPOK ) ); +#endif + delete this; +#ifdef WIN32 + Assert( IsX360() || ( IsPC() && _heapchk() == _HEAPOK ) ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Panel handle implementation +// Returns a pointer to a valid panel, NULL if the panel has been deleted +//----------------------------------------------------------------------------- +Panel *PHandle::Get() +{ + if (m_iPanelID != INVALID_PANEL) + { + VPANEL panel = ivgui()->HandleToPanel(m_iPanelID); + if (panel) + { + Panel *vguiPanel = ipanel()->GetPanel(panel, GetControlsModuleName()); + return vguiPanel; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the smart pointer +//----------------------------------------------------------------------------- +Panel *PHandle::Set(Panel *pent) +{ + if (pent) + { + m_iPanelID = ivgui()->PanelToHandle(pent->GetVPanel()); + } + else + { + m_iPanelID = INVALID_PANEL; + } + return pent; +} + +Panel *PHandle::Set( HPanel hPanel ) +{ + m_iPanelID = hPanel; + return Get(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a handle to a valid panel, NULL if the panel has been deleted +//----------------------------------------------------------------------------- +VPANEL VPanelHandle::Get() +{ + if (m_iPanelID != INVALID_PANEL) + { + if (ivgui()) + { + return ivgui()->HandleToPanel(m_iPanelID); + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the smart pointer +//----------------------------------------------------------------------------- +VPANEL VPanelHandle::Set(VPANEL pent) +{ + if (pent) + { + m_iPanelID = ivgui()->PanelToHandle(pent); + } + else + { + m_iPanelID = INVALID_PANEL; + } + return pent; +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to the tooltip object associated with the panel +//----------------------------------------------------------------------------- +BaseTooltip *Panel::GetTooltip() +{ + if (!m_pTooltips) + { + m_pTooltips = new TextTooltip(this, NULL); + m_bToolTipOverridden = false; + + if ( IsConsoleStylePanel() ) + { + m_pTooltips->SetEnabled( false ); + } + } + + return m_pTooltips; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetTooltip( BaseTooltip *pToolTip, const char *pszText ) +{ + if ( !m_bToolTipOverridden ) + { + // Remove the one we made, we're being overridden. + delete m_pTooltips; + } + + m_pTooltips = pToolTip; + m_bToolTipOverridden = true; + + if ( _tooltipText ) + { + delete [] _tooltipText; + _tooltipText = NULL; + } + + if ( pszText ) + { + int len = Q_strlen(pszText) + 1; + _tooltipText = new char[ len ]; + Q_strncpy( _tooltipText, pszText, len ); + } +} + +//----------------------------------------------------------------------------- +const char *Panel::GetEffectiveTooltipText() const +{ + if ( _tooltipText ) + { + return _tooltipText; + } + if ( m_pTooltips ) + { + const char *result = m_pTooltips->GetText(); + if ( result ) + { + return result; + } + } + return ""; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the proportional flag on this panel and all it's children +//----------------------------------------------------------------------------- +void Panel::SetProportional(bool state) +{ + // only do something if the state changes + if( state != _flags.IsFlagSet( IS_PROPORTIONAL ) ) + { + _flags.SetFlag( IS_PROPORTIONAL, state ); + + for(int i=0;i<GetChildCount();i++) + { + // recursively apply to all children + GetChild(i)->SetProportional( IsProportional() ); + } + } + InvalidateLayout(); +} + + +void Panel::SetKeyBoardInputEnabled( bool state ) +{ + ipanel()->SetKeyBoardInputEnabled( GetVPanel(), state ); + for ( int i = 0; i < GetChildCount(); i++ ) + { + Panel *child = GetChild( i ); + if ( !child ) + { + continue; + } + child->SetKeyBoardInputEnabled( state ); + } + + // If turning off keyboard input enable, then make sure + // this panel is not the current key focus of a parent panel + if ( !state ) + { + Panel *pParent = GetParent(); + if ( pParent ) + { + if ( pParent->GetCurrentKeyFocus() == GetVPanel() ) + { + pParent->RequestFocusNext(); + } + } + } +} + +void Panel::SetMouseInputEnabled( bool state ) +{ + ipanel()->SetMouseInputEnabled( GetVPanel(), state ); + /* for(int i=0;i<GetChildCount();i++) + { + GetChild(i)->SetMouseInput(state); + }*/ + vgui::surface()->CalculateMouseVisible(); +} + +bool Panel::IsKeyBoardInputEnabled() +{ + return ipanel()->IsKeyBoardInputEnabled( GetVPanel() ); +} + +bool Panel::IsMouseInputEnabled() +{ + return ipanel()->IsMouseInputEnabled( GetVPanel() ); +} + +class CFloatProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + kv->SetFloat( entry->name(), *(float *)data ); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(float *)data = kv->GetFloat( entry->name() ); + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(float *)data = atof( entry->defaultvalue() ); + } +}; + +class CProportionalFloatProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + float f = *(float *)data; + f = scheme()->GetProportionalNormalizedValueEx( panel->GetScheme(), f ); + kv->SetFloat( entry->name(), f ); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + float f = kv->GetFloat( entry->name() ); + f = scheme()->GetProportionalScaledValueEx( panel->GetScheme(), f ); + *(float *)data = f; + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + float f = atof( entry->defaultvalue() ); + f = scheme()->GetProportionalScaledValueEx( panel->GetScheme(), f ); + *(float *)data = f; + } +}; + +class CIntProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + kv->SetInt( entry->name(), *(int *)data ); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(int *)data = kv->GetInt( entry->name() ); + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(int *)data = atoi( entry->defaultvalue() ); + } +}; + +class CProportionalIntProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + int i = *(int *)data; + i = scheme()->GetProportionalNormalizedValueEx( panel->GetScheme(), i ); + kv->SetInt( entry->name(), i ); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + int i = kv->GetInt( entry->name() ); + i = scheme()->GetProportionalScaledValueEx( panel->GetScheme(), i ); + *(int *)data = i; + } + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + int i = atoi( entry->defaultvalue() ); + i = scheme()->GetProportionalScaledValueEx( panel->GetScheme(), i ); + *(int *)data = i; + } +}; + +class CProportionalIntWithScreenspacePropertyX : public vgui::IPanelAnimationPropertyConverter +{ +public: + int ExtractValue( Panel *pPanel, const char *pszKey ) + { + int nPos = 0; + ComputePos( pPanel, pszKey, nPos, GetPanelDimension( pPanel ), GetScreenSize( pPanel ), true, OP_ADD ); + return nPos; + } + + virtual int GetScreenSize( Panel *pPanel ) const + { + int nParentWide, nParentTall; + if (pPanel->IsProportional() && pPanel->GetParent()) + { + nParentWide = pPanel->GetParent()->GetWide(); + nParentTall = pPanel->GetParent()->GetTall(); + } + else + { + surface()->GetScreenSize(nParentWide, nParentTall); + } + + return nParentWide; + } + + virtual int GetPanelDimension( Panel *pPanel ) const + { + return pPanel->GetWide(); + } + + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + // Won't work with this, don't use it. + Assert(0); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(int *)data = ExtractValue( panel, kv->GetString( entry->name() ) ); + } + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(int *)data = ExtractValue( panel, entry->defaultvalue() ); + } +}; + +class CProportionalIntWithScreenspacePropertyY : public CProportionalIntWithScreenspacePropertyX +{ +public: + virtual int GetScreenSize( Panel *pPanel ) const OVERRIDE + { + int nParentWide, nParentTall; + if (pPanel->IsProportional() && pPanel->GetParent()) + { + nParentWide = pPanel->GetParent()->GetWide(); + nParentTall = pPanel->GetParent()->GetTall(); + } + else + { + surface()->GetScreenSize(nParentWide, nParentTall); + } + + return nParentTall; + } + + virtual int GetPanelDimension(Panel *pPanel) const OVERRIDE + { + return pPanel->GetTall(); + } +}; + +class CProportionalWidthProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + int ExtractValue(Panel *pPanel, const char *pszKey) + { + if ( pszKey && ( pszKey[0] == 'o' || pszKey[0] == 'O' ) ) + { + // We don't handle sizes based on the other dimension in this case + Assert( 0 ); + return 0; + } + + int nParentWide, nParentTall; + if ( pPanel->IsProportional() && pPanel->GetParent() ) + { + nParentWide = pPanel->GetParent()->GetWide(); + nParentTall = pPanel->GetParent()->GetTall(); + } + else + { + surface()->GetScreenSize( nParentWide, nParentTall ); + } + + unsigned int nBuildFlags = 0; + return Compute( pPanel, nBuildFlags, pszKey, nParentWide, nParentTall, false ); + } + + virtual void GetData(Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry) + { + // Won't work with this, don't use it. + Assert(0); + } + + virtual void SetData(Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry) + { + void *data = (void *)((*entry->m_pfnLookup)(panel)); + *(int *)data = ExtractValue(panel, kv->GetString(entry->name())); + } + virtual void InitFromDefault(Panel *panel, PanelAnimationMapEntry *entry) + { + void *data = (void *)((*entry->m_pfnLookup)(panel)); + *(int *)data = ExtractValue(panel, entry->defaultvalue()); + } + +private: + + virtual int Compute( Panel* pPanel, unsigned int& nBuildFlags, const char *pszKey, int nParentWide, int nParentTall, bool bComputingOther ) + { + KeyValuesAD kv( "temp" ); + kv->SetString( "wide", pszKey ); + + return ComputeWide( pPanel, nBuildFlags, kv, nParentWide, nParentTall, false ); + } +}; + +class CProportionalHeightProperty : public CProportionalWidthProperty +{ +private: + virtual int Compute(Panel* pPanel, unsigned int& nBuildFlags, const char *pszKey, int nParentWide, int nParentTall, bool bComputingOther) OVERRIDE + { + KeyValuesAD kv( "temp" ); + kv->SetString( "tall", pszKey ); + + return ComputeTall(pPanel, nBuildFlags, kv, nParentWide, nParentTall, false); + } + +}; + +class CColorProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + kv->SetColor( entry->name(), *(Color *)data ); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + vgui::IScheme *scheme = vgui::scheme()->GetIScheme( panel->GetScheme() ); + Assert( scheme ); + if ( scheme ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + + char const *colorName = kv->GetString( entry->name() ); + if ( !colorName || !colorName[0] ) + { + *(Color *)data = kv->GetColor( entry->name() ); + } + else + { + *(Color *)data = scheme->GetColor( colorName, Color( 0, 0, 0, 0 ) ); + } + } + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + vgui::IScheme *scheme = vgui::scheme()->GetIScheme( panel->GetScheme() ); + Assert( scheme ); + if ( scheme ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(Color *)data = scheme->GetColor( entry->defaultvalue(), Color( 0, 0, 0, 0 ) ); + } + } +}; + +class CBoolProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + kv->SetInt( entry->name(), *(bool *)data ? 1 : 0 ); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(bool *)data = kv->GetInt( entry->name() ) ? true : false; + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + bool b = false; + if ( !stricmp( entry->defaultvalue(), "true" )|| + atoi( entry->defaultvalue() )!= 0 ) + { + b = true; + } + + *(bool *)data = b; + } +}; + +class CStringProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + kv->SetString( entry->name(), (char *)data ); + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + strcpy( (char *)data, kv->GetString( entry->name() ) ); + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + strcpy( ( char * )data, entry->defaultvalue() ); + } +}; + +class CHFontProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + vgui::IScheme *scheme = vgui::scheme()->GetIScheme( panel->GetScheme() ); + Assert( scheme ); + if ( scheme ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + char const *fontName = scheme->GetFontName( *(HFont *)data ); + kv->SetString( entry->name(), fontName ); + } + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + vgui::IScheme *scheme = vgui::scheme()->GetIScheme( panel->GetScheme() ); + Assert( scheme ); + if ( scheme ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + char const *fontName = kv->GetString( entry->name() ); + *(HFont *)data = scheme->GetFont( fontName, panel->IsProportional() ); + } + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + vgui::IScheme *scheme = vgui::scheme()->GetIScheme( panel->GetScheme() ); + Assert( scheme ); + if ( scheme ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + *(HFont *)data = scheme->GetFont( entry->defaultvalue(), panel->IsProportional() ); + } + } +}; + +class CTextureIdProperty : public vgui::IPanelAnimationPropertyConverter +{ +public: + virtual void GetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + int currentId = *(int *)data; + + // lookup texture name for id + char texturename[ 512 ]; + if ( currentId != -1 && + surface()->DrawGetTextureFile( currentId, texturename, sizeof( texturename ) ) ) + { + kv->SetString( entry->name(), texturename ); + } + else + { + kv->SetString( entry->name(), "" ); + } + } + + virtual void SetData( Panel *panel, KeyValues *kv, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + + int currentId = -1; + + char const *texturename = kv->GetString( entry->name() ); + if ( texturename && texturename[ 0 ] ) + { + currentId = surface()->DrawGetTextureId( texturename ); + if ( currentId == -1 ) + { + currentId = surface()->CreateNewTextureID(); + } + surface()->DrawSetTextureFile( currentId, texturename, false, true ); + } + + *(int *)data = currentId; + } + + virtual void InitFromDefault( Panel *panel, PanelAnimationMapEntry *entry ) + { + void *data = ( void * )( (*entry->m_pfnLookup)( panel ) ); + + int currentId = -1; + + char const *texturename = entry->defaultvalue(); + if ( texturename && texturename[ 0 ] ) + { + currentId = surface()->DrawGetTextureId( texturename ); + if ( currentId == -1 ) + { + currentId = surface()->CreateNewTextureID(); + } + surface()->DrawSetTextureFile( currentId, texturename, false, true ); + } + + *(int *)data = currentId; + } +}; + +static CFloatProperty floatconverter; +static CProportionalFloatProperty p_floatconverter; +static CIntProperty intconverter; +static CProportionalIntProperty p_intconverter; +static CProportionalIntWithScreenspacePropertyX p_screenspace_intconverter_X; +static CProportionalIntWithScreenspacePropertyY p_screenspace_intconverter_Y; +static CColorProperty colorconverter; +static CBoolProperty boolconverter; +static CStringProperty stringconverter; +static CHFontProperty fontconverter; +static CTextureIdProperty textureidconverter; +static CProportionalWidthProperty proportional_width_converter; +static CProportionalHeightProperty proportional_height_converter; +//static CProportionalXPosProperty xposconverter; +//static CProportionalYPosProperty yposconverter; + +static CUtlDict< IPanelAnimationPropertyConverter *, int > g_AnimationPropertyConverters; + +static IPanelAnimationPropertyConverter *FindConverter( char const *typeName ) +{ + int lookup = g_AnimationPropertyConverters.Find( typeName ); + if ( lookup == g_AnimationPropertyConverters.InvalidIndex() ) + return NULL; + + IPanelAnimationPropertyConverter *converter = g_AnimationPropertyConverters[ lookup ]; + return converter; +} + +void Panel::AddPropertyConverter( char const *typeName, IPanelAnimationPropertyConverter *converter ) +{ + int lookup = g_AnimationPropertyConverters.Find( typeName ); + if ( lookup != g_AnimationPropertyConverters.InvalidIndex() ) + { + Msg( "Already have converter for type %s, ignoring...\n", typeName ); + return; + } + + g_AnimationPropertyConverters.Insert( typeName, converter ); +} + +//----------------------------------------------------------------------------- +// Purpose: Static method to initialize all needed converters +//----------------------------------------------------------------------------- +void Panel::InitPropertyConverters( void ) +{ + static bool initialized = false; + if ( initialized ) + return; + initialized = true; + + AddPropertyConverter( "float", &floatconverter ); + AddPropertyConverter( "int", &intconverter ); + AddPropertyConverter( "Color", &colorconverter ); + //AddPropertyConverter( "vgui::Color", &colorconverter ); + AddPropertyConverter( "bool", &boolconverter ); + AddPropertyConverter( "char", &stringconverter ); + AddPropertyConverter( "string", &stringconverter ); + AddPropertyConverter( "HFont", &fontconverter ); + AddPropertyConverter( "vgui::HFont", &fontconverter ); + + // This is an aliased type for proportional float + AddPropertyConverter( "proportional_float", &p_floatconverter ); + AddPropertyConverter( "proportional_int", &p_intconverter ); + + AddPropertyConverter( "proportional_xpos", &p_screenspace_intconverter_X ); + AddPropertyConverter( "proportional_ypos", &p_screenspace_intconverter_Y ); + + AddPropertyConverter( "proportional_width", &proportional_width_converter ); + AddPropertyConverter( "proportional_height", &proportional_height_converter ); + + AddPropertyConverter( "textureid", &textureidconverter ); +} + +bool Panel::InternalRequestInfo( PanelAnimationMap *map, KeyValues *outputData ) +{ + if ( !map ) + return false; + + Assert( outputData ); + + char const *name = outputData->GetName(); + + PanelAnimationMapEntry *e = FindPanelAnimationEntry( name, map ); + if ( e ) + { + IPanelAnimationPropertyConverter *converter = FindConverter( e->type() ); + if ( converter ) + { + converter->GetData( this, outputData, e ); + return true; + } + } + + return false; +} + +bool Panel::InternalSetInfo( PanelAnimationMap *map, KeyValues *inputData ) +{ + if ( !map ) + return false; + + Assert( inputData ); + + char const *name = inputData->GetName(); + + PanelAnimationMapEntry *e = FindPanelAnimationEntry( name, map ); + if ( e ) + { + IPanelAnimationPropertyConverter *converter = FindConverter( e->type() ); + if ( converter ) + { + converter->SetData( this, inputData, e ); + return true; + } + } + + return false; +} + +PanelAnimationMapEntry *Panel::FindPanelAnimationEntry( char const *scriptname, PanelAnimationMap *map ) +{ + if ( !map ) + return NULL; + + Assert( scriptname ); + + // Look through mapping for entry + int c = map->entries.Count(); + for ( int i = 0; i < c; i++ ) + { + PanelAnimationMapEntry *e = &map->entries[ i ]; + + if ( !stricmp( e->name(), scriptname ) ) + { + return e; + } + } + + // Recurse + if ( map->baseMap ) + { + return FindPanelAnimationEntry( scriptname, map->baseMap ); + } + + return NULL; +} + +// Recursively invoke settings for PanelAnimationVars +void Panel::InternalApplySettings( PanelAnimationMap *map, KeyValues *inResourceData) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s", __FUNCTION__, GetName() ); + + // Loop through keys + KeyValues *kv; + + for ( kv = inResourceData->GetFirstSubKey(); kv; kv = kv->GetNextKey() ) + { + char const *varname = kv->GetName(); + + PanelAnimationMapEntry *entry = FindPanelAnimationEntry( varname, GetAnimMap() ); + if ( entry ) + { + // Set value to value from script + IPanelAnimationPropertyConverter *converter = FindConverter( entry->type() ); + if ( converter ) + { + converter->SetData( this, inResourceData, entry ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets the default values of all CPanelAnimationVars +//----------------------------------------------------------------------------- +void Panel::InternalInitDefaultValues( PanelAnimationMap *map ) +{ + _flags.ClearFlag( NEEDS_DEFAULT_SETTINGS_APPLIED ); + + // Look through mapping for entry + int c = map->entries.Count(); + for ( int i = 0; i < c; i++ ) + { + PanelAnimationMapEntry *e = &map->entries[ i ]; + Assert( e ); + IPanelAnimationPropertyConverter *converter = FindConverter( e->type() ); + if ( !converter ) + continue; + + converter->InitFromDefault( this, e ); + } + + if ( map->baseMap ) + { + InternalInitDefaultValues( map->baseMap ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +int Panel::GetPaintBackgroundType() +{ + return m_nPaintBackgroundType; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : w - +// h - +//----------------------------------------------------------------------------- +void Panel::GetCornerTextureSize( int& w, int& h ) +{ + if ( m_nBgTextureId1 == -1 ) + { + w = h = 0; + return; + } + surface()->DrawGetTextureSize(m_nBgTextureId1, w, h); +} + +//----------------------------------------------------------------------------- +// Purpose: draws a selection box +//----------------------------------------------------------------------------- +void Panel::DrawBox(int x, int y, int wide, int tall, Color color, float normalizedAlpha, bool hollow /*=false*/ ) +{ + if ( m_nBgTextureId1 == -1 || + m_nBgTextureId2 == -1 || + m_nBgTextureId3 == -1 || + m_nBgTextureId4 == -1 ) + { + return; + } + + color[3] *= normalizedAlpha; + + // work out our bounds + int cornerWide, cornerTall; + GetCornerTextureSize( cornerWide, cornerTall ); + + // draw the background in the areas not occupied by the corners + // draw it in three horizontal strips + surface()->DrawSetColor(color); + surface()->DrawFilledRect(x + cornerWide, y, x + wide - cornerWide, y + cornerTall); + if ( !hollow ) + { + surface()->DrawFilledRect(x, y + cornerTall, x + wide, y + tall - cornerTall); + } + else + { + surface()->DrawFilledRect(x, y + cornerTall, x + cornerWide, y + tall - cornerTall); + surface()->DrawFilledRect(x + wide - cornerWide, y + cornerTall, x + wide, y + tall - cornerTall); + } + surface()->DrawFilledRect(x + cornerWide, y + tall - cornerTall, x + wide - cornerWide, y + tall); + + // draw the corners + + //============================================================================= + // HPE_BEGIN: + // [tj] We now check each individual corner and decide whether to draw it straight or rounded + //============================================================================= + //TOP-LEFT + if (ShouldDrawTopLeftCornerRounded()) + { + surface()->DrawSetTexture(m_nBgTextureId1); + surface()->DrawTexturedRect(x, y, x + cornerWide, y + cornerTall); + } + else + { + surface()->DrawFilledRect(x, y, x + cornerWide, y + cornerTall); + } + + + //TOP-RIGHT + if (ShouldDrawTopRightCornerRounded()) + { + surface()->DrawSetTexture(m_nBgTextureId2); + surface()->DrawTexturedRect(x + wide - cornerWide, y, x + wide, y + cornerTall); + } + else + { + surface()->DrawFilledRect(x + wide - cornerWide, y, x + wide, y + cornerTall); + } + + //BOTTOM-LEFT + if (ShouldDrawBottomLeftCornerRounded()) + { + surface()->DrawSetTexture(m_nBgTextureId4); + surface()->DrawTexturedRect(x + 0, y + tall - cornerTall, x + cornerWide, y + tall); + } + else + { + surface()->DrawFilledRect(x + 0, y + tall - cornerTall, x + cornerWide, y + tall); + } + + + //BOTTOM-RIGHT + if (ShouldDrawBottomRightCornerRounded()) + { + surface()->DrawSetTexture(m_nBgTextureId3); + surface()->DrawTexturedRect(x + wide - cornerWide, y + tall - cornerTall, x + wide, y + tall); + } + else + { + surface()->DrawFilledRect(x + wide - cornerWide, y + tall - cornerTall, x + wide, y + tall); + } + //============================================================================= + // HPE_END + //============================================================================= +} + +void Panel::DrawBoxFade(int x, int y, int wide, int tall, Color color, float normalizedAlpha, unsigned int alpha0, unsigned int alpha1, bool bHorizontal, bool hollow /*=false*/ ) +{ + if ( m_nBgTextureId1 == -1 || + m_nBgTextureId2 == -1 || + m_nBgTextureId3 == -1 || + m_nBgTextureId4 == -1 || + surface()->DrawGetAlphaMultiplier() == 0 ) + { + return; + } + + color[3] *= normalizedAlpha; + + // work out our bounds + int cornerWide, cornerTall; + GetCornerTextureSize( cornerWide, cornerTall ); + + if ( !bHorizontal ) + { + // draw the background in the areas not occupied by the corners + // draw it in three horizontal strips + surface()->DrawSetColor(color); + surface()->DrawFilledRectFade(x + cornerWide, y, x + wide - cornerWide, y + cornerTall, alpha0, alpha0, bHorizontal ); + if ( !hollow ) + { + surface()->DrawFilledRectFade(x, y + cornerTall, x + wide, y + tall - cornerTall, alpha0, alpha1, bHorizontal); + } + else + { + surface()->DrawFilledRectFade(x, y + cornerTall, x + cornerWide, y + tall - cornerTall, alpha0, alpha1, bHorizontal); + surface()->DrawFilledRectFade(x + wide - cornerWide, y + cornerTall, x + wide, y + tall - cornerTall, alpha0, alpha1, bHorizontal); + } + surface()->DrawFilledRectFade(x + cornerWide, y + tall - cornerTall, x + wide - cornerWide, y + tall, alpha1, alpha1, bHorizontal); + } + else + { + // draw the background in the areas not occupied by the corners + // draw it in three horizontal strips + surface()->DrawSetColor(color); + surface()->DrawFilledRectFade(x, y + cornerTall, x + cornerWide, y + tall - cornerTall, alpha0, alpha0, bHorizontal ); + if ( !hollow ) + { + surface()->DrawFilledRectFade(x + cornerWide, y, x + wide - cornerWide, y + tall, alpha0, alpha1, bHorizontal); + } + else + { + // FIXME: Hollow horz version not implemented + //surface()->DrawFilledRectFade(x, y + cornerTall, x + cornerWide, y + tall - cornerTall, alpha0, alpha1, bHorizontal); + //surface()->DrawFilledRectFade(x + wide - cornerWide, y + cornerTall, x + wide, y + tall - cornerTall, alpha0, alpha1, bHorizontal); + } + surface()->DrawFilledRectFade(x + wide - cornerWide, y + cornerTall, x + wide, y + tall - cornerTall, alpha1, alpha1, bHorizontal); + } + + float fOldAlpha = color[ 3 ]; + int iAlpha0 = fOldAlpha * ( static_cast<float>( alpha0 ) / 255.0f ); + int iAlpha1 = fOldAlpha * ( static_cast<float>( alpha1 ) / 255.0f ); + + // draw the corners + if ( !bHorizontal ) + { + color[ 3 ] = iAlpha0; + surface()->DrawSetColor( color ); + surface()->DrawSetTexture(m_nBgTextureId1); + surface()->DrawTexturedRect(x, y, x + cornerWide, y + cornerTall); + surface()->DrawSetTexture(m_nBgTextureId2); + surface()->DrawTexturedRect(x + wide - cornerWide, y, x + wide, y + cornerTall); + + color[ 3 ] = iAlpha1; + surface()->DrawSetColor( color ); + surface()->DrawSetTexture(m_nBgTextureId3); + surface()->DrawTexturedRect(x + wide - cornerWide, y + tall - cornerTall, x + wide, y + tall); + surface()->DrawSetTexture(m_nBgTextureId4); + surface()->DrawTexturedRect(x + 0, y + tall - cornerTall, x + cornerWide, y + tall); + } + else + { + color[ 3 ] = iAlpha0; + surface()->DrawSetColor( color ); + surface()->DrawSetTexture(m_nBgTextureId1); + surface()->DrawTexturedRect(x, y, x + cornerWide, y + cornerTall); + surface()->DrawSetTexture(m_nBgTextureId4); + surface()->DrawTexturedRect(x + 0, y + tall - cornerTall, x + cornerWide, y + tall); + + color[ 3 ] = iAlpha1; + surface()->DrawSetColor( color ); + surface()->DrawSetTexture(m_nBgTextureId2); + surface()->DrawTexturedRect(x + wide - cornerWide, y, x + wide, y + cornerTall); + surface()->DrawSetTexture(m_nBgTextureId3); + surface()->DrawTexturedRect(x + wide - cornerWide, y + tall - cornerTall, x + wide, y + tall); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +// y - +// wide - +// tall - +// color - +// normalizedAlpha - +//----------------------------------------------------------------------------- +void Panel::DrawHollowBox(int x, int y, int wide, int tall, Color color, float normalizedAlpha ) +{ + DrawBox( x, y, wide, tall, color, normalizedAlpha, true ); +} + +//============================================================================= +// HPE_BEGIN: +// [menglish] Draws a hollow box similar to the already existing draw hollow box function, but takes the indents as params +//============================================================================= + +void Panel::DrawHollowBox( int x, int y, int wide, int tall, Color color, float normalizedAlpha, int cornerWide, int cornerTall ) +{ + if ( m_nBgTextureId1 == -1 || + m_nBgTextureId2 == -1 || + m_nBgTextureId3 == -1 || + m_nBgTextureId4 == -1 ) + { + return; + } + + color[3] *= normalizedAlpha; + + // draw the background in the areas not occupied by the corners + // draw it in three horizontal strips + surface()->DrawSetColor(color); + surface()->DrawFilledRect(x + cornerWide, y, x + wide - cornerWide, y + cornerTall); + surface()->DrawFilledRect(x, y + cornerTall, x + cornerWide, y + tall - cornerTall); + surface()->DrawFilledRect(x + wide - cornerWide, y + cornerTall, x + wide, y + tall - cornerTall); + surface()->DrawFilledRect(x + cornerWide, y + tall - cornerTall, x + wide - cornerWide, y + tall); + + // draw the corners + surface()->DrawSetTexture(m_nBgTextureId1); + surface()->DrawTexturedRect(x, y, x + cornerWide, y + cornerTall); + surface()->DrawSetTexture(m_nBgTextureId2); + surface()->DrawTexturedRect(x + wide - cornerWide, y, x + wide, y + cornerTall); + surface()->DrawSetTexture(m_nBgTextureId3); + surface()->DrawTexturedRect(x + wide - cornerWide, y + tall - cornerTall, x + wide, y + tall); + surface()->DrawSetTexture(m_nBgTextureId4); + surface()->DrawTexturedRect(x + 0, y + tall - cornerTall, x + cornerWide, y + tall); +} + +//============================================================================= +// HPE_END +//============================================================================= + +//----------------------------------------------------------------------------- +// Purpose: draws a selection box +//----------------------------------------------------------------------------- +void Panel::DrawTexturedBox(int x, int y, int wide, int tall, Color color, float normalizedAlpha ) +{ + if ( m_nBgTextureId1 == -1 ) + return; + + color[3] *= normalizedAlpha; + + surface()->DrawSetColor( color ); + surface()->DrawSetTexture(m_nBgTextureId1); + surface()->DrawTexturedRect(x, y, x + wide, y + tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Marks this panel as draggable (note that children will chain to their parents to see if any parent is draggable) +// Input : enabled - +//----------------------------------------------------------------------------- +void Panel::SetDragEnabled( bool enabled ) +{ +#if defined( VGUI_USEDRAGDROP ) + // If turning it off, quit dragging if mid-drag + if ( !enabled && + m_pDragDrop->m_bDragging ) + { + OnFinishDragging( false, (MouseCode)-1 ); + } + m_pDragDrop->m_bDragEnabled = enabled; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsDragEnabled() const +{ +#if defined( VGUI_USEDRAGDROP ) + return m_pDragDrop->m_bDragEnabled; +#endif + return false; +} + +void Panel::SetShowDragHelper( bool enabled ) +{ +#if defined( VGUI_USEDRAGDROP ) + m_pDragDrop->m_bShowDragHelper = enabled; +#endif +} + +// Use this to prevent chaining up from a parent which can mess with mouse functionality if you don't want to chain up from a child panel to the best +// draggable parent. +void Panel::SetBlockDragChaining( bool block ) +{ +#if defined( VGUI_USEDRAGDROP ) + m_pDragDrop->m_bPreventChaining = block; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsBlockingDragChaining() const +{ +#if defined( VGUI_USEDRAGDROP ) + return m_pDragDrop->m_bPreventChaining; +#endif + return true; +} + + +//----------------------------------------------------------------------------- +// accessors for m_nDragStartTolerance +//----------------------------------------------------------------------------- +int Panel::GetDragStartTolerance() const +{ +#if defined( VGUI_USEDRAGDROP ) + return m_pDragDrop->m_nDragStartTolerance; +#endif + return 0; +} + +void Panel::SetDragSTartTolerance( int nTolerance ) +{ +#if defined( VGUI_USEDRAGDROP ) + m_pDragDrop->m_nDragStartTolerance = nTolerance; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Marks this panel as droppable ( note that children will chain to their parents to see if any parent is droppable) +// Input : enabled - +//----------------------------------------------------------------------------- +void Panel::SetDropEnabled( bool enabled, float flHoverContextTime /* = 0.0f */ ) +{ +#if defined( VGUI_USEDRAGDROP ) + m_pDragDrop->m_bDropEnabled = enabled; + m_pDragDrop->m_flHoverContextTime = flHoverContextTime; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsDropEnabled() const +{ +#if defined( VGUI_USEDRAGDROP ) + return m_pDragDrop->m_bDropEnabled; +#endif + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Chains up to any parent +// 1) marked DropEnabled; and +// 2) willing to accept the drop payload +// Input : - +// Output : Panel +//----------------------------------------------------------------------------- +Panel *Panel::GetDropTarget( CUtlVector< KeyValues * >& msglist ) +{ +#if defined( VGUI_USEDRAGDROP ) + // Found one + if ( m_pDragDrop->m_bDropEnabled && + IsDroppable( msglist ) ) + { + return this; + } + + // Chain up + if ( GetParent() ) + { + return GetParent()->GetDropTarget( msglist ); + } +#endif + // No luck + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Chains up to first parent marked DragEnabled +// Input : - +// Output : Panel +//----------------------------------------------------------------------------- +Panel *Panel::GetDragPanel() +{ +#if defined( VGUI_USEDRAGDROP ) + // If we encounter a blocker, stop chaining + if ( m_pDragDrop->m_bPreventChaining ) + return NULL; + + if ( m_pDragDrop->m_bDragEnabled ) + return this; + + // Chain up + if ( GetParent() ) + { + return GetParent()->GetDragPanel(); + } +#endif + // No luck + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void Panel::OnStartDragging() +{ +#if defined( VGUI_USEDRAGDROP ) + // Only left mouse initiates drag/drop. + // FIXME: Revisit? + if ( !input()->IsMouseDown( MOUSE_LEFT ) ) + return; + + if ( !m_pDragDrop->m_bDragEnabled ) + return; + + if ( m_pDragDrop->m_bDragging ) + return; + + g_DragDropCapture = this; + + m_pDragDrop->m_bDragStarted = false; + m_pDragDrop->m_bDragging = true; + input()->GetCursorPos( m_pDragDrop->m_nStartPos[ 0 ], m_pDragDrop->m_nStartPos[ 1 ] ); + m_pDragDrop->m_nLastPos[ 0 ] = m_pDragDrop->m_nStartPos[ 0 ]; + m_pDragDrop->m_nLastPos[ 1 ] = m_pDragDrop->m_nStartPos[ 1 ]; + + OnContinueDragging(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Called if drag drop is started but not dropped on top of droppable panel... +// Input : - +//----------------------------------------------------------------------------- +void Panel::OnDragFailed( CUtlVector< KeyValues * >& msglist ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void Panel::OnFinishDragging( bool mousereleased, MouseCode code, bool abort /*= false*/ ) +{ +#if defined( VGUI_USEDRAGDROP ) + g_DragDropCapture = NULL; + + if ( !m_pDragDrop->m_bDragEnabled ) + return; + + Assert( m_pDragDrop->m_bDragging ); + + if ( !m_pDragDrop->m_bDragging ) + return; + + int x, y; + input()->GetCursorPos( x, y ); + + m_pDragDrop->m_nLastPos[ 0 ] = x; + m_pDragDrop->m_nLastPos[ 1 ] = y; + + if ( s_DragDropHelper.Get() ) + { + s_DragDropHelper->RemovePanel( this ); + } + + m_pDragDrop->m_bDragging = false; + + CUtlVector< KeyValues * >& data = m_pDragDrop->m_DragData; + int nData = data.Count(); + + Panel *target = NULL; + bool shouldDrop = false; + + if ( m_pDragDrop->m_bDragStarted ) + { + char cmd[ 256 ]; + Q_strncpy( cmd, "default", sizeof( cmd ) ); + + if ( mousereleased && + m_pDragDrop->m_hCurrentDrop != 0 && + m_pDragDrop->m_hDropContextMenu.Get() ) + { + Menu *menu = m_pDragDrop->m_hDropContextMenu; + + VPANEL hover = menu->IsWithinTraverse( x, y, false ); + if ( hover ) + { + Panel *pHover = ipanel()->GetPanel( hover, GetModuleName() ); + if ( pHover ) + { + // Figure out if it's a menu item... + int c = menu->GetItemCount(); + for ( int i = 0; i < c; ++i ) + { + int id = menu->GetMenuID( i ); + MenuItem *item = menu->GetMenuItem( id ); + if ( item == pHover ) + { + KeyValues *command = item->GetCommand(); + if ( command ) + { + char const *p = command->GetString( "command", "" ); + if ( p && p[ 0 ] ) + { + Q_strncpy( cmd, p, sizeof( cmd ) ); + } + } + } + } + } + } + + delete menu; + m_pDragDrop->m_hDropContextMenu = NULL; + } + + for ( int i = 0 ; i < nData; ++i ) + { + KeyValues *msg = data[ i ]; + + msg->SetString( "command", cmd ); + + msg->SetInt( "screenx", x ); + msg->SetInt( "screeny", y ); + } + + target = m_pDragDrop->m_hCurrentDrop.Get(); + if ( target && !abort ) + { + int localmousex = x, localmousey = y; + // Convert screen space coordintes to coordinates relative to drop window + target->ScreenToLocal( localmousex, localmousey ); + + for ( int i = 0 ; i < nData; ++i ) + { + KeyValues *msg = data[ i ]; + + msg->SetInt( "x", localmousex ); + msg->SetInt( "y", localmousey ); + } + + shouldDrop = true; + } + + if ( !shouldDrop ) + { + OnDragFailed( data ); + } + } + + m_pDragDrop->m_bDragStarted = false; + m_pDragDrop->m_DragPanels.RemoveAll(); + m_pDragDrop->m_hCurrentDrop = NULL; + + // Copy data ptrs out of data because OnPanelDropped might cause this panel to be deleted + // and our this ptr will be hosed... + CUtlVector< KeyValues * > temp; + for ( int i = 0 ; i < nData; ++i ) + { + temp.AddToTail( data[ i ] ); + } + data.RemoveAll(); + + if ( shouldDrop && target ) + { + target->OnPanelDropped( temp ); + } + for ( int i = 0 ; i < nData; ++i ) + { + temp[ i ]->deleteThis(); + } +#endif +} + +void Panel::OnDropContextHoverShow( CUtlVector< KeyValues * >& msglist ) +{ +} + +void Panel::OnDropContextHoverHide( CUtlVector< KeyValues * >& msglist ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *msg - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsDroppable( CUtlVector< KeyValues * >& msglist ) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : startx - +// starty - +// mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::CanStartDragging( int startx, int starty, int mx, int my ) +{ +#if defined( VGUI_USEDRAGDROP ) + if ( IsStartDragWhenMouseExitsPanel() ) + { + ScreenToLocal( mx, my ); + if ( mx < 0 || my < 0 ) + return true; + if ( mx > GetWide() || my > GetTall() ) + return true; + + return false; + } + + int deltax = abs( mx - startx ); + int deltay = abs( my - starty ); + if ( deltax > m_pDragDrop->m_nDragStartTolerance || + deltay > m_pDragDrop->m_nDragStartTolerance ) + { + return true; + } +#endif + return false; +} + +HCursor Panel::GetDropCursor( CUtlVector< KeyValues * >& msglist ) +{ + return dc_arrow; +} + +bool IsSelfDroppable( CUtlVector< KeyValues * > &dragData ) +{ + if ( dragData.Count() == 0 ) + return false; + + KeyValues *pKeyValues( dragData[ 0 ] ); + if ( !pKeyValues ) + return false; + + return pKeyValues->GetInt( "selfDroppable" ) != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void Panel::OnContinueDragging() +{ +#if defined( VGUI_USEDRAGDROP ) + if ( !m_pDragDrop->m_bDragEnabled ) + return; + + if ( !m_pDragDrop->m_bDragging ) + return; + + int x, y; + input()->GetCursorPos( x, y ); + + // Update last position + m_pDragDrop->m_nLastPos[ 0 ] = x; + m_pDragDrop->m_nLastPos[ 1 ] = y; + + if ( !m_pDragDrop->m_bDragStarted ) + { + if ( CanStartDragging( m_pDragDrop->m_nStartPos[ 0 ], m_pDragDrop->m_nStartPos[ 1 ], x, y ) ) + { + m_pDragDrop->m_bDragStarted = true; + CreateDragData(); + } + else + { + return; + } + } + + if ( !s_DragDropHelper.Get() && m_pDragDrop->m_bShowDragHelper ) + { + s_DragDropHelper = new CDragDropHelperPanel(); + s_DragDropHelper->SetKeyBoardInputEnabled( false ); + s_DragDropHelper->SetMouseInputEnabled( false ); + Assert( s_DragDropHelper.Get() ); + } + + if ( !s_DragDropHelper.Get() ) + return; + + s_DragDropHelper->AddPanel( this ); + + Assert( m_pDragDrop->m_DragData.Count() ); + + vgui::PHandle oldDrop = m_pDragDrop->m_hCurrentDrop; + + // See what's under that + m_pDragDrop->m_hCurrentDrop = NULL; + + // Search under mouse pos... + Panel *dropTarget = FindDropTargetPanel(); + if ( dropTarget ) + { + dropTarget = dropTarget->GetDropTarget( m_pDragDrop->m_DragData ); + } + + // it's not okay until we find a droppable panel + surface()->SetCursor( dc_no ); + + if ( dropTarget ) + { + if ( dropTarget != this || IsSelfDroppable( m_pDragDrop->m_DragData ) ) + { + m_pDragDrop->m_hCurrentDrop = dropTarget; + surface()->SetCursor( dropTarget->GetDropCursor( m_pDragDrop->m_DragData ) ); + } + } + + if ( m_pDragDrop->m_hCurrentDrop.Get() != oldDrop.Get() ) + { + if ( oldDrop.Get() ) + { + oldDrop->OnPanelExitedDroppablePanel( m_pDragDrop->m_DragData ); + } + + if ( m_pDragDrop->m_hCurrentDrop.Get() ) + { + m_pDragDrop->m_hCurrentDrop->OnPanelEnteredDroppablePanel( m_pDragDrop->m_DragData ); + m_pDragDrop->m_hCurrentDrop->OnDropContextHoverHide( m_pDragDrop->m_DragData ); + + // Reset hover time + m_pDragDrop->m_lDropHoverTime = system()->GetTimeMillis(); + m_pDragDrop->m_bDropMenuShown = false; + } + + // Discard any stale context menu... + if ( m_pDragDrop->m_hDropContextMenu.Get() ) + { + delete m_pDragDrop->m_hDropContextMenu.Get(); + } + } + + if ( m_pDragDrop->m_hCurrentDrop != 0 && + m_pDragDrop->m_hDropContextMenu.Get() ) + { + Menu *menu = m_pDragDrop->m_hDropContextMenu; + + VPANEL hover = menu->IsWithinTraverse( x, y, false ); + if ( hover ) + { + Panel *pHover = ipanel()->GetPanel( hover, GetModuleName() ); + if ( pHover ) + { + // Figure out if it's a menu item... + int c = menu->GetItemCount(); + for ( int i = 0; i < c; ++i ) + { + int id = menu->GetMenuID( i ); + MenuItem *item = menu->GetMenuItem( id ); + if ( item == pHover ) + { + menu->SetCurrentlyHighlightedItem( id ); + } + } + } + } + else + { + menu->ClearCurrentlyHighlightedItem(); + } + } +#endif +} + +#if defined( VGUI_USEDRAGDROP ) +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : DragDrop_t +//----------------------------------------------------------------------------- +DragDrop_t *Panel::GetDragDropInfo() +{ + Assert( m_pDragDrop ); + return m_pDragDrop; +} +#endif + +void Panel::OnGetAdditionalDragPanels( CUtlVector< Panel * >& dragabbles ) +{ + // Nothing here +} + +//----------------------------------------------------------------------------- +// Purpose: Virtual method to allow panels to add to the default values +// Input : *msg - +//----------------------------------------------------------------------------- +void Panel::OnCreateDragData( KeyValues *msg ) +{ + // These values are filled in for you: + // "panel" ptr to panel being dropped + // "screenx", "screeny" - drop cursor pos in screen space + // "x", "y" - drop coordinates relative to this window (the window being dropped upon) +} + +// Called if m_flHoverContextTime was non-zero, allows droppee to preview the drop data and show an appropriate menu +bool Panel::GetDropContextMenu( Menu *menu, CUtlVector< KeyValues * >& msglist ) +{ + return false; +} + +void Panel::CreateDragData() +{ +#if defined( VGUI_USEDRAGDROP ) + int i, c; + + if ( m_pDragDrop->m_DragData.Count() ) + { + return; + } + + PHandle h; + h = this; + m_pDragDrop->m_DragPanels.AddToTail( h ); + + CUtlVector< Panel * > temp; + OnGetAdditionalDragPanels( temp ); + c = temp.Count(); + for ( i = 0; i < c; ++i ) + { + h = temp[ i ]; + m_pDragDrop->m_DragPanels.AddToTail( h ); + } + + c = m_pDragDrop->m_DragPanels.Count(); + for ( i = 0 ; i < c; ++i ) + { + Panel *sibling = m_pDragDrop->m_DragPanels[ i ].Get(); + if ( !sibling ) + { + continue; + } + + KeyValues *msg = new KeyValues( "DragDrop" ); + msg->SetPtr( "panel", sibling ); + + sibling->OnCreateDragData( msg ); + + m_pDragDrop->m_DragData.AddToTail( msg ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : KeyValues +//----------------------------------------------------------------------------- +void Panel::GetDragData( CUtlVector< KeyValues * >& list ) +{ +#if defined( VGUI_USEDRAGDROP ) + int i, c; + + list.RemoveAll(); + + c = m_pDragDrop->m_DragData.Count(); + for ( i = 0 ; i < c; ++i ) + { + list.AddToTail( m_pDragDrop->m_DragData[ i ] ); + } +#endif +} + +#if defined( VGUI_USEDRAGDROP ) +CDragDropHelperPanel::CDragDropHelperPanel() : BaseClass( NULL, "DragDropHelper" ) +{ + SetVisible( true ); + SetPaintEnabled( false ); + SetPaintBackgroundEnabled( false ); + SetMouseInputEnabled( false ); + SetKeyBoardInputEnabled( false ); + // SetCursor( dc_none ); + ipanel()->SetTopmostPopup( GetVPanel(), true ); + int w, h; + surface()->GetScreenSize( w, h ); + SetBounds( 0, 0, w, h ); + + SetPostChildPaintEnabled( true ); + + MakePopup( false ); +} + +VPANEL CDragDropHelperPanel::IsWithinTraverse(int x, int y, bool traversePopups) +{ + return (VPANEL)0; +} + +void CDragDropHelperPanel::PostChildPaint() +{ + int c = m_PaintList.Count(); + for ( int i = c - 1; i >= 0 ; --i ) + { + DragHelperPanel_t& data = m_PaintList[ i ]; + + Panel *panel = data.m_hPanel.Get(); + if ( !panel ) + { + m_PaintList.Remove( i ); + continue; + } + + Panel *dropPanel = panel->GetDragDropInfo()->m_hCurrentDrop.Get(); + if ( panel ) + { + if ( !dropPanel ) + { + panel->OnDraggablePanelPaint(); + } + else + { + CUtlVector< Panel * > temp; + CUtlVector< PHandle >& dragData = panel->GetDragDropInfo()->m_DragPanels; + CUtlVector< KeyValues * >& msglist = panel->GetDragDropInfo()->m_DragData; + int j, nDragData; + nDragData = dragData.Count(); + for ( j = 0; j < nDragData; ++j ) + { + Panel *pPanel = dragData[ j ].Get(); + if ( pPanel ) + { + temp.AddToTail( pPanel ); + } + } + + dropPanel->OnDroppablePanelPaint( msglist, temp ); + } + } + } + + if ( c == 0 ) + { + MarkForDeletion(); + } +} + +void CDragDropHelperPanel::AddPanel( Panel *current ) +{ + if ( !current ) + return; + + Menu *hover = current->GetDragDropInfo()->m_hDropContextMenu.Get(); + + surface()->MovePopupToFront( GetVPanel() ); + if ( hover && hover->IsPopup() ) + { + surface()->MovePopupToFront( hover->GetVPanel() ); + } + + int c = m_PaintList.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( m_PaintList[ i ].m_hPanel.Get() == current ) + return; + } + + DragHelperPanel_t data; + data.m_hPanel = current; + m_PaintList.AddToTail( data ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *search - +//----------------------------------------------------------------------------- +void CDragDropHelperPanel::RemovePanel( Panel *search ) +{ + int c = m_PaintList.Count(); + for ( int i = c - 1 ; i >= 0; --i ) + { + if ( m_PaintList[ i ].m_hPanel.Get() == search ) + { + m_PaintList.Remove( i ); + return; + } + } +} +#endif +//----------------------------------------------------------------------------- +// Purpose: Enumerates panels under mouse x,y +// Input : panelList - +// x - +// y - +// check - +//----------------------------------------------------------------------------- +void Panel::FindDropTargetPanel_R( CUtlVector< VPANEL >& panelList, int x, int y, VPANEL check ) +{ +#if defined( VGUI_USEDRAGDROP ) + if ( !ipanel()->IsFullyVisible( check ) ) + return; + + if ( ::ShouldHandleInputMessage( check ) && ipanel()->IsWithinTraverse( check, x, y, false ) ) + { + panelList.AddToTail( check ); + } + + CUtlVector< VPANEL > &children = ipanel()->GetChildren( check ); + int childCount = children.Count(); + for ( int i = 0; i < childCount; i++ ) + { + VPANEL child = children[ i ]; + FindDropTargetPanel_R( panelList, x, y, child ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Panel +//----------------------------------------------------------------------------- +Panel *Panel::FindDropTargetPanel() +{ +#if defined( VGUI_USEDRAGDROP ) + if ( !s_DragDropHelper.Get() ) + return NULL; + + CUtlVector< VPANEL > hits; + + int x, y; + input()->GetCursorPos( x, y ); + + VPANEL embedded = surface()->GetEmbeddedPanel(); + VPANEL helper = s_DragDropHelper.Get()->GetVPanel(); + + if ( surface()->IsCursorVisible() && surface()->IsWithin(x, y) ) + { + // faster version of code below + // checks through each popup in order, top to bottom windows + int c = surface()->GetPopupCount(); + for (int i = c - 1; i >= 0 && hits.Count() == 0; i--) + { + VPANEL popup = surface()->GetPopup(i); + if ( popup == embedded ) + continue; + + // Don't return helper panel!!! + if ( popup == helper ) + continue; + + if ( !ipanel()->IsFullyVisible( popup ) ) + continue; + + FindDropTargetPanel_R( hits, x, y, popup ); + } + + // Check embedded + if ( !hits.Count() ) + { + FindDropTargetPanel_R( hits, x, y, embedded ); + } + } + + // Nothing under mouse... + if ( !hits.Count() ) + return NULL; + + // Return topmost panel under mouse, if it's visible to this .dll + Panel *panel = NULL; + int nCount = hits.Count(); + while ( --nCount >= 0 ) + { + panel = ipanel()->GetPanel( hits[ nCount ], GetModuleName() ); + if ( panel ) + return panel; + } +#endif + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Mouse is on draggable panel and has started moving, but is not over a droppable panel yet +// Input : - +//----------------------------------------------------------------------------- +void Panel::OnDraggablePanelPaint() +{ +#if defined( VGUI_USEDRAGDROP ) + int sw, sh; + GetSize( sw, sh ); + + int x, y; + input()->GetCursorPos( x, y ); + int w, h; + + w = min( sw, 80 ); + h = min( sh, 80 ); + x -= ( w >> 1 ); + y -= ( h >> 1 ); + + surface()->DrawSetColor( m_clrDragFrame ); + surface()->DrawOutlinedRect( x, y, x + w, y + h ); + + if ( m_pDragDrop->m_DragPanels.Count() > 1 ) + { + surface()->DrawSetTextColor( m_clrDragFrame ); + surface()->DrawSetTextFont( m_infoFont ); + surface()->DrawSetTextPos( x + 5, y + 2 ); + + wchar_t sz[ 64 ]; + V_swprintf_safe( sz, L"[ %i ]", m_pDragDrop->m_DragPanels.Count() ); + + surface()->DrawPrintText( sz, wcslen( sz ) ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Mouse is now over a droppable panel +// Input : *dragPanel - +//----------------------------------------------------------------------------- +void Panel::OnDroppablePanelPaint( CUtlVector< KeyValues * >& msglist, CUtlVector< Panel * >& dragPanels ) +{ +#if defined( VGUI_USEDRAGDROP ) + if ( !dragPanels.Count() ) + return; + + // Convert this panel's bounds to screen space + int w, h; + GetSize( w, h ); + + int x, y; + x = y = 0; + LocalToScreen( x, y ); + + surface()->DrawSetColor( m_clrDropFrame ); + // Draw 2 pixel frame + surface()->DrawOutlinedRect( x, y, x + w, y + h ); + surface()->DrawOutlinedRect( x+1, y+1, x + w-1, y + h-1 ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Color +//----------------------------------------------------------------------------- +Color Panel::GetDropFrameColor() +{ +#if defined( VGUI_USEDRAGDROP ) + return m_clrDropFrame; +#endif + return Color(0, 0, 0, 0); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Color +//----------------------------------------------------------------------------- +Color Panel::GetDragFrameColor() +{ +#if defined( VGUI_USEDRAGDROP ) + return m_clrDragFrame; +#endif + return Color(0, 0, 0, 0); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *data - +//----------------------------------------------------------------------------- +void Panel::OnPanelDropped( CUtlVector< KeyValues * >& data ) +{ + // Empty. Derived classes would implement handlers here +} + +//----------------------------------------------------------------------------- +// called on droptarget when draggable panel enters droptarget +//----------------------------------------------------------------------------- +void Panel::OnPanelEnteredDroppablePanel( CUtlVector< KeyValues * >& msglist ) +{ + // Empty. Derived classes would implement handlers here +} + +//----------------------------------------------------------------------------- +// called on droptarget when draggable panel exits droptarget +//----------------------------------------------------------------------------- +void Panel::OnPanelExitedDroppablePanel ( CUtlVector< KeyValues * >& msglist ) +{ + // Empty. Derived classes would implement handlers here +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void Panel::DragDropStartDragging() +{ +#if defined( VGUI_USEDRAGDROP ) + // We somehow missed a mouse release, cancel the previous drag + if ( g_DragDropCapture.Get() ) + { + if ( HasParent( g_DragDropCapture.Get()->GetVPanel() ) ) + return; + + bool started = g_DragDropCapture->GetDragDropInfo()->m_bDragStarted; + g_DragDropCapture->OnFinishDragging( true, (MouseCode)-1 ); + if ( started ) + { + return; + } + } + + // Find actual target panel + Panel *panel = GetDragPanel(); + if ( !panel ) + return; + + DragDrop_t *data = panel->GetDragDropInfo(); + if ( !data ) + return; + + if ( !panel->IsDragEnabled() ) + return; + + if ( data->m_bDragging ) + return; + + panel->OnStartDragging(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool Panel::IsBeingDragged() +{ +#if defined( VGUI_USEDRAGDROP ) + if ( !g_DragDropCapture.Get() ) + return false; + + if ( g_DragDropCapture.Get() == this ) + return true; + + // If we encounter a blocker, stop chaining + if ( m_pDragDrop->m_bPreventChaining ) + return false; + + // Chain up + if ( GetParent() ) + { + return GetParent()->IsBeingDragged(); + } +#endif + // No luck + return false; +} + +struct srect_t +{ + int x0, y0; + int x1, y1; + + bool IsDegenerate() + { + if ( x1 - x0 <= 0 ) + return true; + if ( y1 - y0 <= 0 ) + return true; + return false; + } +}; + +// Draws a filled rect of specified bounds, but omits the bounds of the skip panel from those bounds +void Panel::FillRectSkippingPanel( const Color &clr, int x, int y, int w, int h, Panel *skipPanel ) +{ + int sx = 0, sy = 0, sw, sh; + skipPanel->GetSize( sw, sh ); + skipPanel->LocalToScreen( sx, sy ); + ScreenToLocal( sx, sy ); + + surface()->DrawSetColor( clr ); + + srect_t r1; + r1.x0 = x; + r1.y0 = y; + r1.x1 = x + w; + r1.y1 = y + h; + + srect_t r2; + r2.x0 = sx; + r2.y0 = sy; + r2.x1 = sx + sw; + r2.y1 = sy + sh; + + int topy = r1.y0; + int bottomy = r1.y1; + + // We'll descend vertically and draw: + // 1 a possible bar across the top + // 2 a possible bar across the bottom + // 3 possible left bar + // 4 possible right bar + + // Room at top? + if ( r2.y0 > r1.y0 ) + { + topy = r2.y0; + + surface()->DrawFilledRect( r1.x0, r1.y0, r1.x1, topy ); + } + + // Room at bottom? + if ( r2.y1 < r1.y1 ) + { + bottomy = r2.y1; + + surface()->DrawFilledRect( r1.x0, bottomy, r1.x1, r1.y1 ); + } + + // Room on left side? + if ( r2.x0 > r1.x0 ) + { + int left = r2.x0; + + surface()->DrawFilledRect( r1.x0, topy, left, bottomy ); + } + + // Room on right side + if ( r2.x1 < r1.x1 ) + { + int right = r2.x1; + + surface()->DrawFilledRect( right, topy, r1.x1, bottomy ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *child - +//----------------------------------------------------------------------------- +void Panel::SetSkipChildDuringPainting( Panel *child ) +{ + m_SkipChild = child; +} + +HPanel Panel::ToHandle() const +{ + return ivgui()->PanelToHandle( _vpanel ); +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::NavigateUp() +{ + Panel *target = GetNavUp(); + if ( target ) + { + NavigateFrom(); + target->m_LastNavDirection = ND_UP; + target->NavigateTo(); + } + + return target; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::NavigateDown() +{ + Panel *target = GetNavDown(); + if ( target ) + { + NavigateFrom(); + target->m_LastNavDirection = ND_DOWN; + target->NavigateTo(); + } + + return target; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::NavigateLeft() +{ + Panel *target = GetNavLeft(); + if ( target ) + { + NavigateFrom(); + target->m_LastNavDirection = ND_LEFT; + target->NavigateTo(); + } + return target; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::NavigateRight() +{ + Panel *target = GetNavRight(); + if ( target ) + { + NavigateFrom(); + target->m_LastNavDirection = ND_RIGHT; + target->NavigateTo(); + } + return target; +} + +Panel* Panel::NavigateActivate() +{ + Panel *target = GetNavActivate(); + if ( target ) + { + NavigateFrom(); + target->m_LastNavDirection = ND_NONE; + target->NavigateTo(); + } + return target; +} + +Panel* Panel::NavigateBack() +{ + Panel *target = GetNavBack(); + if ( target ) + { + NavigateFrom(); + target->m_LastNavDirection = ND_NONE; + target->NavigateTo(); + } + return target; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::NavigateTo() +{ + if ( IsX360() ) + { + RequestFocus( 0 ); + } + + CallParentFunction( new KeyValues( "OnNavigateTo", "panelName", GetName() ) ); + + Panel *target = GetNavToRelay(); + if ( target ) + { + NavigateFrom(); + target->m_LastNavDirection = ND_NONE; + NavigateToChild( target ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::NavigateFrom() +{ + for ( int i = 0; i < GetChildCount(); ++i ) + { + Panel* currentNav = GetChild(i); + if ( currentNav != 0 ) + { + currentNav->NavigateFrom(); + } + } + + CallParentFunction( new KeyValues( "OnNavigateFrom", "panelName", GetName() ) ); + + if ( m_pTooltips ) + { + m_pTooltips->HideTooltip(); + } + + m_LastNavDirection = ND_NONE; +} + +void Panel::NavigateToChild( Panel *pNavigateTo ) +{ + for( int i = 0; i != GetChildCount(); ++i ) + { + vgui::Panel *pChild = GetChild(i); + if( pChild ) + pChild->NavigateFrom(); + } + pNavigateTo->NavigateTo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::SetNavUp( Panel* navUp ) +{ + Panel* lastNav = m_NavUp; + m_NavUp = navUp; + + if( navUp ) + m_sNavUpName = navUp->GetName(); + else + m_sNavUpName.Clear(); + + return lastNav; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::SetNavDown( Panel* navDown ) +{ + Panel* lastNav = m_NavDown; + m_NavDown = navDown; + + if( navDown ) + m_sNavDownName = navDown->GetName(); + else + m_sNavDownName.Clear(); + + return lastNav; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::SetNavLeft( Panel* navLeft ) +{ + Panel* lastNav = m_NavLeft; + m_NavLeft = navLeft; + + if( navLeft ) + m_sNavLeftName = navLeft->GetName(); + else + m_sNavLeftName.Clear(); + + return lastNav; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel* Panel::SetNavRight( Panel* navRight ) +{ + Panel* lastNav = m_NavRight; + m_NavRight = navRight; + + if( navRight ) + m_sNavRightName = navRight->GetName(); + else + m_sNavRightName.Clear(); + + return lastNav; +} + +Panel* Panel::SetNavToRelay( Panel* navToRelay ) +{ + Panel* lastNav = m_NavToRelay; + m_NavToRelay = navToRelay; + + return lastNav; +} + +Panel* Panel::SetNavActivate( Panel* navActivate ) +{ + Panel* lastNav = m_NavActivate; + m_NavActivate = navActivate; + + return lastNav; +} + +Panel* Panel::SetNavBack( Panel* navBack ) +{ + Panel* lastNav = m_NavBack; + m_NavBack = navBack; + + return lastNav; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Panel::NAV_DIRECTION Panel::GetLastNavDirection() +{ + return m_LastNavDirection; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::OnNavigateTo( const char* panelName ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::OnNavigateFrom( const char* panelName ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetNavUp( const char* controlName ) +{ + if ( controlName && 0 < Q_strlen( controlName ) && GetParent() != 0 ) + { + m_NavUp = NULL; + m_sNavUpName = controlName; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetNavDown( const char* controlName ) +{ + if ( controlName && 0 < Q_strlen( controlName ) && GetParent() != 0 ) + { + m_NavDown = NULL; + m_sNavDownName = controlName; + } +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetNavLeft( const char* controlName ) +{ + if ( controlName && 0 < Q_strlen( controlName ) && GetParent() != 0 ) + { + m_NavLeft = NULL; + m_sNavLeftName = controlName; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Panel::SetNavRight( const char* controlName ) +{ + if ( controlName && 0 < Q_strlen( controlName ) && GetParent() != 0 ) + { + m_NavRight = NULL; + m_sNavRightName = controlName; + } +} + +void Panel::SetNavToRelay( const char* controlName ) +{ + if ( controlName && 0 < Q_strlen( controlName ) && GetParent() != 0 ) + { + m_NavToRelay = NULL; + m_sNavToRelayName = controlName; + } +} + +void Panel::SetNavActivate( const char* controlName ) +{ + if ( controlName && 0 < Q_strlen( controlName ) && GetParent() != 0 ) + { + m_NavActivate = NULL; + m_sNavActivateName = controlName; + } +} + +void Panel::SetNavBack( const char* controlName ) +{ + if ( controlName && 0 < Q_strlen( controlName ) && GetParent() != 0 ) + { + m_NavBack = NULL; + m_sNavBackName = controlName; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +vgui::Panel* Panel::GetNavUp( Panel *first ) +{ + if ( !m_NavUp && m_sNavUpName.Length() > 0 ) + { + Panel *pParent = GetParent(); + const char *pName = m_sNavUpName.String(); + while ( pParent && pName[ 0 ] == '<' ) + { + pParent = pParent->GetParent(); + pName++; + } + + if ( !pParent ) + { + return NULL; + } + + Panel *foundPanel = pParent->FindChildByName( pName, true ); + if ( foundPanel != 0 ) + { + m_NavUp = foundPanel; + } + } + + vgui::Panel* nextPanel = m_NavUp; + if( m_NavUp && m_NavUp != first && !m_NavUp->IsVisible() ) + { + Panel *firstPanel = first == NULL ? this : first; + nextPanel = nextPanel->GetNavUp( firstPanel ); + } + + return nextPanel; +} + +vgui::Panel* Panel::GetNavDown( Panel *first ) +{ + if ( !m_NavDown && m_sNavDownName.Length() > 0 ) + { + Panel *pParent = GetParent(); + const char *pName = m_sNavDownName.String(); + while ( pParent && pName[ 0 ] == '<' ) + { + pParent = pParent->GetParent(); + pName++; + } + + if ( !pParent ) + { + return NULL; + } + + Panel* foundPanel = pParent->FindChildByName( pName, true ); + if ( foundPanel != 0 ) + { + m_NavDown = foundPanel->GetPanel(); + } + } + + vgui::Panel* nextPanel = m_NavDown; + if( m_NavDown && m_NavDown != first && !m_NavDown->IsVisible() ) + { + Panel *firstPanel = first == NULL ? this : first; + nextPanel = nextPanel->GetNavDown( firstPanel ); + } + + return nextPanel; +} + +vgui::Panel* Panel::GetNavLeft( Panel *first ) +{ + if ( !m_NavLeft && m_sNavLeftName.Length() > 0 ) + { + Panel *pParent = GetParent(); + const char *pName = m_sNavLeftName.String(); + while ( pParent && pName[ 0 ] == '<' ) + { + pParent = pParent->GetParent(); + pName++; + } + + if ( !pParent ) + { + return NULL; + } + + Panel* foundPanel = pParent->FindChildByName( pName, true ); + if ( foundPanel != 0 ) + { + m_NavLeft = foundPanel->GetPanel(); + } + } + + vgui::Panel* nextPanel = m_NavLeft; + if( m_NavLeft && m_NavLeft != first && !m_NavLeft->IsVisible() ) + { + Panel *firstPanel = first == NULL ? this : first; + nextPanel = nextPanel->GetNavLeft( firstPanel ); + } + + return nextPanel; +} + +vgui::Panel* Panel::GetNavRight( Panel *first ) +{ + if ( !m_NavRight && m_sNavRightName.Length() > 0 ) + { + Panel *pParent = GetParent(); + const char *pName = m_sNavRightName.String(); + while ( pParent && pName[ 0 ] == '<' ) + { + pParent = pParent->GetParent(); + pName++; + } + + if ( !pParent ) + { + return NULL; + } + + Panel* foundPanel = pParent->FindChildByName( pName, true ); + if ( foundPanel != 0 ) + { + m_NavRight = foundPanel->GetPanel(); + } + } + + vgui::Panel* nextPanel = m_NavRight; + if( m_NavRight && m_NavRight != first && !m_NavRight->IsVisible() ) + { + Panel *firstPanel = first == NULL ? this : first; + nextPanel = nextPanel->GetNavRight( firstPanel ); + } + + return nextPanel; +} + +vgui::Panel* Panel::GetNavToRelay( Panel *first ) +{ + if ( !m_NavToRelay && m_sNavToRelayName.Length() > 0 ) + { + Panel *pParent = this; + const char *pName = m_sNavToRelayName.String(); + while ( pParent && pName[ 0 ] == '<' ) + { + pParent = pParent->GetParent(); + pName++; + } + + if ( !pParent ) + { + return NULL; + } + + Panel* foundPanel = pParent->FindChildByName( pName, true ); + if ( foundPanel != 0 ) + { + m_NavToRelay = foundPanel->GetPanel(); + } + } + + vgui::Panel* nextPanel = m_NavToRelay; + if ( m_NavToRelay && m_NavToRelay != first && !m_NavToRelay->IsVisible() ) + { + Panel *firstPanel = first == NULL ? this : first; + nextPanel = nextPanel->GetNavToRelay( firstPanel ); + } + + return nextPanel; +} + +vgui::Panel* Panel::GetNavActivate( Panel *first ) +{ + if ( !m_NavActivate && m_sNavActivateName.Length() > 0 ) + { + Panel *pParent = GetParent(); + const char *pName = m_sNavActivateName.String(); + while ( pParent && pName[ 0 ] == '<' ) + { + pParent = pParent->GetParent(); + pName++; + } + + if ( !pParent ) + { + return NULL; + } + + Panel* foundPanel = pParent->FindChildByName( pName, true ); + if ( foundPanel != 0 ) + { + m_NavActivate = foundPanel->GetPanel(); + } + } + + vgui::Panel* nextPanel = m_NavActivate; + if ( m_NavActivate && m_NavActivate != first && !m_NavActivate->IsVisible() ) + { + Panel *firstPanel = first == NULL ? this : first; + nextPanel = nextPanel->GetNavActivate( firstPanel ); + } + + return nextPanel; +} + +vgui::Panel* Panel::GetNavBack( Panel *first ) +{ + if ( !m_NavBack && m_sNavBackName.Length() > 0 ) + { + Panel *pParent = GetParent(); + const char *pName = m_sNavBackName.String(); + while ( pParent && pName[ 0 ] == '<' ) + { + pParent = pParent->GetParent(); + pName++; + } + + if ( !pParent ) + { + return NULL; + } + + Panel *foundPanel = pParent->FindChildByName( pName ); + if ( foundPanel ) + { + m_NavBack = foundPanel; + } + } + + vgui::Panel* nextPanel = m_NavBack; + if ( m_NavBack && m_NavBack != first && !m_NavBack->IsVisible() ) + { + Panel *firstPanel = first == NULL ? this : first; + nextPanel = nextPanel->GetNavBack( firstPanel ); + } + + return nextPanel; +} + +vgui::Panel* Panel::GetNavUpPanel() +{ + return m_NavUp; +} + +vgui::Panel* Panel::GetNavDownPanel() +{ + return m_NavDown; +} + +vgui::Panel* Panel::GetNavLeftPanel() +{ + return m_NavLeft; +} + +vgui::Panel* Panel::GetNavRightPanel() +{ + return m_NavRight; +} + +vgui::Panel* Panel::GetNavToRelayPanel() +{ + return m_NavToRelay; +} + +vgui::Panel* Panel::GetNavActivatePanel() +{ + return m_NavActivate; +} + +vgui::Panel* Panel::GetNavBackPanel() +{ + return m_NavBack; +} + +void Panel::SetConsoleStylePanel( bool bConsoleStyle ) +{ + m_bIsConsoleStylePanel = bConsoleStyle; +} + +bool Panel::IsConsoleStylePanel() const +{ + return m_bIsConsoleStylePanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Utility class for handling message map allocation +//----------------------------------------------------------------------------- +class CPanelMessageMapDictionary +{ +public: + CPanelMessageMapDictionary() : m_PanelMessageMapPool( sizeof(PanelMessageMap), 32, CUtlMemoryPool::GROW_FAST, "CPanelMessageMapDictionary::m_PanelMessageMapPool" ) + { + m_MessageMaps.RemoveAll(); + } + + PanelMessageMap *FindOrAddPanelMessageMap( char const *className ); + PanelMessageMap *FindPanelMessageMap( char const *className ); +private: + + struct PanelMessageMapDictionaryEntry + { + PanelMessageMap *map; + }; + + char const *StripNamespace( char const *className ); + + CUtlDict< PanelMessageMapDictionaryEntry, int > m_MessageMaps; + CUtlMemoryPool m_PanelMessageMapPool; +}; + + +char const *CPanelMessageMapDictionary::StripNamespace( char const *className ) +{ + if ( !strnicmp( className, "vgui::", 6 ) ) + { + return className + 6; + } + return className; +} + +//----------------------------------------------------------------------------- +// Purpose: Find but don't add mapping +//----------------------------------------------------------------------------- +PanelMessageMap *CPanelMessageMapDictionary::FindPanelMessageMap( char const *className ) +{ + int lookup = m_MessageMaps.Find( StripNamespace( className ) ); + if ( lookup != m_MessageMaps.InvalidIndex() ) + { + return m_MessageMaps[ lookup ].map; + } + return NULL; +} + +#include <tier0/memdbgoff.h> +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +PanelMessageMap *CPanelMessageMapDictionary::FindOrAddPanelMessageMap( char const *className ) +{ + PanelMessageMap *map = FindPanelMessageMap( className ); + if ( map ) + return map; + + PanelMessageMapDictionaryEntry entry; + // use the alloc in place method of new + entry.map = new (m_PanelMessageMapPool.Alloc(sizeof(PanelMessageMap))) PanelMessageMap; + Construct(entry.map); + m_MessageMaps.Insert( StripNamespace( className ), entry ); + return entry.map; +} +#include <tier0/memdbgon.h> + +#if defined( VGUI_USEKEYBINDINGMAPS ) +//----------------------------------------------------------------------------- +// Purpose: Utility class for handling keybinding map allocation +//----------------------------------------------------------------------------- +class CPanelKeyBindingMapDictionary +{ +public: + CPanelKeyBindingMapDictionary() : m_PanelKeyBindingMapPool( sizeof(PanelKeyBindingMap), 32, CUtlMemoryPool::GROW_FAST, "CPanelKeyBindingMapDictionary::m_PanelKeyBindingMapPool" ) + { + m_MessageMaps.RemoveAll(); + } + + PanelKeyBindingMap *FindOrAddPanelKeyBindingMap( char const *className ); + PanelKeyBindingMap *FindPanelKeyBindingMap( char const *className ); +private: + + struct PanelKeyBindingMapDictionaryEntry + { + PanelKeyBindingMap *map; + }; + + char const *StripNamespace( char const *className ); + + CUtlDict< PanelKeyBindingMapDictionaryEntry, int > m_MessageMaps; + CUtlMemoryPool m_PanelKeyBindingMapPool; +}; + + +char const *CPanelKeyBindingMapDictionary::StripNamespace( char const *className ) +{ + if ( !strnicmp( className, "vgui::", 6 ) ) + { + return className + 6; + } + return className; +} + +//----------------------------------------------------------------------------- +// Purpose: Find but don't add mapping +//----------------------------------------------------------------------------- +PanelKeyBindingMap *CPanelKeyBindingMapDictionary::FindPanelKeyBindingMap( char const *className ) +{ + int lookup = m_MessageMaps.Find( StripNamespace( className ) ); + if ( lookup != m_MessageMaps.InvalidIndex() ) + { + return m_MessageMaps[ lookup ].map; + } + return NULL; +} + +#include <tier0/memdbgoff.h> +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +PanelKeyBindingMap *CPanelKeyBindingMapDictionary::FindOrAddPanelKeyBindingMap( char const *className ) +{ + PanelKeyBindingMap *map = FindPanelKeyBindingMap( className ); + if ( map ) + return map; + + PanelKeyBindingMapDictionaryEntry entry; + // use the alloc in place method of new + entry.map = new (m_PanelKeyBindingMapPool.Alloc(sizeof(PanelKeyBindingMap))) PanelKeyBindingMap; + Construct(entry.map); + m_MessageMaps.Insert( StripNamespace( className ), entry ); + return entry.map; +} + +#include <tier0/memdbgon.h> + +CPanelKeyBindingMapDictionary& GetPanelKeyBindingMapDictionary() +{ + static CPanelKeyBindingMapDictionary dictionary; + return dictionary; +} + +#endif // VGUI_USEKEYBINDINGMAPS + +CPanelMessageMapDictionary& GetPanelMessageMapDictionary() +{ + static CPanelMessageMapDictionary dictionary; + return dictionary; +} + +namespace vgui +{ + + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + PanelMessageMap *FindOrAddPanelMessageMap( char const *className ) + { + return GetPanelMessageMapDictionary().FindOrAddPanelMessageMap( className ); + } + + //----------------------------------------------------------------------------- + // Purpose: Find but don't add mapping + //----------------------------------------------------------------------------- + PanelMessageMap *FindPanelMessageMap( char const *className ) + { + return GetPanelMessageMapDictionary().FindPanelMessageMap( className ); + } + +#if defined( VGUI_USEKEYBINDINGMAPS ) + CPanelKeyBindingMapDictionary& GetPanelKeyBindingMapDictionary() + { + static CPanelKeyBindingMapDictionary dictionary; + return dictionary; + } + //----------------------------------------------------------------------------- + // Purpose: + //----------------------------------------------------------------------------- + PanelKeyBindingMap *FindOrAddPanelKeyBindingMap( char const *className ) + { + return GetPanelKeyBindingMapDictionary().FindOrAddPanelKeyBindingMap( className ); + } + + //----------------------------------------------------------------------------- + // Purpose: Find but don't add mapping + //----------------------------------------------------------------------------- + PanelKeyBindingMap *FindPanelKeyBindingMap( char const *className ) + { + return GetPanelKeyBindingMapDictionary().FindPanelKeyBindingMap( className ); + } +#endif // VGUI_USEKEYBINDINGMAPS + +SortedPanel_t::SortedPanel_t( Panel *panel ) +{ + pPanel = panel; pButton = dynamic_cast< Button* >( panel ); +} + + +void VguiPanelGetSortedChildPanelList( Panel *pParentPanel, void *pSortedPanels ) +{ + CUtlSortVector< SortedPanel_t, CSortedPanelYLess > *pList = reinterpret_cast< CUtlSortVector< SortedPanel_t, CSortedPanelYLess >* >( pSortedPanels ); + + for ( int i = 0; i < pParentPanel->GetChildCount(); i++ ) + { + // perform auto-layout on the child panel + Panel *pPanel = pParentPanel->GetChild( i ); + if ( !pPanel || !pPanel->IsVisible() ) + continue; + + pList->Insert( SortedPanel_t( static_cast< Panel* >( pPanel ) ) ); + } +} + +void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, char *pchFilter /*= NULL*/, int nFilterType /*= 0*/ ) +{ + CUtlSortVector< SortedPanel_t, CSortedPanelYLess > *pList = reinterpret_cast< CUtlSortVector< SortedPanel_t, CSortedPanelYLess >* >( pSortedPanels ); + + for ( int i = 0; i < pParentPanel->GetChildCount(); i++ ) + { + // perform auto-layout on the child panel + Button *pPanel = dynamic_cast< Button* >( pParentPanel->GetChild( i ) ); + if ( !pPanel || !pPanel->IsVisible() ) + continue; + + if ( pchFilter && pchFilter[ 0 ] != '\0' ) + { + char szBuff[ 128 ]; + pPanel->GetText( szBuff, sizeof( szBuff ) ); + + // Prefix + if ( nFilterType == 0 ) + { + if ( !StringHasPrefix( szBuff, pchFilter ) ) + { + continue; + } + } + // Substring + else if ( nFilterType == 1 ) + { + if ( V_strstr( szBuff, pchFilter ) == NULL ) + { + continue; + } + } + } + + pList->Insert( SortedPanel_t( pPanel ) ); + } +} + +int VguiPanelNavigateSortedChildButtonList( void *pSortedPanels, int nDir ) +{ + CUtlSortVector< SortedPanel_t, CSortedPanelYLess > *pList = reinterpret_cast< CUtlSortVector< SortedPanel_t, CSortedPanelYLess >* >( pSortedPanels ); + + if ( pList->Count() <= 0 ) + return -1; + + if ( nDir != 0 ) + { + int nArmed = -1; + for ( int i = 0; i < pList->Count(); i++ ) + { + if ( (*pList)[ i ].pButton->IsArmed() ) + { + nArmed = i; + break; + } + } + + if ( nArmed == -1 ) + { + (*pList)[ 0 ].pButton->SetArmed( true ); + return 0; + } + else + { + int nNewArmed = clamp( nArmed + nDir, 0, pList->Count() - 1 ); + if ( nNewArmed != nArmed ) + { + (*pList)[ nArmed ].pButton->SetArmed( false ); + } + + (*pList)[ nNewArmed ].pButton->RequestFocus(); + (*pList)[ nNewArmed ].pButton->SetArmed( true ); + + return nNewArmed; + } + } + + return -1; +} + + +int ComputeWide(Panel* pPanel, unsigned int& nBuildFlags, KeyValues *inResourceData, int nParentWide, int nParentTall, bool bComputingOther) +{ + int wide = pPanel->GetWide(); + + const char *wstr = inResourceData->GetString("wide", NULL); + if (wstr) + { + if (wstr[0] == 'f' || wstr[0] == 'F') + { + nBuildFlags |= Panel::BUILDMODE_SAVE_WIDE_FULL; + wstr++; + } + else + { + if (wstr[0] == 'o' || wstr[0] == 'O') + { + wstr++; + if (bComputingOther) + { + Warning("Wide and Tall of panel %s are set to be each other!\n", pPanel->GetName()); + return 0; + } + + nBuildFlags |= Panel::BUILDMODE_SAVE_WIDE_PROPORTIONAL_TALL; + wide = ComputeTall(pPanel, nBuildFlags, inResourceData, nParentWide, nParentTall, true); + + if (pPanel->IsProportional()) + { + wide = scheme()->GetProportionalNormalizedValue(wide); + } + } + else if (wstr[0] == 'p' || wstr[0] == 'P') + { + nBuildFlags |= Panel::BUILDMODE_SAVE_WIDE_PROPORTIONAL; + wstr++; + } + else if (wstr[0] == 's' || wstr[0] == 'S') + { + nBuildFlags |= Panel::BUILDMODE_SAVE_WIDE_PROPORTIONAL_SELF; + wstr++; + } + } + + float flWide = atof(wstr); + if (!(nBuildFlags & Panel::BUILDMODE_SAVE_WIDE_PROPORTIONAL_TALL)) + { + wide = atoi(wstr); + } + + if (nBuildFlags & Panel::BUILDMODE_SAVE_WIDE_PROPORTIONAL_TALL) + { + wide = scheme()->GetProportionalScaledValueEx(pPanel->GetScheme(), wide); + wide *= flWide; + } + else if (nBuildFlags & Panel::BUILDMODE_SAVE_WIDE_PROPORTIONAL) + { + wide = scheme()->GetProportionalScaledValueEx(pPanel->GetScheme(), wide); + wide = nParentWide - wide; + wide *= flWide; + } + else if (nBuildFlags & Panel::BUILDMODE_SAVE_WIDE_PROPORTIONAL_SELF) + { + wide = pPanel->GetWide() * flWide; + } + else + { + if (pPanel->IsProportional()) + { + // scale the width up to our screen co-ords + wide = scheme()->GetProportionalScaledValueEx(pPanel->GetScheme(), wide); + } + // now correct the alignment + if (nBuildFlags & Panel::BUILDMODE_SAVE_WIDE_FULL) + { + wide = nParentWide - wide; + } + } + } + + return wide; +} + +int ComputeTall(Panel* pPanel, unsigned int& nBuildFlags, KeyValues *inResourceData, int nParentWide, int nParentTall, bool bComputingOther) +{ + int tall = pPanel->GetTall(); + + // allow tall to be use the "fill" option, set to the height of the parent/screen + const char *tstr = inResourceData->GetString("tall", NULL); + if (tstr) + { + if (tstr[0] == 'f' || tstr[0] == 'F') + { + nBuildFlags |= Panel::BUILDMODE_SAVE_TALL_FULL; + tstr++; + } + else + { + if (tstr[0] == 'o' || tstr[0] == 'O') + { + tstr++; + if (bComputingOther) + { + Warning("Wide and Tall of panel %s are set to be each other!\n", pPanel->GetName()); + return 0; + } + + nBuildFlags |= Panel::BUILDMODE_SAVE_TALL_PROPORTIONAL_WIDE; + tall = ComputeWide(pPanel, nBuildFlags, inResourceData, nParentWide, nParentTall, true); + if (pPanel->IsProportional()) + { + tall = scheme()->GetProportionalNormalizedValue(tall); + } + } + else if (tstr[0] == 'p' || tstr[0] == 'P') + { + nBuildFlags |= Panel::BUILDMODE_SAVE_TALL_PROPORTIONAL; + tstr++; + } + else if (tstr[0] == 's' || tstr[0] == 'S') + { + nBuildFlags |= Panel::BUILDMODE_SAVE_TALL_PROPORTIONAL_SELF; + tstr++; + } + } + + float flTall = atof(tstr); + if (!(nBuildFlags & Panel::BUILDMODE_SAVE_TALL_PROPORTIONAL_WIDE)) + { + tall = atoi(tstr); + } + + if (nBuildFlags & Panel::BUILDMODE_SAVE_TALL_PROPORTIONAL_WIDE) + { + tall = scheme()->GetProportionalScaledValueEx(pPanel->GetScheme(), tall); + tall *= flTall; + } + else if (nBuildFlags & Panel::BUILDMODE_SAVE_TALL_PROPORTIONAL) + { + // scale the height up to our screen co-ords + tall = scheme()->GetProportionalScaledValueEx(pPanel->GetScheme(), tall); + tall = nParentTall - tall; + tall *= flTall; + } + else if(nBuildFlags & Panel::BUILDMODE_SAVE_TALL_PROPORTIONAL_SELF) + { + tall = pPanel->GetTall() * flTall; + } + else + { + if (pPanel->IsProportional()) + { + // scale the height up to our screen co-ords + tall = scheme()->GetProportionalScaledValueEx(pPanel->GetScheme(), tall); + } + // now correct the alignment + if (nBuildFlags & Panel::BUILDMODE_SAVE_TALL_FULL) + { + tall = nParentTall - tall; + } + } + } + + return tall; +} + +int ComputePos( Panel* pPanel, const char *pszInput, int &nPos, const int& nSize, const int& nParentSize, const bool& bX, EOperator eOp) +{ + const int nFlagRightAlign = bX ? Panel::BUILDMODE_SAVE_XPOS_RIGHTALIGNED : Panel::BUILDMODE_SAVE_YPOS_BOTTOMALIGNED; + const int nFlagCenterAlign = bX ? Panel::BUILDMODE_SAVE_XPOS_CENTERALIGNED : Panel::BUILDMODE_SAVE_YPOS_CENTERALIGNED; + const int nFlagProportionalSelf = bX ? Panel::BUILDMODE_SAVE_XPOS_PROPORTIONAL_SELF : Panel::BUILDMODE_SAVE_YPOS_PROPORTIONAL_SELF; + const int nFlagProportionalParent = bX ? Panel::BUILDMODE_SAVE_XPOS_PROPORTIONAL_PARENT : Panel::BUILDMODE_SAVE_YPOS_PROPORTIONAL_PARENT; + + int nFlags = 0; + int nPosDelta = 0; + if (pszInput) + { + // look for alignment flags + if (pszInput[0] == 'r' || pszInput[0] == 'R') + { + nFlags |= nFlagRightAlign; + pszInput++; + } + else if (pszInput[0] == 'c' || pszInput[0] == 'C') + { + nFlags |= nFlagCenterAlign; + pszInput++; + } + + if (pszInput[0] == 's' || pszInput[0] == 'S') + { + nFlags |= nFlagProportionalSelf; + pszInput++; + } + else if (pszInput[0] == 'p' || pszInput[0] == 'P') + { + nFlags |= nFlagProportionalParent; + pszInput++; + } + + // get the value + int nNewPos = atoi(pszInput); + float flPos = atof(pszInput); + + float flProportion = 1.f; + // scale the x up to our screen co-ords + if ( pPanel->IsProportional() ) + { + int nOldPos = nNewPos; + nNewPos = scheme()->GetProportionalScaledValueEx( pPanel->GetScheme(), nNewPos ); + flProportion = (float)nNewPos / (float)nOldPos; + } + + if (nFlags & nFlagProportionalSelf) + { + nPosDelta = nSize * flPos; + } + else if (nFlags & nFlagProportionalParent) + { + nPosDelta = nParentSize * flPos; + } + else + { + nPosDelta = nNewPos; + } + + // now correct the alignment + if (nFlags & nFlagRightAlign) + { + nNewPos = nParentSize - nPosDelta; + } + else if (nFlags & nFlagCenterAlign) + { + nNewPos = (nParentSize / 2) + nPosDelta; + } + else + { + nNewPos = nPosDelta; + } + + switch (eOp) + { + case OP_ADD: + nPos += nNewPos; + break; + case OP_SUB: + nPos -= nNewPos; + break; + case OP_SET: + nPos = nNewPos; + break; + } + + // Jump the sign if it's there + if (pszInput[0] == '-' || pszInput[0] == '+') + pszInput++; + + // Go past the number + while (V_isdigit(pszInput[0]) || pszInput[0] == '.') + pszInput++; + + // Peep if there's an operator + if (pszInput && pszInput[0]) + { + // Recurse! + switch (pszInput[0]) + { + case '+': + ComputePos( pPanel, ++pszInput, nPos, nSize, nParentSize, bX, OP_ADD); + break; + case '-': + ComputePos( pPanel, ++pszInput, nPos, nSize, nParentSize, bX, OP_SUB); + break; + } + } + + } + + if (tf_debug_tabcontainer.GetBool() && !Q_stricmp("TabContainer", pPanel->GetName())) + { + Msg("TabContainer nFlags:%x nPos:%d nParentSize:%d nPosDelta:%d nSize:%d GetParent:%p (%s) pszInput:'%s'\n", + nFlags, nPos, nParentSize, nPosDelta, nSize, pPanel->GetParent(), pPanel->GetParent() ? pPanel->GetParent()->GetName() : "??", + pszInput ? pszInput : "??"); + } + + return nFlags; +} + +} diff --git a/vgui2/vgui_controls/PanelListPanel.cpp b/vgui2/vgui_controls/PanelListPanel.cpp new file mode 100644 index 0000000..995ce46 --- /dev/null +++ b/vgui2/vgui_controls/PanelListPanel.cpp @@ -0,0 +1,476 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui/MouseCode.h" +#include "vgui/IInput.h" +#include "vgui/IScheme.h" +#include "vgui/ISurface.h" + +#include "vgui_controls/EditablePanel.h" +#include "vgui_controls/ScrollBar.h" +#include "vgui_controls/Label.h" +#include "vgui_controls/Button.h" +#include "vgui_controls/Controls.h" +#include "vgui_controls/PanelListPanel.h" + +#include "KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +DECLARE_BUILD_FACTORY( PanelListPanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +PanelListPanel::PanelListPanel( vgui::Panel *parent, char const *panelName ) : EditablePanel( parent, panelName ) +{ + SetBounds( 0, 0, 100, 100 ); + + m_vbar = new ScrollBar(this, "PanelListPanelVScroll", true); + m_vbar->SetVisible(false); + m_vbar->AddActionSignalTarget( this ); + + m_pPanelEmbedded = new EditablePanel(this, "PanelListEmbedded"); + m_pPanelEmbedded->SetBounds(0, 0, 20, 20); + m_pPanelEmbedded->SetPaintBackgroundEnabled( false ); + m_pPanelEmbedded->SetPaintBorderEnabled(false); + + m_iFirstColumnWidth = 100; // default width + m_iNumColumns = 1; // 1 column by default + + if ( IsProportional() ) + { + m_iDefaultHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_HEIGHT ); + m_iPanelBuffer = scheme()->GetProportionalScaledValueEx( GetScheme(), PANELBUFFER ); + } + else + { + m_iDefaultHeight = DEFAULT_HEIGHT; + m_iPanelBuffer = PANELBUFFER; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +PanelListPanel::~PanelListPanel() +{ + // free data from table + DeleteAllItems(); +} + +void PanelListPanel::SetVerticalBufferPixels( int buffer ) +{ + m_iPanelBuffer = buffer; + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: counts the total vertical pixels +//----------------------------------------------------------------------------- +int PanelListPanel::ComputeVPixelsNeeded() +{ + int iCurrentItem = 0; + int iLargestH = 0; + + int pixels = 0; + for ( int i = 0; i < m_SortedItems.Count(); i++ ) + { + Panel *panel = m_DataItems[ m_SortedItems[i] ].panel; + if ( !panel ) + continue; + + if ( panel->IsLayoutInvalid() ) + { + panel->InvalidateLayout( true ); + } + + int iCurrentColumn = iCurrentItem % m_iNumColumns; + + int w, h; + panel->GetSize( w, h ); + + if ( iLargestH < h ) + iLargestH = h; + + if ( iCurrentColumn == 0 ) + pixels += m_iPanelBuffer; // add in buffer. between rows. + + if ( iCurrentColumn >= m_iNumColumns - 1 ) + { + pixels += iLargestH; + iLargestH = 0; + } + + iCurrentItem++; + } + + // Add in remaining largest height + pixels += iLargestH; + + pixels += m_iPanelBuffer; // add in buffer below last item + + return pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the panel to use to render a cell +//----------------------------------------------------------------------------- +Panel *PanelListPanel::GetCellRenderer( int row ) +{ + if ( !m_SortedItems.IsValidIndex(row) ) + return NULL; + + Panel *panel = m_DataItems[ m_SortedItems[row] ].panel; + return panel; +} + +//----------------------------------------------------------------------------- +// Purpose: adds an item to the view +// data->GetName() is used to uniquely identify an item +// data sub items are matched against column header name to be used in the table +//----------------------------------------------------------------------------- +int PanelListPanel::AddItem( Panel *labelPanel, Panel *panel ) +{ + Assert(panel); + + if ( labelPanel ) + { + labelPanel->SetParent( m_pPanelEmbedded ); + } + + panel->SetParent( m_pPanelEmbedded ); + + int itemID = m_DataItems.AddToTail(); + DATAITEM &newitem = m_DataItems[itemID]; + newitem.labelPanel = labelPanel; + newitem.panel = panel; + m_SortedItems.AddToTail(itemID); + + InvalidateLayout(); + return itemID; +} + +//----------------------------------------------------------------------------- +// Purpose: iteration accessor +//----------------------------------------------------------------------------- +int PanelListPanel::GetItemCount() const +{ + return m_DataItems.Count(); +} + +int PanelListPanel::GetItemIDFromRow( int nRow ) const +{ + if ( nRow < 0 || nRow >= GetItemCount() ) + return m_DataItems.InvalidIndex(); + return m_SortedItems[ nRow ]; +} + + +//----------------------------------------------------------------------------- +// Iteration. Use these until they return InvalidItemID to iterate all the items. +//----------------------------------------------------------------------------- +int PanelListPanel::FirstItem() const +{ + return m_DataItems.Head(); +} + +int PanelListPanel::NextItem( int nItemID ) const +{ + return m_DataItems.Next( nItemID ); +} + +int PanelListPanel::InvalidItemID() const +{ + return m_DataItems.InvalidIndex( ); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns label panel for this itemID +//----------------------------------------------------------------------------- +Panel *PanelListPanel::GetItemLabel(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return NULL; + + return m_DataItems[itemID].labelPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: returns label panel for this itemID +//----------------------------------------------------------------------------- +Panel *PanelListPanel::GetItemPanel(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return NULL; + + return m_DataItems[itemID].panel; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PanelListPanel::RemoveItem(int itemID) +{ + if ( !m_DataItems.IsValidIndex(itemID) ) + return; + + DATAITEM &item = m_DataItems[itemID]; + if ( item.panel ) + { + item.panel->MarkForDeletion(); + } + if ( item.labelPanel ) + { + item.labelPanel->MarkForDeletion(); + } + + m_DataItems.Remove(itemID); + m_SortedItems.FindAndRemove(itemID); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: clears and deletes all the memory used by the data items +//----------------------------------------------------------------------------- +void PanelListPanel::DeleteAllItems() +{ + FOR_EACH_LL( m_DataItems, i ) + { + if ( m_DataItems[i].panel ) + { + m_DataItems[i].panel->MarkForDeletion(); + m_DataItems[i].panel = NULL; + } + } + + m_DataItems.RemoveAll(); + m_SortedItems.RemoveAll(); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: clears and deletes all the memory used by the data items +//----------------------------------------------------------------------------- +void PanelListPanel::RemoveAll() +{ + m_DataItems.RemoveAll(); + m_SortedItems.RemoveAll(); + + // move the scrollbar to the top of the list + m_vbar->SetValue(0); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PanelListPanel::OnSizeChanged(int wide, int tall) +{ + BaseClass::OnSizeChanged(wide, tall); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: relayouts out the panel after any internal changes +//----------------------------------------------------------------------------- +void PanelListPanel::PerformLayout() +{ + int wide, tall; + GetSize( wide, tall ); + + int vpixels = ComputeVPixelsNeeded(); + + m_vbar->SetRange( 0, vpixels ); + m_vbar->SetRangeWindow( tall ); + m_vbar->SetButtonPressedScrollValue( tall / 4 ); // standard height of labels/buttons etc. + + m_vbar->SetPos( wide - m_vbar->GetWide() - 2, 0 ); + m_vbar->SetSize( m_vbar->GetWide(), tall - 2 ); + + int top = m_vbar->GetValue(); + + m_pPanelEmbedded->SetPos( 0, -top ); + m_pPanelEmbedded->SetSize( wide - m_vbar->GetWide(), vpixels ); // scrollbar will sit on top (zpos set explicitly) + + bool bScrollbarVisible = true; + // If we're supposed to automatically hide the scrollbar when unnecessary, check it now + if ( m_bAutoHideScrollbar ) + { + bScrollbarVisible = (m_pPanelEmbedded->GetTall() > tall); + } + m_vbar->SetVisible( bScrollbarVisible ); + + // Now lay out the controls on the embedded panel + int y = 0; + int h = 0; + int totalh = 0; + + int xpos = m_iFirstColumnWidth + m_iPanelBuffer; + int iColumnWidth = ( wide - xpos - m_vbar->GetWide() - 12 ) / m_iNumColumns; + + for ( int i = 0; i < m_SortedItems.Count(); i++ ) + { + int iCurrentColumn = i % m_iNumColumns; + + // add in a little buffer between panels + if ( iCurrentColumn == 0 ) + y += m_iPanelBuffer; + + DATAITEM &item = m_DataItems[ m_SortedItems[i] ]; + + if ( h < item.panel->GetTall() ) + h = item.panel->GetTall(); + + if ( item.labelPanel ) + { + item.labelPanel->SetBounds( 0, y, m_iFirstColumnWidth, item.panel->GetTall() ); + } + + item.panel->SetBounds( xpos + iCurrentColumn * iColumnWidth, y, iColumnWidth, item.panel->GetTall() ); + + if ( iCurrentColumn >= m_iNumColumns - 1 ) + { + y += h; + totalh += h; + + h = 0; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: scheme settings +//----------------------------------------------------------------------------- +void PanelListPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); + SetBgColor(GetSchemeColor("ListPanel.BgColor", GetBgColor(), pScheme)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PanelListPanel::OnSliderMoved( int position ) +{ + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PanelListPanel::MoveScrollBarToTop() +{ + m_vbar->SetValue(0); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PanelListPanel::SetFirstColumnWidth( int width ) +{ + m_iFirstColumnWidth = width; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int PanelListPanel::GetFirstColumnWidth() +{ + return m_iFirstColumnWidth; +} + +void PanelListPanel::SetNumColumns( int iNumColumns ) +{ + m_iNumColumns = iNumColumns; +} + +int PanelListPanel::GetNumColumns( void ) +{ + return m_iNumColumns; +} + +//----------------------------------------------------------------------------- +// Purpose: moves the scrollbar with the mousewheel +//----------------------------------------------------------------------------- +void PanelListPanel::OnMouseWheeled(int delta) +{ + int val = m_vbar->GetValue(); + val -= (delta * DEFAULT_HEIGHT); + m_vbar->SetValue(val); +} + +//----------------------------------------------------------------------------- +// Purpose: selection handler +//----------------------------------------------------------------------------- +void PanelListPanel::SetSelectedPanel( Panel *panel ) +{ + if ( panel != m_hSelectedItem ) + { + // notify the panels of the selection change + if ( m_hSelectedItem ) + { + PostMessage( m_hSelectedItem.Get(), new KeyValues("PanelSelected", "state", 0) ); + } + if ( panel ) + { + PostMessage( panel, new KeyValues("PanelSelected", "state", 1) ); + } + m_hSelectedItem = panel; + } +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +Panel *PanelListPanel::GetSelectedPanel() +{ + return m_hSelectedItem; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PanelListPanel::ScrollToItem( int itemNumber ) +{ + if (!m_vbar->IsVisible()) + { + return; + } + + DATAITEM& item = m_DataItems[ m_SortedItems[ itemNumber ] ]; + if ( !item.panel ) + return; + + int x, y; + item.panel->GetPos( x, y ); + int lx, ly; + lx = x; + ly = y; + m_pPanelEmbedded->LocalToScreen( lx, ly ); + ScreenToLocal( lx, ly ); + + int h = item.panel->GetTall(); + + if ( ly >= 0 && ly + h < GetTall() ) + return; + + m_vbar->SetValue( y ); + InvalidateLayout(); +} + + diff --git a/vgui2/vgui_controls/PerforceFileExplorer.cpp b/vgui2/vgui_controls/PerforceFileExplorer.cpp new file mode 100644 index 0000000..dc0f705 --- /dev/null +++ b/vgui2/vgui_controls/PerforceFileExplorer.cpp @@ -0,0 +1,278 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Contains a list of files, determines their perforce status +// +// $NoKeywords: $ +//===========================================================================// + +#include <vgui_controls/PerforceFileExplorer.h> +#include <vgui_controls/PerforceFileList.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/Tooltip.h> +#include "tier1/KeyValues.h" +#include "vgui/ISystem.h" +#include "filesystem.h" +#include <ctype.h> +#include "p4lib/ip4.h" +#include "tier2/tier2.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +using namespace vgui; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +PerforceFileExplorer::PerforceFileExplorer( Panel *pParent, const char *pPanelName ) : + BaseClass( pParent, pPanelName ) +{ + m_pFileList = new PerforceFileList( this, "PerforceFileList" ); + + // Get the list of available drives and put them in a menu here. + // Start with the directory we are in. + m_pFullPathCombo = new ComboBox( this, "FullPathCombo", 8, false ); + m_pFullPathCombo->GetTooltip()->SetTooltipFormatToSingleLine(); + + char pFullPath[MAX_PATH]; + g_pFullFileSystem->GetCurrentDirectory( pFullPath, sizeof(pFullPath) ); + SetCurrentDirectory( pFullPath ); + + m_pFullPathCombo->AddActionSignalTarget( this ); + + m_pFolderUpButton = new Button(this, "FolderUpButton", "", this); + m_pFolderUpButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_Up" ); + m_pFolderUpButton->SetCommand( new KeyValues( "FolderUp" ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +PerforceFileExplorer::~PerforceFileExplorer() +{ +} + + +//----------------------------------------------------------------------------- +// Inherited from Frame +//----------------------------------------------------------------------------- +void PerforceFileExplorer::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + m_pFolderUpButton->AddImage( scheme()->GetImage( "resource/icon_folderup", false), -3 ); +} + + +//----------------------------------------------------------------------------- +// Inherited from Frame +//----------------------------------------------------------------------------- +void PerforceFileExplorer::PerformLayout() +{ + BaseClass::PerformLayout(); + + int x, y, w, h; + GetClientArea( x, y, w, h ); + + m_pFullPathCombo->SetBounds( x, y + 6, w - 30, 24 ); + m_pFolderUpButton->SetBounds( x + w - 24, y + 6, 24, 24 ); + + m_pFileList->SetBounds( x, y + 36, w, h - 36 ); +} + + +//----------------------------------------------------------------------------- +// Sets the current directory +//----------------------------------------------------------------------------- +void PerforceFileExplorer::SetCurrentDirectory( const char *pFullPath ) +{ + if ( !pFullPath ) + return; + + while ( isspace( *pFullPath ) ) + { + ++pFullPath; + } + + if ( !pFullPath[0] ) + return; + + m_CurrentDirectory = pFullPath; + m_CurrentDirectory.StripTrailingSlash(); + m_CurrentDirectory.FixSlashes(); + + PopulateFileList(); + PopulateDriveList(); + + char pCurrentDirectory[ MAX_PATH ]; + m_pFullPathCombo->GetText( pCurrentDirectory, sizeof(pCurrentDirectory) ); + if ( Q_stricmp( m_CurrentDirectory.Get(), pCurrentDirectory ) ) + { + char pNewDirectory[ MAX_PATH ]; + Q_snprintf( pNewDirectory, sizeof(pNewDirectory), "%s\\", m_CurrentDirectory.Get() ); + m_pFullPathCombo->SetText( pNewDirectory ); + m_pFullPathCombo->GetTooltip()->SetText( pNewDirectory ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PerforceFileExplorer::PopulateDriveList() +{ + char pFullPath[MAX_PATH * 4]; + char pSubDirPath[MAX_PATH * 4]; + Q_strncpy( pFullPath, m_CurrentDirectory.Get(), sizeof( pFullPath ) ); + Q_strncpy( pSubDirPath, m_CurrentDirectory.Get(), sizeof( pSubDirPath ) ); + + m_pFullPathCombo->DeleteAllItems(); + + // populate the drive list + char buf[512]; + int len = system()->GetAvailableDrives(buf, 512); + char *pBuf = buf; + for (int i=0; i < len / 4; i++) + { + m_pFullPathCombo->AddItem(pBuf, NULL); + + // is this our drive - add all subdirectories + if ( !_strnicmp( pBuf, pFullPath, 2 ) ) + { + int indent = 0; + char *pData = pFullPath; + while (*pData) + { + if (*pData == '\\') + { + if (indent > 0) + { + memset(pSubDirPath, ' ', indent); + memcpy(pSubDirPath+indent, pFullPath, pData-pFullPath+1); + pSubDirPath[indent+pData-pFullPath+1] = 0; + + m_pFullPathCombo->AddItem( pSubDirPath, NULL ); + } + indent += 2; + } + pData++; + } + } + pBuf += 4; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Fill the filelist with the names of all the files in the current directory +//----------------------------------------------------------------------------- +void PerforceFileExplorer::PopulateFileList() +{ + // clear the current list + m_pFileList->RemoveAllFiles(); + + // Create filter string + char pFullFoundPath[MAX_PATH]; + char pFilter[MAX_PATH+3]; + Q_snprintf( pFilter, sizeof(pFilter), "%s\\*.*", m_CurrentDirectory.Get() ); + + // Find all files on disk + FileFindHandle_t h; + const char *pFileName = g_pFullFileSystem->FindFirstEx( pFilter, NULL, &h ); + for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( h ) ) + { + if ( !Q_stricmp( pFileName, ".." ) || !Q_stricmp( pFileName, "." ) ) + continue; + + if ( !Q_IsAbsolutePath( pFileName ) ) + { + Q_snprintf( pFullFoundPath, sizeof(pFullFoundPath), "%s\\%s", m_CurrentDirectory.Get(), pFileName ); + pFileName = pFullFoundPath; + } + + int nItemID = m_pFileList->AddFile( pFileName, true ); + m_pFileList->RefreshPerforceState( nItemID, true, NULL ); + } + g_pFullFileSystem->FindClose( h ); + + // Now find all files in perforce + CUtlVector<P4File_t> &fileList = p4->GetFileList( m_CurrentDirectory ); + int nCount = fileList.Count(); + for ( int i = 0; i < nCount; ++i ) + { + pFileName = p4->String( fileList[i].m_sLocalFile ); + if ( !pFileName[0] ) + continue; + + int nItemID = m_pFileList->FindFile( pFileName ); + bool bFileExists = true; + if ( nItemID == m_pFileList->InvalidItemID() ) + { + // If it didn't find it, the file must not exist + // since it already would have added it above + bFileExists = false; + nItemID = m_pFileList->AddFile( pFileName, false, fileList[i].m_bDir ); + } + m_pFileList->RefreshPerforceState( nItemID, bFileExists, &fileList[i] ); + } + + m_pFileList->SortList(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handle an item in the Drive combo box being selected +//----------------------------------------------------------------------------- +void PerforceFileExplorer::OnTextChanged( KeyValues *kv ) +{ + Panel *pPanel = (Panel *)kv->GetPtr( "panel", NULL ); + + // first check which control had its text changed! + if ( pPanel == m_pFullPathCombo ) + { + char pCurrentDirectory[ MAX_PATH ]; + m_pFullPathCombo->GetText( pCurrentDirectory, sizeof(pCurrentDirectory) ); + SetCurrentDirectory( pCurrentDirectory ); + return; + } +} + + +//----------------------------------------------------------------------------- +// Called when the file list was doubleclicked +//----------------------------------------------------------------------------- +void PerforceFileExplorer::OnItemDoubleClicked() +{ + if ( m_pFileList->GetSelectedItemsCount() != 1 ) + return; + + int nItemID = m_pFileList->GetSelectedItem( 0 ); + if ( m_pFileList->IsDirectoryItem( nItemID ) ) + { + const char *pDirectoryName = m_pFileList->GetFile( nItemID ); + SetCurrentDirectory( pDirectoryName ); + } +} + + +//----------------------------------------------------------------------------- +// Called when the folder up button was hit +//----------------------------------------------------------------------------- +void PerforceFileExplorer::OnFolderUp() +{ + char pUpDirectory[MAX_PATH]; + Q_strncpy( pUpDirectory, m_CurrentDirectory.Get(), sizeof(pUpDirectory) ); + Q_StripLastDir( pUpDirectory, sizeof(pUpDirectory) ); + Q_StripTrailingSlash( pUpDirectory ); + + // This occurs at the root directory + if ( !Q_stricmp( pUpDirectory, "." ) ) + return; + SetCurrentDirectory( pUpDirectory ); +} + + + diff --git a/vgui2/vgui_controls/PerforceFileList.cpp b/vgui2/vgui_controls/PerforceFileList.cpp new file mode 100644 index 0000000..de7fa32 --- /dev/null +++ b/vgui2/vgui_controls/PerforceFileList.cpp @@ -0,0 +1,570 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Contains a list of files, determines their perforce status +// +// $NoKeywords: $ +//===========================================================================// + +#include <vgui_controls/PerforceFileList.h> +#include <vgui_controls/ListPanel.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/ImageList.h> +#include "tier1/KeyValues.h" +#include <vgui/ISurface.h> +#include "filesystem.h" +#include "p4lib/ip4.h" +#include "tier2/tier2.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +using namespace vgui; + + +static int ListFileNameSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + NOTE_UNUSED( pPanel ); + + bool dir1 = item1.kv->GetInt("directory") == 1; + bool dir2 = item2.kv->GetInt("directory") == 1; + + // if they're both not directories of files, return if dir1 is a directory (before files) + if ( dir1 != dir2 ) + { + return dir1 ? -1 : 1; + } + + const char *string1 = item1.kv->GetString("text"); + const char *string2 = item2.kv->GetString("text"); + + // YWB: Mimic windows behavior where filenames starting with numbers are sorted based on numeric part + int num1 = Q_atoi( string1 ); + int num2 = Q_atoi( string2 ); + + if ( num1 != 0 && + num2 != 0 ) + { + if ( num1 < num2 ) + return -1; + else if ( num1 > num2 ) + return 1; + } + + // Push numbers before everything else + if ( num1 != 0 ) + { + return -1; + } + + // Push numbers before everything else + if ( num2 != 0 ) + { + return 1; + } + + return Q_stricmp( string1, string2 ); +} + +static int ListBaseStringSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName ) +{ + bool dir1 = item1.kv->GetInt("directory") == 1; + bool dir2 = item2.kv->GetInt("directory") == 1; + + // if they're both not directories of files, return if dir1 is a directory (before files) + if (dir1 != dir2) + { + return -1; + } + + const char *string1 = item1.kv->GetString(fieldName); + const char *string2 = item2.kv->GetString(fieldName); + int cval = Q_stricmp(string1, string2); + if ( cval == 0 ) + { + // Use filename to break ties + return ListFileNameSortFunc( pPanel, item1, item2 ); + } + + return cval; +} + +static int ListBaseIntegerSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName ) +{ + bool dir1 = item1.kv->GetInt("directory") == 1; + bool dir2 = item2.kv->GetInt("directory") == 1; + + // if they're both not directories of files, return if dir1 is a directory (before files) + if (dir1 != dir2) + { + return -1; + } + + int i1 = item1.kv->GetInt(fieldName); + int i2 = item2.kv->GetInt(fieldName); + if ( i1 == i2 ) + { + // Use filename to break ties + return ListFileNameSortFunc( pPanel, item1, item2 ); + } + + return ( i1 < i2 ) ? -1 : 1; +} + +static int ListFileSizeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + return ListBaseIntegerSortFunc( pPanel, item1, item2, "filesizeint" ); +} + +static int ListFileAttributesSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + return ListBaseStringSortFunc( pPanel, item1, item2, "attributes" ); +} + +static int ListFileTypeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) +{ + return ListBaseStringSortFunc( pPanel, item1, item2, "type" ); +} + + +//----------------------------------------------------------------------------- +// Dictionary of start dir contexts +//----------------------------------------------------------------------------- +struct ColumnInfo_t +{ + char const *columnName; + char const *columnText; + int startingWidth; + int minWidth; + int maxWidth; + int flags; + SortFunc *pfnSort; + Label::Alignment alignment; +}; + +static ColumnInfo_t g_ColInfo[] = +{ + { "text", "#PerforceFileList_Col_Name", 175, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileNameSortFunc , Label::a_west }, + { "type", "#PerforceFileList_Col_Type", 150, 20, 10000, 0, &ListFileTypeSortFunc , Label::a_west }, + { "in_perforce", "#PerforceFileList_Col_InPerforce", 50, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileAttributesSortFunc , Label::a_west }, + { "synched", "#PerforceFileList_Col_Synched", 50, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileAttributesSortFunc , Label::a_west }, + { "checked_out", "#PerforceFileList_Col_Checked_Out", 50, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileAttributesSortFunc , Label::a_west }, + { "attributes", "#PerforceFileList_Col_Attributes", 50, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileAttributesSortFunc , Label::a_west }, +}; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +PerforceFileList::PerforceFileList( Panel *pParent, const char *pPanelName ) : + BaseClass( pParent, pPanelName ) +{ + SetMultiselectEnabled( false ); + m_bShowDeletedFiles = false; + + // list panel + for ( int i = 0; i < ARRAYSIZE( g_ColInfo ); ++i ) + { + const ColumnInfo_t& info = g_ColInfo[ i ]; + + AddColumnHeader( i, info.columnName, info.columnText, info.startingWidth, info.minWidth, info.maxWidth, info.flags ); + SetSortFunc( i, info.pfnSort ); + SetColumnTextAlignment( i, info.alignment ); + } + + SetSortColumn( 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +PerforceFileList::~PerforceFileList() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Apply scheme settings +//----------------------------------------------------------------------------- +void PerforceFileList::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + ImageList *pImageList = new ImageList( false ); + pImageList->AddImage( scheme()->GetImage( "resource/icon_file", false ) ); + pImageList->AddImage( scheme()->GetImage( "resource/icon_folder", false ) ); + pImageList->AddImage( scheme()->GetImage( "resource/icon_folder_selected", false ) ); + + SetImageList( pImageList, true ); +} + + +//----------------------------------------------------------------------------- +// Toggle showing deleted files or not +//----------------------------------------------------------------------------- +void PerforceFileList::ShowDeletedFiles( bool bShowDeletedFiles ) +{ + if ( m_bShowDeletedFiles != bShowDeletedFiles ) + { + m_bShowDeletedFiles = bShowDeletedFiles; + + for ( int i = FirstItem(); i != InvalidItemID(); i = NextItem( i ) ) + { + KeyValues *pKeyValues = GetItem( i ); + if ( !pKeyValues->GetInt( "deleted", 0 ) ) + continue; + + SetItemVisible( i, m_bShowDeletedFiles ); + } + } +} + + +//----------------------------------------------------------------------------- +// Add a directory to the directory list, returns client spec +//----------------------------------------------------------------------------- +void PerforceFileList::AddItemToDirectoryList( const char *pFullPath, int nItemID, bool bIsDirectory ) +{ + char pDirectoryBuf[MAX_PATH]; + Q_ExtractFilePath( pFullPath, pDirectoryBuf, sizeof(pDirectoryBuf) ); + Q_StripTrailingSlash( pDirectoryBuf ); + pFullPath = pDirectoryBuf; + + DirectoryInfo_t *pInfo; + UtlSymId_t i = m_Directories.Find( pFullPath ); + if ( i != m_Directories.InvalidIndex() ) + { + pInfo = &m_Directories[i]; + } + else + { + char pClientSpec[MAX_PATH]; + if ( !p4->GetClientSpecForDirectory( pFullPath, pClientSpec, sizeof(pClientSpec) ) ) + { + pClientSpec[0] = 0; + } + + pInfo = &m_Directories[ pFullPath ]; + pInfo->m_ClientSpec = pClientSpec; + } + + pInfo->m_ItemIDs.AddToTail( nItemID ); +} + + +//----------------------------------------------------------------------------- +// Add a file to the file list. +//----------------------------------------------------------------------------- +int PerforceFileList::AddFileToFileList( const char *pFullPath, bool bExistsOnDisk ) +{ + bool bIsFileWriteable = bExistsOnDisk ? g_pFullFileSystem->IsFileWritable( pFullPath, NULL ) : true; + + // add the file to the list + KeyValues *kv = new KeyValues("item"); + + const char *pRelativePath = Q_UnqualifiedFileName( pFullPath ); + kv->SetString( "text", pRelativePath ); + kv->SetString( "fullpath", pFullPath ); + kv->SetInt( "image", 1 ); + + IImage *pImage = surface()->GetIconImageForFullPath( pFullPath ); + if ( pImage ) + { + kv->SetPtr( "iconImage", (void *)pImage ); + } + + kv->SetInt( "imageSelected", 1 ); + kv->SetInt( "directory", 0 ); + + // These are computed by Refresh + kv->SetInt( "in_perforce", 0 ); + kv->SetInt( "synched", 0 ); + kv->SetInt( "checked_out", 0 ); + kv->SetInt( "deleted", 0 ); + + wchar_t pFileType[ 80 ]; + g_pFullFileSystem->GetFileTypeForFullPath( pFullPath, pFileType, sizeof( pFileType ) ); + + kv->SetWString( "type", pFileType ); + kv->SetString( "attributes", bIsFileWriteable ? "" : "R" ); + + int nItemID = AddItem( kv, 0, false, false ); + kv->deleteThis(); + + AddItemToDirectoryList( pFullPath, nItemID, false ); + return nItemID; +} + + +//----------------------------------------------------------------------------- +// Add a directory to the file list. +//----------------------------------------------------------------------------- +int PerforceFileList::AddDirectoryToFileList( const char *pFullPath, bool bExistsOnDisk ) +{ + KeyValues *kv = new KeyValues("item"); + + const char *pRelativePath = Q_UnqualifiedFileName( pFullPath ); + kv->SetString( "text", pRelativePath ); + kv->SetString( "fullpath", pFullPath ); + kv->SetPtr( "iconImage", (void *)NULL ); + kv->SetInt( "image", 2 ); + kv->SetInt( "imageSelected", 3 ); + kv->SetInt( "directory", 1 ); + + // These are computed by Refresh + kv->SetInt( "in_perforce", 0 ); + kv->SetInt( "synched", 0 ); + kv->SetInt( "checked_out", 0 ); + kv->SetInt( "deleted", 0 ); + + kv->SetString( "type", "#PerforceFileList_FileType_Folder" ); + kv->SetString( "attributes", "D" ); + + int nItemID = AddItem(kv, 0, false, false); + kv->deleteThis(); + + AddItemToDirectoryList( pFullPath, nItemID, true ); + return nItemID; +} + + +//----------------------------------------------------------------------------- +// Add a file or directory to the file list. +//----------------------------------------------------------------------------- +int PerforceFileList::AddFile( const char *pFullPath, int nFileExists, int nIsDirectory ) +{ + if ( !pFullPath ) + return InvalidItemID(); + + if ( !Q_IsAbsolutePath( pFullPath ) ) + { + Warning( "Absolute paths required for PerforceFileList::AddFile!\n" + "\"%s\" is not an abolute path", pFullPath ); + return InvalidItemID(); + } + + char pFixedPath[MAX_PATH]; + Q_strncpy( pFixedPath, pFullPath, sizeof(pFixedPath) ); + Q_FixSlashes( pFixedPath ); + + // Check to see if the file is on disk + int nItemID = -1; + bool bFileExists, bIsDirectory; + if ( nFileExists < 0 ) + { + bFileExists = g_pFullFileSystem->FileExists( pFixedPath ) ; + } + else + { + bFileExists = ( nFileExists != 0 ); + } + + if ( nIsDirectory < 0 ) + { + if ( bFileExists ) + { + bIsDirectory = g_pFullFileSystem->IsDirectory( pFixedPath ); + } + else + { + int nLen = Q_strlen( pFixedPath ); + bIsDirectory = ( pFixedPath[nLen-1] == CORRECT_PATH_SEPARATOR ); + } + } + else + { + bIsDirectory = ( nIsDirectory != 0 ); + } + + if ( bIsDirectory ) + { + nItemID = AddDirectoryToFileList( pFixedPath, bFileExists ); + } + else + { + nItemID = AddFileToFileList( pFixedPath, bFileExists ); + } + + return nItemID; +} + + +//----------------------------------------------------------------------------- +// Remove all files from the list +//----------------------------------------------------------------------------- +void PerforceFileList::RemoveAllFiles() +{ + RemoveAll(); + m_Directories.Clear(); +} + + +//----------------------------------------------------------------------------- +// Finds a file in the p4 list +//----------------------------------------------------------------------------- +static P4File_t *FindFileInPerforceList( const char *pFileName, CUtlVector<P4File_t> &fileList, bool *pFound ) +{ + int nCount = fileList.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( pFound[i] ) + continue; + + const char *pPerforceFileName = p4->String( fileList[i].m_sLocalFile ); + if ( !Q_stricmp( pPerforceFileName, pFileName ) ) + { + pFound[i] = true; + return &fileList[i]; + } + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Refresh perforce information +//----------------------------------------------------------------------------- +void PerforceFileList::RefreshPerforceState( int nItemID, bool bFileExists, P4File_t *pFileInfo ) +{ + KeyValues *kv = GetItem( nItemID ); + + bool bIsSynched = false; + bool bIsFileInPerforce = (pFileInfo != NULL); + if ( bIsFileInPerforce ) + { + if ( pFileInfo->m_bDeleted != bFileExists ) + { + bIsSynched = ( pFileInfo->m_bDeleted || ( pFileInfo->m_iHeadRevision == pFileInfo->m_iHaveRevision ) ); + } + } + else + { + bIsSynched = !bFileExists; + } + + bool bIsDeleted = bIsFileInPerforce && !bFileExists && pFileInfo->m_bDeleted; + + kv->SetInt( "in_perforce", bIsFileInPerforce ); + kv->SetInt( "synched", bIsSynched ); + kv->SetInt( "checked_out", bIsFileInPerforce && ( pFileInfo->m_eOpenState != P4FILE_UNOPENED ) ); + kv->SetInt( "deleted", bIsDeleted ); + + if ( bIsDeleted ) + { + SetItemVisible( nItemID, m_bShowDeletedFiles ); + } +} + + +//----------------------------------------------------------------------------- +// Refresh perforce information +//----------------------------------------------------------------------------- +void PerforceFileList::Refresh() +{ + /* + // Slow method.. does too many perforce operations + for ( int i = FirstItem(); i != InvalidItemID(); i = NextItem( i ) ) + { + const char *pFile = GetFile( i ); + + P4File_t fileInfo; + bool bIsFileInPerforce = p4->GetFileInfo( pFile, &fileInfo ); + bool bFileExists = g_pFullFileSystem->FileExists( pFile ); + RefreshPerforceState( i, bFileExists, bIsFileInPerforce ? &fileInfo : NULL ); + } + */ + + // NOTE: Reducing the # of perforce calls is important for performance + int nCount = m_Directories.GetNumStrings(); + for ( int i = 0; i < nCount; ++i ) + { + const char *pDirectory = m_Directories.String(i); + DirectoryInfo_t *pInfo = &m_Directories[i]; + + // Retrives files, uses faster method to avoid finding clientspec + CUtlVector<P4File_t> &fileList = p4->GetFileListUsingClientSpec( pDirectory, pInfo->m_ClientSpec ); + int nFileCount = fileList.Count(); + bool *pFound = (bool*)_alloca( nFileCount * sizeof(bool) ); + memset( pFound, 0, nFileCount * sizeof(bool) ); + + int nItemCount = pInfo->m_ItemIDs.Count(); + for ( int j = 0; j < nItemCount; ++j ) + { + int nItemID = pInfo->m_ItemIDs[j]; + const char *pFileName = GetFile( nItemID ); + bool bFileExists = g_pFullFileSystem->FileExists( pFileName ); + P4File_t *pFileInfo = FindFileInPerforceList( pFileName, fileList, pFound ); + RefreshPerforceState( nItemID, bFileExists, pFileInfo ); + } + } +} + + +//----------------------------------------------------------------------------- +// Is a particular list item a directory? +//----------------------------------------------------------------------------- +bool PerforceFileList::IsDirectoryItem( int nItemID ) +{ + KeyValues *kv = GetItem( nItemID ); + return kv->GetInt( "directory", 0 ) != 0; +} + + +//----------------------------------------------------------------------------- +// Returns the file associated with a particular item ID +//----------------------------------------------------------------------------- +const char *PerforceFileList::GetFile( int nItemID ) +{ + KeyValues *kv = GetItem( nItemID ); + Assert( kv ); + return kv->GetString( "fullpath", "<no file>" ); +} + + +//----------------------------------------------------------------------------- +// Find the item ID associated with a particular file +//----------------------------------------------------------------------------- +int PerforceFileList::FindFile( const char *pFullPath ) +{ + for ( int i = FirstItem(); i != InvalidItemID(); i = NextItem( i ) ) + { + const char *pFile = GetFile( i ); + if ( !Q_stricmp( pFile, pFullPath ) ) + return i; + } + return InvalidItemID(); +} + + +//----------------------------------------------------------------------------- +// Is a file already in the list? +//----------------------------------------------------------------------------- +bool PerforceFileList::IsFileInList( const char *pFullPath ) +{ + return ( FindFile( pFullPath ) != InvalidItemID() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Double-click: expand folders +//----------------------------------------------------------------------------- +void PerforceFileList::OnMouseDoublePressed( MouseCode code ) +{ + if ( code == MOUSE_LEFT ) + { + // select the item + OnMousePressed(code); + + // post a special message + if ( GetSelectedItemsCount() > 0 ) + { + PostActionSignal( new KeyValues("ItemDoubleClicked" ) ); + } + return; + } + + BaseClass::OnMouseDoublePressed( code ); +} + + diff --git a/vgui2/vgui_controls/ProgressBar.cpp b/vgui2/vgui_controls/ProgressBar.cpp new file mode 100644 index 0000000..23e5bee --- /dev/null +++ b/vgui2/vgui_controls/ProgressBar.cpp @@ -0,0 +1,521 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> +#include <math.h> +#include <stdio.h> + +#include <vgui_controls/ProgressBar.h> +#include <vgui_controls/Controls.h> + +#include <vgui/ILocalize.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( ProgressBar ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ProgressBar::ProgressBar(Panel *parent, const char *panelName) : Panel(parent, panelName) +{ + _progress = 0.0f; + m_pszDialogVar = NULL; + SetSegmentInfo( 4, 8 ); + SetBarInset( 4 ); + SetMargin( 0 ); + m_iProgressDirection = PROGRESS_EAST; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +ProgressBar::~ProgressBar() +{ + delete [] m_pszDialogVar; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void ProgressBar::SetSegmentInfo( int gap, int width ) +{ + _segmentGap = gap; + _segmentWide = width; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the number of segment blocks drawn +//----------------------------------------------------------------------------- +int ProgressBar::GetDrawnSegmentCount() +{ + int wide, tall; + GetSize(wide, tall); + int segmentTotal = wide / (_segmentGap + _segmentWide); + return (int)(segmentTotal * _progress); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBar::PaintBackground() +{ + int wide, tall; + GetSize(wide, tall); + + surface()->DrawSetColor(GetBgColor()); + surface()->DrawFilledRect(0, 0, wide, tall); +} + +void ProgressBar::PaintSegment( int &x, int &y, int tall, int wide ) +{ + switch( m_iProgressDirection ) + { + case PROGRESS_EAST: + x += _segmentGap; + surface()->DrawFilledRect(x, y, x + _segmentWide, y + tall - (y * 2)); + x += _segmentWide; + break; + + case PROGRESS_WEST: + x -= _segmentGap + _segmentWide; + surface()->DrawFilledRect(x, y, x + _segmentWide, y + tall - (y * 2)); + break; + + case PROGRESS_NORTH: + y -= _segmentGap + _segmentWide; + surface()->DrawFilledRect(x, y, x + wide - (x * 2), y + _segmentWide ); + break; + + case PROGRESS_SOUTH: + y += _segmentGap; + surface()->DrawFilledRect(x, y, x + wide - (x * 2), y + _segmentWide ); + y += _segmentWide; + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBar::Paint() +{ + int wide, tall; + GetSize(wide, tall); + + // gaps + int segmentTotal = 0, segmentsDrawn = 0; + int x = 0, y = 0; + + switch( m_iProgressDirection ) + { + case PROGRESS_WEST: + wide -= 2 * m_iBarMargin; + x = wide - m_iBarMargin; + y = m_iBarInset; + segmentTotal = wide / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _progress); + break; + + case PROGRESS_EAST: + wide -= 2 * m_iBarMargin; + x = m_iBarMargin; + y = m_iBarInset; + segmentTotal = wide / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _progress); + break; + + case PROGRESS_NORTH: + tall -= 2 * m_iBarMargin; + x = m_iBarInset; + y = tall - m_iBarMargin; + segmentTotal = tall / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _progress); + break; + + case PROGRESS_SOUTH: + tall -= 2 * m_iBarMargin; + x = m_iBarInset; + y = m_iBarMargin; + segmentTotal = tall / (_segmentGap + _segmentWide); + segmentsDrawn = (int)(segmentTotal * _progress); + break; + } + + surface()->DrawSetColor(GetFgColor()); + for (int i = 0; i < segmentsDrawn; i++) + { + PaintSegment( x, y, tall, wide ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBar::SetProgress(float progress) +{ + if (progress != _progress) + { + // clamp the progress value within the range + if (progress < 0.0f) + { + progress = 0.0f; + } + else if (progress > 1.0f) + { + progress = 1.0f; + } + + _progress = progress; + Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +float ProgressBar::GetProgress() +{ + return _progress; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBar::ApplySchemeSettings(IScheme *pScheme) +{ + Panel::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("ProgressBar.FgColor", pScheme)); + SetBgColor(GetSchemeColor("ProgressBar.BgColor", pScheme)); + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); +} + +//----------------------------------------------------------------------------- +// Purpose: utility function for calculating a time remaining string +//----------------------------------------------------------------------------- +bool ProgressBar::ConstructTimeRemainingString(wchar_t *output, int outputBufferSizeInBytes, float startTime, float currentTime, float currentProgress, float lastProgressUpdateTime, bool addRemainingSuffix) +{ + Assert(lastProgressUpdateTime <= currentTime); + output[0] = 0; + + // calculate pre-extrapolation values + float timeElapsed = lastProgressUpdateTime - startTime; + float totalTime = timeElapsed / currentProgress; + + // calculate seconds + int secondsRemaining = (int)(totalTime - timeElapsed); + if (lastProgressUpdateTime < currentTime) + { + // old update, extrapolate + float progressRate = currentProgress / timeElapsed; + float extrapolatedProgress = progressRate * (currentTime - startTime); + float extrapolatedTotalTime = (currentTime - startTime) / extrapolatedProgress; + secondsRemaining = (int)(extrapolatedTotalTime - timeElapsed); + } + // if there's some time, make sure it's at least one second left + if ( secondsRemaining == 0 && ( ( totalTime - timeElapsed ) > 0 ) ) + { + secondsRemaining = 1; + } + + // calculate minutes + int minutesRemaining = 0; + while (secondsRemaining >= 60) + { + minutesRemaining++; + secondsRemaining -= 60; + } + + char minutesBuf[16]; + Q_snprintf(minutesBuf, sizeof( minutesBuf ), "%d", minutesRemaining); + char secondsBuf[16]; + Q_snprintf(secondsBuf, sizeof( secondsBuf ), "%d", secondsRemaining); + + if (minutesRemaining > 0) + { + wchar_t unicodeMinutes[16]; + g_pVGuiLocalize->ConvertANSIToUnicode(minutesBuf, unicodeMinutes, sizeof( unicodeMinutes )); + wchar_t unicodeSeconds[16]; + g_pVGuiLocalize->ConvertANSIToUnicode(secondsBuf, unicodeSeconds, sizeof( unicodeSeconds )); + + const char *unlocalizedString = "#vgui_TimeLeftMinutesSeconds"; + if (minutesRemaining == 1 && secondsRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftMinuteSecond"; + } + else if (minutesRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftMinuteSeconds"; + } + else if (secondsRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftMinutesSecond"; + } + + char unlocString[64]; + Q_strncpy(unlocString, unlocalizedString,sizeof( unlocString )); + if (addRemainingSuffix) + { + Q_strncat(unlocString, "Remaining", sizeof(unlocString ), COPY_ALL_CHARACTERS); + } + g_pVGuiLocalize->ConstructString(output, outputBufferSizeInBytes, g_pVGuiLocalize->Find(unlocString), 2, unicodeMinutes, unicodeSeconds); + + } + else if (secondsRemaining > 0) + { + wchar_t unicodeSeconds[16]; + g_pVGuiLocalize->ConvertANSIToUnicode(secondsBuf, unicodeSeconds, sizeof( unicodeSeconds )); + + const char *unlocalizedString = "#vgui_TimeLeftSeconds"; + if (secondsRemaining == 1) + { + unlocalizedString = "#vgui_TimeLeftSecond"; + } + char unlocString[64]; + Q_strncpy(unlocString, unlocalizedString,sizeof(unlocString)); + if (addRemainingSuffix) + { + Q_strncat(unlocString, "Remaining",sizeof(unlocString), COPY_ALL_CHARACTERS); + } + g_pVGuiLocalize->ConstructString(output, outputBufferSizeInBytes, g_pVGuiLocalize->Find(unlocString), 1, unicodeSeconds); + } + else + { + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void ProgressBar::SetBarInset( int pixels ) +{ + m_iBarInset = pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int ProgressBar::GetBarInset( void ) +{ + return m_iBarInset; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void ProgressBar::SetMargin( int pixels ) +{ + m_iBarMargin = pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int ProgressBar::GetMargin() +{ + return m_iBarMargin; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBar::ApplySettings(KeyValues *inResourceData) +{ + _progress = inResourceData->GetFloat("progress", 0.0f); + + const char *dialogVar = inResourceData->GetString("variable", ""); + if (dialogVar && *dialogVar) + { + m_pszDialogVar = new char[strlen(dialogVar) + 1]; + strcpy(m_pszDialogVar, dialogVar); + } + + BaseClass::ApplySettings(inResourceData); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBar::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + outResourceData->SetFloat("progress", _progress ); + + if (m_pszDialogVar) + { + outResourceData->SetString("variable", m_pszDialogVar); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a string description of the panel fields for use in the UI +//----------------------------------------------------------------------------- +const char *ProgressBar::GetDescription( void ) +{ + static char buf[1024]; + _snprintf(buf, sizeof(buf), "%s, string progress, string variable", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: updates progress bar bases on values +//----------------------------------------------------------------------------- +void ProgressBar::OnDialogVariablesChanged(KeyValues *dialogVariables) +{ + if (m_pszDialogVar) + { + int val = dialogVariables->GetInt(m_pszDialogVar, -1); + if (val >= 0.0f) + { + SetProgress(val / 100.0f); + } + } +} + + +DECLARE_BUILD_FACTORY( ContinuousProgressBar ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ContinuousProgressBar::ContinuousProgressBar(Panel *parent, const char *panelName) : ProgressBar(parent, panelName) +{ + _prevProgress = -1.f; + m_colorGain = Color( 100, 255, 100, 255 ); + m_colorLoss = Color( 200, 45, 45, 255 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ContinuousProgressBar::SetPrevProgress( float progress ) +{ + if ( progress == _prevProgress ) + return; + + _prevProgress = ( progress == -1.f ) ? progress : clamp( progress, 0.f, 1.f ); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ContinuousProgressBar::Paint() +{ + int x = 0, y = 0; + int wide, tall; + GetSize(wide, tall); + + surface()->DrawSetColor( GetFgColor() ); + + bool bUsePrev = _prevProgress >= 0.f; + bool bGain = _progress > _prevProgress; + + switch( m_iProgressDirection ) + { + case PROGRESS_EAST: + if ( bUsePrev ) + { + if ( bGain ) + { + surface()->DrawFilledRect( x, y, x + (int)( wide * _prevProgress ), y + tall ); + + // Delta + surface()->DrawSetColor( m_colorGain ); + surface()->DrawFilledRect( x + (int)( wide * _prevProgress ), y, x + (int)( wide * _progress ), y + tall ); + break; + } + else + { + // Delta + surface()->DrawSetColor( m_colorLoss ); + surface()->DrawFilledRect( x + (int)( wide * _progress ), y, x + (int)( wide * _prevProgress ), y + tall ); + } + } + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawFilledRect( x, y, x + (int)( wide * _progress ), y + tall ); + break; + + case PROGRESS_WEST: + if ( bUsePrev ) + { + if ( bGain ) + { + surface()->DrawFilledRect( x + (int)( wide * ( 1.0f - _prevProgress ) ), y, x + wide, y + tall ); + + // Delta + surface()->DrawSetColor( m_colorGain ); + surface()->DrawFilledRect( x + (int)( wide * ( 1.0f - _progress ) ), y, x + (int)( wide * ( 1.0f - _prevProgress ) ), y + tall ); + break; + } + else + { + // Delta + surface()->DrawSetColor( m_colorLoss ); + surface()->DrawFilledRect( x + (int)( wide * ( 1.0f - _prevProgress ) ), y, x + (int)( wide * ( 1.0f - _progress ) ), y + tall ); + } + } + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawFilledRect( x + (int)( wide * ( 1.0f - _progress ) ), y, x + wide, y + tall ); + break; + + case PROGRESS_NORTH: + if ( bUsePrev ) + { + if ( bGain ) + { + surface()->DrawFilledRect( x, y + (int)( tall * ( 1.0f - _prevProgress ) ), x + wide, y + tall ); + + // Delta + surface()->DrawSetColor( m_colorGain ); + surface()->DrawFilledRect( x, y + (int)( tall * ( 1.0f - _progress ) ), x + wide, y + (int)( tall * ( 1.0f - _prevProgress ) ) ); + break; + } + else + { + // Delta + surface()->DrawSetColor( m_colorLoss ); + surface()->DrawFilledRect( x, y + (int)( tall * ( 1.0f - _prevProgress ) ), x + wide, y + (int)( tall * ( 1.0f - _progress ) ) ); + } + } + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawFilledRect( x, y + (int)( tall * ( 1.0f - _progress ) ), x + wide, y + tall ); + break; + + case PROGRESS_SOUTH: + if ( bUsePrev ) + { + if ( bGain ) + { + surface()->DrawFilledRect( x, y, x + wide, y + (int)( tall * ( 1.0f - _progress ) ) ); + + // Delta + surface()->DrawSetColor( m_colorGain ); + surface()->DrawFilledRect( x, y + (int)( tall * ( 1.0f - _progress ) ), x + wide, y + (int)( tall * ( 1.0f - _prevProgress ) ) ); + break; + } + else + { + // Delta + surface()->DrawSetColor( m_colorLoss ); + surface()->DrawFilledRect( x, y + (int)( tall * ( 1.0f - _prevProgress ) ), x + wide, y + (int)( tall * ( 1.0f - _progress ) ) ); + } + } + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawFilledRect( x, y, x + wide, y + (int)( tall * _progress ) ); + break; + } +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/ProgressBox.cpp b/vgui2/vgui_controls/ProgressBox.cpp new file mode 100644 index 0000000..c67f173 --- /dev/null +++ b/vgui2/vgui_controls/ProgressBox.cpp @@ -0,0 +1,360 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/IInput.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/IVGui.h> +#include <KeyValues.h> + +#include <vgui_controls/Button.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/ProgressBar.h> +#include <vgui_controls/ProgressBox.h> + +#include <stdio.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ProgressBox::ProgressBox(const char *title, const char *text, const char *pszUnknownTimeString, Panel *parent) : Frame(parent, NULL, parent ? false : true) +{ + // save off the non-localized title, since we may need to dynamically localize it (on progress updates) + const wchar_t *ws = g_pVGuiLocalize->Find(title); + if (ws) + { + wcsncpy(m_wszTitleString, ws, sizeof(m_wszTitleString) / sizeof(wchar_t)); + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode(title, m_wszTitleString, sizeof(m_wszTitleString)); + } + + m_pMessageLabel = new Label(this, NULL, pszUnknownTimeString); + + ws = g_pVGuiLocalize->Find(text); + if (ws) + { + wcsncpy(m_wcsInfoString, ws, sizeof(m_wcsInfoString) / sizeof(wchar_t)); + } + else + { + m_wcsInfoString[0] = 0; + } + + ws = g_pVGuiLocalize->Find(pszUnknownTimeString); + if (ws) + { + wcsncpy(m_wszUnknownTimeString, ws, sizeof(m_wszUnknownTimeString) / sizeof(wchar_t)); + } + else + { + m_wszUnknownTimeString[0] = 0; + } + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ProgressBox::ProgressBox(const wchar_t *wszTitle, const wchar_t *wszText, const wchar_t *wszUnknownTimeString, Panel *parent) : Frame(parent, NULL, parent ? false : true) +{ + wcsncpy(m_wszTitleString, wszTitle, sizeof(m_wszTitleString) / sizeof(wchar_t)); + m_pMessageLabel = new Label(this, NULL, wszUnknownTimeString); + wcsncpy(m_wcsInfoString, wszText, sizeof(m_wcsInfoString) / sizeof(wchar_t)); + wcsncpy(m_wszUnknownTimeString, wszUnknownTimeString, sizeof(m_wszUnknownTimeString) / sizeof(wchar_t)); + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor Helper +//----------------------------------------------------------------------------- +void ProgressBox::Init() +{ + m_pProgressBar = new ProgressBar(this, NULL); + m_pProgressBar->SetVisible(false); + + m_pCancelButton = new Button(this, NULL, "#VGui_Cancel"); + m_pCancelButton->SetSize(72, 24); + m_pCancelButton->SetCommand("Cancel"); + + SetMenuButtonResponsive(false); + SetMinimizeButtonVisible(false); + SetCancelButtonVisible(false); + SetSizeable(false); + SetSize(384, 128); + m_flCurrentProgress = 0.0f; + m_flFirstProgressUpdate = -0.1f; + m_flLastProgressUpdate = 0.0f; + + // mark ourselves as needed ticked once a second, to force us to repaint + ivgui()->AddTickSignal(GetVPanel(), 1000); + + UpdateTitle(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +ProgressBox::~ProgressBox() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: resize the message label +//----------------------------------------------------------------------------- +void ProgressBox::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + int wide, tall; + m_pMessageLabel->GetContentSize(wide, tall); + SetSize(384, tall + 92); + m_pMessageLabel->SetSize(344, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Put the message box into a modal state +// Does not suspend execution - use addActionSignal to get return value +//----------------------------------------------------------------------------- +void ProgressBox::DoModal(Frame *pFrameOver) +{ + ShowWindow(pFrameOver); + input()->SetAppModalSurface(GetVPanel()); +} + +//----------------------------------------------------------------------------- +// Purpose: Activates the window +//----------------------------------------------------------------------------- +void ProgressBox::ShowWindow(Frame *pFrameOver) +{ + // move to the middle of the screen + // get the screen size + int wide, tall; + // get our dialog size + GetSize(wide, tall); + + if (pFrameOver) + { + int frameX, frameY; + int frameWide, frameTall; + pFrameOver->GetPos(frameX, frameY); + pFrameOver->GetSize(frameWide, frameTall); + + SetPos((frameWide - wide) / 2 + frameX, (frameTall - tall) / 2 + frameY); + } + else + { + int swide, stall; + surface()->GetScreenSize(swide, stall); + // put the dialog in the middle of the screen + SetPos((swide - wide) / 2, (stall - tall) / 2); + } + + BaseClass::Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: Put the text and OK buttons in correct place +//----------------------------------------------------------------------------- +void ProgressBox::PerformLayout() +{ + int x, y, wide, tall; + GetClientArea(x, y, wide, tall); + wide += x; + tall += y; + + int leftEdge = x + 16; + m_pMessageLabel->SetPos(leftEdge, y + 12); + m_pProgressBar->SetPos(leftEdge, y + 14 + m_pMessageLabel->GetTall() + 2); + m_pProgressBar->SetSize(wide - 44, 24); + + if (m_pCancelButton->IsVisible()) + { + // make room for cancel + int px, py, pw, pt; + int offs = m_pCancelButton->GetWide(); + m_pProgressBar->GetBounds(px, py, pw, pt); + m_pCancelButton->SetPos(px + pw - offs, py); + m_pProgressBar->SetSize(pw - offs - 10, pt); + } + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: updates progress bar, range [0, 1] +//----------------------------------------------------------------------------- +void ProgressBox::SetProgress(float progress) +{ + Assert(progress >= 0.0f && progress <= 1.0f); + m_pProgressBar->SetProgress(progress); + m_pProgressBar->SetVisible(true); + + // only update progress timings if the progress has actually changed + if (progress != m_flCurrentProgress) + { + // store off timings for calculating time remaining + if (m_flFirstProgressUpdate < 0.0f) + { + m_flFirstProgressUpdate = (float)system()->GetFrameTime(); + } + m_flCurrentProgress = progress; + m_flLastProgressUpdate = (float)system()->GetFrameTime(); + + UpdateTitle(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets the info text +//----------------------------------------------------------------------------- +void ProgressBox::SetText(const char *text) +{ + m_pMessageLabel->SetText(text); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the dialog title text +//----------------------------------------------------------------------------- +void ProgressBox::UpdateTitle() +{ + // update progress text + wchar_t unicode[256]; + wchar_t completion[64]; + if ((int)(m_flCurrentProgress * 100.0f) > 0) + { + _snwprintf(completion, sizeof(completion) / sizeof(wchar_t), L"- %d%% complete", (int)(m_flCurrentProgress * 100.0f)); + } + else + { + completion[0] = 0; + } + g_pVGuiLocalize->ConstructString(unicode, sizeof(unicode), m_wszTitleString, 1, completion); + SetTitle(unicode, true); +} + +//----------------------------------------------------------------------------- +// Purpose: called every render +//----------------------------------------------------------------------------- +void ProgressBox::OnThink() +{ + // calculate the progress made + if (m_flFirstProgressUpdate >= 0.0f && m_wcsInfoString[0]) + { + wchar_t timeRemaining[128]; + if (ProgressBar::ConstructTimeRemainingString(timeRemaining, sizeof(timeRemaining), m_flFirstProgressUpdate, (float)system()->GetFrameTime(), m_flCurrentProgress, m_flLastProgressUpdate, true)) + { + wchar_t unicode[256]; + g_pVGuiLocalize->ConstructString(unicode, sizeof(unicode), m_wcsInfoString, 1, timeRemaining); + m_pMessageLabel->SetText(unicode); + } + else + { + m_pMessageLabel->SetText(m_wszUnknownTimeString); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Forces us to repaint once per second +//----------------------------------------------------------------------------- +void ProgressBox::OnTick() +{ + if (m_flFirstProgressUpdate >= 0.0f) + { + Repaint(); + } + + BaseClass::OnTick(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles ESC closing dialog +//----------------------------------------------------------------------------- +void ProgressBox::OnCommand(const char *command) +{ + if (!stricmp(command, "Cancel")) + { + OnCancel(); + } + else + { + BaseClass::OnCommand(command); + } +} + +//----------------------------------------------------------------------------- +// Purpose: close button pressed +//----------------------------------------------------------------------------- +void ProgressBox::OnCloseFrameButtonPressed() +{ + OnCancel(); +} + +//----------------------------------------------------------------------------- +// Purpose: Deletes self when closed +//----------------------------------------------------------------------------- +void ProgressBox::OnClose() +{ + BaseClass::OnClose(); + // modal surface is released on deletion + MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBox::OnShutdownRequest() +{ + // Shutdown the dialog + PostMessage(this, new KeyValues("Command", "command", "Cancel")); +} + +//----------------------------------------------------------------------------- +// Purpose: On update cancelled +//----------------------------------------------------------------------------- +void ProgressBox::OnCancel() +{ + // post a message that we've been cancelled + PostActionSignal(new KeyValues("ProgressBoxCancelled")); + + // close this dialog + Close(); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggles visibility of the close box. +//----------------------------------------------------------------------------- +void ProgressBox::SetCancelButtonVisible(bool state) +{ + BaseClass::SetCloseButtonVisible(state); + m_pCancelButton->SetVisible(state); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ProgressBox::SetCancelButtonEnabled(bool state) +{ + m_pCancelButton->SetEnabled(state); + BaseClass::SetCloseButtonVisible(state); + InvalidateLayout(); + Repaint(); +} diff --git a/vgui2/vgui_controls/PropertyDialog.cpp b/vgui2/vgui_controls/PropertyDialog.cpp new file mode 100644 index 0000000..568b23e --- /dev/null +++ b/vgui2/vgui_controls/PropertyDialog.cpp @@ -0,0 +1,303 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/KeyCode.h> +#include <KeyValues.h> + +#include <vgui_controls/Button.h> +#include <vgui_controls/PropertyDialog.h> +#include <vgui_controls/PropertySheet.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +PropertyDialog::PropertyDialog(Panel *parent, const char *panelName) : Frame(parent, panelName) +{ + // create the property sheet + _propertySheet = new PropertySheet(this, "Sheet"); + _propertySheet->AddActionSignalTarget(this); + _propertySheet->SetTabPosition(1); + + // add the buttons + _okButton = new Button(this, "OKButton", "#PropertyDialog_OK"); + _okButton->AddActionSignalTarget(this); + _okButton->SetTabPosition(2); + _okButton->SetCommand("OK"); + GetFocusNavGroup().SetDefaultButton(_okButton); + + _cancelButton = new Button(this, "CancelButton", "#PropertyDialog_Cancel"); + _cancelButton->AddActionSignalTarget(this); + _cancelButton->SetTabPosition(3); + _cancelButton->SetCommand("Cancel"); + + _applyButton = new Button(this, "ApplyButton", "#PropertyDialog_Apply"); + _applyButton->AddActionSignalTarget(this); + _applyButton->SetTabPosition(4); + _applyButton->SetVisible(false); // default to not visible + _applyButton->SetEnabled(false); // default to not enabled + _applyButton->SetCommand("Apply"); + + SetSizeable(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +PropertyDialog::~PropertyDialog() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a pointer to the PropertySheet this dialog encapsulates +// Output : PropertySheet * +//----------------------------------------------------------------------------- +PropertySheet *PropertyDialog::GetPropertySheet() +{ + return _propertySheet; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a pointer to the currently active page. +// Output : Panel +//----------------------------------------------------------------------------- +Panel *PropertyDialog::GetActivePage() +{ + return _propertySheet->GetActivePage(); +} + +//----------------------------------------------------------------------------- +// Purpose: Wrapped function +//----------------------------------------------------------------------------- +void PropertyDialog::AddPage(Panel *page, const char *title) +{ + _propertySheet->AddPage(page, title); +} + +//----------------------------------------------------------------------------- +// Purpose: reloads the data in all the property page +//----------------------------------------------------------------------------- +void PropertyDialog::ResetAllData() +{ + _propertySheet->ResetAllData(); +} + +//----------------------------------------------------------------------------- +// Purpose: Applies any changes +//----------------------------------------------------------------------------- +void PropertyDialog::ApplyChanges() +{ + OnCommand("Apply"); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets up the sheet +//----------------------------------------------------------------------------- +void PropertyDialog::PerformLayout() +{ + BaseClass::PerformLayout(); + + int iBottom = m_iSheetInsetBottom; + if ( IsProportional() ) + { + iBottom = scheme()->GetProportionalScaledValueEx( GetScheme(), iBottom ); + } + + int x, y, wide, tall; + GetClientArea(x, y, wide, tall); + _propertySheet->SetBounds(x, y, wide, tall - iBottom); + + + // move the buttons to the bottom-right corner + int xpos = x + wide - 80; + int ypos = tall + y - 28; + + if (_applyButton->IsVisible()) + { + _applyButton->SetBounds(xpos, ypos, 72, 24); + xpos -= 80; + } + + if (_cancelButton->IsVisible()) + { + _cancelButton->SetBounds(xpos, ypos, 72, 24); + xpos -= 80; + } + + _okButton->SetBounds(xpos, ypos, 72, 24); + + _propertySheet->InvalidateLayout(); // tell the propertysheet to redraw! + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles command text from the buttons +//----------------------------------------------------------------------------- +void PropertyDialog::OnCommand(const char *command) +{ + if (!stricmp(command, "OK")) + { + if ( OnOK(false) ) + { + OnCommand("Close"); + } + _applyButton->SetEnabled(false); + } + else if (!stricmp(command, "Cancel")) + { + OnCancel(); + Close(); + } + else if (!stricmp(command, "Apply")) + { + OnOK(true); + _applyButton->SetEnabled(false); + InvalidateLayout(); + } + else + { + BaseClass::OnCommand(command); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called when the Cancel button is pressed +//----------------------------------------------------------------------------- +void PropertyDialog::OnCancel() +{ + // designed to be overridden +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : code - +//----------------------------------------------------------------------------- +void PropertyDialog::OnKeyCodeTyped(KeyCode code) +{ + // this has been removed, since it conflicts with how we use the escape key in the game +// if (code == KEY_ESCAPE) +// { +// OnCommand("Cancel"); +// } +// else + { + BaseClass::OnKeyCodeTyped(code); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Command handler +//----------------------------------------------------------------------------- +bool PropertyDialog::OnOK(bool applyOnly) +{ + // the sheet should have the pages apply changes before we tell the world + _propertySheet->ApplyChanges(); + + // this should tell anybody who's watching us that we're done + PostActionSignal(new KeyValues("ApplyChanges")); + + // default to closing + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Overrides build mode so it edits the sub panel +//----------------------------------------------------------------------------- +void PropertyDialog::ActivateBuildMode() +{ + // no subpanel, no build mode + EditablePanel *panel = dynamic_cast<EditablePanel *>(GetActivePage()); + if (!panel) + return; + + panel->ActivateBuildMode(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the text on the OK/Cancel buttons, overriding the default +//----------------------------------------------------------------------------- +void PropertyDialog::SetOKButtonText(const char *text) +{ + _okButton->SetText(text); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the text on the OK/Cancel buttons, overriding the default +//----------------------------------------------------------------------------- +void PropertyDialog::SetCancelButtonText(const char *text) +{ + _cancelButton->SetText(text); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the text on the apply buttons, overriding the default +//----------------------------------------------------------------------------- +void PropertyDialog::SetApplyButtonText(const char *text) +{ + _applyButton->SetText(text); +} + +//----------------------------------------------------------------------------- +// Purpose: changes the visibility of the buttons +//----------------------------------------------------------------------------- +void PropertyDialog::SetOKButtonVisible(bool state) +{ + _okButton->SetVisible(state); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: changes the visibility of the buttons +//----------------------------------------------------------------------------- +void PropertyDialog::SetCancelButtonVisible(bool state) +{ + _cancelButton->SetVisible(state); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: changes the visibility of the buttons +//----------------------------------------------------------------------------- +void PropertyDialog::SetApplyButtonVisible(bool state) +{ + _applyButton->SetVisible(state); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: when a sheet changes, enable the apply button +//----------------------------------------------------------------------------- +void PropertyDialog::OnApplyButtonEnable() +{ + if (_applyButton->IsEnabled()) + return; + + EnableApplyButton(true); +} + +//----------------------------------------------------------------------------- +// Purpose: enable/disable the apply button +//----------------------------------------------------------------------------- +void PropertyDialog::EnableApplyButton(bool bEnable) +{ + _applyButton->SetEnabled(bEnable); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertyDialog::RequestFocus(int direction) +{ + _propertySheet->RequestFocus(direction); +} diff --git a/vgui2/vgui_controls/PropertyPage.cpp b/vgui2/vgui_controls/PropertyPage.cpp new file mode 100644 index 0000000..36aa102 --- /dev/null +++ b/vgui2/vgui_controls/PropertyPage.cpp @@ -0,0 +1,114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui/IScheme.h" +#include "vgui/KeyCode.h" +#include "vgui/ISurface.h" +#include "KeyValues.h" + +#include "vgui_controls/PropertyPage.h" +#include "vgui_controls/Controls.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +PropertyPage::PropertyPage(Panel *parent, const char *panelName) : EditablePanel(parent, panelName) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +PropertyPage::~PropertyPage() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Called when page is loaded. Data should be reloaded from document into controls. +//----------------------------------------------------------------------------- +void PropertyPage::OnResetData() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the OK / Apply button is pressed. Changed data should be written into document. +//----------------------------------------------------------------------------- +void PropertyPage::OnApplyChanges() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Designed to be overriden +//----------------------------------------------------------------------------- +void PropertyPage::OnPageShow() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Designed to be overriden +//----------------------------------------------------------------------------- +void PropertyPage::OnPageHide() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pageTab - +//----------------------------------------------------------------------------- +void PropertyPage::OnPageTabActivated(Panel *pageTab) +{ + _pageTab = pageTab; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertyPage::OnKeyCodeTyped(KeyCode code) +{ + switch (code) + { + // left and right only get propogated to parents if our tab has focus + case KEY_RIGHT: + { + if (_pageTab != 0 && _pageTab->HasFocus()) + BaseClass::OnKeyCodeTyped(code); + break; + } + case KEY_LEFT: + { + if (_pageTab != 0 && _pageTab->HasFocus()) + BaseClass::OnKeyCodeTyped(code); + break; + } + default: + BaseClass::OnKeyCodeTyped(code); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertyPage::SetVisible(bool state) +{ + if (IsVisible() && !state) + { + // if we're going away and we have a current button, get rid of it + if (GetFocusNavGroup().GetCurrentDefaultButton()) + { + GetFocusNavGroup().SetCurrentDefaultButton(NULL); + } + } + + BaseClass::SetVisible(state); +} + diff --git a/vgui2/vgui_controls/PropertySheet.cpp b/vgui2/vgui_controls/PropertySheet.cpp new file mode 100644 index 0000000..06ff890 --- /dev/null +++ b/vgui2/vgui_controls/PropertySheet.cpp @@ -0,0 +1,1675 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/IBorder.h> +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/IScheme.h> +#include <vgui/ISystem.h> +#include <vgui/IVGui.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> +#include <vgui/ISurface.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/PropertySheet.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/Panel.h> +#include <vgui_controls/ToolWindow.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/PropertyPage.h> +#include "vgui_controls/AnimationController.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +namespace vgui +{ + +class ContextLabel : public Label +{ + DECLARE_CLASS_SIMPLE( ContextLabel, Label ); +public: + + ContextLabel( Button *parent, char const *panelName, char const *text ): + BaseClass( (Panel *)parent, panelName, text ), + m_pTabButton( parent ) + { + SetBlockDragChaining( true ); + } + + virtual void OnMousePressed( MouseCode code ) + { + if ( m_pTabButton ) + { + m_pTabButton->FireActionSignal(); + } + } + + virtual void OnMouseReleased( MouseCode code ) + { + BaseClass::OnMouseReleased( code ); + + if ( GetParent() ) + { + GetParent()->OnCommand( "ShowContextMenu" ); + } + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + HFont marlett = pScheme->GetFont( "Marlett" ); + SetFont( marlett ); + SetTextInset( 0, 0 ); + SetContentAlignment( Label::a_northwest ); + + if ( GetParent() ) + { + SetFgColor( pScheme->GetColor( "Button.TextColor", GetParent()->GetFgColor() ) ); + SetBgColor( GetParent()->GetBgColor() ); + } + } +private: + + Button *m_pTabButton; +}; + +//----------------------------------------------------------------------------- +// Purpose: Helper for drag drop +// Input : msglist - +// Output : static PropertySheet +//----------------------------------------------------------------------------- +static PropertySheet *IsDroppingSheet( CUtlVector< KeyValues * >& msglist ) +{ + if ( msglist.Count() == 0 ) + return NULL; + + KeyValues *data = msglist[ 0 ]; + PropertySheet *sheet = reinterpret_cast< PropertySheet * >( data->GetPtr( "propertysheet" ) ); + if ( sheet ) + return sheet; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: A single tab +//----------------------------------------------------------------------------- +class PageTab : public Button +{ + DECLARE_CLASS_SIMPLE( PageTab, Button ); + +private: + bool _active; + Color _textColor; + Color _dimTextColor; + int m_bMaxTabWidth; + IBorder *m_pActiveBorder; + IBorder *m_pNormalBorder; + PropertySheet *m_pParent; + Panel *m_pPage; + ImagePanel *m_pImage; + char *m_pszImageName; + bool m_bShowContextLabel; + bool m_bAttemptingDrop; + ContextLabel *m_pContextLabel; + long m_hoverActivatePageTime; + long m_dropHoverTime; + +public: + PageTab(PropertySheet *parent, const char *panelName, const char *text, char const *imageName, int maxTabWidth, Panel *page, bool showContextButton, long hoverActivatePageTime = -1 ) : + Button( (Panel *)parent, panelName, text), + m_pParent( parent ), + m_pPage( page ), + m_pImage( 0 ), + m_pszImageName( 0 ), + m_bShowContextLabel( showContextButton ), + m_bAttemptingDrop( false ), + m_hoverActivatePageTime( hoverActivatePageTime ), + m_dropHoverTime( -1 ) + { + SetCommand(new KeyValues("TabPressed")); + _active = false; + m_bMaxTabWidth = maxTabWidth; + SetDropEnabled( true ); + SetDragEnabled( m_pParent->IsDraggableTab() ); + if ( imageName ) + { + m_pImage = new ImagePanel( this, text ); + int buflen = Q_strlen( imageName ) + 1; + m_pszImageName = new char[ buflen ]; + Q_strncpy( m_pszImageName, imageName, buflen ); + + } + SetMouseClickEnabled( MOUSE_RIGHT, true ); + m_pContextLabel = m_bShowContextLabel ? new ContextLabel( this, "Context", "9" ) : NULL; + + REGISTER_COLOR_AS_OVERRIDABLE( _textColor, "selectedcolor" ); + REGISTER_COLOR_AS_OVERRIDABLE( _dimTextColor, "unselectedcolor" ); + } + + ~PageTab() + { + delete[] m_pszImageName; + } + + virtual void Paint() + { + BaseClass::Paint(); + } + + virtual void OnCursorEntered() + { + m_dropHoverTime = system()->GetTimeMillis(); + } + + virtual void OnCursorExited() + { + m_dropHoverTime = -1; + } + + virtual void OnThink() + { + if ( m_bAttemptingDrop && m_hoverActivatePageTime >= 0 && m_dropHoverTime >= 0 ) + { + long hoverTime = system()->GetTimeMillis() - m_dropHoverTime; + if ( hoverTime > m_hoverActivatePageTime ) + { + FireActionSignal(); + SetSelected(true); + Repaint(); + } + } + m_bAttemptingDrop = false; + + BaseClass::OnThink(); + } + + virtual bool IsDroppable( CUtlVector< KeyValues * >&msglist ) + { + m_bAttemptingDrop = true; + + if ( !GetParent() ) + return false; + + PropertySheet *sheet = IsDroppingSheet( msglist ); + if ( sheet ) + return GetParent()->IsDroppable( msglist ); + + return BaseClass::IsDroppable( msglist ); + } + + virtual void OnDroppablePanelPaint( CUtlVector< KeyValues * >& msglist, CUtlVector< Panel * >& dragPanels ) + { + PropertySheet *sheet = IsDroppingSheet( msglist ); + if ( sheet ) + { + Panel *target = GetParent()->GetDropTarget( msglist ); + if ( target ) + { + // Fixme, mouse pos could be wrong... + target->OnDroppablePanelPaint( msglist, dragPanels ); + return; + } + } + + // Just highlight the tab if dropping onto active page via the tab + BaseClass::OnDroppablePanelPaint( msglist, dragPanels ); + } + + virtual void OnPanelDropped( CUtlVector< KeyValues * >& msglist ) + { + PropertySheet *sheet = IsDroppingSheet( msglist ); + if ( sheet ) + { + Panel *target = GetParent()->GetDropTarget( msglist ); + if ( target ) + { + // Fixme, mouse pos could be wrong... + target->OnPanelDropped( msglist ); + } + } + + // Defer to active page... + Panel *active = m_pParent->GetActivePage(); + if ( !active || !active->IsDroppable( msglist ) ) + return; + + active->OnPanelDropped( msglist ); + } + + virtual void OnDragFailed( CUtlVector< KeyValues * >& msglist ) + { + PropertySheet *sheet = IsDroppingSheet( msglist ); + if ( !sheet ) + return; + + // Create a new property sheet + if ( m_pParent->IsDraggableTab() ) + { + if ( msglist.Count() == 1 ) + { + KeyValues *data = msglist[ 0 ]; + int screenx = data->GetInt( "screenx" ); + int screeny = data->GetInt( "screeny" ); + + // m_pParent->ScreenToLocal( screenx, screeny ); + if ( !m_pParent->IsWithin( screenx, screeny ) ) + { + Panel *page = reinterpret_cast< Panel * >( data->GetPtr( "propertypage" ) ); + sheet = reinterpret_cast< PropertySheet * >( data->GetPtr( "propertysheet" ) ); + char const *title = data->GetString( "tabname", "" ); + if ( !page || !sheet ) + return; + + // Can only create if sheet was part of a ToolWindow derived object + ToolWindow *tw = dynamic_cast< ToolWindow * >( sheet->GetParent() ); + if ( tw ) + { + IToolWindowFactory *factory = tw->GetToolWindowFactory(); + if ( factory ) + { + bool hasContextMenu = sheet->PageHasContextMenu( page ); + sheet->RemovePage( page ); + factory->InstanceToolWindow( tw->GetParent(), sheet->ShouldShowContextButtons(), page, title, hasContextMenu ); + + if ( sheet->GetNumPages() == 0 ) + { + tw->MarkForDeletion(); + } + } + } + } + } + } + } + + virtual void OnCreateDragData( KeyValues *msg ) + { + Assert( m_pParent->IsDraggableTab() ); + + msg->SetPtr( "propertypage", m_pPage ); + msg->SetPtr( "propertysheet", m_pParent ); + char sz[ 256 ]; + GetText( sz, sizeof( sz ) ); + msg->SetString( "tabname", sz ); + msg->SetString( "text", sz ); + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + // set up the scheme settings + Button::ApplySchemeSettings(pScheme); + + _textColor = GetSchemeColor("PropertySheet.SelectedTextColor", GetFgColor(), pScheme); + _dimTextColor = GetSchemeColor("PropertySheet.TextColor", GetFgColor(), pScheme); + m_pActiveBorder = pScheme->GetBorder("TabActiveBorder"); + m_pNormalBorder = pScheme->GetBorder("TabBorder"); + + if ( m_pImage ) + { + ClearImages(); + m_pImage->SetImage(scheme()->GetImage(m_pszImageName, false)); + AddImage( m_pImage->GetImage(), 2 ); + int w, h; + m_pImage->GetSize( w, h ); + w += m_pContextLabel ? 10 : 0; + if ( m_pContextLabel ) + { + m_pImage->SetPos( 10, 0 ); + } + SetSize( w + 4, h + 2 ); + } + else + { + int wide, tall; + int contentWide, contentTall; + GetSize(wide, tall); + GetContentSize(contentWide, contentTall); + + wide = max(m_bMaxTabWidth, contentWide + 10); // 10 = 5 pixels margin on each side + wide += m_pContextLabel ? 10 : 0; + SetSize(wide, tall); + } + + if ( m_pContextLabel ) + { + SetTextInset( 12, 0 ); + } + } + + virtual void ApplySettings( KeyValues *inResourceData ) + { + const char *pBorder = inResourceData->GetString("activeborder_override", ""); + if (*pBorder) + { + m_pActiveBorder = scheme()->GetIScheme(GetScheme())->GetBorder( pBorder ); + } + pBorder = inResourceData->GetString("normalborder_override", ""); + if (*pBorder) + { + m_pNormalBorder = scheme()->GetIScheme(GetScheme())->GetBorder( pBorder ); + } + BaseClass::ApplySettings(inResourceData); + } + + virtual void OnCommand( char const *cmd ) + { + if ( !Q_stricmp( cmd, "ShowContextMenu" ) ) + { + KeyValues *kv = new KeyValues("OpenContextMenu"); + kv->SetPtr( "page", m_pPage ); + kv->SetPtr( "contextlabel", m_pContextLabel ); + PostActionSignal( kv ); + return; + } + BaseClass::OnCommand( cmd ); + } + + IBorder *GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) + { + if (_active) + { + return m_pActiveBorder; + } + return m_pNormalBorder; + } + + virtual Color GetButtonFgColor() + { + if (_active) + { + return _textColor; + } + else + { + return _dimTextColor; + } + } + + virtual void SetActive(bool state) + { + _active = state; + SetZPos( state ? 100 : 0 ); + InvalidateLayout(); + Repaint(); + } + + virtual void SetTabWidth( int iWidth ) + { + m_bMaxTabWidth = iWidth; + InvalidateLayout(); + } + + virtual bool CanBeDefaultButton(void) + { + return false; + } + + //Fire action signal when mouse is pressed down instead of on release. + virtual void OnMousePressed(MouseCode code) + { + // check for context menu open + if (!IsEnabled()) + return; + + if (!IsMouseClickEnabled(code)) + return; + + if (IsUseCaptureMouseEnabled()) + { + { + RequestFocus(); + FireActionSignal(); + SetSelected(true); + Repaint(); + } + + // lock mouse input to going to this button + input()->SetMouseCapture(GetVPanel()); + } + } + + virtual void OnMouseReleased(MouseCode code) + { + // ensure mouse capture gets released + if (IsUseCaptureMouseEnabled()) + { + input()->SetMouseCapture(NULL); + } + + // make sure the button gets unselected + SetSelected(false); + Repaint(); + + if (code == MOUSE_RIGHT) + { + KeyValues *kv = new KeyValues("OpenContextMenu"); + kv->SetPtr( "page", m_pPage ); + kv->SetPtr( "contextlabel", m_pContextLabel ); + PostActionSignal( kv ); + } + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_pContextLabel ) + { + int w, h; + GetSize( w, h ); + m_pContextLabel->SetBounds( 0, 0, 10, h ); + } + } +}; + + +}; // namespace vgui + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +PropertySheet::PropertySheet( + Panel *parent, + const char *panelName, + bool draggableTabs /*= false*/ ) : BaseClass(parent, panelName) +{ + _activePage = NULL; + _activeTab = NULL; + _tabWidth = 64; + _activeTabIndex = 0; + _showTabs = true; + _combo = NULL; + _tabFocus = false; + m_flPageTransitionEffectTime = 0.0f; + m_bSmallTabs = false; + m_tabFont = 0; + m_bDraggableTabs = draggableTabs; + m_pTabKV = NULL; + m_iTabHeight = 0; + m_iTabHeightSmall = 0; + + if ( m_bDraggableTabs ) + { + SetDropEnabled( true ); + } + + m_bKBNavigationEnabled = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor, associates pages with a combo box +//----------------------------------------------------------------------------- +PropertySheet::PropertySheet(Panel *parent, const char *panelName, ComboBox *combo) : BaseClass(parent, panelName) +{ + _activePage = NULL; + _activeTab = NULL; + _tabWidth = 64; + _activeTabIndex = 0; + _combo=combo; + _combo->AddActionSignalTarget(this); + _showTabs = false; + _tabFocus = false; + m_flPageTransitionEffectTime = 0.0f; + m_bSmallTabs = false; + m_tabFont = 0; + m_bDraggableTabs = false; + m_pTabKV = NULL; + m_iTabHeight = 0; + m_iTabHeightSmall = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +PropertySheet::~PropertySheet() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: ToolWindow uses this to drag tools from container to container by dragging the tab +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PropertySheet::IsDraggableTab() const +{ + return m_bDraggableTabs; +} + +void PropertySheet::SetDraggableTabs( bool state ) +{ + m_bDraggableTabs = state; +} + +//----------------------------------------------------------------------------- +// Purpose: Lower profile tabs +// Input : state - +//----------------------------------------------------------------------------- +void PropertySheet::SetSmallTabs( bool state ) +{ + m_bSmallTabs = state; + m_tabFont = scheme()->GetIScheme( GetScheme() )->GetFont( m_bSmallTabs ? "DefaultVerySmall" : "Default" ); + int c = m_PageTabs.Count(); + for ( int i = 0; i < c ; ++i ) + { + PageTab *tab = m_PageTabs[ i ]; + Assert( tab ); + tab->SetFont( m_tabFont ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PropertySheet::IsSmallTabs() const +{ + return m_bSmallTabs; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void PropertySheet::ShowContextButtons( bool state ) +{ + m_bContextButton = state; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PropertySheet::ShouldShowContextButtons() const +{ + return m_bContextButton; +} + +int PropertySheet::FindPage( Panel *page ) const +{ + int c = m_Pages.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( m_Pages[ i ].page == page ) + return i; + } + + return m_Pages.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: adds a page to the sheet +//----------------------------------------------------------------------------- +void PropertySheet::AddPage(Panel *page, const char *title, char const *imageName /*= NULL*/, bool bHasContextMenu /*= false*/ ) +{ + if (!page) + return; + + // don't add the page if we already have it + if ( FindPage( page ) != m_Pages.InvalidIndex() ) + return; + + long hoverActivatePageTime = 250; + PageTab *tab = new PageTab(this, "tab", title, imageName, _tabWidth, page, m_bContextButton && bHasContextMenu, hoverActivatePageTime ); + if ( m_bDraggableTabs ) + { + tab->SetDragEnabled( true ); + } + + tab->SetFont( m_tabFont ); + if(_showTabs) + { + tab->AddActionSignalTarget(this); + } + else if (_combo) + { + _combo->AddItem(title, NULL); + } + + if ( m_pTabKV ) + { + tab->ApplySettings( m_pTabKV ); + } + + m_PageTabs.AddToTail(tab); + + Page_t info; + info.page = page; + info.contextMenu = m_bContextButton && bHasContextMenu; + + m_Pages.AddToTail( info ); + + page->SetParent(this); + page->AddActionSignalTarget(this); + PostMessage(page, new KeyValues("ResetData")); + + page->SetVisible(false); + InvalidateLayout(); + + if (!_activePage) + { + // first page becomes the active page + ChangeActiveTab( 0 ); + if ( _activePage ) + { + _activePage->RequestFocus( 0 ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::SetActivePage(Panel *page) +{ + // walk the list looking for this page + int index = FindPage( page ); + if (!m_Pages.IsValidIndex(index)) + return; + + ChangeActiveTab(index); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::SetTabWidth(int pixels) +{ + if ( pixels < 0 ) + { + if( !_activeTab ) + return; + + int nTall; + _activeTab->GetContentSize( pixels, nTall ); + } + + if ( _tabWidth == pixels ) + return; + + _tabWidth = pixels; + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: reloads the data in all the property page +//----------------------------------------------------------------------------- +void PropertySheet::ResetAllData() +{ + // iterate all the dialogs resetting them + for (int i = 0; i < m_Pages.Count(); i++) + { + ipanel()->SendMessage(m_Pages[i].page->GetVPanel(), new KeyValues("ResetData"), GetVPanel()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Applies any changes made by the dialog +//----------------------------------------------------------------------------- +void PropertySheet::ApplyChanges() +{ + // iterate all the dialogs resetting them + for (int i = 0; i < m_Pages.Count(); i++) + { + ipanel()->SendMessage(m_Pages[i].page->GetVPanel(), new KeyValues("ApplyChanges"), GetVPanel()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: gets a pointer to the currently active page +//----------------------------------------------------------------------------- +Panel *PropertySheet::GetActivePage() +{ + return _activePage; +} + +//----------------------------------------------------------------------------- +// Purpose: gets a pointer to the currently active tab +//----------------------------------------------------------------------------- +Panel *PropertySheet::GetActiveTab() +{ + return _activeTab; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the number of panels in the sheet +//----------------------------------------------------------------------------- +int PropertySheet::GetNumPages() +{ + return m_Pages.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the name contained in the active tab +// Input : a text buffer to contain the output +//----------------------------------------------------------------------------- +void PropertySheet::GetActiveTabTitle (char *textOut, int bufferLen ) +{ + if(_activeTab) _activeTab->GetText(textOut, bufferLen); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the name contained in the active tab +// Input : a text buffer to contain the output +//----------------------------------------------------------------------------- +bool PropertySheet::GetTabTitle( int i, char *textOut, int bufferLen ) +{ + if ( i < 0 || i >= m_PageTabs.Count() ) + { + return false; + } + + m_PageTabs[i]->GetText(textOut, bufferLen); + return true; +} + +bool PropertySheet::SetTabTitle( int i, char *pchTitle ) +{ + if ( i < 0 || i >= m_PageTabs.Count() ) + { + return false; + } + + m_PageTabs[ i ]->SetText( pchTitle ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the index of the currently active page +//----------------------------------------------------------------------------- +int PropertySheet::GetActivePageNum() +{ + for (int i = 0; i < m_Pages.Count(); i++) + { + if (m_Pages[i].page == _activePage) + { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Forwards focus requests to current active page +//----------------------------------------------------------------------------- +void PropertySheet::RequestFocus(int direction) +{ + if (direction == -1 || direction == 0) + { + if (_activePage) + { + _activePage->RequestFocus(direction); + _tabFocus = false; + } + } + else + { + if (_showTabs && _activeTab) + { + _activeTab->RequestFocus(direction); + _tabFocus = true; + } + else if (_activePage) + { + _activePage->RequestFocus(direction); + _tabFocus = false; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: moves focus back +//----------------------------------------------------------------------------- +bool PropertySheet::RequestFocusPrev(VPANEL panel) +{ + if (_tabFocus || !_showTabs || !_activeTab) + { + _tabFocus = false; + return BaseClass::RequestFocusPrev(panel); + } + else + { + if (GetVParent()) + { + PostMessage(GetVParent(), new KeyValues("FindDefaultButton")); + } + _activeTab->RequestFocus(-1); + _tabFocus = true; + return true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: moves focus forward +//----------------------------------------------------------------------------- +bool PropertySheet::RequestFocusNext(VPANEL panel) +{ + if (!_tabFocus || !_activePage) + { + return BaseClass::RequestFocusNext(panel); + } + else + { + if (!_activeTab) + { + return BaseClass::RequestFocusNext(panel); + } + else + { + _activePage->RequestFocus(1); + _tabFocus = false; + return true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets scheme settings +//----------------------------------------------------------------------------- +void PropertySheet::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + // a little backwards-compatibility with old scheme files + IBorder *pBorder = pScheme->GetBorder("PropertySheetBorder"); + if (pBorder == pScheme->GetBorder("Default")) + { + // get the old name + pBorder = pScheme->GetBorder("RaisedBorder"); + } + + SetBorder(pBorder); + m_flPageTransitionEffectTime = atof(pScheme->GetResourceString("PropertySheet.TransitionEffectTime")); + + m_tabFont = pScheme->GetFont( m_bSmallTabs ? "DefaultVerySmall" : "Default" ); + + if ( m_pTabKV ) + { + for (int i = 0; i < m_PageTabs.Count(); i++) + { + m_PageTabs[i]->ApplySettings( m_pTabKV ); + } + } + + + //============================================================================= + // HPE_BEGIN: + // [tj] Here, we used to use a single size variable and overwrite it when we scaled. + // This led to problems when we changes resolutions, so now we recalcuate the absolute + // size from the relative size each time (based on proportionality) + //============================================================================= + if ( IsProportional() ) + { + m_iTabHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), m_iSpecifiedTabHeight ); + m_iTabHeightSmall = scheme()->GetProportionalScaledValueEx( GetScheme(), m_iSpecifiedTabHeightSmall ); + } + else + { + m_iTabHeight = m_iSpecifiedTabHeight; + m_iTabHeightSmall = m_iSpecifiedTabHeightSmall; + } + //============================================================================= + // HPE_END + //============================================================================= +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + + KeyValues *pTabKV = inResourceData->FindKey( "tabskv" ); + if ( pTabKV ) + { + if ( m_pTabKV ) + { + m_pTabKV->deleteThis(); + } + m_pTabKV = new KeyValues("tabkv"); + pTabKV->CopySubkeys( m_pTabKV ); + } + + KeyValues *pTabWidthKV = inResourceData->FindKey( "tabwidth" ); + if ( pTabWidthKV ) + { + _tabWidth = scheme()->GetProportionalScaledValueEx(GetScheme(), pTabWidthKV->GetInt()); + for (int i = 0; i < m_PageTabs.Count(); i++) + { + m_PageTabs[i]->SetTabWidth( _tabWidth ); + } + } + + KeyValues *pTransitionKV = inResourceData->FindKey( "transition_time" ); + if ( pTransitionKV ) + { + m_flPageTransitionEffectTime = pTransitionKV->GetFloat(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Paint our border specially, with the tabs in mind +//----------------------------------------------------------------------------- +void PropertySheet::PaintBorder() +{ + IBorder *border = GetBorder(); + if (!border) + return; + + // draw the border, but with a break at the active tab + int px = 0, py = 0, pwide = 0, ptall = 0; + if (_activeTab) + { + _activeTab->GetBounds(px, py, pwide, ptall); + ptall -= 1; + } + + // draw the border underneath the buttons, with a break + int wide, tall; + GetSize(wide, tall); + border->Paint(0, py + ptall, wide, tall, IBorder::SIDE_TOP, px + 1, px + pwide - 1); +} + +//----------------------------------------------------------------------------- +// Purpose: Lays out the dialog +//----------------------------------------------------------------------------- +void PropertySheet::PerformLayout() +{ + BaseClass::PerformLayout(); + + int x, y, wide, tall; + GetBounds(x, y, wide, tall); + if (_activePage) + { + int tabHeight = IsSmallTabs() ? m_iTabHeightSmall : m_iTabHeight; + + if(_showTabs) + { + _activePage->SetBounds(0, tabHeight, wide, tall - tabHeight); + } + else + { + _activePage->SetBounds(0, 0, wide, tall ); + } + _activePage->InvalidateLayout(); + } + + + int xtab; + int limit = m_PageTabs.Count(); + + xtab = m_iTabXIndent; + + // draw the visible tabs + if (_showTabs) + { + for (int i = 0; i < limit; i++) + { + int tabHeight = IsSmallTabs() ? (m_iTabHeightSmall-1) : (m_iTabHeight-1); + + m_PageTabs[i]->GetSize(wide, tall); + + if ( m_bTabFitText ) + { + m_PageTabs[i]->SizeToContents(); + wide = m_PageTabs[i]->GetWide(); + + int iXInset, iYInset; + m_PageTabs[i]->GetTextInset( &iXInset, &iYInset ); + wide += (iXInset * 2); + } + + if (m_PageTabs[i] == _activeTab) + { + // active tab is taller + _activeTab->SetBounds(xtab, 2, wide, tabHeight); + } + else + { + m_PageTabs[i]->SetBounds(xtab, 4, wide, tabHeight - 2); + } + m_PageTabs[i]->SetVisible(true); + xtab += (wide + 1) + m_iTabXDelta; + } + } + else + { + for (int i = 0; i < limit; i++) + { + m_PageTabs[i]->SetVisible(false); + } + } + + // ensure draw order (page drawing over all the tabs except one) + if (_activePage) + { + _activePage->MoveToFront(); + _activePage->Repaint(); + } + if (_activeTab) + { + _activeTab->MoveToFront(); + _activeTab->Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Switches the active panel +//----------------------------------------------------------------------------- +void PropertySheet::OnTabPressed(Panel *panel) +{ + // look for the tab in the list + for (int i = 0; i < m_PageTabs.Count(); i++) + { + if (m_PageTabs[i] == panel) + { + // flip to the new tab + ChangeActiveTab(i); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns the panel associated with index i +// Input : the index of the panel to return +//----------------------------------------------------------------------------- +Panel *PropertySheet::GetPage(int i) +{ + if(i<0 || i>=m_Pages.Count()) + { + return NULL; + } + + return m_Pages[i].page; +} + + +//----------------------------------------------------------------------------- +// Purpose: disables page by name +//----------------------------------------------------------------------------- +void PropertySheet::DisablePage(const char *title) +{ + SetPageEnabled(title, false); +} + +//----------------------------------------------------------------------------- +// Purpose: enables page by name +//----------------------------------------------------------------------------- +void PropertySheet::EnablePage(const char *title) +{ + SetPageEnabled(title, true); +} + +//----------------------------------------------------------------------------- +// Purpose: enabled or disables page by name +//----------------------------------------------------------------------------- +void PropertySheet::SetPageEnabled(const char *title, bool state) +{ + for (int i = 0; i < m_PageTabs.Count(); i++) + { + if (_showTabs) + { + char tmp[50]; + m_PageTabs[i]->GetText(tmp,50); + if (!strnicmp(title,tmp,strlen(tmp))) + { + m_PageTabs[i]->SetEnabled(state); + } + } + else + { + _combo->SetItemEnabled(title,state); + } + } +} + +void PropertySheet::RemoveAllPages() +{ + int c = m_Pages.Count(); + for ( int i = c - 1; i >= 0 ; --i ) + { + RemovePage( m_Pages[ i ].page ); + } +} + +void PropertySheet::DeleteAllPages() +{ + int c = m_Pages.Count(); + for ( int i = c - 1; i >= 0 ; --i ) + { + DeletePage( m_Pages[ i ].page ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: deletes the page associated with panel +// Input : *panel - the panel of the page to remove +//----------------------------------------------------------------------------- +void PropertySheet::RemovePage(Panel *panel) +{ + int location = FindPage( panel ); + if ( location == m_Pages.InvalidIndex() ) + return; + + // Since it's being deleted, don't animate!!! + m_hPreviouslyActivePage = NULL; + _activeTab = NULL; + + // ASSUMPTION = that the number of pages equals number of tabs + if( _showTabs ) + { + m_PageTabs[location]->RemoveActionSignalTarget( this ); + } + // now remove the tab + PageTab *tab = m_PageTabs[ location ]; + m_PageTabs.Remove( location ); + tab->MarkForDeletion(); + + // Remove from page list + m_Pages.Remove( location ); + + // Unparent + panel->SetParent( (Panel *)NULL ); + + if ( _activePage == panel ) + { + _activePage = NULL; + // if this page is currently active, backup to the page before this. + ChangeActiveTab( max( location - 1, 0 ) ); + } + + PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: deletes the page associated with panel +// Input : *panel - the panel of the page to remove +//----------------------------------------------------------------------------- +void PropertySheet::DeletePage(Panel *panel) +{ + Assert( panel ); + RemovePage( panel ); + panel->MarkForDeletion(); +} + +//----------------------------------------------------------------------------- +// Purpose: flips to the new tab, sending out all the right notifications +// flipping to a tab activates the tab. +//----------------------------------------------------------------------------- +void PropertySheet::ChangeActiveTab( int index ) +{ + if ( !m_Pages.IsValidIndex( index ) ) + { + _activeTab = NULL; + if ( m_Pages.Count() > 0 ) + { + _activePage = NULL; + + if ( index < 0 ) + { + ChangeActiveTab( m_Pages.Count() - 1 ); + } + else + { + ChangeActiveTab( 0 ); + } + } + return; + } + + if ( m_Pages[index].page == _activePage ) + { + if ( _activeTab ) + { + _activeTab->RequestFocus(); + } + _tabFocus = true; + return; + } + + int c = m_Pages.Count(); + for ( int i = 0; i < c; ++i ) + { + m_Pages[ i ].page->SetVisible( false ); + } + + m_hPreviouslyActivePage = _activePage; + // notify old page + if (_activePage) + { + ivgui()->PostMessage(_activePage->GetVPanel(), new KeyValues("PageHide"), GetVPanel()); + KeyValues *msg = new KeyValues("PageTabActivated"); + msg->SetPtr("panel", (Panel *)NULL); + ivgui()->PostMessage(_activePage->GetVPanel(), msg, GetVPanel()); + } + if (_activeTab) + { + //_activeTabIndex=index; + _activeTab->SetActive(false); + + // does the old tab have the focus? + _tabFocus = _activeTab->HasFocus(); + } + else + { + _tabFocus = false; + } + + // flip page + _activePage = m_Pages[index].page; + _activeTab = m_PageTabs[index]; + _activeTabIndex = index; + + _activePage->SetVisible(true); + _activePage->MoveToFront(); + + _activeTab->SetVisible(true); + _activeTab->MoveToFront(); + _activeTab->SetActive(true); + + if (_tabFocus) + { + // if a tab already has focused,give the new tab the focus + _activeTab->RequestFocus(); + } + else + { + // otherwise, give the focus to the page + _activePage->RequestFocus(); + } + + if (!_showTabs) + { + _combo->ActivateItemByRow(index); + } + + _activePage->MakeReadyForUse(); + + // transition effect + if (m_flPageTransitionEffectTime) + { + if (m_hPreviouslyActivePage.Get()) + { + // fade out the previous page + GetAnimationController()->RunAnimationCommand(m_hPreviouslyActivePage, "Alpha", 0.0f, 0.0f, m_flPageTransitionEffectTime / 2, AnimationController::INTERPOLATOR_LINEAR); + } + + // fade in the new page + _activePage->SetAlpha(0); + GetAnimationController()->RunAnimationCommand(_activePage, "Alpha", 255.0f, m_flPageTransitionEffectTime / 2, m_flPageTransitionEffectTime / 2, AnimationController::INTERPOLATOR_LINEAR); + } + else + { + if (m_hPreviouslyActivePage.Get()) + { + // no transition, just hide the previous page + m_hPreviouslyActivePage->SetVisible(false); + } + _activePage->SetAlpha( 255 ); + } + + // notify + ivgui()->PostMessage(_activePage->GetVPanel(), new KeyValues("PageShow"), GetVPanel()); + + KeyValues *msg = new KeyValues("PageTabActivated"); + msg->SetPtr("panel", (Panel *)_activeTab); + ivgui()->PostMessage(_activePage->GetVPanel(), msg, GetVPanel()); + + // tell parent + PostActionSignal(new KeyValues("PageChanged")); + + // Repaint + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the panel with the specified hotkey, from the current page +//----------------------------------------------------------------------------- +Panel *PropertySheet::HasHotkey(wchar_t key) +{ + if (!_activePage) + return NULL; + + for (int i = 0; i < _activePage->GetChildCount(); i++) + { + Panel *hot = _activePage->GetChild(i)->HasHotkey(key); + if (hot) + { + return hot; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: catches the opencontextmenu event +//----------------------------------------------------------------------------- +void PropertySheet::OnOpenContextMenu( KeyValues *params ) +{ + // tell parent + KeyValues *kv = params->MakeCopy(); + PostActionSignal( kv ); + Panel *page = reinterpret_cast< Panel * >( params->GetPtr( "page" ) ); + if ( page ) + { + PostMessage( page->GetVPanel(), params->MakeCopy() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses, through tabs. +//----------------------------------------------------------------------------- +void PropertySheet::OnKeyCodePressed(KeyCode code) +{ + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + + if ( ctrl && shift && alt && code == KEY_B ) + { + // enable build mode + EditablePanel *ep = dynamic_cast< EditablePanel * >( GetActivePage() ); + if ( ep ) + { + ep->ActivateBuildMode(); + return; + } + } + + if ( IsKBNavigationEnabled() ) + { + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + switch ( nButtonCode ) + { + // for now left and right arrows just open or close submenus if they are there. + case KEY_RIGHT: + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case KEY_XSTICK2_RIGHT: + case STEAMCONTROLLER_DPAD_RIGHT: + { + ChangeActiveTab(_activeTabIndex+1); + break; + } + case KEY_LEFT: + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case KEY_XSTICK2_LEFT: + case STEAMCONTROLLER_DPAD_LEFT: + { + ChangeActiveTab(_activeTabIndex-1); + break; + } + default: + BaseClass::OnKeyCodePressed(code); + break; + } + } + else + { + BaseClass::OnKeyCodePressed(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called by the associated combo box (if in that mode), changes the current panel +//----------------------------------------------------------------------------- +void PropertySheet::OnTextChanged(Panel *panel,const wchar_t *wszText) +{ + if ( panel == _combo ) + { + wchar_t tabText[30]; + for(int i = 0 ; i < m_PageTabs.Count() ; i++ ) + { + tabText[0] = 0; + m_PageTabs[i]->GetText(tabText,30); + if ( !wcsicmp(wszText,tabText) ) + { + ChangeActiveTab(i); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::OnCommand(const char *command) +{ + // propogate the close command to our parent + if (!stricmp(command, "Close") && GetVParent()) + { + CallParentFunction(new KeyValues("Command", "command", command)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::OnApplyButtonEnable() +{ + // tell parent + PostActionSignal(new KeyValues("ApplyButtonEnable")); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::OnCurrentDefaultButtonSet( vgui::VPANEL defaultButton ) +{ + // forward the message up + if (GetVParent()) + { + KeyValues *msg = new KeyValues("CurrentDefaultButtonSet"); + msg->SetInt("button", ivgui()->PanelToHandle( defaultButton ) ); + PostMessage(GetVParent(), msg); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::OnDefaultButtonSet( VPANEL defaultButton ) +{ + // forward the message up + if (GetVParent()) + { + KeyValues *msg = new KeyValues("DefaultButtonSet"); + msg->SetInt("button", ivgui()->PanelToHandle( defaultButton ) ); + PostMessage(GetVParent(), msg); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PropertySheet::OnFindDefaultButton() +{ + if (GetVParent()) + { + PostMessage(GetVParent(), new KeyValues("FindDefaultButton")); + } +} + +bool PropertySheet::PageHasContextMenu( Panel *page ) const +{ + int pageNum = FindPage( page ); + if ( pageNum == m_Pages.InvalidIndex() ) + return false; + + return m_Pages[ pageNum ].contextMenu; +} + +void PropertySheet::OnPanelDropped( CUtlVector< KeyValues * >& msglist ) +{ + if ( msglist.Count() != 1 ) + { + return; + } + + PropertySheet *sheet = IsDroppingSheet( msglist ); + if ( !sheet ) + { + // Defer to active page + if ( _activePage && _activePage->IsDropEnabled() ) + { + return _activePage->OnPanelDropped( msglist ); + } + return; + } + + KeyValues *data = msglist[ 0 ]; + + Panel *page = reinterpret_cast< Panel * >( data->GetPtr( "propertypage" ) ); + char const *title = data->GetString( "tabname", "" ); + if ( !page || !sheet ) + return; + + // Can only create if sheet was part of a ToolWindow derived object + ToolWindow *tw = dynamic_cast< ToolWindow * >( sheet->GetParent() ); + if ( tw ) + { + IToolWindowFactory *factory = tw->GetToolWindowFactory(); + if ( factory ) + { + bool showContext = sheet->PageHasContextMenu( page ); + sheet->RemovePage( page ); + if ( sheet->GetNumPages() == 0 ) + { + tw->MarkForDeletion(); + } + + AddPage( page, title, NULL, showContext ); + } + } +} + +bool PropertySheet::IsDroppable( CUtlVector< KeyValues * >& msglist ) +{ + if ( !m_bDraggableTabs ) + return false; + + if ( msglist.Count() != 1 ) + { + return false; + } + + int mx, my; + input()->GetCursorPos( mx, my ); + ScreenToLocal( mx, my ); + + int tabHeight = IsSmallTabs() ? m_iTabHeightSmall : m_iTabHeight; + if ( my > tabHeight ) + return false; + + PropertySheet *sheet = IsDroppingSheet( msglist ); + if ( !sheet ) + { + return false; + } + + if ( sheet == this ) + return false; + + return true; +} + +// Mouse is now over a droppable panel +void PropertySheet::OnDroppablePanelPaint( CUtlVector< KeyValues * >& msglist, CUtlVector< Panel * >& dragPanels ) +{ + // Convert this panel's bounds to screen space + int x, y, w, h; + + GetSize( w, h ); + + int tabHeight = IsSmallTabs() ? m_iTabHeightSmall : m_iTabHeight; + h = tabHeight + 4; + + x = y = 0; + LocalToScreen( x, y ); + + surface()->DrawSetColor( GetDropFrameColor() ); + // Draw 2 pixel frame + surface()->DrawOutlinedRect( x, y, x + w, y + h ); + surface()->DrawOutlinedRect( x+1, y+1, x + w-1, y + h-1 ); + + if ( !IsDroppable( msglist ) ) + { + return; + } + + if ( !_showTabs ) + { + return; + } + + // Draw a fake new tab... + + x = 0; + y = 2; + w = 1; + h = tabHeight; + + int last = m_PageTabs.Count(); + if ( last != 0 ) + { + m_PageTabs[ last - 1 ]->GetBounds( x, y, w, h ); + } + + // Compute left edge of "fake" tab + + x += ( w + 1 ); + + // Compute size of new panel + KeyValues *data = msglist[ 0 ]; + char const *text = data->GetString( "tabname", "" ); + Assert( text ); + + PageTab *fakeTab = new PageTab( this, "FakeTab", text, NULL, _tabWidth, NULL, false ); + fakeTab->SetBounds( x, 4, w, tabHeight - 4 ); + fakeTab->SetFont( m_tabFont ); + SETUP_PANEL( fakeTab ); + fakeTab->Repaint(); + surface()->SolveTraverse( fakeTab->GetVPanel(), true ); + surface()->PaintTraverse( fakeTab->GetVPanel() ); + delete fakeTab; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void PropertySheet::SetKBNavigationEnabled( bool state ) +{ + m_bKBNavigationEnabled = state; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PropertySheet::IsKBNavigationEnabled() const +{ + return m_bKBNavigationEnabled; +} diff --git a/vgui2/vgui_controls/QueryBox.cpp b/vgui2/vgui_controls/QueryBox.cpp new file mode 100644 index 0000000..4422699 --- /dev/null +++ b/vgui2/vgui_controls/QueryBox.cpp @@ -0,0 +1,222 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// This class is a message box that has two buttons, ok and cancel instead of +// just the ok button of a message box. We use a message box class for the ok button +// and implement another button here. +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/KeyCode.h> + +#include <vgui_controls/QueryBox.h> +#include <vgui_controls/TextImage.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +QueryBox::QueryBox(const char *title, const char *queryText, vgui::Panel *parent) : MessageBox(title, queryText,parent) +{ + SetDeleteSelfOnClose(true); + m_pCancelButton = new Button(this, "CancelButton", "#QueryBox_Cancel"); + m_pCancelButton->SetCommand("Cancel"); + m_pOkButton->SetCommand("OK"); + m_pCancelCommand = NULL; + m_pOkCommand = NULL; + + m_pOkButton->SetTabPosition(1); + m_pCancelButton->SetTabPosition(2); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +QueryBox::QueryBox(const wchar_t *wszTitle, const wchar_t *wszQueryText,vgui::Panel *parent) : MessageBox(wszTitle, wszQueryText,parent) +{ + SetDeleteSelfOnClose(true); + m_pCancelButton = new Button(this, "CancelButton", "#QueryBox_Cancel"); + m_pCancelButton->SetCommand("Cancel"); + m_pOkButton->SetCommand("OK"); + m_pCancelCommand = NULL; + m_pOkCommand = NULL; + + m_pOkButton->SetTabPosition(1); + m_pCancelButton->SetTabPosition(2); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +QueryBox::~QueryBox() +{ + delete m_pCancelButton; + + if ( m_pOkCommand ) + { + m_pOkCommand->deleteThis(); + } + if ( m_pCancelCommand ) + { + m_pCancelCommand->deleteThis(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Layout the window for drawing +//----------------------------------------------------------------------------- +void QueryBox::PerformLayout() +{ + BaseClass::PerformLayout(); + + int boxWidth, boxTall; + GetSize(boxWidth, boxTall); + + int x, y, wide, tall; + GetClientArea(x, y, wide, tall); + wide += x; + tall += y; + + int oldWide, oldTall; + m_pCancelButton->GetSize(oldWide, oldTall); + + int btnWide, btnTall; + m_pCancelButton->GetContentSize(btnWide, btnTall); + btnWide = max(oldWide, btnWide + 10); + btnTall = max(oldTall, btnTall + 10); + m_pCancelButton->SetSize(btnWide, btnTall); + +//nt boxWidth, boxTall; + GetSize(boxWidth, boxTall); +// wide = max(wide, btnWide * 2 + 100); +// SetSize(wide, tall); + + m_pOkButton->SetPos((wide/2)-(m_pOkButton->GetWide())-1 + x, tall - m_pOkButton->GetTall() - 15); + m_pCancelButton->SetPos((wide/2) + x+16, tall - m_pCancelButton->GetTall() - 15); + +} + +//----------------------------------------------------------------------------- +// Purpose: Handles command text from the buttons +// Deletes self when closed +//----------------------------------------------------------------------------- +void QueryBox::OnCommand(const char *command) +{ + if (!stricmp(command, "OK")) + { + OnCommand("Close"); + + if ( m_pOkCommand ) + { + PostActionSignal(m_pOkCommand->MakeCopy()); + } + } + else if (!stricmp(command, "Cancel")) + { + OnCommand("Close"); + + if (m_pCancelCommand) + { + PostActionSignal(m_pCancelCommand->MakeCopy()); + } + } + + BaseClass::OnCommand(command); + +} + +//----------------------------------------------------------------------------- +// Purpose: Set the keyvalues to send when ok button is hit +//----------------------------------------------------------------------------- +void QueryBox::SetOKCommand(KeyValues *keyValues) +{ + if ( m_pOkCommand ) + { + m_pOkCommand->deleteThis(); + } + + m_pOkCommand = keyValues; +} + +//----------------------------------------------------------------------------- +// Purpose: Set a value of the ok command +//----------------------------------------------------------------------------- +void QueryBox::SetOKCommandValue(const char *keyName, int value) +{ + if ( !m_pOkCommand ) + { + m_pOkCommand = new KeyValues("Command"); + } + + m_pOkCommand->SetInt(keyName, value); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the keyvalues to send when the cancel button is hit +//----------------------------------------------------------------------------- +void QueryBox::SetCancelCommand(KeyValues *keyValues) +{ + if ( m_pCancelCommand ) + { + m_pCancelCommand->deleteThis(); + } + + m_pCancelCommand = keyValues; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the cancel button text +//----------------------------------------------------------------------------- +void QueryBox::SetCancelButtonText(const char* buttonText) +{ + m_pCancelButton->SetText(buttonText); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the cancel button text +//----------------------------------------------------------------------------- +void QueryBox::SetCancelButtonText(const wchar_t* wszButtonText) +{ + m_pCancelButton->SetText(wszButtonText); + InvalidateLayout(); +} + +void QueryBox::OnKeyCodeTyped( KeyCode code ) +{ + if ( code == KEY_ESCAPE ) + { + OnCommand("Cancel"); + } + else + { + Frame::OnKeyCodeTyped(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void QueryBox::OnKeyCodePressed( KeyCode code ) +{ + if ( code == KEY_XBUTTON_B ) + { + OnCommand("Cancel"); + } + else + { + Frame::OnKeyCodePressed(code); + } +} + + + diff --git a/vgui2/vgui_controls/RadioButton.cpp b/vgui2/vgui_controls/RadioButton.cpp new file mode 100644 index 0000000..8cb1e79 --- /dev/null +++ b/vgui2/vgui_controls/RadioButton.cpp @@ -0,0 +1,425 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdarg.h> +#include <stdio.h> + +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/IScheme.h> +#include <vgui/ISystem.h> +#include <vgui/IVGui.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> + +#include <vgui_controls/FocusNavGroup.h> +#include <vgui_controls/Image.h> +#include <vgui_controls/RadioButton.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/Controls.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +enum direction +{ + UP = -1, + DOWN = 1, +}; + +//----------------------------------------------------------------------------- +// Purpose: Check box image +//----------------------------------------------------------------------------- +void RadioImage::Paint() +{ + DrawSetTextFont(GetFont()); + + // draw background + if (_radioButton->IsEnabled()) + { + DrawSetTextColor(_bgColor); + } + else + { + DrawSetTextColor(_radioButton->GetBgColor()); + } + DrawPrintChar(0, 1, 'n'); + + // draw border circl + DrawSetTextColor(_borderColor1); + DrawPrintChar(0, 1, 'j'); + DrawSetTextColor(_borderColor2); + DrawPrintChar(0, 1, 'k'); + + // draw selected check + if (_radioButton->IsSelected()) + { + DrawSetTextColor(_checkColor); + DrawPrintChar(0, 1, 'h'); + } +} + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( RadioButton, RadioButton ); + +//----------------------------------------------------------------------------- +// Purpose: Create a radio button. +//----------------------------------------------------------------------------- +RadioButton::RadioButton(Panel *parent, const char *panelName, const char *text) : ToggleButton(parent, panelName, text) +{ + SetContentAlignment(a_west); + + // create the image + _radioBoxImage = new RadioImage(this); + + _oldTabPosition = 0; + _subTabPosition = 0; + + SetTextImageIndex(1); + SetImageAtIndex(0, _radioBoxImage, 0); + + SetButtonActivationType(ACTIVATE_ONPRESSED); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +RadioButton::~RadioButton() +{ + delete _radioBoxImage; +} + +//----------------------------------------------------------------------------- +// Purpose: Apply resource file scheme. +//----------------------------------------------------------------------------- +void RadioButton::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + _radioBoxImage->_bgColor = GetSchemeColor("CheckButton.BgColor", Color(150, 150, 150, 0), pScheme); + _radioBoxImage->_borderColor1 = GetSchemeColor("CheckButton.Border1", Color(20, 20, 20, 0), pScheme); + _radioBoxImage->_borderColor2 = GetSchemeColor("CheckButton.Border2", Color(90, 90, 90, 0), pScheme); + _radioBoxImage->_checkColor = GetSchemeColor("CheckButton.Check", Color(20, 20, 20, 0), pScheme); + + SetFgColor(GetSchemeColor("RadioButton.TextColor", pScheme)); + _selectedFgColor = GetSchemeColor("RadioButton.SelectedTextColor", GetSchemeColor("ControlText", pScheme), pScheme); + + SetDefaultColor( GetFgColor(), GetBgColor() ); + + SetArmedColor( GetSchemeColor("RadioButton.ArmedTextColor", pScheme), GetButtonArmedBgColor() ); + + SetContentAlignment(a_west); + + // reloading the scheme wipes out lists of images + HFont hFont = pScheme->GetFont("MarlettSmall", IsProportional()); + if ( hFont == INVALID_FONT ) + { + // fallback to Marlett if MarlettSmall isn't found + hFont = pScheme->GetFont("Marlett", IsProportional()); + } + _radioBoxImage->SetFont( hFont ); + _radioBoxImage->ResizeImageToContent(); + SetImageAtIndex(0, _radioBoxImage, 0); + + // don't draw a background + SetPaintBackgroundEnabled(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the border style of the button, Radio buttons have no border +//----------------------------------------------------------------------------- +IBorder *RadioButton::GetBorder(bool depressed, bool armed, bool selected, bool keyfocus) +{ + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the tab position of the radio button with the set of radio buttons +// A group of RadioButtons must have the same TabPosition, with [1, n] subtabpositions +//----------------------------------------------------------------------------- +int RadioButton::GetSubTabPosition() +{ + return _subTabPosition; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the tab position of the radio button with the set of radio buttons +// A group of RadioButtons must have the same TabPosition, with [1, n] subtabpositions +//----------------------------------------------------------------------------- +void RadioButton::SetSubTabPosition(int position) +{ + _subTabPosition = position; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the RadioButton's real tab position (its Panel one changes) +//----------------------------------------------------------------------------- +int RadioButton::GetRadioTabPosition() +{ + return _oldTabPosition; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the radio button checked. When a radio button is checked, a +// message is sent to all other radio buttons in the same group so +// they will become unchecked. +//----------------------------------------------------------------------------- +void RadioButton::SetSelected(bool state) +{ + InternalSetSelected( state, true ); +} + +void RadioButton::InternalSetSelected(bool state, bool bFireEvents) +{ + if (state == true) + { + if (!IsEnabled()) + return; + + // restore our tab position + SetTabPosition(_oldTabPosition); + + // Should we send notifications? + if ( bFireEvents ) + { + // send a message + KeyValues *msg = new KeyValues("RadioButtonChecked"); + msg->SetPtr("panel", this); + msg->SetInt("tabposition", _oldTabPosition); + + // send a message to all other panels on the same level as heirarchy, + // so that other radio buttons know to shut off + VPANEL radioParent = GetVParent(); + if (radioParent) + { + for (int i = 0; i < ipanel()->GetChildCount(radioParent); i++) + { + VPANEL child = ipanel()->GetChild(radioParent, i); + if (child != GetVPanel()) + { + ivgui()->PostMessage(child, msg->MakeCopy(), GetVPanel()); + } + } + } + + RequestFocus(); + PostActionSignal(msg); + } + } + else + { + // remove ourselves from the tab order + if (GetTabPosition()) + { + _oldTabPosition = GetTabPosition(); + } + SetTabPosition(0); + } + + InvalidateLayout(); + Repaint(); + + ToggleButton::SetSelected(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the selection state without firing any events +//----------------------------------------------------------------------------- +void RadioButton::SilentSetSelected(bool state) +{ + InternalSetSelected( state, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set up the text color before doing normal layout +//----------------------------------------------------------------------------- +void RadioButton::PerformLayout() +{ + if (IsSelected()) + { + SetFgColor(_selectedFgColor); + } + else + { + SetFgColor(GetButtonFgColor()); + } + + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Apply resource settings including button state and text color. +//----------------------------------------------------------------------------- +void RadioButton::ApplySettings(KeyValues *inResourceData) +{ + ToggleButton::ApplySettings(inResourceData); + SetTextColorState(CS_NORMAL); + + _subTabPosition = inResourceData->GetInt("SubTabPosition"); + _oldTabPosition = inResourceData->GetInt("TabPosition"); + SetImageAtIndex(0, _radioBoxImage, 0); +} + +//----------------------------------------------------------------------------- +// Purpose: Get resource settings including button state, text color, and subTabPosition +//----------------------------------------------------------------------------- +void RadioButton::GetSettings(KeyValues *outResourceData) +{ + ToggleButton::GetSettings(outResourceData); + outResourceData->SetInt("SubTabPosition", _subTabPosition); + outResourceData->SetInt("TabPosition", GetRadioTabPosition()); +} + +//----------------------------------------------------------------------------- +// Purpose: Describe editing details +//----------------------------------------------------------------------------- +const char *RadioButton::GetDescription( void ) +{ + static char buf[1024]; + Q_snprintf(buf, sizeof(buf), "%s, int SubTabPosition", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: When a radio button is checked, all other radio buttons +// in the same group become unchecked. +//----------------------------------------------------------------------------- +void RadioButton::OnRadioButtonChecked(int tabPosition) +{ + // make sure we're in the same tab group + if (tabPosition != _oldTabPosition) + return; + + // wouldn't be sent to us from ourselves, so another radio button has taken over + SetSelected(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the selection rectangle +//----------------------------------------------------------------------------- +void RadioButton::Paint() +{ + BaseClass::Paint(); + + /* + if (HasFocus()) + { + int tx0, ty0, tx1, ty1; + _textImage->GetPos(tx0, ty0); + _textImage->GetSize(tx1, ty1); + DrawFocusBorder(tx0, ty0, tx0 + tx1, ty0 + ty1); + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RadioButton::DoClick() +{ + SetSelected(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle arrow key movement +//----------------------------------------------------------------------------- +void RadioButton::OnKeyCodeTyped(KeyCode code) +{ + switch (code) + { + case KEY_ENTER: + case KEY_SPACE: + { + if (!IsSelected()) + { + SetSelected(true); + } + else + { + Panel::OnKeyCodeTyped(code); + } + } + break; + + case KEY_DOWN: + case KEY_RIGHT: + { + RadioButton *bestRadio = FindBestRadioButton( DOWN ); + if (bestRadio) + { + bestRadio->SetSelected(true); + } + } + break; + case KEY_UP: + case KEY_LEFT: + { + RadioButton *bestRadio = FindBestRadioButton( UP ); + if (bestRadio) + { + bestRadio->SetSelected(true); + } + } + break; + + default: + BaseClass::OnKeyCodeTyped(code); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find the correct radio button to move to. +// Input : direction - the direction we are moving, up or down. +//----------------------------------------------------------------------------- +RadioButton *RadioButton::FindBestRadioButton(int direction) +{ + RadioButton *bestRadio = NULL; + int highestRadio = 0; + Panel *pr = GetParent(); + if (pr) + { + // find the radio button to go to next + for (int i = 0; i < pr->GetChildCount(); i++) + { + RadioButton *child = dynamic_cast<RadioButton *>(pr->GetChild(i)); + if (child && child->GetRadioTabPosition() == _oldTabPosition) + { + if (child->GetSubTabPosition() == _subTabPosition + direction) + { + bestRadio = child; + break; + } + if ( (child->GetSubTabPosition() == 0) && (direction == DOWN) ) + { + bestRadio = child; + continue; + } + else if ( (child->GetSubTabPosition() > highestRadio) && (direction == UP) ) + { + bestRadio = child; + highestRadio = bestRadio->GetSubTabPosition(); + continue; + } + if (!bestRadio) + { + bestRadio = child; + } + } + } + + if (bestRadio) + { + bestRadio->RequestFocus(); + } + + InvalidateLayout(); + Repaint(); + } + + return bestRadio; +} diff --git a/vgui2/vgui_controls/RichText.cpp b/vgui2/vgui_controls/RichText.cpp new file mode 100644 index 0000000..b3c3833 --- /dev/null +++ b/vgui2/vgui_controls/RichText.cpp @@ -0,0 +1,2747 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/pch_vgui_controls.h" +#include "vgui/ILocalize.h" + +// memdbgon must be the last include file in a .cpp file +#include "tier0/memdbgon.h" + +enum +{ + MAX_BUFFER_SIZE = 999999, // maximum size of text buffer + DRAW_OFFSET_X = 3, + DRAW_OFFSET_Y = 1, +}; + +using namespace vgui; + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +namespace vgui +{ + +//#define DRAW_CLICK_PANELS + +//----------------------------------------------------------------------------- +// Purpose: Panel used for clickable URL's +//----------------------------------------------------------------------------- +class ClickPanel : public Panel +{ + DECLARE_CLASS_SIMPLE( ClickPanel, Panel ); + +public: + ClickPanel(Panel *parent) + { + _viewIndex = 0; + _textIndex = 0; + SetParent(parent); + AddActionSignalTarget(parent); + + SetCursor(dc_hand); + + SetPaintBackgroundEnabled(false); + SetPaintEnabled(false); +// SetPaintAppearanceEnabled(false); + +#if defined( DRAW_CLICK_PANELS ) + SetPaintEnabled(true); +#endif + } + + void SetTextIndex( int linkStartIndex, int viewStartIndex ) + { + _textIndex = linkStartIndex; + _viewIndex = viewStartIndex; + } + +#if defined( DRAW_CLICK_PANELS ) + virtual void Paint() + { + surface()->DrawSetColor( Color( 255, 0, 0, 255 ) ); + surface()->DrawOutlinedRect( 0, 0, GetWide(), GetTall() ); + } +#endif + + int GetTextIndex() + { + return _textIndex; + } + + int GetViewTextIndex() + { + return _viewIndex; + } + + void OnMousePressed(MouseCode code) + { + if (code == MOUSE_LEFT) + { + PostActionSignal(new KeyValues("ClickPanel", "index", _textIndex)); + } + else + { + GetParent()->OnMousePressed( code ); + } + } + +private: + int _textIndex; + int _viewIndex; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Panel used only to draw the interior border region +//----------------------------------------------------------------------------- +class RichTextInterior : public Panel +{ + DECLARE_CLASS_SIMPLE( RichTextInterior, Panel ); + +public: + RichTextInterior( RichText *pParent, const char *pchName ) : BaseClass( pParent, pchName ) + { + SetKeyBoardInputEnabled( false ); + SetMouseInputEnabled( false ); + SetPaintBackgroundEnabled( false ); + SetPaintEnabled( false ); + m_pRichText = pParent; + } + +/* virtual IAppearance *GetAppearance() + { + if ( m_pRichText->IsScrollbarVisible() ) + return m_pAppearanceScrollbar; + + return BaseClass::GetAppearance(); + }*/ + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); +// m_pAppearanceScrollbar = FindSchemeAppearance( pScheme, "scrollbar_visible" ); + } + +private: + RichText *m_pRichText; +// IAppearance *m_pAppearanceScrollbar; +}; + +}; // namespace vgui + +DECLARE_BUILD_FACTORY( RichText ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +RichText::RichText(Panel *parent, const char *panelName) : BaseClass(parent, panelName) +{ + m_bAllTextAlphaIsZero = false; + _font = INVALID_FONT; + m_hFontUnderline = INVALID_FONT; + + m_bRecalcLineBreaks = true; + m_pszInitialText = NULL; + _cursorPos = 0; + _mouseSelection = false; + _mouseDragSelection = false; + _vertScrollBar = new ScrollBar(this, "ScrollBar", true); + _vertScrollBar->AddActionSignalTarget(this); + _recalcSavedRenderState = true; + _maxCharCount = (64 * 1024); + AddActionSignalTarget(this); + m_pInterior = new RichTextInterior( this, NULL ); + + //a -1 for _select[0] means that the selection is empty + _select[0] = -1; + _select[1] = -1; + m_pEditMenu = NULL; + + SetCursor(dc_ibeam); + + //position the cursor so it is at the end of the text + GotoTextEnd(); + + // set default foreground color to black + _defaultTextColor = Color(0, 0, 0, 0); + + // initialize the line break array + InvalidateLineBreakStream(); + + if ( IsProportional() ) + { + int width, height; + int sw,sh; + surface()->GetProportionalBase( width, height ); + surface()->GetScreenSize(sw, sh); + + _drawOffsetX = static_cast<int>( static_cast<float>( DRAW_OFFSET_X )*( static_cast<float>( sw )/ static_cast<float>( width ))); + _drawOffsetY = static_cast<int>( static_cast<float>( DRAW_OFFSET_Y )*( static_cast<float>( sw )/ static_cast<float>( width ))); + } + else + { + _drawOffsetX = DRAW_OFFSET_X; + _drawOffsetY = DRAW_OFFSET_Y; + } + + // add a basic format string + TFormatStream stream; + stream.color = _defaultTextColor; + stream.fade.flFadeStartTime = 0.0f; + stream.fade.flFadeLength = -1.0f; + stream.pixelsIndent = 0; + stream.textStreamIndex = 0; + stream.textClickable = false; + m_FormatStream.AddToTail(stream); + + m_bResetFades = false; + m_bInteractive = true; + m_bUnusedScrollbarInvis = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +RichText::~RichText() +{ + delete [] m_pszInitialText; + delete m_pEditMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RichText::SetDrawOffsets( int ofsx, int ofsy ) +{ + _drawOffsetX = ofsx; + _drawOffsetY = ofsy; +} + +//----------------------------------------------------------------------------- +// Purpose: sets it as drawing text only - used for embedded RichText control into other text drawing situations +//----------------------------------------------------------------------------- +void RichText::SetDrawTextOnly() +{ + SetDrawOffsets( 0, 0 ); + SetPaintBackgroundEnabled( false ); +// SetPaintAppearanceEnabled( false ); + SetPostChildPaintEnabled( false ); + m_pInterior->SetVisible( false ); + SetVerticalScrollbar( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: configures colors +//----------------------------------------------------------------------------- +void RichText::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + _font = pScheme->GetFont("Default", IsProportional() ); + m_hFontUnderline = pScheme->GetFont("DefaultUnderline", IsProportional() ); + + SetFgColor(GetSchemeColor("RichText.TextColor", pScheme)); + SetBgColor(GetSchemeColor("RichText.BgColor", pScheme)); + + _selectionTextColor = GetSchemeColor("RichText.SelectedTextColor", GetFgColor(), pScheme); + _selectionColor = GetSchemeColor("RichText.SelectedBgColor", pScheme); + + if ( Q_strlen( pScheme->GetResourceString( "RichText.InsetX" ) ) ) + { + SetDrawOffsets( atoi( pScheme->GetResourceString( "RichText.InsetX" ) ), atoi( pScheme->GetResourceString( "RichText.InsetY" ) ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: if the default format color isn't set then set it +//----------------------------------------------------------------------------- +void RichText::SetFgColor( Color color ) +{ + // Replace default format color if + // the stream is empty and the color is the default ( or the previous FgColor ) + if ( m_FormatStream.Size() == 1 && + ( m_FormatStream[0].color == _defaultTextColor || m_FormatStream[0].color == GetFgColor() ) ) + { + m_FormatStream[0].color = color; + } + + BaseClass::SetFgColor( color ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sends a message if the data has changed +// Turns off any selected text in the window if we are not using the edit menu +//----------------------------------------------------------------------------- +void RichText::OnKillFocus() +{ + // check if we clicked the right mouse button or if it is down + bool mouseRightClicked = input()->WasMousePressed(MOUSE_RIGHT); + bool mouseRightUp = input()->WasMouseReleased(MOUSE_RIGHT); + bool mouseRightDown = input()->IsMouseDown(MOUSE_RIGHT); + + if (mouseRightClicked || mouseRightDown || mouseRightUp ) + { + // get the start and ends of the selection area + int start, end; + if (GetSelectedRange(start, end)) // we have selected text + { + // see if we clicked in the selection area + int startX, startY; + CursorToPixelSpace(start, startX, startY); + int endX, endY; + CursorToPixelSpace(end, endX, endY); + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + ScreenToLocal(cursorX, cursorY); + + // check the area vertically + // we need to handle the horizontal edge cases eventually + int fontTall = GetLineHeight(); + endY = endY + fontTall; + if ((startY < cursorY) && (endY > cursorY)) + { + // if we clicked in the selection area, leave the text highlighted + return; + } + } + } + + // clear any selection + SelectNone(); + + // chain + BaseClass::OnKillFocus(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Wipe line breaks after the size of a panel has been changed +//----------------------------------------------------------------------------- +void RichText::OnSizeChanged( int wide, int tall ) +{ + BaseClass::OnSizeChanged( wide, tall ); + + // blow away the line breaks list + _invalidateVerticalScrollbarSlider = true; + InvalidateLineBreakStream(); + InvalidateLayout(); + + if ( _vertScrollBar->IsVisible() ) + { + _vertScrollBar->MakeReadyForUse(); + m_pInterior->SetBounds( 0, 0, wide - _vertScrollBar->GetWide(), tall ); + } + else + { + m_pInterior->SetBounds( 0, 0, wide, tall ); + } +} + + +const wchar_t *RichText::ResolveLocalizedTextAndVariables( char const *pchLookup, wchar_t *outbuf, size_t outbufsizeinbytes ) +{ + if ( pchLookup[ 0 ] == '#' ) + { + // try lookup in localization tables + StringIndex_t index = g_pVGuiLocalize->FindIndex( pchLookup + 1 ); + if ( index == INVALID_LOCALIZE_STRING_INDEX ) + { +/* // if it's not found, maybe it's a special expanded variable - look for an expansion + char rgchT[MAX_PATH]; + + // get the variables + KeyValues *variables = GetDialogVariables_R(); + if ( variables ) + { + // see if any are any special vars to put in + for ( KeyValues *pkv = variables->GetFirstSubKey(); pkv != NULL; pkv = pkv->GetNextKey() ) + { + if ( !Q_strncmp( pkv->GetName(), "$", 1 ) ) + { + // make a new lookup, with this key appended + Q_snprintf( rgchT, sizeof( rgchT ), "%s%s=%s", pchLookup, pkv->GetName(), pkv->GetString() ); + index = localize()->FindIndex( rgchT ); + break; + } + } + } + */ + } + + // see if we have a valid string + if ( index != INVALID_LOCALIZE_STRING_INDEX ) + { + wchar_t *format = g_pVGuiLocalize->GetValueByIndex( index ); + Assert( format ); + if ( format ) + { + /*// Try and substitute variables if any + KeyValues *variables = GetDialogVariables_R(); + if ( variables ) + { + localize()->ConstructString( outbuf, outbufsizeinbytes, index, variables ); + return outbuf; + }*/ + } + V_wcsncpy( outbuf, format, outbufsizeinbytes ); + return outbuf; + } + } + + Q_UTF8ToUnicode( pchLookup, outbuf, outbufsizeinbytes ); + return outbuf; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text array +// Using this function will cause all lineBreaks to be discarded. +// This is because this fxn replaces the contents of the text buffer. +// For modifying large buffers use insert functions. +//----------------------------------------------------------------------------- +void RichText::SetText(const char *text) +{ + if (!text) + { + text = ""; + } + + wchar_t unicode[1024]; + + if (text[0] == '#') + { + ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) ); + SetText( unicode ); + return; + } + + // convert to unicode + Q_UTF8ToUnicode(text, unicode, sizeof(unicode)); + SetText(unicode); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RichText::SetText(const wchar_t *text) +{ + // reset the formatting stream + m_FormatStream.RemoveAll(); + TFormatStream stream; + stream.color = GetFgColor(); + stream.fade.flFadeLength = -1.0f; + stream.fade.flFadeStartTime = 0.0f; + stream.pixelsIndent = 0; + stream.textStreamIndex = 0; + stream.textClickable = false; + m_FormatStream.AddToTail(stream); + + // set the new text stream + m_TextStream.RemoveAll(); + if ( text && *text ) + { + int textLen = wcslen(text) + 1; + m_TextStream.EnsureCapacity(textLen); + for(int i = 0; i < textLen; i++) + { + m_TextStream.AddToTail(text[i]); + } + } + GotoTextStart(); + SelectNone(); + + // blow away the line breaks list + InvalidateLineBreakStream(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Given cursor's position in the text buffer, convert it to +// the local window's x and y pixel coordinates +// Input: cursorPos: cursor index +// Output: cx, cy, the corresponding coords in the local window +//----------------------------------------------------------------------------- +void RichText::CursorToPixelSpace(int cursorPos, int &cx, int &cy) +{ + int yStart = _drawOffsetY; + int x = _drawOffsetX, y = yStart; + _pixelsIndent = 0; + int lineBreakIndexIndex = 0; + + for (int i = GetStartDrawIndex(lineBreakIndexIndex); i < m_TextStream.Count(); i++) + { + wchar_t ch = m_TextStream[i]; + + // if we've found the position, break + if (cursorPos == i) + { + // if we've passed a line break go to that + if (m_LineBreaks[lineBreakIndexIndex] == i) + { + // add another line + AddAnotherLine(x, y); + lineBreakIndexIndex++; + } + break; + } + + // if we've passed a line break go to that + if (m_LineBreaks[lineBreakIndexIndex] == i) + { + // add another line + AddAnotherLine(x, y); + lineBreakIndexIndex++; + } + + // add to the current position + x += surface()->GetCharacterWidth(_font, ch); + } + + cx = x; + cy = y; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts local pixel coordinates to an index in the text buffer +//----------------------------------------------------------------------------- +int RichText::PixelToCursorSpace(int cx, int cy) +{ + int fontTall = GetLineHeight(); + + // where to start reading + int yStart = _drawOffsetY; + int x = _drawOffsetX, y = yStart; + _pixelsIndent = 0; + int lineBreakIndexIndex = 0; + + int startIndex = GetStartDrawIndex(lineBreakIndexIndex); + if (_recalcSavedRenderState) + { + RecalculateDefaultState(startIndex); + } + + _pixelsIndent = m_CachedRenderState.pixelsIndent; + _currentTextClickable = m_CachedRenderState.textClickable; + TRenderState renderState = m_CachedRenderState; + + bool onRightLine = false; + int i; + for (i = startIndex; i < m_TextStream.Count(); i++) + { + wchar_t ch = m_TextStream[i]; + + renderState.x = x; + if ( UpdateRenderState( i, renderState ) ) + { + x = renderState.x; + } + + // if we are on the right line but off the end of if put the cursor at the end of the line + if (m_LineBreaks[lineBreakIndexIndex] == i) + { + // add another line + AddAnotherLine(x, y); + lineBreakIndexIndex++; + + if (onRightLine) + break; + } + + // check to see if we're on the right line + if (cy < yStart) + { + // cursor is above panel + onRightLine = true; + } + else if (cy >= y && (cy < (y + fontTall + _drawOffsetY))) + { + onRightLine = true; + } + + int wide = surface()->GetCharacterWidth(_font, ch); + + // if we've found the position, break + if (onRightLine) + { + if (cx > GetWide()) // off right side of window + { + } + else if (cx < (_drawOffsetX + renderState.pixelsIndent) || cy < yStart) // off left side of window + { + // Msg( "PixelToCursorSpace() off left size, returning %d '%c'\n", i, m_TextStream[i] ); + return i; // move cursor one to left + } + + if (cx >= x && cx < (x + wide)) + { + // check which side of the letter they're on + if (cx < (x + (wide * 0.5))) // left side + { + // Msg( "PixelToCursorSpace() on the left size, returning %d '%c'\n", i, m_TextStream[i] ); + return i; + } + else // right side + { + // Msg( "PixelToCursorSpace() on the right size, returning %d '%c'\n", i + 1, m_TextStream[i + 1] ); + return i + 1; + } + } + } + x += wide; + } + + // Msg( "PixelToCursorSpace() never hit, returning %d\n", i ); + return i; +} + +//----------------------------------------------------------------------------- +// Purpose: Draws a string of characters in the panel +// Input: iFirst - Index of the first character to draw +// iLast - Index of the last character to draw +// renderState - Render state to use +// font- font to use +// Output: returns the width of the character drawn +//----------------------------------------------------------------------------- +int RichText::DrawString(int iFirst, int iLast, TRenderState &renderState, HFont font) +{ +// VPROF( "RichText::DrawString" ); + + // Calculate the render size + int fontTall = surface()->GetFontTall(font); + // BUGBUG John: This won't exactly match the rendered size + int charWide = 0; + for ( int i = iFirst; i <= iLast; i++ ) + { + wchar_t ch = m_TextStream[i]; +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if ( i > 0 ) + chBefore = m_TextStream[i-1]; + if ( i < iLast ) + chAfter = m_TextStream[i+1]; + float flWide = 0.0f, flabcA = 0.0f; + surface()->GetKernedCharWidth(font, ch, chBefore, chAfter, flWide, flabcA); + if ( ch == L' ' ) + flWide = ceil( flWide ); + charWide += floor( flWide + 0.6 ); +#else + charWide += surface()->GetCharacterWidth(font, ch); +#endif + } + + // draw selection, if any + int selection0 = -1, selection1 = -1; + GetSelectedRange(selection0, selection1); + + if (iFirst >= selection0 && iFirst < selection1) + { + // draw background selection color + surface()->DrawSetColor(_selectionColor); + surface()->DrawFilledRect(renderState.x, renderState.y, renderState.x + charWide, renderState.y + 1 + fontTall); + + // reset text color + surface()->DrawSetTextColor(_selectionTextColor); + m_bAllTextAlphaIsZero = false; + } + else + { + surface()->DrawSetTextColor(renderState.textColor); + } + + if ( renderState.textColor.a() != 0 ) + { + m_bAllTextAlphaIsZero = false; + surface()->DrawSetTextPos(renderState.x, renderState.y); + surface()->DrawPrintText(&m_TextStream[iFirst], iLast - iFirst + 1); + } + + return charWide; +} + +//----------------------------------------------------------------------------- +// Purpose: Finish drawing url +//----------------------------------------------------------------------------- +void RichText::FinishingURL(int x, int y) +{ + // finishing URL + if ( _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ) + { + ClickPanel *clickPanel = _clickableTextPanels[ _clickableTextIndex ]; + int px, py; + clickPanel->GetPos(px, py); + int fontTall = GetLineHeight(); + clickPanel->SetSize( MAX( x - px, 6 ), y - py + fontTall ); + clickPanel->SetVisible(true); + + // if we haven't actually advanced any, step back and ignore this one + // this is probably a data input problem though, need to find root cause + if ( x - px <= 0 ) + { + --_clickableTextIndex; + clickPanel->SetVisible(false); + } + } +} + +void RichText::CalculateFade( TRenderState &renderState ) +{ + if ( m_FormatStream.IsValidIndex( renderState.formatStreamIndex ) ) + { + if ( m_bResetFades == false ) + { + if ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength != -1.0f ) + { + float frac = ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeStartTime - system()->GetCurrentTime() ) / m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength; + + int alpha = frac * m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha; + alpha = clamp( alpha, 0, m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha ); + + renderState.textColor.SetColor( renderState.textColor.r(), renderState.textColor.g(), renderState.textColor.b(), alpha ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the text in the panel +//----------------------------------------------------------------------------- +void RichText::Paint() +{ + // Assume the worst + m_bAllTextAlphaIsZero = true; + + HFont hFontCurrent = _font; + + // hide all the clickable panels until we know where they are to reside + for (int j = 0; j < _clickableTextPanels.Count(); j++) + { + _clickableTextPanels[j]->SetVisible(false); + } + + if ( !HasText() ) + return; + + int wide, tall; + GetSize( wide, tall ); + + int lineBreakIndexIndex = 0; + int startIndex = GetStartDrawIndex(lineBreakIndexIndex); + _currentTextClickable = false; + + _clickableTextIndex = GetClickableTextIndexStart(startIndex); + + // recalculate and cache the render state at the render start + if (_recalcSavedRenderState) + { + RecalculateDefaultState(startIndex); + } + // copy off the cached render state + TRenderState renderState = m_CachedRenderState; + + _pixelsIndent = m_CachedRenderState.pixelsIndent; + _currentTextClickable = m_CachedRenderState.textClickable; + + renderState.textClickable = _currentTextClickable; + + if ( m_FormatStream.IsValidIndex( renderState.formatStreamIndex ) ) + renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color; + + CalculateFade( renderState ); + + renderState.formatStreamIndex++; + + if ( _currentTextClickable ) + { + _clickableTextIndex = startIndex; + } + + // where to start drawing + renderState.x = _drawOffsetX + _pixelsIndent; + renderState.y = _drawOffsetY; + + // draw the text + int selection0 = -1, selection1 = -1; + GetSelectedRange(selection0, selection1); + + surface()->DrawSetTextFont( hFontCurrent ); + + for (int i = startIndex; i < m_TextStream.Count() && renderState.y < tall; ) + { + // 1. + // Update our current render state based on the formatting and color streams, + // this has to happen if it's our very first iteration, or if we are actually changing + // state. + int nXBeforeStateChange = renderState.x; + if ( UpdateRenderState(i, renderState) || i == startIndex ) + { + // check for url state change + if (renderState.textClickable != _currentTextClickable) + { + if (renderState.textClickable) + { + // entering new URL + _clickableTextIndex++; + hFontCurrent = m_hFontUnderline; + surface()->DrawSetTextFont( hFontCurrent ); + + // set up the panel + ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL; + + if (clickPanel) + { + clickPanel->SetPos(renderState.x, renderState.y); + } + } + else + { + FinishingURL(nXBeforeStateChange, renderState.y); + hFontCurrent = _font; + surface()->DrawSetTextFont( hFontCurrent ); + } + _currentTextClickable = renderState.textClickable; + } + } + + // 2. + // if we've passed a line break go to that + if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] <= i ) + { + if (_currentTextClickable) + { + FinishingURL(renderState.x, renderState.y); + } + + // add another line + AddAnotherLine(renderState.x, renderState.y); + lineBreakIndexIndex++; + + // Skip white space unless the previous line ended from the hard carriage return + if ( i && ( m_TextStream[i-1] != '\n' ) && ( m_TextStream[i-1] != '\r') ) + { + while ( m_TextStream[i] == L' ' ) + { + if ( i+1 < m_TextStream.Count() ) + ++i; + else + break; + } + } + + if (renderState.textClickable) + { + // move to the next URL + _clickableTextIndex++; + ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL; + if (clickPanel) + { + clickPanel->SetPos(renderState.x, renderState.y); + } + } + } + + // 3. + // Calculate the range of text to draw all at once + int iLim = m_TextStream.Count(); + + // Stop at the next format change + if ( m_FormatStream.IsValidIndex(renderState.formatStreamIndex) && + m_FormatStream[renderState.formatStreamIndex].textStreamIndex < iLim && + m_FormatStream[renderState.formatStreamIndex].textStreamIndex >= i && + m_FormatStream[renderState.formatStreamIndex].textStreamIndex ) + { + iLim = m_FormatStream[renderState.formatStreamIndex].textStreamIndex; + } + + // Stop at the next line break + if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] < iLim ) + iLim = m_LineBreaks[lineBreakIndexIndex]; + + // Handle non-drawing characters specially + for ( int iT = i; iT < iLim; iT++ ) + { + if ( iswcntrl(m_TextStream[iT]) ) + { + iLim = iT; + break; + } + } + + // 4. + // Draw the current text range + if ( iLim <= i ) + { + if ( m_TextStream[i] == '\t' ) + { + int dxTabWidth = 8 * surface()->GetCharacterWidth(hFontCurrent, ' '); + dxTabWidth = MAX( 1, dxTabWidth ); + + renderState.x = ( dxTabWidth * ( 1 + ( renderState.x / dxTabWidth ) ) ); + } + i++; + } + else + { + renderState.x += DrawString(i, iLim - 1, renderState, hFontCurrent ); + i = iLim; + } + } + + if (renderState.textClickable) + { + FinishingURL(renderState.x, renderState.y); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int RichText::GetClickableTextIndexStart(int startIndex) +{ + // cycle to the right url panel for what is visible after the startIndex. + for (int i = 0; i < _clickableTextPanels.Count(); i++) + { + if (_clickableTextPanels[i]->GetViewTextIndex() >= startIndex) + { + return i - 1; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Recalcultes the formatting state from the specified index +//----------------------------------------------------------------------------- +void RichText::RecalculateDefaultState(int startIndex) +{ + if (!HasText() ) + return; + + Assert(startIndex < m_TextStream.Count()); + + m_CachedRenderState.textColor = GetFgColor(); + _pixelsIndent = 0; + _currentTextClickable = false; + _clickableTextIndex = GetClickableTextIndexStart(startIndex); + + // find where in the formatting stream we need to be + GenerateRenderStateForTextStreamIndex(startIndex, m_CachedRenderState); + _recalcSavedRenderState = false; +} + +//----------------------------------------------------------------------------- +// Purpose: updates a render state based on the formatting and color streams +// Output: true if we changed the render state +//----------------------------------------------------------------------------- +bool RichText::UpdateRenderState(int textStreamPos, TRenderState &renderState) +{ + // check the color stream + if (m_FormatStream.IsValidIndex(renderState.formatStreamIndex) && + m_FormatStream[renderState.formatStreamIndex].textStreamIndex == textStreamPos) + { + // set the current formatting + renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color; + renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable; + + CalculateFade( renderState ); + + int indentChange = m_FormatStream[renderState.formatStreamIndex].pixelsIndent - renderState.pixelsIndent; + renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent; + + if (indentChange) + { + renderState.x = renderState.pixelsIndent + _drawOffsetX; + } + + //!! for supporting old functionality, store off state in globals + _pixelsIndent = renderState.pixelsIndent; + + // move to the next position in the color stream + renderState.formatStreamIndex++; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the index in the format stream for the specified text stream index +//----------------------------------------------------------------------------- +int RichText::FindFormatStreamIndexForTextStreamPos(int textStreamIndex) +{ + int formatStreamIndex = 0; + for (; m_FormatStream.IsValidIndex(formatStreamIndex); formatStreamIndex++) + { + if (m_FormatStream[formatStreamIndex].textStreamIndex > textStreamIndex) + break; + } + + // step back to the color change before the new line + formatStreamIndex--; + if (!m_FormatStream.IsValidIndex(formatStreamIndex)) + { + formatStreamIndex = 0; + } + return formatStreamIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Generates a base renderstate given a index into the text stream +//----------------------------------------------------------------------------- +void RichText::GenerateRenderStateForTextStreamIndex(int textStreamIndex, TRenderState &renderState) +{ + // find where in the format stream we need to be given the specified place in the text stream + renderState.formatStreamIndex = FindFormatStreamIndexForTextStreamPos(textStreamIndex); + + // copy the state data + renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color; + renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent; + renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable; +} + +//----------------------------------------------------------------------------- +// Purpose: Called pre render +//----------------------------------------------------------------------------- +void RichText::OnThink() +{ + if (m_bRecalcLineBreaks) + { + _recalcSavedRenderState = true; + RecalculateLineBreaks(); + + // recalculate scrollbar position + if (_invalidateVerticalScrollbarSlider) + { + LayoutVerticalScrollBarSlider(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when data changes or panel size changes +//----------------------------------------------------------------------------- +void RichText::PerformLayout() +{ + BaseClass::PerformLayout(); + + // force a Repaint + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: inserts a color change into the formatting stream +//----------------------------------------------------------------------------- +void RichText::InsertColorChange(Color col) +{ + // see if color already exists in text stream + TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1]; + if (prevItem.color == col) + { + // inserting same color into stream, just ignore + } + else if (prevItem.textStreamIndex == m_TextStream.Count()) + { + // this item is in the same place; update values + prevItem.color = col; + } + else + { + // add to text stream, based off existing item + TFormatStream streamItem = prevItem; + streamItem.color = col; + streamItem.textStreamIndex = m_TextStream.Count(); + m_FormatStream.AddToTail(streamItem); + } +} + +//----------------------------------------------------------------------------- +// Purpose: inserts a fade into the formatting stream +//----------------------------------------------------------------------------- +void RichText::InsertFade( float flSustain, float flLength ) +{ + // see if color already exists in text stream + TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1]; + if (prevItem.textStreamIndex == m_TextStream.Count()) + { + // this item is in the same place; update values + prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain; + prevItem.fade.flFadeSustain = flSustain; + prevItem.fade.flFadeLength = flLength; + prevItem.fade.iOriginalAlpha = prevItem.color.a(); + } + else + { + // add to text stream, based off existing item + TFormatStream streamItem = prevItem; + + prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain; + prevItem.fade.flFadeLength = flLength; + prevItem.fade.flFadeSustain = flSustain; + prevItem.fade.iOriginalAlpha = prevItem.color.a(); + + streamItem.textStreamIndex = m_TextStream.Count(); + m_FormatStream.AddToTail(streamItem); + } +} + +void RichText::ResetAllFades( bool bHold, bool bOnlyExpired, float flNewSustain ) +{ + m_bResetFades = bHold; + + if ( m_bResetFades == false ) + { + for (int i = 1; i < m_FormatStream.Count(); i++) + { + if ( bOnlyExpired == true ) + { + if ( m_FormatStream[i].fade.flFadeStartTime >= system()->GetCurrentTime() ) + continue; + } + + if ( flNewSustain == -1.0f ) + { + flNewSustain = m_FormatStream[i].fade.flFadeSustain; + } + + m_FormatStream[i].fade.flFadeStartTime = system()->GetCurrentTime() + flNewSustain; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: inserts an indent change into the formatting stream +//----------------------------------------------------------------------------- +void RichText::InsertIndentChange(int pixelsIndent) +{ + if (pixelsIndent < 0) + { + pixelsIndent = 0; + } + else if (pixelsIndent > 255) + { + pixelsIndent = 255; + } + + // see if indent change already exists in text stream + TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1]; + if (prevItem.pixelsIndent == pixelsIndent) + { + // inserting same indent into stream, just ignore + } + else if (prevItem.textStreamIndex == m_TextStream.Count()) + { + // this item is in the same place; update + prevItem.pixelsIndent = pixelsIndent; + } + else + { + // add to text stream, based off existing item + TFormatStream streamItem = prevItem; + streamItem.pixelsIndent = pixelsIndent; + streamItem.textStreamIndex = m_TextStream.Count(); + m_FormatStream.AddToTail(streamItem); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Inserts character Start for clickable text, eg. URLS +//----------------------------------------------------------------------------- +void RichText::InsertClickableTextStart( const char *pchClickAction ) +{ + // see if indent change already exists in text stream + TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1]; + TFormatStream *pFormatStream = &prevItem; + if (prevItem.textStreamIndex == m_TextStream.Count()) + { + // this item is in the same place; update + prevItem.textClickable = true; + pFormatStream->m_sClickableTextAction = pchClickAction; + } + else + { + // add to text stream, based off existing item + TFormatStream formatStreamCopy = prevItem; + int iFormatStream = m_FormatStream.AddToTail( formatStreamCopy ); + + // set the new params + pFormatStream = &m_FormatStream[iFormatStream]; + pFormatStream->textStreamIndex = m_TextStream.Count(); + pFormatStream->textClickable = true; + pFormatStream->m_sClickableTextAction = pchClickAction; + } + + // invalidate the layout to recalculate where the click panels should go + InvalidateLineBreakStream(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Inserts character end for clickable text, eg. URLS +//----------------------------------------------------------------------------- +void RichText::InsertClickableTextEnd() +{ + // see if indent change already exists in text stream + TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1]; + if (!prevItem.textClickable) + { + // inserting same indent into stream, just ignore + } + else if (prevItem.textStreamIndex == m_TextStream.Count()) + { + // this item is in the same place; update + prevItem.textClickable = false; + } + else + { + // add to text stream, based off existing item + TFormatStream streamItem = prevItem; + streamItem.textClickable = false; + streamItem.textStreamIndex = m_TextStream.Count(); + m_FormatStream.AddToTail(streamItem); + } +} + +//----------------------------------------------------------------------------- +// Purpose: moves x,y to the Start of the next line of text +//----------------------------------------------------------------------------- +void RichText::AddAnotherLine(int &cx, int &cy) +{ + cx = _drawOffsetX + _pixelsIndent; + cy += (GetLineHeight() + _drawOffsetY); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculates line breaks +//----------------------------------------------------------------------------- +void RichText::RecalculateLineBreaks() +{ + if ( !m_bRecalcLineBreaks ) + return; + + int wide = GetWide(); + if (!wide) + return; + + wide -= _drawOffsetX; + + m_bRecalcLineBreaks = false; + _recalcSavedRenderState = true; + if (!HasText()) + return; + + int selection0 = -1, selection1 = -1; + + // subtract the scrollbar width + if (_vertScrollBar->IsVisible()) + { + wide -= _vertScrollBar->GetWide(); + } + + int x = _drawOffsetX, y = _drawOffsetY; + + HFont fontWordStart = INVALID_FONT; + int wordStartIndex = 0; + int lineStartIndex = 0; + bool hasWord = false; + bool justStartedNewLine = true; + bool wordStartedOnNewLine = true; + + int startChar = 0; + if (_recalculateBreaksIndex <= 0) + { + m_LineBreaks.RemoveAll(); + } + else + { + // remove the rest of the linebreaks list since its out of date. + for (int i = _recalculateBreaksIndex + 1; i < m_LineBreaks.Count(); ++i) + { + m_LineBreaks.Remove(i); + --i; // removing shrinks the list! + } + startChar = m_LineBreaks[_recalculateBreaksIndex]; + lineStartIndex = m_LineBreaks[_recalculateBreaksIndex]; + wordStartIndex = lineStartIndex; + } + + // handle the case where this char is a new line, in that case + // we have already taken its break index into account above so skip it. + if (m_TextStream[startChar] == '\r' || m_TextStream[startChar] == '\n') + { + startChar++; + lineStartIndex = startChar; + } + + // cycle to the right url panel for what is visible after the startIndex. + int clickableTextNum = GetClickableTextIndexStart(startChar); + clickableTextNum++; + + // initialize the renderstate with the start + TRenderState renderState; + GenerateRenderStateForTextStreamIndex(startChar, renderState); + _currentTextClickable = false; + + HFont font = _font; + + bool bForceBreak = false; + float flLineWidthSoFar = 0; + + // loop through all the characters + for (int i = startChar; i < m_TextStream.Count(); ++i) + { + wchar_t ch = m_TextStream[i]; + renderState.x = x; + if (UpdateRenderState(i, renderState)) + { + x = renderState.x; + int preI = i; + + // check for clickable text + if (renderState.textClickable != _currentTextClickable) + { + if (renderState.textClickable) + { + // make a new clickable text panel + if (clickableTextNum >= _clickableTextPanels.Count()) + { + _clickableTextPanels.AddToTail(new ClickPanel(this)); + } + + ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++]; + clickPanel->SetTextIndex(preI, preI); + } + + // url state change + _currentTextClickable = renderState.textClickable; + } + } + + bool bIsWSpace = iswspace( ch ) ? true : false; + + bool bPreviousWordStartedOnNewLine = wordStartedOnNewLine; + int iPreviousWordStartIndex = wordStartIndex; + if ( !bIsWSpace && ch != L'\t' && ch != L'\n' && ch != L'\r' ) + { + if (!hasWord) + { + // Start a new word + wordStartIndex = i; + hasWord = true; + wordStartedOnNewLine = justStartedNewLine; + fontWordStart = font; + } + // else append to the current word + } + else + { + // whitespace/punctuation character + // end the word + hasWord = false; + } + + float w = 0; + wchar_t wchBefore = 0; + wchar_t wchAfter = 0; + + if ( i > 0 && i > lineStartIndex && i != selection0 && i-1 != selection1 ) + wchBefore = m_TextStream[i-1]; + if ( i < m_TextStream.Count() - 1 && i+1 != selection0 && i != selection1 ) + wchAfter = m_TextStream[i+1]; + + float flabcA; + surface()->GetKernedCharWidth( font, ch, wchBefore, wchAfter, w, flabcA ); + flLineWidthSoFar += w; + + // See if we've exceeded the width we have available, with + if ( floor(flLineWidthSoFar + 0.6) + x > wide ) + { + bForceBreak = true; + } + + if (!iswcntrl(ch)) + { + justStartedNewLine = false; + } + + if ( bForceBreak || ch == '\r' || ch == '\n' ) + { + bForceBreak = false; + // add another line + AddAnotherLine(x, y); + + if ( ch == '\r' || ch == '\n' ) + { + // skip the newline so it's not at the beginning of the new line + lineStartIndex = i + 1; + m_LineBreaks.AddToTail(i + 1); + } + else if ( bPreviousWordStartedOnNewLine || iPreviousWordStartIndex <= lineStartIndex ) + { + lineStartIndex = i; + m_LineBreaks.AddToTail( i ); + + if (renderState.textClickable) + { + // need to split the url into two panels + int oldIndex = _clickableTextPanels[clickableTextNum - 1]->GetTextIndex(); + + // make a new clickable text panel + if (clickableTextNum >= _clickableTextPanels.Count()) + { + _clickableTextPanels.AddToTail(new ClickPanel(this)); + } + + ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++]; + clickPanel->SetTextIndex(oldIndex, i); + } + } + else + { + m_LineBreaks.AddToTail( iPreviousWordStartIndex ); + lineStartIndex = iPreviousWordStartIndex; + i = iPreviousWordStartIndex; + + TRenderState renderStateAtLastWord; + GenerateRenderStateForTextStreamIndex( i, renderStateAtLastWord ); + + // If the word is clickable, and that started prior to the beginning of the word, then we must split the click panel + if ( renderStateAtLastWord.textClickable && m_FormatStream[ renderStateAtLastWord.formatStreamIndex ].textStreamIndex < i ) + { + // need to split the url into two panels + int oldIndex = _clickableTextPanels[clickableTextNum - 1]->GetTextIndex(); + + // make a new clickable text panel + if (clickableTextNum >= _clickableTextPanels.Count()) + { + _clickableTextPanels.AddToTail(new ClickPanel(this)); + } + + ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++]; + clickPanel->SetTextIndex(oldIndex, i); + } + } + + flLineWidthSoFar = 0; + justStartedNewLine = true; + hasWord = false; + wordStartedOnNewLine = false; + _currentTextClickable = false; + continue; + } + } + + // end the list + m_LineBreaks.AddToTail(MAX_BUFFER_SIZE); + + // set up the scrollbar + _invalidateVerticalScrollbarSlider = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate where the vertical scroll bar slider should be +// based on the current cursor line we are on. +//----------------------------------------------------------------------------- +void RichText::LayoutVerticalScrollBarSlider() +{ + _invalidateVerticalScrollbarSlider = false; + + // set up the scrollbar + //if (!_vertScrollBar->IsVisible()) + // return; + + + // see where the scrollbar currently is + int previousValue = _vertScrollBar->GetValue(); + bool bCurrentlyAtEnd = false; + int rmin, rmax; + _vertScrollBar->GetRange(rmin, rmax); + if (rmax && (previousValue + rmin + _vertScrollBar->GetRangeWindow() == rmax)) + { + bCurrentlyAtEnd = true; + } + + // work out position to put scrollbar, factoring in insets + int wide, tall; + GetSize( wide, tall ); + + _vertScrollBar->SetPos( wide - _vertScrollBar->GetWide(), 0 ); + // scrollbar is inside the borders. + _vertScrollBar->SetSize( _vertScrollBar->GetWide(), tall ); + + // calculate how many lines we can fully display + int displayLines = tall / (GetLineHeight() + _drawOffsetY); + int numLines = m_LineBreaks.Count(); + + if (numLines <= displayLines) + { + // disable the scrollbar + _vertScrollBar->SetEnabled(false); + _vertScrollBar->SetRange(0, numLines); + _vertScrollBar->SetRangeWindow(numLines); + _vertScrollBar->SetValue(0); + + if ( m_bUnusedScrollbarInvis ) + { + SetVerticalScrollbar( false ); + } + } + else + { + if ( m_bUnusedScrollbarInvis ) + { + SetVerticalScrollbar( true ); + } + + // set the scrollbars range + _vertScrollBar->SetRange(0, numLines); + _vertScrollBar->SetRangeWindow(displayLines); + _vertScrollBar->SetEnabled(true); + + // this should make it scroll one line at a time + _vertScrollBar->SetButtonPressedScrollValue(1); + if (bCurrentlyAtEnd) + { + _vertScrollBar->SetValue(numLines - displayLines); + } + _vertScrollBar->InvalidateLayout(); + _vertScrollBar->Repaint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets whether a vertical scrollbar is visible +//----------------------------------------------------------------------------- +void RichText::SetVerticalScrollbar(bool state) +{ + if (_vertScrollBar->IsVisible() != state) + { + _vertScrollBar->SetVisible(state); + InvalidateLineBreakStream(); + InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Create cut/copy/paste dropdown menu +//----------------------------------------------------------------------------- +void RichText::CreateEditMenu() +{ + // create a drop down cut/copy/paste menu appropriate for this object's states + if (m_pEditMenu) + delete m_pEditMenu; + m_pEditMenu = new Menu(this, "EditMenu"); + + + // add cut/copy/paste drop down options if its editable, just copy if it is not + m_pEditMenu->AddMenuItem("C&opy", new KeyValues("DoCopySelected"), this); + + m_pEditMenu->SetVisible(false); + m_pEditMenu->SetParent(this); + m_pEditMenu->AddActionSignalTarget(this); +} + +//----------------------------------------------------------------------------- +// Purpose: We want single line windows to scroll horizontally and select text +// in response to clicking and holding outside window +//----------------------------------------------------------------------------- +void RichText::OnMouseFocusTicked() +{ + // if a button is down move the scrollbar slider the appropriate direction + if (_mouseDragSelection) // text is being selected via mouse clicking and dragging + { + OnCursorMoved(0,0); // we want the text to scroll as if we were dragging + } +} + +//----------------------------------------------------------------------------- +// Purpose: If a cursor enters the window, we are not elegible for +// MouseFocusTicked events +//----------------------------------------------------------------------------- +void RichText::OnCursorEntered() +{ + _mouseDragSelection = false; // outside of window dont recieve drag scrolling ticks +} + +//----------------------------------------------------------------------------- +// Purpose: When the cursor is outside the window, if we are holding the mouse +// button down, then we want the window to scroll the text one char at a time using Ticks +//----------------------------------------------------------------------------- +void RichText::OnCursorExited() +{ + // outside of window recieve drag scrolling ticks + if (_mouseSelection) + { + _mouseDragSelection = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle selection of text by mouse +//----------------------------------------------------------------------------- +void RichText::OnCursorMoved(int ignX, int ignY) +{ + if (_mouseSelection) + { + // update the cursor position + int x, y; + input()->GetCursorPos(x, y); + ScreenToLocal(x, y); + _cursorPos = PixelToCursorSpace(x, y); + + if (_cursorPos != _select[1]) + { + _select[1] = _cursorPos; + Repaint(); + } + // Msg( "selecting range [%d..%d]\n", _select[0], _select[1] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse button down events. +//----------------------------------------------------------------------------- +void RichText::OnMousePressed(MouseCode code) +{ + if (code == MOUSE_LEFT) + { + // clear current selection + SelectNone(); + + // move the cursor to where the mouse was pressed + int x, y; + input()->GetCursorPos(x, y); + ScreenToLocal(x, y); + + _cursorPos = PixelToCursorSpace(x, y); + + if ( m_bInteractive ) + { + // enter selection mode + input()->SetMouseCapture(GetVPanel()); + _mouseSelection = true; + + if (_select[0] < 0) + { + // if no initial selection position, Start selection position at cursor + _select[0] = _cursorPos; + } + _select[1] = _cursorPos; + } + + RequestFocus(); + Repaint(); + } + else if (code == MOUSE_RIGHT) // check for context menu open + { + if ( m_bInteractive ) + { + CreateEditMenu(); + Assert(m_pEditMenu); + + OpenEditMenu(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse button up events +//----------------------------------------------------------------------------- +void RichText::OnMouseReleased(MouseCode code) +{ + _mouseSelection = false; + input()->SetMouseCapture(NULL); + + // make sure something has been selected + int cx0, cx1; + if (GetSelectedRange(cx0, cx1)) + { + if (cx1 - cx0 == 0) + { + // nullify selection + _select[0] = -1; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse double clicks +//----------------------------------------------------------------------------- +void RichText::OnMouseDoublePressed(MouseCode code) +{ + if ( !m_bInteractive ) + return; + + // left double clicking on a word selects the word + if (code == MOUSE_LEFT) + { + // move the cursor just as if you single clicked. + OnMousePressed(code); + // then find the start and end of the word we are in to highlight it. + int selectSpot[2]; + GotoWordLeft(); + selectSpot[0] = _cursorPos; + GotoWordRight(); + selectSpot[1] = _cursorPos; + + if ( _cursorPos > 0 && (_cursorPos-1) < m_TextStream.Count() ) + { + if (iswspace(m_TextStream[_cursorPos-1])) + { + selectSpot[1]--; + _cursorPos--; + } + } + + _select[0] = selectSpot[0]; + _select[1] = selectSpot[1]; + _mouseSelection = true; + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Turn off text selection code when mouse button is not down +//----------------------------------------------------------------------------- +void RichText::OnMouseCaptureLost() +{ + _mouseSelection = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Masks which keys get chained up +// Maps keyboard input to text window functions. +//----------------------------------------------------------------------------- +void RichText::OnKeyCodeTyped(KeyCode code) +{ + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + bool winkey = (input()->IsKeyDown(KEY_LWIN) || input()->IsKeyDown(KEY_RWIN)); + bool fallThrough = false; + + if ( ctrl || ( winkey && IsOSX() ) ) + { + switch(code) + { + case KEY_INSERT: + case KEY_C: + case KEY_X: + { + CopySelected(); + break; + } + case KEY_PAGEUP: + case KEY_HOME: + { + GotoTextStart(); + break; + } + case KEY_PAGEDOWN: + case KEY_END: + { + GotoTextEnd(); + break; + } + default: + { + fallThrough = true; + break; + } + } + } + else if (alt) + { + // do nothing with ALT-x keys + fallThrough = true; + } + else + { + switch(code) + { + case KEY_TAB: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_ESCAPE: + case KEY_ENTER: + { + fallThrough = true; + break; + } + case KEY_DELETE: + { + if (shift) + { + // shift-delete is cut + CopySelected(); + } + break; + } + case KEY_HOME: + { + GotoTextStart(); + break; + } + case KEY_END: + { + GotoTextEnd(); + break; + } + case KEY_PAGEUP: + { + // if there is a scroll bar scroll down one rangewindow + if (_vertScrollBar->IsVisible()) + { + int window = _vertScrollBar->GetRangeWindow(); + int newval = _vertScrollBar->GetValue(); + _vertScrollBar->SetValue(newval - window - 1); + } + break; + + } + case KEY_PAGEDOWN: + { + // if there is a scroll bar scroll down one rangewindow + if (_vertScrollBar->IsVisible()) + { + int window = _vertScrollBar->GetRangeWindow(); + int newval = _vertScrollBar->GetValue(); + _vertScrollBar->SetValue(newval + window + 1); + } + break; + } + default: + { + // return if any other char is pressed. + // as it will be a unicode char. + // and we don't want select[1] changed unless a char was pressed that this fxn handles + return; + } + } + } + + // select[1] is the location in the line where the blinking cursor started + _select[1] = _cursorPos; + + // chain back on some keys + if (fallThrough) + { + BaseClass::OnKeyCodeTyped(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list according to the mouse wheel movement +//----------------------------------------------------------------------------- +void RichText::OnMouseWheeled(int delta) +{ + MoveScrollBar(delta); +} + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list +// Input : delta - amount to move scrollbar up +//----------------------------------------------------------------------------- +void RichText::MoveScrollBar(int delta) +{ + MoveScrollBarDirect( delta * 3 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list +// Input : delta - amount to move scrollbar up +//----------------------------------------------------------------------------- +void RichText::MoveScrollBarDirect(int delta) +{ + if (_vertScrollBar->IsVisible()) + { + int val = _vertScrollBar->GetValue(); + val -= delta; + _vertScrollBar->SetValue(val); + _recalcSavedRenderState = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: set the maximum number of chars in the text buffer +//----------------------------------------------------------------------------- +void RichText::SetMaximumCharCount(int maxChars) +{ + _maxCharCount = maxChars; +} + +//----------------------------------------------------------------------------- +// Purpose: Find out what line the cursor is on +//----------------------------------------------------------------------------- +int RichText::GetCursorLine() +{ + // always returns the last place + int pos = m_LineBreaks[m_LineBreaks.Count() - 1]; + Assert(pos == MAX_BUFFER_SIZE); + return pos; +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor over to the Start of the next word to the right +//----------------------------------------------------------------------------- +void RichText::GotoWordRight() +{ + // search right until we hit a whitespace character or a newline + while (++_cursorPos < m_TextStream.Count()) + { + if (iswspace(m_TextStream[_cursorPos])) + break; + } + + // search right until we hit an nonspace character + while (++_cursorPos < m_TextStream.Count()) + { + if (!iswspace(m_TextStream[_cursorPos])) + break; + } + + if (_cursorPos > m_TextStream.Count()) + { + _cursorPos = m_TextStream.Count(); + } + + // now we are at the start of the next word + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor over to the Start of the next word to the left +//----------------------------------------------------------------------------- +void RichText::GotoWordLeft() +{ + if (_cursorPos < 1) + return; + + // search left until we hit an nonspace character + while (--_cursorPos >= 0) + { + if (!iswspace(m_TextStream[_cursorPos])) + break; + } + + // search left until we hit a whitespace character + while (--_cursorPos >= 0) + { + if (iswspace(m_TextStream[_cursorPos])) + { + break; + } + } + + // we end one character off + _cursorPos++; + + // now we are at the start of the previous word + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move cursor to the Start of the text buffer +//----------------------------------------------------------------------------- +void RichText::GotoTextStart() +{ + _cursorPos = 0; // set cursor to start + _invalidateVerticalScrollbarSlider = true; + // force scrollbar to the top + _vertScrollBar->SetValue(0); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move cursor to the end of the text buffer +//----------------------------------------------------------------------------- +void RichText::GotoTextEnd() +{ + _cursorPos = m_TextStream.Count(); // set cursor to end of buffer + _invalidateVerticalScrollbarSlider = true; + + // force the scrollbar to the bottom + int min, max; + _vertScrollBar->GetRange(min, max); + _vertScrollBar->SetValue(max); + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Culls the text stream down to a managable size +//----------------------------------------------------------------------------- +void RichText::TruncateTextStream() +{ + if (_maxCharCount < 1) + return; + + // choose a point to cull at + int cullPos = _maxCharCount / 2; + + // kill half the buffer + m_TextStream.RemoveMultiple(0, cullPos); + + // work out where in the format stream we can start + int formatIndex = FindFormatStreamIndexForTextStreamPos(cullPos); + if (formatIndex > 0) + { + // take a copy, make it first + m_FormatStream[0] = m_FormatStream[formatIndex]; + m_FormatStream[0].textStreamIndex = 0; + // kill the others + m_FormatStream.RemoveMultiple(1, formatIndex); + } + + // renormalize the remainder of the format stream + for (int i = 1; i < m_FormatStream.Count(); i++) + { + Assert(m_FormatStream[i].textStreamIndex > cullPos); + m_FormatStream[i].textStreamIndex -= cullPos; + } + + // mark everything to be recalculated + InvalidateLineBreakStream(); + InvalidateLayout(); + _invalidateVerticalScrollbarSlider = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Insert a character into the text buffer +//----------------------------------------------------------------------------- +void RichText::InsertChar(wchar_t wch) +{ + // throw away redundant linefeed characters + if ( wch == '\r' ) + return; + + if (_maxCharCount > 0 && m_TextStream.Count() > _maxCharCount) + { + TruncateTextStream(); + } + + // insert the new char at the end of the buffer + m_TextStream.AddToTail(wch); + + // mark the linebreak steam as needing recalculating from that point + _recalculateBreaksIndex = m_LineBreaks.Count() - 2; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Insert a string into the text buffer, this is just a series +// of char inserts because we have to check each char is ok to insert +//----------------------------------------------------------------------------- +void RichText::InsertString(const char *text) +{ + if (text[0] == '#') + { + wchar_t unicode[ 1024 ]; + ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) ); + InsertString( unicode ); + return; + } + + // upgrade the ansi text to unicode to display it + int len = strlen(text); + wchar_t *unicode = (wchar_t *)_alloca((len + 1) * sizeof(wchar_t)); + Q_UTF8ToUnicode(text, unicode, ((len + 1) * sizeof(wchar_t))); + InsertString(unicode); +} + +//----------------------------------------------------------------------------- +// Purpose: Insertsa a unicode string into the buffer +//----------------------------------------------------------------------------- +void RichText::InsertString(const wchar_t *wszText) +{ + // insert the whole string + for (const wchar_t *ch = wszText; *ch != 0; ++ch) + { + InsertChar(*ch); + } + InvalidateLayout(); + m_bRecalcLineBreaks = true; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Declare a selection empty +//----------------------------------------------------------------------------- +void RichText::SelectNone() +{ + // tag the selection as empty + _select[0] = -1; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Load in the selection range so cx0 is the Start and cx1 is the end +// from smallest to highest (right to left) +//----------------------------------------------------------------------------- +bool RichText::GetSelectedRange(int &cx0, int &cx1) +{ + // if there is nothing selected return false + if (_select[0] == -1) + return false; + + // sort the two position so cx0 is the smallest + cx0 = _select[0]; + cx1 = _select[1]; + if (cx1 < cx0) + { + int temp = cx0; + cx0 = cx1; + cx1 = temp; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Opens the cut/copy/paste dropdown menu +//----------------------------------------------------------------------------- +void RichText::OpenEditMenu() +{ + // get cursor position, this is local to this text edit window + // so we need to adjust it relative to the parent + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + + /* !! disabled since it recursively gets panel pointers, potentially across dll boundaries, + and doesn't need to be necessary (it's just for handling windowed mode) + + // find the frame that has no parent (the one on the desktop) + Panel *panel = this; + while ( panel->GetParent() != NULL) + { + panel = panel->GetParent(); + } + panel->ScreenToLocal(cursorX, cursorY); + int x, y; + // get base panel's postition + panel->GetPos(x, y); + + // adjust our cursor position accordingly + cursorX += x; + cursorY += y; + */ + + int x0, x1; + if (GetSelectedRange(x0, x1)) // there is something selected + { + m_pEditMenu->SetItemEnabled("&Cut", true); + m_pEditMenu->SetItemEnabled("C&opy", true); + } + else // there is nothing selected, disable cut/copy options + { + m_pEditMenu->SetItemEnabled("&Cut", false); + m_pEditMenu->SetItemEnabled("C&opy", false); + } + m_pEditMenu->SetVisible(true); + m_pEditMenu->RequestFocus(); + + // relayout the menu immediately so that we know it's size + m_pEditMenu->InvalidateLayout(true); + int menuWide, menuTall; + m_pEditMenu->GetSize(menuWide, menuTall); + + // work out where the cursor is and therefore the best place to put the menu + int wide, tall; + surface()->GetScreenSize(wide, tall); + + if (wide - menuWide > cursorX) + { + // menu hanging right + if (tall - menuTall > cursorY) + { + // menu hanging down + m_pEditMenu->SetPos(cursorX, cursorY); + } + else + { + // menu hanging up + m_pEditMenu->SetPos(cursorX, cursorY - menuTall); + } + } + else + { + // menu hanging left + if (tall - menuTall > cursorY) + { + // menu hanging down + m_pEditMenu->SetPos(cursorX - menuWide, cursorY); + } + else + { + // menu hanging up + m_pEditMenu->SetPos(cursorX - menuWide, cursorY - menuTall); + } + } + + m_pEditMenu->RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Cuts the selected chars from the buffer and +// copies them into the clipboard +//----------------------------------------------------------------------------- +void RichText::CutSelected() +{ + CopySelected(); + // have to request focus if we used the menu + RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Copies the selected chars into the clipboard +//----------------------------------------------------------------------------- +void RichText::CopySelected() +{ + int x0, x1; + if (GetSelectedRange(x0, x1)) + { + CUtlVector<wchar_t> buf; + for (int i = x0; i < x1; i++) + { + if ( m_TextStream.IsValidIndex(i) == false ) + continue; + + if (m_TextStream[i] == '\n') + { + buf.AddToTail( '\r' ); + } + // remove any rich edit commands + buf.AddToTail(m_TextStream[i]); + } + buf.AddToTail('\0'); + system()->SetClipboardText(buf.Base(), buf.Count() - 1); + } + + // have to request focus if we used the menu + RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the index in the text buffer of the +// character the drawing should Start at +//----------------------------------------------------------------------------- +int RichText::GetStartDrawIndex(int &lineBreakIndexIndex) +{ + int startIndex = 0; + int startLine = _vertScrollBar->GetValue(); + + if ( startLine >= m_LineBreaks.Count() ) // incase the line breaks got reset and the scroll bar hasn't + { + startLine = m_LineBreaks.Count() - 1; + } + + lineBreakIndexIndex = startLine; + if (startLine && startLine < m_LineBreaks.Count()) + { + startIndex = m_LineBreaks[startLine - 1]; + } + + return startIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Get a string from text buffer +// Input: offset - index to Start reading from +// bufLen - length of string +//----------------------------------------------------------------------------- +void RichText::GetText(int offset, wchar_t *buf, int bufLenInBytes) +{ + if (!buf) + return; + + Assert( bufLenInBytes >= sizeof(buf[0]) ); + int bufLen = bufLenInBytes / sizeof(wchar_t); + int i; + for (i = offset; i < (offset + bufLen - 1); i++) + { + if (i >= m_TextStream.Count()) + break; + + buf[i-offset] = m_TextStream[i]; + } + buf[(i-offset)] = 0; + buf[bufLen-1] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: gets text from the buffer +//----------------------------------------------------------------------------- +void RichText::GetText(int offset, char *pch, int bufLenInBytes) +{ + wchar_t rgwchT[4096]; + GetText(offset, rgwchT, sizeof(rgwchT)); + Q_UnicodeToUTF8(rgwchT, pch, bufLenInBytes); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the font of the buffer text +//----------------------------------------------------------------------------- +void RichText::SetFont(HFont font) +{ + _font = font; + InvalidateLayout(); + m_bRecalcLineBreaks = true; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the scrollbar slider is moved +//----------------------------------------------------------------------------- +void RichText::OnSliderMoved() +{ + _recalcSavedRenderState = true; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool RichText::RequestInfo(KeyValues *outputData) +{ + if (!stricmp(outputData->GetName(), "GetText")) + { + wchar_t wbuf[512]; + GetText(0, wbuf, sizeof(wbuf)); + outputData->SetWString("text", wbuf); + return true; + } + + return BaseClass::RequestInfo(outputData); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RichText::OnSetText(const wchar_t *text) +{ + SetText(text); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a URL, etc has been clicked on +//----------------------------------------------------------------------------- +void RichText::OnClickPanel(int index) +{ + wchar_t wBuf[512]; + int outIndex = 0; + + // parse out the clickable text, and send it to our listeners + _currentTextClickable = true; + TRenderState renderState; + GenerateRenderStateForTextStreamIndex(index, renderState); + for (int i = index; i < (sizeof(wBuf) - 1) && i < m_TextStream.Count(); i++) + { + // stop getting characters when text is no longer clickable + UpdateRenderState(i, renderState); + if (!renderState.textClickable) + break; + + // copy out the character + wBuf[outIndex++] = m_TextStream[i]; + } + + wBuf[outIndex] = 0; + + int iFormatSteam = FindFormatStreamIndexForTextStreamPos( index ); + if ( m_FormatStream[iFormatSteam].m_sClickableTextAction ) + { + Q_UTF8ToUnicode( m_FormatStream[iFormatSteam].m_sClickableTextAction.String(), wBuf, sizeof( wBuf ) ); + } + + PostActionSignal(new KeyValues("TextClicked", "text", wBuf)); + OnTextClicked(wBuf); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RichText::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + SetMaximumCharCount(inResourceData->GetInt("maxchars", -1)); + SetVerticalScrollbar(inResourceData->GetInt("scrollbar", 1)); + + // get the starting text, if any + const char *text = inResourceData->GetString("text", ""); + if (*text) + { + delete [] m_pszInitialText; + int len = Q_strlen(text) + 1; + m_pszInitialText = new char[ len ]; + Q_strncpy( m_pszInitialText, text, len ); + SetText(text); + } + else + { + const char *textfilename = inResourceData->GetString("textfile", NULL); + if ( textfilename ) + { + FileHandle_t f = g_pFullFileSystem->Open( textfilename, "rt" ); + if (!f) + { + Warning( "RichText: textfile parameter '%s' not found.\n", textfilename ); + return; + } + + int len = g_pFullFileSystem->Size( f ); + delete [] m_pszInitialText; + m_pszInitialText = new char[ len + 1 ]; + g_pFullFileSystem->Read( m_pszInitialText, len, f ); + m_pszInitialText[len - 1] = 0; + SetText( m_pszInitialText ); + + g_pFullFileSystem->Close( f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RichText::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + outResourceData->SetInt("maxchars", _maxCharCount); + outResourceData->SetInt("scrollbar", _vertScrollBar->IsVisible() ); + if (m_pszInitialText) + { + outResourceData->SetString("text", m_pszInitialText); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *RichText::GetDescription() +{ + static char buf[1024]; + Q_snprintf(buf, sizeof(buf), "%s, string text, bool scrollbar", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of lines in the window +//----------------------------------------------------------------------------- +int RichText::GetNumLines() +{ + return m_LineBreaks.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the height of the text entry window so all text will fit inside +//----------------------------------------------------------------------------- +void RichText::SetToFullHeight() +{ + PerformLayout(); + int wide, tall; + GetSize(wide, tall); + + tall = GetNumLines() * (GetLineHeight() + _drawOffsetY) + _drawOffsetY + 2; + SetSize (wide, tall); + PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Select all the text. +//----------------------------------------------------------------------------- +void RichText::SelectAllText() +{ + _cursorPos = 0; + _select[0] = 0; + _select[1] = m_TextStream.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Select all the text. +//----------------------------------------------------------------------------- +void RichText::SelectNoText() +{ + _select[0] = 0; + _select[1] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RichText::OnSetFocus() +{ + BaseClass::OnSetFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Invalidates the current linebreak stream +//----------------------------------------------------------------------------- +void RichText::InvalidateLineBreakStream() +{ + // clear the buffer + m_LineBreaks.RemoveAll(); + m_LineBreaks.AddToTail(MAX_BUFFER_SIZE); + _recalculateBreaksIndex = 0; + m_bRecalcLineBreaks = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Inserts a text string while making URLs clickable/different color +// Input : *text - string that may contain URLs to make clickable/color coded +// URLTextColor - color for URL text +// normalTextColor - color for normal text +//----------------------------------------------------------------------------- +void RichText::InsertPossibleURLString(const char* text, Color URLTextColor, Color normalTextColor) +{ + InsertColorChange(normalTextColor); + + // parse out the string for URL's + int len = Q_strlen(text), pos = 0; + bool clickable = false; + char *pchURLText = (char *)stackalloc( len + 1 ); + char *pchURL = (char *)stackalloc( len + 1 ); + + while (pos < len) + { + pos = ParseTextStringForUrls( text, pos, pchURLText, len, pchURL, len, clickable ); + + if ( clickable ) + { + InsertClickableTextStart( pchURL ); + InsertColorChange( URLTextColor ); + } + + InsertString( pchURLText ); + + if ( clickable ) + { + InsertColorChange(normalTextColor); + InsertClickableTextEnd(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: looks for URLs in the string and returns information about the URL +//----------------------------------------------------------------------------- +int RichText::ParseTextStringForUrls( const char *text, int startPos, char *pchURLText, int cchURLText, char *pchURL, int cchURL, bool &clickable ) +{ + // scan for text that looks like a URL + int i = startPos; + while (text[i] != 0) + { + bool bURLFound = false; + + if ( !Q_strnicmp(text + i, "<a href=", 8) ) + { + if (i > startPos) + break; + + // embedded link + bURLFound = true; + clickable = true; + // get the url + i += Q_strlen( "<a href=" ); + const char *pchURLEnd = Q_strstr( text + i, ">" ); + Q_strncpy( pchURL, text + i, min( pchURLEnd - text - i + 1, cchURL ) ); + i += ( pchURLEnd - text - i + 1 ); + + // get the url text + pchURLEnd = Q_strstr( text, "</a>" ); + Q_strncpy( pchURLText, text + i, min( pchURLEnd - text - i + 1, cchURLText ) ); + i += ( pchURLEnd - text - i ); + i += Q_strlen( "</a>" ); + + // we're done + return i; + } + else if (!Q_strnicmp(text + i, "www.", 4)) + { + // scan ahead for another '.' + bool bPeriodFound = false; + for (const char *ch = text + i + 5; ch != 0; ch++) + { + if (*ch == '.') + { + bPeriodFound = true; + break; + } + } + + // URL found + if (bPeriodFound) + { + bURLFound = true; + } + } + else if (!Q_strnicmp(text + i, "http://", 7)) + { + bURLFound = true; + } + else if (!Q_strnicmp(text + i, "ftp://", 6)) + { + bURLFound = true; + } + else if (!Q_strnicmp(text + i, "steam://", 8)) + { + bURLFound = true; + } + else if (!Q_strnicmp(text + i, "steambeta://", 12)) + { + bURLFound = true; + } + else if (!Q_strnicmp(text + i, "mailto:", 7)) + { + bURLFound = true; + } + else if (!Q_strnicmp(text + i, "\\\\", 2)) + { + bURLFound = true; + } + + if (bURLFound) + { + if (i == startPos) + { + // we're at the Start of a URL, so parse that out + clickable = true; + int outIndex = 0; + while (text[i] != 0 && !iswspace(text[i])) + { + pchURLText[outIndex++] = text[i++]; + } + pchURLText[outIndex] = 0; + Q_strncpy( pchURL, pchURLText, cchURL ); + return i; + } + else + { + // no url + break; + } + } + + // increment and loop + i++; + } + + // nothing found; + // parse out the text before the end + clickable = false; + int outIndex = 0; + int fromIndex = startPos; + while ( fromIndex < i && outIndex < cchURLText ) + { + pchURLText[outIndex++] = text[fromIndex++]; + } + pchURLText[outIndex] = 0; + Q_strncpy( pchURL, pchURLText, cchURL ); + + return i; +} + +//----------------------------------------------------------------------------- +// Purpose: Executes the text-clicked command, which opens a web browser by +// default. +//----------------------------------------------------------------------------- +void RichText::OnTextClicked(const wchar_t *wszText) +{ + // Strip leading/trailing quotes, which may be present on href tags or may not. + const wchar_t *pwchURL = wszText; + if ( pwchURL[0] == L'"' || pwchURL[0] == L'\'' ) + pwchURL = wszText + 1; + + char ansi[2048]; + Q_UnicodeToUTF8( pwchURL, ansi, sizeof(ansi) ); + + size_t strLen = Q_strlen(ansi); + if ( strLen && ( ansi[strLen-1] == '"' || ansi[strLen] == '\'' ) ) + { + ansi[strLen-1] = 0; + } + + if ( m_hPanelToHandleClickingURLs.Get() ) + { + PostMessage( m_hPanelToHandleClickingURLs.Get(), new KeyValues( "URLClicked", "url", ansi ) ); + } + else + { + system()->ShellExecute( "open", ansi ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RichText::SetURLClickedHandler( Panel *pPanelToHandleClickMsg ) +{ + m_hPanelToHandleClickingURLs = pPanelToHandleClickMsg; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +bool RichText::IsScrollbarVisible() +{ + return _vertScrollBar->IsVisible(); +} + +void RichText::SetUnderlineFont( HFont font ) +{ + m_hFontUnderline = font; +} + +bool RichText::IsAllTextAlphaZero() const +{ + return m_bAllTextAlphaIsZero; +} + +bool RichText::HasText() const +{ + int c = m_TextStream.Count(); + if ( c == 0 ) + { + return false; + } + return true; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Returns the height of the base font +//----------------------------------------------------------------------------- +int RichText::GetLineHeight() +{ + return surface()->GetFontTall( _font ); +} + + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Run a global validation pass on all of our data structures and memory +// allocations. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void RichText::Validate( CValidator &validator, char *pchName ) +{ + validator.Push( "vgui::RichText", this, pchName ); + + ValidateObj( m_TextStream ); + ValidateObj( m_FormatStream ); + ValidateObj( m_LineBreaks ); + ValidateObj( _clickableTextPanels ); + validator.ClaimMemory( m_pszInitialText ); + + BaseClass::Validate( validator, "vgui::RichText" ); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + diff --git a/vgui2/vgui_controls/RotatingProgressBar.cpp b/vgui2/vgui_controls/RotatingProgressBar.cpp new file mode 100644 index 0000000..6ab9ccb --- /dev/null +++ b/vgui2/vgui_controls/RotatingProgressBar.cpp @@ -0,0 +1,200 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> +#include <math.h> +#include <stdio.h> + +#include <vgui_controls/RotatingProgressBar.h> + +#include <vgui/IVGui.h> +#include <vgui/ILocalize.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> + +#include "mathlib/mathlib.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( RotatingProgressBar ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +RotatingProgressBar::RotatingProgressBar(Panel *parent, const char *panelName) : ProgressBar(parent, panelName) +{ + m_flStartRadians = 0; + m_flEndRadians = 0; + m_flLastAngle = 0; + + m_nTextureId = -1; + m_pszImageName = NULL; + + m_flTickDelay = 30; + + ivgui()->AddTickSignal(GetVPanel(), m_flTickDelay ); + + SetPaintBorderEnabled(false); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +RotatingProgressBar::~RotatingProgressBar() +{ + if ( vgui::surface() && m_nTextureId != -1 ) + { + vgui::surface()->DestroyTextureID( m_nTextureId ); + m_nTextureId = -1; + } + + delete [] m_pszImageName; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RotatingProgressBar::ApplySettings(KeyValues *inResourceData) +{ + const char *imageName = inResourceData->GetString("image", ""); + if (*imageName) + { + SetImage( imageName ); + } + + // Find min and max rotations in radians + m_flStartRadians = DEG2RAD(inResourceData->GetFloat( "start_degrees", 0 ) ); + m_flEndRadians = DEG2RAD( inResourceData->GetFloat( "end_degrees", 0 ) ); + + // Start at 0 progress + m_flLastAngle = m_flStartRadians; + + // approach speed is specified in degrees per second. + // convert to radians per 1/30th of a second + float flDegressPerSecond = DEG2RAD( inResourceData->GetFloat( "approach_speed", 360.0 ) ); // default is super fast + m_flApproachSpeed = flDegressPerSecond * ( m_flTickDelay / 1000.0f ); // divide by number of frames in a second + + m_flRotOriginX = inResourceData->GetFloat( "rot_origin_x_percent", 0.5f ); + m_flRotOriginY = inResourceData->GetFloat( "rot_origin_y_percent", 0.5f ); + + m_flRotatingX = inResourceData->GetFloat( "rotating_x", 0 ); + m_flRotatingY = inResourceData->GetFloat( "rotating_y", 0 ); + m_flRotatingWide = inResourceData->GetFloat( "rotating_wide", 0 ); + m_flRotatingTall = inResourceData->GetFloat( "rotating_tall", 0 ); + + BaseClass::ApplySettings( inResourceData ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RotatingProgressBar::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + if ( m_pszImageName && strlen( m_pszImageName ) > 0 ) + { + if ( m_nTextureId == -1 ) + { + m_nTextureId = surface()->CreateNewTextureID(); + } + + surface()->DrawSetTextureFile( m_nTextureId, m_pszImageName, true, false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets an image by file name +//----------------------------------------------------------------------------- +void RotatingProgressBar::SetImage(const char *imageName) +{ + if ( m_pszImageName ) + { + delete [] m_pszImageName; + m_pszImageName = NULL; + } + + const char *pszDir = "vgui/"; + int len = Q_strlen(imageName) + 1; + len += strlen(pszDir); + m_pszImageName = new char[ len ]; + Q_snprintf( m_pszImageName, len, "%s%s", pszDir, imageName ); + InvalidateLayout(false, true); // force applyschemesettings to run +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RotatingProgressBar::PaintBackground() +{ + // No background +} + +//----------------------------------------------------------------------------- +// Purpose: Update event when we aren't drawing so we don't get huge sweeps +// when we start drawing it +//----------------------------------------------------------------------------- +void RotatingProgressBar::OnTick( void ) +{ + float flDesiredAngle = RemapVal( GetProgress(), 0.0, 1.0, m_flStartRadians, m_flEndRadians ); + m_flLastAngle = Approach( flDesiredAngle, m_flLastAngle, m_flApproachSpeed ); + + BaseClass::OnTick(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RotatingProgressBar::Paint() +{ + // we have an image that we rotate based on the progress, + // where '0' is not rotated,'90' is rotated 90 degrees to the right. + // Image is rotated around its center. + + // desired rotation is GetProgress() ( 0.0 -> 1.0 ) mapped into + // ( m_flStartDegrees -> m_flEndDegrees ) + + vgui::surface()->DrawSetTexture( m_nTextureId ); + vgui::surface()->DrawSetColor( Color(255,255,255,255) ); + + int wide, tall; + GetSize( wide, tall ); + + float mid_x = m_flRotatingX + m_flRotOriginX * m_flRotatingWide; + float mid_y = m_flRotatingY + m_flRotOriginY * m_flRotatingTall; + + Vertex_t vert[4]; + + vert[0].Init( Vector2D( m_flRotatingX, m_flRotatingY ), Vector2D(0,0) ); + vert[1].Init( Vector2D( m_flRotatingX+m_flRotatingWide, m_flRotatingY ), Vector2D(1,0) ); + vert[2].Init( Vector2D( m_flRotatingX+m_flRotatingWide, m_flRotatingY+m_flRotatingTall ), Vector2D(1,1) ); + vert[3].Init( Vector2D( m_flRotatingX, m_flRotatingY+m_flRotatingTall ), Vector2D(0,1) ); + + float flCosA = cos(m_flLastAngle); + float flSinA = sin(m_flLastAngle); + + // rotate each point around (mid_x, mid_y) by flAngle radians + for ( int i=0;i<4;i++ ) + { + Vector2D result; + + // subtract the (x,y) we're rotating around, we'll add it on at the end. + vert[i].m_Position.x -= mid_x; + vert[i].m_Position.y -= mid_y; + + result.x = ( vert[i].m_Position.x * flCosA - vert[i].m_Position.y * flSinA ) + mid_x; + result.y = ( vert[i].m_Position.x * flSinA + vert[i].m_Position.y * flCosA ) + mid_y; + + vert[i].m_Position = result; + } + + vgui::surface()->DrawTexturedPolygon( 4, vert ); +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/ScalableImagePanel.cpp b/vgui2/vgui_controls/ScalableImagePanel.cpp new file mode 100644 index 0000000..89f2e25 --- /dev/null +++ b/vgui2/vgui_controls/ScalableImagePanel.cpp @@ -0,0 +1,266 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> + +#include <vgui/IBorder.h> +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <vgui/IBorder.h> +#include <KeyValues.h> + +#include <vgui_controls/ScalableImagePanel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( ScalableImagePanel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ScalableImagePanel::ScalableImagePanel(Panel *parent, const char *name) : Panel(parent, name) +{ + m_iSrcCornerHeight = 0; + m_iSrcCornerWidth = 0; + + m_iCornerHeight = 0; + m_iCornerWidth = 0; + + m_pszImageName = NULL; + m_pszDrawColorName = NULL; + + m_DrawColor = Color(255,255,255,255); + + m_flCornerWidthPercent = 0; + m_flCornerHeightPercent = 0; + + m_iTextureID = surface()->CreateNewTextureID(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ScalableImagePanel::~ScalableImagePanel() +{ + delete [] m_pszImageName; + delete [] m_pszDrawColorName; + + if ( vgui::surface() && m_iTextureID != -1 ) + { + vgui::surface()->DestroyTextureID( m_iTextureID ); + m_iTextureID = -1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScalableImagePanel::SetImage(const char *imageName) +{ + if ( *imageName ) + { + char szImage[MAX_PATH]; + + const char *pszDir = "vgui/"; + int len = Q_strlen(imageName) + 1; + len += strlen(pszDir); + Q_snprintf( szImage, len, "%s%s", pszDir, imageName ); + + if ( m_pszImageName && V_stricmp( szImage, m_pszImageName ) == 0 ) + return; + + delete [] m_pszImageName; + m_pszImageName = new char[ len ]; + Q_strncpy(m_pszImageName, szImage, len ); + } + else + { + delete [] m_pszImageName; + m_pszImageName = NULL; + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScalableImagePanel::PaintBackground() +{ + int wide, tall; + GetSize(wide, tall); + + surface()->DrawSetColor( m_DrawColor.r(), m_DrawColor.g(), m_DrawColor.b(), GetAlpha() ); + surface()->DrawSetTexture( m_iTextureID ); + + int x = 0; + int y = 0; + + float uvx = 0; + float uvy = 0; + float uvw = 0, uvh = 0; + + float drawW, drawH; + + int row, col; + for ( row=0;row<3;row++ ) + { + x = 0; + uvx = 0; + + if ( row == 0 || row == 2 ) + { + //uvh - row 0 or 2, is src_corner_height + uvh = m_flCornerHeightPercent; + drawH = m_iCornerHeight; + } + else + { + //uvh - row 1, is tall - ( 2 * src_corner_height ) ( min 0 ) + uvh = max( 1.f - 2.f * m_flCornerHeightPercent, 0.0f ); + drawH = max( 0, ( tall - 2 * m_iCornerHeight ) ); + } + + for ( col=0;col<3;col++ ) + { + if ( col == 0 || col == 2 ) + { + //uvw - col 0 or 2, is src_corner_width + uvw = m_flCornerWidthPercent; + drawW = m_iCornerWidth; + } + else + { + //uvw - col 1, is wide - ( 2 * src_corner_width ) ( min 0 ) + uvw = max( 1.f - 2.f * m_flCornerWidthPercent, 0.0f ); + drawW = max( 0, ( wide - 2 * m_iCornerWidth ) ); + } + + Vector2D uv11( uvx, uvy ); + Vector2D uv21( uvx+uvw, uvy ); + Vector2D uv22( uvx+uvw, uvy+uvh ); + Vector2D uv12( uvx, uvy+uvh ); + + vgui::Vertex_t verts[4]; + verts[0].Init( Vector2D( x, y ), uv11 ); + verts[1].Init( Vector2D( x+drawW, y ), uv21 ); + verts[2].Init( Vector2D( x+drawW, y+drawH ), uv22 ); + verts[3].Init( Vector2D( x, y+drawH ), uv12 ); + + vgui::surface()->DrawTexturedPolygon( 4, verts ); + + x += drawW; + uvx += uvw; + } + + y += drawH; + uvy += uvh; + } + + vgui::surface()->DrawSetTexture(0); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets control settings for editing +//----------------------------------------------------------------------------- +void ScalableImagePanel::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + + if (m_pszDrawColorName) + { + outResourceData->SetString("drawcolor", m_pszDrawColorName); + } + + outResourceData->SetInt("src_corner_height", m_iSrcCornerHeight); + outResourceData->SetInt("src_corner_width", m_iSrcCornerWidth); + + outResourceData->SetInt("draw_corner_height", m_iCornerHeight); + outResourceData->SetInt("draw_corner_width", m_iCornerWidth); + + if (m_pszImageName) + { + outResourceData->SetString("image", m_pszImageName); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Applies designer settings from res file +//----------------------------------------------------------------------------- +void ScalableImagePanel::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + + delete [] m_pszDrawColorName; + m_pszDrawColorName = NULL; + + const char *pszDrawColor = inResourceData->GetString("drawcolor", ""); + if (*pszDrawColor) + { + int r = 0, g = 0, b = 0, a = 255; + int len = Q_strlen(pszDrawColor) + 1; + m_pszDrawColorName = new char[ len ]; + Q_strncpy( m_pszDrawColorName, pszDrawColor, len ); + + if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3) + { + // it's a direct color + m_DrawColor = Color(r, g, b, a); + } + else + { + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + m_DrawColor = pScheme->GetColor(pszDrawColor, Color(0, 0, 0, 0)); + } + } + + m_iSrcCornerHeight = inResourceData->GetInt( "src_corner_height" ); + m_iSrcCornerWidth = inResourceData->GetInt( "src_corner_width" ); + + m_iCornerHeight = inResourceData->GetInt( "draw_corner_height" ); + m_iCornerWidth = inResourceData->GetInt( "draw_corner_width" ); + + if ( IsProportional() ) + { + // scale the x and y up to our screen co-ords + m_iCornerHeight = scheme()->GetProportionalScaledValueEx(GetScheme(), m_iCornerHeight); + m_iCornerWidth = scheme()->GetProportionalScaledValueEx(GetScheme(), m_iCornerWidth); + } + + const char *imageName = inResourceData->GetString("image", ""); + SetImage( imageName ); + + InvalidateLayout(); +} + +void ScalableImagePanel::PerformLayout( void ) +{ + if ( m_pszImageName ) + { + surface()->DrawSetTextureFile( m_iTextureID, m_pszImageName, true, false); + } + + // get image dimensions, compare to m_iSrcCornerHeight, m_iSrcCornerWidth + int wide,tall; + surface()->DrawGetTextureSize( m_iTextureID, wide, tall ); + + m_flCornerWidthPercent = ( wide > 0 ) ? ( (float)m_iSrcCornerWidth / (float)wide ) : 0; + m_flCornerHeightPercent = ( tall > 0 ) ? ( (float)m_iSrcCornerHeight / (float)tall ) : 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Describes editing details +//----------------------------------------------------------------------------- +const char *ScalableImagePanel::GetDescription() +{ + static char buf[1024]; + _snprintf(buf, sizeof(buf), "%s string image, int src_corner_height, int src_corner_width, int draw_corner_height, int draw_corner_width", BaseClass::GetDescription()); + return buf; +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/ScrollBar.cpp b/vgui2/vgui_controls/ScrollBar.cpp new file mode 100644 index 0000000..94d7608 --- /dev/null +++ b/vgui2/vgui_controls/ScrollBar.cpp @@ -0,0 +1,802 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> + +#include <vgui/IScheme.h> +#include <vgui/ISystem.h> +#include <vgui/IInput.h> +#include <vgui/IImage.h> +#include <KeyValues.h> + +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/ScrollBarSlider.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/ImagePanel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +namespace +{ + +enum +{ // scroll bar will scroll a little, then continuous scroll like in windows + SCROLL_BAR_DELAY = 400, // default delay for all scroll bars + SCROLL_BAR_SPEED = 50, // this is how fast the bar scrolls when you hold down the arrow button + SCROLLBAR_DEFAULT_WIDTH = 17, +}; + +//----------------------------------------------------------------------------- +// Purpose: Scroll bar button-the arrow button that moves the slider up and down. +//----------------------------------------------------------------------------- +class ScrollBarButton : public Button +{ +public: + ScrollBarButton(Panel *parent, const char *panelName, const char *text) : Button(parent, panelName, text) + { + SetButtonActivationType(ACTIVATE_ONPRESSED); + + SetContentAlignment(Label::a_center); + } + + void OnMouseFocusTicked() + { + // pass straight up to parent + CallParentFunction(new KeyValues("MouseFocusTicked")); + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + Button::ApplySchemeSettings(pScheme); + + SetFont(pScheme->GetFont("Marlett", IsProportional() )); + SetDefaultBorder(pScheme->GetBorder("ScrollBarButtonBorder")); + SetDepressedBorder(pScheme->GetBorder("ScrollBarButtonDepressedBorder")); + + SetDefaultColor(GetSchemeColor("ScrollBarButton.FgColor", pScheme), GetSchemeColor("ScrollBarButton.BgColor", pScheme)); + SetArmedColor(GetSchemeColor("ScrollBarButton.ArmedFgColor", pScheme), GetSchemeColor("ScrollBarButton.ArmedBgColor", pScheme)); + SetDepressedColor(GetSchemeColor("ScrollBarButton.DepressedFgColor", pScheme), GetSchemeColor("ScrollBarButton.DepressedBgColor", pScheme)); + } + + // Don't request focus. + // This will keep cursor focus in main window in text entry windows. + virtual void OnMousePressed(MouseCode code) + { + if (!IsEnabled()) + return; + + if (!IsMouseClickEnabled(code)) + return; + + if (IsUseCaptureMouseEnabled()) + { + { + SetSelected(true); + Repaint(); + } + + // lock mouse input to going to this button + input()->SetMouseCapture(GetVPanel()); + } + } + virtual void OnMouseReleased(MouseCode code) + { + if (!IsEnabled()) + return; + + if (!IsMouseClickEnabled(code)) + return; + + if (IsUseCaptureMouseEnabled()) + { + { + SetSelected(false); + Repaint(); + } + + // lock mouse input to going to this button + input()->SetMouseCapture(NULL); + } + + if( input()->GetMouseOver() == GetVPanel() ) + { + SetArmed( true ); + } + } + +}; + +} + +vgui::Panel *ScrollBar_Vertical_Factory() +{ + return new ScrollBar(NULL, NULL, true ); +} + +vgui::Panel *ScrollBar_Horizontal_Factory() +{ + return new ScrollBar(NULL, NULL, false ); +} + +DECLARE_BUILD_FACTORY_CUSTOM_ALIAS( ScrollBar, ScrollBar_Vertical, ScrollBar_Vertical_Factory ); +DECLARE_BUILD_FACTORY_CUSTOM_ALIAS( ScrollBar, ScrollBar_Horizontal, ScrollBar_Horizontal_Factory ); +// Default is a horizontal one +DECLARE_BUILD_FACTORY_CUSTOM( ScrollBar, ScrollBar_Horizontal_Factory ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ScrollBar::ScrollBar(Panel *parent, const char *panelName, bool vertical) : Panel(parent, panelName) +{ + _slider=null; + _button[0]=null; + _button[1]=null; + _scrollDelay = SCROLL_BAR_DELAY; + _respond = true; + m_pUpArrow = NULL; + m_pLine = NULL; + m_pDownArrow = NULL; + m_pBox = NULL; + m_bNoButtons = false; + m_pOverriddenButtons[0] = NULL; + m_pOverriddenButtons[1] = NULL; + + if (vertical) + { + // FIXME: proportional changes needed??? + SetSlider(new ScrollBarSlider(NULL, "Slider", true)); + SetButton(new ScrollBarButton(NULL, "UpButton", "t"), 0); + SetButton(new ScrollBarButton(NULL, "DownButton", "u"), 1); + _button[0]->SetTextInset(0, 1); + _button[1]->SetTextInset(0, -1); + + SetSize(SCROLLBAR_DEFAULT_WIDTH, 64); + } + else + { + SetSlider(new ScrollBarSlider(NULL, NULL, false)); + SetButton(new ScrollBarButton(NULL, NULL, "w"), 0); + SetButton(new ScrollBarButton(NULL, NULL, "4"), 1); + _button[0]->SetTextInset(0, 0); + _button[1]->SetTextInset(0, 0); + + SetSize(64, SCROLLBAR_DEFAULT_WIDTH); + } + + Panel::SetPaintBorderEnabled(true); + Panel::SetPaintBackgroundEnabled(false); + Panel::SetPaintEnabled(true); + SetButtonPressedScrollValue(20); + SetBlockDragChaining( true ); + + Validate(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets up the width of the scrollbar according to the scheme +//----------------------------------------------------------------------------- +void ScrollBar::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + const char *resourceString = pScheme->GetResourceString("ScrollBar.Wide"); + + if (resourceString) + { + int value = atoi(resourceString); + if (IsProportional()) + { + value = scheme()->GetProportionalScaledValueEx(GetScheme(), value); + } + + if (_slider && _slider->IsVertical()) + { + // we're vertical, so reset the width + SetSize( value, GetTall() ); + } + else + { + // we're horizontal, so the width means the height + SetSize( GetWide(), value ); + } + } + + UpdateButtonsForImages(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the slider's Paint border enabled. +//----------------------------------------------------------------------------- +void ScrollBar::SetPaintBorderEnabled(bool state) +{ + if ( _slider ) + { + _slider->SetPaintBorderEnabled( state ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBar::SetPaintBackgroundEnabled(bool state) +{ + if ( _slider ) + { + _slider->SetPaintBackgroundEnabled( state ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBar::SetPaintEnabled(bool state) +{ + if ( _slider ) + { + _slider->SetPaintEnabled( state ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Layout the scroll bar and buttons on screen +//----------------------------------------------------------------------------- +void ScrollBar::PerformLayout() +{ + if (_slider) + { + int wide, tall; + GetPaintSize(wide,tall); + if(_slider->IsVertical()) + { + if ( m_bNoButtons ) + { + _slider->SetBounds(0, 0, wide, tall + 1); + } + else + { + _slider->SetBounds(0, wide, wide, tall-(wide*2)+1); + _button[0]->SetBounds(0,0, wide, wide ); + _button[1]->SetBounds(0,tall-wide ,wide, wide ); + } + } + else + { + if ( m_bNoButtons ) + { + _slider->SetBounds(tall, 0, wide, tall + 1); + } + else + { + _slider->SetBounds(tall, -1, wide-(tall*2)+1, tall + 1 ); + _button[0]->SetBounds(0, 0, tall, tall); + _button[1]->SetBounds(wide-tall, 0, tall, tall); + } + } + + // Place the images over the appropriate controls + int x,y; + if ( m_pUpArrow ) + { + _button[0]->GetBounds( x,y,wide,tall ); + m_pUpArrow->SetBounds( x,y,wide,tall ); + } + if ( m_pDownArrow ) + { + _button[1]->GetBounds( x,y,wide,tall ); + m_pDownArrow->SetBounds( x,y,wide,tall ); + } + if ( m_pLine ) + { + _slider->GetBounds( x,y,wide,tall ); + m_pLine->SetBounds( x,y,wide,tall ); + } + if ( m_pBox ) + { + m_pBox->SetBounds( 0, wide, wide, wide ); + } + + _slider->MoveToFront(); + // after resizing our child, we should remind it to perform a layout + _slider->InvalidateLayout(); + + UpdateSliderImages(); + } + + if ( m_bAutoHideButtons ) + { + SetScrollbarButtonsVisible( _slider->IsSliderVisible() ); + } + + // get tooltips to draw + Panel::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the value of the scroll bar slider. +//----------------------------------------------------------------------------- +void ScrollBar::SetValue(int value) +{ + _slider->SetValue(value); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the value of the scroll bar slider. +//----------------------------------------------------------------------------- +int ScrollBar::GetValue() +{ + return _slider->GetValue(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the range of the scroll bar slider. +// This the range of numbers the slider can scroll through. +//----------------------------------------------------------------------------- +void ScrollBar::SetRange(int min,int max) +{ + _slider->SetRange(min,max); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the range of the scroll bar slider. +// This the range of numbers the slider can scroll through. +//----------------------------------------------------------------------------- +void ScrollBar::GetRange(int &min, int &max) +{ + _slider->GetRange(min, max); +} + +//----------------------------------------------------------------------------- +// Purpose: Send a message when the slider is moved. +// Input : value - +//----------------------------------------------------------------------------- +void ScrollBar::SendSliderMoveMessage(int value) +{ + PostActionSignal(new KeyValues("ScrollBarSliderMoved", "position", value)); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the Slider is dragged by the user +// Input : value - +//----------------------------------------------------------------------------- +void ScrollBar::OnSliderMoved(int value) +{ + SendSliderMoveMessage(value); + UpdateSliderImages(); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if the scrollbar is vertical (true) or horizontal (false) +//----------------------------------------------------------------------------- +bool ScrollBar::IsVertical() +{ + return _slider->IsVertical(); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if the the scrollbar slider has full range. +// Normally if you have a scroll bar and the range goes from a to b and +// the slider is sized to c, the range will go from a to b-c. +// This makes it so the slider goes from a to b fully. +//----------------------------------------------------------------------------- +bool ScrollBar::HasFullRange() +{ + return _slider->HasFullRange(); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup the indexed scroll bar button with the input params. +//----------------------------------------------------------------------------- +//LEAK: new and old slider will leak +void ScrollBar::SetButton(Button *button, int index) +{ + if(_button[index]!=null) + { + _button[index]->SetParent((Panel *)NULL); + } + _button[index]=button; + _button[index]->SetParent(this); + _button[index]->AddActionSignalTarget(this); + _button[index]->SetCommand(new KeyValues("ScrollButtonPressed", "index", index)); + + Validate(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return the indexed scroll bar button +//----------------------------------------------------------------------------- +Button* ScrollBar::GetButton(int index) +{ + return _button[index]; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set up the slider. +//----------------------------------------------------------------------------- +//LEAK: new and old slider will leak +void ScrollBar::SetSlider(ScrollBarSlider *slider) +{ + if(_slider!=null) + { + _slider->SetParent((Panel *)NULL); + } + _slider=slider; + _slider->AddActionSignalTarget(this); + _slider->SetParent(this); + + Validate(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return a pointer to the slider. +//----------------------------------------------------------------------------- +ScrollBarSlider *ScrollBar::GetSlider() +{ + return _slider; +} + +Button *ScrollBar::GetDepressedButton( int iIndex ) +{ + if ( iIndex == 0 ) + return ( m_pOverriddenButtons[0] ? m_pOverriddenButtons[0] : _button[0] ); + return ( m_pOverriddenButtons[1] ? m_pOverriddenButtons[1] : _button[1] ); +} + +//----------------------------------------------------------------------------- +// Purpose: Scrolls in response to clicking and holding on up or down arrow +// The idea is to have the slider move one step then delay a bit and then +// the bar starts moving at normal speed. This gives a stepping feeling +// to just clicking an arrow once. +//----------------------------------------------------------------------------- +void ScrollBar::OnMouseFocusTicked() +{ + int direction = 0; + + // top button is down + if ( GetDepressedButton(0)->IsDepressed() ) + { + direction = -1; + } + // bottom top button is down + else if (GetDepressedButton(1)->IsDepressed()) + { + direction = 1; + } + + // a button is down + if ( direction != 0 ) + { + RespondToScrollArrow(direction); + if (_scrollDelay < system()->GetTimeMillis()) + { + _scrollDelay = system()->GetTimeMillis() + SCROLL_BAR_SPEED; + _respond = true; + } + else + { + _respond = false; + } + } + // a button is not down. + else + { + // if neither button is down keep delay at max + _scrollDelay = system()->GetTimeMillis() + SCROLL_BAR_DELAY; + _respond = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: move scroll bar in response to the first button +// Input: button and direction to move scroll bar when that button is pressed +// direction can only by +/- 1 +// Output: whether button is down or not +//----------------------------------------------------------------------------- +void ScrollBar::RespondToScrollArrow(int const direction) +{ + if (_respond) + { + int newValue = _slider->GetValue() + (direction * _buttonPressedScrollValue); + _slider->SetValue(newValue); + SendSliderMoveMessage(newValue); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Trigger layout changes when the window size is changed. +//----------------------------------------------------------------------------- +void ScrollBar::OnSizeChanged(int wide, int tall) +{ + InvalidateLayout(); + _slider->InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set how far the scroll bar slider moves when a scroll bar button is +// pressed. +//----------------------------------------------------------------------------- +void ScrollBar::SetButtonPressedScrollValue(int value) +{ + _buttonPressedScrollValue=value; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the range of the rangewindow. This is how many +// lines are displayed at one time +// in the window the scroll bar is attached to. +// This also controls the size of the slider, its size is proportional +// to the number of lines displayed / total number of lines. +//----------------------------------------------------------------------------- +void ScrollBar::SetRangeWindow(int rangeWindow) +{ + _slider->SetRangeWindow(rangeWindow); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the range of the rangewindow. This is how many +// lines are displayed at one time +// in the window the scroll bar is attached to. +// This also controls the size of the slider, its size is proportional +// to the number of lines displayed / total number of lines. +//----------------------------------------------------------------------------- +int ScrollBar::GetRangeWindow() +{ + return _slider->GetRangeWindow(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBar::Validate() +{ + if ( _slider != null ) + { + int buttonOffset = 0; + + for( int i=0; i<2; i++ ) + { + if( _button[i] != null ) + { + if( _button[i]->IsVisible() ) + { + if( _slider->IsVertical() ) + { + buttonOffset += _button[i]->GetTall(); + } + else + { + buttonOffset += _button[i]->GetWide(); + } + } + } + } + + _slider->SetButtonOffset(buttonOffset); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBar::SetScrollbarButtonsVisible(bool visible) +{ + for( int i=0; i<2; i++ ) + { + if( _button[i] != null ) + { + _button[i]->SetShouldPaint( visible ); + _button[i]->SetEnabled( visible ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBar::UseImages( const char *pszUpArrow, const char *pszDownArrow, const char *pszLine, const char *pszBox ) +{ + if ( pszUpArrow ) + { + if ( !m_pUpArrow ) + { + m_pUpArrow = new vgui::ImagePanel( this, "UpArrow" ); + if ( m_pUpArrow ) + { + m_pUpArrow->SetImage( pszUpArrow ); + m_pUpArrow->SetShouldScaleImage( true ); + m_pUpArrow->SetFgColor( Color( 255, 255, 255, 255 ) ); + m_pUpArrow->SetAlpha( 255 ); + m_pUpArrow->SetZPos( -1 ); + } + } + + m_pUpArrow->SetImage( pszUpArrow ); + m_pUpArrow->SetRotation( IsVertical() ? ROTATED_UNROTATED : ROTATED_CLOCKWISE_90 ); + } + else if ( m_pUpArrow ) + { + m_pUpArrow->MarkForDeletion(); + m_pUpArrow = NULL; + } + + if ( pszDownArrow ) + { + if ( !m_pDownArrow ) + { + m_pDownArrow = new vgui::ImagePanel( this, "DownArrow" ); + if ( m_pDownArrow ) + { + m_pDownArrow->SetShouldScaleImage( true ); + m_pDownArrow->SetFgColor( Color( 255, 255, 255, 255 ) ); + m_pDownArrow->SetAlpha( 255 ); + m_pDownArrow->SetZPos( -1 ); + } + } + + m_pDownArrow->SetImage( pszDownArrow ); + m_pDownArrow->SetRotation( IsVertical() ? ROTATED_UNROTATED : ROTATED_CLOCKWISE_90 ); + } + else if ( m_pDownArrow ) + { + m_pDownArrow->MarkForDeletion(); + m_pDownArrow = NULL; + } + + if ( pszLine ) + { + if ( !m_pLine ) + { + m_pLine = new ImagePanel( this, "Line" ); + if ( m_pLine ) + { + m_pLine->SetShouldScaleImage( true ); + m_pLine->SetZPos( -1 ); + } + } + + m_pLine->SetImage( pszLine ); + m_pLine->SetRotation( IsVertical() ? ROTATED_UNROTATED : ROTATED_CLOCKWISE_90 ); + } + else if ( m_pLine ) + { + m_pLine->MarkForDeletion(); + m_pLine = NULL; + } + + if ( pszBox ) + { + if ( !m_pBox ) + { + m_pBox = new ImagePanel( this, "Box" ); + if ( m_pBox ) + { + m_pBox->SetShouldScaleImage( true ); + m_pBox->SetZPos( -1 ); + } + } + + m_pBox->SetImage( pszBox ); + m_pBox->SetRotation( IsVertical() ? ROTATED_UNROTATED : ROTATED_CLOCKWISE_90 ); + } + else if ( m_pBox ) + { + m_pBox->MarkForDeletion(); + m_pBox = NULL; + } + + UpdateButtonsForImages(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBar::UpdateButtonsForImages( void ) +{ + // Turn off parts of our drawing based on which images we're replacing it with + if ( m_pUpArrow || m_pDownArrow ) + { + SetScrollbarButtonsVisible( false ); + _button[0]->SetPaintBorderEnabled( false ); + _button[1]->SetPaintBorderEnabled( false ); + m_bAutoHideButtons = false; + } + if ( m_pLine || m_pBox ) + { + SetPaintBackgroundEnabled( false ); + SetPaintBorderEnabled( false ); + + if ( _slider ) + { + _slider->SetPaintEnabled( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBar::UpdateSliderImages( void ) +{ + if ( m_pUpArrow && m_pDownArrow ) + { + // set the alpha on the up arrow + int nMin, nMax; + GetRange( nMin, nMax ); + int nScrollPos = GetValue(); + int nRangeWindow = GetRangeWindow(); + int nBottom = nMax - nRangeWindow; + if ( nBottom < 0 ) + { + nBottom = 0; + } + + // set the alpha on the up arrow + int nAlpha = ( nScrollPos - nMin <= 0 ) ? 90 : 255; + m_pUpArrow->SetAlpha( nAlpha ); + + // set the alpha on the down arrow + nAlpha = ( nScrollPos >= nBottom ) ? 90 : 255; + m_pDownArrow->SetAlpha( nAlpha ); + } + + if ( m_pLine && m_pBox ) + { + ScrollBarSlider *pSlider = GetSlider(); + if ( pSlider && pSlider->GetRangeWindow() > 0 ) + { + int x, y, w, t, min, max; + m_pLine->GetBounds( x, y, w, t ); + + // If our slider needs layout, force it to do it now + if ( pSlider->IsLayoutInvalid() ) + { + pSlider->InvalidateLayout( true ); + } + pSlider->GetNobPos( min, max ); + + if ( IsVertical() ) + { + m_pBox->SetBounds( x, y + min, w, ( max - min ) ); + } + else + { + m_pBox->SetBounds( x + min, 0, (max-min), t ); + } + } + } +} +void ScrollBar::ApplySettings( KeyValues *pInResourceData ) +{ + BaseClass::ApplySettings( pInResourceData ); + + m_bNoButtons = pInResourceData->GetBool( "nobuttons", false ); + + KeyValues *pSliderKV = pInResourceData->FindKey( "Slider" ); + if ( pSliderKV && _slider ) + { + _slider->ApplySettings( pSliderKV ); + } + + KeyValues *pDownButtonKV = pInResourceData->FindKey( "DownButton" ); + if ( pDownButtonKV && _button[0] ) + { + _button[0]->ApplySettings( pDownButtonKV ); + } + + KeyValues *pUpButtonKV = pInResourceData->FindKey( "UpButton" ); + if ( pUpButtonKV && _button[0] ) + { + _button[1]->ApplySettings( pUpButtonKV ); + } +} diff --git a/vgui2/vgui_controls/ScrollBarSlider.cpp b/vgui2/vgui_controls/ScrollBarSlider.cpp new file mode 100644 index 0000000..32df2fa --- /dev/null +++ b/vgui2/vgui_controls/ScrollBarSlider.cpp @@ -0,0 +1,606 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#define PROTECTED_THINGS_DISABLE + +#include <vgui/IBorder.h> +#include <vgui/IInput.h> +#include <vgui/ISystem.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/MouseCode.h> +#include <KeyValues.h> + +#include <vgui_controls/ScrollBarSlider.h> +#include <vgui_controls/Controls.h> + +#include <math.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// The ScrollBarSlider is the scroll bar nob that moves up and down in through a range. +//----------------------------------------------------------------------------- +ScrollBarSlider::ScrollBarSlider(Panel *parent, const char *panelName, bool vertical) : Panel(parent, panelName) +{ + _vertical=vertical; + _dragging=false; + _value=0; + _range[0]=0; + _range[1]=0; + _rangeWindow=0; + _buttonOffset=0; + _ScrollBarSliderBorder=NULL; + RecomputeNobPosFromValue(); + SetBlockDragChaining( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the size of the ScrollBarSlider nob +//----------------------------------------------------------------------------- +void ScrollBarSlider::SetSize(int wide,int tall) +{ + BaseClass::SetSize(wide,tall); + RecomputeNobPosFromValue(); +} + +//----------------------------------------------------------------------------- +// Purpose: Whether the scroll bar is vertical (true) or not (false) +//----------------------------------------------------------------------------- +bool ScrollBarSlider::IsVertical() +{ + return _vertical; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the ScrollBarSlider value of the nob. +//----------------------------------------------------------------------------- +void ScrollBarSlider::SetValue(int value) +{ + int oldValue = _value; + + if (value > _range[1] - _rangeWindow) + { + // note our scrolling range must take into acount _rangeWindow + value = _range[1] - _rangeWindow; + } + + if (value < _range[0]) + { + value = _range[0]; + } + + _value = value; + RecomputeNobPosFromValue(); + + if (_value != oldValue) + { + SendScrollBarSliderMovedMessage(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the ScrollBarSlider value of the nob. +//----------------------------------------------------------------------------- +int ScrollBarSlider::GetValue() +{ + return _value; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBarSlider::PerformLayout() +{ + RecomputeNobPosFromValue(); + BaseClass::PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Given the value of the ScrollBarSlider, adjust the ends of the nob. +//----------------------------------------------------------------------------- +void ScrollBarSlider::RecomputeNobPosFromValue() +{ + int wide, tall; + GetPaintSize(wide, tall); + + float fwide = (float)( wide - 1 ); + float ftall = (float)( tall - 1 ); + float frange = (float)(_range[1] -_range[0]); + float fvalue = (float)(_value - _range[0]); + float frangewindow = (float)(_rangeWindow); + float fper = ( frange != frangewindow ) ? fvalue / ( frange-frangewindow ) : 0; + +// Msg( "fwide: %f ftall: %f frange: %f fvalue: %f frangewindow: %f fper: %f\n", +// fwide, ftall, frange, fvalue, frangewindow, fper ); + + if ( frangewindow > 0 ) + { + if ( frange <= 0.0 ) + { + frange = 1.0; + } + + float width, length; + if (_vertical) + { + width = fwide; + length = ftall; + } + else + { + width = ftall; + length = fwide; + } + + // our size is proportional to frangewindow/frange + // the scroll bar nob's length reflects the amount of stuff on the screen + // vs the total amount of stuff we could scroll through in window + // so if a window showed half its contents and the other half is hidden the + // scroll bar's length is half the window. + // if everything is on the screen no nob is displayed + // frange is how many 'lines' of stuff we can display + // frangewindow is how many 'lines' are in the display window + + // proportion of whole window that is on screen + float proportion = frangewindow / frange; + float fnobsize = length * proportion; + if ( fnobsize < width ) fnobsize = (float)width; + + float freepixels = length - fnobsize; + + float firstpixel = freepixels * fper; + + _nobPos[0] = (int)( firstpixel ); + _nobPos[1] = (int)( firstpixel + fnobsize ); + + if ( _nobPos[1] > length ) + { + _nobPos[0] = (int)( length - fnobsize ); + _nobPos[1] = (int)length; + } + + } + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the ScrollBarSlider value using the location of the nob ends. +//----------------------------------------------------------------------------- +void ScrollBarSlider::RecomputeValueFromNobPos() +{ + int wide, tall; + GetPaintSize(wide, tall); + + float fwide = (float)( wide - 1 ); + float ftall = (float)( tall - 1 ); + float frange = (float)( _range[1] - _range[0] ); + float fvalue = (float)( _value - _range[0] ); + float fnob = (float)_nobPos[0]; + float frangewindow = (float)(_rangeWindow); + + if ( frangewindow > 0 ) + { + if ( frange <= 0.0 ) + { + frange = 1.0; + } + + // set local width and length + float width, length; + if ( _vertical ) + { + width = fwide; + length = ftall; + } + else + { + width = ftall; + length = fwide; + } + + // calculate the size of the nob + float proportion = frangewindow / frange; + float fnobsize = length * proportion; + + if ( fnobsize < width ) + { + fnobsize = width; + } + + // Our scroll bar actually doesnt scroll through all frange lines in the truerange, we + // actually only scroll through frange-frangewindow number of lines so we must take that + // into account when we calculate the value + // convert to our local size system + + // Make sure we don't divide by zero + if ( length - fnobsize == 0 ) + { + fvalue = 0.0f; + } + else + { + fvalue = (frange - frangewindow) * ( fnob / ( length - fnobsize ) ); + } + } + + // check to see if we should just snap to the bottom + if (fabs(fvalue + _rangeWindow - _range[1]) < (0.01f * frange)) + { + // snap to the end + _value = _range[1] - _rangeWindow; + } + else + { + // Take care of rounding issues. + _value = (int)( fvalue + _range[0] + 0.5); + } + + // Clamp final result + _value = ( _value < (_range[1] - _rangeWindow) ) ? _value : (_range[1] - _rangeWindow); + + if (_value < _range[0]) + { + _value = _range[0]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check if the ScrollBarSlider can move through one or more pixels per +// unit of its range. +//----------------------------------------------------------------------------- +bool ScrollBarSlider::HasFullRange() +{ + int wide, tall; + GetPaintSize(wide, tall); + + float frangewindow = (float)(_rangeWindow); + + float checkAgainst = 0; + if(_vertical) + { + checkAgainst = (float)tall; + } + else + { + checkAgainst = (float)wide; + } + + if ( frangewindow > 0 ) + { + if( frangewindow <= ( checkAgainst + _buttonOffset ) ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Inform other watchers that the ScrollBarSlider was moved +//----------------------------------------------------------------------------- +void ScrollBarSlider::SendScrollBarSliderMovedMessage() +{ + // send a changed message + PostActionSignal(new KeyValues("ScrollBarSliderMoved", "position", _value)); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this slider is actually drawing itself +//----------------------------------------------------------------------------- +bool ScrollBarSlider::IsSliderVisible( void ) +{ + int itemRange = _range[1] - _range[0]; + + // Don't draw nob, no items in list + if ( itemRange <= 0 ) + return false ; + + // Not enough range + if ( itemRange <= _rangeWindow ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBarSlider::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("ScrollBarSlider.FgColor", pScheme)); + SetBgColor(GetSchemeColor("ScrollBarSlider.BgColor", pScheme)); + + IBorder *newBorder = pScheme->GetBorder("ScrollBarSliderBorder"); + + if ( newBorder ) + { + _ScrollBarSliderBorder = newBorder; + } + else + { + _ScrollBarSliderBorder = pScheme->GetBorder("ButtonBorder"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBarSlider::ApplySettings( KeyValues *pInResourceData ) +{ + BaseClass::ApplySettings( pInResourceData ); + + const char *pButtonBorderName = pInResourceData->GetString( "ButtonBorder", NULL ); + if ( pButtonBorderName ) + { + _ScrollBarSliderBorder = vgui::scheme()->GetIScheme( GetScheme() )->GetBorder( pButtonBorderName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBarSlider::Paint() +{ + int wide,tall; + GetPaintSize(wide,tall); + + if ( !IsSliderVisible() ) + return; + + Color col = GetFgColor(); + surface()->DrawSetColor(col); + + if (_vertical) + { + if ( GetPaintBackgroundType() == 2 ) + { + DrawBox( 1, _nobPos[0], wide - 2, _nobPos[1] - _nobPos[0], col, 1.0f ); + } + else + { + // Nob + surface()->DrawFilledRect(1, _nobPos[0], wide - 2, _nobPos[1]); + } + + // border + if (_ScrollBarSliderBorder) + { + _ScrollBarSliderBorder->Paint(0, _nobPos[0], wide, _nobPos[1]); + } + } + else + { + // horizontal nob + surface()->DrawFilledRect(_nobPos[0], 1, _nobPos[1], tall - 2 ); + + // border + if (_ScrollBarSliderBorder) + { + _ScrollBarSliderBorder->Paint(_nobPos[0] - 1, 1, _nobPos[1], tall ); + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBarSlider::PaintBackground() +{ +// BaseClass::PaintBackground(); + + int wide,tall; + GetPaintSize(wide,tall); + surface()->DrawSetColor(GetBgColor()); + surface()->DrawFilledRect(0, 0, wide-1, tall-1); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the range of the ScrollBarSlider +//----------------------------------------------------------------------------- +void ScrollBarSlider::SetRange(int min,int max) +{ + if(max<min) + { + max=min; + } + + if(min>max) + { + min=max; + } + + _range[0]=min; + _range[1]=max; + + // update the value (forces it within the range) + SetValue( _value ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the range values of the ScrollBarSlider +//----------------------------------------------------------------------------- +void ScrollBarSlider::GetRange(int& min,int& max) +{ + min=_range[0]; + max=_range[1]; +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to cursor movements, we only care about clicking and dragging +//----------------------------------------------------------------------------- +void ScrollBarSlider::OnCursorMoved(int x,int y) +{ + if (!_dragging) + { + return; + } + +// input()->GetCursorPos(x, y); +// ScreenToLocal(x, y); + + int wide, tall; + GetPaintSize(wide, tall); + + if (_vertical) + { + _nobPos[0] = _nobDragStartPos[0] + (y - _dragStartPos[1]); + _nobPos[1] = _nobDragStartPos[1] + (y - _dragStartPos[1]); + + if (_nobPos[1] > tall) + { + _nobPos[0] = tall - (_nobPos[1] - _nobPos[0]); + _nobPos[1] = tall; + SetValue( _range[1] - _rangeWindow ); + } + } + else + { + _nobPos[0] = _nobDragStartPos[0] + (x - _dragStartPos[0]); + _nobPos[1] = _nobDragStartPos[1] + (x - _dragStartPos[0]); + + if (_nobPos[1] > wide) + { + _nobPos[0] = wide - (_nobPos[1] - _nobPos[0]); + _nobPos[1] = wide; + } + + } + if (_nobPos[0] < 0) + { + _nobPos[1] = _nobPos[1] - _nobPos[0]; + _nobPos[0] = 0; + SetValue(0); + } + + InvalidateLayout(); // not invalidatelayout - because it won't draw while we're scrolling the slider + RecomputeValueFromNobPos(); +// Repaint(); + SendScrollBarSliderMovedMessage(); +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to mouse clicks on the ScrollBarSlider +//----------------------------------------------------------------------------- +void ScrollBarSlider::OnMousePressed(MouseCode code) +{ + int x,y; + input()->GetCursorPos(x,y); + ScreenToLocal(x,y); + + if (_vertical) + { + if ((y >= _nobPos[0]) && (y < _nobPos[1])) + { + _dragging = true; + input()->SetMouseCapture(GetVPanel()); + _nobDragStartPos[0] = _nobPos[0]; + _nobDragStartPos[1] = _nobPos[1]; + _dragStartPos[0] = x; + _dragStartPos[1] = y; + } + else if (y < _nobPos[0]) + { + // jump the bar up by the range window + int val = GetValue(); + val -= _rangeWindow; + SetValue(val); + } + else if (y >= _nobPos[1]) + { + // jump the bar down by the range window + int val = GetValue(); + val += _rangeWindow; + SetValue(val); + } + } + else + { + if((x >= _nobPos[0]) && (x < _nobPos[1])) + { + _dragging = true; + input()->SetMouseCapture(GetVPanel()); + _nobDragStartPos[0] = _nobPos[0]; + _nobDragStartPos[1] = _nobPos[1]; + _dragStartPos[0] = x; + _dragStartPos[1] = y; + } + else if (x < _nobPos[0]) + { + // jump the bar up by the range window + int val = GetValue(); + val -= _rangeWindow; + SetValue(val); + } + else if (x >= _nobPos[1]) + { + // jump the bar down by the range window + int val = GetValue(); + val += _rangeWindow; + SetValue(val); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Treat double clicks as single clicks +//----------------------------------------------------------------------------- +void ScrollBarSlider::OnMouseDoublePressed(MouseCode code) +{ + OnMousePressed(code); +} + +//----------------------------------------------------------------------------- +// Purpose: Stop looking for mouse events when mouse is up. +//----------------------------------------------------------------------------- +void ScrollBarSlider::OnMouseReleased(MouseCode code) +{ + _dragging = false; + input()->SetMouseCapture(null); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the position of the ends of the ScrollBarSlider. +//----------------------------------------------------------------------------- +void ScrollBarSlider::GetNobPos(int& min, int& max) +{ + min=_nobPos[0]; + max=_nobPos[1]; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the number of lines visible in the window the ScrollBarSlider is attached to +//----------------------------------------------------------------------------- +void ScrollBarSlider::SetRangeWindow(int rangeWindow) +{ + _rangeWindow = rangeWindow; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of lines visible in the window the ScrollBarSlider is attached to +//----------------------------------------------------------------------------- +int ScrollBarSlider::GetRangeWindow() +{ + return _rangeWindow; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ScrollBarSlider::SetButtonOffset(int buttonOffset) +{ + _buttonOffset = buttonOffset; +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/ScrollableEditablePanel.cpp b/vgui2/vgui_controls/ScrollableEditablePanel.cpp new file mode 100644 index 0000000..d734dba --- /dev/null +++ b/vgui2/vgui_controls/ScrollableEditablePanel.cpp @@ -0,0 +1,86 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_controls/ScrollableEditablePanel.h" +#include "vgui_controls/ScrollBar.h" +#include "vgui_controls/ScrollBarSlider.h" +#include "vgui_controls/Button.h" +#include "KeyValues.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +using namespace vgui; + +ScrollableEditablePanel::ScrollableEditablePanel( vgui::Panel *pParent, vgui::EditablePanel *pChild, const char *pName ) : + BaseClass( pParent, pName ) +{ + m_pChild = pChild; + m_pChild->SetParent( this ); + + m_pScrollBar = new vgui::ScrollBar( this, "VerticalScrollBar", true ); + m_pScrollBar->SetWide( 16 ); + m_pScrollBar->SetAutoResize( PIN_TOPRIGHT, AUTORESIZE_DOWN, 0, 0, -16, 0 ); + m_pScrollBar->AddActionSignalTarget( this ); +} + +void ScrollableEditablePanel::ApplySettings( KeyValues *pInResourceData ) +{ + BaseClass::ApplySettings( pInResourceData ); + + KeyValues *pScrollbarKV = pInResourceData->FindKey( "Scrollbar" ); + if ( pScrollbarKV ) + { + m_pScrollBar->ApplySettings( pScrollbarKV ); + } +} + +void ScrollableEditablePanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + m_pChild->SetWide( GetWide() - m_pScrollBar->GetWide() ); + m_pScrollBar->SetRange( 0, m_pChild->GetTall() ); + m_pScrollBar->SetRangeWindow( GetTall() ); + + if ( m_pScrollBar->GetSlider() ) + { + m_pScrollBar->GetSlider()->SetFgColor( GetFgColor() ); + } + if ( m_pScrollBar->GetButton(0) ) + { + m_pScrollBar->GetButton(0)->SetFgColor( GetFgColor() ); + } + if ( m_pScrollBar->GetButton(1) ) + { + m_pScrollBar->GetButton(1)->SetFgColor( GetFgColor() ); + } +} + + +//----------------------------------------------------------------------------- +// Called when the scroll bar moves +//----------------------------------------------------------------------------- +void ScrollableEditablePanel::OnScrollBarSliderMoved() +{ + InvalidateLayout(); + + int nScrollAmount = m_pScrollBar->GetValue(); + m_pChild->SetPos( 0, -nScrollAmount ); +} + +//----------------------------------------------------------------------------- +// respond to mouse wheel events +//----------------------------------------------------------------------------- +void ScrollableEditablePanel::OnMouseWheeled(int delta) +{ + int val = m_pScrollBar->GetValue(); + val -= (delta * 50); + m_pScrollBar->SetValue( val ); +} + diff --git a/vgui2/vgui_controls/SectionedListPanel.cpp b/vgui2/vgui_controls/SectionedListPanel.cpp new file mode 100644 index 0000000..d3939e6 --- /dev/null +++ b/vgui2/vgui_controls/SectionedListPanel.cpp @@ -0,0 +1,2271 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> + +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/ILocalize.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> + +#include <vgui_controls/SectionedListPanel.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/ImageList.h> + +#include "utlvector.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +namespace vgui +{ + +//----------------------------------------------------------------------------- +// Purpose: header label that separates and names each section +//----------------------------------------------------------------------------- +SectionedListPanelHeader::SectionedListPanelHeader(SectionedListPanel *parent, const char *name, int sectionID) : Label(parent, name, "") +{ + m_pListPanel = parent; + m_iSectionID = sectionID; + SetTextImageIndex(-1); + ClearImages(); + SetPaintBackgroundEnabled( false ); + m_bDrawDividerBar = true; +} + +SectionedListPanelHeader::SectionedListPanelHeader(SectionedListPanel *parent, const wchar_t *name, int sectionID) : Label(parent, "SectionHeader", "") +{ + SetText(name); + SetVisible(false); + m_pListPanel = parent; + m_iSectionID = sectionID; + SetTextImageIndex(-1); + ClearImages(); + m_bDrawDividerBar = true; +} + +void SectionedListPanelHeader::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("SectionedListPanel.HeaderTextColor", pScheme)); + m_SectionDividerColor = GetSchemeColor("SectionedListPanel.DividerColor", pScheme); + SetBgColor(GetSchemeColor("SectionedListPanelHeader.BgColor", GetBgColor(), pScheme)); + SetFont(pScheme->GetFont("DefaultVerySmall", IsProportional())); + ClearImages(); + + HFont hFont = m_pListPanel->GetHeaderFont(); + if ( hFont != INVALID_FONT ) + { + SetFont( hFont ); + } + else + { + SetFont(pScheme->GetFont("DefaultVerySmall", IsProportional())); + } +} + +void SectionedListPanelHeader::Paint() +{ + BaseClass::Paint(); + + if ( m_bDrawDividerBar ) + { + int x, y, wide, tall; + GetBounds(x, y, wide, tall); + + y = (tall - 2); // draw the line under the panel + + surface()->DrawSetColor(m_SectionDividerColor); + surface()->DrawFilledRect(1, y, GetWide() - 2, y + 1); + } +} + +void SectionedListPanelHeader::SetColor(Color col) +{ + m_SectionDividerColor = col; + SetFgColor(col); +} +void SectionedListPanelHeader::SetDividerColor(Color col ) +{ + m_SectionDividerColor = col; +} + +void SectionedListPanelHeader::PerformLayout() +{ + BaseClass::PerformLayout(); + + // set up the text in the header + int colCount = m_pListPanel->GetColumnCountBySection(m_iSectionID); + if (colCount != GetImageCount()) + { + // rebuild the image list + for (int i = 0; i < colCount; i++) + { + int columnFlags = m_pListPanel->GetColumnFlagsBySection(m_iSectionID, i); + IImage *image = NULL; + if (columnFlags & SectionedListPanel::HEADER_IMAGE) + { + //!! need some kind of image reference + image = NULL; + } + else + { + TextImage *textImage = new TextImage(""); + textImage->SetFont(GetFont()); + HFont fallback = m_pListPanel->GetColumnFallbackFontBySection( m_iSectionID, i ); + if ( INVALID_FONT != fallback ) + { + textImage->SetUseFallbackFont( true, fallback ); + } + textImage->SetColor(GetFgColor()); + image = textImage; + } + + SetImageAtIndex(i, image, 0); + } + } + + for (int repeat = 0; repeat <= 1; repeat++) + { + int xpos = 0; + for (int i = 0; i < colCount; i++) + { + int columnFlags = m_pListPanel->GetColumnFlagsBySection(m_iSectionID, i); + int columnWidth = m_pListPanel->GetColumnWidthBySection(m_iSectionID, i); + int maxWidth = columnWidth; + + IImage *image = GetImageAtIndex(i); + if (!image) + { + xpos += columnWidth; + continue; + } + + // set the image position within the label + int contentWide, wide, tall; + image->GetContentSize(wide, tall); + contentWide = wide; + + // see if we can draw over the next few column headers (if we're left-aligned) + if (!(columnFlags & SectionedListPanel::COLUMN_RIGHT)) + { + for (int j = i + 1; j < colCount; j++) + { + // see if this column header has anything for a header + int iwide = 0, itall = 0; + if (GetImageAtIndex(j)) + { + GetImageAtIndex(j)->GetContentSize(iwide, itall); + } + + if (iwide == 0) + { + // it's a blank header, ok to draw over it + maxWidth += m_pListPanel->GetColumnWidthBySection(m_iSectionID, j); + } + } + } + if (maxWidth >= 0) + { + wide = maxWidth; + } + + if (columnFlags & SectionedListPanel::COLUMN_RIGHT) + { + SetImageBounds(i, xpos + wide - contentWide, wide - SectionedListPanel::COLUMN_DATA_GAP); + } + else + { + SetImageBounds(i, xpos, wide - SectionedListPanel::COLUMN_DATA_GAP); + } + xpos += columnWidth; + + if (!(columnFlags & SectionedListPanel::HEADER_IMAGE)) + { + Assert(dynamic_cast<TextImage *>(image) != NULL); + TextImage *textImage = (TextImage *)image; + textImage->SetFont(GetFont()); + textImage->SetText(m_pListPanel->GetColumnTextBySection(m_iSectionID, i)); + textImage->ResizeImageToContentMaxWidth( maxWidth ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Individual items in the list +//----------------------------------------------------------------------------- +class CItemButton : public Label +{ + DECLARE_CLASS_SIMPLE( CItemButton, Label ); + +public: + CItemButton(SectionedListPanel *parent, int itemID) : Label(parent, NULL, "< item >") + { + m_pListPanel = parent; + m_iID = itemID; + m_pData = NULL; + Clear(); + m_nHorizFillInset = 0; + } + + ~CItemButton() + { + // free all the keyvalues + if (m_pData) + { + m_pData->deleteThis(); + } + + // clear any section data + SetSectionID(-1); + } + + void Clear() + { + m_bSelected = false; + m_bOverrideColors = false; + m_iSectionID = -1; + SetPaintBackgroundEnabled( false ); + SetTextImageIndex(-1); + ClearImages(); + } + + int GetID() + { + return m_iID; + } + + void SetID(int itemID) + { + m_iID = itemID; + } + + int GetSectionID() + { + return m_iSectionID; + } + + void SetSectionID(int sectionID) + { + if (sectionID != m_iSectionID) + { + // free any existing textimage list + ClearImages(); + // delete any images we've created + for (int i = 0; i < m_TextImages.Count(); i++) + { + delete m_TextImages[i]; + } + m_TextImages.RemoveAll(); + // mark the list as needing rebuilding + InvalidateLayout(); + } + m_iSectionID = sectionID; + } + + void SetData(const KeyValues *data) + { + if (m_pData) + { + m_pData->deleteThis(); + } + + m_pData = data->MakeCopy(); + InvalidateLayout(); + } + + KeyValues *GetData() + { + return m_pData; + } + + virtual void PerformLayout() + { + // get our button text + int colCount = m_pListPanel->GetColumnCountBySection(m_iSectionID); + if (!m_pData || colCount < 1) + { + SetText("< unset >"); + } + else + { + if (colCount != GetImageCount()) + { + // rebuild the image list + for (int i = 0; i < colCount; i++) + { + int columnFlags = m_pListPanel->GetColumnFlagsBySection(m_iSectionID, i); + if (!(columnFlags & SectionedListPanel::COLUMN_IMAGE)) + { + TextImage *image = new TextImage(""); + m_TextImages.AddToTail(image); + image->SetFont( GetFont() ); + HFont fallback = m_pListPanel->GetColumnFallbackFontBySection( m_iSectionID, i ); + if ( INVALID_FONT != fallback ) + { + image->SetUseFallbackFont( true, fallback ); + } + SetImageAtIndex(i, image, 0); + } + } + + {for ( int i = GetImageCount(); i < colCount; i++ ) // make sure we have enough image slots + { + AddImage( NULL, 0 ); + }} + } + + // set the text for each column + int xpos = 0; + for (int i = 0; i < colCount; i++) + { + const char *keyname = m_pListPanel->GetColumnNameBySection(m_iSectionID, i); + + int columnFlags = m_pListPanel->GetColumnFlagsBySection(m_iSectionID, i); + int maxWidth = m_pListPanel->GetColumnWidthBySection(m_iSectionID, i); + + IImage *image = NULL; + if (columnFlags & SectionedListPanel::COLUMN_IMAGE) + { + // lookup which image is being referred to + if (m_pListPanel->m_pImageList) + { + int imageIndex = m_pData->GetInt(keyname, 0); + if (m_pListPanel->m_pImageList->IsValidIndex(imageIndex)) + { + // 0 is always the blank image + if (imageIndex > 0) + { + image = m_pListPanel->m_pImageList->GetImage(imageIndex); + SetImageAtIndex(i, image, 0); + } + } + else + { + // this is mildly valid (CGamesList hits it because of the way it uses the image indices) + // Assert(!("Image index out of range for ImageList in SectionedListPanel")); + } + } + else + { + Assert(!("Images columns used in SectionedListPanel with no ImageList set")); + } + } + else + { + TextImage *textImage = dynamic_cast<TextImage *>(GetImageAtIndex(i)); + if (textImage) + { + const wchar* pwszOverride = m_pData->GetWString( keyname, NULL ); + if ( pwszOverride && pwszOverride[0] != '#' ) + { + textImage->SetText( pwszOverride ); + } + else + { + textImage->SetText(m_pData->GetString(keyname, "")); + } + textImage->ResizeImageToContentMaxWidth( maxWidth ); + + // set the text color based on the selection state - if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected + VPANEL focus = input()->GetFocus(); + if ( !m_bOverrideColors ) + { + if (IsSelected() && !m_pListPanel->IsInEditMode()) + { + if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))) + { + textImage->SetColor(m_ArmedFgColor2); + } + else + { + textImage->SetColor(m_OutOfFocusSelectedTextColor); + } + } + else if (columnFlags & SectionedListPanel::COLUMN_BRIGHT) + { + textImage->SetColor(m_ArmedFgColor1); + } + else + { + textImage->SetColor(m_FgColor2); + } + } + else + { + // custom colors + if (IsSelected() && (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent())))) + { + textImage->SetColor(m_ArmedFgColor2); + } + else + { + Color *clrOverride = m_pListPanel->GetColorOverrideForCell(m_iSectionID, m_iID, i); + textImage->SetColor( (clrOverride != NULL) ? *clrOverride : GetFgColor()); + } + } + } + image = textImage; + } + + // set the image position within the label + int imageWide = 0, tall = 0; + int wide; + if (image) + { + image->GetContentSize(imageWide, tall); + } + if (maxWidth >= 0) + { + wide = maxWidth; + } + else + { + wide = imageWide; + } + + if (i == 0 && !(columnFlags & SectionedListPanel::COLUMN_IMAGE)) + { + // first column has an extra indent + SetImageBounds(i, xpos + SectionedListPanel::COLUMN_DATA_INDENT, wide - (SectionedListPanel::COLUMN_DATA_INDENT + SectionedListPanel::COLUMN_DATA_GAP)); + } + else + { + if (columnFlags & SectionedListPanel::COLUMN_CENTER) + { + int offSet = (wide / 2) - (imageWide / 2); + SetImageBounds(i, xpos + offSet, wide - offSet - SectionedListPanel::COLUMN_DATA_GAP); + } + else if (columnFlags & SectionedListPanel::COLUMN_RIGHT) + { + SetImageBounds(i, xpos + wide - imageWide, wide - SectionedListPanel::COLUMN_DATA_GAP); + } + else + { + SetImageBounds(i, xpos, wide - SectionedListPanel::COLUMN_DATA_GAP); + } + } + xpos += wide; + } + } + + BaseClass::PerformLayout(); + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + BaseClass::ApplySchemeSettings(pScheme); + + m_ArmedFgColor1 = GetSchemeColor("SectionedListPanel.BrightTextColor", pScheme); + m_ArmedFgColor2 = GetSchemeColor("SectionedListPanel.SelectedTextColor", pScheme); + m_OutOfFocusSelectedTextColor = GetSchemeColor("SectionedListPanel.OutOfFocusSelectedTextColor", pScheme); + m_ArmedBgColor = GetSchemeColor("SectionedListPanel.SelectedBgColor", pScheme); + + m_FgColor2 = GetSchemeColor("SectionedListPanel.TextColor", pScheme); + + m_BgColor = GetSchemeColor("SectionedListPanel.BgColor", GetBgColor(), pScheme); + m_SelectionBG2Color = GetSchemeColor("SectionedListPanel.OutOfFocusSelectedBgColor", pScheme); + + + HFont hFont = m_pListPanel->GetRowFont(); + if ( hFont != INVALID_FONT ) + { + SetFont( hFont ); + } + else + { + const char *fontName = pScheme->GetResourceString( "SectionedListPanel.Font" ); + HFont font = pScheme->GetFont(fontName, IsProportional()); + if ( font != INVALID_FONT ) + { + SetFont( font ); + } + } + + ClearImages(); + } + + virtual void PaintBackground() + { + int wide, tall; + GetSize(wide, tall); + + if (IsSelected() && !m_pListPanel->IsInEditMode()) + { + VPANEL focus = input()->GetFocus(); + // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected + if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))) + { + surface()->DrawSetColor(m_ArmedBgColor); + } + else + { + surface()->DrawSetColor(m_SelectionBG2Color); + } + } + else + { + surface()->DrawSetColor(GetBgColor()); + } + surface()->DrawFilledRect(0, m_nHorizFillInset, wide, tall - m_nHorizFillInset); + } + + virtual void Paint() + { + BaseClass::Paint(); + + if ( !m_bShowColumns ) + return; + + // Debugging code to show column widths + int wide, tall; + GetSize(wide, tall); + surface()->DrawSetColor( 255,255,255,255 ); + surface()->DrawOutlinedRect(0, 0, wide, tall); + + int colCount = m_pListPanel->GetColumnCountBySection(m_iSectionID); + if (m_pData && colCount >= 0) + { + int xpos = 0; + for (int i = 0; i < colCount; i++) + { + const char *keyname = m_pListPanel->GetColumnNameBySection(m_iSectionID, i); + int columnFlags = m_pListPanel->GetColumnFlagsBySection(m_iSectionID, i); + int maxWidth = m_pListPanel->GetColumnWidthBySection(m_iSectionID, i); + + IImage *image = NULL; + if (columnFlags & SectionedListPanel::COLUMN_IMAGE) + { + // lookup which image is being referred to + if (m_pListPanel->m_pImageList) + { + int imageIndex = m_pData->GetInt(keyname, 0); + if (m_pListPanel->m_pImageList->IsValidIndex(imageIndex)) + { + if (imageIndex > 0) + { + image = m_pListPanel->m_pImageList->GetImage(imageIndex); + } + } + } + } + else + { + image = GetImageAtIndex(i); + } + + int imageWide = 0; + if (image) + { + image->GetContentSize(imageWide, tall); + } + if (maxWidth >= 0) + { + wide = maxWidth; + } + else + { + wide = imageWide; + } + + xpos += wide;//max(maxWidth,wide); + surface()->DrawOutlinedRect( xpos, 0, xpos, GetTall() ); + } + } + } + + virtual void OnMousePressed(MouseCode code) + { + if ( m_pListPanel && m_pListPanel->IsClickable() && IsEnabled() ) + { + if (code == MOUSE_LEFT) + { + m_pListPanel->PostActionSignal(new KeyValues("ItemLeftClick", "itemID", m_iID)); + } + if (code == MOUSE_RIGHT) + { + KeyValues *msg = new KeyValues("ItemContextMenu", "itemID", m_iID); + msg->SetPtr("SubPanel", this); + m_pListPanel->PostActionSignal(msg); + } + + m_pListPanel->SetSelectedItem(this); + } + } + + void SetSelected(bool state) + { + if (m_bSelected != state) + { + if (state) + { + RequestFocus(); + } + m_bSelected = state; + SetPaintBackgroundEnabled( state ); + InvalidateLayout(); + Repaint(); + } + } + + bool IsSelected() + { + return m_bSelected; + } + + virtual void OnSetFocus() + { + InvalidateLayout(); // force the layout to be redone so we can change text color according to focus + BaseClass::OnSetFocus(); + } + + virtual void OnKillFocus() + { + InvalidateLayout(); // force the layout to be redone so we can change text color according to focus + BaseClass::OnSetFocus(); + } + + virtual void OnMouseDoublePressed(MouseCode code) + { + //============================================================================= + // HPE_BEGIN: + // [tj] Only do this if clicking is enabled. + //============================================================================= + if (m_pListPanel && m_pListPanel->IsClickable()) + { + if (code == MOUSE_LEFT) + { + m_pListPanel->PostActionSignal(new KeyValues("ItemDoubleLeftClick", "itemID", m_iID)); + + // post up an enter key being hit + m_pListPanel->OnKeyCodeTyped(KEY_ENTER); + } + else + { + OnMousePressed(code); + } + + m_pListPanel->SetSelectedItem(this); + } + //============================================================================= + // HPE_END + //============================================================================= + } + + void GetCellBounds(int column, int &xpos, int &columnWide) + { + xpos = 0, columnWide = 0; + int colCount = m_pListPanel->GetColumnCountBySection(m_iSectionID); + for (int i = 0; i < colCount; i++) + { + int maxWidth = m_pListPanel->GetColumnWidthBySection(m_iSectionID, i); + + IImage *image = GetImageAtIndex(i); + if (!image) + continue; + + // set the image position within the label + int wide, tall; + image->GetContentSize(wide, tall); + if (maxWidth >= 0) + { + wide = maxWidth; + } + + if (i == column) + { + // found the cell size, bail + columnWide = wide; + return; + } + + xpos += wide; + } + } + + //============================================================================= + // HPE_BEGIN: + // [menglish] gets the local coordinates of a cell using the max width for every column + //============================================================================= + + void GetMaxCellBounds(int column, int &xpos, int &columnWide) + { + xpos = 0, columnWide = 0; + int colCount = m_pListPanel->GetColumnCountBySection(m_iSectionID); + for (int i = 0; i < colCount; i++) + { + int maxWidth = m_pListPanel->GetColumnWidthBySection(m_iSectionID, i); + + if (i == column) + { + // found the cell size, bail + columnWide = maxWidth; + return; + } + + xpos += maxWidth; + } + } + + //============================================================================= + // HPE_END + //============================================================================= + + virtual void SetOverrideColors( bool state ) + { + m_bOverrideColors = state; + } + + void SetShowColumns( bool bShow ) + { + m_bShowColumns = bShow; + } + + void SetItemBgHorizFillInset( int nHorizFillInset ){ m_nHorizFillInset = nHorizFillInset; } + +private: + SectionedListPanel *m_pListPanel; + int m_iID; + int m_iSectionID; + KeyValues *m_pData; + Color m_FgColor2; + Color m_BgColor; + Color m_ArmedFgColor1; + Color m_ArmedFgColor2; + Color m_OutOfFocusSelectedTextColor; + Color m_ArmedBgColor; + Color m_SelectionBG2Color; + CUtlVector<vgui::TextImage *> m_TextImages; + + bool m_bSelected; + bool m_bOverrideColors; + bool m_bShowColumns; + int m_nHorizFillInset; +}; + +}; // namespace vgui + +DECLARE_BUILD_FACTORY( SectionedListPanel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +SectionedListPanel::SectionedListPanel(vgui::Panel *parent, const char *name) : BaseClass(parent, name) +{ + m_pScrollBar = new ScrollBar(this, "SectionedScrollBar", true); + m_pScrollBar->SetVisible(false); + m_pScrollBar->AddActionSignalTarget(this); + + m_iEditModeItemID = 0; + m_iEditModeColumn = 0; + m_bSortNeeded = false; + m_bVerticalScrollbarEnabled = true; + m_iLineSpacing = DEFAULT_LINE_SPACING; + m_iLineGap = 0; + m_iSectionGap = DEFAULT_SECTION_GAP; + + m_pImageList = NULL; + m_bDeleteImageListWhenDone = false; + + m_hHeaderFont = INVALID_FONT; + m_hRowFont = INVALID_FONT; + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + // [tj] Default clickability to true so existing controls aren't affected. + m_clickable = true; + // [tj] draw section headers by default so existing controls aren't affected. + m_bDrawSectionHeaders = true; + //============================================================================= + // HPE_END + //============================================================================= +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +SectionedListPanel::~SectionedListPanel() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Sorts the list +//----------------------------------------------------------------------------- +void SectionedListPanel::ReSortList() +{ + m_SortedItems.RemoveAll(); + + int sectionStart = 0; + // layout the buttons + for (int sectionIndex = 0; sectionIndex < m_Sections.Size(); sectionIndex++) + { + section_t §ion = m_Sections[sectionIndex]; + sectionStart = m_SortedItems.Count(); + + // find all the items in this section + for( int i = m_Items.Head(); i != m_Items.InvalidIndex(); i = m_Items.Next( i ) ) + { + if (m_Items[i]->GetSectionID() == m_Sections[sectionIndex].m_iID) + { + // insert the items sorted + if (section.m_pSortFunc) + { + int insertionPoint = sectionStart; + for (;insertionPoint < m_SortedItems.Count(); insertionPoint++) + { + if (section.m_pSortFunc(this, i, m_SortedItems[insertionPoint]->GetID())) + break; + } + + if (insertionPoint == m_SortedItems.Count()) + { + m_SortedItems.AddToTail(m_Items[i]); + } + else + { + m_SortedItems.InsertBefore(insertionPoint, m_Items[i]); + } + } + else + { + // just add to the end + m_SortedItems.AddToTail(m_Items[i]); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: iterates through and sets up the position of all the sections and items +//----------------------------------------------------------------------------- +void SectionedListPanel::PerformLayout() +{ + // lazy resort the list + if (m_bSortNeeded) + { + ReSortList(); + m_bSortNeeded = false; + } + + BaseClass::PerformLayout(); + + LayoutPanels(m_iContentHeight); + + int cx, cy, cwide, ctall; + GetBounds(cx, cy, cwide, ctall); + if (m_iContentHeight > ctall && m_bVerticalScrollbarEnabled) + { + m_pScrollBar->SetVisible(true); + m_pScrollBar->MoveToFront(); + + m_pScrollBar->SetPos(cwide - m_pScrollBar->GetWide() - 2, 0); + m_pScrollBar->SetSize(m_pScrollBar->GetWide(), ctall - 2); + + m_pScrollBar->SetRangeWindow(ctall); + + m_pScrollBar->SetRange(0, m_iContentHeight); + m_pScrollBar->InvalidateLayout(); + m_pScrollBar->Repaint(); + + // since we're just about to make the scrollbar visible, we need to re-layout + // the buttons since they depend on the scrollbar size + LayoutPanels(m_iContentHeight); + } + else + { + m_pScrollBar->SetValue(0); + + bool bWasVisible = m_pScrollBar->IsVisible(); + m_pScrollBar->SetVisible(false); + + // When we hide the scrollbar, we need to layout the buttons because they'll have more width to work with + if ( bWasVisible ) + { + LayoutPanels(m_iContentHeight); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: lays out the sections and rows in the panel +//----------------------------------------------------------------------------- +void SectionedListPanel::LayoutPanels(int &contentTall) +{ + int tall = GetSectionTall(); + int x = 5, wide = GetWide() - 10; + int y = 5; + + if (m_pScrollBar->IsVisible()) + { + y -= m_pScrollBar->GetValue(); + wide -= m_pScrollBar->GetWide(); + } + + int iStart = -1; + int iEnd = -1; + + // layout the buttons + bool bFirstVisibleSection = true; + for (int sectionIndex = 0; sectionIndex < m_Sections.Size(); sectionIndex++) + { + section_t §ion = m_Sections[sectionIndex]; + + iStart = -1; + iEnd = -1; + for (int i = 0; i < m_SortedItems.Count(); i++) + { + if (m_SortedItems[i]->GetSectionID() == m_Sections[sectionIndex].m_iID) + { + if (iStart == -1) + iStart = i; + iEnd = i; + } + } + + // don't draw this section at all if there are no items in it + if (iStart == -1 && !section.m_bAlwaysVisible) + { + section.m_pHeader->SetVisible(false); + continue; + } + + // Skip down a bit if this is not the first section to be drawn + if ( bFirstVisibleSection ) + bFirstVisibleSection = false; + else + y += m_iSectionGap; + + //============================================================================= + // HPE_BEGIN: + // [tj] Only draw the header if it is enabled + //============================================================================= + int nMinNextSectionY = y + section.m_iMinimumHeight; + if (m_bDrawSectionHeaders) + { + // draw the header + section.m_pHeader->SetBounds(x, y, wide, tall); + section.m_pHeader->SetVisible(true); + y += tall; + } + else + { + section.m_pHeader->SetVisible(false); + } + //============================================================================= + // HPE_END + //============================================================================= + + if (iStart == -1 && section.m_bAlwaysVisible) + { + } + else + { + // arrange all the items in this section underneath + for (int i = iStart; i <= iEnd; i++) + { + CItemButton *item = m_SortedItems[i]; //items[i]; + item->SetBounds(x, y, wide, m_iLineSpacing); + + // setup edit mode + if (m_hEditModePanel.Get() && m_iEditModeItemID == item->GetID()) + { + int cx, cwide; + item->GetCellBounds(1, cx, cwide); + m_hEditModePanel->SetBounds(cx, y, cwide, tall); + } + + y += m_iLineSpacing + m_iLineGap; + } + } + + // Add space, if needed to fulfill minimum requested content height + if ( y < nMinNextSectionY ) + y = nMinNextSectionY; + } + + // calculate height + contentTall = y; + if (m_pScrollBar->IsVisible()) + { + contentTall += m_pScrollBar->GetValue(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Ensures that the specified item is visible in the display +//----------------------------------------------------------------------------- +void SectionedListPanel::ScrollToItem(int iItem) +{ + int tall = GetSectionTall(); + int itemX, itemY ; + int nCurrentValue = m_pScrollBar->GetValue(); + + // find out where the item is + m_Items[iItem]->GetPos(itemX, itemY); + // add in the current scrollbar position + itemY += nCurrentValue; + + // compare that in the list + int cx, cy, cwide, ctall; + GetBounds(cx, cy, cwide, ctall); + if (m_iContentHeight > ctall) + { + if (itemY < nCurrentValue) + { + // scroll up + m_pScrollBar->SetValue(itemY); + } + else if (itemY > nCurrentValue + ctall - tall) + { + // scroll down + m_pScrollBar->SetValue(itemY - ctall + tall); + } + else + { + // keep the current value + } + } + else + { + // area isn't big enough, just remove the scrollbar + m_pScrollBar->SetValue(0); + } + + // reset scrollbar + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets background color & border +//----------------------------------------------------------------------------- +void SectionedListPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetBgColor(GetSchemeColor("SectionedListPanel.BgColor", GetBgColor(), pScheme)); + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); + + FOR_EACH_LL( m_Items, j ) + { + m_Items[j]->SetShowColumns( m_bShowColumns ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::SetHeaderFont( HFont hFont ) +{ + m_hHeaderFont = hFont; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +HFont SectionedListPanel::GetHeaderFont( void ) const +{ + return m_hHeaderFont; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::SetRowFont( HFont hFont ) +{ + m_hRowFont = hFont; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +HFont SectionedListPanel::GetRowFont( void ) const +{ + return m_hRowFont; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + m_iLineSpacing = inResourceData->GetInt("linespacing", 0); + if (!m_iLineSpacing) + { + m_iLineSpacing = DEFAULT_LINE_SPACING; + } + if (IsProportional()) + { + m_iLineSpacing = scheme()->GetProportionalScaledValueEx(GetScheme(), m_iLineSpacing); + } + + m_iSectionGap = inResourceData->GetInt("sectiongap", 0); + if (!m_iSectionGap) + { + m_iSectionGap = DEFAULT_SECTION_GAP; + } + m_iLineGap = inResourceData->GetInt( "linegap", 0 ); + if (IsProportional()) + { + m_iSectionGap = scheme()->GetProportionalScaledValueEx(GetScheme(), m_iSectionGap); + m_iLineGap = scheme()->GetProportionalScaledValueEx( GetScheme(), m_iLineGap ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: passes on proportional state to children +//----------------------------------------------------------------------------- +void SectionedListPanel::SetProportional(bool state) +{ + BaseClass::SetProportional(state); + + // now setup the section headers and items + int i; + for (i = 0; i < m_Sections.Count(); i++) + { + m_Sections[i].m_pHeader->SetProportional(state); + } + FOR_EACH_LL( m_Items, j ) + { + m_Items[j]->SetProportional(state); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets whether or not the vertical scrollbar should ever be displayed +//----------------------------------------------------------------------------- +void SectionedListPanel::SetVerticalScrollbar(bool state) +{ + m_bVerticalScrollbarEnabled = state; +} + +//----------------------------------------------------------------------------- +// Purpose: adds a new section +//----------------------------------------------------------------------------- +void SectionedListPanel::AddSection(int sectionID, const char *name, SectionSortFunc_t sortFunc) +{ + SectionedListPanelHeader *header = new SectionedListPanelHeader(this, name, sectionID); + AddSection(sectionID, header, sortFunc); +} + +//----------------------------------------------------------------------------- +// Purpose: adds a new section +//----------------------------------------------------------------------------- +void SectionedListPanel::AddSection(int sectionID, const wchar_t *name, SectionSortFunc_t sortFunc) +{ + SectionedListPanelHeader *header = new SectionedListPanelHeader(this, name, sectionID); + AddSection(sectionID, header, sortFunc); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a new section, given object +//----------------------------------------------------------------------------- +void SectionedListPanel::AddSection(int sectionID, SectionedListPanelHeader *header, SectionSortFunc_t sortFunc) +{ + header = SETUP_PANEL( header ); + int index = m_Sections.AddToTail(); + m_Sections[index].m_iID = sectionID; + m_Sections[index].m_pHeader = header; + m_Sections[index].m_pSortFunc = sortFunc; + m_Sections[index].m_bAlwaysVisible = false; + m_Sections[index].m_iMinimumHeight = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: removes all the sections from the current panel +//----------------------------------------------------------------------------- +void SectionedListPanel::RemoveAllSections() +{ + for (int i = 0; i < m_Sections.Count(); i++) + { + if (!m_Sections.IsValidIndex(i)) + continue; + + m_Sections[i].m_pHeader->SetVisible(false); + m_Sections[i].m_pHeader->MarkForDeletion(); + } + + m_Sections.RemoveAll(); + m_Sections.Purge(); + m_SortedItems.RemoveAll(); + + InvalidateLayout(); + ReSortList(); +} + +//----------------------------------------------------------------------------- +// Purpose: adds a new column to a section +//----------------------------------------------------------------------------- +bool SectionedListPanel::AddColumnToSection(int sectionID, const char *columnName, const char *columnText, int columnFlags, int width, HFont fallbackFont /*= INVALID_FONT*/ ) +{ + wchar_t wtext[64]; + wchar_t *pwtext = g_pVGuiLocalize->Find(columnText); + if (!pwtext) + { + g_pVGuiLocalize->ConvertANSIToUnicode(columnText, wtext, sizeof(wtext)); + pwtext = wtext; + } + return AddColumnToSection(sectionID, columnName, pwtext, columnFlags, width, fallbackFont ); +} + +//----------------------------------------------------------------------------- +// Purpose: as above but with wchar_t's +//----------------------------------------------------------------------------- +bool SectionedListPanel::AddColumnToSection(int sectionID, const char *columnName, const wchar_t *columnText, int columnFlags, int width, HFont fallbackFont /*= INVALID_FONT*/ ) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return false; + section_t §ion = m_Sections[index]; + + // add the new column to the sections' list + index = section.m_Columns.AddToTail(); + column_t &column = section.m_Columns[index]; + + Q_strncpy(column.m_szColumnName, columnName, sizeof(column.m_szColumnName)); + wcsncpy(column.m_szColumnText, columnText, sizeof(column.m_szColumnText) / sizeof(wchar_t)); + column.m_szColumnText[sizeof(column.m_szColumnText) / sizeof(wchar_t) - 1] = 0; + column.m_iColumnFlags = columnFlags; + column.m_iWidth = width; + column.m_hFallbackFont = fallbackFont; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: modifies the text in an existing column +//----------------------------------------------------------------------------- +bool SectionedListPanel::ModifyColumn(int sectionID, const char *columnName, const wchar_t *columnText) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return false; + section_t §ion = m_Sections[index]; + + // find the specified column + int columnIndex; + for (columnIndex = 0; columnIndex < section.m_Columns.Count(); columnIndex++) + { + if (!stricmp(section.m_Columns[columnIndex].m_szColumnName, columnName)) + break; + } + if (!section.m_Columns.IsValidIndex(columnIndex)) + return false; + column_t &column = section.m_Columns[columnIndex]; + + // modify the text + wcsncpy(column.m_szColumnText, columnText, sizeof(column.m_szColumnText) / sizeof(wchar_t)); + column.m_szColumnText[sizeof(column.m_szColumnText) / sizeof(wchar_t) - 1] = 0; + section.m_pHeader->InvalidateLayout(); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: adds an item to the list; returns itemID +//----------------------------------------------------------------------------- +int SectionedListPanel::AddItem(int sectionID, const KeyValues *data) +{ + int itemID = GetNewItemButton(); + ModifyItem(itemID, sectionID, data); + + // not sorted but in list + m_SortedItems.AddToTail(m_Items[itemID]); + m_bSortNeeded = true; + + return itemID; +} + +//----------------------------------------------------------------------------- +// Purpose: modifies an existing item; returns false if the item does not exist +//----------------------------------------------------------------------------- +bool SectionedListPanel::ModifyItem(int itemID, int sectionID, const KeyValues *data) +{ + if ( !m_Items.IsValidIndex(itemID) ) + return false; + + InvalidateLayout(); + m_Items[itemID]->SetSectionID(sectionID); + m_Items[itemID]->SetData(data); + m_Items[itemID]->InvalidateLayout(); + m_bSortNeeded = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::SetItemFgColor( int itemID, Color color ) +{ + Assert( m_Items.IsValidIndex(itemID) ); + if ( !m_Items.IsValidIndex(itemID) ) + return; + + m_Items[itemID]->SetFgColor( color ); + m_Items[itemID]->SetOverrideColors( true ); + m_Items[itemID]->InvalidateLayout(); +} + +//============================================================================= +// HPE_BEGIN: +// [menglish] Setter for the background color similar to the foreground color +//============================================================================= + +void SectionedListPanel::SetItemBgColor( int itemID, Color color ) +{ + Assert( m_Items.IsValidIndex(itemID) ); + if ( !m_Items.IsValidIndex(itemID) ) + return; + + m_Items[itemID]->SetBgColor( color ); + m_Items[itemID]->SetPaintBackgroundEnabled( true ); + m_Items[itemID]->SetOverrideColors( true ); + m_Items[itemID]->InvalidateLayout(); +} + +void SectionedListPanel::SetItemBgHorizFillInset( int itemID, int nInset ) +{ + Assert( m_Items.IsValidIndex( itemID ) ); + if ( !m_Items.IsValidIndex( itemID ) ) + return; + + m_Items[itemID]->SetItemBgHorizFillInset( nInset ); +} + +void SectionedListPanel::SetItemFont( int itemID, HFont font ) +{ + Assert( m_Items.IsValidIndex(itemID) ); + if ( !m_Items.IsValidIndex(itemID) ) + return; + + m_Items[itemID]->SetFont( font ); +} + +void SectionedListPanel::SetItemEnabled( int itemID, bool bEnabled ) +{ + Assert( m_Items.IsValidIndex(itemID) ); + if ( !m_Items.IsValidIndex(itemID) ) + return; + + m_Items[itemID]->SetEnabled( bEnabled ); +} + +//============================================================================= +// HPE_END +//============================================================================= +//----------------------------------------------------------------------------- +// Purpose: sets the color of a section text & underline +//----------------------------------------------------------------------------- +void SectionedListPanel::SetSectionFgColor(int sectionID, Color color) +{ + if (!m_Sections.IsValidIndex(sectionID)) + return; + + m_Sections[sectionID].m_pHeader->SetColor(color); +} +//----------------------------------------------------------------------------- +// Purpose: added so you can change the divider color AFTER the main color. +//----------------------------------------------------------------------------- +void SectionedListPanel::SetSectionDividerColor( int sectionID, Color color) +{ + if (!m_Sections.IsValidIndex(sectionID)) + return; + + m_Sections[sectionID].m_pHeader->SetDividerColor(color); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::SetSectionDrawDividerBar( int sectionID, bool bDraw ) +{ + if ( !m_Sections.IsValidIndex( sectionID ) ) + return; + + m_Sections[sectionID].m_pHeader->DrawDividerBar( bDraw ); +} + +//----------------------------------------------------------------------------- +// Purpose: forces a section to always be visible +//----------------------------------------------------------------------------- +void SectionedListPanel::SetSectionAlwaysVisible(int sectionID, bool visible) +{ + if (!m_Sections.IsValidIndex(sectionID)) + return; + + m_Sections[sectionID].m_bAlwaysVisible = visible; +} +void SectionedListPanel::SetFontSection(int sectionID, HFont font) +{ + if (!m_Sections.IsValidIndex(sectionID)) + return; + + m_Sections[sectionID].m_pHeader->SetFont(font); +} +void SectionedListPanel::SetSectionMinimumHeight(int sectionID, int iMinimumHeight) +{ + if (!m_Sections.IsValidIndex(sectionID)) + return; + + m_Sections[sectionID].m_iMinimumHeight = iMinimumHeight; + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: removes an item from the list; returns false if the item does not exist or is already removed +//----------------------------------------------------------------------------- +bool SectionedListPanel::RemoveItem(int itemID) +{ + if ( !m_Items.IsValidIndex(itemID) ) + return false; + + m_SortedItems.FindAndRemove(m_Items[itemID]); + m_bSortNeeded = true; + + m_Items[itemID]->MarkForDeletion(); + m_Items.Remove(itemID); + + InvalidateLayout(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the number of columns in a section +//----------------------------------------------------------------------------- +int SectionedListPanel::GetColumnCountBySection(int sectionID) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return NULL; + + return m_Sections[index].m_Columns.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the name of a column by section and column index; returns NULL if there are no more columns +// valid range of columnIndex is [0, GetColumnCountBySection) +//----------------------------------------------------------------------------- +const char *SectionedListPanel::GetColumnNameBySection(int sectionID, int columnIndex) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0 || columnIndex >= m_Sections[index].m_Columns.Size()) + return NULL; + + return m_Sections[index].m_Columns[columnIndex].m_szColumnName; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the text for a column by section and column index +//----------------------------------------------------------------------------- +const wchar_t *SectionedListPanel::GetColumnTextBySection(int sectionID, int columnIndex) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0 || columnIndex >= m_Sections[index].m_Columns.Size()) + return NULL; + + return m_Sections[index].m_Columns[columnIndex].m_szColumnText; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the type of a column by section and column index +//----------------------------------------------------------------------------- +int SectionedListPanel::GetColumnFlagsBySection(int sectionID, int columnIndex) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return 0; + + if (columnIndex >= m_Sections[index].m_Columns.Size()) + return 0; + + return m_Sections[index].m_Columns[columnIndex].m_iColumnFlags; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int SectionedListPanel::GetColumnWidthBySection(int sectionID, int columnIndex) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return 0; + + if (columnIndex >= m_Sections[index].m_Columns.Size()) + return 0; + + return m_Sections[index].m_Columns[columnIndex].m_iWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::SetColumnWidthBySection(int sectionID, const char *columnName, int iWidth) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return; + section_t §ion = m_Sections[index]; + + // find the specified column + int columnIndex; + for (columnIndex = 0; columnIndex < section.m_Columns.Count(); columnIndex++) + { + if (!stricmp(section.m_Columns[columnIndex].m_szColumnName, columnName)) + break; + } + if (!section.m_Columns.IsValidIndex(columnIndex)) + return; + + column_t &column = section.m_Columns[columnIndex]; + column.m_iWidth = iWidth; + + // make sure the header for this section reloads with the new width + section.m_pHeader->InvalidateLayout(); +} + +//============================================================================= +// HPE_BEGIN: +// [menglish] Gets the column index by the string identifier +//============================================================================= + +int SectionedListPanel::GetColumnIndexByName(int sectionID, char* name) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return 0; + + for ( int columnIndex = 0; columnIndex < m_Sections[index].m_Columns.Count(); ++ columnIndex) + { + if( !V_strcmp( m_Sections[index].m_Columns[columnIndex].m_szColumnName, name ) ) + return columnIndex; + } + + return -1; +} + +//============================================================================= +// HPE_END +//============================================================================= + +//----------------------------------------------------------------------------- +// Purpose: returns -1 if section not found +//----------------------------------------------------------------------------- +int SectionedListPanel::FindSectionIndexByID(int sectionID) +{ + for (int i = 0; i < m_Sections.Size(); i++) + { + if (m_Sections[i].m_iID == sectionID) + { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the scrollbar is moved +//----------------------------------------------------------------------------- +void SectionedListPanel::OnSliderMoved() +{ + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list according to the mouse wheel movement +//----------------------------------------------------------------------------- +void SectionedListPanel::OnMouseWheeled(int delta) +{ + if (m_hEditModePanel.Get()) + { + // ignore mouse wheel in edit mode, forward right up to parent + CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); + return; + } + + // scroll the window based on the delta + int val = m_pScrollBar->GetValue(); + val -= (delta * BUTTON_HEIGHT_DEFAULT * 3); + m_pScrollBar->SetValue(val); +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the scrollbar position on size change +//----------------------------------------------------------------------------- +void SectionedListPanel::OnSizeChanged(int wide, int tall) +{ + BaseClass::OnSizeChanged(wide, tall); + m_pScrollBar->SetValue(0); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: deselects any items +//----------------------------------------------------------------------------- +void SectionedListPanel::OnMousePressed(MouseCode code) +{ + //============================================================================= + // HPE_BEGIN: + // [tj] Only do this if clicking is enabled. + //============================================================================= + if (m_clickable){ + ClearSelection(); + } + //============================================================================= + // HPE_END + //=============================================================================} +} +//----------------------------------------------------------------------------- +// Purpose: deselects any items +//----------------------------------------------------------------------------- +void SectionedListPanel::ClearSelection( void ) +{ + SetSelectedItem((CItemButton *)NULL); +} + +void SectionedListPanel::MoveSelectionDown( void ) +{ + int itemID = GetSelectedItem(); + if (itemID == -1) + return; + + if (!m_SortedItems.Count()) // if the list has been emptied + return; + + int i; + for (i = 0; i < m_SortedItems.Count(); i++) + { + if (m_SortedItems[i]->GetID() == itemID) + break; + } + + Assert(i != m_SortedItems.Count()); + + // we're already on the end + if (i >= m_SortedItems.Count() - 1) + return; + + int newItemID = m_SortedItems[i + 1]->GetID(); + SetSelectedItem(m_Items[newItemID]); + ScrollToItem(newItemID); +} + +void SectionedListPanel::MoveSelectionUp( void ) +{ + int itemID = GetSelectedItem(); + if (itemID == -1) + return; + + if (!m_SortedItems.Count()) // if the list has been emptied + return; + + int i; + for (i = 0; i < m_SortedItems.Count(); i++) + { + if (m_SortedItems[i]->GetID() == itemID) + break; + } + + Assert(i != m_SortedItems.Count()); + + // we're already on the end + if (i == 0 || i >= m_SortedItems.Count() ) + return; + + int newItemID = m_SortedItems[i - 1]->GetID(); + SetSelectedItem(m_Items[newItemID]); + ScrollToItem(newItemID); +} + +void SectionedListPanel::NavigateTo( void ) +{ + BaseClass::NavigateTo(); + + if ( m_SortedItems.Count() ) + { + int nItemID = m_SortedItems[ 0 ]->GetID(); + SetSelectedItem( m_Items[ nItemID ] ); + ScrollToItem( nItemID ); + } + + RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: arrow key movement handler +//----------------------------------------------------------------------------- +void SectionedListPanel::OnKeyCodePressed( KeyCode code ) +{ + if (m_hEditModePanel.Get()) + { + // ignore arrow keys in edit mode + // forward right up to parent so that tab focus change doesn't occur + CallParentFunction(new KeyValues("KeyCodePressed", "code", code)); + return; + } + + int buttonTall = GetSectionTall(); + + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + if ( nButtonCode == KEY_XBUTTON_DOWN || + nButtonCode == KEY_XSTICK1_DOWN || + nButtonCode == KEY_XSTICK2_DOWN || + nButtonCode == STEAMCONTROLLER_DPAD_DOWN || + code == KEY_DOWN ) + { + int itemID = GetSelectedItem(); + MoveSelectionDown(); + if ( itemID != GetSelectedItem() ) + { + // Only eat the input if it did something + return; + } + } + else if ( nButtonCode == KEY_XBUTTON_UP || + nButtonCode == KEY_XSTICK1_UP || + nButtonCode == KEY_XSTICK2_UP || + nButtonCode == STEAMCONTROLLER_DPAD_UP || + code == KEY_UP) + { + int itemID = GetSelectedItem(); + MoveSelectionUp(); + if ( itemID != GetSelectedItem() ) + { + // Only eat the input if it did something + return; + } + } + else if (code == KEY_PAGEDOWN) + { + // calculate info for # of rows + int cx, cy, cwide, ctall; + GetBounds(cx, cy, cwide, ctall); + + int rowsperpage = ctall/buttonTall; + + int itemID = GetSelectedItem(); + int lastValidItem = itemID; + int secID = m_Items[itemID]->GetSectionID(); + int i=0; + int row = m_SortedItems.Find(m_Items[itemID]); + + while ( i < rowsperpage ) + { + if ( m_SortedItems.IsValidIndex(++row) ) + { + itemID = m_SortedItems[row]->GetID(); + lastValidItem = itemID; + i++; + + // if we switched sections, then count the section header as a row + if (m_Items[itemID]->GetSectionID() != secID) + { + secID = m_Items[itemID]->GetSectionID(); + i++; + } + } + else + { + itemID = lastValidItem; + break; + } + } + SetSelectedItem(m_Items[itemID]); + ScrollToItem(itemID); + return; + } + else if (code == KEY_PAGEUP) + { + // calculate info for # of rows + int cx, cy, cwide, ctall; + GetBounds(cx, cy, cwide, ctall); + int rowsperpage = ctall/buttonTall; + + int itemID = GetSelectedItem(); + int lastValidItem = itemID; + int secID = m_Items[itemID]->GetSectionID(); + int i=0; + int row = m_SortedItems.Find(m_Items[itemID]); + while ( i < rowsperpage ) + { + if ( m_SortedItems.IsValidIndex(--row) ) + { + itemID = m_SortedItems[row]->GetID(); + lastValidItem = itemID; + i++; + + // if we switched sections, then count the section header as a row + if (m_Items[itemID]->GetSectionID() != secID) + { + secID = m_Items[itemID]->GetSectionID(); + i++; + } + } + else + { + SetSelectedItem(m_Items[lastValidItem]); + m_pScrollBar->SetValue(0); + return; + } + } + SetSelectedItem(m_Items[itemID]); + ScrollToItem(itemID); + return; + } + else if ( code == KEY_ENTER || nButtonCode == KEY_XBUTTON_A || nButtonCode == STEAMCONTROLLER_A ) + { + Panel *pSelectedItem = m_hSelectedItem; + if ( pSelectedItem ) + { + pSelectedItem->OnMousePressed( MOUSE_LEFT ); + } + return; + } + + BaseClass::OnKeyCodePressed( code ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Clears the list +//----------------------------------------------------------------------------- +void SectionedListPanel::DeleteAllItems() +{ + FOR_EACH_LL( m_Items, i ) + { + m_Items[i]->SetVisible(false); + m_Items[i]->Clear(); + + // don't delete, move to free list + int freeIndex = m_FreeItems.AddToTail(); + m_FreeItems[freeIndex] = m_Items[i]; + } + + m_Items.RemoveAll(); + m_SortedItems.RemoveAll(); + m_hSelectedItem = NULL; + InvalidateLayout(); + m_bSortNeeded = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Changes the current list selection +//----------------------------------------------------------------------------- +void SectionedListPanel::SetSelectedItem(CItemButton *item) +{ + if (m_hSelectedItem.Get() == item) + return; + + // deselect the current item + if (m_hSelectedItem.Get()) + { + m_hSelectedItem->SetSelected(false); + } + + // set the new item + m_hSelectedItem = item; + if (m_hSelectedItem.Get()) + { + m_hSelectedItem->SetSelected(true); + } + + Repaint(); + PostActionSignal(new KeyValues("ItemSelected", "itemID", m_hSelectedItem.Get() ? m_hSelectedItem->GetID() : -1)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int SectionedListPanel::GetSelectedItem() +{ + if (m_hSelectedItem.Get()) + { + return m_hSelectedItem->GetID(); + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: sets which item is currently selected +//----------------------------------------------------------------------------- +void SectionedListPanel::SetSelectedItem(int itemID) +{ + if ( m_Items.IsValidIndex(itemID) ) + { + SetSelectedItem(m_Items[itemID]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns the data of a selected item +//----------------------------------------------------------------------------- +KeyValues *SectionedListPanel::GetItemData(int itemID) +{ + Assert(m_Items.IsValidIndex(itemID)); + if ( !m_Items.IsValidIndex(itemID) ) + return NULL; + + return m_Items[itemID]->GetData(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns what section an item is in +//----------------------------------------------------------------------------- +int SectionedListPanel::GetItemSection(int itemID) +{ + if ( !m_Items.IsValidIndex(itemID) ) + return -1; + + return m_Items[itemID]->GetSectionID(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the itemID is valid for use +//----------------------------------------------------------------------------- +bool SectionedListPanel::IsItemIDValid(int itemID) +{ + return m_Items.IsValidIndex(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the itemID is valid for use +//----------------------------------------------------------------------------- +int SectionedListPanel::GetHighestItemID() +{ + return m_Items.MaxElementIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: item iterators +//----------------------------------------------------------------------------- +int SectionedListPanel::GetItemCount() +{ + return m_SortedItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: item iterators +//----------------------------------------------------------------------------- +int SectionedListPanel::GetItemIDFromRow(int row) +{ + if ( !m_SortedItems.IsValidIndex(row) ) + return -1; + + return m_SortedItems[row]->GetID(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the row that this itemID occupies. -1 if the itemID is invalid +//----------------------------------------------------------------------------- +int SectionedListPanel::GetRowFromItemID(int itemID) +{ + for (int i = 0; i < m_SortedItems.Count(); i++) + { + if ( m_SortedItems[i]->GetID() == itemID ) + { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the local coordinates of a cell +//----------------------------------------------------------------------------- +bool SectionedListPanel::GetCellBounds(int itemID, int column, int &x, int &y, int &wide, int &tall) +{ + x = y = wide = tall = 0; + if ( !IsItemIDValid(itemID) ) + return false; + + // get the item + CItemButton *item = m_Items[itemID]; + + if ( !item->IsVisible() ) + return false; + + //!! ignores column for now + item->GetBounds(x, y, wide, tall); + item->GetCellBounds(column, x, wide); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the local coordinates of a section header +//----------------------------------------------------------------------------- +bool SectionedListPanel::GetSectionHeaderBounds(int sectionID, int &x, int &y, int &wide, int &tall) +{ + x = y = wide = tall = 0; + int index = FindSectionIndexByID(sectionID); + if (index < 0 || !m_Sections[index].m_pHeader ) + return false; + + m_Sections[index].m_pHeader->GetBounds( x, y, wide, tall ); + return true; +} + +//============================================================================= +// HPE_BEGIN: +// [menglish] Gets the local coordinates of a cell using the max width for every column +// Gets the local coordinates of a cell +//============================================================================= + +bool SectionedListPanel::GetMaxCellBounds(int itemID, int column, int &x, int &y, int &wide, int &tall) +{ + x = y = wide = tall = 0; + if ( !IsItemIDValid(itemID) ) + return false; + + // get the item + CItemButton *item = m_Items[itemID]; + + if ( !item->IsVisible() ) + return false; + + //!! ignores column for now + item->GetBounds(x, y, wide, tall); + item->GetMaxCellBounds(column, x, wide); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the local coordinates of a cell +//----------------------------------------------------------------------------- +bool SectionedListPanel::GetItemBounds(int itemID, int &x, int &y, int &wide, int &tall) +{ + x = y = wide = tall = 0; + if ( !IsItemIDValid(itemID) ) + return false; + + // get the item + CItemButton *item = m_Items[itemID]; + + if ( !item->IsVisible() ) + return false; + + //!! ignores column for now + item->GetBounds(x, y, wide, tall); + return true; +} + +//============================================================================= +// HPE_END +//============================================================================= + +//----------------------------------------------------------------------------- +// Purpose: forces an item to redraw +//----------------------------------------------------------------------------- +void SectionedListPanel::InvalidateItem(int itemID) +{ + if ( !IsItemIDValid(itemID) ) + return; + + m_Items[itemID]->InvalidateLayout(); + m_Items[itemID]->Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: set up a field for editing +//----------------------------------------------------------------------------- +void SectionedListPanel::EnterEditMode(int itemID, int column, vgui::Panel *editPanel) +{ + m_hEditModePanel = editPanel; + m_iEditModeItemID = itemID; + m_iEditModeColumn = column; + editPanel->SetParent(this); + editPanel->SetVisible(true); + editPanel->RequestFocus(); + editPanel->MoveToFront(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: leaves editing mode +//----------------------------------------------------------------------------- +void SectionedListPanel::LeaveEditMode() +{ + if (m_hEditModePanel.Get()) + { + InvalidateItem(m_iEditModeItemID); + m_hEditModePanel->SetVisible(false); + m_hEditModePanel->SetParent((Panel *)NULL); + m_hEditModePanel = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if we are currently in inline editing mode +//----------------------------------------------------------------------------- +bool SectionedListPanel::IsInEditMode() +{ + return (m_hEditModePanel.Get() != NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: list used to match indexes in image columns to image pointers +//----------------------------------------------------------------------------- +void SectionedListPanel::SetImageList(ImageList *imageList, bool deleteImageListWhenDone) +{ + m_bDeleteImageListWhenDone = deleteImageListWhenDone; + m_pImageList = imageList; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::OnSetFocus() +{ + if (m_hSelectedItem.Get()) + { + m_hSelectedItem->RequestFocus(); + } + else + { + BaseClass::OnSetFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int SectionedListPanel::GetSectionTall() +{ + if (m_Sections.Count()) + { + HFont font = m_Sections[0].m_pHeader->GetFont(); + if (font != INVALID_FONT) + { + return surface()->GetFontTall(font) + BUTTON_HEIGHT_SPACER; + } + } + + return BUTTON_HEIGHT_DEFAULT; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the size required to fully draw the contents of the panel +//----------------------------------------------------------------------------- +void SectionedListPanel::GetContentSize(int &wide, int &tall) +{ + // make sure our layout is done + if (IsLayoutInvalid()) + { + if (m_bSortNeeded) + { + ReSortList(); + m_bSortNeeded = false; + } + LayoutPanels(m_iContentHeight); + } + + wide = GetWide(); + tall = m_iContentHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the index of a new item button +//----------------------------------------------------------------------------- +int SectionedListPanel::GetNewItemButton() +{ + int itemID = m_Items.AddToTail(); + if (m_FreeItems.Count()) + { + // reusing an existing CItemButton + m_Items[itemID] = m_FreeItems[m_FreeItems.Head()]; + m_Items[itemID]->SetID(itemID); + m_Items[itemID]->SetVisible(true); + m_FreeItems.Remove(m_FreeItems.Head()); + } + else + { + // create a new CItemButton + m_Items[itemID] = SETUP_PANEL(new CItemButton(this, itemID)); + m_Items[itemID]->SetShowColumns( m_bShowColumns ); + } + + // Gross. Le's hope this isn't the only property that doesn't get defaulted + // properly when an item is recycled..... + m_Items[itemID]->SetEnabled( true ); + + return itemID; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns fallback font to use for text image for this column +// Input : sectionID - +// columnIndex - +// Output : virtual HFont +//----------------------------------------------------------------------------- +HFont SectionedListPanel::GetColumnFallbackFontBySection( int sectionID, int columnIndex ) +{ + int index = FindSectionIndexByID(sectionID); + if (index < 0) + return INVALID_FONT; + + if (columnIndex >= m_Sections[index].m_Columns.Size()) + return INVALID_FONT; + + return m_Sections[index].m_Columns[columnIndex].m_hFallbackFont; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color *SectionedListPanel::GetColorOverrideForCell( int sectionID, int itemID, int columnID ) +{ + FOR_EACH_VEC( m_ColorOverrides, i ) + { + if ( ( m_ColorOverrides[i].m_SectionID == sectionID ) && + ( m_ColorOverrides[i].m_ItemID == itemID ) && + ( m_ColorOverrides[i].m_ColumnID == columnID ) ) + { + return &(m_ColorOverrides[i].m_clrOverride); + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SectionedListPanel::SetColorOverrideForCell( int sectionID, int itemID, int columnID, Color clrOverride ) +{ + // is this value already in the override list? + FOR_EACH_VEC( m_ColorOverrides, i ) + { + if ( ( m_ColorOverrides[i].m_SectionID == sectionID ) && + ( m_ColorOverrides[i].m_ItemID == itemID ) && + ( m_ColorOverrides[i].m_ColumnID == columnID ) ) + { + m_ColorOverrides[i].m_clrOverride = clrOverride; + return; + } + } + + int iIndex = m_ColorOverrides.AddToTail(); + m_ColorOverrides[iIndex].m_SectionID = sectionID; + m_ColorOverrides[iIndex].m_ItemID = itemID; + m_ColorOverrides[iIndex].m_ColumnID = columnID; + m_ColorOverrides[iIndex].m_clrOverride = clrOverride; +} diff --git a/vgui2/vgui_controls/Slider.cpp b/vgui2/vgui_controls/Slider.cpp new file mode 100644 index 0000000..a05454a --- /dev/null +++ b/vgui2/vgui_controls/Slider.cpp @@ -0,0 +1,954 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> +#define PROTECTED_THINGS_DISABLE + +#include <vgui/MouseCode.h> +#include <KeyValues.h> +#include <vgui/IBorder.h> +#include <vgui/IInput.h> +#include <vgui/ISystem.h> +#include <vgui/IScheme.h> +#include <vgui/ISurface.h> +#include <vgui/ILocalize.h> + +#include <vgui_controls/Slider.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/TextImage.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( Slider ); + +static const float NOB_SIZE = 8.0f; + +//----------------------------------------------------------------------------- +// Purpose: Create a slider bar with ticks underneath it +//----------------------------------------------------------------------------- +Slider::Slider(Panel *parent, const char *panelName ) : BaseClass(parent, panelName) +{ + m_bIsDragOnRepositionNob = false; + _dragging = false; + _value = 0; + _range[0] = 0; + _range[1] = 0; + _buttonOffset = 0; + _sliderBorder = NULL; + _insetBorder = NULL; + m_nNumTicks = 10; + _leftCaption = NULL; + _rightCaption = NULL; + + _subrange[ 0 ] = 0; + _subrange[ 1 ] = 0; + m_bUseSubRange = false; + m_bInverted = false; + + SetThumbWidth( 8 ); + RecomputeNobPosFromValue(); + AddActionSignalTarget(this); + SetBlockDragChaining( true ); +} + +// This allows the slider to behave like it's larger than what's actually being drawn +//----------------------------------------------------------------------------- +// Purpose: +// Input : bEnable - +// 0 - +// 100 - +//----------------------------------------------------------------------------- +void Slider::SetSliderThumbSubRange( bool bEnable, int nMin /*= 0*/, int nMax /*= 100*/ ) +{ + m_bUseSubRange = bEnable; + _subrange[ 0 ] = nMin; + _subrange[ 1 ] = nMax; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the size of the slider bar. +// Warning less than 30 pixels tall and everything probably won't fit. +//----------------------------------------------------------------------------- +void Slider::OnSizeChanged(int wide,int tall) +{ + BaseClass::OnSizeChanged(wide,tall); + + RecomputeNobPosFromValue(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the value of the slider to one of the ticks. +//----------------------------------------------------------------------------- +void Slider::SetValue(int value, bool bTriggerChangeMessage) +{ + int oldValue=_value; + + if ( _range[0] < _range[1] ) + { + if(value<_range[0]) + { + value=_range[0]; + } + if(value>_range[1]) + { + value=_range[1]; + } + } + else + { + if(value<_range[1]) + { + value=_range[1]; + } + if(value>_range[0]) + { + value=_range[0]; + } + } + + _value = value; + + RecomputeNobPosFromValue(); + + if (_value != oldValue && bTriggerChangeMessage) + { + SendSliderMovedMessage(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the value of the slider +//----------------------------------------------------------------------------- +int Slider::GetValue() +{ + return _value; +} + +//----------------------------------------------------------------------------- +// Purpose: Layout the slider before drawing it on screen. +//----------------------------------------------------------------------------- +void Slider::PerformLayout() +{ + BaseClass::PerformLayout(); + RecomputeNobPosFromValue(); + + if (_leftCaption) + { + _leftCaption->ResizeImageToContent(); + } + if (_rightCaption) + { + _rightCaption->ResizeImageToContent(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Move the nob on the slider in response to changing its value. +//----------------------------------------------------------------------------- +void Slider::RecomputeNobPosFromValue() +{ + //int wide,tall; + //GetPaintSize(wide,tall); + int x, y, wide, tall; + GetTrackRect( x, y, wide, tall ); + + float usevalue = _value; + int *userange = &_range[ 0 ]; + if ( m_bUseSubRange ) + { + userange = &_subrange[ 0 ]; + usevalue = clamp( _value, _subrange[ 0 ], _subrange[ 1 ] ); + } + + float fwide=(float)wide; + float frange=(float)(userange[1] -userange[0]); + float fvalue=(float)(usevalue -userange[0]); + float fper = (frange != 0.0f) ? fvalue / frange : 0.0f; + + if ( m_bInverted ) + fper = 1.0f - fper; + + float freepixels = fwide - _nobSize; + float leftpixel = (float)x; + float firstpixel = leftpixel + freepixels * fper + 0.5f; + + _nobPos[0]=(int)( firstpixel ); + _nobPos[1]=(int)( firstpixel + _nobSize ); + + + int rightEdge = x + wide; + + if(_nobPos[1]> rightEdge ) + { + _nobPos[0]=rightEdge-((int)_nobSize); + _nobPos[1]=rightEdge; + } + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sync the slider's value up with the nob's position. +//----------------------------------------------------------------------------- +void Slider::RecomputeValueFromNobPos() +{ + int value = EstimateValueAtPos( _nobPos[ 0 ], 0 ); + SetValue( value ); +} + +int Slider::EstimateValueAtPos( int localMouseX, int /*localMouseY*/ ) +{ + int x, y, wide, tall; + GetTrackRect( x, y, wide, tall ); + + int *userange = &_range[ 0 ]; + if ( m_bUseSubRange ) + { + userange = &_subrange[ 0 ]; + } + + float fwide = (float)wide; + float fvalue = (float)( _value - userange[0] ); + float fnob = (float)( localMouseX - x ); + float freepixels = fwide - _nobSize; + + // Map into reduced range + fvalue = freepixels != 0.0f ? fnob / freepixels : 0.0f; + + return (int) (RemapVal( fvalue, 0.0, 1.0, userange[0], userange[1] )); +} + +void Slider::SetInverted( bool bInverted ) +{ + m_bInverted = bInverted; +} + + +//----------------------------------------------------------------------------- +// Purpose: Send a message to interested parties when the slider moves +//----------------------------------------------------------------------------- +void Slider::SendSliderMovedMessage() +{ + // send a changed message + KeyValues *pParams = new KeyValues("SliderMoved", "position", _value); + pParams->SetPtr( "panel", this ); + PostActionSignal( pParams ); +} + +//----------------------------------------------------------------------------- +// Purpose: Send a message to interested parties when the user begins dragging the slider +//----------------------------------------------------------------------------- +void Slider::SendSliderDragStartMessage() +{ + // send a message + KeyValues *pParams = new KeyValues("SliderDragStart", "position", _value); + pParams->SetPtr( "panel", this ); + PostActionSignal( pParams ); +} + +//----------------------------------------------------------------------------- +// Purpose: Send a message to interested parties when the user ends dragging the slider +//----------------------------------------------------------------------------- +void Slider::SendSliderDragEndMessage() +{ + // send a message + KeyValues *pParams = new KeyValues("SliderDragEnd", "position", _value); + pParams->SetPtr( "panel", this ); + PostActionSignal( pParams ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Slider::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("Slider.NobColor", pScheme)); + // this line is useful for debugging + //SetBgColor(GetSchemeColor("0 0 0 255")); + + m_TickColor = pScheme->GetColor( "Slider.TextColor", GetFgColor() ); + m_TrackColor = pScheme->GetColor( "Slider.TrackColor", GetFgColor() ); + +#ifdef _X360 + m_DepressedBgColor = GetSchemeColor("Slider.NobFocusColor", pScheme); +#endif + + m_DisabledTextColor1 = pScheme->GetColor( "Slider.DisabledTextColor1", GetFgColor() ); + m_DisabledTextColor2 = pScheme->GetColor( "Slider.DisabledTextColor2", GetFgColor() ); + + _sliderBorder = pScheme->GetBorder("ButtonBorder"); + _insetBorder = pScheme->GetBorder("ButtonDepressedBorder"); + + if ( _leftCaption ) + { + _leftCaption->SetFont(pScheme->GetFont("DefaultVerySmall", IsProportional() )); + } + + if ( _rightCaption ) + { + _rightCaption->SetFont(pScheme->GetFont("DefaultVerySmall", IsProportional() )); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Slider::GetSettings(KeyValues *outResourceData) +{ + BaseClass::GetSettings(outResourceData); + + char buf[256]; + if (_leftCaption) + { + _leftCaption->GetUnlocalizedText(buf, sizeof(buf)); + outResourceData->SetString("leftText", buf); + } + + if (_rightCaption) + { + _rightCaption->GetUnlocalizedText(buf, sizeof(buf)); + outResourceData->SetString("rightText", buf); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Slider::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + + const char *left = inResourceData->GetString("leftText", NULL); + const char *right = inResourceData->GetString("rightText", NULL); + + int thumbWidth = inResourceData->GetInt("thumbwidth", 0); + if (thumbWidth != 0) + { + SetThumbWidth(thumbWidth); + } + + SetTickCaptions(left, right); + + int nNumTicks = inResourceData->GetInt( "numTicks", -1 ); + if ( nNumTicks >= 0 ) + { + SetNumTicks( nNumTicks ); + } + + int nCurrentRange[2]; + GetRange( nCurrentRange[0], nCurrentRange[1] ); + KeyValues *pRangeMin = inResourceData->FindKey( "rangeMin", false ); + KeyValues *pRangeMax = inResourceData->FindKey( "rangeMax", false ); + bool bDoClamp = false; + if ( pRangeMin ) + { + _range[0] = inResourceData->GetInt( "rangeMin" ); + bDoClamp = true; + } + if ( pRangeMax ) + { + _range[1] = inResourceData->GetInt( "rangeMax" ); + bDoClamp = true; + } + + if ( bDoClamp ) + { + ClampRange(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *Slider::GetDescription() +{ + static char buf[1024]; + Q_snprintf(buf, sizeof(buf), "%s, string leftText, string rightText", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the rectangle to draw the slider track in. +//----------------------------------------------------------------------------- +void Slider::GetTrackRect( int& x, int& y, int& w, int& h ) +{ + int wide, tall; + GetPaintSize( wide, tall ); + + x = 0; + y = 8; + w = wide - (int)_nobSize; + h = 4; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw everything on screen +//----------------------------------------------------------------------------- +void Slider::Paint() +{ + DrawTicks(); + + DrawTickLabels(); + + // Draw nob last so it draws over ticks. + DrawNob(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the ticks below the slider. +//----------------------------------------------------------------------------- +void Slider::DrawTicks() +{ + int x, y; + int wide,tall; + GetTrackRect( x, y, wide, tall ); + + // Figure out how to draw the ticks +// GetPaintSize( wide, tall ); + + float fwide = (float)wide; + float freepixels = fwide - _nobSize; + + float leftpixel = _nobSize / 2.0f; + + float pixelspertick = freepixels / ( m_nNumTicks ); + + y += (int)_nobSize; + int tickHeight = 5; + + if (IsEnabled()) + { + surface()->DrawSetColor( m_TickColor ); //vgui::Color( 127, 140, 127, 255 ) ); + for ( int i = 0; i <= m_nNumTicks; i++ ) + { + int xpos = (int)( leftpixel + i * pixelspertick ); + + surface()->DrawFilledRect( xpos, y, xpos + 1, y + tickHeight ); + } + } + else + { + surface()->DrawSetColor( m_DisabledTextColor1 ); //vgui::Color( 127, 140, 127, 255 ) ); + for ( int i = 0; i <= m_nNumTicks; i++ ) + { + int xpos = (int)( leftpixel + i * pixelspertick ); + surface()->DrawFilledRect( xpos+1, y+1, xpos + 2, y + tickHeight + 1 ); + } + surface()->DrawSetColor( m_DisabledTextColor2 ); //vgui::Color( 127, 140, 127, 255 ) ); + for ( int i = 0; i <= m_nNumTicks; i++ ) + { + int xpos = (int)( leftpixel + i * pixelspertick ); + surface()->DrawFilledRect( xpos, y, xpos + 1, y + tickHeight ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw Tick labels under the ticks. +//----------------------------------------------------------------------------- +void Slider::DrawTickLabels() +{ + int x, y; + int wide,tall; + GetTrackRect( x, y, wide, tall ); + + // Figure out how to draw the ticks +// GetPaintSize( wide, tall ); + y += (int)NOB_SIZE + 4; + + // Draw Start and end range values + if (IsEnabled()) + surface()->DrawSetTextColor( m_TickColor ); //vgui::Color( 127, 140, 127, 255 ) ); + else + surface()->DrawSetTextColor( m_DisabledTextColor1 ); //vgui::Color( 127, 140, 127, 255 ) ); + + + if ( _leftCaption != NULL ) + { + _leftCaption->SetPos(0, y); + if (IsEnabled()) + { + _leftCaption->SetColor( m_TickColor ); + } + else + { + _leftCaption->SetColor( m_DisabledTextColor1 ); + } + + _leftCaption->Paint(); + } + + if ( _rightCaption != NULL) + { + int rwide, rtall; + _rightCaption->GetSize(rwide, rtall); + _rightCaption->SetPos((int)(wide - rwide) , y); + if (IsEnabled()) + { + _rightCaption->SetColor( m_TickColor ); + } + else + { + _rightCaption->SetColor( m_DisabledTextColor1 ); + } + + _rightCaption->Paint(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the nob part of the slider. +//----------------------------------------------------------------------------- +void Slider::DrawNob() +{ + // horizontal nob + int x, y; + int wide,tall; + GetTrackRect( x, y, wide, tall ); + Color col = GetFgColor(); +#ifdef _X360 + if(HasFocus()) + { + col = m_DepressedBgColor; + } +#endif + surface()->DrawSetColor(col); + + int nobheight = 16; + + surface()->DrawFilledRect( + _nobPos[0], + y + tall / 2 - nobheight / 2, + _nobPos[1], + y + tall / 2 + nobheight / 2); + // border + if (_sliderBorder) + { + _sliderBorder->Paint( + _nobPos[0], + y + tall / 2 - nobheight / 2, + _nobPos[1], + y + tall / 2 + nobheight / 2); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text labels of the Start and end ticks. +//----------------------------------------------------------------------------- +void Slider::SetTickCaptions( const char *left, const char *right ) +{ + if (left) + { + if (_leftCaption) + { + _leftCaption->SetText(left); + } + else + { + _leftCaption = new TextImage(left); + } + } + if (right) + { + if (_rightCaption) + { + _rightCaption->SetText(right); + } + else + { + _rightCaption = new TextImage(right); + } + } + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text labels of the Start and end ticks. +//----------------------------------------------------------------------------- +void Slider::SetTickCaptions( const wchar_t *left, const wchar_t *right ) +{ + if (left) + { + if (_leftCaption) + { + _leftCaption->SetText(left); + } + else + { + _leftCaption = new TextImage(left); + } + } + if (right) + { + if (_rightCaption) + { + _rightCaption->SetText(right); + } + else + { + _rightCaption = new TextImage(right); + } + } + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the slider track +//----------------------------------------------------------------------------- +void Slider::PaintBackground() +{ + BaseClass::PaintBackground(); + + int x, y; + int wide,tall; + + GetTrackRect( x, y, wide, tall ); + + surface()->DrawSetColor( m_TrackColor ); + surface()->DrawFilledRect( x, y, x + wide, y + tall ); + if (_insetBorder) + { + _insetBorder->Paint( x, y, x + wide, y + tall ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the range of the slider. +//----------------------------------------------------------------------------- +void Slider::SetRange(int min,int max) +{ + _range[0]=min; + _range[1]=max; + + ClampRange(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sanity check and clamp the range if necessary. +//----------------------------------------------------------------------------- +void Slider::ClampRange() +{ + if ( _range[0] < _range[1] ) + { + if(_value<_range[0]) + { + SetValue( _range[0], false ); + } + else if( _value>_range[1]) + { + SetValue( _range[1], false ); + } + } + else + { + if(_value<_range[1]) + { + SetValue( _range[1], false ); + } + else if( _value>_range[0]) + { + SetValue( _range[0], false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the max and min values of the slider +//----------------------------------------------------------------------------- +void Slider::GetRange(int& min,int& max) +{ + min=_range[0]; + max=_range[1]; +} + +//----------------------------------------------------------------------------- +// Purpose: Respond when the cursor is moved in our window if we are clicking +// and dragging. +//----------------------------------------------------------------------------- +void Slider::OnCursorMoved(int x,int y) +{ + if(!_dragging) + { + return; + } + +// input()->GetCursorPos(x,y); + input()->GetCursorPosition( x, y ); + ScreenToLocal(x,y); + +// int wide,tall; +// GetPaintSize(wide,tall); + int _x, _y, wide, tall; + GetTrackRect( _x, _y, wide, tall ); + + _nobPos[0]=_nobDragStartPos[0]+(x-_dragStartPos[0]); + _nobPos[1]=_nobDragStartPos[1]+(x-_dragStartPos[0]); + + int rightEdge = _x +wide; + int unclamped = _nobPos[ 0 ]; + + if(_nobPos[1]>rightEdge) + { + _nobPos[0]=rightEdge-(_nobPos[1]-_nobPos[0]); + _nobPos[1]=rightEdge; + } + + if(_nobPos[0]<_x) + { + int offset = _x - _nobPos[0]; + _nobPos[1]=_nobPos[1]-offset; + _nobPos[0]=0; + } + + int value = EstimateValueAtPos( unclamped, 0 ); + SetValue( value ); + + // RecomputeValueFromNobPos(); + Repaint(); + SendSliderMovedMessage(); +} + +//----------------------------------------------------------------------------- +// Purpose: If you click on the slider outside of the nob, the nob jumps +// to the click position, and if this setting is enabled, the nob +// is then draggable from the new position until the mouse is released +// Input : state - +//----------------------------------------------------------------------------- +void Slider::SetDragOnRepositionNob( bool state ) +{ + m_bIsDragOnRepositionNob = state; +} + +bool Slider::IsDragOnRepositionNob() const +{ + return m_bIsDragOnRepositionNob; +} + +bool Slider::IsDragged( void ) const +{ + return _dragging; +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to mouse presses. Trigger Record staring positon. +//----------------------------------------------------------------------------- +void Slider::OnMousePressed(MouseCode code) +{ + int x,y; + + if (!IsEnabled()) + return; + +// input()->GetCursorPos(x,y); + input()->GetCursorPosition( x, y ); + + ScreenToLocal(x,y); + RequestFocus(); + + bool startdragging = false, bPostDragStartSignal = false; + + if ((x >= _nobPos[0]) && (x < _nobPos[1])) + { + startdragging = true; + bPostDragStartSignal = true; + } + else + { + // we clicked elsewhere on the slider; move the nob to that position + int min, max; + GetRange(min, max); + if ( m_bUseSubRange ) + { + min = _subrange[ 0 ]; + max = _subrange[ 1 ]; + } + +// int wide = GetWide(); + int _x, _y, wide, tall; + GetTrackRect( _x, _y, wide, tall ); + if ( wide > 0 ) + { + float frange = ( float )( max - min ); + float clickFrac = clamp( ( float )( x - _x ) / (float)( wide - 1 ), 0.0f, 1.0f ); + + float value = (float)min + clickFrac * frange; + + startdragging = IsDragOnRepositionNob(); + + if ( startdragging ) + { + _dragging = true; // Required when as + SendSliderDragStartMessage(); + } + + SetValue( ( int )( value + 0.5f ) ); + } + } + + if ( startdragging ) + { + // drag the nob + _dragging = true; + input()->SetMouseCapture(GetVPanel()); + _nobDragStartPos[0] = _nobPos[0]; + _nobDragStartPos[1] = _nobPos[1]; + _dragStartPos[0] = x; + _dragStartPos[1] = y; + } + + if ( bPostDragStartSignal ) + SendSliderDragStartMessage(); +} + +//----------------------------------------------------------------------------- +// Purpose: Just handle double presses like mouse presses +//----------------------------------------------------------------------------- +void Slider::OnMouseDoublePressed(MouseCode code) +{ + OnMousePressed(code); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef _X360 +void Slider::OnKeyCodePressed(KeyCode code) +{ + switch ( GetBaseButtonCode( code ) ) + { + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + case KEY_XSTICK2_LEFT: + SetValue(GetValue() - 1); + break; + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + case KEY_XSTICK2_RIGHT: + SetValue(GetValue() + 1); + break; + default: + BaseClass::OnKeyCodePressed(code); + break; + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses +//----------------------------------------------------------------------------- +void Slider::OnKeyCodeTyped(KeyCode code) +{ + switch (code) + { + // for now left and right arrows just open or close submenus if they are there. + case KEY_LEFT: + case KEY_DOWN: + { + int val = GetValue(); + SetValue(val-1); + break; + } + case KEY_RIGHT: + case KEY_UP: + { + int val = GetValue(); + SetValue(val+1); + break; + } + case KEY_PAGEDOWN: + { + int min, max; + GetRange(min, max); + float range = (float) max-min; + float pertick = range/m_nNumTicks; + int val = GetValue(); + SetValue(val - (int) pertick); + break; + } + case KEY_PAGEUP: + { + int min, max; + GetRange(min, max); + float range = (float) max-min; + float pertick = range/m_nNumTicks; + int val = GetValue(); + SetValue(val + (int) pertick); + break; + } + case KEY_HOME: + { + int min, max; + GetRange(min, max); + SetValue(min); + break; + } + case KEY_END: + { + int min, max; + GetRange(min, max); + SetValue(max); + break; + } + default: + BaseClass::OnKeyCodeTyped(code); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop dragging when the mouse is released. +//----------------------------------------------------------------------------- +void Slider::OnMouseReleased(MouseCode code) +{ + if ( _dragging ) + { + _dragging=false; + input()->SetMouseCapture(null); + } + + if ( IsEnabled() ) + { + SendSliderDragEndMessage(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the nob's position (the ends of each side of the nob) +//----------------------------------------------------------------------------- +void Slider::GetNobPos(int& min, int& max) +{ + min=_nobPos[0]; + max=_nobPos[1]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Slider::SetButtonOffset(int buttonOffset) +{ + _buttonOffset=buttonOffset; +} + +void Slider::SetThumbWidth( int width ) +{ + _nobSize = (float)width; +} + + +//----------------------------------------------------------------------------- +// Purpose: Set the number of ticks that appear under the slider. +//----------------------------------------------------------------------------- +void Slider::SetNumTicks( int ticks ) +{ + m_nNumTicks = ticks; +} diff --git a/vgui2/vgui_controls/Splitter.cpp b/vgui2/vgui_controls/Splitter.cpp new file mode 100644 index 0000000..96bde98 --- /dev/null +++ b/vgui2/vgui_controls/Splitter.cpp @@ -0,0 +1,765 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include <vgui/IScheme.h> +#include <vgui/Cursor.h> +#include <vgui/IInput.h> +#include <vgui_controls/Splitter.h> +#include "tier1/KeyValues.h" +#include <limits.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + + +enum +{ + SPLITTER_HANDLE_WIDTH = 4 +}; + + +//----------------------------------------------------------------------------- +// Splitter handle +//----------------------------------------------------------------------------- +namespace vgui +{ + +class SplitterHandle : public Panel +{ + DECLARE_CLASS_SIMPLE( SplitterHandle, Panel ); + +public: + SplitterHandle( Splitter *parent, const char *name, SplitterMode_t mode, int nIndex ); + ~SplitterHandle(); + + virtual void ApplySchemeSettings( IScheme *pScheme ); + virtual void OnMousePressed( MouseCode code ); + virtual void OnMouseReleased( MouseCode code ); + virtual void OnCursorMoved( int x, int y ); + virtual void OnMouseDoublePressed( MouseCode code ); + +private: + SplitterMode_t m_nMode; + int m_nIndex; + bool m_bDragging; +}; + +} // end namespace vgui + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +SplitterHandle::SplitterHandle( Splitter *parent, const char *name, SplitterMode_t mode, int nIndex ) : BaseClass( parent, name ) +{ + int w, h; + parent->GetSize( w, h ); + + if ( mode == SPLITTER_MODE_HORIZONTAL ) + { + SetSize( w, SPLITTER_HANDLE_WIDTH ); + SetCursor( dc_sizens ); + } + else + { + SetSize( SPLITTER_HANDLE_WIDTH, h ); + SetCursor( dc_sizewe ); + } + + SetVisible( true ); + SetPaintBackgroundEnabled( false ); + SetPaintEnabled( false ); + SetPaintBorderEnabled( true ); + m_bDragging = false; + m_nIndex = nIndex; + m_nMode = mode; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +SplitterHandle::~SplitterHandle() +{ +} + + +//----------------------------------------------------------------------------- +// Scheme settings +//----------------------------------------------------------------------------- +void SplitterHandle::ApplySchemeSettings(IScheme *pScheme) +{ + // Cache off background color stored in SetSplitterColor + Color c = GetBgColor(); + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); + BaseClass::ApplySchemeSettings(pScheme); + SetBgColor( c ); +} + + +//----------------------------------------------------------------------------- +// Capture mouse when dragging +//----------------------------------------------------------------------------- +void SplitterHandle::OnMousePressed(MouseCode code) +{ + if ( !m_bDragging ) + { + input()->SetMouseCapture(GetVPanel()); + m_bDragging = true; + } +} + + +//----------------------------------------------------------------------------- +// Release mouse capture when finished dragging +//----------------------------------------------------------------------------- +void SplitterHandle::OnMouseReleased(MouseCode code) +{ + if ( m_bDragging ) + { + input()->SetMouseCapture(NULL); + m_bDragging = false; + } +} + + +//----------------------------------------------------------------------------- +// While dragging, update the splitter position +//----------------------------------------------------------------------------- +void SplitterHandle::OnCursorMoved(int x, int y) +{ + if (m_bDragging) + { + input()->GetCursorPos( x, y ); + Splitter *pSplitter = assert_cast<Splitter*>( GetParent() ); + pSplitter->ScreenToLocal( x,y ); + pSplitter->SetSplitterPosition( m_nIndex, (m_nMode == SPLITTER_MODE_HORIZONTAL) ? y : x ); + } +} + + +//----------------------------------------------------------------------------- +// Double-click: make both panels on either side of the splitter equal size +//----------------------------------------------------------------------------- +void SplitterHandle::OnMouseDoublePressed( MouseCode code ) +{ + Splitter *pSplitter = assert_cast<Splitter*>( GetParent() ); + pSplitter->EvenlyRespaceSplitters(); +} + + + +//----------------------------------------------------------------------------- +// Returns a panel that chains user configs +//----------------------------------------------------------------------------- +namespace vgui +{ + +class SplitterChildPanel : public EditablePanel +{ + DECLARE_CLASS_SIMPLE( SplitterChildPanel, EditablePanel ); + +public: + SplitterChildPanel( Panel *parent, const char *panelName ) : BaseClass( parent, panelName ) + { + SetPaintBackgroundEnabled( false ); + SetPaintEnabled( false ); + SetPaintBorderEnabled( false ); + } + + virtual ~SplitterChildPanel() {} + + // Children may have user config settings + bool HasUserConfigSettings() + { + return true; + } +}; + +} // end namespace vgui + +//----------------------------------------------------------------------------- +// +// Splitter panel +// +//----------------------------------------------------------------------------- +vgui::Panel *Splitter_V_Factory() +{ + return new Splitter( NULL, NULL, SPLITTER_MODE_VERTICAL, 1 ); +} + +vgui::Panel *Splitter_H_Factory() +{ + return new Splitter( NULL, NULL, SPLITTER_MODE_HORIZONTAL, 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Splitter::Splitter( Panel *parent, const char *name, SplitterMode_t mode, int nCount ) : BaseClass( parent, name ) +{ + Assert( nCount >= 1 ); + m_Mode = mode; + + SetPaintBackgroundEnabled( false ); + SetPaintEnabled( false ); + SetPaintBorderEnabled( false ); + + RecreateSplitters( nCount ); + + EvenlyRespaceSplitters(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Splitter::~Splitter() +{ + m_Splitters.RemoveAll(); +} + +void Splitter::RecreateSplitters( int nCount ) +{ + int i; + int c = m_Splitters.Count(); + for ( i = 0; i < c; ++i ) + { + delete m_Splitters[ i ].m_pPanel; + delete m_Splitters[ i ].m_pHandle; + } + m_Splitters.RemoveAll(); + + for ( i = 0; i < (nCount + 1); ++i ) + { + char pBuffer[512]; + Q_snprintf( pBuffer, sizeof(pBuffer), "child%d", i ); + + int nIndex = m_Splitters.AddToTail( ); + SplitterChildPanel *pEditablePanel = new SplitterChildPanel( this, pBuffer ); + m_Splitters[nIndex].m_pPanel = pEditablePanel; + m_Splitters[nIndex].m_bLocked = false; + m_Splitters[nIndex].m_nLockedSize = 0; + } + + // We do this in 2 loops so that the first N children are actual child panels + for ( i = 0; i < nCount; ++i ) + { + SplitterHandle *pHandle = new SplitterHandle( this, "SplitterHandle", m_Mode, i ); + m_Splitters[i].m_pHandle = pHandle; + pHandle->MoveToFront(); + } + m_Splitters[nCount].m_pHandle = NULL; +} + + +//----------------------------------------------------------------------------- +// Sets the splitter color +//----------------------------------------------------------------------------- +void Splitter::SetSplitterColor( Color c ) +{ + int nCount = m_Splitters.Count() - 1; + if ( c.a() != 0 ) + { + for ( int i = 0; i < nCount; ++i ) + { + m_Splitters[i].m_pHandle->SetBgColor( c ); + m_Splitters[i].m_pHandle->SetPaintBackgroundEnabled( true ); + } + } + else + { + for ( int i = 0; i < nCount; ++i ) + { + m_Splitters[i].m_pHandle->SetPaintBackgroundEnabled( false ); + } + } +} + + +//----------------------------------------------------------------------------- +// Enables borders on the splitters +//----------------------------------------------------------------------------- +void Splitter::EnableBorders( bool bEnable ) +{ + int nCount = m_Splitters.Count() - 1; + for ( int i = 0; i < nCount; ++i ) + { + m_Splitters[i].m_pHandle->SetPaintBorderEnabled( bEnable ); + } +} + + +//----------------------------------------------------------------------------- +// controls splitters +//----------------------------------------------------------------------------- +int Splitter::GetSplitterCount() const +{ + return m_Splitters.Count() - 1; +} + + +//----------------------------------------------------------------------------- +// controls splitters +//----------------------------------------------------------------------------- +int Splitter::GetSubPanelCount() const +{ + return m_Splitters.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Applies resouce settings +//----------------------------------------------------------------------------- +void Splitter::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + + // Look for splitter positions + int nSplitterCount = GetSplitterCount(); + for ( int i = 0; i < nSplitterCount; ++i ) + { + char pBuffer[512]; + Q_snprintf( pBuffer, sizeof(pBuffer), "splitter%d", i ); + + int nSplitterPos = inResourceData->GetInt( pBuffer , -1 ); + if ( nSplitterPos >= 0 ) + { + SetSplitterPosition( i, nSplitterPos ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Splitter::GetPosRange() +{ + int w, h; + GetSize( w, h ); + int nPosRange = (m_Mode == SPLITTER_MODE_HORIZONTAL) ? h : w; + return nPosRange; +} + + +//----------------------------------------------------------------------------- +// Locks the size of a particular child in pixels. +//----------------------------------------------------------------------------- +void Splitter::LockChildSize( int nChildIndex, int nSize ) +{ + Assert( nChildIndex < m_Splitters.Count() ); + SplitterInfo_t &info = m_Splitters[nChildIndex]; + nSize += SPLITTER_HANDLE_WIDTH; + if ( !info.m_bLocked || (info.m_nLockedSize != nSize) ) + { + float flPrevPos = (nChildIndex > 0) ? m_Splitters[nChildIndex-1].m_flPos : 0.0f; + float flOldSize = info.m_flPos - flPrevPos; + float flDelta = nSize - flOldSize; + int nCount = m_Splitters.Count(); + for ( int i = nChildIndex; i < nCount-1; ++i ) + { + m_Splitters[i].m_flPos += flDelta; + } + m_Splitters[nCount-1].m_flPos = GetPosRange(); + + info.m_bLocked = true; + info.m_nLockedSize = nSize; + InvalidateLayout(); + } +} + +void Splitter::UnlockChildSize( int nChildIndex ) +{ + Assert( nChildIndex < m_Splitters.Count() ); + SplitterInfo_t &info = m_Splitters[nChildIndex]; + if ( info.m_bLocked ) + { + info.m_bLocked = false; + + float flPrevPos = (nChildIndex > 0) ? m_Splitters[nChildIndex-1].m_flPos : 0.0f; + float flBelowSize = GetPosRange() - flPrevPos; + + int nLockedSize = ComputeLockedSize( nChildIndex + 1 ); + int nUnlockedCount = 1; + int nCount = m_Splitters.Count(); + for ( int i = nChildIndex + 1; i < nCount; ++i ) + { + if ( !m_Splitters[i].m_bLocked ) + { + ++nUnlockedCount; + } + } + + float flUnlockedSize = ( flBelowSize - nLockedSize ) / nUnlockedCount; + + for ( int i = nChildIndex; i < nCount; ++i ) + { + if ( !m_Splitters[i].m_bLocked ) + { + m_Splitters[i].m_flPos = flPrevPos + flUnlockedSize; + } + else + { + m_Splitters[i].m_flPos = flPrevPos + m_Splitters[i].m_nLockedSize; + } + flPrevPos = m_Splitters[i].m_flPos; + } + InvalidateLayout(); + } +} + + +//----------------------------------------------------------------------------- +// Called when size changes +//----------------------------------------------------------------------------- +void Splitter::OnSizeChanged( int newWide, int newTall ) +{ + BaseClass::OnSizeChanged( newWide, newTall ); + + // Don't resize if it's degenerate and won't show up anyway... + if ( newTall <= 0 || newWide <= 0 ) + return; + + int nLockedSize = 0; + float flUnlockedSize = 0.0f; + int nCount = m_Splitters.Count(); + float flLastPos = 0.0f; + int nUnlockedCount = 0; + for ( int i = 0; i < nCount; ++i ) + { + SplitterInfo_t &info = m_Splitters[i]; + if ( info.m_bLocked ) + { + nLockedSize += info.m_nLockedSize; + } + else + { + ++nUnlockedCount; + flUnlockedSize += info.m_flPos - flLastPos; + } + flLastPos = info.m_flPos; + } + + int nNewTotalSize = (m_Mode == SPLITTER_MODE_HORIZONTAL) ? newTall : newWide; + int nNewUnlockedSize = nNewTotalSize - nLockedSize; + if ( nNewUnlockedSize < nUnlockedCount * SPLITTER_HANDLE_WIDTH ) + { + nNewUnlockedSize = nUnlockedCount * SPLITTER_HANDLE_WIDTH; + } + + float flRatio = nNewUnlockedSize / flUnlockedSize; + float flLastPrevPos = 0.0f; + flLastPos = 0.0f; + for ( int i = 0; i < nCount - 1; ++i ) + { + SplitterInfo_t &info = m_Splitters[i]; + if ( info.m_bLocked ) + { + flLastPrevPos = info.m_flPos; + info.m_flPos = flLastPos + info.m_nLockedSize; + } + else + { + float flNewSize = info.m_flPos - flLastPrevPos; + flNewSize *= flRatio; + flLastPrevPos = info.m_flPos; + info.m_flPos = flLastPos + flNewSize; + } + flLastPos = info.m_flPos; + } + + // Clamp the bottom to 1.0 + m_Splitters[nCount-1].m_flPos = nNewTotalSize; +} + + +//----------------------------------------------------------------------------- +// Splitter position +//----------------------------------------------------------------------------- +int Splitter::GetSplitterPosition( int nIndex ) +{ + return (int)( m_Splitters[nIndex].m_flPos + 0.5f ); +} + +void Splitter::SetSplitterPosition( int nIndex, int nPos ) +{ + int nPosRange = GetPosRange(); + if ( nPosRange == 0 ) + return; + + // If we're locked to a sibling, move the previous sibling first + while ( ( nIndex >= 0 ) && m_Splitters[nIndex].m_bLocked ) + { + nPos -= m_Splitters[nIndex].m_nLockedSize; + --nIndex; + } + if ( nIndex < 0 ) + return; + + // Clamp to the valid positional range + int i; + int nMinPos = 0; + for ( i = 0; i < nIndex; ++i ) + { + if ( !m_Splitters[i].m_bLocked ) + { + nMinPos += SPLITTER_HANDLE_WIDTH; + } + else + { + nMinPos += m_Splitters[i].m_nLockedSize; + } + } + + int nMaxPos = nPosRange - SPLITTER_HANDLE_WIDTH; + int c = GetSplitterCount(); + for ( i = nIndex + 1; i < c; ++i ) + { + if ( !m_Splitters[i].m_bLocked ) + { + nMaxPos -= SPLITTER_HANDLE_WIDTH; + } + else + { + nMaxPos -= m_Splitters[i].m_nLockedSize; + } + } + nPos = clamp( nPos, nMinPos, nMaxPos ); + + m_Splitters[nIndex].m_flPos = nPos; + int p = nPos; + for ( i = nIndex - 1 ; i >= 0; --i ) + { + int nMinPrevPos; + int nMaxPrevPos; + if ( !m_Splitters[i+1].m_bLocked ) + { + nMinPrevPos = -INT_MAX; + nMaxPrevPos = nPos - SPLITTER_HANDLE_WIDTH; + } + else + { + nMinPrevPos = nMaxPrevPos = p - m_Splitters[i+1].m_nLockedSize; + } + + int nCurPos = GetSplitterPosition( i ); + if ( nMaxPrevPos < nCurPos || nMinPrevPos > nCurPos ) + { + m_Splitters[ i ].m_flPos = nMaxPrevPos; + p = nMaxPrevPos; + } + else + { + p = m_Splitters[ i ].m_flPos; + } + } + + for ( i = nIndex + 1 ; i < c; ++i ) + { + int nMinNextPos; + int nMaxNextPos; + if ( !m_Splitters[i].m_bLocked ) + { + nMinNextPos = nPos + SPLITTER_HANDLE_WIDTH; + nMaxNextPos = INT_MAX; + } + else + { + nMinNextPos = nMaxNextPos = nPos + m_Splitters[i].m_nLockedSize; + } + + int nCurPos = GetSplitterPosition( i ); + if ( nMinNextPos > nCurPos || nMaxNextPos < nCurPos ) + { + m_Splitters[ i ].m_flPos = nMinNextPos; + nPos = nMinNextPos; + } + else + { + nPos = m_Splitters[ i ].m_flPos; + } + } + + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Computes the locked size +//----------------------------------------------------------------------------- +int Splitter::ComputeLockedSize( int nStartingIndex ) +{ + int nLockedSize = 0; + int nCount = m_Splitters.Count(); + for ( int i = nStartingIndex; i < nCount; ++i ) + { + if ( m_Splitters[i].m_bLocked ) + { + nLockedSize += m_Splitters[i].m_nLockedSize; + } + } + return nLockedSize; +} + + +//----------------------------------------------------------------------------- +// Evenly respaces all the splitters +//----------------------------------------------------------------------------- +void Splitter::EvenlyRespaceSplitters( ) +{ + int nSplitterCount = GetSubPanelCount(); + if ( nSplitterCount == 0 ) + return; + + int nLockedSize = ComputeLockedSize( 0 ); + float flUnlockedSize = (float)( GetPosRange() - nLockedSize ); + float flDPos = flUnlockedSize / (float)nSplitterCount; + if ( flDPos < SPLITTER_HANDLE_WIDTH ) + { + flDPos = SPLITTER_HANDLE_WIDTH; + } + float flPos = 0.0f; + for ( int i = 0; i < nSplitterCount; ++i ) + { + if ( !m_Splitters[i].m_bLocked ) + { + flPos += flDPos; + } + else + { + flPos += m_Splitters[i].m_nLockedSize; + } + m_Splitters[i].m_flPos = flPos; + } + + InvalidateLayout(); +} + +void Splitter::RespaceSplitters( float *flFractions ) +{ + int nSplitterCount = GetSubPanelCount(); + if ( nSplitterCount == 0 ) + return; + + float flPos = 0.0f; + int nPosRange = GetPosRange(); + for ( int i = 0; i < nSplitterCount; ++i ) + { + flPos += flFractions[i]; + m_Splitters[i].m_flPos = flPos * nPosRange; + } + + Assert( flPos == 1.0f ); + + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: sets user settings +//----------------------------------------------------------------------------- +void Splitter::ApplyUserConfigSettings(KeyValues *userConfig) +{ + BaseClass::ApplyUserConfigSettings( userConfig ); + + // read the splitter sizes + int c = m_Splitters.Count(); + float *pFractions = (float*)_alloca( c * sizeof(float) ); + float flTotalSize = 0.0f; + for ( int i = 0; i < c; i++ ) + { + char name[128]; + _snprintf(name, sizeof(name), "%d_splitter_pos", i); + pFractions[i] = userConfig->GetFloat( name, flTotalSize + SPLITTER_HANDLE_WIDTH + 1 ); + flTotalSize = pFractions[i]; + } + + if ( flTotalSize != 0.0f ) + { + int nPosRange = GetPosRange(); + for ( int i = 0; i < c; ++i ) + { + pFractions[i] /= flTotalSize; + m_Splitters[i].m_flPos = pFractions[i] * nPosRange; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns user config settings for this control +//----------------------------------------------------------------------------- +void Splitter::GetUserConfigSettings(KeyValues *userConfig) +{ + BaseClass::GetUserConfigSettings( userConfig ); + + // save which columns are hidden + int c = m_Splitters.Count(); + for ( int i = 0 ; i < c; i++ ) + { + char name[128]; + _snprintf(name, sizeof(name), "%d_splitter_pos", i); + userConfig->SetFloat( name, m_Splitters[i].m_flPos ); + } +} + + +//----------------------------------------------------------------------------- +// Called to perform layout +//----------------------------------------------------------------------------- +void Splitter::PerformLayout( ) +{ + BaseClass::PerformLayout(); + + int nSplitterCount = GetSubPanelCount(); + if ( nSplitterCount == 0 ) + return; + + int w, h; + GetSize( w, h ); + + int nLastPos = 0; + for ( int i = 0; i < nSplitterCount; ++i ) + { + Panel *pChild = m_Splitters[i].m_pPanel; + SplitterHandle *pHandle = m_Splitters[i].m_pHandle; + int nSplitterPos = (int)( m_Splitters[i].m_flPos + 0.5f ); + + if ( m_Mode == SPLITTER_MODE_HORIZONTAL ) + { + pChild->SetPos( 0, nLastPos ); + pChild->SetSize( w, nSplitterPos - nLastPos ); + if ( pHandle ) + { + pHandle->SetPos( 0, nSplitterPos ); + pHandle->SetSize( w, SPLITTER_HANDLE_WIDTH ); + } + } + else + { + pChild->SetPos( nLastPos, 0 ); + pChild->SetSize( nSplitterPos - nLastPos, h ); + if ( pHandle ) + { + pHandle->SetPos( nSplitterPos, 0 ); + pHandle->SetSize( SPLITTER_HANDLE_WIDTH, h ); + } + } + + nLastPos = nSplitterPos + SPLITTER_HANDLE_WIDTH; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Splitter::GetSettings( KeyValues *outResourceData ) +{ + BaseClass::GetSettings( outResourceData ); +} diff --git a/vgui2/vgui_controls/TextEntry.cpp b/vgui2/vgui_controls/TextEntry.cpp new file mode 100644 index 0000000..dac6cbe --- /dev/null +++ b/vgui2/vgui_controls/TextEntry.cpp @@ -0,0 +1,4279 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <utlvector.h> + +#include <vgui/Cursor.h> +#include <vgui/IInput.h> +#include <vgui/IScheme.h> +#include <vgui/ISystem.h> +#include <vgui/ISurface.h> +#include <vgui/ILocalize.h> +#include <vgui/IPanel.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> + +#include <vgui_controls/Menu.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/MenuItem.h> +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +enum +{ + // maximum size of text buffer + BUFFER_SIZE=999999, +}; + +using namespace vgui; + +static const int DRAW_OFFSET_X = 3,DRAW_OFFSET_Y = 1; + +DECLARE_BUILD_FACTORY( TextEntry ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +TextEntry::TextEntry(Panel *parent, const char *panelName) : BaseClass(parent, panelName) +{ + SetTriplePressAllowed( true ); + + _font = INVALID_FONT; + _smallfont = INVALID_FONT; + + m_szComposition[ 0 ] = L'\0'; + + m_bAllowNumericInputOnly = false; + m_bAllowNonAsciiCharacters = false; + _hideText = false; + _editable = false; + _verticalScrollbar = false; + _cursorPos = 0; + _currentStartIndex = 0; + _horizScrollingAllowed = true; + _cursorIsAtEnd = false; + _putCursorAtEnd = false; + _multiline = false; + _cursorBlinkRate = 400; + _mouseSelection = false; + _mouseDragSelection = false; + _vertScrollBar=NULL; + _catchEnterKey = false; + _maxCharCount = -1; + _charCount = 0; + _wrap = false; // don't wrap by default + _sendNewLines = false; // don't pass on a newline msg by default + _drawWidth = 0; + m_bAutoProgressOnHittingCharLimit = false; + m_pIMECandidates = NULL; + m_hPreviousIME = input()->GetEnglishIMEHandle(); + m_bDrawLanguageIDAtLeft = false; + m_nLangInset = 0; + m_bUseFallbackFont = false; + m_hFallbackFont = INVALID_FONT; + + //a -1 for _select[0] means that the selection is empty + _select[0] = -1; + _select[1] = -1; + m_pEditMenu = NULL; + + //this really just inits it when in here + ResetCursorBlink(); + + SetCursor(dc_ibeam); + + SetEditable(true); + + // initialize the line break array + m_LineBreaks.AddToTail(BUFFER_SIZE); + + _recalculateBreaksIndex = 0; + + _selectAllOnFirstFocus = false; + _selectAllOnFocusAlways = false; + + //position the cursor so it is at the end of the text + GotoTextEnd(); + + // If keyboard focus is in an edit control, don't chain keyboard mappings up to parents since it could mess with typing in text. + SetAllowKeyBindingChainToParent( false ); + + REGISTER_COLOR_AS_OVERRIDABLE( _disabledFgColor, "disabledFgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _disabledBgColor, "disabledBgColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _selectionColor, "selectionColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _selectionTextColor, "selectionTextColor_override" ); + REGISTER_COLOR_AS_OVERRIDABLE( _defaultSelectionBG2Color, "defaultSelectionBG2Color_override" ); +} + + +TextEntry::~TextEntry() +{ + delete m_pEditMenu; + delete m_pIMECandidates; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TextEntry::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("TextEntry.TextColor", pScheme)); + SetBgColor(GetSchemeColor("TextEntry.BgColor", pScheme)); + + _cursorColor = GetSchemeColor("TextEntry.CursorColor", pScheme); + _disabledFgColor = GetSchemeColor("TextEntry.DisabledTextColor", pScheme); + _disabledBgColor = GetSchemeColor("TextEntry.DisabledBgColor", pScheme); + + _selectionTextColor = GetSchemeColor("TextEntry.SelectedTextColor", GetFgColor(), pScheme); + _selectionColor = GetSchemeColor("TextEntry.SelectedBgColor", pScheme); + _defaultSelectionBG2Color = GetSchemeColor("TextEntry.OutOfFocusSelectedBgColor", pScheme); + _focusEdgeColor = GetSchemeColor("TextEntry.FocusEdgeColor", Color(0, 0, 0, 0), pScheme); + + SetBorder( pScheme->GetBorder("ButtonDepressedBorder")); + + if ( _font == INVALID_FONT ) _font = pScheme->GetFont("Default", IsProportional() ); + if ( _smallfont == INVALID_FONT ) _smallfont = pScheme->GetFont( "DefaultVerySmall", IsProportional() ); + + SetFont( _font ); +} + +void TextEntry::SetSelectionTextColor( const Color& clr ) +{ + _selectionTextColor = clr; +} + +void TextEntry::SetSelectionBgColor( const Color& clr ) +{ + _selectionColor = clr; +} + +void TextEntry::SetSelectionUnfocusedBgColor( const Color& clr ) +{ + _defaultSelectionBG2Color = clr; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the color of the background when the control is disabled +//----------------------------------------------------------------------------- +void TextEntry::SetDisabledBgColor(Color col) +{ + _disabledBgColor = col; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sends a message if the data has changed +// Turns off any selected text in the window if we are not using the edit menu +//----------------------------------------------------------------------------- +void TextEntry::OnKillFocus() +{ + m_szComposition[ 0 ] = L'\0'; + HideIMECandidates(); + + if (_dataChanged) + { + FireActionSignal(); + _dataChanged = false; + } + + // check if we clicked the right mouse button or if it is down + bool mouseRightClicked = input()->WasMousePressed(MOUSE_RIGHT); + bool mouseRightUp = input()->WasMouseReleased(MOUSE_RIGHT); + bool mouseRightDown = input()->IsMouseDown(MOUSE_RIGHT); + + if (mouseRightClicked || mouseRightDown || mouseRightUp ) + { + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + + // if we're right clicking within our window, we don't actually kill focus + if (IsWithin(cursorX, cursorY)) + return; + } + + // clear any selection + SelectNone(); + + // move the cursor to the start +// GotoTextStart(); + + PostActionSignal( new KeyValues( "TextKillFocus" ) ); + + // chain + BaseClass::OnKillFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Wipe line breaks after the size of a panel has been changed +//----------------------------------------------------------------------------- +void TextEntry::OnSizeChanged(int newWide, int newTall) +{ + BaseClass::OnSizeChanged(newWide, newTall); + + // blow away the line breaks list + _recalculateBreaksIndex = 0; + m_LineBreaks.RemoveAll(); + m_LineBreaks.AddToTail(BUFFER_SIZE); + + // if we're bigger, see if we can scroll left to put more text in the window + if (newWide > _drawWidth) + { + ScrollLeftForResize(); + } + + _drawWidth = newWide; + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set the text array - convert ANSI text to unicode and pass to unicode function +//----------------------------------------------------------------------------- +void TextEntry::SetText(const char *text) +{ + if (!text) + { + text = ""; + } + + if (text[0] == '#') + { + // check for localization + wchar_t *wsz = g_pVGuiLocalize->Find(text); + if (wsz) + { + SetText(wsz); + return; + } + } + + size_t len = strlen( text ); + if ( len < 1023 ) + { + wchar_t unicode[ 1024 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( text, unicode, sizeof( unicode ) ); + SetText( unicode ); + } + else + { + size_t lenUnicode = ( len * sizeof( wchar_t ) + 4 ); + wchar_t *unicode = ( wchar_t * ) malloc( lenUnicode ); + g_pVGuiLocalize->ConvertANSIToUnicode( text, unicode, lenUnicode ); + SetText( unicode ); + free( unicode ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the text array +// Using this function will cause all lineBreaks to be discarded. +// This is because this fxn replaces the contents of the text buffer. +// For modifying large buffers use insert functions. +//----------------------------------------------------------------------------- +void TextEntry::SetText(const wchar_t *wszText) +{ + if (!wszText) + { + wszText = L""; + } + int textLen = wcslen(wszText); + m_TextStream.RemoveAll(); + m_TextStream.EnsureCapacity(textLen); + + int missed_count = 0; + for (int i = 0; i < textLen; i++) + { + if(wszText[i]=='\r') // don't insert \r characters + { + missed_count++; + continue; + } + m_TextStream.AddToTail(wszText[i]); + SetCharAt(wszText[i], i-missed_count); + } + + GotoTextStart(); + SelectNone(); + + // reset the data changed flag + _dataChanged = false; + + // blow away the line breaks list + _recalculateBreaksIndex = 0; + m_LineBreaks.RemoveAll(); + m_LineBreaks.AddToTail(BUFFER_SIZE); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the value of char at index position. +//----------------------------------------------------------------------------- +void TextEntry::SetCharAt(wchar_t ch, int index) +{ + if ((ch == '\n') || (ch == '\0')) + { + // if its not at the end of the buffer it matters. + // redo the linebreaks + //if (index != m_TextStream.Count()) + { + _recalculateBreaksIndex = 0; + m_LineBreaks.RemoveAll(); + m_LineBreaks.AddToTail(BUFFER_SIZE); + } + } + + if (index < 0) + return; + + if (index >= m_TextStream.Count()) + { + m_TextStream.AddMultipleToTail(index - m_TextStream.Count() + 1); + } + m_TextStream[index] = ch; + _dataChanged = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Restarts the time of the next cursor blink +//----------------------------------------------------------------------------- +void TextEntry::ResetCursorBlink() +{ + _cursorBlink=false; + _cursorNextBlinkTime=system()->GetTimeMillis()+_cursorBlinkRate; +} + +//----------------------------------------------------------------------------- +// Purpose: Hides the text buffer so it will not be drawn +//----------------------------------------------------------------------------- +void TextEntry::SetTextHidden(bool bHideText) +{ + _hideText = bHideText; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: return character width +//----------------------------------------------------------------------------- +int getCharWidth(HFont font, wchar_t ch) +{ + if (!iswcntrl(ch)) + { + int a, b, c; + surface()->GetCharABCwide(font, ch, a, b, c); + return (a + b + c); + } + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Given cursor's position in the text buffer, convert it to +// the local window's x and y pixel coordinates +// Input: cursorPos: cursor index +// Output: cx, cy, the corresponding coords in the local window +//----------------------------------------------------------------------------- +void TextEntry::CursorToPixelSpace(int cursorPos, int &cx, int &cy) +{ + int yStart = GetYStart(); + + int x = DRAW_OFFSET_X, y = yStart; + _pixelsIndent = 0; + int lineBreakIndexIndex = 0; + + for (int i = GetStartDrawIndex(lineBreakIndexIndex); i < m_TextStream.Count(); i++) + { + wchar_t ch = m_TextStream[i]; + if (_hideText) + { + ch = '*'; + } + + // if we've found the position, break + if (cursorPos == i) + { + // even if this is a line break entry for the cursor, the next insert + // will be at this position, which will push the line break forward one + // so don't push the cursor down a line here... + /*if (!_putCursorAtEnd) + { + // if we've passed a line break go to that + if (m_LineBreaks[lineBreakIndexIndex] == i) + { + // add another line + AddAnotherLine(x,y); + lineBreakIndexIndex++; + } + }*/ + break; + } + + // if we've passed a line break go to that + if (m_LineBreaks.Count() && + lineBreakIndexIndex < m_LineBreaks.Count() && + m_LineBreaks[lineBreakIndexIndex] == i) + { + // add another line + AddAnotherLine(x,y); + lineBreakIndexIndex++; + } + + // add to the current position + x += getCharWidth(_font, ch); + } + + if ( m_bDrawLanguageIDAtLeft ) + { + x += m_nLangInset; + } + + cx = x; + cy = y; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts local pixel coordinates to an index in the text buffer +// This function appears to be used only in response to mouse clicking +// Input : cx - +// cy - pixel location +//----------------------------------------------------------------------------- +int TextEntry::PixelToCursorSpace(int cx, int cy) +{ + + int w, h; + GetSize(w, h); + cx = clamp(cx, 0, w+100); + cy = clamp(cy, 0, h); + + _putCursorAtEnd = false; // Start off assuming we clicked somewhere in the text + + int fontTall = surface()->GetFontTall(_font); + + // where to Start reading + int yStart = GetYStart(); + int x = DRAW_OFFSET_X, y = yStart; + _pixelsIndent = 0; + int lineBreakIndexIndex = 0; + + int startIndex = GetStartDrawIndex(lineBreakIndexIndex); + bool onRightLine = false; + int i; + for (i = startIndex; i < m_TextStream.Count(); i++) + { + wchar_t ch = m_TextStream[i]; + if (_hideText) + { + ch = '*'; + } + + // if we are on the right line but off the end of if put the cursor at the end of the line + if (m_LineBreaks[lineBreakIndexIndex] == i ) + { + // add another line + AddAnotherLine(x,y); + lineBreakIndexIndex++; + + if (onRightLine) + { + _putCursorAtEnd = true; + return i; + } + } + + // check to see if we're on the right line + if (cy < yStart) + { + // cursor is above panel + onRightLine = true; + _putCursorAtEnd = true; // this will make the text scroll up if needed + } + else if (cy >= y && (cy < (y + fontTall + DRAW_OFFSET_Y))) + { + onRightLine = true; + } + + int wide = getCharWidth(_font, ch); + + // if we've found the position, break + if (onRightLine) + { + if (cx > GetWide()) // off right side of window + { + } + else if (cx < (DRAW_OFFSET_X + _pixelsIndent) || cy < yStart) // off left side of window + { + return i; // move cursor one to left + } + + if (cx >= x && cx < (x + wide)) + { + // check which side of the letter they're on + if (cx < (x + (wide * 0.5))) // left side + { + return i; + } + else // right side + { + return i + 1; + } + } + } + x += wide; + } + + return i; +} + +//----------------------------------------------------------------------------- +// Purpose: Draws a character in the panel +// Input: ch - character to draw +// font - font to use +// x, y - pixel location to draw char at +// Output: returns the width of the character drawn +//----------------------------------------------------------------------------- +int TextEntry::DrawChar(wchar_t ch, HFont font, int index, int x, int y) +{ + // add to the current position + int charWide = getCharWidth(font, ch); + int fontTall=surface()->GetFontTall(font); + if (!iswcntrl(ch)) + { + // draw selection, if any + int selection0 = -1, selection1 = -1; + GetSelectedRange(selection0, selection1); + + if (index >= selection0 && index < selection1) + { + // draw background selection color + VPANEL focus = input()->GetFocus(); + Color bgColor; + bool hasFocus = HasFocus(); + bool childOfFocus = focus && ipanel()->HasParent(focus, GetVPanel()); + + // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected + if ( hasFocus || childOfFocus ) + { + bgColor = _selectionColor; + } + else + { + bgColor =_defaultSelectionBG2Color; + } + + surface()->DrawSetColor(bgColor); + + surface()->DrawFilledRect(x, y, x + charWide, y + 1 + fontTall); + + // reset text color + surface()->DrawSetTextColor(_selectionTextColor); + } + if (index == selection1) + { + // we've come out of selection, reset the color + surface()->DrawSetTextColor(GetFgColor()); + } + + surface()->DrawSetTextPos(x, y); + surface()->DrawUnicodeChar(ch); + + return charWide; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the cursor, cursor is not drawn when it is blinked gone +// Input: x,y where to draw cursor +// Output: returns true if cursor was drawn. +//----------------------------------------------------------------------------- +bool TextEntry::DrawCursor(int x, int y) +{ + if (!_cursorBlink) + { + int cx, cy; + CursorToPixelSpace(_cursorPos, cx, cy); + surface()->DrawSetColor(_cursorColor); + int fontTall=surface()->GetFontTall(_font); + surface()->DrawFilledRect(cx, cy, cx + 1, cy + fontTall); + return true; + } + return false; +} + +bool TextEntry::NeedsEllipses( HFont font, int *pIndex ) +{ + Assert( pIndex ); + *pIndex = -1; + int wide = DRAW_OFFSET_X; // buffer on left and right end of text. + for ( int i = 0; i < m_TextStream.Count(); ++i ) + { + wide += getCharWidth( font , m_TextStream[i] ); + if (wide > _drawWidth) + { + *pIndex = i; + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the text in the panel +//----------------------------------------------------------------------------- +void TextEntry::PaintBackground() +{ + BaseClass::PaintBackground(); + + // draw background + Color col; + if (IsEnabled()) + { + col = GetBgColor(); + } + else + { + col = _disabledBgColor; + } + Color saveBgColor = col; + + int wide, tall; + GetSize( wide, tall ); + +// surface()->DrawSetColor(col); +// surface()->DrawFilledRect(0, 0, wide, tall); + + // where to Start drawing + int x = DRAW_OFFSET_X + _pixelsIndent, y = GetYStart(); + + m_nLangInset = 0; + + int langlen = 0; + wchar_t shortcode[ 5 ]; + shortcode[ 0 ] = L'\0'; + + if ( m_bAllowNonAsciiCharacters ) + { + input()->GetIMELanguageShortCode( shortcode, sizeof( shortcode ) ); + + if ( shortcode[ 0 ] != L'\0' && + wcsicmp( shortcode, L"EN" ) ) + { + m_nLangInset = 0; + langlen = wcslen( shortcode ); + for ( int i = 0; i < langlen; ++i ) + { + m_nLangInset += getCharWidth( _smallfont, shortcode[ i ] ); + } + + m_nLangInset += 4; + + if ( m_bDrawLanguageIDAtLeft ) + { + x += m_nLangInset; + } + + wide -= m_nLangInset; + } + } + + HFont useFont = _font; + + surface()->DrawSetTextFont(useFont); + if (IsEnabled()) + { + col = GetFgColor(); + } + else + { + col = _disabledFgColor; + } + surface()->DrawSetTextColor(col); + _pixelsIndent = 0; + + int lineBreakIndexIndex = 0; + int startIndex = GetStartDrawIndex(lineBreakIndexIndex); + int remembery = y; + + int oldEnd = m_TextStream.Count(); + int oldCursorPos = _cursorPos; + int nCompStart = -1; + int nCompEnd = -1; + + // FIXME: Should insert at cursor pos instead + bool composing = m_bAllowNonAsciiCharacters && wcslen( m_szComposition ) > 0; + bool invertcomposition = input()->GetShouldInvertCompositionString(); + + if ( composing ) + { + nCompStart = _cursorPos; + + wchar_t *s = m_szComposition; + while ( *s != L'\0' ) + { + m_TextStream.InsertBefore( _cursorPos, *s ); + ++s; + ++_cursorPos; + } + + nCompEnd = _cursorPos; + } + + bool highlight_composition = ( nCompStart != -1 && nCompEnd != -1 ) ? true : false; + + // draw text with an elipsis + if ( (!_multiline) && (!_horizScrollingAllowed) ) + { + int endIndex = m_TextStream.Count(); + // In editable windows only do the ellipsis if we don't have focus. + // In non editable windows do it all the time. + if ( (!HasFocus() && (IsEditable())) || (!IsEditable()) ) + { + int i = -1; + + // loop through all the characters and sum their widths + bool addEllipses = NeedsEllipses( useFont, &i ); + if ( addEllipses && + !IsEditable() && + m_bUseFallbackFont && + INVALID_FONT != m_hFallbackFont ) + { + // Switch to small font!!! + useFont = m_hFallbackFont; + surface()->DrawSetTextFont(useFont); + addEllipses = NeedsEllipses( useFont, &i ); + } + if (addEllipses) + { + int elipsisWidth = 3 * getCharWidth(useFont, '.'); + while (elipsisWidth > 0 && i >= 0) + { + elipsisWidth -= getCharWidth(useFont, m_TextStream[i]); + i--; + } + endIndex = i + 1; + } + + // if we take off less than the last 3 chars we have to make sure + // we take off the last 3 chars so selected text will look right. + if (m_TextStream.Count() - endIndex < 3 && m_TextStream.Count() - endIndex > 0 ) + { + endIndex = m_TextStream.Count() - 3; + } + } + // draw the text + int i; + for (i = startIndex; i < endIndex; i++) + { + wchar_t ch = m_TextStream[i]; + if (_hideText) + { + ch = '*'; + } + + bool iscompositionchar = false; + + if ( highlight_composition ) + { + iscompositionchar = ( i >= nCompStart && i < nCompEnd ) ? true : false; + if ( iscompositionchar ) + { + // Set the underline color to the text color + surface()->DrawSetColor( col ); + + int w = getCharWidth( useFont, ch ); + + if ( invertcomposition ) + { + // Invert color + surface()->DrawSetTextColor( saveBgColor ); + surface()->DrawSetColor( col ); + + surface()->DrawFilledRect(x, 0, x+w, tall); + // Set the underline color to the text color + surface()->DrawSetColor( saveBgColor ); + } + + surface()->DrawFilledRect( x, tall - 2, x + w, tall - 1 ); + } + } + + + // draw the character and update xposition + x += DrawChar(ch, useFont, i, x, y); + + // Restore color + surface()->DrawSetTextColor(col); + + } + if (endIndex < m_TextStream.Count()) // add an elipsis + { + x += DrawChar('.', useFont, i, x, y); + i++; + x += DrawChar('.', useFont, i, x, y); + i++; + x += DrawChar('.', useFont, i, x, y); + i++; + } + } + else + { + // draw the text + for ( int i = startIndex; i < m_TextStream.Count(); i++) + { + wchar_t ch = m_TextStream[i]; + if (_hideText) + { + ch = '*'; + } + + // if we've passed a line break go to that + if ( _multiline && m_LineBreaks[lineBreakIndexIndex] == i) + { + // add another line + AddAnotherLine(x, y); + lineBreakIndexIndex++; + } + + bool iscompositionchar = false; + + if ( highlight_composition ) + { + iscompositionchar = ( i >= nCompStart && i < nCompEnd ) ? true : false; + if ( iscompositionchar ) + { + // Set the underline color to the text color + surface()->DrawSetColor( col ); + + int w = getCharWidth( useFont, ch ); + + if ( invertcomposition ) + { + // Invert color + surface()->DrawSetTextColor( saveBgColor ); + surface()->DrawFilledRect(x, 0, x+w, tall); + // Set the underline color to the text color + surface()->DrawSetColor( saveBgColor ); + } + + surface()->DrawFilledRect( x, tall - 2, x + w, tall - 1 ); + } + } + + // draw the character and update xposition + x += DrawChar(ch, useFont, i, x, y); + + // Restore color + surface()->DrawSetTextColor(col); + } + } + + // custom border + //!! need to replace this with scheme stuff (TextEntryBorder/TextEntrySelectedBorder) + surface()->DrawSetColor(50, 50, 50, 255); + + if (IsEnabled() && IsEditable() && HasFocus()) + { + // set a more distinct border color + surface()->DrawSetColor(0, 0, 0, 255); + + DrawCursor (x, y); + + if ( composing ) + { + LocalToScreen( x, y ); + input()->SetCandidateWindowPos( x, y ); + } + } + + int newEnd = m_TextStream.Count(); + int remove = newEnd - oldEnd; + if ( remove > 0 ) + { + m_TextStream.RemoveMultiple( oldCursorPos, remove ); + } + _cursorPos = oldCursorPos; + + if ( HasFocus() && m_bAllowNonAsciiCharacters && langlen > 0 ) + { + wide += m_nLangInset; + + if ( m_bDrawLanguageIDAtLeft ) + { + x = 0; + } + else + { + // Draw language identififer + x = wide - m_nLangInset; + } + + surface()->DrawSetColor( col ); + + surface()->DrawFilledRect( x, 2, x + m_nLangInset-2, tall - 2 ); + + saveBgColor[ 3 ] = 255; + surface()->DrawSetTextColor( saveBgColor ); + + x += 1; + + surface()->DrawSetTextFont(_smallfont); + for ( int i = 0; i < langlen; ++i ) + { + x += DrawChar( shortcode[ i ], _smallfont, i, x, remembery ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when data changes or panel size changes +//----------------------------------------------------------------------------- +void TextEntry::PerformLayout() +{ + BaseClass::PerformLayout(); + + RecalculateLineBreaks(); + + // recalculate scrollbar position + if (_verticalScrollbar) + { + LayoutVerticalScrollBarSlider(); + } + + // force a Repaint + Repaint(); +} + +// moves x,y to the Start of the next line of text +void TextEntry::AddAnotherLine(int &cx, int &cy) +{ + cx = DRAW_OFFSET_X + _pixelsIndent; + cy += (surface()->GetFontTall(_font) + DRAW_OFFSET_Y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Recalculates line breaks +//----------------------------------------------------------------------------- +void TextEntry::RecalculateLineBreaks() +{ + if (!_multiline || _hideText) + return; + + if (m_TextStream.Count() < 1) + return; + + HFont font = _font; + + // line break to our width -2 pixel to keep cursor blinking in window + // (assumes borders are 1 pixel) + int wide = GetWide()-2; + + // subtract the scrollbar width + if (_vertScrollBar) + { + wide -= _vertScrollBar->GetWide(); + } + + int charWidth; + int x = DRAW_OFFSET_X, y = DRAW_OFFSET_Y; + + int wordStartIndex = 0; + int wordLength = 0; + bool hasWord = false; + bool justStartedNewLine = true; + bool wordStartedOnNewLine = true; + + int startChar; + if (_recalculateBreaksIndex <= 0) + { + m_LineBreaks.RemoveAll(); + startChar=0; + } + else + { + // remove the rest of the linebreaks list since its out of date. + for (int i=_recalculateBreaksIndex+1; i < m_LineBreaks.Count(); ++i) + { + m_LineBreaks.Remove((int)i); + --i; // removing shrinks the list! + } + startChar = m_LineBreaks[_recalculateBreaksIndex]; + } + + // handle the case where this char is a new line, in that case + // we have already taken its break index into account above so skip it. + if (m_TextStream[startChar] == '\r' || m_TextStream[startChar] == '\n') + { + startChar++; + } + + // loop through all the characters + int i; + for (i = startChar; i < m_TextStream.Count(); ++i) + { + wchar_t ch = m_TextStream[i]; + + // line break only on whitespace characters + if (!iswspace(ch)) + { + if (hasWord) + { + // append to the current word + } + else + { + // Start a new word + wordStartIndex = i; + hasWord = true; + wordStartedOnNewLine = justStartedNewLine; + wordLength = 0; + } + } + else + { + // whitespace/punctuation character + // end the word + hasWord = false; + } + + // get the width + charWidth = getCharWidth(font, ch); + if (!iswcntrl(ch)) + { + justStartedNewLine = false; + } + + // check to see if the word is past the end of the line [wordStartIndex, i) + if ((x + charWidth) >= wide || ch == '\r' || ch == '\n') + { + // add another line + AddAnotherLine(x,y); + + justStartedNewLine = true; + hasWord = false; + + if (ch == '\r' || ch == '\n') + { + // set the break at the current character + m_LineBreaks.AddToTail(i); + } + else if (wordStartedOnNewLine) + { + // word is longer than a line, so set the break at the current cursor + m_LineBreaks.AddToTail(i); + } + else + { + // set it at the last word Start + m_LineBreaks.AddToTail(wordStartIndex); + + // just back to reparse the next line of text + i = wordStartIndex; + } + + // reset word length + wordLength = 0; + } + + // add to the size + x += charWidth; + wordLength += charWidth; + } + + _charCount = i-1; + + // end the list + m_LineBreaks.AddToTail(BUFFER_SIZE); + + // set up the scrollbar + LayoutVerticalScrollBarSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate where the vertical scroll bar slider should be +// based on the current cursor line we are on. +//----------------------------------------------------------------------------- +void TextEntry::LayoutVerticalScrollBarSlider() +{ + // set up the scrollbar + if (_vertScrollBar) + { + int wide, tall; + GetSize (wide, tall); + + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + // with a scroll bar we take off the inset + wide -= iright; + + _vertScrollBar->SetPos(wide - _vertScrollBar->GetWide(), 0); + // scrollbar is inside the borders. + _vertScrollBar->SetSize(_vertScrollBar->GetWide(), tall - ibottom - itop); + + // calculate how many lines we can fully display + int displayLines = tall / (surface()->GetFontTall(_font) + DRAW_OFFSET_Y); + int numLines = m_LineBreaks.Count(); + + if (numLines <= displayLines) + { + // disable the scrollbar + _vertScrollBar->SetEnabled(false); + _vertScrollBar->SetRange(0, numLines); + _vertScrollBar->SetRangeWindow(numLines); + _vertScrollBar->SetValue(0); + } + else + { + // set the scrollbars range + _vertScrollBar->SetRange(0, numLines); + _vertScrollBar->SetRangeWindow(displayLines); + + _vertScrollBar->SetEnabled(true); + + // this should make it scroll one line at a time + _vertScrollBar->SetButtonPressedScrollValue(1); + + // set the value to view the last entries + int val = _vertScrollBar->GetValue(); + int maxval = _vertScrollBar->GetValue() + displayLines; + if (GetCursorLine() < val ) + { + while (GetCursorLine() < val) + { + val--; + } + } + else if (GetCursorLine() >= maxval) + { + while (GetCursorLine() >= maxval) + { + maxval++; + } + maxval -= displayLines; + val = maxval; + } + else + { + //val = GetCursorLine(); + } + + _vertScrollBar->SetValue(val); + _vertScrollBar->InvalidateLayout(); + _vertScrollBar->Repaint(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Set boolean value of baseclass variables. +//----------------------------------------------------------------------------- +void TextEntry::SetEnabled(bool state) +{ + BaseClass::SetEnabled(state); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets whether text wraps around multiple lines or not +// Input : state - true or false +//----------------------------------------------------------------------------- +void TextEntry::SetMultiline(bool state) +{ + _multiline = state; +} + +bool TextEntry::IsMultiline() +{ + return _multiline; +} + +//----------------------------------------------------------------------------- +// Purpose: sets whether or not the edit catches and stores ENTER key presses +//----------------------------------------------------------------------------- +void TextEntry::SetCatchEnterKey(bool state) +{ + _catchEnterKey = state; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets whether a vertical scrollbar is visible +// Input : state - true or false +//----------------------------------------------------------------------------- +void TextEntry::SetVerticalScrollbar(bool state) +{ + _verticalScrollbar = state; + + if (_verticalScrollbar) + { + if (!_vertScrollBar) + { + _vertScrollBar = new ScrollBar(this, "ScrollBar", true); + _vertScrollBar->AddActionSignalTarget(this); + } + + _vertScrollBar->SetVisible(true); + } + else if (_vertScrollBar) + { + _vertScrollBar->SetVisible(false); + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets _editable flag +// Input : state - true or false +//----------------------------------------------------------------------------- +void TextEntry::SetEditable(bool state) +{ + if ( state ) + { + SetDropEnabled( true, 1.0f ); + } + else + { + SetDropEnabled( false ); + } + _editable = state; +} + +const wchar_t *UnlocalizeUnicode( wchar_t *unicode ) +{ + if ( !unicode ) + return L""; + + if ( *unicode == L'#' ) + { + char lookup[ 512 ]; + g_pVGuiLocalize->ConvertUnicodeToANSI( unicode + 1, lookup, sizeof( lookup ) ); + return g_pVGuiLocalize->Find( lookup ); + } + return unicode; +} + +Menu * TextEntry::GetEditMenu() +{ + return m_pEditMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Create cut/copy/paste dropdown menu +//----------------------------------------------------------------------------- +void TextEntry::CreateEditMenu() +{ + // create a drop down cut/copy/paste menu appropriate for this object's states + if (m_pEditMenu) + delete m_pEditMenu; + m_pEditMenu = new Menu(this, "EditMenu"); + + m_pEditMenu->SetFont( _font ); + + // add cut/copy/paste drop down options if its editable, just copy if it is not + if (_editable && !_hideText) + { + m_pEditMenu->AddMenuItem("#TextEntry_Cut", new KeyValues("DoCutSelected"), this); + } + + if ( !_hideText ) + { + m_pEditMenu->AddMenuItem("#TextEntry_Copy", new KeyValues("DoCopySelected"), this); + } + + if (_editable) + { + m_pEditMenu->AddMenuItem("#TextEntry_Paste", new KeyValues("DoPaste"), this); + } + + + if ( m_bAllowNonAsciiCharacters ) + { + IInput::LanguageItem *langs = NULL; + + int count = input()->GetIMELanguageList( NULL, 0 ); + if ( count > 0 ) + { + langs = new IInput::LanguageItem[ count ]; + input()->GetIMELanguageList( langs, count ); + + // Create a submenu + Menu *subMenu = new Menu( this, "LanguageMenu" ); + + subMenu->SetFont( _font ); + + for ( int i = 0; i < count; ++i ) + { + int id = subMenu->AddCheckableMenuItem( "Language", UnlocalizeUnicode( langs[ i ].menuname ), new KeyValues( "DoLanguageChanged", "handle", langs[ i ].handleValue ), this ); + if ( langs[ i ].active ) + { + subMenu->SetMenuItemChecked( id, true ); + } + } + + m_pEditMenu->AddCascadingMenuItem( "Language", "#TextEntry_Language", "", this, subMenu ); + + delete[] langs; + } + + IInput::ConversionModeItem *modes = NULL; + + count = input()->GetIMEConversionModes( NULL, 0 ); + // if count == 0 then native mode is the only mode... + if ( count > 0 ) + { + modes = new IInput::ConversionModeItem[ count ]; + input()->GetIMEConversionModes( modes, count ); + + // Create a submenu + Menu *subMenu = new Menu( this, "ConversionModeMenu" ); + + subMenu->SetFont( _font ); + + for ( int i = 0; i < count; ++i ) + { + int id = subMenu->AddCheckableMenuItem( "ConversionMode", UnlocalizeUnicode( modes[ i ].menuname ), new KeyValues( "DoConversionModeChanged", "handle", modes[ i ].handleValue ), this ); + if ( modes[ i ].active ) + { + subMenu->SetMenuItemChecked( id, true ); + } + } + + m_pEditMenu->AddCascadingMenuItem( "ConversionMode", "#TextEntry_ConversionMode", "", this, subMenu ); + + delete[] modes; + } + + IInput::SentenceModeItem *sentencemodes = NULL; + + count = input()->GetIMESentenceModes( NULL, 0 ); + // if count == 0 then native mode is the only mode... + if ( count > 0 ) + { + sentencemodes = new IInput::SentenceModeItem[ count ]; + input()->GetIMESentenceModes( sentencemodes, count ); + + // Create a submenu + Menu *subMenu = new Menu( this, "SentenceModeMenu" ); + + subMenu->SetFont( _font ); + + for ( int i = 0; i < count; ++i ) + { + int id = subMenu->AddCheckableMenuItem( "SentenceMode", UnlocalizeUnicode( sentencemodes[ i ].menuname ), new KeyValues( "DoConversionModeChanged", "handle", modes[ i ].handleValue ), this ); + if ( modes[ i ].active ) + { + subMenu->SetMenuItemChecked( id, true ); + } + } + + m_pEditMenu->AddCascadingMenuItem( "SentenceMode", "#TextEntry_SentenceMode", "", this, subMenu ); + + delete[] sentencemodes; + } + } + + + m_pEditMenu->SetVisible(false); + m_pEditMenu->SetParent(this); + m_pEditMenu->AddActionSignalTarget(this); +} + +//----------------------------------------------------------------------------- +// Purpsoe: Returns state of _editable flag +//----------------------------------------------------------------------------- +bool TextEntry::IsEditable() +{ + return _editable && IsEnabled(); +} + +//----------------------------------------------------------------------------- +// Purpose: We want single line windows to scroll horizontally and select text +// in response to clicking and holding outside window +//----------------------------------------------------------------------------- +void TextEntry::OnMouseFocusTicked() +{ + // if a button is down move the scrollbar slider the appropriate direction + if (_mouseDragSelection) // text is being selected via mouse clicking and dragging + { + OnCursorMoved(0,0); // we want the text to scroll as if we were dragging + } +} + +//----------------------------------------------------------------------------- +// Purpose: If a cursor enters the window, we are not elegible for +// MouseFocusTicked events +//----------------------------------------------------------------------------- +void TextEntry::OnCursorEntered() +{ + _mouseDragSelection = false; // outside of window dont recieve drag scrolling ticks +} + +//----------------------------------------------------------------------------- +// Purpose: When the cursor is outside the window, if we are holding the mouse +// button down, then we want the window to scroll the text one char at a time +// using Ticks +//----------------------------------------------------------------------------- +void TextEntry::OnCursorExited() // outside of window recieve drag scrolling ticks +{ + if (_mouseSelection) + _mouseDragSelection = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle selection of text by mouse +//----------------------------------------------------------------------------- +void TextEntry::OnCursorMoved(int ignX, int ignY) +{ + if (_mouseSelection) + { + // update the cursor position + int x, y; + input()->GetCursorPos(x, y); + ScreenToLocal(x, y); + _cursorPos = PixelToCursorSpace(x, y); + + // if we are at Start of buffer don't put cursor at end, this will keep + // window from scrolling up to a blank line + if (_cursorPos == 0) + _putCursorAtEnd = false; + + // scroll if we went off left side + if (_cursorPos == _currentStartIndex) + { + if (_cursorPos > 0) + _cursorPos--; + + ScrollLeft(); + _cursorPos = _currentStartIndex; + } + if ( _cursorPos != _select[1]) + { + _select[1] = _cursorPos; + Repaint(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle Mouse button down events. +//----------------------------------------------------------------------------- +void TextEntry::OnMousePressed(MouseCode code) +{ + if (code == MOUSE_LEFT) + { + bool keepChecking = SelectCheck( true ); + if ( !keepChecking ) + { + BaseClass::OnMousePressed( code ); + return; + } + + // move the cursor to where the mouse was pressed + int x, y; + input()->GetCursorPos(x, y); + ScreenToLocal(x, y); + + _cursorIsAtEnd = _putCursorAtEnd; // save this off before calling PixelToCursorSpace() + _cursorPos = PixelToCursorSpace(x, y); + // if we are at Start of buffer don't put cursor at end, this will keep + // window from scrolling up to a blank line + if (_cursorPos == 0) + _putCursorAtEnd = false; + + // enter selection mode + input()->SetMouseCapture(GetVPanel()); + _mouseSelection = true; + + if (_select[0] < 0) + { + // if no initial selection position, Start selection position at cursor + _select[0] = _cursorPos; + } + _select[1] = _cursorPos; + + ResetCursorBlink(); + RequestFocus(); + Repaint(); + } + else if (code == MOUSE_RIGHT) // check for context menu open + { + CreateEditMenu(); + Assert(m_pEditMenu); + + OpenEditMenu(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse button up events +//----------------------------------------------------------------------------- +void TextEntry::OnMouseReleased(MouseCode code) +{ + _mouseSelection = false; + + input()->SetMouseCapture(NULL); + + // make sure something has been selected + int cx0, cx1; + if (GetSelectedRange(cx0, cx1)) + { + if (cx1 - cx0 == 0) + { + // nullify selection + _select[0] = -1; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : code - +//----------------------------------------------------------------------------- +void TextEntry::OnMouseTriplePressed( MouseCode code ) +{ + BaseClass::OnMouseTriplePressed( code ); + + // left triple clicking on a word selects all + if (code == MOUSE_LEFT) + { + GotoTextEnd(); + + SelectAllText( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle mouse double clicks +//----------------------------------------------------------------------------- +void TextEntry::OnMouseDoublePressed(MouseCode code) +{ + // left double clicking on a word selects the word + if (code == MOUSE_LEFT) + { + // move the cursor just as if you single clicked. + OnMousePressed(code); + // then find the start and end of the word we are in to highlight it. + int selectSpot[2]; + GotoWordLeft(); + selectSpot[0] = _cursorPos; + GotoWordRight(); + selectSpot[1] = _cursorPos; + + if (_cursorPos > 0) + { + if (iswspace(m_TextStream[_cursorPos - 1])) + { + selectSpot[1]--; + _cursorPos--; + } + + _select[0] = selectSpot[0]; + _select[1] = selectSpot[1]; + _mouseSelection = true; + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Turn off text selection code when mouse button is not down +//----------------------------------------------------------------------------- +void TextEntry::OnMouseCaptureLost() +{ + _mouseSelection = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Only pass some keys upwards +// everything else we don't relay to the parent +//----------------------------------------------------------------------------- +void TextEntry::OnKeyCodePressed(KeyCode code) +{ + // Pass enter on only if _catchEnterKey isn't set + if ( code == KEY_ENTER ) + { + if ( !_catchEnterKey ) + { + Panel::OnKeyCodePressed( code ); + return; + } + } + + // Forward on just a few key codes, everything else can be handled by TextEntry itself + switch ( code ) + { + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + case KEY_ESCAPE: + case KEY_APP: + Panel::OnKeyCodePressed( code ); + return; + } + + // Pass on the joystick and mouse codes + if ( IsMouseCode(code) || IsNovintButtonCode(code) || IsJoystickCode(code) || IsJoystickButtonCode(code) || + IsJoystickPOVCode(code) || IsJoystickAxisCode(code) ) + { + Panel::OnKeyCodePressed( code ); + return; + } + +} + + +//----------------------------------------------------------------------------- +// Purpose: Masks which keys get chained up +// Maps keyboard input to text window functions. +//----------------------------------------------------------------------------- +void TextEntry::OnKeyCodeTyped(KeyCode code) +{ + _cursorIsAtEnd = _putCursorAtEnd; + _putCursorAtEnd = false; + + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + bool winkey = (input()->IsKeyDown(KEY_LWIN) || input()->IsKeyDown(KEY_RWIN)); + bool fallThrough = false; + + if ( ( ctrl || ( winkey && IsOSX() ) ) && !alt) + { + switch(code) + { + case KEY_A: + SelectAllText(false); + // move the cursor to the end + _cursorPos = _select[1]; + break; + + case KEY_INSERT: + case KEY_C: + { + CopySelected(); + break; + } + case KEY_V: + { + DeleteSelected(); + Paste(); + break; + } + case KEY_X: + { + CopySelected(); + DeleteSelected(); + break; + } + case KEY_Z: + { + Undo(); + break; + } + case KEY_RIGHT: + { + GotoWordRight(); + break; + } + case KEY_LEFT: + { + GotoWordLeft(); + break; + } + case KEY_ENTER: + { + // insert a newline + if (_multiline) + { + DeleteSelected(); + SaveUndoState(); + InsertChar('\n'); + } + // fire newlines back to the main target if asked to + if(_sendNewLines) + { + PostActionSignal(new KeyValues("TextNewLine")); + } + break; + } + case KEY_HOME: + { + GotoTextStart(); + break; + } + case KEY_END: + { + GotoTextEnd(); + break; + } + case KEY_PAGEUP: + { + OnChangeIME( true ); + } + break; + case KEY_PAGEDOWN: + { + OnChangeIME( false ); + } + break; + case KEY_UP: + case KEY_DOWN: + if ( m_bAllowNonAsciiCharacters ) + { + FlipToLastIME(); + } + else + { + fallThrough = true; + } + break; + default: + { + fallThrough = true; + break; + } + } + } + else if (alt) + { + // do nothing with ALT-x keys + if ( !m_bAllowNonAsciiCharacters || ( code != KEY_BACKQUOTE ) ) + { + fallThrough = true; + } + } + else + { + switch(code) + { + case KEY_TAB: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_ESCAPE: + { + fallThrough = true; + break; + } + case KEY_INSERT: + { + if (shift) + { + DeleteSelected(); + Paste(); + } + else + { + fallThrough = true; + } + + break; + } + case KEY_DELETE: + { + if (shift) + { + // shift-delete is cut + CopySelected(); + DeleteSelected(); + } + else + { + Delete(); + } + break; + } + case KEY_LEFT: + { + GotoLeft(); + break; + } + case KEY_RIGHT: + { + GotoRight(); + break; + } + case KEY_UP: + { + if (_multiline) + { + GotoUp(); + } + else + { + fallThrough = true; + } + break; + } + case KEY_DOWN: + { + if (_multiline) + { + GotoDown(); + } + else + { + fallThrough = true; + } + break; + } + case KEY_HOME: + { + if (_multiline) + { + GotoFirstOfLine(); + } + else + { + GotoTextStart(); + } + break; + } + case KEY_END: + { + GotoEndOfLine(); + break; + } + case KEY_BACKSPACE: + { + int x0, x1; + if (GetSelectedRange(x0, x1)) + { + // act just like delete if there is a selection + DeleteSelected(); + } + else + { + Backspace(); + } + break; + } + case KEY_ENTER: + { + // insert a newline + if (_multiline && _catchEnterKey) + { + DeleteSelected(); + SaveUndoState(); + InsertChar('\n'); + } + else + { + fallThrough = true; + } + // fire newlines back to the main target if asked to + if(_sendNewLines) + { + PostActionSignal(new KeyValues("TextNewLine")); + } + break; + } + case KEY_PAGEUP: + { + int val = 0; + fallThrough = (!_multiline) && (!_vertScrollBar); + if (_vertScrollBar) + { + val = _vertScrollBar->GetValue(); + } + + // if there is a scroll bar scroll down one rangewindow + if (_multiline) + { + int displayLines = GetTall() / (surface()->GetFontTall(_font) + DRAW_OFFSET_Y); + // move the cursor down + for (int i=0; i < displayLines; i++) + { + GotoUp(); + } + } + + // if there is a scroll bar scroll down one rangewindow + if (_vertScrollBar) + { + int window = _vertScrollBar->GetRangeWindow(); + int newval = _vertScrollBar->GetValue(); + int linesToMove = window - (val - newval); + _vertScrollBar->SetValue(val - linesToMove - 1); + } + break; + + } + case KEY_PAGEDOWN: + { + int val = 0; + fallThrough = (!_multiline) && (!_vertScrollBar); + if (_vertScrollBar) + { + val = _vertScrollBar->GetValue(); + } + + if (_multiline) + { + int displayLines = GetTall() / (surface()->GetFontTall(_font) + DRAW_OFFSET_Y); + // move the cursor down + for (int i=0; i < displayLines; i++) + { + GotoDown(); + } + } + + // if there is a scroll bar scroll down one rangewindow + if (_vertScrollBar) + { + int window = _vertScrollBar->GetRangeWindow(); + int newval = _vertScrollBar->GetValue(); + int linesToMove = window - (newval - val); + _vertScrollBar->SetValue(val + linesToMove + 1); + } + break; + } + + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + { + fallThrough = true; + break; + } + + default: + { + // return if any other char is pressed. + // as it will be a unicode char. + // and we don't want select[1] changed unless a char was pressed that this fxn handles + return; + } + } + } + + // select[1] is the location in the line where the blinking cursor started + _select[1] = _cursorPos; + + if (_dataChanged) + { + FireActionSignal(); + } + + // chain back on some keys + if (fallThrough) + { + _putCursorAtEnd=_cursorIsAtEnd; // keep state of cursor on fallthroughs + BaseClass::OnKeyCodeTyped(code); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Masks which keys get chained up +// Maps keyboard input to text window functions. +//----------------------------------------------------------------------------- +void TextEntry::OnKeyTyped(wchar_t unichar) +{ + _cursorIsAtEnd = _putCursorAtEnd; + _putCursorAtEnd=false; + + bool fallThrough = false; + + // KeyCodes handle all non printable chars + if (iswcntrl(unichar) || unichar == 9 ) // tab key (code 9) is printable but handled elsewhere + return; + + // do readonly keys + if (!IsEditable()) + { + BaseClass::OnKeyTyped(unichar); + return; + } + + if (unichar != 0) + { + DeleteSelected(); + SaveUndoState(); + InsertChar(unichar); + } + + // select[1] is the location in the line where the blinking cursor started + _select[1] = _cursorPos; + + if (_dataChanged) + { + FireActionSignal(); + } + + // chain back on some keys + if (fallThrough) + { + _putCursorAtEnd=_cursorIsAtEnd; // keep state of cursor on fallthroughs + BaseClass::OnKeyTyped(unichar); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list according to the mouse wheel movement +//----------------------------------------------------------------------------- +void TextEntry::OnMouseWheeled(int delta) +{ + if (_vertScrollBar) + { + MoveScrollBar(delta); + } + else + { + // if we don't use the input, chain back + BaseClass::OnMouseWheeled(delta); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list +// Input : delta - amount to move scrollbar up +//----------------------------------------------------------------------------- +void TextEntry::MoveScrollBar(int delta) +{ + if (_vertScrollBar) + { + int val = _vertScrollBar->GetValue(); + val -= (delta * 3); + _vertScrollBar->SetValue(val); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame the entry has keyboard focus; +// blinks the text cursor +//----------------------------------------------------------------------------- +void TextEntry::OnKeyFocusTicked() +{ + int time=system()->GetTimeMillis(); + if(time>_cursorNextBlinkTime) + { + _cursorBlink=!_cursorBlink; + _cursorNextBlinkTime=time+_cursorBlinkRate; + Repaint(); + } +} + +Panel *TextEntry::GetDragPanel() +{ + if ( input()->IsMouseDown( MOUSE_LEFT ) ) + { + int x, y; + input()->GetCursorPos(x, y); + ScreenToLocal(x, y); + int cursor = PixelToCursorSpace(x, y); + + int cx0, cx1; + bool check = GetSelectedRange( cx0, cx1 ); + + if ( check && cursor >= cx0 && cursor < cx1 ) + { + // Don't deselect in this case!!! + return BaseClass::GetDragPanel(); + } + return NULL; + } + + return BaseClass::GetDragPanel(); +} + +void TextEntry::OnCreateDragData( KeyValues *msg ) +{ + BaseClass::OnCreateDragData( msg ); + + char txt[ 256 ]; + GetText( txt, sizeof( txt ) ); + + int r0, r1; + if ( GetSelectedRange( r0, r1 ) && r0 != r1 ) + { + int len = r1 - r0; + if ( len > 0 && r0 < 1024 ) + { + char selection[ 512 ]; + Q_strncpy( selection, &txt[ r0 ], len + 1 ); + selection[ len ] = 0; + msg->SetString( "text", selection ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check if we are selecting text (so we can highlight it) +//----------------------------------------------------------------------------- +bool TextEntry::SelectCheck( bool fromMouse /*=false*/ ) +{ + bool bret = true; + if (!HasFocus() || !(input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT))) + { + bool deselect = true; + int cx0, cx1; + if ( fromMouse && + GetDragPanel() != NULL ) + { + // move the cursor to where the mouse was pressed + int x, y; + input()->GetCursorPos(x, y); + ScreenToLocal(x, y); + int cursor = PixelToCursorSpace(x, y); + + bool check = GetSelectedRange( cx0, cx1 ); + + if ( check && cursor >= cx0 && cursor < cx1 ) + { + // Don't deselect in this case!!! + deselect = false; + bret = false; + } + } + + if ( deselect ) + { + _select[0] = -1; + } + } + else if (_select[0] == -1) + { + _select[0] = _cursorPos; + } + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: set the maximum number of chars in the text buffer +//----------------------------------------------------------------------------- +void TextEntry::SetMaximumCharCount(int maxChars) +{ + _maxCharCount = maxChars; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int TextEntry::GetMaximumCharCount() +{ + return _maxCharCount; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void TextEntry::SetAutoProgressOnHittingCharLimit(bool state) +{ + m_bAutoProgressOnHittingCharLimit = state; +} + +//----------------------------------------------------------------------------- +// Purpose: set whether to wrap the text buffer +//----------------------------------------------------------------------------- +void TextEntry::SetWrap(bool wrap) +{ + _wrap = wrap; +} + +//----------------------------------------------------------------------------- +// Purpose: set whether to pass newline msgs to parent +//----------------------------------------------------------------------------- +void TextEntry::SendNewLine(bool send) +{ + _sendNewLines = send; +} + +//----------------------------------------------------------------------------- +// Purpose: Tell if an index is a linebreakindex +//----------------------------------------------------------------------------- +bool TextEntry::IsLineBreak(int index) +{ + for (int i=0; i<m_LineBreaks.Count(); ++i) + { + if (index == m_LineBreaks[i]) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor one character to the left, scroll the text +// horizontally if needed +//----------------------------------------------------------------------------- +void TextEntry::GotoLeft() +{ + SelectCheck(); + + // if we are on a line break just move the cursor to the prev line + if (IsLineBreak(_cursorPos)) + { + // if we're already on the prev line at the end dont put it on the end + if (!_cursorIsAtEnd) + _putCursorAtEnd = true; + } + // if we are not at Start decrement cursor + if (!_putCursorAtEnd && _cursorPos > 0) + { + _cursorPos--; + } + + ScrollLeft(); + + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor one character to the right, scroll the text +// horizontally if needed +//----------------------------------------------------------------------------- +void TextEntry::GotoRight() +{ + SelectCheck(); + + // if we are on a line break just move the cursor to the next line + if (IsLineBreak(_cursorPos)) + { + if (_cursorIsAtEnd) + { + _putCursorAtEnd = false; + } + else + { + // if we are not at end increment cursor + if (_cursorPos < m_TextStream.Count()) + { + _cursorPos++; + } + } + } + else + { + // if we are not at end increment cursor + if (_cursorPos < m_TextStream.Count()) + { + _cursorPos++; + } + + // if we are on a line break move the cursor to end of line + if (IsLineBreak(_cursorPos)) + { + if (!_cursorIsAtEnd) + _putCursorAtEnd = true; + } + } + // scroll right if we need to + ScrollRight(); + + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Find out what line the cursor is on +//----------------------------------------------------------------------------- +int TextEntry::GetCursorLine() +{ + // find which line the cursor is on + int cursorLine; + for (cursorLine = 0; cursorLine < m_LineBreaks.Count(); cursorLine++) + { + if (_cursorPos < m_LineBreaks[cursorLine]) + break; + } + + if (_putCursorAtEnd) // correct for when cursor is at end of line rather than Start of next + { + // we are not at end of buffer, in which case there is no next line to be at the Start of + if (_cursorPos != m_TextStream.Count() ) + cursorLine--; + } + + return cursorLine; +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor one line up +//----------------------------------------------------------------------------- +void TextEntry::GotoUp() +{ + SelectCheck(); + + if (_cursorIsAtEnd) + { + if ( (GetCursorLine() - 1 ) == 0) // we are on first line + { + // stay at end of line + _putCursorAtEnd = true; + return; // dont move the cursor + } + else + _cursorPos--; + } + + int cx, cy; + CursorToPixelSpace(_cursorPos, cx, cy); + + // move the cursor to the previous line + MoveCursor(GetCursorLine() - 1, cx); +} + + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor one line down +//----------------------------------------------------------------------------- +void TextEntry::GotoDown() +{ + SelectCheck(); + + if (_cursorIsAtEnd) + { + _cursorPos--; + if (_cursorPos < 0) + _cursorPos = 0; + } + + int cx, cy; + CursorToPixelSpace(_cursorPos, cx, cy); + + // move the cursor to the next line + MoveCursor(GetCursorLine() + 1, cx); + if (!_putCursorAtEnd && _cursorIsAtEnd ) + { + _cursorPos++; + if (_cursorPos > m_TextStream.Count()) + { + _cursorPos = m_TextStream.Count(); + } + } + LayoutVerticalScrollBarSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the starting ypixel positon for a walk through the window +//----------------------------------------------------------------------------- +int TextEntry::GetYStart() +{ + if (_multiline) + { + // just Start from the top + return DRAW_OFFSET_Y; + } + + int fontTall = surface()->GetFontTall(_font); + return (GetTall() / 2) - (fontTall / 2); +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor to a line, need to know how many pixels are in a line +//----------------------------------------------------------------------------- +void TextEntry::MoveCursor(int line, int pixelsAcross) +{ + // clamp to a valid line + if (line < 0) + line = 0; + if (line >= m_LineBreaks.Count()) + line = m_LineBreaks.Count() -1; + + // walk the whole text set looking for our place + // work out where to Start checking + + int yStart = GetYStart(); + + int x = DRAW_OFFSET_X, y = yStart; + int lineBreakIndexIndex = 0; + _pixelsIndent = 0; + int i; + for ( i = 0; i < m_TextStream.Count(); i++) + { + wchar_t ch = m_TextStream[i]; + + if (_hideText) + { + ch = '*'; + } + + // if we've passed a line break go to that + if (m_LineBreaks[lineBreakIndexIndex] == i) + { + if (lineBreakIndexIndex == line) + { + _putCursorAtEnd = true; + _cursorPos = i; + break; + } + + // add another line + AddAnotherLine(x,y); + lineBreakIndexIndex++; + + } + + // add to the current position + int charWidth = getCharWidth(_font, ch); + + if (line == lineBreakIndexIndex) + { + // check to see if we're in range + if ((x + (charWidth / 2)) > pixelsAcross) + { + // found position + _cursorPos = i; + break; + } + } + + x += charWidth; + } + + // if we never find the cursor it must be past the end + // of the text buffer, to let's just slap it on the end of the text buffer then. + if (i == m_TextStream.Count()) + { + GotoTextEnd(); + } + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Turn horizontal scrolling on or off. +// Horizontal scrolling is disabled in multline windows. +// Toggling this will disable it in single line windows as well. +//----------------------------------------------------------------------------- +void TextEntry::SetHorizontalScrolling(bool status) +{ + _horizScrollingAllowed = status; +} + +//----------------------------------------------------------------------------- +// Purpose: Horizontal scrolling function, not used in multiline windows +// Function will scroll the buffer to the left if the cursor is not in the window +// scroll left if we need to +//----------------------------------------------------------------------------- +void TextEntry::ScrollLeft() +{ + if (_multiline) // early out + { + return; + } + + if (!_horizScrollingAllowed) //early out + { + return; + } + + if(_cursorPos < _currentStartIndex) // scroll left if we need to + { + if (_cursorPos < 0)// dont scroll past the Start of buffer + { + _cursorPos=0; + } + _currentStartIndex = _cursorPos; + } + + LayoutVerticalScrollBarSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TextEntry::ScrollLeftForResize() +{ + if (_multiline) // early out + { + return; + } + + if (!_horizScrollingAllowed) //early out + { + return; + } + + while (_currentStartIndex > 0) // go until we hit leftmost + { + _currentStartIndex--; + int nVal = _currentStartIndex; + + // check if the cursor is now off the screen + if (IsCursorOffRightSideOfWindow(_cursorPos)) + { + _currentStartIndex++; // we've gone too far, return it + break; + } + + // IsCursorOffRightSideOfWindow actually fixes the _currentStartIndex, + // so if our value changed that menas we really are off the screen + if (nVal != _currentStartIndex) + break; + } + LayoutVerticalScrollBarSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: Horizontal scrolling function, not used in multiline windows +// Scroll one char right until the cursor is visible in the window. +// We do this one char at a time because char width isn't a constant. +//----------------------------------------------------------------------------- +void TextEntry::ScrollRight() +{ + if (!_horizScrollingAllowed) + return; + + if (_multiline) + { + } + // check if cursor is off the right side of window + else if (IsCursorOffRightSideOfWindow(_cursorPos)) + { + _currentStartIndex++; //scroll over + ScrollRight(); // scroll again, check if cursor is in window yet + } + + LayoutVerticalScrollBarSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: Check and see if cursor position is off the right side of the window +// just compare cursor's pixel coords with the window size coords. +// Input: an integer cursor Position, if you pass _cursorPos fxn will tell you +// if current cursor is outside window. +// Output: true: cursor is outside right edge or window +// false: cursor is inside right edge +//----------------------------------------------------------------------------- +bool TextEntry::IsCursorOffRightSideOfWindow(int cursorPos) +{ + int cx, cy; + CursorToPixelSpace(cursorPos, cx, cy); + int wx=GetWide()-1; //width of inside of window is GetWide()-1 + if ( wx <= 0 ) + return false; + + return (cx >= wx); +} + +//----------------------------------------------------------------------------- +// Purpose: Check and see if cursor position is off the left side of the window +// just compare cursor's pixel coords with the window size coords. +// Input: an integer cursor Position, if you pass _cursorPos fxn will tell you +// if current cursor is outside window. +// Output: true - cursor is outside left edge or window +// false - cursor is inside left edge +//----------------------------------------------------------------------------- +bool TextEntry::IsCursorOffLeftSideOfWindow(int cursorPos) +{ + int cx, cy; + CursorToPixelSpace(cursorPos, cx, cy); + return (cx <= 0); +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor over to the Start of the next word to the right +//----------------------------------------------------------------------------- +void TextEntry::GotoWordRight() +{ + SelectCheck(); + + // search right until we hit a whitespace character or a newline + while (++_cursorPos < m_TextStream.Count()) + { + if (iswspace(m_TextStream[_cursorPos])) + break; + } + + // search right until we hit an nonspace character + while (++_cursorPos < m_TextStream.Count()) + { + if (!iswspace(m_TextStream[_cursorPos])) + break; + } + + if (_cursorPos > m_TextStream.Count()) + _cursorPos = m_TextStream.Count(); + + // now we are at the start of the next word + + // scroll right if we need to + ScrollRight(); + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move the cursor over to the Start of the next word to the left +//----------------------------------------------------------------------------- +void TextEntry::GotoWordLeft() +{ + SelectCheck(); + + if (_cursorPos < 1) + return; + + // search left until we hit an nonspace character + while (--_cursorPos >= 0) + { + if (!iswspace(m_TextStream[_cursorPos])) + break; + } + + // search left until we hit a whitespace character + while (--_cursorPos >= 0) + { + if (iswspace(m_TextStream[_cursorPos])) + { + break; + } + } + + // we end one character off + _cursorPos++; + // now we are at the Start of the previous word + + + // scroll left if we need to + ScrollLeft(); + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move cursor to the Start of the text buffer +//----------------------------------------------------------------------------- +void TextEntry::GotoTextStart() +{ + SelectCheck(); + _cursorPos = 0; // set cursor to Start + _putCursorAtEnd = false; + _currentStartIndex=0; // scroll over to Start + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move cursor to the end of the text buffer +//----------------------------------------------------------------------------- +void TextEntry::GotoTextEnd() +{ + SelectCheck(); + _cursorPos=m_TextStream.Count(); // set cursor to end of buffer + _putCursorAtEnd = true; // move cursor Start of next line + ScrollRight(); // scroll over until cursor is on screen + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Move cursor to the Start of the current line +//----------------------------------------------------------------------------- +void TextEntry::GotoFirstOfLine() +{ + SelectCheck(); + // to get to the Start of the line you have to take into account line wrap + // we have to figure out at which point the line wraps + // given the current cursor position, select[1], find the index that is the + // line Start to the left of the cursor + //_cursorPos = 0; //TODO: this is wrong, should go to first non-whitespace first, then to zero + _cursorPos = GetCurrentLineStart(); + _putCursorAtEnd = false; + + _currentStartIndex=_cursorPos; + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the index of the first char on the current line +//----------------------------------------------------------------------------- +int TextEntry::GetCurrentLineStart() +{ + if (!_multiline) // quick out for non multline buffers + return _currentStartIndex; + + int i; + if (IsLineBreak(_cursorPos)) + { + for (i = 0; i < m_LineBreaks.Count(); ++i ) + { + if (_cursorPos == m_LineBreaks[i]) + break; + } + if (_cursorIsAtEnd) + { + if (i > 0) + { + return m_LineBreaks[i-1]; + } + return m_LineBreaks[0]; + } + else + return _cursorPos; // we are already at Start + } + + for ( i = 0; i < m_LineBreaks.Count(); ++i ) + { + if (_cursorPos < m_LineBreaks[i]) + { + if (i == 0) + return 0; + else + return m_LineBreaks[i-1]; + } + } + // if there were no line breaks, the first char in the line is the Start of the buffer + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Move cursor to the end of the current line +//----------------------------------------------------------------------------- +void TextEntry::GotoEndOfLine() +{ + SelectCheck(); + // to get to the end of the line you have to take into account line wrap in the buffer + // we have to figure out at which point the line wraps + // given the current cursor position, select[1], find the index that is the + // line end to the right of the cursor + //_cursorPos=m_TextStream.Count(); //TODO: this is wrong, should go to last non-whitespace, then to true EOL + _cursorPos = GetCurrentLineEnd(); + _putCursorAtEnd = true; + + ScrollRight(); + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the index of the last char on the current line +//----------------------------------------------------------------------------- +int TextEntry::GetCurrentLineEnd() +{ + int i; + if (IsLineBreak(_cursorPos) ) + { + for ( i = 0; i < m_LineBreaks.Count()-1; ++i ) + { + if (_cursorPos == m_LineBreaks[i]) + break; + } + if (!_cursorIsAtEnd) + { + if (i == m_LineBreaks.Count()-2 ) + m_TextStream.Count(); + else + return m_LineBreaks[i+1]; + } + else + return _cursorPos; // we are already at end + } + + for ( i = 0; i < m_LineBreaks.Count()-1; i++ ) + { + if ( _cursorPos < m_LineBreaks[i]) + { + return m_LineBreaks[i]; + } + } + return m_TextStream.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Insert a character into the text buffer +//----------------------------------------------------------------------------- +void TextEntry::InsertChar(wchar_t ch) +{ + // throw away redundant linefeed characters + if (ch == '\r') + return; + + // no newline characters in single-line dialogs + if (!_multiline && ch == '\n') + return; + + // no tab characters + if (ch == '\t') + return; + + if (m_bAllowNumericInputOnly) + { + if (!iswdigit(ch) && ((char)ch != '.')) + { + surface()->PlaySound("Resource\\warning.wav"); + return; + } + } + + // check against unicode characters + if (!m_bAllowNonAsciiCharacters) + { + if (ch > 127) + return; + } + + // don't add characters if the max char count has been reached + // ding at the user + if (_maxCharCount > -1 && m_TextStream.Count() >= _maxCharCount) + { + if (_maxCharCount>0 && _multiline && _wrap) + { + // if we wrap lines rather than stopping + while (m_TextStream.Count() > _maxCharCount) + { + if (_recalculateBreaksIndex==0) + { + // we can get called before this has been run for the first time :) + RecalculateLineBreaks(); + } + if (m_LineBreaks[0]> m_TextStream.Count()) + { + // if the line break is the past the end of the buffer recalc + _recalculateBreaksIndex=-1; + RecalculateLineBreaks(); + } + + if (m_LineBreaks[0]+1 < m_TextStream.Count()) + { + // delete the line + m_TextStream.RemoveMultiple(0, m_LineBreaks[0]); + + // in case we just deleted text from where the cursor is + if (_cursorPos> m_TextStream.Count()) + { + _cursorPos = m_TextStream.Count(); + } + else + { // shift the cursor up. don't let it wander past zero + _cursorPos-=m_LineBreaks[0]+1; + if (_cursorPos<0) + { + _cursorPos=0; + } + } + + // move any selection area up + if(_select[0]>-1) + { + _select[0] -=m_LineBreaks[0]+1; + + if(_select[0] <=0) + { + _select[0] =-1; + } + + _select[1] -=m_LineBreaks[0]+1; + if(_select[1] <=0) + { + _select[1] =-1; + } + + } + + // now redraw the buffer + for (int i = m_TextStream.Count() - 1; i >= 0; i--) + { + SetCharAt(m_TextStream[i], i+1); + } + + // redo all the line breaks + _recalculateBreaksIndex=-1; + RecalculateLineBreaks(); + + } + } + + } + else + { + // make a sound + // we've hit the max character limit + surface()->PlaySound("Resource\\warning.wav"); + return; + } + } + + + if (_wrap) + { + // when wrapping you always insert the new char at the end of the buffer + SetCharAt(ch, m_TextStream.Count()); + _cursorPos=m_TextStream.Count(); + } + else + { + // move chars right 1 starting from cursor, then replace cursorPos with char and increment cursor + for (int i = m_TextStream.Count()- 1; i >= _cursorPos; i--) + { + SetCharAt(m_TextStream[i], i+1); + } + + SetCharAt(ch, _cursorPos); + _cursorPos++; + } + + // if its a newline char we can't do the slider until we recalc the line breaks + if (ch == '\n') + { + RecalculateLineBreaks(); + } + + // see if we've hit the char limit + if (m_bAutoProgressOnHittingCharLimit && m_TextStream.Count() == _maxCharCount) + { + // move the next panel (most likely another TextEntry) + RequestFocusNext(); + } + + // scroll right if this pushed the cursor off screen + ScrollRight(); + + _dataChanged = true; + + CalcBreakIndex(); + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the lineBreakIndex index of the line before the cursor +// note _recalculateBreaksIndex < 0 flags RecalculateLineBreaks +// to figure it all out from scratch +//----------------------------------------------------------------------------- +void TextEntry::CalcBreakIndex() +{ + // an optimization to handle when the cursor is at the end of the buffer. + // pays off if the buffer is large, and the search loop would be long. + if (_cursorPos == m_TextStream.Count()) + { + // we know m_LineBreaks array always has at least one element in it (99999 sentinel) + // when there is just one line this will make recalc = -1 which is ok. + _recalculateBreaksIndex = m_LineBreaks.Count()-2; + return; + } + + _recalculateBreaksIndex=0; + // find the line break just before the cursor position + while (_cursorPos > m_LineBreaks[_recalculateBreaksIndex]) + ++_recalculateBreaksIndex; + + // -1 is ok. + --_recalculateBreaksIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Insert a string into the text buffer, this is just a series +// of char inserts because we have to check each char is ok to insert +//----------------------------------------------------------------------------- +void TextEntry::InsertString(const wchar_t *wszText) +{ + SaveUndoState(); + + for (const wchar_t *ch = wszText; *ch != 0; ++ch) + { + InsertChar(*ch); + } + + if (_dataChanged) + { + FireActionSignal(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Converts an ansi string to unicode and inserts it into the text stream +//----------------------------------------------------------------------------- +void TextEntry::InsertString(const char *text) +{ + // check for to see if the string is in the localization tables + if (text[0] == '#') + { + wchar_t *wsz = g_pVGuiLocalize->Find(text); + if (wsz) + { + InsertString(wsz); + return; + } + } + + // straight convert the ansi to unicode and insert + wchar_t unicode[1024]; + g_pVGuiLocalize->ConvertANSIToUnicode(text, unicode, sizeof(unicode)); + InsertString(unicode); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle the effect of user hitting backspace key +// we delete the char before the cursor and reformat the text so it +// behaves like in windows. +//----------------------------------------------------------------------------- +void TextEntry::Backspace() +{ + if (!IsEditable()) + return; + + //if you are at the first position don't do anything + if(_cursorPos==0) + { + return; + } + + //if the line is empty, don't do anything + if(m_TextStream.Count()==0) + { + return; + } + + SaveUndoState(); + + //shift chars left one, starting at the cursor position, then make the line one smaller + for(int i=_cursorPos;i<m_TextStream.Count(); ++i) + { + SetCharAt(m_TextStream[i],i-1); + } + m_TextStream.Remove(m_TextStream.Count() - 1); + + // As we hit the Start of the window, expose more chars so we can see what we are deleting + if (_cursorPos==_currentStartIndex) + { + // windows tabs over 6 chars + if (_currentStartIndex-6 >= 0) // dont scroll if there are not enough chars to scroll + { + _currentStartIndex-=6; + } + else + _currentStartIndex=0; + } + + //move the cursor left one + _cursorPos--; + + _dataChanged = true; + + // recalculate linebreaks (the fast incremental linebreak function doesn't work in this case) + _recalculateBreaksIndex = 0; + m_LineBreaks.RemoveAll(); + m_LineBreaks.AddToTail(BUFFER_SIZE); + + LayoutVerticalScrollBarSlider(); + ResetCursorBlink(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Deletes the current selection, if any, moving the cursor to the Start +// of the selection +//----------------------------------------------------------------------------- +void TextEntry::DeleteSelected() +{ + if (!IsEditable()) + return; + + // if the line is empty, don't do anything + if (m_TextStream.Count() == 0) + return; + + // get the range to delete + int x0, x1; + if (!GetSelectedRange(x0, x1)) + { + // no selection, don't touch anything + return; + } + + SaveUndoState(); + + // shift chars left one starting after cursor position, then make the line one smaller + int dif = x1 - x0; + for (int i = 0; i < dif; ++i) + { + m_TextStream.Remove(x0); + } + + // clear any selection + SelectNone(); + ResetCursorBlink(); + + // move the cursor to just after the deleted section + _cursorPos = x0; + + _dataChanged = true; + + _recalculateBreaksIndex = 0; + m_LineBreaks.RemoveAll(); + m_LineBreaks.AddToTail(BUFFER_SIZE); + + CalcBreakIndex(); + + LayoutVerticalScrollBarSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle the effect of the user hitting the delete key +// removes the char in front of the cursor +//----------------------------------------------------------------------------- +void TextEntry::Delete() +{ + if (!IsEditable()) + return; + + // if the line is empty, don't do anything + if (m_TextStream.Count() == 0) + return; + + // get the range to delete + int x0, x1; + if (!GetSelectedRange(x0, x1)) + { + // no selection, so just delete the one character + x0 = _cursorPos; + x1 = x0 + 1; + + // if we're at the end of the line don't do anything + if (_cursorPos >= m_TextStream.Count()) + return; + } + + SaveUndoState(); + + // shift chars left one starting after cursor position, then make the line one smaller + int dif = x1 - x0; + for (int i = 0; i < dif; i++) + { + m_TextStream.Remove((int)x0); + } + + ResetCursorBlink(); + + // clear any selection + SelectNone(); + + // move the cursor to just after the deleted section + _cursorPos = x0; + + _dataChanged = true; + + _recalculateBreaksIndex = 0; + m_LineBreaks.RemoveAll(); + m_LineBreaks.AddToTail(BUFFER_SIZE); + + CalcBreakIndex(); + + LayoutVerticalScrollBarSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: Declare a selection empty +//----------------------------------------------------------------------------- +void TextEntry::SelectNone() +{ + // tag the selection as empty + _select[0] = -1; + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Load in the selection range so cx0 is the Start and cx1 is the end +// from smallest to highest (right to left) +//----------------------------------------------------------------------------- +bool TextEntry::GetSelectedRange(int& cx0,int& cx1) +{ + // if there is nothing selected return false + if (_select[0] == -1) + { + return false; + } + + // sort the two position so cx0 is the smallest + cx0=_select[0]; + cx1=_select[1]; + int temp; + if(cx1<cx0){temp=cx0;cx0=cx1;cx1=temp;} + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Opens the cut/copy/paste dropdown menu +//----------------------------------------------------------------------------- +void TextEntry::OpenEditMenu() +{ + // get cursor position, this is local to this text edit window + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + + /* !! disabled since it recursively gets panel pointers, potentially across dll boundaries, + and doesn't need to be necessary (it's just for handling windowed mode) + + // find the frame that has no parent (the one on the desktop) + Panel *panel = this; + while ( panel->GetParent() != NULL) + { + panel = panel->GetParent(); + } + panel->ScreenToLocal(cursorX, cursorY); + int x, y; + // get base panel's postition + panel->GetPos(x, y); + + // adjust our cursor position accordingly + cursorX += x; + cursorY += y; + */ + + int x0, x1; + if (GetSelectedRange(x0, x1)) // there is something selected + { + m_pEditMenu->SetItemEnabled("&Cut", true); + m_pEditMenu->SetItemEnabled("C&opy", true); + } + else // there is nothing selected, disable cut/copy options + { + m_pEditMenu->SetItemEnabled("&Cut", false); + m_pEditMenu->SetItemEnabled("C&opy", false); + } + m_pEditMenu->SetVisible(true); + m_pEditMenu->RequestFocus(); + + // relayout the menu immediately so that we know it's size + m_pEditMenu->InvalidateLayout(true); + int menuWide, menuTall; + m_pEditMenu->GetSize(menuWide, menuTall); + + // work out where the cursor is and therefore the best place to put the menu + int wide, tall; + surface()->GetScreenSize(wide, tall); + + if (wide - menuWide > cursorX) + { + // menu hanging right + if (tall - menuTall > cursorY) + { + // menu hanging down + m_pEditMenu->SetPos(cursorX, cursorY); + } + else + { + // menu hanging up + m_pEditMenu->SetPos(cursorX, cursorY - menuTall); + } + } + else + { + // menu hanging left + if (tall - menuTall > cursorY) + { + // menu hanging down + m_pEditMenu->SetPos(cursorX - menuWide, cursorY); + } + else + { + // menu hanging up + m_pEditMenu->SetPos(cursorX - menuWide, cursorY - menuTall); + } + } + + m_pEditMenu->RequestFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Cuts the selected chars from the buffer and +// copies them into the clipboard +//----------------------------------------------------------------------------- +void TextEntry::CutSelected() +{ + CopySelected(); + DeleteSelected(); + // have to request focus if we used the menu + RequestFocus(); + + if ( _dataChanged ) + { + FireActionSignal(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Copies the selected chars into the clipboard +//----------------------------------------------------------------------------- +void TextEntry::CopySelected() +{ + if (_hideText) + return; + + int x0, x1; + if (GetSelectedRange(x0, x1)) + { + CUtlVector<wchar_t> buf; + for (int i = x0; i < x1; i++) + { + if ( m_TextStream[i]=='\n') + { + buf.AddToTail( '\r' ); + } + buf.AddToTail(m_TextStream[i]); + } + buf.AddToTail('\0'); + system()->SetClipboardText(buf.Base(), buf.Count()); + } + + // have to request focus if we used the menu + RequestFocus(); + + if ( _dataChanged ) + { + FireActionSignal(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Pastes the selected chars from the clipboard into the text buffer +// truncates if text is longer than our _maxCharCount +//----------------------------------------------------------------------------- +void TextEntry::Paste() +{ + if (!IsEditable()) + return; + + CUtlVector<wchar_t> buf; + int bufferSize = system()->GetClipboardTextCount(); + if (!m_bAutoProgressOnHittingCharLimit) + { + bufferSize = _maxCharCount > 0 ? _maxCharCount + 1 : system()->GetClipboardTextCount(); // +1 for terminator + } + + buf.AddMultipleToTail(bufferSize); + int len = system()->GetClipboardText(0, buf.Base(), bufferSize * sizeof(wchar_t)); + if (len < 1) + return; + + SaveUndoState(); + bool bHaveMovedFocusAwayFromCurrentEntry = false; + + // insert all the characters + for (int i = 0; i < len && buf[i] != 0; i++) + { + if (m_bAutoProgressOnHittingCharLimit) + { + // see if we're about to hit the char limit + if (m_TextStream.Count() == _maxCharCount) + { + // move the next panel (most likely another TextEntry) + RequestFocusNext(); + // copy the remainder into the clipboard + wchar_t *remainingText = &buf[i]; + system()->SetClipboardText(remainingText, len - i - 1); + // set the next entry to paste + if (GetVParent() && ipanel()->GetCurrentKeyFocus(GetVParent()) != GetVPanel()) + { + bHaveMovedFocusAwayFromCurrentEntry = true; + ipanel()->SendMessage(ipanel()->GetCurrentKeyFocus(GetVParent()), new KeyValues("DoPaste"), GetVPanel()); + } + break; + } + } + + // insert the character + InsertChar(buf[i]); + } + + // restore the original clipboard text if neccessary + if (m_bAutoProgressOnHittingCharLimit) + { + system()->SetClipboardText(buf.Base(), bufferSize); + } + + _dataChanged = true; + FireActionSignal(); + + if (!bHaveMovedFocusAwayFromCurrentEntry) + { + // have to request focus if we used the menu + RequestFocus(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Reverts back to last saved changes +//----------------------------------------------------------------------------- +void TextEntry::Undo() +{ + _cursorPos = _undoCursorPos; + m_TextStream.CopyArray(m_UndoTextStream.Base(), m_UndoTextStream.Count()); + + InvalidateLayout(); + Repaint(); + SelectNone(); +} + +//----------------------------------------------------------------------------- +// Purpose: Saves the current state to the undo stack +//----------------------------------------------------------------------------- +void TextEntry::SaveUndoState() +{ + _undoCursorPos = _cursorPos; + m_UndoTextStream.CopyArray(m_TextStream.Base(), m_TextStream.Count()); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the index in the text buffer of the +// character the drawing should Start at +//----------------------------------------------------------------------------- +int TextEntry::GetStartDrawIndex(int &lineBreakIndexIndex) +{ + int startIndex = 0; + + int numLines = m_LineBreaks.Count(); + int startLine = 0; + + // determine the Start point from the scroll bar + // do this only if we are not selecting text in the window with the mouse + if (_vertScrollBar && !_mouseDragSelection) + { + // skip to line indicated by scrollbar + startLine = _vertScrollBar->GetValue(); + } + else + { + // check to see if the cursor is off the screen-multiline case + HFont font = _font; + int displayLines = GetTall() / (surface()->GetFontTall(font) + DRAW_OFFSET_Y); + if (displayLines < 1) + { + displayLines = 1; + } + if (numLines > displayLines) + { + int cursorLine = GetCursorLine(); + + startLine = _currentStartLine; + + // see if that is visible + if (cursorLine < _currentStartLine) + { + // cursor is above visible area; scroll back + startLine = cursorLine; + if (_vertScrollBar) + { + MoveScrollBar( 1 ); // should be calibrated for speed + // adjust startline incase we hit a limit + startLine = _vertScrollBar->GetValue(); + } + } + else if (cursorLine > (_currentStartLine + displayLines - 1)) + { + // cursor is down below visible area; scroll forward + startLine = cursorLine - displayLines + 1; + if (_vertScrollBar) + { + MoveScrollBar( -1 ); + startLine = _vertScrollBar->GetValue(); + } + } + } + else if (!_multiline) + { + // check to see if cursor is off the right side of screen-single line case + // get cursor's x coordinate in pixel space + bool done = false; + while ( !done ) + { + done = true; + int x = DRAW_OFFSET_X; + for (int i = _currentStartIndex; i < m_TextStream.Count(); i++) + { + done = false; + wchar_t ch = m_TextStream[i]; + if (_hideText) + { + ch = '*'; + } + + // if we've found the position, break + if (_cursorPos == i) + { + break; + } + + // add to the current position + x += getCharWidth(font, ch); + } + + if ( x >= GetWide() ) + { + _currentStartIndex++; + // Keep searching... + continue; + } + + if ( x <= 0 ) + { + // dont go past the Start of buffer + if (_currentStartIndex > 0) + _currentStartIndex--; + } + + break; + } + } + } + + if (startLine > 0) + { + lineBreakIndexIndex = startLine; + if (startLine && startLine < m_LineBreaks.Count()) + { + startIndex = m_LineBreaks[startLine - 1]; + } + } + + if (!_horizScrollingAllowed) + return 0; + + _currentStartLine = startLine; + if (_multiline) + return startIndex; + else + return _currentStartIndex; + + +} + +// helper accessors for common gets +float TextEntry::GetValueAsFloat() +{ + int nTextLength = GetTextLength() + 1; + char* txt = ( char* )_alloca( nTextLength * sizeof( char ) ); + GetText( txt, nTextLength ); + + return V_atof( txt ); +} + +int TextEntry::GetValueAsInt() +{ + int nTextLength = GetTextLength() + 1; + char* txt = ( char* )_alloca( nTextLength * sizeof( char ) ); + GetText( txt, nTextLength ); + + return V_atoi( txt ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get a string from text buffer +// Input: offset - index to Start reading from +// bufLenInBytes - length of string +//----------------------------------------------------------------------------- +void TextEntry::GetText(OUT_Z_BYTECAP(bufLenInBytes) char *buf, int bufLenInBytes) +{ + Assert(bufLenInBytes >= sizeof(buf[0])); + if (m_TextStream.Count()) + { + // temporarily null terminate the text stream so we can use the conversion function + int nullTerminatorIndex = m_TextStream.AddToTail((wchar_t)0); + g_pVGuiLocalize->ConvertUnicodeToANSI(m_TextStream.Base(), buf, bufLenInBytes); + m_TextStream.FastRemove(nullTerminatorIndex); + } + else + { + // no characters in the stream + buf[0] = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get a string from text buffer +// Input: offset - index to Start reading from +// bufLen - length of string +//----------------------------------------------------------------------------- +void TextEntry::GetText(OUT_Z_BYTECAP(bufLenInBytes) wchar_t *wbuf, int bufLenInBytes) +{ + Assert(bufLenInBytes >= sizeof(wbuf[0])); + int len = m_TextStream.Count(); + if (m_TextStream.Count()) + { + int terminator = min(len, (bufLenInBytes / (int)sizeof(wchar_t)) - 1); + wcsncpy(wbuf, m_TextStream.Base(), terminator); + wbuf[terminator] = 0; + } + else + { + wbuf[0] = 0; + } +} + +void TextEntry::GetTextRange( wchar_t *buf, int from, int numchars ) +{ + int len = m_TextStream.Count(); + int cpChars = max( 0, min( numchars, len - from ) ); + + wcsncpy( buf, m_TextStream.Base() + max( 0, min( len, from ) ), cpChars ); + buf[ cpChars ] = 0; +} + +void TextEntry::GetTextRange( char *buf, int from, int numchars ) +{ + int len = m_TextStream.Count(); + int cpChars = max( 0, min( numchars, len - from ) ); + + g_pVGuiLocalize->ConvertUnicodeToANSI( m_TextStream.Base() + max( 0, min( len, from ) ), buf, cpChars + 1 ); + buf[ cpChars ] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Sends a message that the text has changed +//----------------------------------------------------------------------------- +void TextEntry::FireActionSignal() +{ + PostActionSignal(new KeyValues("TextChanged")); + _dataChanged = false; // reset the data changed flag + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the font of the buffer text +// Input: font to change to +//----------------------------------------------------------------------------- +void TextEntry::SetFont(HFont font) +{ + _font = font; + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the scrollbar slider is moved +//----------------------------------------------------------------------------- +void TextEntry::OnSliderMoved() +{ + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool TextEntry::RequestInfo(KeyValues *outputData) +{ + if (!stricmp(outputData->GetName(), "GetText")) + { + wchar_t wbuf[256]; + GetText(wbuf, 255); + outputData->SetWString("text", wbuf); + return true; + } + else if (!stricmp(outputData->GetName(), "GetState")) + { + char buf[64]; + GetText(buf, sizeof(buf)); + outputData->SetInt("state", atoi(buf)); + return true; + } + return BaseClass::RequestInfo(outputData); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TextEntry::OnSetText(const wchar_t *text) +{ + SetText(text); +} + +//----------------------------------------------------------------------------- +// Purpose: as above, but sets an integer +//----------------------------------------------------------------------------- +void TextEntry::OnSetState(int state) +{ + char buf[64]; + Q_snprintf(buf, sizeof(buf), "%d", state); + SetText(buf); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TextEntry::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + _font = scheme()->GetIScheme( GetScheme() )->GetFont( inResourceData->GetString( "font", "Default" ), IsProportional() ); + SetFont( _font ); + + SetTextHidden((bool)inResourceData->GetInt("textHidden", 0)); + SetEditable((bool)inResourceData->GetInt("editable", 1)); + SetMaximumCharCount(inResourceData->GetInt("maxchars", -1)); + SetAllowNumericInputOnly(inResourceData->GetInt("NumericInputOnly", 0)); + SetAllowNonAsciiCharacters(inResourceData->GetInt("unicode", 0)); + SelectAllOnFirstFocus(inResourceData->GetInt("selectallonfirstfocus", 0)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TextEntry::GetSettings( KeyValues *outResourceData ) +{ + BaseClass::GetSettings( outResourceData ); + outResourceData->SetInt("textHidden", _hideText); + outResourceData->SetInt("editable", IsEditable()); + outResourceData->SetInt("maxchars", GetMaximumCharCount()); + outResourceData->SetInt("NumericInputOnly", m_bAllowNumericInputOnly); + outResourceData->SetInt("unicode", m_bAllowNonAsciiCharacters); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *TextEntry::GetDescription() +{ + static char buf[1024]; + Q_snprintf(buf, sizeof(buf), "%s, bool textHidden, bool editable, bool unicode, bool NumericInputOnly, int maxchars", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the number of lines in the window +//----------------------------------------------------------------------------- +int TextEntry::GetNumLines() +{ + return m_LineBreaks.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the height of the text entry window so all text will fit inside +//----------------------------------------------------------------------------- +void TextEntry::SetToFullHeight() +{ + PerformLayout(); + int wide, tall; + GetSize(wide, tall); + + tall = GetNumLines() * (surface()->GetFontTall(_font) + DRAW_OFFSET_Y) + DRAW_OFFSET_Y + 2; + SetSize (wide, tall); + PerformLayout(); + +} + +//----------------------------------------------------------------------------- +// Purpose: Select all the text. +//----------------------------------------------------------------------------- +void TextEntry::SelectAllText( bool bResetCursorPos ) +{ + // if there's no text at all, select none + if ( m_TextStream.Count() == 0 ) + { + _select[0] = -1; + } + else + { + _select[0] = 0; + } + + _select[1] = m_TextStream.Count(); + + if ( bResetCursorPos ) + { + _cursorPos = _select[1]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Select no text. +//----------------------------------------------------------------------------- +void TextEntry::SelectNoText() +{ + _select[0] = -1; + _select[1] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the width of the text entry window so all text will fit inside +//----------------------------------------------------------------------------- +void TextEntry::SetToFullWidth() +{ + // probably be problems if you try using this on multi line buffers + // or buffers with clickable text in them. + if (_multiline) + return; + + PerformLayout(); + int wide = 2*DRAW_OFFSET_X; // buffer on left and right end of text. + + // loop through all the characters and sum their widths + for (int i = 0; i < m_TextStream.Count(); ++i) + { + wide += getCharWidth(_font, m_TextStream[i]); + } + + // height of one line of text + int tall = (surface()->GetFontTall(_font) + DRAW_OFFSET_Y) + DRAW_OFFSET_Y + 2; + + SetSize (wide, tall); + PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TextEntry::SelectAllOnFirstFocus( bool status ) +{ + _selectAllOnFirstFocus = status; +} + +void TextEntry::SelectAllOnFocusAlways( bool status ) +{ + _selectAllOnFirstFocus = status; + _selectAllOnFocusAlways = status; +} + +//----------------------------------------------------------------------------- +// Purpose: called when the text entry receives focus +//----------------------------------------------------------------------------- +void TextEntry::OnSetFocus() +{ + // see if we should highlight all on selection + if (_selectAllOnFirstFocus) + { + _select[1] = m_TextStream.Count(); + _select[0] = _select[1] > 0 ? 0 : -1; + _cursorPos = _select[1]; // cursor at end of line + if ( !_selectAllOnFocusAlways ) + { + _selectAllOnFirstFocus = false; + } + } + else if (input()->IsKeyDown(KEY_TAB) || input()->WasKeyReleased(KEY_TAB)) + { + // if we've tabbed to this field then move to the end of the text + GotoTextEnd(); + // clear any selection + SelectNone(); + } + + BaseClass::OnSetFocus(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the width we have to draw text in. +// Do not use in multiline windows. +//----------------------------------------------------------------------------- +void TextEntry::SetDrawWidth(int width) +{ + _drawWidth = width; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the width we have to draw text in. +//----------------------------------------------------------------------------- +int TextEntry::GetDrawWidth() +{ + return _drawWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void TextEntry::SetAllowNonAsciiCharacters(bool state) +{ + m_bAllowNonAsciiCharacters = state; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void TextEntry::SetAllowNumericInputOnly(bool state) +{ + m_bAllowNumericInputOnly = state; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : forward - +//----------------------------------------------------------------------------- +void TextEntry::OnChangeIME( bool forward ) +{ + // Only change ime if Unicode aware + if ( m_bAllowNonAsciiCharacters ) + { + input()->OnChangeIME( forward ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handleValue - +//----------------------------------------------------------------------------- +void TextEntry::LanguageChanged( int handleValue ) +{ + input()->OnChangeIMEByHandle( handleValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handleValue - +//----------------------------------------------------------------------------- +void TextEntry::ConversionModeChanged( int handleValue ) +{ + input()->OnChangeIMEConversionModeByHandle( handleValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handleValue - +//----------------------------------------------------------------------------- +void TextEntry::SentenceModeChanged( int handleValue ) +{ + input()->OnChangeIMESentenceModeByHandle( handleValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *compstr - +//----------------------------------------------------------------------------- +void TextEntry::CompositionString( const wchar_t *compstr ) +{ + wcsncpy( m_szComposition, compstr, sizeof( m_szComposition ) / sizeof( wchar_t ) - 1 ); + m_szComposition[ sizeof( m_szComposition ) / sizeof( wchar_t ) - 1 ] = L'\0'; +} + +void TextEntry::ShowIMECandidates() +{ + HideIMECandidates(); + + int c = input()->GetCandidateListCount(); + if ( c == 0 ) + { + return; + } + + m_pIMECandidates = new Menu( this, "IMECandidatesMenu" ); + + int pageStart = input()->GetCandidateListPageStart(); + int pageSize = input()->GetCandidateListPageSize(); + int selected = input()->GetCandidateListSelectedItem(); + + int startAtOne = input()->CandidateListStartsAtOne() ? 1 : 0; + + if ( ( selected < pageStart ) || ( selected >= pageStart + pageSize ) ) + { + pageStart = ( selected / pageSize ) * pageSize; + input()->SetCandidateListPageStart( pageStart ); + } + + for ( int i = pageStart; i < pageStart + pageSize; ++i ) + { + if ( i >= c ) + continue; + + bool isSelected = ( i == selected ) ? true : false; + + wchar_t unicode[ 32 ]; + input()->GetCandidate( i, unicode, sizeof( unicode ) ); + + wchar_t label[ 64 ]; + _snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %s", i - pageStart + startAtOne, unicode ); + label[ sizeof( label ) / sizeof( wchar_t ) - 1 ] = L'\0'; + + int id = m_pIMECandidates->AddMenuItem( "Candidate", label, (KeyValues *)NULL, this ); + if ( isSelected ) + { + m_pIMECandidates->SetCurrentlyHighlightedItem( id ); + } + } + + m_pIMECandidates->SetVisible(true); + m_pIMECandidates->SetParent(this); + m_pIMECandidates->AddActionSignalTarget(this); + m_pIMECandidates->SetKeyBoardInputEnabled( false ); + + int cx, cy; + CursorToPixelSpace(_cursorPos, cx, cy); + cy = GetTall(); + + LocalToScreen( cx, cy ); + + //m_pIMECandidates->SetPos( cx, cy ); + + // relayout the menu immediately so that we know it's size + m_pIMECandidates->InvalidateLayout(true); + int menuWide, menuTall; + m_pIMECandidates->GetSize(menuWide, menuTall); + + // work out where the cursor is and therefore the best place to put the menu + int wide, tall; + surface()->GetScreenSize(wide, tall); + + if (wide - menuWide > cx) + { + // menu hanging right + if (tall - menuTall > cy) + { + // menu hanging down + m_pIMECandidates->SetPos(cx, cy); + } + else + { + // menu hanging up + m_pIMECandidates->SetPos(cx, cy - menuTall - GetTall()); + } + } + else + { + // menu hanging left + if (tall - menuTall > cy) + { + // menu hanging down + m_pIMECandidates->SetPos(cx - menuWide, cy); + } + else + { + // menu hanging up + m_pIMECandidates->SetPos(cx - menuWide, cy - menuTall-GetTall()); + } + } +} + +void TextEntry::HideIMECandidates() +{ + if ( m_pIMECandidates ) + { + m_pIMECandidates->SetVisible( false ); + } + delete m_pIMECandidates; + m_pIMECandidates = NULL; +} + +void TextEntry::UpdateIMECandidates() +{ + if ( !m_pIMECandidates ) + return; + + int c = input()->GetCandidateListCount(); + if ( c == 0 ) + { + HideIMECandidates(); + return; + } + + int oldCount = m_pIMECandidates->GetItemCount(); + int newCount = input()->GetCandidateListPageSize(); + + if ( oldCount != newCount ) + { + // Recreate the entire menu + ShowIMECandidates(); + return; + } + + int pageSize = input()->GetCandidateListPageSize(); + int selected = input()->GetCandidateListSelectedItem(); + int pageStart = input()->GetCandidateListPageStart(); + + if ( ( selected < pageStart ) || selected >= pageStart + pageSize ) + { + pageStart = ( selected / pageSize ) * pageSize; + input()->SetCandidateListPageStart( pageStart ); + } + + int startAtOne = input()->CandidateListStartsAtOne() ? 1 : 0; + + for ( int i = pageStart; i < pageStart + pageSize; ++i ) + { + int id = m_pIMECandidates->GetMenuID( i - pageStart ); + + MenuItem *item = m_pIMECandidates->GetMenuItem( id ); + if ( !item ) + continue; + + if ( i >= c ) + { + item->SetVisible( false ); + continue; + } + else + { + item->SetVisible( true ); + } + + bool isSelected = ( i == selected ) ? true : false; + + wchar_t unicode[ 32 ]; + input()->GetCandidate( i, unicode, sizeof( unicode ) ); + + wchar_t label[ 64 ]; + _snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %s", i - pageStart + startAtOne, unicode ); + label[ sizeof( label ) / sizeof( wchar_t ) - 1 ] = L'\0'; + item->SetText( label ); + if ( isSelected ) + { + m_pIMECandidates->SetCurrentlyHighlightedItem( id ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TextEntry::FlipToLastIME() +{ + int hCurrentIME = input()->GetCurrentIMEHandle(); + int hEnglishIME = input()->GetEnglishIMEHandle(); + + bool isEnglish = ( hCurrentIME == hEnglishIME ) ? true : false; + + // If in english, flip back to previous + if ( isEnglish ) + { + input()->OnChangeIMEByHandle( m_hPreviousIME ); + } + else + { + // If not, remember language and flip to english... + m_hPreviousIME = hCurrentIME; + input()->OnChangeIMEByHandle( hEnglishIME ); + } +} + +void TextEntry::SetDrawLanguageIDAtLeft( bool state ) +{ + m_bDrawLanguageIDAtLeft = state; +} + +bool TextEntry::GetDropContextMenu( Menu *menu, CUtlVector< KeyValues * >& msglist ) +{ + menu->AddMenuItem( "replace", "#TextEntry_ReplaceText", "replace", this ); + menu->AddMenuItem( "append", "#TextEntry_AppendText", "append", this ); + menu->AddMenuItem( "prepend", "#TextEntry_PrependText", "prepend", this ); + return true; +} + +bool TextEntry::IsDroppable( CUtlVector< KeyValues * >& msglist ) +{ + if ( msglist.Count() != 1 ) + return false; + + if ( !IsEnabled() ) + return false; + + KeyValues *msg = msglist[ 0 ]; + + const wchar_t *txt = msg->GetWString( "text", L"" ); + if ( !txt || txt[ 0 ] == L'\0' ) + return false; + + return true; +} + +void TextEntry::OnPanelDropped( CUtlVector< KeyValues * >& msglist ) +{ + if ( msglist.Count() != 1 ) + return; + + KeyValues *data = msglist[ 0 ]; + + const wchar_t *newText = data->GetWString( "text" ); + if ( !newText || newText[ 0 ] == L'\0' ) + return; + + char const *cmd = data->GetString( "command" ); + if ( !Q_stricmp( cmd, "replace" ) || + !Q_stricmp( cmd, "default" ) ) + { + SetText( newText ); + _dataChanged = true; + FireActionSignal(); + } + else if ( !Q_stricmp( cmd, "append" ) ) + { + int newLen = wcslen( newText ); + int curLen = m_TextStream.Count(); + + size_t outsize = sizeof( wchar_t ) * ( newLen + curLen + 1 ); + wchar_t *out = (wchar_t *)_alloca( outsize ); + Q_memset( out, 0, outsize ); + wcsncpy( out, m_TextStream.Base(), curLen ); + wcsncat( out, newText, wcslen( newText ) ); + out[ newLen + curLen ] = L'\0'; + SetText( out ); + _dataChanged = true; + FireActionSignal(); + } + else if ( !Q_stricmp( cmd, "prepend" ) ) + { + int newLen = wcslen( newText ); + int curLen = m_TextStream.Count(); + + size_t outsize = sizeof( wchar_t ) * ( newLen + curLen + 1 ); + wchar_t *out = (wchar_t *)_alloca( outsize ); + Q_memset( out, 0, outsize ); + wcsncpy( out, newText, wcslen( newText ) ); + wcsncat( out, m_TextStream.Base(), curLen ); + out[ newLen + curLen ] = L'\0'; + SetText( out ); + _dataChanged = true; + FireActionSignal(); + } +} + +int TextEntry::GetTextLength() const +{ + return m_TextStream.Count(); +} + +bool TextEntry::IsTextFullySelected() const +{ + if ( _select[ 0 ] != 0 ) + return false; + + if ( _select[ 1 ] != GetTextLength() ) + return false; + + return true; +} + +void TextEntry::SetUseFallbackFont( bool bState, HFont hFallback ) +{ + m_bUseFallbackFont = bState; + m_hFallbackFont = hFallback; +} diff --git a/vgui2/vgui_controls/TextImage.cpp b/vgui2/vgui_controls/TextImage.cpp new file mode 100644 index 0000000..6153212 --- /dev/null +++ b/vgui2/vgui_controls/TextImage.cpp @@ -0,0 +1,985 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Implementation of vgui::TextImage control +// +// $NoKeywords: $ +//=============================================================================// + +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <assert.h> +#include <malloc.h> + +#include <vgui/IPanel.h> +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <vgui/IInput.h> +#include <vgui/ILocalize.h> +#include <KeyValues.h> + +#include <vgui_controls/TextImage.h> +#include <vgui_controls/Controls.h> + +#include "tier0/dbg.h" +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +// enable this define if you want unlocalized strings logged to files unfound.txt and unlocalized.txt +// #define LOG_UNLOCALIZED_STRINGS + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +TextImage::TextImage(const char *text) : Image() +{ + _utext = NULL; + _textBufferLen = 0; + _font = INVALID_FONT; + _fallbackFont = INVALID_FONT; + _unlocalizedTextSymbol = INVALID_LOCALIZE_STRING_INDEX; + _drawWidth = 0; + _textBufferLen = 0; + _textLen = 0; + m_bWrap = false; + m_bWrapCenter = false; + m_LineBreaks.RemoveAll(); + m_LineXIndent.RemoveAll(); + m_pwszEllipsesPosition = NULL; + m_bUseFallbackFont = false; + m_bRenderUsingFallbackFont = false; + m_bAllCaps = false; + + SetText(text); // set the text. +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +TextImage::TextImage(const wchar_t *wszText) : Image() +{ + _utext = NULL; + _textBufferLen = 0; + _font = INVALID_FONT; + _fallbackFont = INVALID_FONT; + _unlocalizedTextSymbol = INVALID_LOCALIZE_STRING_INDEX; + _drawWidth = 0; + _textBufferLen = 0; + _textLen = 0; + m_bWrap = false; + m_bWrapCenter = false; + m_LineBreaks.RemoveAll(); + m_LineXIndent.RemoveAll(); + m_bUseFallbackFont = false; + m_bRenderUsingFallbackFont = false; + m_bAllCaps = false; + + SetText(wszText); // set the text. +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +TextImage::~TextImage() +{ + delete [] _utext; +} + +//----------------------------------------------------------------------------- +// Purpose: takes the string and looks it up in the localization file to convert it to unicode +//----------------------------------------------------------------------------- +void TextImage::SetText(const char *text) +{ + if (!text) + { + text = ""; + } + + // check for localization + if (*text == '#') + { + // try lookup in localization tables + _unlocalizedTextSymbol = g_pVGuiLocalize->FindIndex(text + 1); + + if (_unlocalizedTextSymbol != INVALID_LOCALIZE_STRING_INDEX) + { + wchar_t *unicode = g_pVGuiLocalize->GetValueByIndex(_unlocalizedTextSymbol); + SetText(unicode); + return; + } + else + { + // could not find string + // debug code for logging unlocalized strings +#if defined(LOG_UNLOCALIZED_STRINGS) + if (*text) + { + // write out error to unfound.txt log file + static bool first = true; + FILE *f; + if (first) + { + first = false; + f = fopen("unfound.txt", "wt"); + } + else + { + f = fopen("unfound.txt", "at"); + } + + if (f) + { + fprintf(f, "\"%s\"\n", text); + fclose(f); + } + } +#endif // LOG_UNLOCALIZED_STRINGS + } + } + else + { + // debug code for logging unlocalized strings +#if defined(LOG_UNLOCALIZED_STRINGS) + if (text[0]) + { + // setting a label to be ANSI text, write out error to unlocalized.txt log file + static bool first = true; + FILE *f; + if (first) + { + first = false; + f = fopen("unlocalized.txt", "wt"); + } + else + { + f = fopen("unlocalized.txt", "at"); + } + if (f) + { + fprintf(f, "\"%s\"\n", text); + fclose(f); + } + } +#endif // LOG_UNLOCALIZED_STRINGS + } + + // convert the ansi string to unicode and use that + wchar_t unicode[1024]; + g_pVGuiLocalize->ConvertANSIToUnicode(text, unicode, sizeof(unicode)); + SetText(unicode); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the width that the text can be. +//----------------------------------------------------------------------------- +void TextImage::SetDrawWidth(int width) +{ + _drawWidth = width; + m_bRecalculateTruncation = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the width that the text can be. +//----------------------------------------------------------------------------- +void TextImage::GetDrawWidth(int &width) +{ + width = _drawWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: sets unicode text directly +//----------------------------------------------------------------------------- +void TextImage::SetText(const wchar_t *unicode, bool bClearUnlocalizedSymbol) +{ + if ( bClearUnlocalizedSymbol ) + { + // Clear out unlocalized text symbol so that changing dialog variables + // doesn't stomp over the custom unicode string we're being set to. + _unlocalizedTextSymbol = INVALID_LOCALIZE_STRING_INDEX; + } + + if (!unicode) + { + unicode = L""; + } + + // reallocate the buffer if necessary + _textLen = (short)wcslen(unicode); + if (_textLen >= _textBufferLen) + { + delete [] _utext; + _textBufferLen = (short)(_textLen + 1); + _utext = new wchar_t[_textBufferLen]; + } + + m_LineBreaks.RemoveAll(); + m_LineXIndent.RemoveAll(); + + // store the text as unicode + wcscpy(_utext, unicode); + + m_bRecalculateTruncation = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the text in the textImage +//----------------------------------------------------------------------------- +void TextImage::GetText(char *buffer, int bufferSize) +{ + g_pVGuiLocalize->ConvertUnicodeToANSI(_utext, buffer, bufferSize); + + if ( m_bAllCaps ) + { + // Uppercase all the letters + for ( int i = Q_strlen( buffer ); i >= 0; --i ) + { + buffer[ i ] = toupper( buffer[ i ] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the text in the textImage +//----------------------------------------------------------------------------- +void TextImage::GetText(wchar_t *buffer, int bufLenInBytes) +{ + wcsncpy(buffer, _utext, bufLenInBytes / sizeof(wchar_t)); + + if ( m_bAllCaps ) + { + // Uppercase all the letters + for ( int i = Q_wcslen( buffer ) - 1; i >= 0; --i ) + { + buffer[ i ] = towupper( buffer[ i ] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void TextImage::GetUnlocalizedText(char *buffer, int bufferSize) +{ + if (_unlocalizedTextSymbol != INVALID_LOCALIZE_STRING_INDEX) + { + const char *text = g_pVGuiLocalize->GetNameByIndex(_unlocalizedTextSymbol); + buffer[0] = '#'; + Q_strncpy(buffer + 1, text, bufferSize - 1); + buffer[bufferSize-1] = 0; + } + else + { + GetText(buffer, bufferSize); + } +} + +//----------------------------------------------------------------------------- +// Purpose: unlocalized text symbol +//----------------------------------------------------------------------------- +StringIndex_t TextImage::GetUnlocalizedTextSymbol() +{ + return _unlocalizedTextSymbol; +} + +//----------------------------------------------------------------------------- +// Purpose: Changes the current font +//----------------------------------------------------------------------------- +void TextImage::SetFont(HFont font) +{ + _font = font; + m_bRecalculateTruncation = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the font of the text. +//----------------------------------------------------------------------------- +HFont TextImage::GetFont() +{ + if ( m_bRenderUsingFallbackFont ) + { + return _fallbackFont; + } + + return _font; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the size of the TextImage. This is directly tied to drawWidth +//----------------------------------------------------------------------------- +void TextImage::SetSize(int wide, int tall) +{ + Image::SetSize(wide, tall); + _drawWidth = wide; + m_bRecalculateTruncation = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the Image on screen. +//----------------------------------------------------------------------------- +void TextImage::Paint() +{ + int wide, tall; + GetSize(wide, tall); + + if (!_utext || GetFont() == INVALID_FONT ) + return; + + if (m_bRecalculateTruncation) + { + if ( m_bWrap || m_bWrapCenter ) + { + RecalculateNewLinePositions(); + } + + RecalculateEllipsesPosition(); + } + + DrawSetTextColor(GetColor()); + HFont font = GetFont(); + DrawSetTextFont(font); + + int lineHeight = surface()->GetFontTall(font); + float x = 0.0f; + int y = 0; + int iIndent = 0; + int iNextColorChange = 0; + + int px, py; + GetPos(px, py); + + int currentLineBreak = 0; + + if ( m_bWrapCenter && m_LineXIndent.Count() ) + { + x = m_LineXIndent[0]; + } + + for (wchar_t *wsz = _utext; *wsz != 0; wsz++) + { + wchar_t ch = wsz[0]; + + if ( m_bAllCaps ) + { + ch = towupper( ch ); + } + + if ( m_ColorChangeStream.Count() > iNextColorChange ) + { + if ( m_ColorChangeStream[iNextColorChange].textStreamIndex == (wsz - _utext) ) + { + DrawSetTextColor( m_ColorChangeStream[iNextColorChange].color ); + iNextColorChange++; + } + } + + // check for special characters + if ( ch == '\r' || ch <= 8 ) + { + // ignore, just use \n for newlines + continue; + } + else if (ch == '\n') + { + // newline + iIndent++; + if ( m_bWrapCenter && iIndent < m_LineXIndent.Count() ) + { + x = m_LineXIndent[iIndent]; + } + else + { + x = 0; + } + y += lineHeight; + continue; + } + else if (ch == '&') + { + // "&&" means draw a single ampersand, single one is a shortcut character + if (wsz[1] == '&') + { + // just move on and draw the second ampersand + wsz++; + } + else + { + // draw the underline, then continue to the next character without moving forward +#ifdef VGUI_DRAW_HOTKEYS_ENABLED + surface()->DrawSetTextPos(x + px, y + py); + surface()->DrawUnicodeChar('_'); +#endif + continue; + } + } + + // see if we've hit the truncated portion of the string + if (wsz == m_pwszEllipsesPosition) + { + // string is truncated, draw ellipses + for (int i = 0; i < 3; i++) + { + surface()->DrawSetTextPos(x + px, y + py); + surface()->DrawUnicodeChar('.'); + x += surface()->GetCharacterWidth(font, '.'); + } + break; + } + + if (currentLineBreak != m_LineBreaks.Count()) + { + if (wsz == m_LineBreaks[currentLineBreak]) + { + // newline + iIndent++; + if ( m_bWrapCenter && iIndent < m_LineXIndent.Count() ) + { + x = m_LineXIndent[iIndent]; + } + else + { + x = 0; + } + + y += lineHeight; + currentLineBreak++; + } + } + + // Underlined text wants to draw the spaces anyway +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if ( wsz > _utext ) + chBefore = wsz[-1]; + chAfter = wsz[1]; + float flWide = 0.0f, flabcA = 0.0f; + surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA ); + if ( ch == L' ' ) + x = ceil( x ); + + surface()->DrawSetTextPos( x + px, y + py); + surface()->DrawUnicodeChar(ch); + x += floor(flWide + 0.6); +#else + surface()->DrawSetTextPos( x + px, y + py); + surface()->DrawUnicodeChar(ch); + x += surface()->GetCharacterWidth(font, ch); +#endif + } + + // Useful debugging + /* + DrawSetColor(GetColor()); + DrawOutlinedRect( 0,0, _drawWidth, tall ); + */ +} + +//----------------------------------------------------------------------------- +// Purpose: Get the size of a text string in pixels +//----------------------------------------------------------------------------- +void TextImage::GetTextSize(int &wide, int &tall) +{ + wide = 0; + tall = 0; + int maxWide = 0; + const wchar_t *text = _utext; + + HFont font = _font; + if ( font == INVALID_FONT ) + return; + + if ( m_bWrap || m_bWrapCenter ) + { + RecalculateNewLinePositions(); + } + + // For height, use the remapped font + int fontHeight = surface()->GetFontTall(GetFont()); + tall = fontHeight; + + int textLen = wcslen(text); + for (int i = 0; i < textLen; i++) + { + wchar_t ch = text[i]; + + // handle stupid special characters, these should be removed + if ( ch == '&' ) + { + continue; + } + + if ( m_bAllCaps ) + { + ch = towupper( ch ); + } + +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if ( i > 0 ) + chBefore = text[i-1]; + chAfter = text[i+1]; + float flWide = 0.0f, flabcA; + surface()->GetKernedCharWidth( font, text[i], chBefore, chAfter, flWide, flabcA ); + if ( text[i] == L' ' ) + flWide = ceil( flWide ); + wide += floor( flWide + 0.6); +#else + int a, b, c; + surface()->GetCharABCwide(font, ch, a, b, c); + wide += (a + b + c); +#endif + + + if (ch == '\n') + { + tall += fontHeight; + if(wide>maxWide) + { + maxWide=wide; + } + wide=0; // new line, wide is reset... + } + + if ( m_bWrap || m_bWrapCenter ) + { + for(int j=0; j<m_LineBreaks.Count(); j++) + { + if ( &text[i] == m_LineBreaks[j] ) + { + tall += fontHeight; + if(wide>maxWide) + { + maxWide=wide; + } + wide=0; // new line, wide is reset... + } + } + } + + } +#ifdef OSX + wide += 2; + if ( textLen < 3 ) + wide += 3; +#endif + if (wide < maxWide) + { + // maxWide only gets set if a newline is in the label + wide = maxWide; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the size of the text in the image +//----------------------------------------------------------------------------- +void TextImage::GetContentSize(int &wide, int &tall) +{ + GetTextSize(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Resize the text image to the content size +//----------------------------------------------------------------------------- +void TextImage::ResizeImageToContent() +{ + int wide, tall; + GetContentSize(wide, tall); + SetSize(wide, tall); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculates line breaks +//----------------------------------------------------------------------------- +void TextImage::RecalculateNewLinePositions() +{ + HFont font = GetFont(); + + int charWidth; + int x = 0; + + //int wordStartIndex = 0; + wchar_t *wordStartIndex = _utext; + int wordLength = 0; + bool hasWord = false; + bool justStartedNewLine = true; + bool wordStartedOnNewLine = true; + + int startChar = 0; + + // clear the line breaks list + m_LineBreaks.RemoveAll(); + m_LineXIndent.RemoveAll(); + + // handle the case where this char is a new line, in that case + // we have already taken its break index into account above so skip it. + if (_utext[startChar] == '\r' || _utext[startChar] == '\n') + { + startChar++; + } + + // loop through all the characters + for (wchar_t *wsz = &_utext[startChar]; *wsz != 0; wsz++) + { + // handle stupid special characters, these should be removed + // 0x01, 0x02 and 0x03 are color escape characters and should be ignored + if ( ( wsz[0] == '&' || wsz[0] == 0x01 || wsz[0] == 0x02 || wsz[0] == 0x03 ) && wsz[1] != 0 ) + { + wsz++; + } + + wchar_t ch = wsz[0]; + + if ( m_bAllCaps ) + { + ch = towupper( ch ); + } + + // line break only on whitespace characters + if (!iswspace(ch)) + { + if ( !hasWord ) + { + // Start a new word + wordStartIndex = wsz; + hasWord = true; + wordStartedOnNewLine = justStartedNewLine; + wordLength = 0; + } + //else append to the current word + } + else + { + // whitespace/punctuation character + // end the word + hasWord = false; + } + + // get the width +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if ( wsz > _utext ) + chBefore = wsz[-1]; + chAfter = wsz[1]; + float flWide = 0.0f, flabcA = 0.0f; + surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA ); + charWidth = floor( flWide + 0.6 ); +#else + charWidth = surface()->GetCharacterWidth(font, ch); +#endif + if (!iswcntrl(ch)) + { + justStartedNewLine = false; + } + + // check to see if the word is past the end of the line [wordStartIndex, i) + if ((x + charWidth) > _drawWidth || ch == '\r' || ch == '\n') + { + justStartedNewLine = true; + hasWord = false; + + if (ch == '\r' || ch == '\n') + { + // set the break at the current character + //don't do this, paint will manually wrap on newline chars + // m_LineBreaks.AddToTail(i); + } + else if (wordStartedOnNewLine) + { + // word is longer than a line, so set the break at the current cursor + m_LineBreaks.AddToTail(wsz); + } + else + { + // set it at the last word Start + m_LineBreaks.AddToTail(wordStartIndex); + + // just back to reparse the next line of text + // ywb 8/1/07: Back off one extra char since the 'continue' will increment wsz for us by one in the for loop + wsz = wordStartIndex - 1; + } + + // reset word length + wordLength = 0; + x = 0; + continue; + } + + // add to the size + x += charWidth; + wordLength += charWidth; + } + + RecalculateCenterWrapIndents(); +} + +//----------------------------------------------------------------------------- +// Purpose: Calculates where the text should be truncated +//----------------------------------------------------------------------------- +void TextImage::RecalculateEllipsesPosition() +{ + m_bRecalculateTruncation = false; + m_pwszEllipsesPosition = NULL; + + //don't insert ellipses on wrapped strings + if ( m_bWrap || m_bWrapCenter ) + return; + + // don't truncate strings with newlines + if (wcschr(_utext, '\n') != NULL) + return; + + if ( _drawWidth == 0 ) + { + int h; + GetSize( _drawWidth, h ); + } + + for ( int check = 0; check < (m_bUseFallbackFont ? 2 : 1); ++check ) + { + HFont font = GetFont(); + if ( check == 1 && _fallbackFont != INVALID_FONT ) + { + m_pwszEllipsesPosition = NULL; + font = _fallbackFont; + m_bRenderUsingFallbackFont = true; + } + + int ellipsesWidth = 3 * surface()->GetCharacterWidth(font, '.'); + int x = 0; + + for (wchar_t *wsz = _utext; *wsz != 0; wsz++) + { + wchar_t ch = wsz[0]; + + if ( m_bAllCaps ) + { + ch = towupper( ch ); + } + + // check for special characters + if (ch == '\r') + { + // ignore, just use \n for newlines + continue; + } + else if (ch == '&') + { + // "&&" means draw a single ampersand, single one is a shortcut character + if (wsz[1] == '&') + { + // just move on and draw the second ampersand + wsz++; + } + else + { + continue; + } + } + +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if ( wsz > _utext ) + chBefore = wsz[-1]; + chAfter = wsz[1]; + float flWide = 0.0f, flabcA = 0.0f; + surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA ); + int len = floor( flWide + 0.6 ); +#else + int len = surface()->GetCharacterWidth(font, ch); +#endif + + // don't truncate the first character + if (wsz == _utext) + { + x += len; + continue; + } + + if (x + len + ellipsesWidth > _drawWidth) + { + // potential have an ellipses, see if the remaining characters will fit + int remainingLength = len; + for (const wchar_t *rwsz = wsz + 1; *rwsz != 0; rwsz++) + { +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if ( rwsz > _utext ) + chBefore = rwsz[-1]; + chAfter = rwsz[1]; + float flWide = 0.0f, flabcA = 0.0f; + surface()->GetKernedCharWidth( font, *rwsz, chBefore, chAfter, flWide, flabcA ); + int len = floor( flWide + 0.6 ); + remainingLength += floor( flWide + 0.6 ); +#else + remainingLength += surface()->GetCharacterWidth(font, *rwsz); +#endif + } + + if (x + remainingLength > _drawWidth) + { + // looks like we've got an ellipses situation + m_pwszEllipsesPosition = wsz; + break; + } + } + + x += len; + } + + // Didn't need ellipses... + if ( !m_pwszEllipsesPosition ) + break; + } +} + +void TextImage::SetWrap( bool bWrap ) +{ + m_bWrap = bWrap; +} + +void TextImage::SetCenterWrap( bool bWrap ) +{ + m_bWrapCenter = bWrap; +} + + +void TextImage::SetUseFallbackFont( bool bState, HFont hFallback ) +{ + m_bUseFallbackFont = bState; + _fallbackFont = hFallback; +} + +void TextImage::SetAllCaps( bool bAllCaps ) +{ + m_bAllCaps = bAllCaps; +} + +void TextImage::ResizeImageToContentMaxWidth( int nMaxWidth ) +{ + _drawWidth = nMaxWidth; + // Since we might have to use the "fallback" font, go ahead and recalc the ellipses state first to see if that's the case + // NOTE: I think there may be a race condition lurking here, but this seems to work. + if ( m_bRecalculateTruncation ) + { + if ( m_bWrap || m_bWrapCenter ) + { + RecalculateNewLinePositions(); + } + + RecalculateEllipsesPosition(); + } + + ResizeImageToContent(); +} + +//----------------------------------------------------------------------------- +// Purpose: For center wrapping of multi-line text images, determines the indent each line needs to be centered +//----------------------------------------------------------------------------- +void TextImage::RecalculateCenterWrapIndents() +{ + m_LineXIndent.RemoveAll(); + + if ( !m_bWrapCenter ) + return; + + if (!_utext || GetFont() == INVALID_FONT ) + return; + + HFont font = GetFont(); + int px, py; + GetPos(px, py); + + int currentLineBreak = 0; + int iCurLineW = 0; + + for (wchar_t *wsz = _utext; *wsz != 0; wsz++) + { + wchar_t ch = wsz[0]; + + if ( m_bAllCaps ) + { + ch = towupper( ch ); + } + + // check for special characters + if (ch == '\r') + { + // ignore, just use \n for newlines + continue; + } + else if (ch == '\n') + { + int iIdx = m_LineXIndent.AddToTail(); + m_LineXIndent[iIdx] = (_drawWidth - iCurLineW) * 0.5; + + iCurLineW = 0; + continue; + } + else if (ch == '&') + { + // "&&" means draw a single ampersand, single one is a shortcut character + if (wsz[1] == '&') + { + // just move on and draw the second ampersand + wsz++; + } + else + { + // draw the underline, then continue to the next character without moving forward + continue; + } + } + + // Don't need to check ellipses, they're not used when wrapping + + if (currentLineBreak != m_LineBreaks.Count()) + { + if (wsz == m_LineBreaks[currentLineBreak]) + { + int iIdx = m_LineXIndent.AddToTail(); + m_LineXIndent[iIdx] = (_drawWidth - iCurLineW) * 0.5; + + iCurLineW = 0; + currentLineBreak++; + } + } + +#if USE_GETKERNEDCHARWIDTH + wchar_t chBefore = 0; + wchar_t chAfter = 0; + if ( wsz > _utext ) + chBefore = wsz[-1]; + chAfter = wsz[1]; + float flWide = 0.0f, flabcA = 0.0f; + surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA ); + iCurLineW += floor( flWide + 0.6 ); +#else + iCurLineW += surface()->GetCharacterWidth(font, ch); +#endif + } + + // Add the final line + int iIdx = m_LineXIndent.AddToTail(); + m_LineXIndent[iIdx] = (_drawWidth - iCurLineW) * 0.5; +} + +void TextImage::AddColorChange( Color col, int iTextStreamIndex ) +{ + label_colorchange_t tmpChange; + tmpChange.color = col; + tmpChange.textStreamIndex = iTextStreamIndex; + m_ColorChangeStream.Insert( tmpChange ); +} + +void TextImage::SetColorChangeStream( CUtlSortVector<label_colorchange_t,CColorChangeListLess> *pUtlVecStream ) +{ + ClearColorChangeStream(); + + m_ColorChangeStream = *pUtlVecStream; +} diff --git a/vgui2/vgui_controls/ToggleButton.cpp b/vgui2/vgui_controls/ToggleButton.cpp new file mode 100644 index 0000000..63e4dd6 --- /dev/null +++ b/vgui2/vgui_controls/ToggleButton.cpp @@ -0,0 +1,102 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/KeyCode.h> + +#include <vgui_controls/ToggleButton.h> + +#include <KeyValues.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY_DEFAULT_TEXT( ToggleButton, ToggleButton ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ToggleButton::ToggleButton(Panel *parent, const char *panelName, const char* text) : Button(parent, panelName, text) +{ + SetButtonActivationType(ACTIVATE_ONPRESSED); +} + +//----------------------------------------------------------------------------- +// Purpose: Turns double-click into normal click +//----------------------------------------------------------------------------- +void ToggleButton::OnMouseDoublePressed(MouseCode code) +{ + OnMousePressed(code); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color ToggleButton::GetButtonFgColor() +{ + if (IsSelected()) + { + // highlight the text when depressed + return _selectedColor; + } + else + { + return BaseClass::GetButtonFgColor(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ToggleButton::CanBeDefaultButton(void) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Toggles the state of the button +//----------------------------------------------------------------------------- +void ToggleButton::DoClick() +{ + if (IsSelected()) + { + ForceDepressed(false); + } + else if (!IsSelected()) + { + ForceDepressed(true); + } + + SetSelected(!IsSelected()); + FireActionSignal(); + + // post a button toggled message + KeyValues *msg = new KeyValues("ButtonToggled"); + msg->SetInt("state", (int)IsSelected()); + PostActionSignal(msg); + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ToggleButton::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + _selectedColor = GetSchemeColor("ToggleButton.SelectedTextColor", pScheme); +} + +void ToggleButton::OnKeyCodePressed(KeyCode code) +{ + if (code != KEY_ENTER) + { + BaseClass::OnKeyCodePressed(code); + } +} + diff --git a/vgui2/vgui_controls/ToolWindow.cpp b/vgui2/vgui_controls/ToolWindow.cpp new file mode 100644 index 0000000..51109c6 --- /dev/null +++ b/vgui2/vgui_controls/ToolWindow.cpp @@ -0,0 +1,478 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include "vgui/IInput.h" +#include "vgui/MouseCode.h" +#include "vgui/ISurface.h" + +#include <vgui_controls/ToolWindow.h> +#include <vgui_controls/PropertySheet.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +CUtlVector< ToolWindow * > ToolWindow::s_ToolWindows; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : int +//----------------------------------------------------------------------------- +int ToolWindow::GetToolWindowCount() +{ + return s_ToolWindows.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : PropertySheet +//----------------------------------------------------------------------------- +ToolWindow *ToolWindow::GetToolWindow( int index ) +{ + return s_ToolWindows[ index ]; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +ToolWindow::ToolWindow( + Panel *parent, + bool contextlabel, + IToolWindowFactory *factory /*= 0*/, + Panel *page /*= NULL*/, + char const *title /*= NULL */, + bool contextMenu /*=false*/, + bool inGlobalList /*= true*/ ) : BaseClass( parent, "ToolWindow" ), + m_pFactory( factory ) +{ + if ( inGlobalList ) + { + s_ToolWindows.AddToTail( this ); + } + + // create the property sheet + m_pPropertySheet = new PropertySheet(this, "ToolWindowSheet", true ); + m_pPropertySheet->ShowContextButtons( contextlabel ); + m_pPropertySheet->AddPage( page, title, 0, contextMenu ); + m_pPropertySheet->AddActionSignalTarget(this); + m_pPropertySheet->SetSmallTabs( true ); + m_pPropertySheet->SetKBNavigationEnabled( false ); + + SetSmallCaption( true ); + + SetMenuButtonResponsive(false); + SetMinimizeButtonVisible(false); + SetCloseButtonVisible(true); + SetMoveable( true ); + SetSizeable(true); + + SetClipToParent( false ); + SetVisible( true ); + + SetDeleteSelfOnClose( true ); + + SetTitle( "", false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +ToolWindow::~ToolWindow() +{ + // These don't actually kill the children of the property sheet + m_pPropertySheet->RemoveAllPages(); + + s_ToolWindows.FindAndRemove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Pass through to sheet +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ToolWindow::IsDraggableTabContainer() const +{ + return m_pPropertySheet->IsDraggableTab(); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a pointer to the PropertySheet this dialog encapsulates +// Output : PropertySheet * +//----------------------------------------------------------------------------- +PropertySheet *ToolWindow::GetPropertySheet() +{ + return m_pPropertySheet; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a pointer to the currently active page. +// Output : Panel +//----------------------------------------------------------------------------- +Panel *ToolWindow::GetActivePage() +{ + return m_pPropertySheet->GetActivePage(); +} + +void ToolWindow::SetActivePage( Panel *page ) +{ + m_pPropertySheet->SetActivePage( page ); +} + +//----------------------------------------------------------------------------- +// Purpose: Wrapped function +//----------------------------------------------------------------------------- +void ToolWindow::AddPage(Panel *page, const char *title, bool contextMenu) +{ + m_pPropertySheet->AddPage(page, title, 0, contextMenu ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *page - +//----------------------------------------------------------------------------- +void ToolWindow::RemovePage( Panel *page ) +{ + m_pPropertySheet->RemovePage( page ); + if ( m_pPropertySheet->GetNumPages() == 0 ) + { + MarkForDeletion(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets up the sheet +//----------------------------------------------------------------------------- +void ToolWindow::PerformLayout() +{ + BaseClass::PerformLayout(); + + int x, y, wide, tall; + GetClientArea(x, y, wide, tall); + m_pPropertySheet->SetBounds(x, y, wide, tall); + m_pPropertySheet->InvalidateLayout(); // tell the propertysheet to redraw! + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Overrides build mode so it edits the sub panel +//----------------------------------------------------------------------------- +void ToolWindow::ActivateBuildMode() +{ + // no subpanel, no build mode + EditablePanel *panel = dynamic_cast<EditablePanel *>(GetActivePage()); + if (!panel) + return; + + panel->ActivateBuildMode(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ToolWindow::RequestFocus(int direction) +{ + m_pPropertySheet->RequestFocus(direction); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *factory - +//----------------------------------------------------------------------------- +void ToolWindow::SetToolWindowFactory( IToolWindowFactory *factory ) +{ + m_pFactory = factory; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : IToolWindowFactory +//----------------------------------------------------------------------------- +IToolWindowFactory *ToolWindow::GetToolWindowFactory() +{ + return m_pFactory; +} + +//----------------------------------------------------------------------------- +// Purpose: To fill the space left by other tool windows +// Input : edge: 0=all, 1=top, 2=right, 3=bottom, 4=left +// Output : +//----------------------------------------------------------------------------- + +void ToolWindow::Grow( int edge, int from_x, int from_y ) +{ + int status_h = 24; + int menubar_h = 27; + + int sw, sh; + surface()->GetScreenSize( sw, sh ); + + int old_x, old_y, old_w, old_h; + GetBounds( old_x, old_y, old_w, old_h ); + + int new_x, new_y, new_w, new_h; + new_x = old_x; + new_y = old_y; + new_w = old_w; + new_h = old_h; + + int c = GetToolWindowCount(); + + // grow up + if ( ( edge == 0 ) || ( edge == 1 ) ) + { + // first shrink the edge back to the grow point + if ( from_y >= 0 ) + { + old_h = old_h - ( from_y - old_y ); + old_y = from_y; + } + + // now grow the edge as far as it can go + new_h = old_h + ( old_y - menubar_h ); + new_y = menubar_h; + + for ( int i = 0 ; i < c; ++i ) + { + ToolWindow *tw = GetToolWindow( i ); + Assert( tw ); + if ( ( !tw ) || ( tw == this ) ) + continue; + + // Get panel bounds + int x, y, w, h; + tw->GetBounds( x, y, w, h ); + + // grow it + if ( ( ( ( old_x > x ) && ( old_x < x + w ) ) + || ( ( old_x + old_w > x ) && ( old_x + old_w < x + w ) ) + || ( ( old_x <= x ) && old_x + old_w >= x + w )) + && ( ( old_y >= y + h ) && ( new_y < y + h ) ) ) + { + new_h = old_h + ( old_y - ( y + h ) ); + new_y = y + h; + } + } + old_h = new_h; + old_y = new_y; + } + + // grow right + if ( ( edge == 0 ) || ( edge == 2 ) ) + { + // first shrink the edge back to the grow point + if ( from_x >= 0 ) + { + old_w = from_x - old_x; + } + + // now grow the edge as far as it can go + new_w = sw - old_x; + + for ( int i = 0 ; i < c; ++i ) + { + ToolWindow *tw = GetToolWindow( i ); + Assert( tw ); + if ( ( !tw ) || ( tw == this ) ) + continue; + + // Get panel bounds + int x, y, w, h; + tw->GetBounds( x, y, w, h ); + + // grow it + if ( ( ( ( old_y > y ) && ( old_y < y + h ) ) + || ( ( old_y + old_h > y ) && ( old_y + old_h < y + h ) ) + || ( ( old_y <= y ) && old_y + old_h >= y + h )) + && ( ( old_x + old_w <= x ) && ( new_w > x - old_x ) ) ) + { + new_w = x - old_x; + } + } + old_w = new_w; + } + + // grow down + if ( ( edge == 0 ) || ( edge == 3 ) ) + { + // first shrink the edge back to the grow point + if ( from_y >= 0 ) + { + old_h = from_y - old_y; + } + + // now grow the edge as far as it can go + new_h = sh - old_y - status_h; + + for ( int i = 0 ; i < c; ++i ) + { + ToolWindow *tw = GetToolWindow( i ); + Assert( tw ); + if ( ( !tw ) || ( tw == this ) ) + continue; + + // Get panel bounds + int x, y, w, h; + tw->GetBounds( x, y, w, h ); + + // grow it + if ( ( ( ( old_x > x ) && ( old_x < x + w ) ) + || ( ( old_x + old_w > x ) && ( old_x + old_w < x + w ) ) + || ( ( old_x <= x ) && old_x + old_w >= x + w )) + && ( ( old_y + old_h <= y ) && ( new_h > y - old_y ) ) ) + { + new_h = y - old_y; + } + } + old_h = new_h; + } + + // grow left + if ( ( edge == 0 ) || ( edge == 4 ) ) + { + // first shrink the edge back to the grow point + if ( from_x >= 0 ) + { + old_w = old_w - ( from_x - old_x ); + old_x = from_x; + } + + // now grow the edge as far as it can go + new_w = old_w + old_x; + new_x = 0; + + for ( int i = 0 ; i < c; ++i ) + { + ToolWindow *tw = GetToolWindow( i ); + Assert( tw ); + if ( ( !tw ) || ( tw == this ) ) + continue; + + // Get panel bounds + int x, y, w, h; + tw->GetBounds( x, y, w, h ); + + // grow it + if ( ( ( ( old_y > y ) && ( old_y < y + h ) ) + || ( ( old_y + old_h > y ) && ( old_y + old_h < y + h ) ) + || ( ( old_y <= y ) && old_y + old_h >= y + h )) + && ( ( old_x >= x + w ) && ( new_x < x + w ) ) ) + { + new_w = old_w + ( old_x - ( x + w ) ); + new_x = x + w; + } + } + old_w = new_w; + old_x = new_x; + } + + // Set panel bounds + SetBounds( new_x, new_y, new_w, new_h ); + +} + +//----------------------------------------------------------------------------- +// Purpose: Calls Grow based on where the mouse is. +// over titlebar: grows all edges ( from mouse pos ) +// over edge grab area: grows just that edge +// over corner grab area: grows the two adjacent edges +// Input : +// Output : +//----------------------------------------------------------------------------- + +void ToolWindow::GrowFromClick() +{ + int mx, my; + input()->GetCursorPos( mx, my ); + + int esz, csz, brsz, ch; + esz = GetDraggerSize(); + csz = GetCornerSize(); + brsz = GetBottomRightSize(); + ch = GetCaptionHeight(); + + int x, y, w, h; + GetBounds( x, y, w, h ); + + // upper right + if ( ( mx > x+w-csz-1 ) && ( my < y+csz ) ) + { + Grow(1); + Grow(2); + } + // lower right (the big one) + else if ( ( mx > x+w-brsz-1 ) && ( my > y+h-brsz-1 ) ) + { + Grow(2); + Grow(3); + } + // lower left + else if ( ( mx < x+csz ) && ( my > y+h-csz-1 ) ) + { + Grow(3); + Grow(4); + } + // upper left + else if ( ( mx < x+csz ) && ( my < y+csz ) ) + { + Grow(4); + Grow(1); + } + // top edge + else if ( my < y+esz ) + { + Grow(1); + } + // right edge + else if ( mx > x+w-esz-1 ) + { + Grow(2); + } + // bottom edge + else if ( my > y+h-esz-1 ) + { + Grow(3); + } + // left edge + else if ( mx < x+esz ) + { + Grow(4); + } + // otherwise (if over the grab bar), grow all edges (from the clicked point) + else if ( my < y + ch ) + { + Grow(0, mx, my); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : +//----------------------------------------------------------------------------- + +void ToolWindow::OnMouseDoublePressed( MouseCode code ) +{ + GrowFromClick(); +} + +void ToolWindow::OnMousePressed( MouseCode code ) +{ + switch ( code ) + { + case MOUSE_MIDDLE: + GrowFromClick(); + break; + default: + BaseClass::OnMousePressed( code ); + } +} diff --git a/vgui2/vgui_controls/Tooltip.cpp b/vgui2/vgui_controls/Tooltip.cpp new file mode 100644 index 0000000..a16c542 --- /dev/null +++ b/vgui2/vgui_controls/Tooltip.cpp @@ -0,0 +1,416 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// This class is a message box that has two buttons, ok and cancel instead of +// just the ok button of a message box. We use a message box class for the ok button +// and implement another button here. +//=============================================================================// + +#include <math.h> +#define PROTECTED_THINGS_DISABLE + +#include <vgui/IInput.h> +#include <vgui/ISystem.h> +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <vgui/IVGui.h> +#include <vgui/IPanel.h> + +#include <vgui_controls/Tooltip.h> +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/Controls.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + + +//------------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------------ +static vgui::DHANDLE< TextEntry > s_TooltipWindow; +static int s_iTooltipWindowCount = 0; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +BaseTooltip::BaseTooltip(Panel *parent, const char *text) +{ + SetText(text); + + _displayOnOneLine = false; + _makeVisible = false; + _isDirty = false; + _enabled = true; + + _tooltipDelay = 500; // default delay for opening tooltips + _delay = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Reset the tooltip delay timer +//----------------------------------------------------------------------------- +void BaseTooltip::ResetDelay() +{ + _isDirty = true; + _delay = system()->GetTimeMillis() + _tooltipDelay; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the tooltip delay before a tooltip comes up. +//----------------------------------------------------------------------------- +void BaseTooltip::SetTooltipDelay( int tooltipDelay ) +{ + _tooltipDelay = tooltipDelay; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the tooltip delay before a tooltip comes up. +//----------------------------------------------------------------------------- +int BaseTooltip::GetTooltipDelay() +{ + return _tooltipDelay; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the tool tip to display on one line only +// Default is multiple lines. +//----------------------------------------------------------------------------- +void BaseTooltip::SetTooltipFormatToSingleLine() +{ + _displayOnOneLine = true; + _isDirty = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the tool tip to display on multiple lines. +//----------------------------------------------------------------------------- +void BaseTooltip::SetTooltipFormatToMultiLine() +{ + _displayOnOneLine = false; + _isDirty = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Display the tooltip +//----------------------------------------------------------------------------- +void BaseTooltip::ShowTooltip(Panel *currentPanel) +{ + _makeVisible = true; + + PerformLayout(); +} + +void BaseTooltip::SetEnabled( bool bState ) +{ + _enabled = bState; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BaseTooltip::ShouldLayout( void ) +{ + if (!_makeVisible) + return false; + + if (_delay > system()->GetTimeMillis()) + return false; + + // We only need to layout when we first become visible + if ( !_isDirty ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Display the tooltip +//----------------------------------------------------------------------------- +void BaseTooltip::HideTooltip() +{ + _makeVisible = false; + _isDirty = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the tooltip text +//----------------------------------------------------------------------------- +void BaseTooltip::SetText(const char *text) +{ + _isDirty = true; + + if (!text) + { + text = ""; + } + + if (m_Text.Size() > 0) + { + m_Text.RemoveAll(); + } + + for (unsigned int i = 0; i < strlen(text); i++) + { + m_Text.AddToTail(text[i]); + } + m_Text.AddToTail('\0'); + + if (s_TooltipWindow.Get() && m_pParent == s_TooltipWindow.Get()->GetParent()) + { + s_TooltipWindow->SetText(m_Text.Base()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the tooltip text +//----------------------------------------------------------------------------- +const char *BaseTooltip::GetText() +{ + return m_Text.Base(); +} + +//----------------------------------------------------------------------------- +// Purpose: Position the tool tip +//----------------------------------------------------------------------------- +void BaseTooltip::PositionWindow( Panel *pTipPanel ) +{ + int iTipW, iTipH; + pTipPanel->GetSize( iTipW, iTipH ); + + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + + int wide, tall; + surface()->GetScreenSize(wide, tall); + + int iParentX = 0, iParentY = 0; + if ( !pTipPanel->IsPopup() ) + { + pTipPanel->GetParent()->GetPos( iParentX, iParentY ); + pTipPanel->GetParent()->LocalToScreen( iParentX, iParentY ); + } + + cursorX -= iParentX; + cursorY -= iParentY; + + if (wide - iTipW > cursorX) + { + cursorY += 20; + // menu hanging right + if (tall - iTipH > cursorY) + { + // menu hanging down + pTipPanel->SetPos(cursorX, cursorY); + } + else + { + // menu hanging up + pTipPanel->SetPos(cursorX, cursorY - iTipH - 20); + } + } + else + { + // menu hanging left + if (tall - iTipH > cursorY) + { + // menu hanging down + pTipPanel->SetPos(cursorX - iTipW, cursorY); + } + else + { + // menu hanging up + pTipPanel->SetPos(cursorX - iTipW, cursorY - iTipH - 20 ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +TextTooltip::TextTooltip(Panel *parent, const char *text) : BaseTooltip( parent, text ) +{ + if (!s_TooltipWindow.Get()) + { + s_TooltipWindow = new TextEntry(NULL, "tooltip"); + + s_TooltipWindow->InvalidateLayout(false, true); + + // this bit of hackery is necessary because tooltips don't get ApplySchemeSettings called from their parents + IScheme *pScheme = scheme()->GetIScheme( s_TooltipWindow->GetScheme() ); + s_TooltipWindow->SetBgColor(s_TooltipWindow->GetSchemeColor("Tooltip.BgColor", s_TooltipWindow->GetBgColor(), pScheme)); + s_TooltipWindow->SetFgColor(s_TooltipWindow->GetSchemeColor("Tooltip.TextColor", s_TooltipWindow->GetFgColor(), pScheme)); + s_TooltipWindow->SetBorder(pScheme->GetBorder("ToolTipBorder")); + s_TooltipWindow->SetFont( pScheme->GetFont("DefaultSmall", s_TooltipWindow->IsProportional())); + } + s_iTooltipWindowCount++; + + // this line prevents the main window from losing focus + // when a tooltip pops up + s_TooltipWindow->MakePopup(false, true); + s_TooltipWindow->SetKeyBoardInputEnabled( false ); + s_TooltipWindow->SetMouseInputEnabled( false ); + + SetText(text); + s_TooltipWindow->SetText(m_Text.Base()); + s_TooltipWindow->SetEditable(false); + s_TooltipWindow->SetMultiline(true); + s_TooltipWindow->SetVisible(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +TextTooltip::~TextTooltip() +{ + if (--s_iTooltipWindowCount < 1) + { + if ( s_TooltipWindow.Get() ) + { + s_TooltipWindow->MarkForDeletion(); + } + s_TooltipWindow = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the tooltip text +//----------------------------------------------------------------------------- +void TextTooltip::SetText(const char *text) +{ + BaseTooltip::SetText( text ); + + if (s_TooltipWindow.Get()) + { + s_TooltipWindow->SetText(m_Text.Base()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: gets the font from the scheme +//----------------------------------------------------------------------------- +void TextTooltip::ApplySchemeSettings(IScheme *pScheme) +{ + if ( s_TooltipWindow ) + { + s_TooltipWindow->SetFont(pScheme->GetFont("DefaultSmall", s_TooltipWindow->IsProportional())); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Display the tooltip +//----------------------------------------------------------------------------- +void TextTooltip::ShowTooltip(Panel *currentPanel) +{ + if ( s_TooltipWindow.Get() ) + { + int nLen = s_TooltipWindow->GetTextLength(); + + if ( nLen <= 0 ) + { + // Empty tool tip, no need to show it + _makeVisible = false; + return; + } + + char *pBuf = (char*)_alloca( nLen+1 ); + s_TooltipWindow->GetText( pBuf, nLen+1 ); + Panel *pCurrentParent = s_TooltipWindow->GetParent(); + + _isDirty = _isDirty || ( pCurrentParent != currentPanel ); + s_TooltipWindow->SetText( m_Text.Base() ); + s_TooltipWindow->SetParent(currentPanel); + } + BaseTooltip::ShowTooltip( currentPanel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Display the tooltip +//----------------------------------------------------------------------------- +void TextTooltip::PerformLayout() +{ + if ( !ShouldLayout() ) + return; + // we're ready, just make us visible + if ( !s_TooltipWindow.Get() ) + return; + + _isDirty = false; + + s_TooltipWindow->SetVisible(true); + s_TooltipWindow->MakePopup( false, true ); + s_TooltipWindow->SetKeyBoardInputEnabled( false ); + s_TooltipWindow->SetMouseInputEnabled( false ); + + // relayout the textwindow immediately so that we know it's size + //m_pTextEntry->InvalidateLayout(true); + + SizeTextWindow(); + PositionWindow( s_TooltipWindow ); +} + +//----------------------------------------------------------------------------- +// Purpose: Size the text window so all the text fits inside it. +//----------------------------------------------------------------------------- +void TextTooltip::SizeTextWindow() +{ + if ( !s_TooltipWindow.Get() ) + return; + + if (_displayOnOneLine) + { + // We want the tool tip to be one line + s_TooltipWindow->SetMultiline(false); + s_TooltipWindow->SetToFullWidth(); + } + else + { + // We want the tool tip to be one line + s_TooltipWindow->SetMultiline(false); + s_TooltipWindow->SetToFullWidth(); + int wide, tall; + s_TooltipWindow->GetSize( wide, tall ); + double newWide = sqrt( (2.0/1) * wide * tall ); + double newTall = (1/2) * newWide; + s_TooltipWindow->SetMultiline(true); + s_TooltipWindow->SetSize((int)newWide, (int)newTall ); + s_TooltipWindow->SetToFullHeight(); + + s_TooltipWindow->GetSize( wide, tall ); + + if (( wide < 100 ) && ( s_TooltipWindow->GetNumLines() == 2) ) + { + s_TooltipWindow->SetMultiline(false); + s_TooltipWindow->SetToFullWidth(); + } + else + { + + while ( (float)wide/(float)tall < 2 ) + { + s_TooltipWindow->SetSize( wide + 1, tall ); + s_TooltipWindow->SetToFullHeight(); + s_TooltipWindow->GetSize( wide, tall ); + } + } + s_TooltipWindow->GetSize( wide, tall ); + // ivgui()->DPrintf("End Ratio: %f\n", (float)wide/(float)tall); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Display the tooltip +//----------------------------------------------------------------------------- +void TextTooltip::HideTooltip() +{ + if ( s_TooltipWindow.Get() ) + { + s_TooltipWindow->SetVisible(false); + } + + BaseTooltip::HideTooltip(); +} + diff --git a/vgui2/vgui_controls/TreeView.cpp b/vgui2/vgui_controls/TreeView.cpp new file mode 100644 index 0000000..b7ad4d3 --- /dev/null +++ b/vgui2/vgui_controls/TreeView.cpp @@ -0,0 +1,2854 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <assert.h> + +#define PROTECTED_THINGS_DISABLE + +#include <vgui/Cursor.h> +#include <vgui/IScheme.h> +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include <vgui/IVGui.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> + +#include <vgui_controls/TreeView.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/ImageList.h> +#include <vgui_controls/ImagePanel.h> + +#include "tier1/utlstring.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +using namespace vgui; +enum +{ + WINDOW_BORDER_WIDTH=2 // the width of the window's border +}; + +#define TREE_INDENT_AMOUNT 20 + +namespace vgui +{ + +//----------------------------------------------------------------------------- +// Purpose: Displays an editable text field for the text control +//----------------------------------------------------------------------------- +class TreeNodeText : public TextEntry +{ + DECLARE_CLASS_SIMPLE( TreeNodeText, TextEntry ); + +public: + TreeNodeText(Panel *parent, const char *panelName, TreeView *tree) : BaseClass(parent, panelName), m_pTree( tree ) + { + m_bEditingInPlace = false; + m_bLabelEditingAllowed = false; + SetDragEnabled( false ); + SetDropEnabled( false ); + AddActionSignalTarget( this ); + m_bArmForEditing = false; + m_bWaitingForRelease = false; + m_lArmingTime = 0L; + SetAllowKeyBindingChainToParent( true ); + } + + MESSAGE_FUNC( OnTextChanged, "TextChanged" ) + { + GetParent()->InvalidateLayout(); + } + + bool IsKeyRebound( KeyCode code, int modifiers ) + { + // If in editing mode, don't try and chain keypresses + if ( m_bEditingInPlace ) + { + return false; + } + + return BaseClass::IsKeyRebound( code, modifiers ); + } + + virtual void PaintBackground() + { + BaseClass::PaintBackground(); + + if ( !m_bLabelEditingAllowed ) + return; + + if ( !m_bEditingInPlace ) + return; + + int w, h; + GetSize( w, h ); + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawOutlinedRect( 0, 0, w, h ); + } + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + TextEntry::ApplySchemeSettings(pScheme); + SetBorder(NULL); + SetCursor(dc_arrow); + } + + virtual void OnKeyCodeTyped(KeyCode code) + { + if ( m_bEditingInPlace ) + { + if ( code == KEY_ENTER ) + { + FinishEditingInPlace(); + } + else if ( code == KEY_ESCAPE ) + { + FinishEditingInPlace( true ); + } + else + { + BaseClass::OnKeyCodeTyped( code ); + } + return; + } + else if ( code == KEY_ENTER && IsLabelEditingAllowed() ) + { + EnterEditingInPlace(); + } + else + { + // let parent deal with it (don't chain back to TextEntry) + CallParentFunction(new KeyValues("KeyCodeTyped", "code", code)); + } + } + +#define CLICK_TO_EDIT_DELAY_MSEC 500 + + virtual void OnTick() + { + BaseClass::OnTick(); + if ( m_bArmForEditing ) + { + long msecSinceArming = system()->GetTimeMillis() - m_lArmingTime; + + if ( msecSinceArming > CLICK_TO_EDIT_DELAY_MSEC ) + { + m_bArmForEditing = false; + m_bWaitingForRelease = false; + ivgui()->RemoveTickSignal( GetVPanel() ); + EnterEditingInPlace(); + } + } + } + + virtual void OnMouseReleased( MouseCode code ) + { + if ( m_bEditingInPlace ) + { + BaseClass::OnMouseReleased( code ); + return; + } + else + { + if ( m_bWaitingForRelease && !IsBeingDragged() ) + { + m_bArmForEditing = true; + m_bWaitingForRelease = false; + m_lArmingTime = system()->GetTimeMillis(); + ivgui()->AddTickSignal( GetVPanel() ); + } + else + { + m_bWaitingForRelease = false; + } + } + + // let parent deal with it + CallParentFunction(new KeyValues("MouseReleased", "code", code)); + } + + virtual void OnCursorMoved( int x, int y ) + { + // let parent deal with it + CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); + } + + virtual void OnMousePressed(MouseCode code) + { + if ( m_bEditingInPlace ) + { + BaseClass::OnMousePressed( code ); + return; + } + else + { + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + + // make sure there is only one item selected + // before "WaitingForRelease" which leads to label editing. + CUtlVector< int > list; + m_pTree->GetSelectedItems( list ); + bool bIsOnlyOneItemSelected = ( list.Count() == 1 ); + + if ( !shift && + !ctrl && + !m_bArmForEditing && + IsLabelEditingAllowed() && + bIsOnlyOneItemSelected && + IsTextFullySelected() && + !IsBeingDragged() ) + { + m_bWaitingForRelease = true; + } + } + + // let parent deal with it + CallParentFunction(new KeyValues("MousePressed", "code", code)); + } + + void SetLabelEditingAllowed( bool state ) + { + m_bLabelEditingAllowed = state; + } + + bool IsLabelEditingAllowed() + { + return m_bLabelEditingAllowed; + } + + virtual void OnMouseDoublePressed(MouseCode code) + { + // Once we are editing, double pressing shouldn't chain up + if ( m_bEditingInPlace ) + { + BaseClass::OnMouseDoublePressed( code ); + return; + } + + if ( m_bArmForEditing ) + { + m_bArmForEditing = false; + m_bWaitingForRelease = false; + ivgui()->RemoveTickSignal( GetVPanel() ); + } + + CallParentFunction(new KeyValues("MouseDoublePressed", "code", code)); + } + + void EnterEditingInPlace() + { + if ( m_bEditingInPlace ) + return; + + m_bEditingInPlace = true; + char buf[ 1024 ]; + GetText( buf, sizeof( buf ) ); + m_OriginalText = buf; + SetCursor(dc_ibeam); + SetEditable( true ); + SelectNone(); + GotoTextEnd(); + RequestFocus(); + SelectAllText(false); + m_pTree->SetLabelBeingEdited( true ); + } + + void FinishEditingInPlace( bool revert = false ) + { + if ( !m_bEditingInPlace ) + return; + + m_pTree->SetLabelBeingEdited( false ); + SetEditable( false ); + SetCursor(dc_arrow); + m_bEditingInPlace = false; + char buf[ 1024 ]; + GetText( buf, sizeof( buf ) ); + + // Not actually changed... + if ( !Q_strcmp( buf, m_OriginalText.Get() ) ) + return; + + if ( revert ) + { + SetText( m_OriginalText.Get() ); + GetParent()->InvalidateLayout(); + } + else + { + KeyValues *kv = new KeyValues( "LabelChanged", "original", m_OriginalText.Get(), "changed", buf ); + PostActionSignal( kv ); + } + } + + virtual void OnKillFocus() + { + BaseClass::OnKillFocus(); + + FinishEditingInPlace(); + } + + virtual void OnMouseWheeled(int delta) + { + if ( m_bEditingInPlace ) + { + BaseClass::OnMouseWheeled( delta ); + return; + } + + CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); + } + // editable - cursor normal, and ability to edit text + + bool IsBeingEdited() const + { + return m_bEditingInPlace; + } + +private: + + bool m_bEditingInPlace; + CUtlString m_OriginalText; + bool m_bLabelEditingAllowed; + + bool m_bArmForEditing; + bool m_bWaitingForRelease; + long m_lArmingTime; + TreeView *m_pTree; +}; + +//----------------------------------------------------------------------------- +// Purpose: icon for the tree node (folder icon, file icon, etc.) +//----------------------------------------------------------------------------- +class TreeNodeImage : public ImagePanel +{ +public: + TreeNodeImage(Panel *parent, const char *name) : ImagePanel(parent, name) + { + SetBlockDragChaining( true ); + } + + //!! this could possibly be changed to just disallow mouse input on the image panel + virtual void OnMousePressed(MouseCode code) + { + // let parent deal with it + CallParentFunction(new KeyValues("MousePressed", "code", code)); + } + + virtual void OnMouseDoublePressed(MouseCode code) + { + // let parent deal with it + CallParentFunction(new KeyValues("MouseDoublePressed", "code", code)); + } + + virtual void OnMouseWheeled(int delta) + { + // let parent deal with it + CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); + } + + virtual void OnCursorMoved( int x, int y ) + { + // let parent deal with it + CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Scrollable area of the tree control, holds the tree itself only +//----------------------------------------------------------------------------- +class TreeViewSubPanel : public Panel +{ +public: + TreeViewSubPanel(Panel *parent) : Panel(parent) {} + + virtual void ApplySchemeSettings(IScheme *pScheme) + { + Panel::ApplySchemeSettings(pScheme); + + SetBorder(NULL); + } + + virtual void OnMouseWheeled(int delta) + { + // let parent deal with it + CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); + } + virtual void OnMousePressed(MouseCode code) + { + // let parent deal with it + CallParentFunction(new KeyValues("MousePressed", "code", code)); + } + virtual void OnMouseDoublePressed(MouseCode code) + { + // let parent deal with it + CallParentFunction(new KeyValues("MouseDoublePressed", "code", code)); + } + + virtual void OnCursorMoved( int x, int y ) + { + // let parent deal with it + CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: A single entry in the tree +//----------------------------------------------------------------------------- +class TreeNode : public Panel +{ + DECLARE_CLASS_SIMPLE( TreeNode, Panel ); + +public: + TreeNode(Panel *parent, TreeView *pTreeView); + ~TreeNode(); + void SetText(const char *pszText); + void SetFont(HFont font); + void SetKeyValues(KeyValues *data); + bool IsSelected(); + // currently unused, could be re-used if necessary +// bool IsInFocus(); + virtual void PaintBackground(); + virtual void PerformLayout(); + TreeNode *GetParentNode(); + int GetChildrenCount(); + void ClearChildren(); + int ComputeInsertionPosition( TreeNode *pChild ); + int FindChild( TreeNode *pChild ); + void AddChild(TreeNode *pChild); + void SetNodeExpanded(bool bExpanded); + bool IsExpanded(); + int CountVisibleNodes(); + void CalculateVisibleMaxWidth(); + void OnChildWidthChange(); + int GetMaxChildrenWidth(); + int GetVisibleMaxWidth(); + int GetDepth(); + bool HasParent(TreeNode *pTreeNode); + bool IsBeingDisplayed(); + virtual void SetVisible(bool state); + virtual void Paint(); + virtual void ApplySchemeSettings(IScheme *pScheme); + virtual void SetBgColor( Color color ); + virtual void SetFgColor( Color color ); + virtual void OnSetFocus(); + void SelectPrevChild(TreeNode *pCurrentChild); + void SelectNextChild(TreeNode *pCurrentChild); + + int GetPrevChildItemIndex( TreeNode *pCurrentChild ); + int GetNextChildItemIndex( TreeNode *pCurrentChild ); + + virtual void ClosePreviousParents( TreeNode *pPreviousParent ); + virtual void StepInto( bool bClosePrevious=true ); + virtual void StepOut( bool bClosePrevious=true ); + virtual void StepOver( bool bClosePrevious=true ); + virtual void OnKeyCodeTyped(KeyCode code); + virtual void OnMouseWheeled(int delta); + virtual void OnMousePressed( MouseCode code); + virtual void OnMouseReleased( MouseCode code); + virtual void OnCursorMoved( int x, int y ); + virtual bool IsDragEnabled() const; + void PositionAndSetVisibleNodes(int &nStart, int &nCount, int x, int &y); + + // counts items above this item including itself + int CountVisibleIndex(); + + virtual void OnCreateDragData( KeyValues *msg ); + // For handling multiple selections... + virtual void OnGetAdditionalDragPanels( CUtlVector< Panel * >& dragabbles ); + virtual void OnMouseDoublePressed( MouseCode code ); + TreeNode *FindItemUnderMouse( int &nStart, int& nCount, int x, int &y, int mx, int my ); + MESSAGE_FUNC_PARAMS( OnLabelChanged, "LabelChanged", data ); + void EditLabel(); + void SetLabelEditingAllowed( bool state ); + bool IsLabelEditingAllowed() const; + + virtual bool IsDroppable( CUtlVector< KeyValues * >& msglist ); + virtual void OnPanelDropped( CUtlVector< KeyValues * >& msglist ); + virtual HCursor GetDropCursor( CUtlVector< KeyValues * >& msglist ); + virtual bool GetDropContextMenu( Menu *menu, CUtlVector< KeyValues * >& msglist ); + + void FindNodesInRange( CUtlVector< TreeNode * >& list, int startIndex, int endIndex ); + + void RemoveChildren(); + + void SetSelectionTextColor( const Color& clr ); + void SetSelectionBgColor( const Color& clr ); + void SetSelectionUnfocusedBgColor( const Color& clr ); +public: + int m_ItemIndex; + int m_ParentIndex; + KeyValues *m_pData; + CUtlVector<TreeNode *> m_Children; + bool m_bExpand; + +private: + + void FindNodesInRange_R( CUtlVector< TreeNode * >& list, bool& finished, bool& foundStart, int startIndex, int endIndex ); + + int m_iNodeWidth; + int m_iMaxVisibleWidth; + + TreeNodeText *m_pText; + TextImage *m_pExpandImage; + TreeNodeImage *m_pImagePanel; + + bool m_bExpandableWithoutChildren; + + TreeView *m_pTreeView; + int m_nClickedItem; + bool m_bClickedSelected; +}; + + +TreeNode::TreeNode(Panel *parent, TreeView *pTreeView) : + BaseClass(parent, "TreeNode" ), + m_nClickedItem( 0 ), + m_bClickedSelected( false ) +{ + m_pData = NULL; + m_pTreeView = pTreeView; + m_ItemIndex = -1; + m_iNodeWidth = 0; + m_iMaxVisibleWidth = 0; + + m_pExpandImage = new TextImage("+"); + m_pExpandImage->SetPos(3, 1); + + m_pImagePanel = new TreeNodeImage(this, "TreeImage"); + m_pImagePanel->SetPos(TREE_INDENT_AMOUNT, 3); + + m_pText = new TreeNodeText(this, "TreeNodeText",pTreeView); + m_pText->SetMultiline(false); + m_pText->SetEditable(false); + m_pText->SetPos(TREE_INDENT_AMOUNT*2, 0); + m_pText->AddActionSignalTarget( this ); + + m_bExpand = false; + m_bExpandableWithoutChildren = false; +} + +TreeNode::~TreeNode() +{ + delete m_pExpandImage; + if ( m_pData ) + { + m_pData->deleteThis(); + } +} + +void TreeNode::SetText(const char *pszText) +{ + m_pText->SetText(pszText); + InvalidateLayout(); +} + +void TreeNode::SetLabelEditingAllowed( bool state ) +{ + Assert( m_pTreeView->IsLabelEditingAllowed() ); + m_pText->SetLabelEditingAllowed( state ); +} + +bool TreeNode::IsLabelEditingAllowed() const +{ + return m_pText->IsLabelEditingAllowed(); +} + +bool TreeNode::GetDropContextMenu( Menu *menu, CUtlVector< KeyValues * >& msglist ) +{ + return m_pTreeView->GetItemDropContextMenu( m_ItemIndex, menu, msglist ); +} + +bool TreeNode::IsDroppable( CUtlVector< KeyValues * >& msglist ) +{ + return m_pTreeView->IsItemDroppable( m_ItemIndex, msglist ); +} + +void TreeNode::OnPanelDropped( CUtlVector< KeyValues * >& msglist ) +{ + m_pTreeView->OnItemDropped( m_ItemIndex, msglist ); +} + +HCursor TreeNode::GetDropCursor( CUtlVector< KeyValues * >& msglist ) +{ + return m_pTreeView->GetItemDropCursor( m_ItemIndex, msglist ); +} + + +void TreeNode::OnCreateDragData( KeyValues *msg ) +{ + // make sure the dragged item appears selected, + // on the off chance it appears deselected by a cntl mousedown + m_pTreeView->AddSelectedItem( m_ItemIndex, false ); + + m_pTreeView->GenerateDragDataForItem( m_ItemIndex, msg ); +} + +// For handling multiple selections... +void TreeNode::OnGetAdditionalDragPanels( CUtlVector< Panel * >& dragabbles ) +{ + CUtlVector< int > list; + m_pTreeView->GetSelectedItems( list ); + int c = list.Count(); + // walk this in reverse order so that panels are in order of selection + // even though GetSelectedItems returns items in reverse selection order + for ( int i = c - 1; i >= 0; --i ) + { + int itemIndex = list[ i ]; + // Skip self + if ( itemIndex == m_ItemIndex ) + continue; + + dragabbles.AddToTail( ( Panel * )m_pTreeView->GetItem( itemIndex ) ); + } +} + +void TreeNode::OnLabelChanged( KeyValues *data ) +{ + char const *oldString = data->GetString( "original" ); + char const *newString = data->GetString( "changed" ); + if ( m_pTreeView->IsLabelEditingAllowed() ) + { + m_pTreeView->OnLabelChanged( m_ItemIndex, oldString, newString ); + } +} + +void TreeNode::EditLabel() +{ + if ( m_pText->IsLabelEditingAllowed() && + !m_pText->IsBeingEdited() ) + { + m_pText->EnterEditingInPlace(); + } +} + +void TreeNode::SetFont(HFont font) +{ + Assert( font ); + if ( !font ) + return; + + m_pText->SetFont(font); + m_pExpandImage->SetFont(font); + InvalidateLayout(); + int i; + for (i=0;i<GetChildrenCount();i++) + { + m_Children[i]->SetFont(font); + } +} + +void TreeNode::SetKeyValues(KeyValues *data) +{ + if ( m_pData != data ) + { + if (m_pData) + { + m_pData->deleteThis(); + } + + m_pData = data->MakeCopy(); + } + + // set text + m_pText->SetText(data->GetString("Text", "")); + m_bExpandableWithoutChildren = data->GetInt("Expand"); + InvalidateLayout(); +} + +bool TreeNode::IsSelected() +{ + return m_pTreeView->IsItemSelected( m_ItemIndex ); +} + +void TreeNode::PaintBackground() +{ + if ( !m_pText->IsBeingEdited() ) + { + // setup panel drawing + if ( IsSelected() ) + { + m_pText->SelectAllText(false); + } + else + { + m_pText->SelectNoText(); + } + } + + BaseClass::PaintBackground(); +} + + +// currently unused, could be re-used if necessary +/* +bool TreeNode::IsInFocus() +{ + // check if our parent or one of it's children has focus + VPANEL focus = input()->GetFocus(); + return (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent()))); +} +*/ + +void TreeNode::PerformLayout() +{ + BaseClass::PerformLayout(); + + int width = 0; + if (m_pData->GetInt("SelectedImage", 0) == 0 && + m_pData->GetInt("Image", 0) == 0) + { + width = TREE_INDENT_AMOUNT; + } + else + { + width = TREE_INDENT_AMOUNT * 2; + } + + m_pText->SetPos(width, 0); + + int contentWide, contentTall; + m_pText->SetToFullWidth(); + m_pText->GetSize(contentWide, contentTall); + contentWide += 10; + m_pText->SetSize( contentWide, m_pTreeView->GetRowHeight() ); + width += contentWide; + SetSize(width, m_pTreeView->GetRowHeight()); + + m_iNodeWidth = width; + CalculateVisibleMaxWidth(); +} + +TreeNode *TreeNode::GetParentNode() +{ + if (m_pTreeView->m_NodeList.IsValidIndex(m_ParentIndex)) + { + return m_pTreeView->m_NodeList[m_ParentIndex]; + } + return NULL; +} + +int TreeNode::GetChildrenCount() +{ + return m_Children.Count(); +} + +int TreeNode::ComputeInsertionPosition( TreeNode *pChild ) +{ + if ( !m_pTreeView->m_pSortFunc ) + { + return GetChildrenCount() - 1; + } + + int start = 0, end = GetChildrenCount() - 1; + while (start <= end) + { + int mid = (start + end) >> 1; + if ( m_pTreeView->m_pSortFunc( m_Children[mid]->m_pData, pChild->m_pData ) ) + { + start = mid + 1; + } + else if ( m_pTreeView->m_pSortFunc( pChild->m_pData, m_Children[mid]->m_pData ) ) + { + end = mid - 1; + } + else + { + return mid; + } + } + return end; +} + +int TreeNode::FindChild( TreeNode *pChild ) +{ + if ( !m_pTreeView->m_pSortFunc ) + { + AssertMsg( 0, "This code has never been tested. Is it correct?" ); + for ( int i = 0; i < GetChildrenCount(); ++i ) + { + if ( m_Children[i] == pChild ) + return i; + } + return -1; + } + + // Find the first entry <= to the child + int start = 0, end = GetChildrenCount() - 1; + while (start <= end) + { + int mid = (start + end) >> 1; + + if ( m_Children[mid] == pChild ) + return mid; + + if ( m_pTreeView->m_pSortFunc( m_Children[mid]->m_pData, pChild->m_pData ) ) + { + start = mid + 1; + } + else + { + end = mid - 1; + } + } + + int nMax = GetChildrenCount(); + while( end < nMax ) + { + // Stop when we reach a child that has a different value + if ( m_pTreeView->m_pSortFunc( pChild->m_pData, m_Children[end]->m_pData ) ) + return -1; + + if ( m_Children[end] == pChild ) + return end; + + ++end; + } + + return -1; +} + +void TreeNode::AddChild(TreeNode *pChild) +{ + int i = ComputeInsertionPosition( pChild ); + m_Children.InsertAfter( i, pChild ); +} + +void TreeNode::SetNodeExpanded(bool bExpanded) +{ + m_bExpand = bExpanded; + + if (m_bExpand) + { + // see if we have any child nodes + if (GetChildrenCount() < 1) + { + // we need to get our children from the control + m_pTreeView->GenerateChildrenOfNode(m_ItemIndex); + + // if we still don't have any children, then hide the expand button + if (GetChildrenCount() < 1) + { + m_bExpand = false; + m_bExpandableWithoutChildren = false; + m_pTreeView->InvalidateLayout(); + return; + } + } + + m_pExpandImage->SetText("-"); + } + else + { + m_pExpandImage->SetText("+"); + + if ( m_bExpandableWithoutChildren && GetChildrenCount() > 0 ) + { + m_pTreeView->RemoveChildrenOfNode( m_ItemIndex ); + } + + // check if we've closed down on one of our children, if so, we get the focus + int selectedItem = m_pTreeView->GetFirstSelectedItem(); + if (selectedItem != -1 && m_pTreeView->m_NodeList[selectedItem]->HasParent(this)) + { + m_pTreeView->AddSelectedItem( m_ItemIndex, true ); + } + } + CalculateVisibleMaxWidth(); + m_pTreeView->InvalidateLayout(); +} + +bool TreeNode::IsExpanded() +{ + return m_bExpand; +} + +int TreeNode::CountVisibleNodes() +{ + int count = 1; // count myself + if (m_bExpand) + { + int i; + for (i=0;i<m_Children.Count();i++) + { + count += m_Children[i]->CountVisibleNodes(); + } + } + return count; +} + +void TreeNode::CalculateVisibleMaxWidth() +{ + int width; + if (m_bExpand) + { + int childMaxWidth = GetMaxChildrenWidth(); + childMaxWidth += TREE_INDENT_AMOUNT; + + width = max(childMaxWidth, m_iNodeWidth); + } + else + { + width = m_iNodeWidth; + } + if (width != m_iMaxVisibleWidth) + { + m_iMaxVisibleWidth = width; + if (GetParentNode()) + { + GetParentNode()->OnChildWidthChange(); + } + else + { + m_pTreeView->InvalidateLayout(); + } + } +} + +void TreeNode::OnChildWidthChange() +{ + CalculateVisibleMaxWidth(); +} + +int TreeNode::GetMaxChildrenWidth() +{ + int maxWidth = 0; + int i; + for (i=0;i<GetChildrenCount();i++) + { + int childWidth = m_Children[i]->GetVisibleMaxWidth(); + if (childWidth > maxWidth) + { + maxWidth = childWidth; + } + } + return maxWidth; +} + +int TreeNode::GetVisibleMaxWidth() +{ + return m_iMaxVisibleWidth; +} + +int TreeNode::GetDepth() +{ + int depth = 0; + TreeNode *pParent = GetParentNode(); + while (pParent) + { + depth++; + pParent = pParent->GetParentNode(); + } + return depth; +} + +bool TreeNode::HasParent(TreeNode *pTreeNode) +{ + TreeNode *pParent = GetParentNode(); + while (pParent) + { + if (pParent == pTreeNode) + return true; + pParent = pParent->GetParentNode(); + } + return false; +} + +bool TreeNode::IsBeingDisplayed() +{ + TreeNode *pParent = GetParentNode(); + while (pParent) + { + // our parents aren't showing us + if (!pParent->m_bExpand) + return false; + + pParent = pParent->GetParentNode(); + } + return true; +} + +void TreeNode::SetVisible(bool state) +{ + BaseClass::SetVisible(state); + + bool bChildrenVisible = state && m_bExpand; + int i; + for (i=0;i<GetChildrenCount();i++) + { + m_Children[i]->SetVisible(bChildrenVisible); + } +} + +void TreeNode::Paint() +{ + if (GetChildrenCount() > 0 || m_bExpandableWithoutChildren) + { + m_pExpandImage->Paint(); + } + + // set image + int imageIndex = 0; + if (IsSelected()) + { + imageIndex = m_pData->GetInt("SelectedImage", 0); + } + else + { + imageIndex = m_pData->GetInt("Image", 0); + } + + if (imageIndex) + { + IImage *pImage = m_pTreeView->GetImage(imageIndex); + if (pImage) + { + m_pImagePanel->SetImage(pImage); + } + m_pImagePanel->Paint(); + } + + m_pText->Paint(); +} + +void TreeNode::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetBorder( NULL ); + SetFgColor( m_pTreeView->GetFgColor() ); + SetBgColor( m_pTreeView->GetBgColor() ); + SetFont( m_pTreeView->GetFont() ); +} + +void TreeNode::SetSelectionTextColor( const Color& clr ) +{ + if ( m_pText ) + { + m_pText->SetSelectionTextColor( clr ); + } +} + +void TreeNode::SetSelectionBgColor( const Color& clr ) +{ + if ( m_pText ) + { + m_pText->SetSelectionBgColor( clr ); + } +} + +void TreeNode::SetSelectionUnfocusedBgColor( const Color& clr ) +{ + if ( m_pText ) + { + m_pText->SetSelectionUnfocusedBgColor( clr ); + } +} + +void TreeNode::SetBgColor( Color color ) +{ + BaseClass::SetBgColor( color ); + if ( m_pText ) + { + m_pText->SetBgColor( color ); + } + +} + +void TreeNode::SetFgColor( Color color ) +{ + BaseClass::SetFgColor( color ); + if ( m_pText ) + { + m_pText->SetFgColor( color ); + } +} + +void TreeNode::OnSetFocus() +{ + m_pText->RequestFocus(); +} + +int TreeNode::GetPrevChildItemIndex( TreeNode *pCurrentChild ) +{ + int i; + for (i=0;i<GetChildrenCount();i++) + { + if ( m_Children[i] == pCurrentChild ) + { + if ( i <= 0 ) + return -1; + + TreeNode *pChild = m_Children[i-1]; + return pChild->m_ItemIndex; + } + } + return -1; +} + +int TreeNode::GetNextChildItemIndex( TreeNode *pCurrentChild ) +{ + int i; + for (i=0;i<GetChildrenCount();i++) + { + if ( m_Children[i] == pCurrentChild ) + { + if ( i >= GetChildrenCount() - 1 ) + return -1; + + TreeNode *pChild = m_Children[i+1]; + return pChild->m_ItemIndex; + } + } + return -1; +} + +void TreeNode::SelectPrevChild(TreeNode *pCurrentChild) +{ + int i; + for (i=0;i<GetChildrenCount();i++) + { + if (m_Children[i] == pCurrentChild) + break; + } + + // this shouldn't happen + if (i == GetChildrenCount()) + { + Assert(0); + return; + } + + // were we on the first child? + if (i == 0) + { + // if so, then we take over! + m_pTreeView->AddSelectedItem( m_ItemIndex, true ); + } + else + { + // see if we need to find a grandchild of the previous sibling + TreeNode *pChild = m_Children[i-1]; + + // if this child is expanded with children, then we have to find the last child + while (pChild->m_bExpand && pChild->GetChildrenCount()>0) + { + // find the last child + pChild = pChild->m_Children[pChild->GetChildrenCount()-1]; + } + m_pTreeView->AddSelectedItem( pChild->m_ItemIndex, true ); + } +} + +void TreeNode::SelectNextChild(TreeNode *pCurrentChild) +{ + int i; + for (i=0;i<GetChildrenCount();i++) + { + if (m_Children[i] == pCurrentChild) + break; + } + + // this shouldn't happen + if (i == GetChildrenCount()) + { + Assert(0); + return; + } + + // were we on the last child? + if (i == GetChildrenCount() - 1) + { + // tell our parent to get the next child + if (GetParentNode()) + { + GetParentNode()->SelectNextChild(this); + } + } + else + { + m_pTreeView->AddSelectedItem( m_Children[i+1]->m_ItemIndex, true ); + } +} + +void TreeNode::ClosePreviousParents( TreeNode *pPreviousParent ) +{ + // close up all the open nodes we've just stepped out of. + CUtlVector< int > selected; + m_pTreeView->GetSelectedItems( selected ); + if ( selected.Count() == 0 ) + { + Assert( 0 ); + return; + } + + // Most recently clicked item + TreeNode *selectedItem = m_pTreeView->GetItem( selected[ 0 ] ); + TreeNode *pNewParent = selectedItem->GetParentNode(); + if ( pPreviousParent && pNewParent ) + { + while ( pPreviousParent->m_ItemIndex > pNewParent->m_ItemIndex ) + { + pPreviousParent->SetNodeExpanded(false); + pPreviousParent = pPreviousParent->GetParentNode(); + } + } +} + +void TreeNode::StepInto( bool bClosePrevious ) +{ + if ( !m_bExpand ) + { + SetNodeExpanded(true); + } + + if ( ( GetChildrenCount() > 0 ) && m_bExpand ) + { + m_pTreeView->AddSelectedItem( m_Children[0]->m_ItemIndex, true ); + } + else if ( GetParentNode() ) + { + TreeNode *pParent = GetParentNode(); + pParent->SelectNextChild(this); + + if ( bClosePrevious ) + { + ClosePreviousParents( pParent ); + } + } +} + +void TreeNode::StepOut( bool bClosePrevious ) +{ + TreeNode *pParent = GetParentNode(); + if ( pParent ) + { + m_pTreeView->AddSelectedItem( pParent->m_ItemIndex, true ); + if ( pParent->GetParentNode() ) + { + pParent->GetParentNode()->SelectNextChild(pParent); + } + if ( bClosePrevious ) + { + ClosePreviousParents( pParent ); + } + else + { + pParent->SetNodeExpanded(true); + } + } +} + +void TreeNode::StepOver( bool bClosePrevious ) +{ + TreeNode *pParent = GetParentNode(); + if ( pParent ) + { + GetParentNode()->SelectNextChild(this); + if ( bClosePrevious ) + { + ClosePreviousParents( pParent ); + } + } +} + +void TreeNode::OnKeyCodeTyped(KeyCode code) +{ + switch (code) + { + case KEY_LEFT: + { + if (m_bExpand && GetChildrenCount() > 0) + { + SetNodeExpanded(false); + } + else + { + if (GetParentNode()) + { + m_pTreeView->AddSelectedItem( GetParentNode()->m_ItemIndex, true ); + } + } + break; + } + case KEY_RIGHT: + { + if (!m_bExpand) + { + SetNodeExpanded(true); + } + else if (GetChildrenCount() > 0) + { + m_pTreeView->AddSelectedItem( m_Children[0]->m_ItemIndex, true ); + } + break; + } + case KEY_UP: + { + if (GetParentNode()) + { + GetParentNode()->SelectPrevChild(this); + } + break; + } + case KEY_DOWN: + { + if (GetChildrenCount() > 0 && m_bExpand) + { + m_pTreeView->AddSelectedItem( m_Children[0]->m_ItemIndex, true ); + } + else if (GetParentNode()) + { + GetParentNode()->SelectNextChild(this); + } + break; + } + case KEY_SPACE: + { + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + if ( shift ) + { + StepOut( !ctrl ); + } + else if ( alt ) + { + StepOver( !ctrl ); + } + else + { + StepInto( !ctrl ); + } + break; + } + case KEY_I: + { + StepInto(); + break; + } + case KEY_U: + { + StepOut(); + break; + } + case KEY_O: + { + StepOver(); + break; + } + case KEY_ESCAPE: + { + if ( m_pTreeView->GetSelectedItemCount() > 0 ) + { + m_pTreeView->ClearSelection(); + } + else + { + BaseClass::OnKeyCodeTyped(code); + } + } + break; + case KEY_A: + { + bool ctrldown = input()->IsKeyDown( KEY_LCONTROL ) || input()->IsKeyDown( KEY_RCONTROL ); + if ( ctrldown ) + { + m_pTreeView->SelectAll(); + } + else + { + BaseClass::OnKeyCodeTyped(code); + } + } + break; + default: + BaseClass::OnKeyCodeTyped(code); + return; + } +} + +void TreeNode::OnMouseWheeled(int delta) +{ + CallParentFunction(new KeyValues("MouseWheeled", "delta", delta)); +} + +void TreeNode::OnMouseDoublePressed( MouseCode code ) +{ + int x, y; + input()->GetCursorPos(x, y); + + if (code == MOUSE_LEFT) + { + ScreenToLocal(x, y); + if (x > TREE_INDENT_AMOUNT) + { + SetNodeExpanded(!m_bExpand); + } + } +} + +bool TreeNode::IsDragEnabled() const +{ + int x, y; + input()->GetCursorPos(x, y); + ((TreeNode *)this)->ScreenToLocal(x, y); + if ( x < TREE_INDENT_AMOUNT ) + return false; + + return BaseClass::IsDragEnabled(); +} + +void TreeNode::OnMouseReleased(MouseCode code) +{ + BaseClass::OnMouseReleased( code ); + + if ( input()->GetMouseCapture() == GetVPanel() ) + { + input()->SetMouseCapture( NULL ); + return; + } + int x, y; + input()->GetCursorPos(x, y); + ScreenToLocal(x, y); + + if ( x < TREE_INDENT_AMOUNT ) + return; + + bool ctrldown = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool shiftdown = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + + if ( !ctrldown && !shiftdown && ( code == MOUSE_LEFT ) ) + { + m_pTreeView->AddSelectedItem( m_ItemIndex, true ); + } +} + +void TreeNode::OnCursorMoved( int x, int y ) +{ + if ( input()->GetMouseCapture() != GetVPanel() ) + return; + + LocalToScreen( x, y ); + m_pTreeView->ScreenToLocal( x, y ); + int newItem = m_pTreeView->FindItemUnderMouse( x, y ); + if ( newItem == -1 ) + { + // Fixme: Figure out best item + return; + } + + int startItem = m_nClickedItem; + int endItem = newItem; + if ( startItem > endItem ) + { + int temp = startItem; + startItem = endItem; + endItem = temp; + } + + CUtlVector< TreeNode * > list; + m_pTreeView->m_pRootNode->FindNodesInRange( list, startItem, endItem ); + + int c = list.Count(); + for ( int i = 0; i < c; ++i ) + { + TreeNode *item = list[ i ]; + if ( m_bClickedSelected ) + { + m_pTreeView->AddSelectedItem( item->m_ItemIndex, false ); + } + else + { + m_pTreeView->RemoveSelectedItem( item->m_ItemIndex ); + } + } +} + +void TreeNode::OnMousePressed( MouseCode code) +{ + BaseClass::OnMousePressed( code ); + + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + int x, y; + input()->GetCursorPos(x, y); + + bool bExpandTree = m_pTreeView->m_bLeftClickExpandsTree; + + if ( code == MOUSE_LEFT ) + { + ScreenToLocal(x, y); + if ( x < TREE_INDENT_AMOUNT ) + { + if ( bExpandTree ) + { + SetNodeExpanded(!m_bExpand); + } + // m_pTreeView->SetSelectedItem(m_ItemIndex); // explorer doesn't actually select item when it expands an item + // purposely commented out in case we want to change the behavior + } + else + { + m_nClickedItem = m_ItemIndex; + if ( m_pTreeView->IsMultipleItemDragEnabled() ) + { + input()->SetMouseCapture( GetVPanel() ); + } + + if ( shift ) + { + m_pTreeView->RangeSelectItems( m_ItemIndex ); + } + else + { + if ( !IsSelected() || ctrl ) + { + if ( IsSelected() && ctrl ) + { + m_pTreeView->RemoveSelectedItem( m_ItemIndex ); + } + else + { + m_pTreeView->AddSelectedItem( m_ItemIndex, !ctrl ); + } + } + else if ( IsSelected() && m_pTreeView->IsMultipleItemDragEnabled() ) + { + m_pTreeView->AddSelectedItem( m_ItemIndex, !shift ); + } + } + + m_bClickedSelected = m_pTreeView->IsItemSelected( m_ItemIndex ); + } + } + else if (code == MOUSE_RIGHT) + { + // context menu selection + // If the item was selected, leave selected items alone, otherwise make it the only selected item + if ( !m_pTreeView->IsItemSelected( m_ItemIndex ) ) + { + m_pTreeView->AddSelectedItem( m_ItemIndex, true ); + } + + // ask parent to context menu + m_pTreeView->GenerateContextMenu(m_ItemIndex, x, y); + } +} + +void TreeNode::RemoveChildren() +{ + int c = m_Children.Count(); + for ( int i = c - 1 ; i >= 0 ; --i ) + { + m_pTreeView->RemoveItem( m_Children[ i ]->m_ItemIndex, false, true ); + } + m_Children.RemoveAll(); +} + +void TreeNode::FindNodesInRange( CUtlVector< TreeNode * >& list, int startIndex, int endIndex ) +{ + list.RemoveAll(); + bool finished = false; + bool foundstart = false; + FindNodesInRange_R( list, finished, foundstart, startIndex, endIndex ); +} + +void TreeNode::FindNodesInRange_R( CUtlVector< TreeNode * >& list, bool& finished, bool& foundStart, int startIndex, int endIndex ) +{ + if ( finished ) + return; + if ( foundStart == true ) + { + list.AddToTail( this ); + + if ( m_ItemIndex == startIndex || m_ItemIndex == endIndex ) + { + finished = true; + return; + } + } + else if ( m_ItemIndex == startIndex || m_ItemIndex == endIndex ) + { + foundStart = true; + list.AddToTail( this ); + if ( startIndex == endIndex ) + { + finished = true; + return; + } + } + + if ( !m_bExpand ) + return; + + + int i; + int c = GetChildrenCount(); + for (i=0;i<c;i++) + { + m_Children[i]->FindNodesInRange_R( list, finished, foundStart, startIndex, endIndex ); + } +} + +void TreeNode::PositionAndSetVisibleNodes(int &nStart, int &nCount, int x, int &y) +{ + // position ourselves + if (nStart == 0) + { + BaseClass::SetVisible(true); + SetPos(x, y); + y += m_pTreeView->GetRowHeight(); // m_nRowHeight + nCount--; + } + else // still looking for first element + { + nStart--; + BaseClass::SetVisible(false); + } + + x += TREE_INDENT_AMOUNT; + int i; + for (i=0;i<GetChildrenCount();i++) + { + if (nCount > 0 && m_bExpand) + { + m_Children[i]->PositionAndSetVisibleNodes(nStart, nCount, x, y); + } + else + { + m_Children[i]->SetVisible(false); // this will make all grand children hidden as well + } + } +} + +TreeNode *TreeNode::FindItemUnderMouse( int &nStart, int& nCount, int x, int &y, int mx, int my ) +{ + // position ourselves + if (nStart == 0) + { + int posx, posy; + GetPos(posx, posy); + if ( my >= posy && my < posy + m_pTreeView->GetRowHeight() ) + { + return this; + } + y += m_pTreeView->GetRowHeight(); + nCount--; + } + else // still looking for first element + { + nStart--; + } + + x += TREE_INDENT_AMOUNT; + int i; + for (i=0;i<GetChildrenCount();i++) + { + if (nCount > 0 && m_bExpand) + { + TreeNode *child = m_Children[i]->FindItemUnderMouse(nStart, nCount, x, y, mx, my); + if ( child != NULL ) + { + return child; + } + } + } + + return NULL; +} + +// counts items above this item including itself +int TreeNode::CountVisibleIndex() +{ + int nCount = 1; // myself + if (GetParentNode()) + { + int i; + for (i=0;i<GetParentNode()->GetChildrenCount();i++) + { + if (GetParentNode()->m_Children[i] == this) + break; + + nCount += GetParentNode()->m_Children[i]->CountVisibleNodes(); + } + return nCount + GetParentNode()->CountVisibleIndex(); + } + else + return nCount; +} + + +}; // namespace vgui + +DECLARE_BUILD_FACTORY( TreeView ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TreeView::TreeView(Panel *parent, const char *panelName) : Panel(parent, panelName) +{ + m_bScrollbarExternal[ 0 ] = m_bScrollbarExternal[ 1 ] = false; + m_nRowHeight = 20; + m_pRootNode = NULL; + m_pImageList = NULL; + m_pSortFunc = NULL; + m_Font = 0; + + m_pSubPanel = new TreeViewSubPanel(this); + m_pSubPanel->SetVisible(true); + m_pSubPanel->SetPos(0,0); + + m_pHorzScrollBar = new ScrollBar(this, "HorizScrollBar", false); + m_pHorzScrollBar->AddActionSignalTarget(this); + m_pHorzScrollBar->SetVisible(false); + + m_pVertScrollBar = new ScrollBar(this, "VertScrollBar", true); + m_pVertScrollBar->SetVisible(false); + m_pVertScrollBar->AddActionSignalTarget(this); + + m_bAllowLabelEditing = false; + m_bDragEnabledItems = false; + m_bDeleteImageListWhenDone = false; + m_bLabelBeingEdited = false; + m_bMultipleItemDragging = false; + m_bLeftClickExpandsTree = true; + m_bAllowMultipleSelections = false; + m_nMostRecentlySelectedItem = -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TreeView::~TreeView() +{ + CleanUpImageList(); +} + + +//----------------------------------------------------------------------------- +// Clean up the image list +//----------------------------------------------------------------------------- +void TreeView::CleanUpImageList( ) +{ + if ( m_pImageList ) + { + if ( m_bDeleteImageListWhenDone ) + { + delete m_pImageList; + } + m_pImageList = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::SetSortFunc(TreeViewSortFunc_t pSortFunc) +{ + m_pSortFunc = pSortFunc; +} + +HFont TreeView::GetFont() +{ + return m_Font; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::SetFont(HFont font) +{ + Assert( font ); + if ( !font ) + return; + + m_Font = font; + m_nRowHeight = surface()->GetFontTall(font) + 2; + + if (m_pRootNode) + { + m_pRootNode->SetFont(font); + } + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int TreeView::GetRowHeight() +{ + return m_nRowHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int TreeView::GetVisibleMaxWidth() +{ + if (m_pRootNode) + { + return m_pRootNode->GetVisibleMaxWidth(); + } + else + { + return 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int TreeView::AddItem(KeyValues *data, int parentItemIndex) +{ + Assert(parentItemIndex == -1 || m_NodeList.IsValidIndex(parentItemIndex)); + + TreeNode *pTreeNode = new TreeNode(m_pSubPanel, this); + pTreeNode->SetDragEnabled( m_bDragEnabledItems ); + pTreeNode->m_ItemIndex = m_NodeList.AddToTail(pTreeNode); + pTreeNode->SetKeyValues(data); + + if ( m_Font != 0 ) + { + pTreeNode->SetFont( m_Font ); + } + pTreeNode->SetBgColor( GetBgColor() ); + + if ( data->GetInt( "droppable", 0 ) != 0 ) + { + float flContextDelay = data->GetFloat( "drophoverdelay" ); + if ( flContextDelay ) + { + pTreeNode->SetDropEnabled( true, flContextDelay ); + } + else + { + pTreeNode->SetDropEnabled( true ); + } + } + + // there can be only one root + if (parentItemIndex == -1) + { + Assert(m_pRootNode == NULL); + m_pRootNode = pTreeNode; + pTreeNode->m_ParentIndex = -1; + } + else + { + pTreeNode->m_ParentIndex = parentItemIndex; + + // add to parent list + pTreeNode->GetParentNode()->AddChild(pTreeNode); + } + + SETUP_PANEL( pTreeNode ); + + return pTreeNode->m_ItemIndex; +} + + +int TreeView::GetRootItemIndex() +{ + if ( m_pRootNode ) + return m_pRootNode->m_ItemIndex; + else + return -1; +} + + +int TreeView::GetNumChildren( int itemIndex ) +{ + if ( itemIndex == -1 ) + return 0; + + return m_NodeList[itemIndex]->m_Children.Count(); +} + + +int TreeView::GetChild( int iParentItemIndex, int iChild ) +{ + return m_NodeList[iParentItemIndex]->m_Children[iChild]->m_ItemIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : itemIndex - +// Output : TreeNode +//----------------------------------------------------------------------------- +TreeNode *TreeView::GetItem( int itemIndex ) +{ + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + { + Assert( 0 ); + return NULL; + } + + return m_NodeList[ itemIndex ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int TreeView::GetItemCount(void) +{ + return m_NodeList.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues* TreeView::GetItemData(int itemIndex) +{ + if (!m_NodeList.IsValidIndex(itemIndex)) + return NULL; + else + return m_NodeList[itemIndex]->m_pData; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::RemoveItem(int itemIndex, bool bPromoteChildren, bool bFullDelete ) +{ + // HACK: there's a bug with RemoveItem where panels are lingering. This gets around it temporarily. + + // FIXME: Negative item indices is a bogus interface method! + // because what if you want to recursively remove everything under node 0? + // Use the bFullDelete parameter instead. + if ( itemIndex < 0 ) + { + itemIndex = -itemIndex; + bFullDelete = true; + } + + if (!m_NodeList.IsValidIndex(itemIndex)) + return; + + TreeNode *pNode = m_NodeList[itemIndex]; + TreeNode *pParent = pNode->GetParentNode(); + + // are we promoting the children + if (bPromoteChildren && pParent) + { + int i; + for (i=0;i<pNode->GetChildrenCount();i++) + { + TreeNode *pChild = pNode->m_Children[i]; + pChild->m_ParentIndex = pParent->m_ItemIndex; + } + } + else + { + // delete our children + if ( bFullDelete ) + { + while ( pNode->GetChildrenCount() ) + RemoveItem( -pNode->m_Children[0]->m_ItemIndex, false ); + } + else + { + int i; + for (i=0;i<pNode->GetChildrenCount();i++) + { + TreeNode *pDeleteChild = pNode->m_Children[i]; + RemoveItem(pDeleteChild->m_ItemIndex, false); + } + } + } + + // remove from our parent's children list + if (pParent) + { + pParent->m_Children.FindAndRemove(pNode); + } + + // finally get rid of ourselves from the main list + m_NodeList.Remove(itemIndex); + + if ( bFullDelete ) + delete pNode; + else + pNode->MarkForDeletion(); + + // Make sure we don't leave ourselves with an invalid selected item. + m_SelectedItems.FindAndRemove( pNode ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::RemoveAll() +{ + int i; + for (i=0;i<m_NodeList.MaxElementIndex();i++) + { + if (!m_NodeList.IsValidIndex(i)) + continue; + + m_NodeList[i]->MarkForDeletion(); + } + m_NodeList.RemoveAll(); + m_pRootNode = NULL; + ClearSelection(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool TreeView::ModifyItem(int itemIndex, KeyValues *data) +{ + if (!m_NodeList.IsValidIndex(itemIndex)) + return false; + + TreeNode *pNode = m_NodeList[itemIndex]; + TreeNode *pParent = pNode->GetParentNode(); + bool bReSort = ( m_pSortFunc && pParent ); + int nChildIndex = -1; + if ( bReSort ) + { + nChildIndex = pParent->FindChild( pNode ); + } + + pNode->SetKeyValues(data); + + // Changing the data can cause it to re-sort + if ( bReSort ) + { + int nChildren = pParent->GetChildrenCount(); + bool bLeftBad = (nChildIndex > 0) && m_pSortFunc( pNode->m_pData, pParent->m_Children[nChildIndex-1]->m_pData ); + bool bRightBad = (nChildIndex < nChildren - 1) && m_pSortFunc( pParent->m_Children[nChildIndex+1]->m_pData, pNode->m_pData ); + if ( bLeftBad || bRightBad ) + { + pParent->m_Children.Remove( nChildIndex ); + pParent->AddChild( pNode ); + } + } + + InvalidateLayout(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: set the selection colors of an element in the tree view +//----------------------------------------------------------------------------- + +void TreeView::SetItemSelectionTextColor( int itemIndex, const Color& clr ) +{ + Assert( m_NodeList.IsValidIndex(itemIndex) ); + if ( !m_NodeList.IsValidIndex(itemIndex) ) + return; + + TreeNode *pNode = m_NodeList[itemIndex]; + pNode->SetSelectionTextColor( clr ); +} + +void TreeView::SetItemSelectionBgColor( int itemIndex, const Color& clr ) +{ + Assert( m_NodeList.IsValidIndex(itemIndex) ); + if ( !m_NodeList.IsValidIndex(itemIndex) ) + return; + + TreeNode *pNode = m_NodeList[itemIndex]; + pNode->SetSelectionBgColor( clr ); +} + +void TreeView::SetItemSelectionUnfocusedBgColor( int itemIndex, const Color& clr ) +{ + Assert( m_NodeList.IsValidIndex(itemIndex) ); + if ( !m_NodeList.IsValidIndex(itemIndex) ) + return; + + TreeNode *pNode = m_NodeList[itemIndex]; + pNode->SetSelectionUnfocusedBgColor( clr ); +} + +//----------------------------------------------------------------------------- +// Purpose: set the fg color of an element in the tree view +//----------------------------------------------------------------------------- +void TreeView::SetItemFgColor(int itemIndex, const Color& color) +{ + Assert( m_NodeList.IsValidIndex(itemIndex) ); + if ( !m_NodeList.IsValidIndex(itemIndex) ) + return; + + TreeNode *pNode = m_NodeList[itemIndex]; + pNode->SetFgColor( color ); +} + +//----------------------------------------------------------------------------- +// Purpose: set the bg color of an element in the tree view +//----------------------------------------------------------------------------- +void TreeView::SetItemBgColor(int itemIndex, const Color& color) +{ + Assert( m_NodeList.IsValidIndex(itemIndex) ); + if ( !m_NodeList.IsValidIndex(itemIndex) ) + return; + + TreeNode *pNode = m_NodeList[itemIndex]; + pNode->SetBgColor( color ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int TreeView::GetItemParent(int itemIndex) +{ + return m_NodeList[itemIndex]->m_ParentIndex; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::SetImageList(ImageList *imageList, bool deleteImageListWhenDone) +{ + CleanUpImageList(); + m_pImageList = imageList; + m_bDeleteImageListWhenDone = deleteImageListWhenDone; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +IImage *TreeView::GetImage(int index) +{ + return m_pImageList->GetImage(index); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::GetSelectedItems( CUtlVector< int >& list ) +{ + list.RemoveAll(); + + int c = m_SelectedItems.Count(); + for ( int i = 0 ; i < c; ++i ) + { + list.AddToTail( m_SelectedItems[ i ]->m_ItemIndex ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::GetSelectedItemData( CUtlVector< KeyValues * >& list ) +{ + list.RemoveAll(); + + int c = m_SelectedItems.Count(); + for ( int i = 0 ; i < c; ++i ) + { + list.AddToTail( m_SelectedItems[ i ]->m_pData ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool TreeView::IsItemIDValid(int itemIndex) +{ + return m_NodeList.IsValidIndex(itemIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int TreeView::GetHighestItemID() +{ + return m_NodeList.MaxElementIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::ExpandItem(int itemIndex, bool bExpand) +{ + if (!m_NodeList.IsValidIndex(itemIndex)) + return; + + m_NodeList[itemIndex]->SetNodeExpanded(bExpand); + InvalidateLayout(); +} + +bool TreeView::IsItemExpanded( int itemIndex ) +{ + if (!m_NodeList.IsValidIndex(itemIndex)) + return false; + + return m_NodeList[itemIndex]->IsExpanded(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Scrolls the list according to the mouse wheel movement +//----------------------------------------------------------------------------- +void TreeView::OnMouseWheeled(int delta) +{ + if ( !m_pVertScrollBar->IsVisible() ) + { + return; + } + int val = m_pVertScrollBar->GetValue(); + val -= (delta * 3); + m_pVertScrollBar->SetValue(val); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::OnSizeChanged(int wide, int tall) +{ + BaseClass::OnSizeChanged(wide, tall); + InvalidateLayout(); + Repaint(); +} + +void TreeView::GetScrollBarSize( bool vertical, int& w, int& h ) +{ + int idx = vertical ? 0 : 1; + + if ( m_bScrollbarExternal[ idx ] ) + { + w = h = 0; + return; + } + + if ( vertical ) + { + m_pVertScrollBar->GetSize( w, h ); + } + else + { + m_pHorzScrollBar->GetSize( w, h ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::PerformLayout() +{ + int wide, tall; + GetSize( wide, tall ); + + if ( !m_pRootNode ) + { + m_pSubPanel->SetSize( wide, tall ); + return; + } + + int sbhw, sbhh; + GetScrollBarSize( false, sbhw, sbhh ); + int sbvw, sbvh; + GetScrollBarSize( true, sbvw, sbvh ); + + bool vbarNeeded = false; + bool hbarNeeded = false; + + // okay we have to check if we need either scroll bars, since if we need one + // it might make it necessary to have the other one + int nodesVisible = tall / m_nRowHeight; + + // count the number of visible items + int visibleItemCount = m_pRootNode->CountVisibleNodes(); + int maxWidth = m_pRootNode->GetVisibleMaxWidth() + 10; // 10 pixel buffer + + vbarNeeded = visibleItemCount > nodesVisible; + + if (!vbarNeeded) + { + if (maxWidth > wide) + { + hbarNeeded = true; + + // recalculate if vbar is needed now + // double check that we really don't need it + nodesVisible = (tall - sbhh) / m_nRowHeight; + vbarNeeded = visibleItemCount > nodesVisible; + } + } + else + { + // we've got the vertical bar here, so shrink the width + hbarNeeded = maxWidth > (wide - (sbvw+2)); + + if (hbarNeeded) + { + nodesVisible = (tall - sbhh) / m_nRowHeight; + } + } + + int subPanelWidth = wide; + int subPanelHeight = tall; + + int vbarPos = 0; + if (vbarNeeded) + { + subPanelWidth -= (sbvw + 2); + int barSize = tall; + if (hbarNeeded) + { + barSize -= sbhh; + } + + //!! need to make it recalculate scroll positions + m_pVertScrollBar->SetVisible(true); + m_pVertScrollBar->SetEnabled(false); + m_pVertScrollBar->SetRangeWindow( nodesVisible ); + m_pVertScrollBar->SetRange( 0, visibleItemCount); + m_pVertScrollBar->SetButtonPressedScrollValue( 1 ); + + if ( !m_bScrollbarExternal[ 0 ] ) + { + m_pVertScrollBar->SetPos(wide - (sbvw + WINDOW_BORDER_WIDTH), 0); + m_pVertScrollBar->SetSize(sbvw, barSize - 2); + } + + // need to figure out + vbarPos = m_pVertScrollBar->GetValue(); + } + else + { + m_pVertScrollBar->SetVisible(false); + m_pVertScrollBar->SetValue( 0 ); + } + + int hbarPos = 0; + if (hbarNeeded) + { + subPanelHeight -= (sbhh + 2); + int barSize = wide; + if (vbarNeeded) + { + barSize -= sbvw; + } + m_pHorzScrollBar->SetVisible(true); + m_pHorzScrollBar->SetEnabled(false); + m_pHorzScrollBar->SetRangeWindow( barSize ); + m_pHorzScrollBar->SetRange( 0, maxWidth); + m_pHorzScrollBar->SetButtonPressedScrollValue( 10 ); + + if ( !m_bScrollbarExternal[ 1 ] ) + { + m_pHorzScrollBar->SetPos(0, tall - (sbhh + WINDOW_BORDER_WIDTH)); + m_pHorzScrollBar->SetSize(barSize - 2, sbhh); + } + + hbarPos = m_pHorzScrollBar->GetValue(); + } + else + { + m_pHorzScrollBar->SetVisible(false); + m_pHorzScrollBar->SetValue( 0 ); + } + + m_pSubPanel->SetSize(subPanelWidth, subPanelHeight); + + int y = 0; + m_pRootNode->PositionAndSetVisibleNodes(vbarPos, visibleItemCount, -hbarPos, y); + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::MakeItemVisible(int itemIndex) +{ + // first make sure that all parents are expanded + TreeNode *pNode = m_NodeList[itemIndex]; + TreeNode *pParent = pNode->GetParentNode(); + while (pParent) + { + if (!pParent->m_bExpand) + { + pParent->SetNodeExpanded(true); + } + pParent = pParent->GetParentNode(); + } + + // recalculate scroll bar due to possible exapnsion + PerformLayout(); + + if (!m_pVertScrollBar->IsVisible()) + return; + + int visibleIndex = pNode->CountVisibleIndex()-1; + int range = m_pVertScrollBar->GetRangeWindow(); + int vbarPos = m_pVertScrollBar->GetValue(); + + // do we need to scroll up or down? + if (visibleIndex < vbarPos) + { + m_pVertScrollBar->SetValue(visibleIndex); + } + else if (visibleIndex+1 > vbarPos+range) + { + m_pVertScrollBar->SetValue(visibleIndex+1-range); + } + InvalidateLayout(); +} + +void TreeView::GetVBarInfo( int &top, int &nItemsVisible, bool& hbarVisible ) +{ + int wide, tall; + GetSize( wide, tall ); + nItemsVisible = tall / m_nRowHeight; + + if ( m_pVertScrollBar->IsVisible() ) + { + top = m_pVertScrollBar->GetValue(); + } + else + { + top = 0; + } + hbarVisible = m_pHorzScrollBar->IsVisible(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetBorder(pScheme->GetBorder("ButtonDepressedBorder")); + SetBgColor(GetSchemeColor("TreeView.BgColor", GetSchemeColor("WindowDisabledBgColor", pScheme), pScheme)); + SetFont( pScheme->GetFont( "Default", IsProportional() ) ); + m_pSubPanel->SetBgColor( GetBgColor() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::SetBgColor( Color color ) +{ + BaseClass::SetBgColor( color ); + m_pSubPanel->SetBgColor( color ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::OnSliderMoved( int position ) +{ + InvalidateLayout(); + Repaint(); +} + +void TreeView::GenerateDragDataForItem( int itemIndex, KeyValues *msg ) +{ + // Implemented by subclassed TreeView +} + +void TreeView::SetDragEnabledItems( bool state ) +{ + m_bDragEnabledItems = state; +} + +void TreeView::OnLabelChanged( int itemIndex, char const *oldString, char const *newString ) +{ +} + +bool TreeView::IsLabelEditingAllowed() const +{ + return m_bAllowLabelEditing; +} + +void TreeView::SetLabelBeingEdited( bool state ) +{ + m_bLabelBeingEdited = state; +} + +bool TreeView::IsLabelBeingEdited() const +{ + return m_bLabelBeingEdited; +} + +void TreeView::SetAllowLabelEditing( bool state ) +{ + m_bAllowLabelEditing = state; +} + +void TreeView::EnableExpandTreeOnLeftClick( bool bEnable ) +{ + m_bLeftClickExpandsTree = bEnable; +} + +int TreeView::FindItemUnderMouse( int mx, int my ) +{ + mx = clamp( mx, 0, GetWide() - 1 ); + my = clamp( my, 0, GetTall() - 1 ); + if ( mx >= TREE_INDENT_AMOUNT ) + { + // Find what's under this position + // need to figure out + int vbarPos = m_pVertScrollBar->IsVisible() ? m_pVertScrollBar->GetValue() : 0; + int hbarPos = m_pHorzScrollBar->IsVisible() ? m_pHorzScrollBar->GetValue() : 0; + int count = m_pRootNode->CountVisibleNodes(); + + int y = 0; + TreeNode *item = m_pRootNode->FindItemUnderMouse( vbarPos, count, -hbarPos, y, mx, my ); + if ( item ) + { + return item->m_ItemIndex; + } + } + + return -1; +} + +void TreeView::OnMousePressed( MouseCode code ) +{ + bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); + bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)); + + // Try to map mouse position to a row + if ( code == MOUSE_LEFT && m_pRootNode ) + { + int mx, my; + input()->GetCursorPos( mx, my ); + ScreenToLocal( mx, my ); + if ( mx >= TREE_INDENT_AMOUNT ) + { + // Find what's under this position + // need to figure out + int vbarPos = m_pVertScrollBar->IsVisible() ? m_pVertScrollBar->GetValue() : 0; + int hbarPos = m_pHorzScrollBar->IsVisible() ? m_pHorzScrollBar->GetValue() : 0; + int count = m_pRootNode->CountVisibleNodes(); + + int y = 0; + TreeNode *item = m_pRootNode->FindItemUnderMouse( vbarPos, count, -hbarPos, y, mx, my ); + if ( item ) + { + if ( !item->IsSelected() ) + { + AddSelectedItem( item->m_ItemIndex, !ctrl && !shift ); + } + return; + } + else + { + ClearSelection(); + } + } + } + + BaseClass::OnMousePressed( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void TreeView::SetAllowMultipleSelections( bool state ) +{ + m_bAllowMultipleSelections = state; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TreeView::IsMultipleSelectionAllowed() const +{ + return m_bAllowMultipleSelections; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : int +//----------------------------------------------------------------------------- +int TreeView::GetSelectedItemCount() const +{ + return m_SelectedItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void TreeView::ClearSelection() +{ + m_SelectedItems.RemoveAll(); + m_nMostRecentlySelectedItem = -1; + PostActionSignal( new KeyValues( "TreeViewItemSelectionCleared" ) ); +} + +void TreeView::RangeSelectItems( int endItem ) +{ + int startItem = m_nMostRecentlySelectedItem; + ClearSelection(); + m_nMostRecentlySelectedItem = startItem; + + if ( !m_NodeList.IsValidIndex( startItem ) ) + { + AddSelectedItem( endItem, false ); + return; + } + + Assert( m_NodeList.IsValidIndex( endItem ) ); + + if ( !m_pRootNode ) + { + return; + } + + CUtlVector< TreeNode * > list; + m_pRootNode->FindNodesInRange( list, startItem, endItem ); + + int c = list.Count(); + for ( int i = 0; i < c; ++i ) + { + TreeNode *item = list[ i ]; + AddSelectedItem( item->m_ItemIndex, false ); + } +} + +void TreeView::FindNodesInRange( int startItem, int endItem, CUtlVector< int >& itemIndices ) +{ + CUtlVector< TreeNode * > nodes; + m_pRootNode->FindNodesInRange( nodes, startItem, endItem ); + + int c = nodes.Count(); + for ( int i = 0; i < c; ++i ) + { + TreeNode *item = nodes[ i ]; + itemIndices.AddToTail( item->m_ItemIndex ); + } +} + +void TreeView::RemoveSelectedItem( int itemIndex ) +{ + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return; + + TreeNode *sel = m_NodeList[ itemIndex ]; + Assert( sel ); + int slot = m_SelectedItems.Find( sel ); + if ( slot != m_SelectedItems.InvalidIndex() ) + { + m_SelectedItems.Remove( slot ); + PostActionSignal( new KeyValues( "TreeViewItemDeselected", "itemIndex", itemIndex ) ); + + m_nMostRecentlySelectedItem = itemIndex; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TreeView::AddSelectedItem( int itemIndex, bool clearCurrentSelection, bool requestFocus /* = true */, bool bMakeItemVisible /*= true*/ ) +{ + if ( clearCurrentSelection ) + { + ClearSelection(); + } + + // Assume it's bogus + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return; + + TreeNode *sel = m_NodeList[ itemIndex ]; + Assert( sel ); + if ( requestFocus ) + { + sel->RequestFocus(); + } + + // Item 0 is most recently selected!!! + int slot = m_SelectedItems.Find( sel ); + if ( slot == m_SelectedItems.InvalidIndex() ) + { + m_SelectedItems.AddToHead( sel ); + } + else if ( slot != 0 ) + { + m_SelectedItems.Remove( slot ); + m_SelectedItems.AddToHead( sel ); + } + + if ( bMakeItemVisible ) + { + MakeItemVisible( itemIndex ); + } + + PostActionSignal( new KeyValues( "TreeViewItemSelected", "itemIndex", itemIndex ) ); + InvalidateLayout(); + + if ( clearCurrentSelection ) + { + m_nMostRecentlySelectedItem = itemIndex; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : int +//----------------------------------------------------------------------------- +int TreeView::GetFirstSelectedItem() const +{ + if ( m_SelectedItems.Count() <= 0 ) + return -1; + return m_SelectedItems[ 0 ]->m_ItemIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : itemIndex - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TreeView::IsItemSelected( int itemIndex ) +{ + // Assume it's bogus + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return false; + + TreeNode *sel = m_NodeList[ itemIndex ]; + return m_SelectedItems.Find( sel ) != m_SelectedItems.InvalidIndex(); +} + +void TreeView::SetLabelEditingAllowed( int itemIndex, bool state ) +{ + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return; + + TreeNode *sel = m_NodeList[ itemIndex ]; + sel->SetLabelEditingAllowed( state ); +} + +void TreeView::StartEditingLabel( int itemIndex ) +{ + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return; + + Assert( IsLabelEditingAllowed() ); + + TreeNode *sel = m_NodeList[ itemIndex ]; + Assert( sel->IsLabelEditingAllowed() ); + if ( !sel->IsLabelEditingAllowed() ) + return; + + sel->EditLabel(); +} + +int TreeView::GetPrevChildItemIndex( int itemIndex ) +{ + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return -1; + TreeNode *sel = m_NodeList[ itemIndex ]; + TreeNode *parent = sel->GetParentNode(); + if ( !parent ) + return -1; + + return parent->GetPrevChildItemIndex( sel ); +} + +int TreeView::GetNextChildItemIndex( int itemIndex ) +{ + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return -1; + TreeNode *sel = m_NodeList[ itemIndex ]; + TreeNode *parent = sel->GetParentNode(); + if ( !parent ) + return -1; + + return parent->GetNextChildItemIndex( sel ); +} + +bool TreeView::IsItemDroppable( int itemIndex, CUtlVector< KeyValues * >& msglist ) +{ + // Derived classes should implement + return false; +} + +void TreeView::OnItemDropped( int itemIndex, CUtlVector< KeyValues * >& msglist ) +{ +} + +bool TreeView::GetItemDropContextMenu( int itemIndex, Menu *menu, CUtlVector< KeyValues * >& msglist ) +{ + return false; +} + +HCursor TreeView::GetItemDropCursor( int itemIndex, CUtlVector< KeyValues * >& msglist ) +{ + return dc_arrow; +} + +void TreeView::RemoveChildrenOfNode( int itemIndex ) +{ + if ( !m_NodeList.IsValidIndex( itemIndex ) ) + return; + + TreeNode *node = m_NodeList[ itemIndex ]; + node->RemoveChildren(); +} + +ScrollBar *TreeView::SetScrollBarExternal( bool vertical, Panel *newParent ) +{ + if ( vertical ) + { + m_bScrollbarExternal[ 0 ] = true; + m_pVertScrollBar->SetParent( newParent ); + return m_pVertScrollBar; + } + m_bScrollbarExternal[ 1 ] = true; + m_pHorzScrollBar->SetParent( newParent ); + return m_pHorzScrollBar; +} + +// if this is set, then clicking on one row and dragging will select a run or items, etc. +void TreeView::SetMultipleItemDragEnabled( bool state ) +{ + m_bMultipleItemDragging = state; +} + +bool TreeView::IsMultipleItemDragEnabled() const +{ + return m_bMultipleItemDragging; +} + +void TreeView::SelectAll() +{ + m_SelectedItems.RemoveAll(); + FOR_EACH_LL( m_NodeList, i ) + { + m_SelectedItems.AddToTail( m_NodeList[ i ] ); + } + + PostActionSignal( new KeyValues( "TreeViewItemSelected", "itemIndex", GetRootItemIndex() ) ); + InvalidateLayout(); +} diff --git a/vgui2/vgui_controls/TreeViewListControl.cpp b/vgui2/vgui_controls/TreeViewListControl.cpp new file mode 100644 index 0000000..30cb3b7 --- /dev/null +++ b/vgui2/vgui_controls/TreeViewListControl.cpp @@ -0,0 +1,315 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <assert.h> + +#define PROTECTED_THINGS_DISABLE + +#include <vgui/Cursor.h> +#include <vgui/IScheme.h> +#include <vgui/IInput.h> +#include <vgui/IPanel.h> +#include <vgui/ISurface.h> +#include <vgui/KeyCode.h> +#include <KeyValues.h> +#include <vgui/MouseCode.h> +#include <vgui/IBorder.h> + +#include <vgui_controls/TreeViewListControl.h> +#include <vgui_controls/ScrollBar.h> +#include <vgui_controls/TextEntry.h> +#include <vgui_controls/TreeView.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/TextImage.h> +#include <vgui_controls/ImageList.h> +#include <vgui_controls/ImagePanel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +DECLARE_BUILD_FACTORY( CTreeViewListControl ); + +CTreeViewListControl::CTreeViewListControl( vgui::Panel *pParent, const char *pName ) : + BaseClass( pParent, pName ) +{ + m_pTree = NULL; + m_BorderColor.SetColor( 255, 255, 255, 255 ); + m_TitleBarFont = NULL; + m_TitleBarHeight = 20; + SetPostChildPaintEnabled( true ); +} + +void CTreeViewListControl::SetTreeView( vgui::TreeView *pTree ) +{ + m_pTree = pTree; + if ( m_pTree ) + { + m_pTree->SetParent( this ); + m_pTree->SetPaintBackgroundEnabled( false ); + } + + InvalidateLayout(); +} + +vgui::TreeView *CTreeViewListControl::GetTree() +{ + return m_pTree; +} + +int CTreeViewListControl::GetTitleBarHeight() +{ + return m_TitleBarHeight; +} + +void CTreeViewListControl::SetTitleBarInfo( vgui::HFont hFont, int titleBarHeight ) +{ + m_TitleBarFont = hFont; + m_TitleBarHeight = titleBarHeight; + + InvalidateLayout(); +} + +void CTreeViewListControl::SetBorderColor( Color clr ) +{ + m_BorderColor = clr; +} + +void CTreeViewListControl::SetNumColumns( int nColumns ) +{ + m_Columns.Purge(); + m_Columns.SetSize( nColumns ); + InvalidateLayout(); +} + +int CTreeViewListControl::GetNumColumns() const +{ + return m_Columns.Count(); +} + +void CTreeViewListControl::SetColumnInfo( int iColumn, const char *pTitle, int width, int ciFlags ) +{ + if ( iColumn < 0 || iColumn >= m_Columns.Count() ) + { + Assert( false ); + return; + } + CColumnInfo *pInfo = &m_Columns[iColumn]; + pInfo->m_Title = pTitle; + pInfo->m_Width = width; + pInfo->m_ciFlags = ciFlags; + + InvalidateLayout(); +} + +int CTreeViewListControl::GetNumRows() +{ + return m_Rows.Count(); +} + +int CTreeViewListControl::GetTreeItemAtRow( int iRow ) +{ + if ( iRow < 0 || iRow >= m_Rows.Count() ) + return -1; + else + return m_Rows[iRow]; +} + +void CTreeViewListControl::GetGridElementBounds( int iColumn, int iRow, int &left, int &top, int &right, int &bottom ) +{ + left = m_Columns[iColumn].m_Left; + right = m_Columns[iColumn].m_Right; + + // vgui doesn't seem to be drawing things exactly right. Like it you draw a line at (0,0) to (100,0), + // then a rectangle from (1,1) to (100,100), it'll overwrite the line at the top. + int treeTopBorder = 0; + IBorder *treeBorder = m_pTree->GetBorder(); + if ( treeBorder ) + { + int l, t, r, b; + treeBorder->GetInset( l, t, r, b ); + treeTopBorder = t; + } + if ( iRow == -1 ) + { + top = 1; + bottom = m_TitleBarHeight - 2; + } + else if ( m_pTree ) + { + int x, y; + m_pTree->GetPos( x, y ); + + top = treeTopBorder + m_TitleBarHeight + ( iRow * m_pTree->GetRowHeight() ); + bottom = top + m_pTree->GetRowHeight(); + } + else + { + left = top = right = bottom = 0; + } +} + +void CTreeViewListControl::PerformLayout() +{ + RecalculateRows(); + RecalculateColumns(); + + // Reposition the tree view. + if ( m_pTree && m_Columns.Count() > 0 ) + { + int left, top, right, bottom; + GetGridElementBounds( 0, -1, left, top, right, bottom ); + + top = m_TitleBarHeight; + + m_pTree->SetBounds( left, top, right - left, GetTall() - top ); + } + + BaseClass::PerformLayout(); +} + + +void CTreeViewListControl::RecalculateRows() +{ + m_Rows.Purge(); + + if ( !m_pTree || m_pTree->GetRootItemIndex() == -1 ) + return; + + int iRoot = m_pTree->GetRootItemIndex(); + RecalculateRows_R( iRoot ); +} + + +void CTreeViewListControl::RecalculateRows_R( int index ) +{ + m_Rows.AddToTail( index ); + if ( !m_pTree->IsItemExpanded( index ) ) + return; + + int nChildren = m_pTree->GetNumChildren( index ); + for ( int i=0; i < nChildren; i++ ) + { + int iChild = m_pTree->GetChild( index, i ); + RecalculateRows_R( iChild ); + } +} + +int CTreeViewListControl::GetScrollBarSize() +{ + return 0; +} + +void CTreeViewListControl::RecalculateColumns() +{ + int rightEdge = GetWide()-1 - GetScrollBarSize(); + + int x = 0; + int c = m_Columns.Count(); + for ( int i=0; i < c; i++ ) + { + m_Columns[i].m_Left = x + 1; + int cw = m_Columns[i].m_Width; + if ( i == c - 1 ) + { + cw = rightEdge - x - 2; + } + m_Columns[i].m_Right = x + cw - 2; + x += cw; + } +} + +void CTreeViewListControl::PostChildPaint() +{ + BaseClass::PostChildPaint(); + + // Draw the grid lines. + vgui::surface()->DrawSetColor( m_BorderColor ); + + if ( m_Columns.Count() <= 0 ) + return; + + // Draw the horizontal lines. + int endX = 0; + endX = m_Columns[m_Columns.Count()-1].m_Right + 1; + + int bottomY = 0; + for ( int i=0; i < m_Rows.Count(); i++ ) + { + int left, top, right, bottom; + GetGridElementBounds( 0, i, left, top, right, bottom ); + + bottomY = bottom; + vgui::surface()->DrawLine( 0, bottomY, endX, bottomY ); + } + + // Draw the vertical lines. + int curX = 0; + for ( int i=0; i < m_Columns.Count(); i++ ) + { + vgui::surface()->DrawLine( curX, 0, curX, bottomY ); + curX += m_Columns[i].m_Width; + } + vgui::surface()->DrawLine( curX, 0, curX, bottomY ); +} + +void CTreeViewListControl::Paint() +{ + BaseClass::Paint(); + + // Draw the title bars. + DrawTitleBars(); +} + +void CTreeViewListControl::DrawTitleBars() +{ + int rightEdge = GetWide(); + + for ( int i=0; i < m_Columns.Count(); i++ ) + { + int left, top, right, bottom; + GetGridElementBounds( i, -1, left, top, right, bottom ); + + if ( left >= rightEdge ) + continue; + + vgui::surface()->DrawSetColor( 0, 0, 0, 255 ); + vgui::surface()->DrawFilledRect( left, top, right, bottom ); + + vgui::surface()->DrawSetTextColor( 255, 255, 255, 255 ); + + const char *pTitleString = m_Columns[i].m_Title.String(); + + wchar_t unicodeString[1024]; + g_pVGuiLocalize->ConvertANSIToUnicode( pTitleString, unicodeString, sizeof(unicodeString) ); + + int wide, tall; + surface()->GetTextSize( m_TitleBarFont, unicodeString, wide, tall ); + + surface()->DrawSetTextFont( m_TitleBarFont ); + + if ( m_Columns[i].m_ciFlags & CTreeViewListControl::CI_HEADER_LEFTALIGN ) + { + int midy = (top+bottom)/2; + surface()->DrawSetTextPos( left, midy ); + } + else + { + int textRight = min( right, rightEdge ); + + int midx = (left+textRight)/2; + int midy = (top+bottom)/2; + + surface()->DrawSetTextPos( midx - wide/2, midy - tall/2 ); + } + + surface()->DrawPrintText( unicodeString, strlen( pTitleString ) ); + } +} + diff --git a/vgui2/vgui_controls/URLLabel.cpp b/vgui2/vgui_controls/URLLabel.cpp new file mode 100644 index 0000000..456f0db --- /dev/null +++ b/vgui2/vgui_controls/URLLabel.cpp @@ -0,0 +1,158 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <stdio.h> + +#include "vgui/ISurface.h" +#include "vgui/ISystem.h" +#include "vgui/MouseCode.h" +#include "vgui/Cursor.h" +#include "KeyValues.h" + +#include "vgui_controls/URLLabel.h" +#include "vgui_controls/TextImage.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +vgui::Panel *URLLabel_Factory() +{ + return new URLLabel(NULL, NULL, "URLLabel", NULL); +} + +DECLARE_BUILD_FACTORY_CUSTOM( URLLabel, URLLabel_Factory ); +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +URLLabel::URLLabel(Panel *parent, const char *panelName, const char *text, const char *pszURL) : Label(parent, panelName, text) +{ + m_pszURL = NULL; + m_bUnderline = false; + m_iURLSize = 0; + if (pszURL && strlen(pszURL) > 0) + { + SetURL(pszURL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: unicode constructor +//----------------------------------------------------------------------------- +URLLabel::URLLabel(Panel *parent, const char *panelName, const wchar_t *wszText, const char *pszURL) : Label(parent, panelName, wszText) +{ + m_pszURL = NULL; + m_bUnderline = false; + m_iURLSize = 0; + if (pszURL && strlen(pszURL) > 0) + { + SetURL(pszURL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: destructor +//----------------------------------------------------------------------------- +URLLabel::~URLLabel() +{ + if (m_pszURL) + delete [] m_pszURL; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the URL +//----------------------------------------------------------------------------- +void URLLabel::SetURL(const char *pszURL) +{ + int iNewURLSize = strlen(pszURL); + if (iNewURLSize > m_iURLSize || !m_pszURL) + { + delete [] m_pszURL; + m_pszURL = new char [iNewURLSize + 1]; + } + strcpy(m_pszURL, pszURL); + m_iURLSize = iNewURLSize; +} + +//----------------------------------------------------------------------------- +// Purpose: If we were left clicked on, launch the URL +//----------------------------------------------------------------------------- +void URLLabel::OnMousePressed(MouseCode code) +{ + if (code == MOUSE_LEFT) + { + if (m_pszURL) + { + system()->ShellExecute("open", m_pszURL); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Applies resouce settings +//----------------------------------------------------------------------------- +void URLLabel::ApplySettings(KeyValues *inResourceData) +{ + BaseClass::ApplySettings(inResourceData); + + const char *pszURL = inResourceData->GetString("URLText", NULL); + if (pszURL) + { + if (pszURL[0] == '#') + { + // it's a localized url, look it up + const wchar_t *ws = g_pVGuiLocalize->Find(pszURL + 1); + if (ws) + { + char localizedUrl[512]; + g_pVGuiLocalize->ConvertUnicodeToANSI(ws, localizedUrl, sizeof(localizedUrl)); + SetURL(localizedUrl); + } + } + else + { + SetURL(pszURL); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: saves them to disk +//----------------------------------------------------------------------------- +void URLLabel::GetSettings( KeyValues *outResourceData ) +{ + BaseClass::GetSettings(outResourceData); + + if (m_pszURL) + { + outResourceData->SetString("URLText", m_pszURL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a description of the label string +//----------------------------------------------------------------------------- +const char *URLLabel::GetDescription( void ) +{ + static char buf[1024]; + _snprintf(buf, sizeof(buf), "%s, string URLText", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: scheme settings +//----------------------------------------------------------------------------- +void URLLabel::ApplySchemeSettings(IScheme *pScheme) +{ + // set our font to be underlined by default + // the Label::ApplySchemeSettings() will override it if override set in scheme file + SetFont( pScheme->GetFont( "DefaultUnderline", IsProportional() ) ); + BaseClass::ApplySchemeSettings(pScheme); + SetCursor(dc_hand); +} + diff --git a/vgui2/vgui_controls/WizardPanel.cpp b/vgui2/vgui_controls/WizardPanel.cpp new file mode 100644 index 0000000..87b78a5 --- /dev/null +++ b/vgui2/vgui_controls/WizardPanel.cpp @@ -0,0 +1,720 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <vgui/IVGui.h> +#include <KeyValues.h> + +#include <vgui_controls/BuildGroup.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/Controls.h> +#include <vgui_controls/WizardPanel.h> +#include <vgui_controls/WizardSubPanel.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +WizardPanel::WizardPanel(Panel *parent, const char *panelName) : Frame(parent, panelName) +{ + _currentSubPanel = NULL; + _currentData = new KeyValues("WizardData"); + _showButtons = true; + + + SetSizeable(false); + + CreateButtons(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +WizardPanel::~WizardPanel() +{ + if (_currentData) + { + _currentData->deleteThis(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + // resize the sub panel to fit in the Client area + int x, y, wide, tall; + GetClientArea(x, y, wide, tall); + + if (_currentSubPanel && _currentSubPanel->isNonWizardPanel()) + { + // just have the subpanel cover the full size + _currentSubPanel->SetBounds(x, y, wide, tall); + _cancelButton->SetVisible(false); + _prevButton->SetVisible(false); + _nextButton->SetVisible(false); + _finishButton->SetVisible(false); + } + else + { + // make room for the buttons at bottom + if (_currentSubPanel) + { + if( _showButtons ) + { + _currentSubPanel->SetBounds(x, y, wide, tall - 35); + } + else + { + _currentSubPanel->SetBounds(x, y, wide, tall); + } + } + + // align the buttons to the right hand side + GetSize(wide, tall); + + + int bwide, btall; + _cancelButton->GetSize(bwide, btall); + + x = wide - (20 + bwide); + y = tall - (12 + btall); + + _cancelButton->SetPos(x, y); + x -= (20 + bwide); + + // only display one of the next or finish buttons (and only if both are visible) + if ( _showButtons ) + { + if (_finishButton->IsEnabled() ) + { + _nextButton->SetVisible(false); + _finishButton->SetVisible(true); + _finishButton->SetPos(x, y); + } + else + { + _nextButton->SetVisible(true); + _finishButton->SetVisible(false); + _nextButton->SetPos(x, y); + } + } + + x -= (1 + bwide); + _prevButton->SetPos(x, y); + + ResetDefaultButton(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: if we don't show buttons then let the sub panel occupy the whole screen +//----------------------------------------------------------------------------- +void WizardPanel::GetClientArea(int &x, int &y, int &wide, int &tall) +{ + if( _showButtons ) + { + BaseClass::GetClientArea( x, y, wide, tall ); + } + else + { + x = 0; + y = 0; + GetSize( wide, tall ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::Run(WizardSubPanel *startPanel) +{ + // skip over sub panels if they don't want to be displayed + startPanel = FindNextValidSubPanel(startPanel); + + // show it + ActivateNextSubPanel(startPanel); + + // make sure we're set up and Run the first panel + Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::ActivateBuildMode() +{ + // no subpanel, no build mode + if (!_currentSubPanel) + return; + + _currentSubPanel->ActivateBuildMode(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::ResetDefaultButton() +{ + // work out which is the default button + if (_nextButton->IsEnabled()) + { + _nextButton->SetAsDefaultButton(true); + } + else if (_finishButton->IsEnabled()) + { + _finishButton->SetAsDefaultButton(true); + } + else if (_prevButton->IsEnabled()) + { + _prevButton->SetAsDefaultButton(true); + } + /* Don't ever set the cancel button as the default, as it is too easy for users to quit the wizard without realizing + else if (_cancelButton->IsEnabled()) + { + _cancelButton->SetAsDefaultButton(true); + } + */ + + // reset them all (this may not be necessary) + _nextButton->InvalidateLayout(); + _prevButton->InvalidateLayout(); + _cancelButton->InvalidateLayout(); + _finishButton->InvalidateLayout(); + + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::ResetKeyFocus() +{ + // set the focus on the default + FocusNavGroup &navGroup = GetFocusNavGroup(); + Panel *def = navGroup.GetDefaultPanel(); + if (def) + { + if (def->IsEnabled() && def->IsVisible()) + { + def->RequestFocus(); + } + else + { + def->RequestFocusNext(); + } + } + + ResetDefaultButton(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::CreateButtons() +{ + _prevButton = new Button(this, "PrevButton", ""); + _nextButton = new Button(this, "NextButton", ""); + _cancelButton = new Button(this, "CancelButton", ""); + _finishButton = new Button(this, "FinishButton", ""); + + _prevButton->SetCommand(new KeyValues("PrevButton")); + _nextButton->SetCommand(new KeyValues("NextButton")); + _cancelButton->SetCommand(new KeyValues("CancelButton")); + _finishButton->SetCommand(new KeyValues("FinishButton")); + + SetNextButtonText(NULL); + SetPrevButtonText(NULL); + SetFinishButtonText(NULL); + SetCancelButtonText(NULL); + + _prevButton->SetSize(82, 24); + _nextButton->SetSize(82, 24); + _cancelButton->SetSize(82, 24); + _finishButton->SetSize(82, 24); +} + +//----------------------------------------------------------------------------- +// Purpose: clears all previous history +//----------------------------------------------------------------------------- +void WizardPanel::ResetHistory() +{ + _subPanelStack.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::ActivateNextSubPanel(WizardSubPanel *subPanel) +{ + // get rid of previous panel + WizardSubPanel *prevPanel = _currentSubPanel; + if (prevPanel && prevPanel->ShouldDisplayPanel()) + { + // hide + prevPanel->SetVisible(false); + + // push onto history stack + _subPanelStack.AddElement(_currentSubPanel); + } + + // reenable all buttons, returning them to their default state + _prevButton->SetEnabled(true); + _nextButton->SetEnabled(true); + _cancelButton->SetEnabled(true); + _finishButton->SetEnabled(true); + if ( _showButtons ) + { + _prevButton->SetVisible(true); + _cancelButton->SetVisible(true); + } + + // set up new subpanel + _currentSubPanel = subPanel; + _currentSubPanel->SetParent(this); + _currentSubPanel->SetVisible(true); + + _currentSubPanel->SetWizardPanel(this); + _currentSubPanel->OnDisplayAsNext(); + _currentSubPanel->OnDisplay(); + _currentSubPanel->InvalidateLayout(false); + + SETUP_PANEL( _currentSubPanel ); + int wide, tall; + if ( _currentSubPanel->GetDesiredSize(wide, tall) ) + { + SetSize(wide, tall); + } + + if (!prevPanel) + { + // no previous panel, so disable the back button + _prevButton->SetEnabled(false); + } + + _currentSubPanel->RequestFocus(); + + RecalculateTabOrdering(); + InvalidateLayout(false); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Pops the last panel off the stack and runs it +//----------------------------------------------------------------------------- +void WizardPanel::ActivatePrevSubPanel() +{ + _currentSubPanel->SetVisible(false); + + WizardSubPanel *prevPanel = NULL; + if (_subPanelStack.GetCount()) + { + // check to see if we need to jump back to a previous sub panel + WizardSubPanel *searchPanel = _currentSubPanel->GetPrevSubPanel(); + if (searchPanel && _subPanelStack.HasElement(searchPanel)) + { + // keep poping the stack till we find it + while (_subPanelStack.GetCount() && prevPanel != searchPanel) + { + prevPanel = _subPanelStack[_subPanelStack.GetCount() - 1]; + _subPanelStack.RemoveElementAt(_subPanelStack.GetCount() - 1); + } + } + else + { + // just get the last one + prevPanel = _subPanelStack[_subPanelStack.GetCount() - 1]; + _subPanelStack.RemoveElementAt(_subPanelStack.GetCount() - 1); + } + } + + if (!prevPanel) + { + ivgui()->DPrintf2("Error: WizardPanel::ActivatePrevSubPanel(): no previous panel to go back to\n"); + return; + } + + // hide old panel + _currentSubPanel->SetVisible(false); + + // reenable all buttons, returning them to their default state + _prevButton->SetEnabled(true); + _nextButton->SetEnabled(true); + _cancelButton->SetEnabled(true); + _finishButton->SetEnabled(true); + + // Activate new panel + _currentSubPanel = prevPanel; + _currentSubPanel->RequestFocus(); + _currentSubPanel->SetWizardPanel(this); + _currentSubPanel->OnDisplayAsPrev(); + _currentSubPanel->OnDisplay(); + _currentSubPanel->InvalidateLayout(false); + + SETUP_PANEL( _currentSubPanel ); + int wide, tall; + if ( _currentSubPanel->GetDesiredSize(wide, tall) ) + { + SetSize(wide, tall); + } + + // show the previous panel, but don't Activate it (since it should show just what it was previously) + _currentSubPanel->SetVisible(true); + + if (!_subPanelStack.GetCount()) + { + // no previous panel, so disable the back button + _prevButton->SetEnabled(false); + } + + RecalculateTabOrdering(); + InvalidateLayout(false); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets up the new tab ordering +//----------------------------------------------------------------------------- +void WizardPanel::RecalculateTabOrdering() +{ + if (_currentSubPanel) + { + _currentSubPanel->SetTabPosition(1); + } + _prevButton->SetTabPosition(2); + _nextButton->SetTabPosition(3); + _finishButton->SetTabPosition(4); + _cancelButton->SetTabPosition(5); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetNextButtonEnabled(bool state) +{ + if (_nextButton->IsEnabled() != state) + { + _nextButton->SetEnabled(state); + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetPrevButtonEnabled(bool state) +{ + if (_prevButton->IsEnabled() != state) + { + _prevButton->SetEnabled(state); + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetFinishButtonEnabled(bool state) +{ + if (_finishButton->IsEnabled() != state) + { + _finishButton->SetEnabled(state); + InvalidateLayout(false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetCancelButtonEnabled(bool state) +{ + if (_cancelButton->IsEnabled() != state) + { + _cancelButton->SetEnabled(state); + InvalidateLayout(false); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetNextButtonVisible(bool state) +{ + _nextButton->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetPrevButtonVisible(bool state) +{ + _prevButton->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetFinishButtonVisible(bool state) +{ + _finishButton->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetCancelButtonVisible(bool state) +{ + _cancelButton->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetNextButtonText(const char *text) +{ + if (text) + { + _nextButton->SetText(text); + } + else + { + _nextButton->SetText("#WizardPanel_Next"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetPrevButtonText(const char *text) +{ + if (text) + { + _prevButton->SetText(text); + } + else + { + _prevButton->SetText("#WizardPanel_Back"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetFinishButtonText(const char *text) +{ + if (text) + { + _finishButton->SetText(text); + } + else + { + _finishButton->SetText("#WizardPanel_Finish"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::SetCancelButtonText(const char *text) +{ + if (text) + { + _cancelButton->SetText(text); + } + else + { + _cancelButton->SetText("#WizardPanel_Cancel"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the next panel that wants to be shown +//----------------------------------------------------------------------------- +WizardSubPanel *WizardPanel::FindNextValidSubPanel(WizardSubPanel *currentPanel) +{ + // skip over sub panels if they don't want to be displayed + while (currentPanel) + { + currentPanel->SetWizardPanel(this); + if (currentPanel->ShouldDisplayPanel()) + break; + + // ok the panel wants to be skipped, so skip ahead + currentPanel = currentPanel->GetNextSubPanel(); + } + + return currentPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Advances to the next panel +//----------------------------------------------------------------------------- +void WizardPanel::OnNextButton() +{ + if (_currentSubPanel) + { + bool shouldAdvance = _currentSubPanel->OnNextButton(); + if (shouldAdvance) + { + WizardSubPanel *nextPanel = FindNextValidSubPanel(_currentSubPanel->GetNextSubPanel()); + + if (nextPanel) + { + KeyValues *kv = new KeyValues("ActivateNextSubPanel"); + kv->SetPtr("panel", nextPanel); + ivgui()->PostMessage(GetVPanel(), kv, GetVPanel()); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Retreats to the previous panel +//----------------------------------------------------------------------------- +void WizardPanel::OnPrevButton() +{ + bool shouldRetreat = true; + if (_currentSubPanel) + { + shouldRetreat = _currentSubPanel->OnPrevButton(); + } + + if (shouldRetreat) + { + ActivatePrevSubPanel(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::OnFinishButton() +{ + if (_currentSubPanel && _currentSubPanel->OnFinishButton()) + { + // hide ourselves away + BaseClass::OnClose(); + + // automatically delete ourselves if marked to do so + if (IsAutoDeleteSet()) + { + MarkForDeletion(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardPanel::OnCancelButton() +{ + if (_currentSubPanel && _currentSubPanel->OnCancelButton()) + { + // hide ourselves away + BaseClass::OnClose(); + if (IsAutoDeleteSet()) + { + MarkForDeletion(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: command handler for catching escape key presses +//----------------------------------------------------------------------------- +void WizardPanel::OnCommand(const char *command) +{ + if (!stricmp(command, "Cancel")) + { + if (_cancelButton->IsEnabled()) + { + _cancelButton->DoClick(); + } + } + else + { + BaseClass::OnCommand(command); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Maps close button to cancel button +//----------------------------------------------------------------------------- +void WizardPanel::OnClose() +{ + if (_cancelButton->IsEnabled()) + { + _cancelButton->DoClick(); + } + else if (_finishButton->IsEnabled()) + { + _finishButton->DoClick(); + } + + // don't chain back +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *WizardPanel::GetWizardData() +{ + return _currentData; +} + + +//----------------------------------------------------------------------------- +// Purpose: whether to show the next,prev,finish and cancel buttons +//----------------------------------------------------------------------------- +void WizardPanel::ShowButtons(bool state) +{ + _showButtons = state; // hide the wizard panel buttons + SetNextButtonVisible( state ); + SetPrevButtonVisible( state ); + SetFinishButtonVisible( state ); + SetCancelButtonVisible( state ); +} + +//----------------------------------------------------------------------------- +// Purpose: filters close buttons +//----------------------------------------------------------------------------- +void WizardPanel::OnCloseFrameButtonPressed() +{ + // only allow close if the cancel button is enabled + if (_cancelButton->IsEnabled()) + { + BaseClass::OnCloseFrameButtonPressed(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns a page by name +//----------------------------------------------------------------------------- +WizardSubPanel *WizardPanel::GetSubPanelByName(const char *pageName) +{ + return dynamic_cast<WizardSubPanel *>(FindChildByName(pageName)); +} diff --git a/vgui2/vgui_controls/WizardSubPanel.cpp b/vgui2/vgui_controls/WizardSubPanel.cpp new file mode 100644 index 0000000..ae682cc --- /dev/null +++ b/vgui2/vgui_controls/WizardSubPanel.cpp @@ -0,0 +1,114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/WizardPanel.h" +#include "vgui_controls/WizardSubPanel.h" +#include "vgui_controls/BuildGroup.h" + +#include "KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +#include <stdio.h> +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +WizardSubPanel::WizardSubPanel(Panel *parent, const char *panelName) : EditablePanel(parent, panelName), _wizardPanel(NULL) +{ + SetVisible(false); + m_iDesiredWide = 0; + m_iDesiredTall = 0; + SetBuildGroup(GetBuildGroup()); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +WizardSubPanel::~WizardSubPanel() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardSubPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + SetBgColor(GetSchemeColor("WizardSubPanel.BgColor",pScheme)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardSubPanel::GetSettings( KeyValues *outResourceData ) +{ + BaseClass::GetSettings(outResourceData); + + outResourceData->SetInt("WizardWide", m_iDesiredWide); + outResourceData->SetInt("WizardTall", m_iDesiredTall); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void WizardSubPanel::ApplySettings(KeyValues *inResourceData) +{ + // don't adjust visiblity during settings application (since it's our parent who really controls it) + bool bVisible = IsVisible(); + + BaseClass::ApplySettings(inResourceData); + + m_iDesiredWide = inResourceData->GetInt("WizardWide", 0); + m_iDesiredTall = inResourceData->GetInt("WizardTall", 0); + + if (GetWizardPanel() && m_iDesiredWide && m_iDesiredTall) + { + GetWizardPanel()->SetSize(m_iDesiredWide, m_iDesiredTall); + } + + SetVisible(bVisible); +} + +//----------------------------------------------------------------------------- +// Purpose: build mode description +//----------------------------------------------------------------------------- +const char *WizardSubPanel::GetDescription() +{ + static char buf[1024]; + _snprintf(buf, sizeof(buf), "%s, int WizardWide, int WizardTall", BaseClass::GetDescription()); + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the size this subpanel would like the wizard to be +//----------------------------------------------------------------------------- +bool WizardSubPanel::GetDesiredSize(int &wide, int &tall) +{ + wide = m_iDesiredWide; + tall = m_iDesiredTall; + + return (m_iDesiredWide && m_iDesiredTall); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *WizardSubPanel::GetWizardData() +{ + return GetWizardPanel()->GetWizardData(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +WizardSubPanel *WizardSubPanel::GetSiblingSubPanelByName(const char *pageName) +{ + return GetWizardPanel()->GetSubPanelByName(pageName); +} diff --git a/vgui2/vgui_controls/consoledialog.cpp b/vgui2/vgui_controls/consoledialog.cpp new file mode 100644 index 0000000..d788abc --- /dev/null +++ b/vgui2/vgui_controls/consoledialog.cpp @@ -0,0 +1,1256 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "vgui_controls/consoledialog.h" + +#include "vgui/IInput.h" +#include "vgui/IScheme.h" +#include "vgui/IVGui.h" +#include "vgui/ISurface.h" +#include "vgui/ILocalize.h" +#include "KeyValues.h" + +#include "vgui_controls/Button.h" +#include "vgui/KeyCode.h" +#include "vgui_controls/Menu.h" +#include "vgui_controls/TextEntry.h" +#include "vgui_controls/RichText.h" +#include "tier1/convar.h" +#include "tier1/convar_serverbounded.h" +#include "icvar.h" +#include "filesystem.h" + +#include <stdlib.h> + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + + +//----------------------------------------------------------------------------- +// Used by the autocompletion system +//----------------------------------------------------------------------------- +class CNonFocusableMenu : public Menu +{ + DECLARE_CLASS_SIMPLE( CNonFocusableMenu, Menu ); + +public: + CNonFocusableMenu( Panel *parent, const char *panelName ) + : BaseClass( parent, panelName ), + m_pFocus( 0 ) + { + } + + void SetFocusPanel( Panel *panel ) + { + m_pFocus = panel; + } + + VPANEL GetCurrentKeyFocus() + { + if ( !m_pFocus ) + return GetVPanel(); + + return m_pFocus->GetVPanel(); + } + +private: + Panel *m_pFocus; +}; + + +//----------------------------------------------------------------------------- +// Purpose: forwards tab key presses up from the text entry so we can do autocomplete +//----------------------------------------------------------------------------- +class TabCatchingTextEntry : public TextEntry +{ +public: + TabCatchingTextEntry(Panel *parent, const char *name, VPANEL comp) : TextEntry(parent, name), m_pCompletionList( comp ) + { + SetAllowNonAsciiCharacters( true ); + SetDragEnabled( true ); + } + + virtual void OnKeyCodeTyped(KeyCode code) + { + if (code == KEY_TAB) + { + GetParent()->OnKeyCodeTyped(code); + } + else if ( code == KEY_ENTER ) + { + // submit is the default button whose click event will have been called already + } + else + { + TextEntry::OnKeyCodeTyped(code); + } + } + + virtual void OnKillFocus() + { + if ( input()->GetFocus() != m_pCompletionList ) // if its not the completion window trying to steal our focus + { + PostMessage(GetParent(), new KeyValues("CloseCompletionList")); + } + } + +private: + VPANEL m_pCompletionList; +}; + + + +// Things the user typed in and hit submit/return with +CHistoryItem::CHistoryItem( void ) +{ + m_text = NULL; + m_extraText = NULL; + m_bHasExtra = false; +} + +CHistoryItem::CHistoryItem( const char *text, const char *extra ) +{ + Assert( text ); + m_text = NULL; + m_extraText = NULL; + m_bHasExtra = false; + SetText( text , extra ); +} + +CHistoryItem::CHistoryItem( const CHistoryItem& src ) +{ + m_text = NULL; + m_extraText = NULL; + m_bHasExtra = false; + SetText( src.GetText(), src.GetExtra() ); +} + +CHistoryItem::~CHistoryItem( void ) +{ + delete[] m_text; + delete[] m_extraText; + m_text = NULL; +} + +const char *CHistoryItem::GetText() const +{ + if ( m_text ) + { + return m_text; + } + else + { + return ""; + } +} + +const char *CHistoryItem::GetExtra() const +{ + if ( m_extraText ) + { + return m_extraText; + } + else + { + return NULL; + } +} + +void CHistoryItem::SetText( const char *text, const char *extra ) +{ + delete[] m_text; + int len = strlen( text ) + 1; + + m_text = new char[ len ]; + Q_memset( m_text, 0x0, len ); + Q_strncpy( m_text, text, len ); + + if ( extra ) + { + m_bHasExtra = true; + delete[] m_extraText; + int elen = strlen( extra ) + 1; + m_extraText = new char[ elen ]; + Q_memset( m_extraText, 0x0, elen); + Q_strncpy( m_extraText, extra, elen ); + } + else + { + m_bHasExtra = false; + } +} + + +//----------------------------------------------------------------------------- +// +// Console page completion item starts here +// +//----------------------------------------------------------------------------- +CConsolePanel::CompletionItem::CompletionItem( void ) +{ + m_bIsCommand = true; + m_pCommand = NULL; + m_pText = NULL; +} + +CConsolePanel::CompletionItem::CompletionItem( const CompletionItem& src ) +{ + m_bIsCommand = src.m_bIsCommand; + m_pCommand = src.m_pCommand; + if ( src.m_pText ) + { + m_pText = new CHistoryItem( (const CHistoryItem& )src.m_pText ); + } + else + { + m_pText = NULL; + } +} + +CConsolePanel::CompletionItem& CConsolePanel::CompletionItem::operator =( const CompletionItem& src ) +{ + if ( this == &src ) + return *this; + + m_bIsCommand = src.m_bIsCommand; + m_pCommand = src.m_pCommand; + if ( src.m_pText ) + { + m_pText = new CHistoryItem( (const CHistoryItem& )*src.m_pText ); + } + else + { + m_pText = NULL; + } + + return *this; +} + +CConsolePanel::CompletionItem::~CompletionItem( void ) +{ + if ( m_pText ) + { + delete m_pText; + m_pText = NULL; + } +} + +const char *CConsolePanel::CompletionItem::GetName() const +{ + if ( m_bIsCommand ) + return m_pCommand->GetName(); + return m_pCommand ? m_pCommand->GetName() : GetCommand(); +} + +const char *CConsolePanel::CompletionItem::GetItemText( void ) +{ + static char text[256]; + text[0] = 0; + if ( m_pText ) + { + if ( m_pText->HasExtra() ) + { + Q_snprintf( text, sizeof( text ), "%s %s", m_pText->GetText(), m_pText->GetExtra() ); + } + else + { + Q_strncpy( text, m_pText->GetText(), sizeof( text ) ); + } + } + return text; +} + +const char *CConsolePanel::CompletionItem::GetCommand( void ) const +{ + static char text[256]; + text[0] = 0; + if ( m_pText ) + { + Q_strncpy( text, m_pText->GetText(), sizeof( text ) ); + } + return text; +} + + +//----------------------------------------------------------------------------- +// +// Console page starts here +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Purpose: Constructor, destuctor +//----------------------------------------------------------------------------- +CConsolePanel::CConsolePanel( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) : + BaseClass( pParent, pName ), m_bStatusVersion( bStatusVersion ) +{ + SetKeyBoardInputEnabled( true ); + + if ( !m_bStatusVersion ) + { + SetMinimumSize(100,100); + } + + // create controls + m_pHistory = new RichText(this, "ConsoleHistory"); + m_pHistory->SetAllowKeyBindingChainToParent( false ); + SETUP_PANEL( m_pHistory ); + m_pHistory->SetVerticalScrollbar( !m_bStatusVersion ); + if ( m_bStatusVersion ) + { + m_pHistory->SetDrawOffsets( 3, 3 ); + } + m_pHistory->GotoTextEnd(); + + m_pSubmit = new Button(this, "ConsoleSubmit", "#Console_Submit"); + m_pSubmit->SetCommand("submit"); + m_pSubmit->SetVisible( !m_bStatusVersion ); + + CNonFocusableMenu *pCompletionList = new CNonFocusableMenu( this, "CompletionList" ); + m_pCompletionList = pCompletionList; + m_pCompletionList->SetVisible(false); + + m_pEntry = new TabCatchingTextEntry(this, "ConsoleEntry", m_pCompletionList->GetVPanel() ); + m_pEntry->AddActionSignalTarget(this); + m_pEntry->SendNewLine(true); + pCompletionList->SetFocusPanel( m_pEntry ); + + // need to set up default colors, since ApplySchemeSettings won't be called until later + m_PrintColor = Color(216, 222, 211, 255); + m_DPrintColor = Color(196, 181, 80, 255); + + m_pEntry->SetTabPosition(1); + + m_bAutoCompleteMode = false; + m_szPartialText[0] = 0; + m_szPreviousPartialText[0]=0; + + // Add to global console list + g_pCVar->InstallConsoleDisplayFunc( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CConsolePanel::~CConsolePanel() +{ + ClearCompletionList(); + m_CommandHistory.Purge(); + g_pCVar->RemoveConsoleDisplayFunc( this ); +} + + +//----------------------------------------------------------------------------- +// Updates the completion list +//----------------------------------------------------------------------------- +void CConsolePanel::OnThink() +{ + BaseClass::OnThink(); + + if ( !IsVisible() ) + return; + + if ( !m_pCompletionList->IsVisible() ) + return; + + UpdateCompletionListPosition(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Clears the console +//----------------------------------------------------------------------------- +void CConsolePanel::Clear() +{ + m_pHistory->SetText(""); + m_pHistory->GotoTextEnd(); +} + + +//----------------------------------------------------------------------------- +// Purpose: color text print +//----------------------------------------------------------------------------- +void CConsolePanel::ColorPrint( const Color& clr, const char *msg ) +{ + if ( m_bStatusVersion ) + { + Clear(); + } + + m_pHistory->InsertColorChange( clr ); + m_pHistory->InsertString( msg ); +} + + +//----------------------------------------------------------------------------- +// Purpose: normal text print +//----------------------------------------------------------------------------- +void CConsolePanel::Print(const char *msg) +{ + ColorPrint( m_PrintColor, msg ); +} + + +//----------------------------------------------------------------------------- +// Purpose: debug text print +//----------------------------------------------------------------------------- +void CConsolePanel::DPrint( const char *msg ) +{ + ColorPrint( m_DPrintColor, msg ); +} + + +void CConsolePanel::ClearCompletionList() +{ + int c = m_CompletionList.Count(); + int i; + for ( i = c - 1; i >= 0; i-- ) + { + delete m_CompletionList[ i ]; + } + m_CompletionList.Purge(); +} + + +static ConCommand *FindAutoCompleteCommmandFromPartial( const char *partial ) +{ + char command[ 256 ]; + Q_strncpy( command, partial, sizeof( command ) ); + + char *space = Q_strstr( command, " " ); + if ( space ) + { + *space = 0; + } + + ConCommand *cmd = g_pCVar->FindCommand( command ); + if ( !cmd ) + return NULL; + + if ( !cmd->CanAutoComplete() ) + return NULL; + + return cmd; +} + + +//----------------------------------------------------------------------------- +// Purpose: rebuilds the list of possible completions from the current entered text +//----------------------------------------------------------------------------- +void CConsolePanel::RebuildCompletionList(const char *text) +{ + ClearCompletionList(); + + // we need the length of the text for the partial string compares + int len = Q_strlen(text); + if ( len < 1 ) + { + // Fill the completion list with history instead + for ( int i = 0 ; i < m_CommandHistory.Count(); i++ ) + { + CHistoryItem *item = &m_CommandHistory[ i ]; + CompletionItem *comp = new CompletionItem(); + m_CompletionList.AddToTail( comp ); + comp->m_bIsCommand = false; + comp->m_pCommand = NULL; + comp->m_pText = new CHistoryItem( *item ); + } + return; + } + + bool bNormalBuild = true; + + // if there is a space in the text, and the command isn't of the type to know how to autocomplet, then command completion is over + const char *space = strstr( text, " " ); + if ( space ) + { + ConCommand *pCommand = FindAutoCompleteCommmandFromPartial( text ); + if ( !pCommand ) + return; + + bNormalBuild = false; + + CUtlVector< CUtlString > commands; + int count = pCommand->AutoCompleteSuggest( text, commands ); + Assert( count <= COMMAND_COMPLETION_MAXITEMS ); + int i; + + for ( i = 0; i < count; i++ ) + { + // match found, add to list + CompletionItem *item = new CompletionItem(); + m_CompletionList.AddToTail( item ); + item->m_bIsCommand = false; + item->m_pCommand = NULL; + item->m_pText = new CHistoryItem( commands[ i ].String() ); + } + } + + if ( bNormalBuild ) + { + // look through the command list for all matches + ConCommandBase const *cmd = (ConCommandBase const *)cvar->GetCommands(); + while (cmd) + { + if ( cmd->IsFlagSet( FCVAR_DEVELOPMENTONLY ) || cmd->IsFlagSet( FCVAR_HIDDEN ) ) + { + cmd = cmd->GetNext(); + continue; + } + + if ( !strnicmp(text, cmd->GetName(), len)) + { + // match found, add to list + CompletionItem *item = new CompletionItem(); + m_CompletionList.AddToTail( item ); + item->m_pCommand = (ConCommandBase *)cmd; + const char *tst = cmd->GetName(); + if ( !cmd->IsCommand() ) + { + item->m_bIsCommand = false; + ConVar *var = ( ConVar * )cmd; + ConVar_ServerBounded *pBounded = dynamic_cast<ConVar_ServerBounded*>( var ); + if ( pBounded || var->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) + { + char strValue[512]; + + int intVal = pBounded ? pBounded->GetInt() : var->GetInt(); + float floatVal = pBounded ? pBounded->GetFloat() : var->GetFloat(); + + if ( floatVal == intVal ) + Q_snprintf( strValue, sizeof( strValue ), "%d", intVal ); + else + Q_snprintf( strValue, sizeof( strValue ), "%f", floatVal ); + + item->m_pText = new CHistoryItem( var->GetName(), strValue ); + } + else + { + item->m_pText = new CHistoryItem( var->GetName(), var->GetString() ); + } + } + else + { + item->m_bIsCommand = true; + item->m_pText = new CHistoryItem( tst ); + } + } + + cmd = cmd->GetNext(); + } + + // Now sort the list by command name + if ( m_CompletionList.Count() >= 2 ) + { + for ( int i = 0 ; i < m_CompletionList.Count(); i++ ) + { + for ( int j = i + 1; j < m_CompletionList.Count(); j++ ) + { + const CompletionItem *i1, *i2; + i1 = m_CompletionList[ i ]; + i2 = m_CompletionList[ j ]; + + if ( Q_stricmp( i1->GetName(), i2->GetName() ) > 0 ) + { + CompletionItem *temp = m_CompletionList[ i ]; + m_CompletionList[ i ] = m_CompletionList[ j ]; + m_CompletionList[ j ] = temp; + } + } + } + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: auto completes current text +//----------------------------------------------------------------------------- +void CConsolePanel::OnAutoComplete(bool reverse) +{ + if (!m_bAutoCompleteMode) + { + // we're not in auto-complete mode, Start + m_iNextCompletion = 0; + m_bAutoCompleteMode = true; + } + + // if we're in reverse, move back to before the current + if (reverse) + { + m_iNextCompletion -= 2; + if (m_iNextCompletion < 0) + { + // loop around in reverse + m_iNextCompletion = m_CompletionList.Size() - 1; + } + } + + // get the next completion + if (!m_CompletionList.IsValidIndex(m_iNextCompletion)) + { + // loop completion list + m_iNextCompletion = 0; + } + + // make sure everything is still valid + if (!m_CompletionList.IsValidIndex(m_iNextCompletion)) + return; + + // match found, set text + char completedText[256]; + CompletionItem *item = m_CompletionList[m_iNextCompletion]; + Assert( item ); + + if ( !item->m_bIsCommand && item->m_pCommand ) + { + Q_strncpy(completedText, item->GetCommand(), sizeof(completedText) - 2 ); + } + else + { + Q_strncpy(completedText, item->GetItemText(), sizeof(completedText) - 2 ); + } + + if ( !Q_strstr( completedText, " " ) ) + { + Q_strncat(completedText, " ", sizeof(completedText), COPY_ALL_CHARACTERS ); + } + + m_pEntry->SetText(completedText); + m_pEntry->GotoTextEnd(); + m_pEntry->SelectNone(); + + m_iNextCompletion++; +} + + +//----------------------------------------------------------------------------- +// Purpose: Called whenever the user types text +//----------------------------------------------------------------------------- +void CConsolePanel::OnTextChanged(Panel *panel) +{ + if (panel != m_pEntry) + return; + + Q_strncpy( m_szPreviousPartialText, m_szPartialText, sizeof( m_szPreviousPartialText ) ); + + // get the partial text the user type + m_pEntry->GetText(m_szPartialText, sizeof(m_szPartialText)); + + // see if they've hit the tilde key (which opens & closes the console) + int len = Q_strlen(m_szPartialText); + + bool hitTilde = ( m_szPartialText[len - 1] == '~' || m_szPartialText[len - 1] == '`' ) ? true : false; + + bool altKeyDown = ( vgui::input()->IsKeyDown( KEY_LALT ) || vgui::input()->IsKeyDown( KEY_RALT ) ) ? true : false; + bool ctrlKeyDown = ( vgui::input()->IsKeyDown( KEY_LCONTROL ) || vgui::input()->IsKeyDown( KEY_RCONTROL ) ) ? true : false; + + // Alt-Tilde toggles Japanese IME on/off!!! + if ( ( len > 0 ) && hitTilde ) + { + // Strip the last character (tilde) + m_szPartialText[ len - 1 ] = L'\0'; + + if( !altKeyDown && !ctrlKeyDown ) + { + m_pEntry->SetText( "" ); + + // close the console + PostMessage( this, new KeyValues( "Close" ) ); + PostActionSignal( new KeyValues( "ClosedByHittingTilde" ) ); + } + else + { + m_pEntry->SetText( m_szPartialText ); + } + return; + } + + // clear auto-complete state since the user has typed + m_bAutoCompleteMode = false; + + RebuildCompletionList(m_szPartialText); + + // build the menu + if ( m_CompletionList.Count() < 1 ) + { + m_pCompletionList->SetVisible(false); + } + else + { + m_pCompletionList->SetVisible(true); + m_pCompletionList->DeleteAllItems(); + const int MAX_MENU_ITEMS = 10; + + // add the first ten items to the list + for (int i = 0; i < m_CompletionList.Count() && i < MAX_MENU_ITEMS; i++) + { + char text[256]; + text[0] = 0; + if (i == MAX_MENU_ITEMS - 1) + { + Q_strncpy(text, "...", sizeof( text ) ); + } + else + { + Assert( m_CompletionList[i] ); + Q_strncpy(text, m_CompletionList[i]->GetItemText(), sizeof( text ) ); + } + KeyValues *kv = new KeyValues("CompletionCommand"); + kv->SetString("command",text); + m_pCompletionList->AddMenuItem(text, kv, this); + } + + UpdateCompletionListPosition(); + } + + RequestFocus(); + m_pEntry->RequestFocus(); + +} + +//----------------------------------------------------------------------------- +// Purpose: generic vgui command handler +//----------------------------------------------------------------------------- +void CConsolePanel::OnCommand(const char *command) +{ + if ( !Q_stricmp( command, "Submit" ) ) + { + // submit the entry as a console commmand + char szCommand[256]; + m_pEntry->GetText(szCommand, sizeof(szCommand)); + PostActionSignal( new KeyValues( "CommandSubmitted", "command", szCommand ) ); + + // add to the history + Print("] "); + Print(szCommand); + Print("\n"); + + // clear the field + m_pEntry->SetText(""); + + // clear the completion state + OnTextChanged(m_pEntry); + + // always go the end of the buffer when the user has typed something + m_pHistory->GotoTextEnd(); + + // Add the command to the history + char *extra = strchr(szCommand, ' '); + if ( extra ) + { + *extra = '\0'; + extra++; + } + + if ( Q_strlen( szCommand ) > 0 ) + { + AddToHistory( szCommand, extra ); + } + m_pCompletionList->SetVisible(false); + } + else + { + BaseClass::OnCommand(command); + } +} + + +//----------------------------------------------------------------------------- +// Focus related methods +//----------------------------------------------------------------------------- +bool CConsolePanel::TextEntryHasFocus() const +{ + return ( input()->GetFocus() == m_pEntry->GetVPanel() ); +} + +void CConsolePanel::TextEntryRequestFocus() +{ + m_pEntry->RequestFocus(); +} + + +//----------------------------------------------------------------------------- +// Purpose: swallows tab key pressed +//----------------------------------------------------------------------------- +void CConsolePanel::OnKeyCodeTyped(KeyCode code) +{ + BaseClass::OnKeyCodeTyped(code); + + // check for processing + if ( TextEntryHasFocus() ) + { + if (code == KEY_TAB) + { + bool reverse = false; + if (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)) + { + reverse = true; + } + + // attempt auto-completion + OnAutoComplete(reverse); + m_pEntry->RequestFocus(); + } + else if (code == KEY_DOWN) + { + OnAutoComplete(false); + // UpdateCompletionListPosition(); + // m_pCompletionList->SetVisible(true); + + m_pEntry->RequestFocus(); + } + else if (code == KEY_UP) + { + OnAutoComplete(true); + m_pEntry->RequestFocus(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: lays out controls +//----------------------------------------------------------------------------- +void CConsolePanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + // setup tab ordering + GetFocusNavGroup().SetDefaultButton(m_pSubmit); + + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + m_pEntry->SetBorder(pScheme->GetBorder("DepressedButtonBorder")); + m_pHistory->SetBorder(pScheme->GetBorder("DepressedButtonBorder")); + + // layout controls + int wide, tall; + GetSize(wide, tall); + + if ( !m_bStatusVersion ) + { + const int inset = 8; + const int entryHeight = 24; + const int topHeight = 4; + const int entryInset = 4; + const int submitWide = 64; + const int submitInset = 7; // x inset to pull the submit button away from the frame grab + + m_pHistory->SetPos(inset, inset + topHeight); + m_pHistory->SetSize(wide - (inset * 2), tall - (entryInset * 2 + inset * 2 + topHeight + entryHeight)); + m_pHistory->InvalidateLayout(); + + int nSubmitXPos = wide - ( inset + submitWide + submitInset ); + m_pSubmit->SetPos( nSubmitXPos, tall - (entryInset * 2 + entryHeight)); + m_pSubmit->SetSize( submitWide, entryHeight); + + m_pEntry->SetPos( inset, tall - (entryInset * 2 + entryHeight) ); + m_pEntry->SetSize( nSubmitXPos - entryInset - 2 * inset, entryHeight); + } + else + { + const int inset = 2; + + int entryWidth = wide / 2; + if ( wide > 400 ) + { + entryWidth = 200; + } + + m_pEntry->SetBounds( inset, inset, entryWidth, tall - 2 * inset ); + + m_pHistory->SetBounds( inset + entryWidth + inset, inset, ( wide - entryWidth ) - inset, tall - 2 * inset ); + } + + UpdateCompletionListPosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the position of the completion list popup +//----------------------------------------------------------------------------- +void CConsolePanel::UpdateCompletionListPosition() +{ + int ex, ey; + m_pEntry->GetPos(ex, ey); + + if ( !m_bStatusVersion ) + { + // Position below text entry + ey += m_pEntry->GetTall(); + } + else + { + // Position above text entry + int menuwide, menutall; + m_pCompletionList->GetSize( menuwide, menutall ); + ey -= ( menutall + 4 ); + } + + LocalToScreen( ex, ey ); + m_pCompletionList->SetPos( ex, ey ); + + if ( m_pCompletionList->IsVisible() ) + { + m_pEntry->RequestFocus(); + MoveToFront(); + m_pCompletionList->MoveToFront(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Closes the completion list +//----------------------------------------------------------------------------- +void CConsolePanel::CloseCompletionList() +{ + m_pCompletionList->SetVisible(false); +} + +//----------------------------------------------------------------------------- +// Purpose: sets up colors +//----------------------------------------------------------------------------- +void CConsolePanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + m_PrintColor = GetSchemeColor("Console.TextColor", pScheme); + m_DPrintColor = GetSchemeColor("Console.DevTextColor", pScheme); + m_pHistory->SetFont( pScheme->GetFont( "ConsoleText", IsProportional() ) ); + m_pCompletionList->SetFont( pScheme->GetFont( "DefaultSmall", IsProportional() ) ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles autocompletion menu input +//----------------------------------------------------------------------------- +void CConsolePanel::OnMenuItemSelected(const char *command) +{ + if ( strstr( command, "..." ) ) // stop the menu going away if you click on ... + { + m_pCompletionList->SetVisible( true ); + } + else + { + m_pEntry->SetText(command); + m_pEntry->GotoTextEnd(); + m_pEntry->InsertChar(' '); + m_pEntry->GotoTextEnd(); + } +} + +void CConsolePanel::Hide() +{ + OnClose(); + m_iNextCompletion = 0; + RebuildCompletionList(""); +} + +void CConsolePanel::AddToHistory( const char *commandText, const char *extraText ) +{ + // Newest at end, oldest at head + while ( m_CommandHistory.Count() >= MAX_HISTORY_ITEMS ) + { + // Remove from head until size is reasonable + m_CommandHistory.Remove( 0 ); + } + + // strip the space off the end of the command before adding it to the history + // If this code gets cleaned up then we should remove the redundant calls to strlen, + // the check for whether _alloca succeeded, and should use V_strncpy instead of the + // error prone memset/strncpy sequence. + char *command = static_cast<char *>( _alloca( (strlen( commandText ) + 1 ) * sizeof( char ) )); + if ( command ) + { + memset( command, 0x0, strlen( commandText ) + 1 ); + strncpy( command, commandText, strlen( commandText )); + // There is no actual bug here, just some sloppy/odd code. + // src\vgui2\vgui_controls\consoledialog.cpp(974): warning C6053: The prior call to 'strncpy' might not zero-terminate string 'command' + ANALYZE_SUPPRESS( 6053 ) + if ( command[ strlen( command ) -1 ] == ' ' ) + { + command[ strlen( command ) -1 ] = '\0'; + } + } + + // strip the quotes off the extra text + char *extra = NULL; + + if ( extraText ) + { + extra = static_cast<char *>( malloc( (strlen( extraText ) + 1 ) * sizeof( char ) )); + if ( extra ) + { + memset( extra, 0x0, strlen( extraText ) + 1 ); + strncpy( extra, extraText, strlen( extraText )); // +1 to dodge the starting quote + + // Strip trailing spaces + int i = strlen( extra ) - 1; + while ( i >= 0 && // Check I before referencing i == -1 into the extra array! + extra[ i ] == ' ' ) + { + extra[ i ] = '\0'; + i--; + } + } + } + + // If it's already there, then remove since we'll add it to the end instead + CHistoryItem *item = NULL; + for ( int i = m_CommandHistory.Count() - 1; i >= 0; i-- ) + { + item = &m_CommandHistory[ i ]; + if ( !item ) + continue; + + if ( stricmp( item->GetText(), command ) ) + continue; + + if ( extra || item->GetExtra() ) + { + if ( !extra || !item->GetExtra() ) + continue; + + // stricmp so two commands with the same starting text get added + if ( stricmp( item->GetExtra(), extra ) ) + continue; + } + m_CommandHistory.Remove( i ); + } + + item = &m_CommandHistory[ m_CommandHistory.AddToTail() ]; + Assert( item ); + item->SetText( command, extra ); + + m_iNextCompletion = 0; + RebuildCompletionList( m_szPartialText ); + + free( extra ); +} + +void CConsolePanel::GetConsoleText( char *pchText, size_t bufSize ) const +{ + wchar_t *temp = new wchar_t[ bufSize ]; + m_pHistory->GetText( 0, temp, bufSize * sizeof( wchar_t ) ); + g_pVGuiLocalize->ConvertUnicodeToANSI( temp, pchText, bufSize ); + delete[] temp; +} + +//----------------------------------------------------------------------------- +// Purpose: writes out console to disk +//----------------------------------------------------------------------------- +void CConsolePanel::DumpConsoleTextToFile() +{ + const int CONDUMP_FILES_MAX_NUM = 1000; + + FileHandle_t handle; + bool found = false; + char szfile[ 512 ]; + + // we don't want to overwrite other condump.txt files + for ( int i = 0 ; i < CONDUMP_FILES_MAX_NUM ; ++i ) + { + _snprintf( szfile, sizeof(szfile), "condump%03d.txt", i ); + if ( !g_pFullFileSystem->FileExists(szfile) ) + { + found = true; + break; + } + } + + if ( !found ) + { + Print( "Can't condump! Too many existing condump output files in the gamedir!\n" ); + return; + } + + handle = g_pFullFileSystem->Open( szfile, "wb" ); + if ( handle != FILESYSTEM_INVALID_HANDLE ) + { + int pos = 0; + while (1) + { + wchar_t buf[512]; + m_pHistory->GetText(pos, buf, sizeof(buf)); + pos += sizeof(buf) / sizeof(wchar_t); + + // don't continue if none left + if (buf[0] == 0) + break; + + // convert to ansi + char ansi[512]; + g_pVGuiLocalize->ConvertUnicodeToANSI(buf, ansi, sizeof(ansi)); + + // write to disk + int len = strlen(ansi); + for (int i = 0; i < len; i++) + { + // preceed newlines with a return + if (ansi[i] == '\n') + { + char ret = '\r'; + g_pFullFileSystem->Write( &ret, 1, handle ); + } + + g_pFullFileSystem->Write( ansi + i, 1, handle ); + } + } + + g_pFullFileSystem->Close( handle ); + + Print( "console dumped to " ); + Print( szfile ); + Print( "\n" ); + } + else + { + Print( "Unable to condump to " ); + Print( szfile ); + Print( "\n" ); + } +} + + +//----------------------------------------------------------------------------- +// +// Console dialog starts here +// +//----------------------------------------------------------------------------- +CConsoleDialog::CConsoleDialog( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) : + BaseClass( pParent, pName ) +{ + // initialize dialog + SetVisible( false ); + SetTitle( "#Console_Title", true ); + m_pConsolePanel = new CConsolePanel( this, "ConsolePage", bStatusVersion ); + m_pConsolePanel->AddActionSignalTarget( this ); +} + +void CConsoleDialog::OnScreenSizeChanged( int iOldWide, int iOldTall ) +{ + BaseClass::OnScreenSizeChanged( iOldWide, iOldTall ); + + int sx, sy; + surface()->GetScreenSize( sx, sy ); + + int w, h; + GetSize( w, h ); + if ( w > sx || h > sy ) + { + if ( w > sx ) + { + w = sx; + } + if ( h > sy ) + { + h = sy; + } + + // Try and lower the size to match the screen bounds + SetSize( w, h ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: brings dialog to the fore +//----------------------------------------------------------------------------- +void CConsoleDialog::PerformLayout() +{ + BaseClass::PerformLayout(); + + int x, y, w, h; + GetClientArea( x, y, w, h ); + m_pConsolePanel->SetBounds( x, y, w, h ); +} + + +//----------------------------------------------------------------------------- +// Purpose: brings dialog to the fore +//----------------------------------------------------------------------------- +void CConsoleDialog::Activate() +{ + BaseClass::Activate(); + m_pConsolePanel->m_pEntry->RequestFocus(); +} + + +//----------------------------------------------------------------------------- +// Hides the dialog +//----------------------------------------------------------------------------- +void CConsoleDialog::Hide() +{ + OnClose(); + m_pConsolePanel->Hide(); +} + + +//----------------------------------------------------------------------------- +// Close just hides the dialog +//----------------------------------------------------------------------------- +void CConsoleDialog::Close() +{ + Hide(); +} + + +//----------------------------------------------------------------------------- +// Submits commands +//----------------------------------------------------------------------------- +void CConsoleDialog::OnCommandSubmitted( const char *pCommand ) +{ + PostActionSignal( new KeyValues( "CommandSubmitted", "command", pCommand ) ); +} + + +//----------------------------------------------------------------------------- +// Chain to the page +//----------------------------------------------------------------------------- +void CConsoleDialog::Print( const char *pMessage ) +{ + m_pConsolePanel->Print( pMessage ); +} + +void CConsoleDialog::DPrint( const char *pMessage ) +{ + m_pConsolePanel->DPrint( pMessage ); +} + +void CConsoleDialog::ColorPrint( const Color& clr, const char *msg ) +{ + m_pConsolePanel->ColorPrint( clr, msg ); +} + +void CConsoleDialog::Clear() +{ + m_pConsolePanel->Clear( ); +} + +void CConsoleDialog::DumpConsoleTextToFile() +{ + m_pConsolePanel->DumpConsoleTextToFile( ); +} + + +void CConsoleDialog::OnKeyCodePressed( vgui::KeyCode code ) +{ + if ( code == KEY_XBUTTON_B ) + { + Hide(); + } + else + { + BaseClass::OnKeyCodePressed(code); + } +}
\ No newline at end of file diff --git a/vgui2/vgui_controls/controls.cpp b/vgui2/vgui_controls/controls.cpp new file mode 100644 index 0000000..edc89e4 --- /dev/null +++ b/vgui2/vgui_controls/controls.cpp @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include <vgui_controls/Controls.h> +#include <locale.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern int g_nYou_Must_Add_Public_Vgui_Controls_Vgui_ControlsCpp_To_Your_Project; + +namespace vgui +{ + +static char g_szControlsModuleName[256]; + +//----------------------------------------------------------------------------- +// Purpose: Initializes the controls +//----------------------------------------------------------------------------- +extern "C" { extern int _heapmin(); } + +bool VGui_InitInterfacesList( const char *moduleName, CreateInterfaceFn *factoryList, int numFactories ) +{ + g_nYou_Must_Add_Public_Vgui_Controls_Vgui_ControlsCpp_To_Your_Project = 1; + + // If you hit this error, then you need to include memoverride.cpp in the project somewhere or else + // you'll get crashes later when vgui_controls allocates KeyValues and vgui tries to delete them. +#if !defined(NO_MALLOC_OVERRIDE) && defined( WIN32 ) + if ( _heapmin() != 1 ) + { + Assert( false ); + Error( "Must include memoverride.cpp in your project." ); + } +#endif + // keep a record of this module name + strncpy(g_szControlsModuleName, moduleName, sizeof(g_szControlsModuleName)); + g_szControlsModuleName[sizeof(g_szControlsModuleName) - 1] = 0; + + // initialize our locale (must be done for every vgui dll/exe) + // "" makes it use the default locale, required to make iswprint() work correctly in different languages + setlocale(LC_CTYPE, ""); + setlocale(LC_TIME, ""); + setlocale(LC_COLLATE, ""); + setlocale(LC_MONETARY, ""); + + // NOTE: Vgui expects to use these interfaces which are defined in tier3.lib + if ( !g_pVGui || !g_pVGuiInput || !g_pVGuiPanel || + !g_pVGuiSurface || !g_pVGuiSchemeManager || !g_pVGuiSystem ) + { + Warning( "vgui_controls is missing a required interface!\n" ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the name of the module this has been compiled into +//----------------------------------------------------------------------------- +const char *GetControlsModuleName() +{ + return g_szControlsModuleName; +} + +} // namespace vgui + + + diff --git a/vgui2/vgui_controls/cvartogglecheckbutton.cpp b/vgui2/vgui_controls/cvartogglecheckbutton.cpp new file mode 100644 index 0000000..0db6deb --- /dev/null +++ b/vgui2/vgui_controls/cvartogglecheckbutton.cpp @@ -0,0 +1,25 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier1/KeyValues.h" + +#include <vgui/ISurface.h> +#include <vgui/IScheme.h> +#include <vgui_controls/cvartogglecheckbutton.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +using namespace vgui; + +vgui::Panel *Create_CvarToggleCheckButton() +{ + return new CvarToggleCheckButton< ConVarRef >( NULL, NULL ); +} + +DECLARE_BUILD_FACTORY_CUSTOM_ALIAS( CvarToggleCheckButton<ConVarRef>, CvarToggleCheckButton, Create_CvarToggleCheckButton ); + diff --git a/vgui2/vgui_controls/perforcefilelistframe.cpp b/vgui2/vgui_controls/perforcefilelistframe.cpp new file mode 100644 index 0000000..b6abbf6 --- /dev/null +++ b/vgui2/vgui_controls/perforcefilelistframe.cpp @@ -0,0 +1,647 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// List of perforce files and operations +// +//============================================================================= + +#include "vgui_controls/perforcefilelistframe.h" +#include "tier1/KeyValues.h" +#include "vgui_controls/Button.h" +#include "vgui_controls/ListPanel.h" +#include "vgui_controls/Splitter.h" +#include "vgui_controls/TextEntry.h" +#include "vgui_controls/MessageBox.h" +#include "tier2/tier2.h" +#include "p4lib/ip4.h" +#include "filesystem.h" +#include "vgui/IVGui.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Sort by asset name +//----------------------------------------------------------------------------- +static int __cdecl OperationSortFunc( vgui::ListPanel *pPanel, const vgui::ListPanelItem &item1, const vgui::ListPanelItem &item2 ) +{ + const char *string1 = item1.kv->GetString("operation"); + const char *string2 = item2.kv->GetString("operation"); + int nRetVal = Q_stricmp( string1, string2 ); + if ( nRetVal != 0 ) + return nRetVal; + + string1 = item1.kv->GetString("filename"); + string2 = item2.kv->GetString("filename"); + return Q_stricmp( string1, string2 ); +} + +static int __cdecl FileBrowserSortFunc( vgui::ListPanel *pPanel, const vgui::ListPanelItem &item1, const vgui::ListPanelItem &item2 ) +{ + const char *string1 = item1.kv->GetString("filename"); + const char *string2 = item2.kv->GetString("filename"); + return Q_stricmp( string1, string2 ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +COperationFileListFrame::COperationFileListFrame( vgui::Panel *pParent, const char *pTitle, const char *pColumnHeader, bool bShowDescription, bool bShowOkOnly, int nDialogID ) : + BaseClass( pParent, "PerforceFileList" ) +{ + m_pText = NULL; + vgui::Panel *pBrowserParent = this; + + m_pDescription = NULL; + m_pSplitter = NULL; + if ( bShowDescription ) + { + m_pSplitter = new vgui::Splitter( this, "Splitter", vgui::SPLITTER_MODE_HORIZONTAL, 1 ); + + pBrowserParent = m_pSplitter->GetChild( 0 ); + vgui::Panel *pDescParent = m_pSplitter->GetChild( 1 ); + + m_pDescription = new vgui::TextEntry( pDescParent, "Description" ); + m_pDescription->SetMultiline( true ); + m_pDescription->SetCatchEnterKey( true ); + m_pDescription->SetText( "<enter description here>" ); + } + + // FIXME: Might be nice to have checkboxes per row + m_pFileBrowser = new vgui::ListPanel( pBrowserParent, "Browser" ); + m_pFileBrowser->AddColumnHeader( 0, "operation", "Operation", 52, 0 ); + m_pFileBrowser->AddColumnHeader( 1, "filename", pColumnHeader, 128, vgui::ListPanel::COLUMN_RESIZEWITHWINDOW ); + m_pFileBrowser->SetSelectIndividualCells( false ); + m_pFileBrowser->SetMultiselectEnabled( false ); + m_pFileBrowser->SetEmptyListText( "No Perforce Operations" ); + m_pFileBrowser->SetDragEnabled( true ); + m_pFileBrowser->AddActionSignalTarget( this ); + m_pFileBrowser->SetSortFunc( 0, OperationSortFunc ); + m_pFileBrowser->SetSortFunc( 1, FileBrowserSortFunc ); + m_pFileBrowser->SetSortColumn( 0 ); + + m_pYesButton = new vgui::Button( this, "YesButton", "Yes", this, "Yes" ); + m_pNoButton = new vgui::Button( this, "NoButton", "No", this, "No" ); + + SetBlockDragChaining( true ); + SetDeleteSelfOnClose( true ); + + if ( bShowDescription ) + { + LoadControlSettingsAndUserConfig( "resource/perforcefilelistdescription.res", nDialogID ); + } + else + { + LoadControlSettingsAndUserConfig( "resource/perforcefilelist.res", nDialogID ); + } + + if ( bShowOkOnly ) + { + m_pYesButton->SetText( "#MessageBox_OK" ); + m_pNoButton->SetVisible( false ); + } + + m_pContextKeyValues = NULL; + + SetTitle( pTitle, false ); +} + +COperationFileListFrame::~COperationFileListFrame() +{ + SaveUserConfig(); + CleanUpMessage(); + if ( m_pText ) + { + delete[] m_pText; + } +} + + +//----------------------------------------------------------------------------- +// Deletes the message +//----------------------------------------------------------------------------- +void COperationFileListFrame::CleanUpMessage() +{ + if ( m_pContextKeyValues ) + { + m_pContextKeyValues->deleteThis(); + m_pContextKeyValues = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Performs layout +//----------------------------------------------------------------------------- +void COperationFileListFrame::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( m_pSplitter ) + { + int x, y, w, h; + GetClientArea( x, y, w, h ); + y += 6; + h -= 36; + m_pSplitter->SetBounds( x, y, w, h ); + } +} + + +//----------------------------------------------------------------------------- +// Adds files to the frame +//----------------------------------------------------------------------------- +void COperationFileListFrame::ClearAllOperations() +{ + m_pFileBrowser->RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Adds the strings to the list panel +//----------------------------------------------------------------------------- +void COperationFileListFrame::AddOperation( const char *pOperation, const char *pFileName ) +{ + KeyValues *kv = new KeyValues( "node", "filename", pFileName ); + kv->SetString( "operation", pOperation ); + m_pFileBrowser->AddItem( kv, 0, false, false ); +} + +void COperationFileListFrame::AddOperation( const char *pOperation, const char *pFileName, const Color& clr ) +{ + KeyValues *kv = new KeyValues( "node", "filename", pFileName ); + kv->SetString( "operation", pOperation ); + kv->SetColor( "cellcolor", clr ); + m_pFileBrowser->AddItem( kv, 0, false, false ); +} + + +//----------------------------------------------------------------------------- +// Resizes the operation column to fit the operation text +//----------------------------------------------------------------------------- +void COperationFileListFrame::ResizeOperationColumnToContents() +{ + m_pFileBrowser->ResizeColumnToContents( 0 ); +} + + +//----------------------------------------------------------------------------- +// Sets the column header for the 'operation' column +//----------------------------------------------------------------------------- +void COperationFileListFrame::SetOperationColumnHeaderText( const char *pText ) +{ + m_pFileBrowser->SetColumnHeaderText( 0, pText ); +} + + +//----------------------------------------------------------------------------- +// Adds the strings to the list panel +//----------------------------------------------------------------------------- +void COperationFileListFrame::DoModal( KeyValues *pContextKeyValues, const char *pMessage ) +{ + m_MessageName = pMessage ? pMessage : "OperationConfirmed"; + CleanUpMessage(); + m_pContextKeyValues = pContextKeyValues; + m_pFileBrowser->SortList(); + if ( m_pNoButton->IsVisible() ) + { + m_pYesButton->SetEnabled( m_pFileBrowser->GetItemCount() != 0 ); + } + BaseClass::DoModal(); +} + + +//----------------------------------------------------------------------------- +// Retrieves the number of files, the file names, and operations +//----------------------------------------------------------------------------- +int COperationFileListFrame::GetOperationCount() +{ + return m_pFileBrowser->GetItemCount(); +} + +const char *COperationFileListFrame::GetFileName( int i ) +{ + int nItemId = m_pFileBrowser->GetItemIDFromRow( i ); + KeyValues *pKeyValues = m_pFileBrowser->GetItem( nItemId ); + return pKeyValues->GetString( "filename" ); +} + +const char *COperationFileListFrame::GetOperation( int i ) +{ + int nItemId = m_pFileBrowser->GetItemIDFromRow( i ); + KeyValues *pKeyValues = m_pFileBrowser->GetItem( nItemId ); + return pKeyValues->GetString( "operation" ); +} + + +//----------------------------------------------------------------------------- +// Retreives the description (only if it was shown) +//----------------------------------------------------------------------------- +const char *COperationFileListFrame::GetDescription() +{ + return m_pText; +} + + +//----------------------------------------------------------------------------- +// Returns the message name +//----------------------------------------------------------------------------- +const char *COperationFileListFrame::CompletionMessage() +{ + return m_MessageName; +} + + +//----------------------------------------------------------------------------- +// On command +//----------------------------------------------------------------------------- +void COperationFileListFrame::OnCommand( const char *pCommand ) +{ + if ( !Q_stricmp( pCommand, "Yes" ) ) + { + if ( m_pDescription ) + { + int nLen = m_pDescription->GetTextLength() + 1; + m_pText = new char[ nLen ]; + m_pDescription->GetText( m_pText, nLen ); + } + + KeyValues *pActionKeys; + if ( PerformOperation() ) + { + pActionKeys = new KeyValues( CompletionMessage(), "operationPerformed", 1 ); + } + else + { + pActionKeys = new KeyValues( CompletionMessage(), "operationPerformed", 0 ); + } + + if ( m_pContextKeyValues ) + { + pActionKeys->AddSubKey( m_pContextKeyValues ); + m_pContextKeyValues = NULL; + } + CloseModal(); + PostActionSignal( pActionKeys ); + return; + } + + if ( !Q_stricmp( pCommand, "No" ) ) + { + KeyValues *pActionKeys = new KeyValues( CompletionMessage(), "operationPerformed", 0 ); + if ( m_pContextKeyValues ) + { + pActionKeys->AddSubKey( m_pContextKeyValues ); + m_pContextKeyValues = NULL; + } + CloseModal(); + PostActionSignal( pActionKeys ); + return; + } + + BaseClass::OnCommand( pCommand ); +} + + +//----------------------------------------------------------------------------- +// +// Version that does the work of perforce actions +// +//----------------------------------------------------------------------------- +CPerforceFileListFrame::CPerforceFileListFrame( vgui::Panel *pParent, const char *pTitle, const char *pColumnHeader, PerforceAction_t action ) : + BaseClass( pParent, pTitle, pColumnHeader, (action == PERFORCE_ACTION_FILE_SUBMIT), false, OPERATION_DIALOG_ID_PERFORCE ) +{ + m_Action = action; +} + +CPerforceFileListFrame::~CPerforceFileListFrame() +{ +} + + +//----------------------------------------------------------------------------- +// Activates the modal dialog +//----------------------------------------------------------------------------- +void CPerforceFileListFrame::DoModal( KeyValues *pContextKeys, const char *pMessage ) +{ + BaseClass::DoModal( pContextKeys, pMessage ? pMessage : "PerforceActionConfirmed" ); +} + + +//----------------------------------------------------------------------------- +// Adds a file for open +//----------------------------------------------------------------------------- +void CPerforceFileListFrame::AddFileForOpen( const char *pFullPath ) +{ + if ( !p4 ) + return; + + bool bIsInPerforce = p4->IsFileInPerforce( pFullPath ); + bool bIsOpened = ( p4->GetFileState( pFullPath ) != P4FILE_UNOPENED ); + switch( m_Action ) + { + case PERFORCE_ACTION_FILE_ADD: + if ( !bIsInPerforce && !bIsOpened ) + { + AddOperation( "Add", pFullPath ); + } + break; + + case PERFORCE_ACTION_FILE_EDIT: + if ( bIsInPerforce && !bIsOpened ) + { + AddOperation( "Edit", pFullPath ); + } + break; + + case PERFORCE_ACTION_FILE_DELETE: + if ( bIsInPerforce && !bIsOpened ) + { + AddOperation( "Delete", pFullPath ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Add files to dialog for submit/revert dialogs +//----------------------------------------------------------------------------- +void CPerforceFileListFrame::AddFileForSubmit( const char *pFullPath, P4FileState_t state ) +{ + if ( state == P4FILE_UNOPENED ) + return; + + char pBuf[128]; + const char *pPrefix = (m_Action == PERFORCE_ACTION_FILE_REVERT) ? "Revert" : "Submit"; + switch( state ) + { + case P4FILE_OPENED_FOR_ADD: + Q_snprintf( pBuf, sizeof(pBuf), "%s Add", pPrefix ); + AddOperation( pBuf, pFullPath ); + break; + + case P4FILE_OPENED_FOR_EDIT: + Q_snprintf( pBuf, sizeof(pBuf), "%s Edit", pPrefix ); + AddOperation( pBuf, pFullPath ); + break; + + case P4FILE_OPENED_FOR_DELETE: + Q_snprintf( pBuf, sizeof(pBuf), "%s Delete", pPrefix ); + AddOperation( pBuf, pFullPath ); + break; + + case P4FILE_OPENED_FOR_INTEGRATE: + Q_snprintf( pBuf, sizeof(pBuf), "%s Integrate", pPrefix ); + AddOperation( pBuf, pFullPath ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Version of AddFile that accepts full paths +//----------------------------------------------------------------------------- +void CPerforceFileListFrame::AddFile( const char *pFullPath ) +{ + if ( !p4 ) + return; + + if ( m_Action < PERFORCE_ACTION_FILE_REVERT ) + { + // If the file wasn't found on the disk, then abort + if ( g_pFullFileSystem->FileExists( pFullPath, NULL ) ) + { + AddFileForOpen( pFullPath ); + } + return; + } + + // Deal with submit, revert + bool bFileExists = g_pFullFileSystem->FileExists( pFullPath, NULL ); + P4FileState_t state = p4->GetFileState( pFullPath ); + if ( bFileExists || (state == P4FILE_OPENED_FOR_DELETE) ) + { + AddFileForSubmit( pFullPath, state ); + } +} + + +//----------------------------------------------------------------------------- +// Version of AddFile that accepts relative paths + search path ids +//----------------------------------------------------------------------------- +void CPerforceFileListFrame::AddFile( const char *pRelativePath, const char *pPathId ) +{ + if ( !p4 ) + return; + + // Deal with add, open, edit + if ( m_Action < PERFORCE_ACTION_FILE_REVERT ) + { + // If the file wasn't found on the disk, then abort + if ( g_pFullFileSystem->FileExists( pRelativePath, pPathId ) ) + { + char pFullPath[MAX_PATH]; + g_pFullFileSystem->RelativePathToFullPath( pRelativePath, pPathId, pFullPath, sizeof( pFullPath ) ); + AddFileForOpen( pFullPath ); + } + return; + } + + // Deal with submit, revert + + // First, handle the case where the file exists on the drive + char pFullPath[MAX_PATH]; + if ( g_pFullFileSystem->FileExists( pRelativePath, pPathId ) ) + { + g_pFullFileSystem->RelativePathToFullPath( pRelativePath, pPathId, pFullPath, sizeof( pFullPath ) ); + P4FileState_t state = p4->GetFileState( pFullPath ); + AddFileForSubmit( pFullPath, state ); + return; + } + + // Get the list of opened files, cache it off so we aren't continually reasking + if ( Q_stricmp( pPathId, m_LastOpenedFilePathId ) ) + { + p4->GetOpenedFileListInPath( pPathId, m_OpenedFiles ); + m_LastOpenedFilePathId = pPathId; + } + + // If the file doesn't exist, it was opened for delete. + // Using the client spec of the path, we need to piece together + // the full path; the full path unfortunately is usually ambiguous: + // you can never exactly know which mod it came from. + char pTemp[MAX_PATH]; + char pSearchString[MAX_PATH]; + Q_strncpy( pSearchString, pRelativePath, sizeof(pSearchString) ); + Q_FixSlashes( pSearchString ); + + int k; + int nOpenedFileCount = m_OpenedFiles.Count(); + for ( k = 0; k < nOpenedFileCount; ++k ) + { + if ( m_OpenedFiles[k].m_eOpenState != P4FILE_OPENED_FOR_DELETE ) + continue; + + // Check to see if the end of the local file matches the file + const char *pLocalFile = p4->String( m_OpenedFiles[k].m_sLocalFile ); + + // This ensures the full path lies under the search path + if ( !g_pFullFileSystem->FullPathToRelativePathEx( pLocalFile, pPathId, pTemp, sizeof(pTemp) ) ) + continue; + + // The relative paths had better be the same + if ( Q_stricmp( pTemp, pSearchString ) ) + continue; + + AddFileForSubmit( pLocalFile, m_OpenedFiles[k].m_eOpenState ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Does the perforce operation +//----------------------------------------------------------------------------- +bool CPerforceFileListFrame::PerformOperation( ) +{ + if ( !p4 ) + return false; + + int nFileCount = GetOperationCount(); + const char **ppFileNames = (const char**)_alloca( nFileCount * sizeof(char*) ); + for ( int i = 0; i < nFileCount; ++i ) + { + ppFileNames[i] = GetFileName( i ); + } + + bool bSuccess = false; + + switch ( m_Action ) + { + case PERFORCE_ACTION_FILE_ADD: + bSuccess = p4->OpenFilesForAdd( nFileCount, ppFileNames ); + break; + + case PERFORCE_ACTION_FILE_EDIT: + bSuccess = p4->OpenFilesForEdit( nFileCount, ppFileNames ); + break; + + case PERFORCE_ACTION_FILE_DELETE: + bSuccess = p4->OpenFilesForDelete( nFileCount, ppFileNames ); + break; + + case PERFORCE_ACTION_FILE_REVERT: + bSuccess = p4->RevertFiles( nFileCount, ppFileNames ); + break; + + case PERFORCE_ACTION_FILE_SUBMIT: + { + // Ensure a description was added + const char *pDescription = GetDescription(); + if ( !pDescription[0] || !Q_stricmp( pDescription, "<enter description here>" ) ) + { + vgui::MessageBox *pError = new vgui::MessageBox( "Submission Error!", "Description required for submission.", GetParent() ); + pError->SetSmallCaption( true ); + pError->DoModal(); + return false; + } + else + { + bSuccess = p4->SubmitFiles( nFileCount, ppFileNames, pDescription ); + } + } + break; + } + + const char *pErrorString = p4->GetLastError(); + if ( !bSuccess ) + { + vgui::MessageBox *pError = new vgui::MessageBox( "Perforce Error!", pErrorString, GetParent() ); + pError->SetSmallCaption( true ); + pError->DoModal(); + } +#if 0 + if ( *pErrorString ) + { + if ( V_strstr( pErrorString, "opened for add" ) ) + return bSuccess; + if ( V_strstr( pErrorString, "opened for edit" ) ) + return bSuccess; + // TODO - figure out the rest of these... + + const char *pPrefix = "Perforce has generated the following message which may or may not be an error.\n" + "Please email joe with the text of the message, whether you think it was an error, and what perforce operation you where performing.\n" + "To copy the message, hit ~ to enter the console, where you will find the message reprinted.\n" + "Select the lines of text in the message, right click, select Copy, and then paste into an email message.\n\n"; + static int nPrefixLen = V_strlen( pPrefix ); + int nErrorStringLength = V_strlen( pErrorString ); + char *pMsg = (char*)_alloca( nPrefixLen + nErrorStringLength + 1 ); + V_strcpy( pMsg, pPrefix ); + V_strcpy( pMsg + nPrefixLen, pErrorString ); + + vgui::MessageBox *pError = new vgui::MessageBox( "Dubious Perforce Message", pMsg, GetParent() ); + pError->SetSmallCaption( true ); + pError->DoModal(); + } +#endif + return bSuccess; +} + + +//----------------------------------------------------------------------------- +// Show the perforce query dialog +//----------------------------------------------------------------------------- +void ShowPerforceQuery( vgui::Panel *pParent, const char *pFileName, vgui::Panel *pActionSignalTarget, KeyValues *pKeyValues, PerforceAction_t actionFilter ) +{ + if ( !p4 ) + { + KeyValues *pSpoofKeys = new KeyValues( "PerforceQueryCompleted", "operationPerformed", 1 ); + if ( pKeyValues ) + { + pSpoofKeys->AddSubKey( pKeyValues ); + } + vgui::ivgui()->PostMessage( pActionSignalTarget->GetVPanel(), pSpoofKeys, 0 ); + + return; + } + + // Refresh the current perforce settings + p4->RefreshActiveClient(); + + PerforceAction_t action = PERFORCE_ACTION_NONE; + const char *pTitle = NULL; + if ( !p4->IsFileInPerforce( pFileName ) ) + { + // If the file isn't in perforce, ask to add it + action = PERFORCE_ACTION_FILE_ADD; + pTitle = "Add File to Perforce?"; + } + else if ( p4->GetFileState( pFileName ) == P4FILE_UNOPENED ) + { + // If the file isn't checked out yet, ask to check it out + action = PERFORCE_ACTION_FILE_EDIT; + pTitle = "Check Out File from Perforce?"; + } + + if ( ( action == PERFORCE_ACTION_NONE ) || ( ( actionFilter != PERFORCE_ACTION_NONE ) && ( actionFilter != action ) ) ) + { + // Spoof a completion event + KeyValues *pSpoofKeys = new KeyValues( "PerforceQueryCompleted", "operationPerformed", 1 ); + if ( pKeyValues ) + { + pSpoofKeys->AddSubKey( pKeyValues ); + } + vgui::ivgui()->PostMessage( pActionSignalTarget->GetVPanel(), pSpoofKeys, 0 ); + return; + } + + CPerforceFileListFrame *pQuery = new CPerforceFileListFrame( pParent, pTitle, "File", action ); + pQuery->AddFile( pFileName ); + if ( pActionSignalTarget ) + { + pQuery->AddActionSignalTarget( pActionSignalTarget ); + } + pQuery->DoModal( pKeyValues, "PerforceQueryCompleted" ); +} diff --git a/vgui2/vgui_controls/savedocumentquery.cpp b/vgui2/vgui_controls/savedocumentquery.cpp new file mode 100644 index 0000000..05ec95b --- /dev/null +++ b/vgui2/vgui_controls/savedocumentquery.cpp @@ -0,0 +1,195 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Core Movie Maker UI API +// +//============================================================================= + +#include "vgui_controls/savedocumentquery.h" +#include "vgui_controls/Button.h" +#include "vgui_controls/Label.h" +#include "vgui_controls/Frame.h" +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" +#include "tier1/KeyValues.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +using namespace vgui; + + +//----------------------------------------------------------------------------- +// This dialog asks if you want to save your work +//----------------------------------------------------------------------------- +class CSaveDocumentQuery : public vgui::Frame +{ + DECLARE_CLASS_SIMPLE( CSaveDocumentQuery, vgui::Frame ); + +public: + CSaveDocumentQuery( vgui::Panel *pParent, const char *filename, const char *pFileType, int nContext, + vgui::Panel *pActionSignalTarget = 0, KeyValues *pKeyValues = 0 ); + ~CSaveDocumentQuery(); + + // Inherited from vgui::Frame + virtual void OnCommand( char const *cmd ); + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + // Put the message box into a modal state + void DoModal(); + +private: + // Posts commands to the action signal target + void PostCommand( const char *pCommand ); + + vgui::Label *m_pMessageLabel; + vgui::Button *m_pYesButton; + vgui::Button *m_pNoButton; + vgui::Button *m_pCancelButton; + vgui::Panel *m_pActionSignalTarget; + + char m_szFileName[ 256 ]; + char m_szFileType[ 256 ]; + int m_nContext; + KeyValues* m_pPostSaveKeyValues; +}; + + +//----------------------------------------------------------------------------- +// Show the save document query dialog +//----------------------------------------------------------------------------- +void ShowSaveDocumentQuery( vgui::Panel *pParent, const char *pFileName, const char *pFileType, int nContext, vgui::Panel *pActionSignalTarget, KeyValues *pPostSaveCommand ) +{ + CSaveDocumentQuery *query = new CSaveDocumentQuery( pParent, pFileName, pFileType, nContext, pActionSignalTarget, pPostSaveCommand ); + query->SetSmallCaption( true ); + query->DoModal(); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CSaveDocumentQuery::CSaveDocumentQuery( vgui::Panel *pParent, char const *pFileName, const char *pFileType, int nContext, vgui::Panel *pActionSignalTarget, KeyValues *pPostSaveCommand ) : + BaseClass( pParent, "SaveDocumentQuery" ), + m_nContext( nContext ), + m_pActionSignalTarget( pActionSignalTarget ) +{ + if ( !pFileName || !pFileName[0] ) + { + pFileName = "<untitled>"; + } + Q_strncpy( m_szFileName, pFileName, sizeof( m_szFileName ) ); + Q_strncpy( m_szFileType, pFileType, sizeof( m_szFileType ) ); + m_pPostSaveKeyValues = pPostSaveCommand; + + SetDeleteSelfOnClose(true); + + SetMenuButtonResponsive(false); + SetMinimizeButtonVisible(false); + SetCloseButtonVisible(false); + SetSizeable(false); + + SetTitle( "Save Changes", true ); + + m_pMessageLabel = new Label( this, "FileNameLabel", "" ); + + m_pYesButton = new Button( this, "Yes", "Yes", this, "yes" ); + m_pNoButton = new Button( this, "No", "No", this, "no" ); + m_pCancelButton = new Button( this, "Cancel", "Cancel", this, "cancel" ); + + LoadControlSettings( "resource/ToolSaveDocumentQuery.res" ); + + m_pMessageLabel->SetText( m_szFileName ); +} + +CSaveDocumentQuery::~CSaveDocumentQuery() +{ + if ( m_pPostSaveKeyValues ) + { + m_pPostSaveKeyValues->deleteThis(); + m_pPostSaveKeyValues = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Posts commands to the action signal target +//----------------------------------------------------------------------------- +void CSaveDocumentQuery::PostCommand( const char *pCommand ) +{ + KeyValues *kv = new KeyValues( pCommand ); + vgui::ivgui()->PostMessage( m_pActionSignalTarget->GetVPanel(), kv, 0 ); +} + + +//----------------------------------------------------------------------------- +// Process commands +//----------------------------------------------------------------------------- +void CSaveDocumentQuery::OnCommand( char const *cmd ) +{ + if ( !Q_stricmp( cmd, "yes" ) ) + { + KeyValues *kv = new KeyValues( "OnSaveFile" ); + kv->SetString( "filename", m_szFileName ); + kv->SetString( "filetype", m_szFileType ); + kv->SetInt( "context", m_nContext ); + kv->SetPtr( "actionTarget", m_pActionSignalTarget ); + if ( m_pPostSaveKeyValues ) + { + kv->AddSubKey( m_pPostSaveKeyValues->MakeCopy() ); + } + vgui::ivgui()->PostMessage( m_pActionSignalTarget->GetVPanel(), kv, 0 ); + MarkForDeletion(); + } + else if ( !Q_stricmp( cmd, "no" ) ) + { + PostCommand( "OnMarkNotDirty" ); + if ( m_pPostSaveKeyValues ) + { + vgui::ivgui()->PostMessage( m_pActionSignalTarget->GetVPanel(), m_pPostSaveKeyValues->MakeCopy(), 0 ); + } + MarkForDeletion(); + } + else if ( !Q_stricmp( cmd, "cancel" ) ) + { + PostCommand( "OnCancelSaveDocument" ); + MarkForDeletion(); + } + else + { + BaseClass::OnCommand( cmd ); + } +} + + +//----------------------------------------------------------------------------- +// Deal with scheme +//----------------------------------------------------------------------------- +void CSaveDocumentQuery::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + int wide, tall; + GetSize( wide, tall ); + + int swide, stall; + surface()->GetScreenSize(swide, stall); + + // put the dialog in the middle of the screen + SetPos((swide - wide) / 2, (stall - tall) / 2); +} + + +//----------------------------------------------------------------------------- +// Put the message box into a modal state +//----------------------------------------------------------------------------- +void CSaveDocumentQuery::DoModal() +{ + SetVisible( true ); + SetEnabled( true ); + MoveToFront(); + + RequestFocus(); + + InvalidateLayout(); +} diff --git a/vgui2/vgui_controls/subrectimage.cpp b/vgui2/vgui_controls/subrectimage.cpp new file mode 100644 index 0000000..1293999 --- /dev/null +++ b/vgui2/vgui_controls/subrectimage.cpp @@ -0,0 +1,212 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "vgui_controls/subrectimage.h" +#include "tier0/dbg.h" +#include "vgui/ISurface.h" +#include "vgui_controls/Controls.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +using namespace vgui; + +// Officially the invalid texture ID is zero, but -1 is used in many +// places, and changing it carries some risk. Adding a named constant +// for this file avoids warnings and makes future changes easier. +const HTexture SUBRECT_INVALID_TEXTURE = (HTexture)-1; + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CSubRectImage::CSubRectImage( const char *filename, bool hardwareFiltered, int subx, int suby, int subw, int subh ) +{ + SetSize( subw, subh ); + sub[ 0 ] = subx; + sub[ 1 ] = suby; + sub[ 2 ] = subw; + sub[ 3 ] = subh; + + _filtered = hardwareFiltered; + // HACKHACK - force VGUI materials to be in the vgui/ directory + // This needs to be revisited once GoldSRC is grandfathered off. + //!! need to make this work with goldsrc + int size = strlen(filename) + 1 + strlen("vgui/"); + _filename = (char *)malloc( size ); + Assert( _filename ); + + Q_snprintf( _filename, size, "vgui/%s", filename ); + + _id = SUBRECT_INVALID_TEXTURE; + _uploaded = false; + _color = Color(255, 255, 255, 255); + _pos[0] = _pos[1] = 0; + _valid = true; + _wide = subw; + _tall = subh; + ForceUpload(); +} + +CSubRectImage::~CSubRectImage() +{ + if ( vgui::surface() && _id != SUBRECT_INVALID_TEXTURE ) + { + vgui::surface()->DestroyTextureID( _id ); + _id = SUBRECT_INVALID_TEXTURE; + } + + if ( _filename ) + { + free( _filename ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void CSubRectImage::GetSize(int &wide, int &tall) +{ + wide = _wide; + tall = _tall; +} + +//----------------------------------------------------------------------------- +// Purpose: size of the bitmap +//----------------------------------------------------------------------------- +void CSubRectImage::GetContentSize(int &wide, int &tall) +{ + wide = 0; + tall = 0; + + if (!_valid) + return; + + if ( _id != SUBRECT_INVALID_TEXTURE ) + { + surface()->DrawGetTextureSize(_id, wide, tall); + } +} + +//----------------------------------------------------------------------------- +// Purpose: ignored +//----------------------------------------------------------------------------- +void CSubRectImage::SetSize(int x, int y) +{ + _wide = x; + _tall = y; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void CSubRectImage::SetPos(int x, int y) +{ + _pos[0] = x; + _pos[1] = y; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void CSubRectImage::SetColor(Color col) +{ + _color = col; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the file name of the bitmap +//----------------------------------------------------------------------------- +const char *CSubRectImage::GetName() +{ + return _filename; +} + +//----------------------------------------------------------------------------- +// Purpose: Renders the loaded image, uploading it if necessary +// Assumes a valid image is always returned from uploading +//----------------------------------------------------------------------------- +void CSubRectImage::Paint() +{ + if ( !_valid ) + return; + + // if we don't have an _id then lets make one + if ( _id == SUBRECT_INVALID_TEXTURE ) + { + _id = surface()->CreateNewTextureID(); + } + + // if we have not uploaded yet, lets go ahead and do so + if ( !_uploaded ) + { + ForceUpload(); + } + + // set the texture current, set the color, and draw the biatch + surface()->DrawSetColor( _color[0], _color[1], _color[2], _color[3] ); + surface()->DrawSetTexture( _id ); + + if ( _wide == 0 || _tall == 0 ) + return; + + int cw, ch; + GetContentSize( cw, ch ); + if ( cw == 0 || ch == 0 ) + return; + + float s[ 2 ]; + float t[ 2 ]; + + s[ 0 ] = (float)sub[ 0 ] / (float)cw; + s[ 1 ] = (float)(sub[ 0 ]+sub[ 2 ]) / (float)cw; + t[ 0 ] = (float)sub[ 1 ] / (float)ch; + t[ 1 ] = (float)(sub[ 1 ]+sub[ 3 ]) / (float)ch; + surface()->DrawTexturedSubRect( + _pos[0], + _pos[1], + _pos[0] + _wide, + _pos[1] + _tall, + s[ 0 ], + t[ 0 ], + s[ 1 ], + t[ 1 ] ); +} + +//----------------------------------------------------------------------------- +// Purpose: ensures the bitmap has been uploaded +//----------------------------------------------------------------------------- +void CSubRectImage::ForceUpload() +{ + if ( !_valid || _uploaded ) + return; + + if ( _id == SUBRECT_INVALID_TEXTURE ) + { + _id = surface()->CreateNewTextureID( false ); + } + + surface()->DrawSetTextureFile( _id, _filename, _filtered, false ); + + _uploaded = true; + + _valid = surface()->IsTextureIDValid( _id ); +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +HTexture CSubRectImage::GetID() +{ + return _id; +} + +bool CSubRectImage::IsValid() +{ + return _valid; +} + diff --git a/vgui2/vgui_controls/vgui_controls.vpc b/vgui2/vgui_controls/vgui_controls.vpc new file mode 100644 index 0000000..8f417e2 --- /dev/null +++ b/vgui2/vgui_controls/vgui_controls.vpc @@ -0,0 +1,198 @@ +//----------------------------------------------------------------------------- +// VGUI_CONTROLS.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR "..\.." +$Macro GENERATED_PROTO_DIR "generated_proto" +$macro PROTOBUF_LITE 0 + +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" +$include "$SRCDIR\vpc_scripts\protobuf_builder.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;$SRCDIR\thirdparty;$SRCDIR\thirdparty\cef;$GENERATED_PROTO_DIR" + } +} + +$Project "vgui_controls" +{ + $Folder "Source Files" + { + $File "AnalogBar.cpp" + $File "AnimatingImagePanel.cpp" + $File "AnimationController.cpp" + $File "BitmapImagePanel.cpp" + $File "BuildFactoryHelper.cpp" + $File "BuildGroup.cpp" + $File "BuildModeDialog.cpp" + $File "Button.cpp" + $File "CheckButton.cpp" + $File "CheckButtonList.cpp" + $File "CircularProgressBar.cpp" + $File "ComboBox.cpp" + $File "consoledialog.cpp" + $File "ControllerMap.cpp" + $File "controls.cpp" + $File "CvarToggleCheckButton.cpp" + $File "DirectorySelectDialog.cpp" + $File "Divider.cpp" + $File "EditablePanel.cpp" + $File "ExpandButton.cpp" + $File "FileOpenDialog.cpp" + $File "FileOpenStateMachine.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "FocusNavGroup.cpp" + $File "Frame.cpp" + $File "GraphPanel.cpp" + $File "HTML.cpp" + $File "Image.cpp" + $File "ImageList.cpp" + $File "ImagePanel.cpp" + $File "InputDialog.cpp" + $File "KeyBindingHelpDialog.cpp" + $File "KeyBoardEditorDialog.cpp" + $File "KeyRepeat.cpp" + $File "Label.cpp" + $File "ListPanel.cpp" + $File "ListViewPanel.cpp" + $File "Menu.cpp" + $File "MenuBar.cpp" + $File "MenuButton.cpp" + $File "MenuItem.cpp" + $File "MessageBox.cpp" + $File "MessageDialog.cpp" + $File "Panel.cpp" + $File "PanelListPanel.cpp" + $File "PerforceFileExplorer.cpp" + $File "PerforceFileList.cpp" + $File "perforcefilelistframe.cpp" + $File "ProgressBar.cpp" + $File "ProgressBox.cpp" + $File "PropertyDialog.cpp" + $File "PropertyPage.cpp" + $File "PropertySheet.cpp" + $File "QueryBox.cpp" + $File "RadioButton.cpp" + $File "RichText.cpp" + $File "RotatingProgressBar.cpp" + $File "savedocumentquery.cpp" + $File "ScalableImagePanel.cpp" + $File "ScrollableEditablePanel.cpp" + $File "ScrollBar.cpp" + $File "ScrollBarSlider.cpp" + $File "SectionedListPanel.cpp" + $File "Slider.cpp" + $File "Splitter.cpp" + $File "subrectimage.cpp" + $File "TextEntry.cpp" + $File "TextImage.cpp" + $File "ToggleButton.cpp" + $File "Tooltip.cpp" + $File "ToolWindow.cpp" + $File "TreeView.cpp" + $File "TreeViewListControl.cpp" + $File "URLLabel.cpp" + $File "WizardPanel.cpp" + $File "WizardSubPanel.cpp" + $File "$SRCDIR/vgui2/src/vgui_key_translation.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\vgui_controls\AnalogBar.h" + $File "$SRCDIR\public\vgui_controls\AnimatingImagePanel.h" + $File "$SRCDIR\public\vgui_controls\AnimationController.h" + $File "$SRCDIR\public\vgui_controls\BitmapImagePanel.h" + $File "$SRCDIR\public\vgui_controls\BuildGroup.h" + $File "$SRCDIR\public\vgui_controls\BuildModeDialog.h" + $File "$SRCDIR\public\vgui_controls\Button.h" + $File "$SRCDIR\public\vgui_controls\CheckButton.h" + $File "$SRCDIR\public\vgui_controls\CheckButtonList.h" + $File "$SRCDIR\public\vgui_controls\CircularProgressBar.h" + $File "$SRCDIR\public\Color.h" + $File "$SRCDIR\public\vgui_controls\ComboBox.h" + $File "$SRCDIR\public\vgui_controls\consoledialog.h" + $File "$SRCDIR\public\vgui_controls\ControllerMap.h" + $File "$SRCDIR\public\vgui_controls\Controls.h" + $File "$SRCDIR\public\vgui_controls\CvarToggleCheckButton.h" + $File "$SRCDIR\public\vgui_controls\DialogManager.h" + $File "$SRCDIR\public\vgui_controls\DirectorySelectDialog.h" + $File "$SRCDIR\public\vgui_controls\Divider.h" + $File "$SRCDIR\public\vgui_controls\EditablePanel.h" + $File "$SRCDIR\public\vgui_controls\ExpandButton.h" + $File "$SRCDIR\public\vgui_controls\FileOpenDialog.h" + $File "$SRCDIR\public\vgui_controls\FileOpenStateMachine.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\vgui_controls\FocusNavGroup.h" + $File "$SRCDIR\public\vgui_controls\Frame.h" + $File "$SRCDIR\public\vgui_controls\GraphPanel.h" + $File "$SRCDIR\public\vgui_controls\HTML.h" + $File "$SRCDIR\public\vgui_controls\Image.h" + $File "$SRCDIR\public\vgui_controls\ImageList.h" + $File "$SRCDIR\public\vgui_controls\ImagePanel.h" + $File "$SRCDIR\public\vgui_controls\InputDialog.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\vgui_controls\KeyBindingHelpDialog.h" + $File "$SRCDIR\public\vgui_controls\KeyBindingMap.h" + $File "$SRCDIR\public\vgui_controls\KeyBoardEditorDialog.h" + $File "$SRCDIR\public\vgui_controls\KeyRepeat.h" + $File "$SRCDIR\public\tier1\KeyValues.h" + $File "$SRCDIR\public\vgui_controls\Label.h" + $File "$SRCDIR\public\vgui_controls\ListPanel.h" + $File "$SRCDIR\public\vgui_controls\ListViewPanel.h" + $File "$SRCDIR\public\tier0\memdbgoff.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\tier1\mempool.h" + $File "$SRCDIR\public\vgui_controls\Menu.h" + $File "$SRCDIR\public\vgui_controls\MenuBar.h" + $File "$SRCDIR\public\vgui_controls\MenuButton.h" + $File "$SRCDIR\public\vgui_controls\MenuItem.h" + $File "$SRCDIR\public\vgui_controls\MessageBox.h" + $File "$SRCDIR\public\vgui_controls\MessageDialog.h" + $File "$SRCDIR\public\vgui_controls\MessageMap.h" + $File "$SRCDIR\public\vgui_controls\Panel.h" + $File "$SRCDIR\public\vgui_controls\PanelAnimationVar.h" + $File "$SRCDIR\public\vgui_controls\PanelListPanel.h" + $File "$SRCDIR\public\vgui_controls\PerforceFileExplorer.h" + $File "$SRCDIR\public\vgui_controls\PerforceFileList.h" + $File "$SRCDIR\public\vgui_controls\perforcefilelistframe.h" + $File "$SRCDIR\public\vgui_controls\PHandle.h" + $File "$SRCDIR\public\vgui_controls\ProgressBar.h" + $File "$SRCDIR\public\vgui_controls\ProgressBox.h" + $File "$SRCDIR\public\vgui_controls\PropertyDialog.h" + $File "$SRCDIR\public\vgui_controls\PropertyPage.h" + $File "$SRCDIR\public\vgui_controls\PropertySheet.h" + $File "$SRCDIR\public\vgui_controls\QueryBox.h" + $File "$SRCDIR\public\vgui_controls\RadioButton.h" + $File "$SRCDIR\public\vgui_controls\RichText.h" + $File "$SRCDIR\public\vgui_controls\RotatingProgressBar.h" + $File "$SRCDIR\public\vgui_controls\savedocumentquery.h" + $File "$SRCDIR\public\vgui_controls\ScalableImagePanel.h" + $File "$SRCDIR\public\vgui_controls\ScrollableEditablePanel.h" + $File "$SRCDIR\public\vgui_controls\ScrollBar.h" + $File "$SRCDIR\public\vgui_controls\ScrollBarSlider.h" + $File "$SRCDIR\public\vgui_controls\SectionedListPanel.h" + $File "$SRCDIR\public\vgui_controls\Slider.h" + $File "$SRCDIR\public\vgui_controls\Splitter.h" + $File "$SRCDIR\public\vgui_controls\subrectimage.h" + $File "$SRCDIR\public\vgui_controls\TextEntry.h" + $File "$SRCDIR\public\vgui_controls\TextImage.h" + $File "$SRCDIR\public\vgui_controls\ToggleButton.h" + $File "$SRCDIR\public\vgui_controls\Tooltip.h" + $File "$SRCDIR\public\vgui_controls\ToolWindow.h" + $File "$SRCDIR\public\vgui_controls\TreeView.h" + $File "$SRCDIR\public\vgui_controls\TreeViewListControl.h" + $File "$SRCDIR\public\vgui_controls\URLLabel.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vgui_controls\WizardPanel.h" + $File "$SRCDIR\public\vgui_controls\WizardSubPanel.h" + } +} |