#pragma once
#define USE_STL_CONTAINERS 0
#if USE_STL_CONTAINERS
#include <vector>
#include <algorithm>
#endif
#include <atlcrack.h>
#include <atlmisc.h>
#include <atlcoll.h>
#define SCROLL_TIMER 1023
#define SBHIT_NONE 0
#define SBHIT_TOP 1
#define SBHIT_LEFT 1
#define SBHIT_SLIDER 2
#define SBHIT_BOTTOM 3
#define SBHIT_RIGHT 3
#if USE_STL_CONTAINERS
template<typename CT>
class CTouchArray : public std::vector<CT>
{
public:
inline size_t Add(CT element)
{
insert(end(), element);
return (end() - begin());
}
inline void InsertAt(size_t iElement,CT element, size_t nCount = 1)
{
insert(begin()+iElement, element);
}
inline void RemoveAt(size_t iElement, size_t nCount = 1 )
{
erase(begin()+iElement, begin()+iElement+nCount);
}
inline size_t GetCount( ) const throw( )
{
return size();
}
inline void RemoveAll( ) throw( )
{
clear();
}
};
#else
//typedef CAtlArray CTouchArray;
#define CTouchArray CAtlArray
#endif
class CScrollBarData
{
private:
int m_nMinSize,
m_nSize,
m_nFill,
m_nOffset,
m_nHitArea;
bool m_bVisible;
int RoundDiv(int a, int b)
{
ATLASSERT(b > 0);
int d = a / b;
if((a % b) > (b / 2))
++d;
return d;
}
public:
CScrollBarData(int nMinSize = 16)
: m_nMinSize (nMinSize),
m_nSize (0),
m_nFill (0),
m_nOffset (0),
m_nHitArea (SBHIT_NONE),
m_bVisible (true)
{ }
bool IsVisible () { return m_bVisible && m_nSize != 0; }
void SetVisible (bool bVisible) { m_bVisible = bVisible; }
int GetSize () { return m_nSize; }
int GetOffset () { return m_nOffset; }
void SetOffset (int nOffset) { m_nOffset = nOffset; }
void SetHitArea (int nHitArea) { m_nHitArea = nHitArea; }
int GetHitArea () { return m_nHitArea; }
void SetMinSize(int nMinSize) { m_nMinSize = nMinSize; }
int SetHitPos(int z)
{
if(z < m_nOffset)
m_nHitArea = SBHIT_TOP;
else if(z < m_nOffset + m_nSize)
m_nHitArea = SBHIT_SLIDER;
else
m_nHitArea = SBHIT_BOTTOM;
return m_nHitArea;
}
void SetViewData(int nViewOffset, int nViewSize, int nRealSize)
{
if(nRealSize > 0 && nViewSize < nRealSize)
{
m_nSize = RoundDiv(nViewSize * nViewSize, nRealSize);
if(m_nSize < m_nMinSize)
{
m_nFill = m_nMinSize - m_nSize;
m_nSize = m_nMinSize;
}
else
m_nFill = 0;
m_nOffset = RoundDiv((nViewSize - m_nFill) * nViewOffset, nRealSize);
}
else
{
m_nSize = 0;
m_nOffset = 0;
}
}
int GetViewOffset(int nViewSize, int nRealSize)
{
int nOffset = RoundDiv(m_nOffset * nRealSize, nViewSize - m_nFill);
return nOffset;
}
};
//-------------------------------------------------------------------------
//
// CTouchWindow
//
//-------------------------------------------------------------------------
// CTouchWindow
//
// Minin template class for touch-based windows
//
template <class T>
class CTouchWindow
{
protected:
CPoint m_ptOrg, // View origin
m_ptLast; // Last clicked point
CSize m_sizeExt, // Document size
m_sizeWnd, // Window size
m_sizeMove, // Automatic scrolling movement size
m_sizeSens, // Sensitivity
m_sizeAccel, // Number of pixels to reduce m_sizeMove per 1/4 sec
m_sizeScroll; // Scroll bar widths (cx -> Vertical, cy -> Horizontal)
CScrollBarData m_sbVer, // Vertical scroll bar data
m_sbHor; // Horizontal scroll bar data
bool m_bTimer,
m_bScroll;
DWORD m_dwTickIni, // Initial tick
m_dwTickMove,
m_dwTickDown; // Tick on WM_LBUTTONDOWN
CBitmap m_bmpScrollVer,
m_bmpScrollHor;
void Decelerate(CSize &move)
{
DWORD dwNumTicks = GetTickCount() - m_dwTickIni;
int nMult = dwNumTicks / 250;
if(move.cx && m_sizeAccel.cx > 0)
{
if(move.cx > 0)
{
move.cx -= nMult * m_sizeAccel.cx;
if(move.cx < 0)
move.cx = 0;
}
else
{
move.cx += nMult * m_sizeAccel.cx;
if(move.cx > 0)
move.cx = 0;
}
}
if(move.cy && m_sizeAccel.cy > 0)
{
if(move.cy > 0)
{
move.cy -= nMult * m_sizeAccel.cy;
if(move.cy < 0)
move.cy = 0;
}
else
{
move.cy += nMult * m_sizeAccel.cy;
if(move.cy > 0)
move.cy = 0;
}
}
}
public:
CTouchWindow()
: m_bTimer (false),
m_bScroll (false),
m_sizeScroll(DRA::SCALEX(16), DRA::SCALEY(16)),
m_sizeAccel ( 1, 1),
m_sizeSens ( 4, 4),
m_dwTickIni (0),
m_dwTickDown(0),
m_dwTickMove(0)
{
}
int GetVScrollWidth () { return m_sizeScroll.cx; }
int GetHScrollHeight() { return m_sizeScroll.cy; }
bool IsVScrollVisible() { return m_sbVer.IsVisible(); }
bool IsHScrollVisible() { return m_sbHor.IsVisible(); }
void SetExtent(CSize size) { m_sizeExt = size; }
void SetExtent(int cx, int cy) { m_sizeExt = CSize(cx, cy); }
void SetScrollWidth(int cx, int cy) { m_sizeScroll.SetSize(cx, cy); }
void SetSensitivity(int cx, int cy) { m_sizeSens.SetSize(cx, cy); }
void SetOrigin(int x, int y)
{
m_ptOrg.SetPoint(x, y);
UpdateScrollBars();
}
void UpdateScrollBars()
{
m_sbVer.SetViewData(m_ptOrg.y, m_sizeWnd.cy, m_sizeExt.cy);
m_sbHor.SetViewData(m_ptOrg.x, m_sizeWnd.cx, m_sizeExt.cx);
}
void DrawScrollBars(HDC hDC)
{
CDCHandle dc(hDC);
CDC dcMem;
int cyBar,
yBar;
CRect rcThumb,
rcBmp;
HBRUSH hOldBrush;
HBITMAP hOldBmp;
if(!m_sbVer.IsVisible())
return;
if(m_sizeWnd.cy >= m_sizeExt.cy)
return;
dcMem.CreateCompatibleDC(hDC);
if(m_bmpScrollVer.IsNull())
m_bmpScrollVer.CreateCompatibleBitmap(hDC, m_sizeScroll.cx, m_sizeWnd.cy);
hOldBmp = dcMem.SelectBitmap(m_bmpScrollVer);
rcBmp.left = 0;
rcBmp.top = 0;
rcBmp.right = m_sizeScroll.cx;
rcBmp.bottom = m_sizeWnd.cy;
dcMem.FillRect(&rcBmp, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
cyBar = m_sbVer.GetSize();
yBar = m_sbVer.GetOffset();
rcThumb.top = yBar;
rcThumb.bottom = yBar + cyBar;
rcThumb.right = rcBmp.right - 2;
rcThumb.left = rcBmp.left + 2;
hOldBrush = dcMem.SelectBrush((HBRUSH)GetStockObject(GRAY_BRUSH));
dcMem.RoundRect(&rcThumb, CPoint(4, 4));
dcMem.SelectBrush(hOldBrush);
#if _WIN32_WCE > 0x500
BLENDFUNCTION blend;
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.AlphaFormat = 0;
blend.SourceConstantAlpha = 128;
::AlphaBlend(dc, m_sizeWnd.cx - m_sizeScroll.cx, 0, m_sizeScroll.cx, m_sizeWnd.cy,
dcMem, 0, 0, rcBmp.right, rcBmp.bottom, blend);
#else
dc.BitBlt(m_sizeWnd.cx - m_sizeScroll.cx, 0, m_sizeScroll.cx, m_sizeWnd.cy,
dcMem, 0, 0, SRCCOPY);
#endif
dcMem.SelectBitmap(hOldBmp);
}
BEGIN_MSG_MAP(CTouchWindow<T>)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown )
MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp )
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove )
MESSAGE_HANDLER(WM_TIMER, OnTimer )
MESSAGE_HANDLER(WM_SIZE, OnSize )
END_MSG_MAP()
LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
T* pWnd = static_cast<T*>(this);
if(m_bTimer)
{
pWnd->KillTimer(SCROLL_TIMER);
m_bTimer = false;
}
m_ptLast = pt;
// Check if the user is clicking on any of the scroll bars
if(m_sbVer.IsVisible() && (pt.x >= m_sizeWnd.cx - m_sizeScroll.cx))
{
m_sbVer.SetHitPos(pt.y);
}
else if(m_sbHor.IsVisible() && (pt.y >= m_sizeWnd.cy - m_sizeScroll.cy))
{
m_sbHor.SetHitPos(pt.x);
}
else
{
// User is clicking the main window area
m_sizeMove.SetSize(0, 0);
m_dwTickIni = GetTickCount();
m_dwTickDown = m_dwTickIni;
m_bScroll = false;
m_sbVer.SetHitArea(SBHIT_NONE);
m_sbHor.SetHitArea(SBHIT_NONE);
}
return 0;
}
LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
T* pWnd = static_cast<T*>(this);
if(m_bScroll)
{
//CSize move = m_ptLast - pt;
//DWORD dwTickNow = GetTickCount();
if(m_sizeSens.cx < 0)
m_sizeMove.cx = 0;
if(m_sizeSens.cy < 0)
m_sizeMove.cy = 0;
if(abs(m_sizeMove.cx) > m_sizeSens.cx || abs(m_sizeMove.cy) > m_sizeSens.cy)
{
if(m_dwTickMove != 0)
{
pWnd->SetTimer(SCROLL_TIMER, m_dwTickMove);
m_bTimer = true;
}
}
}
m_bScroll = false;
return 0;
}
LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CPoint pt(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
if(wParam & MK_LBUTTON)
{
T* pWnd = static_cast<T*>(this);
CSize move = m_ptLast - pt;
DWORD dwTickNow;
m_ptLast = pt;
// Check for motion sensitivity
if((m_sizeSens.cx < 0) || (abs(move.cx) <= m_sizeSens.cx))
move.cx = 0;
if((m_sizeSens.cy < 0) || (abs(move.cy) <= m_sizeSens.cy))
move.cy = 0;
if((move.cx == 0) && (move.cy == 0))
return 0;
m_bScroll = false;
if(m_sbVer.GetHitArea() != SBHIT_NONE)
{
m_sbVer.SetOffset(m_sbVer.GetOffset() - move.cy);
m_ptOrg.y = m_sbVer.GetViewOffset(m_sizeWnd.cy, m_sizeExt.cy);
}
else if(m_sbHor.GetHitArea() != SBHIT_NONE)
{
m_sbHor.SetOffset(m_sbHor.GetOffset() - move.cx);
m_ptOrg.x = m_sbVer.GetViewOffset(m_sizeWnd.cx, m_sizeExt.cx);
}
else
{
if(m_sizeExt.cx > m_sizeWnd.cx)
{
m_sizeMove.cx = move.cx;
m_ptOrg.x += move.cx;
}
else
move.cx = 0;
if(m_sizeExt.cy > m_sizeWnd.cy)
{
m_sizeMove.cy = move.cy;
m_ptOrg.y += move.cy;
}
else
move.cy = 0;
m_bScroll = (move.cx != 0) || (move.cy != 0);
}
// Check if the scroll is out of bound
if(m_ptOrg.y < 0)
m_ptOrg.y = 0;
else if((m_sizeExt.cy > m_sizeWnd.cy) && (m_ptOrg.y + m_sizeWnd.cy > m_sizeExt.cy))
m_ptOrg.y = m_sizeExt.cy - m_sizeWnd.cy;
if(m_ptOrg.x < 0)
m_ptOrg.x = 0;
else if((m_sizeExt.cx > m_sizeWnd.cx) && (m_ptOrg.x + m_sizeWnd.cx > m_sizeExt.cx))
m_ptOrg.x = m_sizeExt.cx - m_sizeWnd.cx;
m_sbVer.SetViewData(m_ptOrg.y, m_sizeWnd.cy, m_sizeExt.cy);
m_sbHor.SetViewData(m_ptOrg.x, m_sizeWnd.cx, m_sizeExt.cx);
pWnd->InvalidateRect(NULL, FALSE);
pWnd->UpdateWindow();
dwTickNow = GetTickCount();
m_dwTickMove = dwTickNow - m_dwTickIni;
m_dwTickIni = dwTickNow;
}
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
CSize size(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
if(size != m_sizeWnd)
{
T* pWnd = static_cast<T*>(this);
m_sizeWnd = size;
m_sbVer.SetViewData(m_ptOrg.y, size.cy, m_sizeExt.cy);
m_sbHor.SetViewData(m_ptOrg.x, size.cx, m_sizeExt.cx);
if(!m_bmpScrollVer.IsNull())
m_bmpScrollVer.DeleteObject();
if(!m_bmpScrollHor.IsNull())
m_bmpScrollHor.DeleteObject();
pWnd->InvalidateRect(NULL, FALSE);
pWnd->UpdateWindow();
}
return 0;
}
LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if(wParam == SCROLL_TIMER)
{
T* pWnd = static_cast<T*>(this);
CSize move = m_sizeMove;
Decelerate(move);
// Move the origin
m_ptOrg += move;
// Check for bounds
if(m_ptOrg.x < 0)
{
m_ptOrg.x = 0;
m_sizeMove.cx = 0;
}
else if(m_ptOrg.x + m_sizeWnd.cx > m_sizeExt.cx)
{
m_ptOrg.x = m_sizeExt.cx - m_sizeWnd.cx;
m_sizeMove.cx = 0;
}
if(m_ptOrg.y < 0)
{
m_ptOrg.y = 0;
m_sizeMove.cy = 0;
}
else if(m_ptOrg.y + m_sizeWnd.cy > m_sizeExt.cy)
{
m_ptOrg.y = m_sizeExt.cy - m_sizeWnd.cy;
m_sizeMove.cy = 0;
}
// Check if the speed reached zero
if(move.cx == 0 && move.cy == 0)
{
pWnd->KillTimer(SCROLL_TIMER);
m_bTimer = false;
}
m_sbVer.SetViewData(m_ptOrg.y, m_sizeWnd.cy, m_sizeExt.cy);
m_sbHor.SetViewData(m_ptOrg.x, m_sizeWnd.cx, m_sizeExt.cx);
pWnd->InvalidateRect(NULL, FALSE);
pWnd->UpdateWindow();
}
return 0;
}
};
//-------------------------------------------------------------------------
//
// CTouchList
//
//-------------------------------------------------------------------------
// Forwad declaration
class CTouchListBase;
#define TLIS_SELECTED 0x00000001
#define TLIS_HASFOCUS 0x00000002
#define TLIS_ENABLED 0x00000004
#define TLIHIT_NONE 0x00000000
#define TLIHIT_SELECT 0x00000001
#define TLIHIT_TEXT 0x00000002
// CTouchListItem
//
// A touch list item
//
class CTouchListItem
{
protected:
CTouchListBase* m_pList;
DWORD m_dwData,
m_dwState;
volatile int m_nHeight;
bool m_bManaged; // Deleted by container?
public:
CTouchListItem()
: m_pList (NULL),
m_dwData (0),
m_dwState (0),
m_nHeight (0),
m_bManaged (true)
{
}
virtual ~CTouchListItem()
{
}
bool IsManaged () { return m_bManaged; }
bool IsSelected () { return (m_dwState & TLIS_SELECTED) != 0; }
bool IsEnabled () { return (m_dwState & TLIS_ENABLED) != 0; }
bool HasFocus () { return (m_dwState & TLIS_HASFOCUS) != 0; }
DWORD GetData() { return m_dwData; }
void SetData(DWORD dwData) { m_dwData = dwData; }
DWORD GetState() { return m_dwState; }
void SetState(DWORD dwFlags, bool bSet) { m_dwState = bSet ? (m_dwState | dwFlags) : (m_dwState & ~dwFlags); }
void SetSelected (bool bSelected) { SetState(TLIS_SELECTED, bSelected); }
void SetFocused (bool bFocused) { SetState(TLIS_HASFOCUS, bFocused ); }
void SetEnabled (bool bEnabled) { SetState(TLIS_ENABLED, bEnabled ); }
void SetManaged (bool bManaged) { m_bManaged = bManaged; }
int GetHeight() { return m_nHeight; }
void SetHeight(int nHeight) { m_nHeight = nHeight; }
void SetList (CTouchListBase* pList) { m_pList = pList; }
CTouchListBase* GetList () { return m_pList; }
virtual void Draw (HDC hDC, int iItem, CRect& rcItem) { }
virtual int HitTest (int iItem, CRect& rcItem, CPoint& ptHit) { return TLIHIT_SELECT; }
};
#define TLN_FIRST (0U - 2000U)
#define TLN_LAST (0U - 2100U)
#define TLN_ITEMSELECTED (TLN_FIRST - 0)
#define TLN_ITEMACTIVATED (TLN_FIRST - 1)
#define TLN_ITEMGOTFOCUS (TLN_FIRST - 2)
typedef struct _NMTOUCHLIST
{
NMHDR hdr;
int iItem; // Item index
DWORD dwState; // Item state
} NMTOUCHLIST;
// CTouchListBase
//
// Base class for the touch list
//
class CTouchListBase
{
protected:
CTouchArray<CTouchListItem*> m_items;
CTouchArray<size_t> m_selected;
int m_nItems,
m_iClickItem,
m_iActiveItem,
m_iFocusItem;
bool m_bSingleSel;
CBitmap m_bmpMem;
CSize m_cxyBmp;
public:
CTouchListBase()
: m_nItems (0),
m_iClickItem (-1),
m_iActiveItem (-1),
m_iFocusItem (-1), // Item with the focus
m_bSingleSel (true),
m_cxyBmp (0, 0)
{
}
void AddItem(CTouchListItem* pItem)
{
pItem->SetList(this);
m_items.Add(pItem);
++m_nItems;
}
void InsertItem(CTouchListItem* pItem, int iIndex)
{
pItem->SetList(this);
m_items.InsertAt(iIndex, pItem);
++m_nItems;
}
void RemoveItem(int iIndex)
{
if(m_items[iIndex]->IsManaged())
delete m_items[iIndex];
m_items.RemoveAt(iIndex);
--m_nItems;
}
int GetItemCount() { return (int)m_items.GetCount(); }
int GetActiveItem () { return m_iActiveItem; }
int GetFocusItem () { return m_iFocusItem; }
CTouchListItem* GetItem (int iItem) { return m_items[iItem]; }
int GetItemIndex(CTouchListItem* pItem)
{
size_t i, n = m_items.GetCount();
for(i = 0; i < n; ++i)
{
if(m_items[i] == pItem)
return (int)i;
}
return -1;
}
int GetItemAt(int yPos)
{
int i, n = (int)m_items.GetCount(),
y = 0;
for(i = 0; i < n; ++i)
{
y += m_items[i]->GetHeight();
if(yPos <= y)
return i;
}
return -1;
}
int GetItemHeightSum(int nItems)
{
int i, h = 0;
for(i = 0; i < nItems; ++i)
h += m_items[i]->GetHeight();
return h;
}
int GetTotalItemHeight()
{
return GetItemHeightSum((int)m_items.GetCount());
}
};
// CTouchList
//
// Implements a touch list
//
template <class TBase>
class CTouchList : public CWindowImpl<CTouchList<TBase> >,
public CTouchWindow<CTouchList<TBase> >,
public CTouchListBase
{
protected:
bool IsValidItem(int iIndex) { return iIndex >= 0 && iIndex < (int)m_items.GetCount(); }
void NotifyItemChange(int iIndex, UINT code, DWORD dwState)
{
NMTOUCHLIST nmtl;
TBase* pT = static_cast<TBase*>(this);
CWindow wndParent = GetParent();
nmtl.hdr.code = code;
nmtl.hdr.hwndFrom = m_hWnd;
nmtl.hdr.idFrom = ::GetDlgCtrlID(m_hWnd);
nmtl.iItem = iIndex;
nmtl.dwState = dwState;
// Call "virtual" method in derived class
pT->OnItemChangedState(nmtl);
wndParent.SendMessage(WM_NOTIFY, nmtl.hdr.idFrom, (LPARAM)&nmtl);
}
public:
CTouchList()
{
}
DECLARE_WND_CLASS(NULL)
BOOL PreTranslateMessage(MSG* pMsg)
{
pMsg;
return FALSE;
}
void ClearItems()
{
size_t i;
m_selected.RemoveAll();
for(i = 0; i < m_items.GetCount(); ++i)
{
CTouchListItem* pItem = m_items[i];
if(pItem != NULL && pItem->IsManaged())
delete pItem;
m_items[i] = NULL;
}
m_items.RemoveAll();
SetExtent(m_sizeWnd.cx, 0);
m_nItems = 0;
m_iFocusItem = -1;
SetListPos(0, 0);
}
void SetItemCount(int nItems)
{
int i;
ClearItems();
m_nItems = nItems;
m_items.SetCount(nItems);
for(i = 0; i < nItems; ++i)
m_items[i] = NULL;
SetExtent(m_sizeWnd.cx, GetTotalItemHeight());
SetListPos(m_ptOrg.x, m_ptOrg.y);
}
void TotalItemHeightUpdated()
{
m_sizeExt.cy = GetTotalItemHeight();
UpdateScrollBars();
}
void SetListPos(int xOrg, int yOrg)
{
if(IsWindow())
{
CRect rc;
GetClientRect(&rc);
m_sbVer.SetViewData(yOrg, rc.Height(), GetTotalItemHeight());
}
m_ptOrg.x = xOrg;
m_ptOrg.y = yOrg;
}
int GetFirstVisibleItem()
{
return GetItemAt(m_ptOrg.y);
}
void ClearSelection()
{
size_t i;
for(i = 0; i < m_selected.GetCount(); ++i)
{
size_t iItem = m_selected[i];
m_items[iItem]->SetState(TLIS_SELECTED, false);
}
m_selected.RemoveAll();
}
void OnItemChangedState(NMTOUCHLIST& nmtl)
{
}
void OnItemGotFocus(NMTOUCHLIST& nmtl)
{
}
void OnItemSelected(NMTOUCHLIST& nmtl)
{
}
void OnItemActivated(NMTOUCHLIST& nmtl)
{
}
// RedrawItem
//
// Redraws the item at the given index
//
void RedrawItem(int iIndex, bool bUpdateWindow = true)
{
ATLASSERT(IsValidItem(iIndex));
if(!IsWindow())
return;
CRect rc;
GetItemRect(iIndex, rc);
InvalidateRect(&rc, FALSE);
if(bUpdateWindow)
UpdateWindow();
}
// EnsureVisible
//
// Scrolls the item into view
//
void EnsureVisible(int iItem)
{
CRect rc;
if(m_sizeWnd.cy == 0 || m_sizeWnd.cx == 0)
return;
GetItemRect(iItem, rc);
if(rc.top < 0)
{
// Item is totally or partially hidden to the top
int dy = -rc.top,
ym = DRA::SCALEY(8),
yy,
yOld = m_ptOrg.y;
for(yy = ym; yy < dy; yy += ym)
{
SetOrigin(0, yOld - yy);
InvalidateRect(NULL, FALSE);
UpdateWindow();
}
SetOrigin(0, yOld - dy);
InvalidateRect(NULL, FALSE);
UpdateWindow();
}
else if(rc.bottom > m_sizeWnd.cy)
{
// Item is totally or partially hidden to the bottom
int dy = rc.bottom - m_sizeWnd.cy,
ym = DRA::SCALEY(8),
yy,
yOld = m_ptOrg.y;
for(yy = ym; yy < dy; yy += ym)
{
SetOrigin(0, yOld + yy);
InvalidateRect(NULL, FALSE);
UpdateWindow();
}
SetOrigin(0, yOld + dy);
InvalidateRect(NULL, FALSE);
UpdateWindow();
}
}
int HitTest(CPoint pt, int& iItem)
{
iItem = GetItemAt(m_ptOrg.y + pt.y);
if(IsValidItem(iItem))
{
CRect rcItem;
GetItemRect(iItem, rcItem);
return m_items[iItem]->HitTest(iItem, rcItem,pt);
}
return TLIHIT_NONE;
}
// FocusItem
//
// Sets the focus on an item. Only one item can have the focus at any one time.
//
void FocusItem(int iItem)
{
// Check if old and new are the same. If so, skip further processing.
if(iItem == m_iFocusItem)
return;
// Start by removing the focus from the current item
if(IsValidItem(m_iFocusItem))
{
m_items[m_iFocusItem]->SetFocused(false);
NotifyItemChange(m_iFocusItem, TLN_ITEMGOTFOCUS, 0);
}
// Now set the focus to the new item
if(IsValidItem(iItem))
{
EnsureVisible(iItem);
m_items[iItem]->SetFocused(true);
NotifyItemChange(iItem, TLN_ITEMGOTFOCUS, 1);
m_iFocusItem = iItem;
}
else
m_iFocusItem = -1;
}
// SelectItem
//
// Selects an item on the list. If the list is in single-selection mode,
// de-selects the previous item.
//
void SelectItem(int iItem)
{
// Is this a single-selection list?
// If so, clear the last selection
if(m_bSingleSel && m_selected.GetCount())
{
int iOld = (int)m_selected[0];
m_items[iOld]->SetState(TLIS_SELECTED, false);
NotifyItemChange(iOld, TLN_ITEMSELECTED, 0);
ClearSelection();
}
// Check if the new item index is in bounds
if(IsValidItem(iItem))
{
// Now, select the new item
m_items[iItem]->SetState(TLIS_SELECTED, true);
m_selected.Add((size_t)iItem);
NotifyItemChange(iItem, TLN_ITEMSELECTED, 1);
}
}
void ActivateItem(int iItem, bool bActive)
{
// Deactivate the old active item, if any
if(bActive && IsValidItem(m_iActiveItem))
NotifyItemChange(m_iActiveItem, TLN_ITEMACTIVATED, 0);
m_iActiveItem = bActive ? iItem : -1;
if(IsValidItem(iItem))
NotifyItemChange(iItem, TLN_ITEMACTIVATED, bActive ? 1 : 0);
}
void PreDrawList(HDC hDC, CRect &rcClient)
{
}
void PostDrawList(HDC hDC)
{
}
void DrawItem(HDC hDC, int iItem, CRect &rcItem)
{
}
CTouchListItem* NewListItem(int iItem)
{
return new CTouchListItem;
}
void GetItemRect(int iItem, RECT& rc)
{
GetClientRect(&rc);
rc.top = GetItemHeightSum(iItem) - m_ptOrg.y;
rc.bottom = rc.top + m_items[iItem]->GetHeight();
}
int GetItemVisibility(int iItem)
{
return 0;
}
// GetNextItemIndex
//
// Calculates the next item index for navigation purposes.
// Override in your derived class for custom behavior.
//
int GetNextItemIndex(int iItem, int delta)
{
return iItem + delta;
}
BEGIN_MSG_MAP(CTouchList<TBase>)
MESSAGE_HANDLER (WM_PAINT, OnPaint )
MESSAGE_HANDLER (WM_SIZE, OnSize )
MSG_WM_ERASEBKGND (OnEraseBackground)
MSG_WM_LBUTTONDOWN (OnLButtonDown)
MSG_WM_LBUTTONUP (OnLButtonUp)
// MSG_WM_MOUSEMOVE(OnMouseMove)
MSG_WM_DESTROY (OnDestroy)
MSG_WM_KEYUP (OnKeyUp)
CHAIN_MSG_MAP(CTouchWindow<CTouchList<TBase> >)
END_MSG_MAP()
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
TBase* pT = static_cast<TBase*>(this);
CPaintDC dc(m_hWnd);
CDC dcMem;
HBITMAP hOldBmp;
CRect rcClient,
rcItem,
rcPaint(dc.m_ps.rcPaint),
rcIntersect;
int iItem,
cyItem;
BOOL bOk;
DWORD dwError = 0;
CTouchListItem* pItem;
dcMem.CreateCompatibleDC(dc);
GetClientRect(&rcClient);
rcItem = rcClient;
// Check if the second-plane bitmap needs to be recreated
if(m_cxyBmp.cx != rcClient.Width() && m_cxyBmp.cy != rcClient.Height())
{
m_cxyBmp.cx = rcClient.Width();
m_cxyBmp.cy = rcClient.Height();
if(!m_bmpMem.IsNull())
m_bmpMem.DeleteObject();
m_bmpMem.CreateCompatibleBitmap(dc, m_cxyBmp.cx, m_cxyBmp.cy);
}
hOldBmp = dcMem.SelectBitmap(m_bmpMem);
// Calculate the first item to display
iItem = GetFirstVisibleItem();
if(iItem != -1)
{
cyItem = m_items[iItem]->GetHeight();
rcItem.top = GetItemHeightSum(iItem) - m_ptOrg.y;
rcItem.bottom = rcItem.top + cyItem;
pT->PreDrawList(dcMem, rcClient);
while((rcItem.top < rcClient.bottom) && (iItem >= 0) && (iItem < m_nItems))
{
CRect rcTest;
pItem = m_items[iItem];
if(pItem == NULL)
pItem = m_items[iItem] = pT->NewListItem(iItem);
// Calculate the intersection between the item's rect and the paint rect
rcTest.IntersectRect(rcPaint, rcItem);
// Paint only if the the intersection rect is not empty
if(!rcTest.IsRectEmpty())
pT->DrawItem(dcMem, iItem, rcItem);
cyItem = pItem->GetHeight();
if (iItem < m_nItems - 1)
{
rcItem.top += cyItem;
rcItem.bottom = rcItem.top + m_items[iItem+1]->GetHeight();
}
++iItem;
}
}
// Check if the list did not fill the whole window
if((rcItem.top < rcClient.bottom))
{
CRect rc(rcClient);
//rc.top = (iItem == -1) ? rcItem.top : rcItem.bottom;
if (iItem != -1)
rc.top = rcItem.bottom;
if(!dcMem.FillRect(&rc, (HBRUSH)GetStockObject(WHITE_BRUSH)))
dwError = GetLastError();
}
pT->DrawScrollBars(dcMem);
pT->PostDrawList(dcMem);
int xPaint = dc.m_ps.rcPaint.left;
int yPaint = dc.m_ps.rcPaint.top;
// Paint only the invalidated area for performance reasons
bOk = dc.BitBlt(xPaint, yPaint, m_cxyBmp.cx, m_cxyBmp.cy, dcMem, xPaint, yPaint, SRCCOPY);
//bOk = dc.BitBlt(0, 0, m_cxyBmp.cx, m_cxyBmp.cy, dcMem, 0, 0, SRCCOPY);
// This is here for debugging purposes
if(!bOk)
dwError = GetLastError();
dcMem.SelectBitmap(hOldBmp);
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
CSize size(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
SetExtent(size.cx, GetTotalItemHeight());
bHandled = FALSE;
return 0;
}
LRESULT OnEraseBackground(HDC hDC)
{
return TRUE;
}
LRESULT OnLButtonDown(UINT flags, CPoint pt)
{
SetMsgHandled(FALSE);
if(m_sbVer.IsVisible() && pt.x > m_sizeWnd.cx - m_sizeScroll.cx)
m_sbVer.SetHitPos(pt.x);
else
m_sbVer.SetHitArea(SBHIT_NONE);
m_iClickItem = GetItemAt(m_ptOrg.y + pt.y);
FocusItem(m_iClickItem);
return 0;
}
LRESULT OnLButtonUp(UINT flags, CPoint pt)
{
SetMsgHandled(FALSE);
if(m_sbVer.GetHitArea() == SBHIT_NONE)
{
DWORD dwTickCount = GetTickCount() - m_dwTickDown;
// Select the item only if we saw a button down / up within 200 milliseconds
if(dwTickCount < 200)
{
int iItem,
nHitTest = HitTest(pt, iItem);
if(iItem != m_iClickItem)
return 0;
KillTimer(SCROLL_TIMER);
m_bTimer = false;
if(nHitTest == TLIHIT_SELECT)
{
// Select and activate the item
SelectItem (iItem);
ActivateItem(iItem, true);
}
SetMsgHandled(TRUE);
}
}
return 0;
}
LRESULT OnDestroy(void)
{
//You should call SetMsgHandled(FALSE) or set bHandled = FALSE for the main window of your application
ClearItems();
SetMsgHandled(FALSE);
return 0;
}
// OnKeyUp
//
// The user released a key
//
void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
TBase* pT = static_cast<TBase*>(this);
switch(nChar)
{
case VK_UP:
if(m_iFocusItem == -1)
FocusItem((int)m_items.GetCount() - 1);
else
{
int iNext = pT->GetNextItemIndex(m_iFocusItem, -1);
FocusItem(iNext);
}
break;
case VK_DOWN:
if(m_iFocusItem == -1)
FocusItem(0);
else
{
int iNext = pT->GetNextItemIndex(m_iFocusItem, 1);
FocusItem(iNext);
}
break;
case VK_RETURN:
if(IsValidItem(m_iFocusItem))
ActivateItem(m_iFocusItem, true);
break;
default:
// All other keys are processed by the base class
SetMsgHandled(FALSE);
}
}
};