Files
Client/GameTools/WORLDCREATOR/GridListCtrl.cpp
LGram16 dd97ddec92 Restructure repository to include all source folders
Move git root from Client/ to src/ to track all source code:
- Client: Game client source (moved to Client/Client/)
- Server: Game server source
- GameTools: Development tools
- CryptoSource: Encryption utilities
- database: Database scripts
- Script: Game scripts
- rylCoder_16.02.2008_src: Legacy coder tools
- GMFont, Game: Additional resources

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 20:17:20 +09:00

733 lines
22 KiB
C++

// GridListCtrl.cpp : implementation file
//
#include "stdafx.h"
#include "GridListCtrl.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CGridListCtrl
CGridListCtrl::CGridListCtrl()
{
m_CurSubItem = -1;
m_pListEdit =NULL;
}
CGridListCtrl::~CGridListCtrl()
{
if( m_pListEdit)
{
delete m_pListEdit;
}
}
BEGIN_MESSAGE_MAP(CGridListCtrl, CListCtrl)
//{{AFX_MSG_MAP(CGridListCtrl)
ON_WM_LBUTTONDOWN()
ON_WM_HSCROLL()
ON_WM_VSCROLL()
ON_NOTIFY_REFLECT(LVN_BEGINLABELEDIT, OnBeginlabeledit)
ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnEndlabeledit)
//}}AFX_MSG_MAP
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CGridListCtrl message handlers
BOOL CGridListCtrl::PrepareControl(WORD wStyle)
{
m_wStyle = wStyle;
ASSERT( m_hWnd );
DWORD dwStyle = GetWindowLong(m_hWnd, GWL_STYLE);
dwStyle &= ~(LVS_TYPEMASK);
dwStyle &= ~(LVS_EDITLABELS);
// Make sure we have report view and send edit label messages.
SetWindowLong( m_hWnd, GWL_STYLE, dwStyle | LVS_REPORT );
// Enable the full row selection and the drag drop of headers.
DWORD styles = LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP ;
// Use macro since this is new and not in MFC.
ListView_SetExtendedListViewStyleEx(m_hWnd, styles, styles );
return TRUE;
}
void CGridListCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
LVHITTESTINFO ht;
ht.pt = point;
// Test for which subitem was clicked.
// Use macro since this is new and not in MFC.
int rval = ListView_SubItemHitTest( m_hWnd, &ht );
// Store the old column number and set the new column value.
int oldsubitem = m_CurSubItem;
m_CurSubItem = IndexToOrder( ht.iSubItem );
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
// Make the column fully visible.
// We have to take into account that the columns may be reordered
MakeColumnVisible( Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
// Store old state of the item.
int state = GetItemState( ht.iItem, LVIS_FOCUSED );
// Call default left button click is here just before we might bail.
// Also updates the state of the item.
CListCtrl::OnLButtonDown(nFlags, point);
NMLISTVIEW nmlv;
nmlv.hdr.code = LVN_COLUMNCLICK;
nmlv.hdr.hwndFrom = GetSafeHwnd();
nmlv.hdr.idFrom = (UINT)(::GetMenu( GetSafeHwnd() ));
nmlv.iItem = ht.iItem;
nmlv.iSubItem = ht.iSubItem;
nmlv.uNewState = 0;
nmlv.uOldState = 0;
nmlv.uChanged = 0;
GetParent()->SendMessage( WM_NOTIFY, (WPARAM)0, (LPARAM)(&nmlv) );
// Bail if the state from before was not focused or the
// user has not already clicked on this cell.
if( !state
|| m_CurSubItem == -1
|| oldsubitem != m_CurSubItem )
{
RedrawItems( rval, rval );
return;
}
int doedit = 0;
// If we are in column 0 make sure that the user clicked on
// the item label.
if( 0 == ht.iSubItem )
{
if( ht.flags & LVHT_ONITEMLABEL ) doedit = 1;
}
else
{
doedit = 1;
}
if( !doedit ) return;
// check if focused column has editable attribute
if( GLCT_NORMAL == GetColumnType(ht.iSubItem) )
{
return;
}
// Send Notification to parent of ListView ctrl
CString str;
str = GetItemText( ht.iItem, ht.iSubItem );
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_BEGINLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = ht.iItem;
dispinfo.item.iSubItem = ht.iSubItem;
dispinfo.item.pszText = (LPTSTR)((LPCTSTR)str);
dispinfo.item.cchTextMax = str.GetLength();
GetParent()->SendMessage( WM_NOTIFY, GetDlgCtrlID(),
(LPARAM)&dispinfo );
}
BOOL CGridListCtrl::PositionControl( CWnd * pWnd, int iItem, int iSubItem )
{
ASSERT( pWnd && pWnd->m_hWnd );
ASSERT( iItem >= 0 );
// Make sure that the item is visible
if( !EnsureVisible( iItem, TRUE ) ) return NULL;
// Make sure that nCol is valid
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int nColumnCount = pHeader->GetItemCount();
ASSERT( iSubItem >= 0 && iSubItem < nColumnCount );
if( iSubItem >= nColumnCount ||
// We have to take into account that the header may be reordered
GetColumnWidth(Header_OrderToIndex( pHeader->m_hWnd,iSubItem)) < 5 )
{
return 0;
}
// Get the header order array to sum the column widths up to the selected cell.
int *orderarray = new int[ nColumnCount ];
Header_GetOrderArray( pHeader->m_hWnd, nColumnCount, orderarray );
int offset = 0;
int i;
for( i = 0; orderarray[i] != iSubItem; i++ )
offset += GetColumnWidth( orderarray[i] );
int colwidth = GetColumnWidth( iSubItem );
delete[] orderarray;
CRect rect;
GetItemRect( iItem, &rect, LVIR_BOUNDS );
// Scroll if we need to expose the column
CRect rcClient;
GetClientRect( &rcClient );
if( offset + rect.left < 0 || offset + colwidth + rect.left > rcClient.right )
{
CSize size;
size.cx = offset + rect.left;
size.cy = 0;
Scroll( size );
rect.left -= size.cx;
}
rect.left += offset+4;
rect.right = rect.left + colwidth - 3 ;
// The right end of the control should not go past the edge
// of the grid control.
if( rect.right > rcClient.right)
rect.right = rcClient.right;
pWnd->MoveWindow( &rect );
return 1;
}
void CGridListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
// This function is called by the control in different
// stages during the control drawing process.
NMLVCUSTOMDRAW *pCD = (NMLVCUSTOMDRAW*)pNMHDR;
// By default set the return value to do the default behavior.
*pResult = 0;
switch( pCD->nmcd.dwDrawStage )
{
case CDDS_PREPAINT: // First stage (for the whole control)
// Tell the control we want to receive drawing messages
// for drawing items.
*pResult = CDRF_NOTIFYITEMDRAW;
// The next stage is handled in the default:
break;
case CDDS_ITEMPREPAINT | CDDS_SUBITEM: // Stage three (called for each subitem of the focused item)
{
// We don't want to draw anything here, but we need to respond
// of DODEFAULT will be the next stage.
// Tell the control we want to handle drawing after the subitem
// is drawn.
*pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT;
}
break;
case CDDS_ITEMPOSTPAINT | CDDS_SUBITEM: // Stage four (called for each subitem of the focused item)
{
// We do the drawing here (well maybe).
// This is actually after the control has done its drawing
// on the subitem. Since drawing a cell is near instantaneous
// the user won't notice.
int subitem = pCD->iSubItem;
// Only do our own drawing if this subitem has focus at the item level.
if( (pCD->nmcd.uItemState & CDIS_FOCUS) )
{
// If this subitem is the subitem with the current focus,
// draw it. Otherwise let the control draw it.
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
// We have to take into account the possibility that the
// columns may be reordered.
if( subitem == Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) )
{
// POSTERASE
CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
// Calculate the offset of the text from the right and left of the cell.
int offset = pDC->GetTextExtent(_T(" "), 1 ).cx*2;
// The rect for the cell gives correct left and right values.
CRect rect = pCD->nmcd.rc;
CRect bounds;
GetItemRect( pCD->nmcd.dwItemSpec, &bounds, LVIR_BOUNDS );
// Get the top and bottom from the item itself.
rect.top = bounds.top;
rect.bottom = bounds.bottom;
// Adjust rectangle for horizontal scroll and first column label
{
if( subitem == 0 )
{
CRect lrect;
GetItemRect( pCD->nmcd.dwItemSpec, &lrect, LVIR_LABEL );
rect.left = lrect.left;
rect.right = lrect.right;
}
else
{
rect.right += bounds.left;
rect.left += bounds.left;
}
}
// Clear the background with button face color
pDC->FillRect(rect, &CBrush(::GetSysColor(COLOR_HIGHLIGHT)));
// Draw column focus box
rect.DeflateRect( 1, 1 );
pDC->FrameRect(rect, &CBrush(::GetSysColor(COLOR_BTNTEXT)));
// Draw text
CString str;
str = GetItemText( pCD->nmcd.dwItemSpec, pCD->iSubItem );
rect.DeflateRect( 4, 1, 0, 0 );
pDC->SetTextColor( ::GetSysColor(COLOR_HIGHLIGHTTEXT) );
pDC->DrawText( str, rect,
DT_SINGLELINE|DT_NOPREFIX|DT_LEFT|DT_VCENTER|DT_END_ELLIPSIS);
// Tell the control that we handled the drawing for this subitem.*/
*pResult = CDRF_SKIPDEFAULT;
}
}
}
break;
default: // Stage two handled here. (called for each item)
if( !(pCD->nmcd.uItemState & CDIS_FOCUS) )
{
// If this item does not have focus, let the
// control draw the whole item.
*pResult = CDRF_DODEFAULT;
}
else
{
// If this item has focus, tell the control we want
// to handle subitem drawing.
*pResult = CDRF_NOTIFYSUBITEMDRAW;
}
break;
}
}
void CGridListCtrl::MakeColumnVisible(int nCol)
{
if( nCol < 0 )
return;
// Get the order array to total the column offset.
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int colcount = pHeader->GetItemCount();
ASSERT( nCol < colcount );
int *orderarray = new int[ colcount ];
Header_GetOrderArray( pHeader->m_hWnd, colcount, orderarray );
// Get the column offset
int offset = 0;
for( int i = 0; orderarray[i] != nCol; i++ )
offset += GetColumnWidth( orderarray[i] );
int colwidth = GetColumnWidth( nCol );
delete[] orderarray;
CRect rect;
GetItemRect( 0, &rect, LVIR_BOUNDS );
// Now scroll if we need to expose the column
CRect rcClient;
GetClientRect( &rcClient );
if( offset + rect.left < 0 || offset + colwidth + rect.left > rcClient.right )
{
CSize size;
size.cx = offset + rect.left;
size.cy = 0;
Scroll( size );
rect.left -= size.cx;
}
}
BOOL CGridListCtrl::PreTranslateMessage(MSG* pMsg)
{
if(pMsg->message == WM_KEYDOWN)
{
// Handle the keystrokes for the left and right keys
// to move the cell selection left and right.
// Handle F2 to commence edit mode from the keyboard.
// Only handle these if the grid control has the focus.
// (Messages also come through here for the edit control
// and we don't want them.
if( this == GetFocus() )
{
switch( pMsg->wParam )
{
case VK_LEFT:
{
// Decrement the order number.
m_CurSubItem--;
if( m_CurSubItem < -1 )
{
// This indicates that the whole row is selected and F2 means nothing.
m_CurSubItem = -1;
}
else
{
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
// Make the column visible.
// We have to take into account that the header
// may be reordered.
MakeColumnVisible( Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
// Invalidate the item.
int iItem = GetNextItem( -1, LVNI_FOCUSED );
if( iItem != -1 )
{
CRect rcBounds;
GetItemRect(iItem, rcBounds, LVIR_BOUNDS);
InvalidateRect( &rcBounds );
}
}
}
return TRUE;
case VK_RIGHT:
{
// Increment the order number.
m_CurSubItem++;
CHeaderCtrl* pHeader = (CHeaderCtrl*) GetDlgItem(0);
int nColumnCount = pHeader->GetItemCount();
// Don't go beyond the last column.
if( m_CurSubItem > nColumnCount -1 )
{
m_CurSubItem = nColumnCount-1;
}
else
{
// We have to take into account that the header
// may be reordered.
MakeColumnVisible( Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
int iItem = GetNextItem( -1, LVNI_FOCUSED );
// Invalidate the item.
if( iItem != -1 )
{
CRect rcBounds;
GetItemRect(iItem, rcBounds, LVIR_BOUNDS);
InvalidateRect( &rcBounds );
}
}
}
return TRUE;
case VK_F2: // Enter nondestructive edit mode.
{
int iItem = GetNextItem( -1, LVNI_FOCUSED );
if( m_CurSubItem != -1 && iItem != -1 &&
GLCT_EDIT == GetColumnType(m_CurSubItem) )
{
// Send Notification to parent of ListView ctrl
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
CString str;
// We have to take into account that the header
// may be reordered.
str = GetItemText( iItem, Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem ) );
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_BEGINLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = iItem;
// We have to take into account that the header
// may be reordered.
dispinfo.item.iSubItem = Header_OrderToIndex( pHeader->m_hWnd, m_CurSubItem );
dispinfo.item.pszText = (LPTSTR)((LPCTSTR)str);
dispinfo.item.cchTextMax = str.GetLength();
// Send message to the parent that we are ready to edit.
GetParent()->SendMessage( WM_NOTIFY, GetDlgCtrlID(),
(LPARAM)&dispinfo );
}
}
break;
default:
break;
}
}
}
return CListCtrl::PreTranslateMessage(pMsg);
}
int CGridListCtrl::IndexToOrder( int iIndex )
{
// Since the control only provide the OrderToIndex macro,
// we have to provide the IndexToOrder. This translates
// a column index value to a column order value.
int nRet = -1;
CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
int colcount = pHeader->GetItemCount();
int *orderarray = new int[ colcount ];
Header_GetOrderArray( pHeader->m_hWnd, colcount, orderarray );
int i;
for( i=0; i<colcount; i++ )
{
if( orderarray[i] == iIndex )
{
nRet = i;
break;
}
}
delete[] orderarray;
return nRet;
}
void CGridListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if( GetFocus() != this ) SetFocus();
CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
}
void CGridListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if( GetFocus() != this ) SetFocus();
CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}
void CGridListCtrl::OnBeginlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
// TODO: Add your control notification handler code here
CString str = pDispInfo->item.pszText;
int item = pDispInfo->item.iItem;
int subitem = pDispInfo->item.iSubItem;
// Construct and create the custom multiline edit control.
// We could just as well have used a combobox, checkbox,
// rich text control, etc.
m_pListEdit = new CInPlaceEdit( item, subitem, str );
// Start with a small rectangle. We'll change it later.
CRect rect( 0,0,1,1 );
DWORD dwStyle = ES_LEFT;
dwStyle |= WS_BORDER|WS_CHILD|WS_VISIBLE;//|ES_MULTILINE|ES_AUTOVSCROLL;
m_pListEdit->Create( dwStyle, rect, this, 103 );
// Have the Grid position and size the custom edit control
this->PositionControl( m_pListEdit, item, subitem );
// Have the edit box size itself to its content.
m_pListEdit->CalculateSize();
// Return TRUE so that the list control will hnadle NOT edit label itself.
*pResult = 1;
}
void CGridListCtrl::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
// TODO: Add your control notification handler code here
int item = pDispInfo->item.iItem;
int subitem = pDispInfo->item.iSubItem;
// This is coming from the grid list control notification.
if( m_pListEdit )
{
CString str;
if( pDispInfo->item.pszText )
{
this->SetItemText( item, subitem, pDispInfo->item.pszText );
}
delete m_pListEdit;
m_pListEdit = 0;
}
*pResult = 0;
}
void CGridListCtrl::SetColumnType( int nCol, GLC_COLUMNTYPE columnType )
{
if( nCol < 100 )
{
m_aColumnType[nCol] = columnType;
}
}
GLC_COLUMNTYPE CGridListCtrl::GetColumnType( int nCol )
{
if( nCol < 100 )
{
return m_aColumnType[nCol];
}
return GLCT_EDIT;
}
/////////////////////////////////////////////////////////////////////////////
// CInPlaceEdit
CInPlaceEdit::CInPlaceEdit(int iItem, int iSubItem, CString sInitText)
:m_sInitText( sInitText )
{
m_iItem = iItem;
m_iSubItem = iSubItem;
m_bESC = FALSE;
}
CInPlaceEdit::~CInPlaceEdit()
{
}
BEGIN_MESSAGE_MAP(CInPlaceEdit, CEdit)
//{{AFX_MSG_MAP(CInPlaceEdit)
ON_WM_KILLFOCUS()
ON_WM_CHAR()
ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CInPlaceEdit message handlers
BOOL CInPlaceEdit::PreTranslateMessage(MSG* pMsg)
{
if( pMsg->message == WM_KEYDOWN )
{
SHORT sKey = GetKeyState( VK_CONTROL);
if(pMsg->wParam == VK_RETURN
|| pMsg->wParam == VK_DELETE
|| pMsg->wParam == VK_ESCAPE
|| sKey
)
{
::TranslateMessage(pMsg);
/* Strange but true:
If the edit control has ES_MULTILINE and ESC
is pressed the parent is destroyed if the
message is dispatched. In this
case the parent is the list control. */
if( !(GetStyle() & ES_MULTILINE) || pMsg->wParam != VK_ESCAPE )
{
::DispatchMessage(pMsg);
}
return TRUE; // DO NOT process further
}
}
return CEdit::PreTranslateMessage(pMsg);
}
void CInPlaceEdit::OnKillFocus(CWnd* pNewWnd)
{
CEdit::OnKillFocus(pNewWnd);
CString str;
GetWindowText(str);
// Send Notification to parent of ListView ctrl
LV_DISPINFO dispinfo;
dispinfo.hdr.hwndFrom = GetParent()->m_hWnd;
dispinfo.hdr.idFrom = GetDlgCtrlID();
dispinfo.hdr.code = LVN_ENDLABELEDIT;
dispinfo.item.mask = LVIF_TEXT;
dispinfo.item.iItem = m_iItem;
dispinfo.item.iSubItem = m_iSubItem;
dispinfo.item.pszText = m_bESC ? NULL : LPTSTR((LPCTSTR)str);
dispinfo.item.cchTextMax = m_bESC ? 0 : str.GetLength();
GetParent()->GetParent()->SendMessage( WM_NOTIFY, GetParent()->GetDlgCtrlID(),
(LPARAM)&dispinfo );
}
void CInPlaceEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if( nChar == VK_ESCAPE || nChar == VK_RETURN)
{
if( nChar == VK_ESCAPE )
m_bESC = TRUE;
GetParent()->SetFocus();
return;
}
CEdit::OnChar(nChar, nRepCnt, nFlags);
// Resize edit control if needed
CalculateSize();
}
int CInPlaceEdit::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CEdit::OnCreate(lpCreateStruct) == -1)
return -1;
// Set the proper font
CFont* font = GetParent()->GetFont();
SetFont(font);
SetWindowText( m_sInitText );
SetFocus();
CalculateSize();
SetSel( 0, -1 );
return 0;
}
void CInPlaceEdit::CalculateSize()
{
// Get text extent
CString str;
GetWindowText( str );
CWindowDC dc(this);
CFont *pFont = GetParent()->GetFont();
CFont *pFontDC = dc.SelectObject( pFont );
CSize size;
// Get client rect
CRect rect, parentrect;
GetClientRect( &rect );
GetParent()->GetClientRect( &parentrect );
// Transform rect to parent coordinates
ClientToScreen( &rect );
GetParent()->ScreenToClient( &rect );
if( !(GetStyle() & ES_MULTILINE ) )
{
size = dc.GetTextExtent( str );
dc.SelectObject( pFontDC );
size.cx += 5; // add some extra buffer
}
else
{
CRect thinrect( rect ); // To measure the skinniest text box
CRect widerect( rect ); // To measure the wides text box
widerect.right = parentrect.right;
// Use the shortest of the two box sizes.
int thinheight = dc.DrawText( str, &thinrect, DT_CALCRECT|DT_NOPREFIX|DT_LEFT|DT_EXPANDTABS|DT_WORDBREAK );
int wideheight = dc.DrawText( str, &widerect, DT_CALCRECT|DT_NOPREFIX|DT_LEFT|DT_EXPANDTABS|DT_WORDBREAK );
if( thinheight >= wideheight )
{
size.cy = wideheight + 5;
size.cx = widerect.right - widerect.left + 5;
}
else
{
size.cy = thinheight + 5;
size.cx = thinrect.right - thinrect.left + 5;
}
}
// Check whether control needs to be resized
// and whether there is space to grow
int changed = 0;
if( size.cx > rect.Width() )
{
if( size.cx + rect.left < parentrect.right-2 )
rect.right = rect.left + size.cx;
else
rect.right = parentrect.right-2;
changed = 1;
}
if( size.cy > rect.Height() )
{
if( size.cy + rect.top < parentrect.bottom-2 )
rect.bottom = rect.top + size.cy;
else
{
rect.bottom = parentrect.bottom-2;
ShowScrollBar( SB_VERT );
}
changed = 1;
}
// If the size became larger rposition the window.
if( changed )
MoveWindow( &rect );
}