#if !defined(AFX_COOLTABCTRL_H__20010513_D899_C89D_D02E_0080AD509054__INCLUDED_)
#define AFX_COOLTABCTRL_H__20010513_D899_C89D_D02E_0080AD509054__INCLUDED_

#pragma once

/////////////////////////////////////////////////////////////////////////////
// CCoolTabCtrls - A set of Tab Controls with different appearances
//
// Written by Bjarke Viksoe (bjarke@viksoe.dk)
// Copyright (c) 2001 Bjarke Viksoe.
//
// Add the following macro to the parent's message map:
//   REFLECT_NOTIFICATIONS()
// CFolderTabCtrl code based on a Paul DiLascia MSJ 1999 article.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed by any means PROVIDING it is 
// not sold for profit without the authors written consent, and 
// providing that this notice and the authors name is included. 
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever. It's free, so don't hassle me about it.
//
// Beware of bugs.
//

#ifndef __cplusplus
  #error WTL requires C++ compilation (use a .cpp suffix)
#endif

#ifndef __ATLAPP_H__
  #error CoolTabCtrls.h requires atlapp.h to be included first
#endif

#ifndef __ATLCTRLS_H__
  #error CoolTabCtrls.h requires atlctrls.h to be included first
#endif

#if (_WIN32_IE < 0x0400)
  #error CoolTabCtrls.h requires _WIN32_IE >= 0x0400
#endif


#define TCN_INITIALIZE TCN_FIRST-10
#define TCN_INSERTITEM TCN_FIRST-11
#define TCN_DELETEITEM TCN_FIRST-12

struct TC_SIZES
{
   int iPadding;
   int iMargin;
   int iSelMargin;
   int iIndent;
};

template <class T, class TBase = CWindow, class TWinTraits = CControlWinTraits>
class ATL_NO_VTABLE CCustomTabCtrl : 
   public CWindowImpl< T, TBase, TWinTraits >
{
public:
   CCustomTabCtrl() : m_iCurSel(-1)
   {
      ::ZeroMemory(&m_settings, sizeof(TC_SIZES));
   }

   int m_iCurSel;
   TC_SIZES m_settings;
   CSimpleArray< LPTCITEM > m_Items;
   CSimpleValArray< RECT > m_Sizes;
   UINT m_idDlgCtrl;

   BEGIN_MSG_MAP(CCustomTabCtrl)
      MESSAGE_HANDLER(WM_CREATE, OnCreate)
      MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
      MESSAGE_HANDLER(WM_PAINT, OnPaint)
      MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
      MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonClick)
      MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
      MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
      MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
   END_MSG_MAP()

   // Operations

   BOOL SubclassWindow(HWND hWnd)
   {
      ATLASSERT(m_hWnd == NULL);
      ATLASSERT(::IsWindow(hWnd));
      BOOL bRet = CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
      if( bRet ) _Init();
      return bRet;
   }

   // Implementation

   void _Init()
   {
      ATLASSERT(::IsWindow(m_hWnd));

#ifdef _DEBUG
      // Check class
      TCHAR szBuffer[20];
      if( ::GetClassName(m_hWnd, szBuffer, sizeof(szBuffer)/sizeof(TCHAR)) ) {
         ATLASSERT(::lstrcmpi(szBuffer, CStatic::GetWndClassName())==0);
      }
#endif
      ATLASSERT(GetStyle() & WS_CHILD);
      ModifyStyle(0, SS_NOTIFY); // We need this for mouse-clicks

      m_idDlgCtrl = GetDlgCtrlID();

      T* pT = static_cast<T*>(this);
      BOOL bDummy;
      pT->OnSettingChange(0,0,0,bDummy);

      // This is a little WTL subclass helper notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_INITIALIZE };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);
   }

   void _InflateSize(int i, int cx, int cy)
   {
      ATLASSERT(i>=0 && i<m_Sizes.GetSize());
      RECT rc = m_Sizes[i];
      ::InflateRect(&rc, cx,cy);
      m_Sizes.SetAtIndex(i, rc);
   }

   // Message Handlers

   LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      _Init();
      return 0;
   }

   LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      DeleteAllItems();
      return 0;
   }

   LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      // Empty - but might get overloaded by derived classes.
      Invalidate();
      return 0;
   }

   LRESULT OnGetDlgCode(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
   {
      return DefWindowProc(uMsg, wParam, lParam) | DLGC_WANTARROWS;
   }

   LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      T* pT = static_cast<T*>(this);
      if( wParam != NULL ) {
         RECT rc;
         GetClientRect(&rc);
         pT->DoPaint((HDC)wParam, rc);
      }
      else {
         PAINTSTRUCT m_ps;
         HDC hDC = ::BeginPaint(m_hWnd, &m_ps);
         pT->DoPaint(hDC, m_ps.rcPaint);
         ::EndPaint(m_hWnd, &m_ps);
      }
      return 0;
   }

   LRESULT OnLButtonClick(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
   {
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, NM_CLICK };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);

      // Search for a tab
      T* pT = static_cast<T*>(this);
      POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
      TCHITTESTINFO tchti = { 0 };
      tchti.pt = pt;
      int idx = pT->HitTest(&tchti);
      if( idx!=-1 ) {
         SetFocus();
         SetCurSel(idx);
      }
      return 0;
   }

   LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
   {
      switch( wParam ) {
      case VK_LEFT:
         if( m_iCurSel>0 ) SetCurSel(m_iCurSel-1);
         return 0;
      case VK_RIGHT:
         if( m_iCurSel<m_Items.GetSize()-1 ) SetCurSel(m_iCurSel+1);
         return 0;
      }
      bHandled = FALSE;
      return 0;
   }

   // Overridables

   void UpdateLayout()
   {
      CClientDC dc(m_hWnd);
      HFONT hOldFont = (HFONT)::SelectObject(dc, GetFont());    

      RECT rcClient;
      GetClientRect(&rcClient);

      // Reposition buttons
      int cnt = m_Sizes.GetSize();
      int xpos = m_settings.iIndent;
      for( int i=0; i<cnt; i++ ) {
         LPTCITEM pItem = m_Items[i];
         RECT rc;
         rc.left = rc.right = xpos;
         if( pItem->mask & TCIF_TEXT ) {
            RECT rcText = { 0 };
            ::DrawText(dc, pItem->pszText, ::lstrlen(pItem->pszText), &rcText, DT_SINGLELINE|DT_CALCRECT);
            rc.right += (rcText.right-rcText.left) + (m_settings.iPadding*2);
         }
         rc.top = 0;
         rc.bottom = rcClient.bottom-rcClient.top;
         m_Sizes.SetAtIndex(i, rc);
         xpos += (rc.right-rc.left) + m_settings.iMargin;
      }
      if( m_iCurSel!=-1 ) {
         _InflateSize(m_iCurSel, m_settings.iSelMargin, 0);
      }
      
      ::SelectObject(dc, hOldFont);
   }

   void DoPaint(HDC hdc, RECT &rcClip)
   {
      // NOTE: The handling of NM_CUSTOMDRAW is probably not entirely correct
      //       in the code below. But at least it makes a brave attempt to
      //       implement all the states described in MSDN.

      // Choose which DC to use: The HDC passed has a clip region,
      // while the client DC paints the entire item.
      CClientDC dcClient(m_hWnd);
      if( m_settings.iSelMargin>0 ) {
         hdc = dcClient;
      }

      // Save current DC selections
      int save = ::SaveDC(hdc);
      ATLASSERT(save!=0);

      // Make sure we don't paint outside client area (possible with paint dc)
      RECT rcClient;
      GetClientRect(&rcClient);
      ::IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);

      // Prepare DC
      ::SelectObject(hdc, GetFont());

      T* pT = static_cast<T*>(this);
      LRESULT lResStage;
      NMCUSTOMDRAW nmc = { 0 };
      nmc.hdr.hwndFrom = m_hWnd;
      nmc.hdr.idFrom = m_idDlgCtrl;
      nmc.hdr.code = NM_CUSTOMDRAW;
      nmc.hdc = hdc;
      nmc.uItemState = 0;

      nmc.dwDrawStage = CDDS_PREPAINT;
      lResStage = ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM)&nmc);
      if( lResStage==CDRF_NOTIFYITEMDRAW || lResStage==CDRF_DODEFAULT ) {
         RECT rc;
         int cnt = m_Items.GetSize();
         // Draw the list items, except the selected one. It is drawn last
         // so it can cover the tabs below it.
         RECT rcIntersect;
         for( int i=0; i<cnt; i++ ) {
            rc = m_Sizes[i];
            if( i!=m_iCurSel ) {
               if( ::IntersectRect(&rcIntersect, &rc, &rcClip) ) {
                  nmc.dwItemSpec = i;
                  nmc.uItemState = 0;
                  nmc.rc = rc;
                  pT->ProcessItem(lResStage, nmc);
               }
            }
         }
         if( m_iCurSel!=-1 ) {
            rc = m_Sizes[m_iCurSel];
            if( ::IntersectRect(&rcIntersect, &rc, &rcClip) ) {
               nmc.dwItemSpec = m_iCurSel;
               nmc.uItemState = CDIS_SELECTED;
               nmc.rc = rc;
               pT->ProcessItem(lResStage, nmc);
               nmc.uItemState = 0;
            }
         }
         nmc.dwItemSpec = 0;
      }

      if( lResStage==CDRF_NOTIFYPOSTPAINT ) {
         nmc.dwDrawStage = CDDS_POSTPAINT;
         ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM)&nmc);
      }

      ::RestoreDC(hdc, save);
   }

   void ProcessItem(LRESULT lResStage, NMCUSTOMDRAW &nmc)
   {
      LRESULT lResItem = CDRF_DODEFAULT;
      if( lResStage==CDRF_NOTIFYITEMDRAW ) {
         nmc.dwDrawStage = CDDS_ITEMPREPAINT;
         lResItem = ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM)&nmc);
      }
      if( lResItem!=CDRF_SKIPDEFAULT ) {
         // Do default item-drawing
         T* pT = static_cast<T*>(this);
         pT->DoItemPaint(nmc);
      }
      if( lResStage==CDRF_NOTIFYITEMDRAW && lResItem==CDRF_NOTIFYPOSTPAINT ) {
         nmc.dwDrawStage = CDDS_ITEMPOSTPAINT;
         ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM)&nmc);
      }
   }

   void DoItemPaint(NMCUSTOMDRAW &/*nmc*/)
   {
   }

   // Operations

#define CHECK_ITEM(iItem) \
   ATLASSERT(iItem>=0 && iItem<m_Items.GetSize()); \
   if( iItem<0 || iItem>=m_Items.GetSize() ) return 0

   int InsertItem(int iItem, const LPTCITEM pItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(pItem);
      ATLASSERT(iItem<0 || iItem>=m_Items.GetSize()); // We only support appending right now
      if( iItem<0 || iItem>m_Items.GetSize() ) iItem = m_Items.GetSize();
      LPTCITEM pNewItem;
      RECT rc = { 0 };
      ATLTRY(pNewItem = new TCITEM);
      if( pNewItem==NULL ) return -1;
      ::ZeroMemory(pNewItem, sizeof(TCITEM));
      m_Items.Add(pNewItem);
      m_Sizes.Add(rc);
      int idx = m_Items.GetSize()-1;
      if( SetItem(idx, pItem)==FALSE ) return FALSE;
      // Send notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_INSERTITEM };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);
      // Select if first tab
      if( m_Items.GetSize()==1 ) SetCurSel(0);
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return idx;
   }

   BOOL DeleteItem(int iItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      if( iItem<0 || iItem>=m_Items.GetSize() ) return FALSE;
      // If currently selected, select something else
      if( iItem<=m_iCurSel ) SetCurSel( m_Items.GetSize()>1 ? 0 : -1 );
      // Remove from structures
      LPTCITEM pItem = m_Items[iItem];
      m_Items.RemoveAt(iItem);
      m_Sizes.RemoveAt(iItem);
      if( pItem->mask & TCIF_TEXT ) ATLTRY(delete [] pItem->pszText);
      ATLTRY(delete pItem);
      // Send notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_DELETEITEM };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);
      // Repaint
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return TRUE;
   }

   BOOL DeleteAllItems()
   {
      ATLASSERT(::IsWindow(m_hWnd));
      while( GetItemCount()>0 ) DeleteItem(0); // Slooow!!!
      return TRUE;
   }

   BOOL SetItem(int iItem, LPTCITEM pItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(!::IsBadReadPtr(pItem,sizeof(TCITEM)));
      CHECK_ITEM(iItem);

      // Copy caller's data to the internal structure
      LPTCITEM pItemT = m_Items[iItem];
      UINT mask = pItem->mask;
      if( mask & TCIF_TEXT ) {
         ATLASSERT(!::IsBadStringPtr(pItem->pszText, (UINT)-1));
         if( pItemT->mask & TCIF_TEXT ) ATLTRY(delete [] pItemT->pszText);
         ATLTRY(pItemT->pszText = new TCHAR[ ::lstrlen(pItem->pszText)+1 ]);
         ::lstrcpy(pItemT->pszText, pItem->pszText);
         pItemT->mask |= TCIF_TEXT;
      }
      if( mask & TCIF_PARAM ) {
         pItemT->lParam = pItem->lParam;
         pItemT->mask |= TCIF_PARAM;
      }

      // Repaint control
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return TRUE;
   }

   BOOL GetItem(int iItem, LPTCITEM pItem) const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(!::IsBadWritePtr(pItem,sizeof(TCITEM)));
      CHECK_ITEM(iItem);

      // Copy item data
      LPTCITEM pItemT = m_Items[iItem];
      UINT mask = pItem->mask;
      if( mask & TCIF_TEXT ) {
         ATLASSERT(!::IsBadWritePtr(pItem->pszText, pItem->cchTextMax));
         ::lstrcpyn( pItem->pszText, pItemT->pszText==NULL ? _T("") : pItemT->pszText, pItem->cchTextMax );
      }
      if( mask & TCIF_PARAM ) pItem->lParam = pItemT->lParam;     
      return 0;
   }

   int SetCurSel(int iItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      // Selecting same tab? Not worth it.
      if( iItem==m_iCurSel ) return m_iCurSel;
      if( iItem<-1 || iItem>=m_Items.GetSize() ) return m_iCurSel;

      RECT rc = { 0 };
      int iOldSel = m_iCurSel;
      // Send notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_SELCHANGING };
      if( ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh)==TRUE ) return -1;
      // Repaint old button area
      GetItemRect(iItem, &rc); InvalidateRect(&rc);
      GetItemRect(iOldSel, &rc); InvalidateRect(&rc);
      // Change tab
      m_iCurSel = iItem;
      // Recalc new positions
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      // Repaint new button area with new button positions
      GetItemRect(iItem, &rc); InvalidateRect(&rc);
      GetItemRect(iOldSel, &rc); InvalidateRect(&rc);
      // Send notification
      nmh.code = TCN_SELCHANGE;
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);
      return iOldSel;
   }

   int GetCurSel() const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      return m_iCurSel;
   }

   int GetItemCount() const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      return m_Items.GetSize();
   }

   int HitTest(LPTCHITTESTINFO pinfo) const
   {
      ATLASSERT(!::IsBadWritePtr(pinfo,sizeof(TCHITTESTINFO)));
      RECT rc;
      int cnt = m_Items.GetSize();
      for( int i=0; i<cnt; i++ ) {
         GetItemRect(i, &rc);
         if( ::PtInRect(&rc, pinfo->pt) ) {
            pinfo->flags = TCHT_ONITEM;
            return i;
         }
      }
      return -1;
   }

   int GetRowCount() const
   {
      return 1;
   }

   DWORD SetItemSize(int iItem, int cx, int cy)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      CHECK_ITEM(iItem);

      RECT rcOld = m_Sizes[iItem];
      RECT rcNew = { rcOld.left, rcOld.top, rcOld.left + cx, rcOld.top cy };
      m_Sizes[iItem] = rcNew;
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return MAKELONG(rcOld.right-rcOld.left, rcOld.bottom-rcOld.top);
   }

   BOOL GetItemRect(int iItem, RECT *prcItem) const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(prcItem);
      if( prcItem==NULL ) return FALSE;
      if( iItem<0 || iItem>=m_Sizes.GetSize() ) {
         ::SetRectEmpty(prcItem);
         return FALSE;
      }
      *prcItem = m_Sizes[iItem];
      return TRUE;
   }

   void SetPadding(int iPadding) 
   { 
      m_iPadding = iPadding; 
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
   };

   void GetSizeSettings(TC_SIZES *pSizes) const
   {
      ATLASSERT(!::IsBadWritePtr(pSizes,sizeof(TC_SIZES)));
      *pSizes = m_settings;
   }

   void SetSizeSettings(const TC_SIZES *pSizes)
   {
      ATLASSERT(!::IsBadReadPtr(pSizes,sizeof(TC_SIZES)));
      m_settings = *pSizes;
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
   }

};


/////////////////////////////////////////////////////////////////////////////
//
// The sample tab controls
//
// The follwing samples derive directly from CCustomTabCtrl.
// This means that they can actually use the internal members
// of this class. But they will not! To keep the code clean, I'm only
// using public member methods to access all variables.
//
// You need to add the...
//   REFLECT_NOTIFICATIONS()
// macro to the parent's message map.
//

class CButtonTabCtrl : 
   public CCustomTabCtrl<CButtonTabCtrl>,
   public CCustomDraw<CButtonTabCtrl>
{
public:
   DECLARE_WND_CLASS(_T("WTL_CoolButtonTabCtrl"))

   BEGIN_MSG_MAP(CButtonTabCtrl)
      CHAIN_MSG_MAP(CCustomTabCtrl<CButtonTabCtrl>)
      CHAIN_MSG_MAP_ALT(CCustomDraw<CButtonTabCtrl>, 1)
      REFLECTED_NOTIFY_CODE_HANDLER(TCN_INITIALIZE, OnInitialize)
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()

   LRESULT OnInitialize(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
   {
      TC_SIZES sizes;
      GetSizeSettings(&sizes);
      sizes.iPadding = 10;
      sizes.iMargin = 3;
      SetSizeSettings(&sizes);
      return 0;
   }

   DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
   {
      return CDRF_NOTIFYITEMDRAW;   // We need per-item notifications
   }

   DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      ::SetBkMode(lpNMCustomDraw->hdc, TRANSPARENT);
      ::SetTextColor(lpNMCustomDraw->hdc, ::GetSysColor(COLOR_BTNTEXT));
      
      UINT state = 0;
      if( lpNMCustomDraw->uItemState & CDIS_SELECTED ) state |= DFCS_PUSHED;
      if( lpNMCustomDraw->uItemState & CDIS_DISABLED ) state |= DFCS_INACTIVE;
      ::DrawFrameControl(lpNMCustomDraw->hdc, &lpNMCustomDraw->rc, DFC_BUTTON, DFCS_BUTTONPUSH | state );
      
      TCITEM item = { 0 };
      TCHAR szText[128];
      item.mask = TCIF_TEXT;
      item.pszText = szText;
      item.cchTextMax = sizeof(szText)/sizeof(TCHAR);
      GetItem(lpNMCustomDraw->dwItemSpec, &item);
      if( lpNMCustomDraw->uItemState & CDIS_SELECTED ) {
         lpNMCustomDraw->rc.left += 2;
         lpNMCustomDraw->rc.top += 2;
      }
      ::DrawText(lpNMCustomDraw->hdc, item.pszText, ::lstrlen(item.pszText), &lpNMCustomDraw->rc, DT_SINGLELINE|DT_CENTER|DT_VCENTER);

      return CDRF_SKIPDEFAULT;
   }
};

class CFolderTabCtrl : 
   public CCustomTabCtrl<CFolderTabCtrl>,
   public CCustomDraw<CFolderTabCtrl>
{
public:
   DECLARE_WND_CLASS(_T("WTL_CoolFolderTabCtrl"))

   CFont m_font;

   enum { CXOFFSET = 8 };     // defined pitch of trapezoid slant
   enum { CXMARGIN = 2 };     // left/right text margin
   enum { CYMARGIN = 1 };     // top/bottom text margin
   enum { CYBORDER = 1 };     // top border thickness
 
   BEGIN_MSG_MAP(CFolderTabCtrl)
      MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
      CHAIN_MSG_MAP(CCustomTabCtrl<CFolderTabCtrl>)
      CHAIN_MSG_MAP_ALT(CCustomDraw<CFolderTabCtrl>, 1)
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()

   DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
   {
      return CDRF_NOTIFYITEMDRAW;   // We need per-item notifications
   }

   DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      CDCHandle dc( lpNMCustomDraw->hdc );
      bool bSelected = lpNMCustomDraw->uItemState & CDIS_SELECTED;

      COLORREF bgColor = ::GetSysColor(bSelected ? COLOR_WINDOW     : COLOR_3DFACE);
      COLORREF fgColor = ::GetSysColor(bSelected ? COLOR_WINDOWTEXT : COLOR_BTNTEXT);

      CBrush brush;
      brush.CreateSolidBrush(bgColor);     // background brush
      dc.SetBkColor(bgColor);              // text background
      dc.SetTextColor(fgColor);            // text color = fg color

      CPen shadowPen;
      shadowPen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));

      // Fill trapezoid
      POINT pts[4];
      RECT rc = lpNMCustomDraw->rc;
      _GetTrapezoid(rc, pts);
      CPenHandle hOldPen = dc.SelectStockPen(BLACK_PEN);   
      CRgn rgn;
      rgn.CreatePolygonRgn(pts, 4, WINDING);
      dc.FillRgn(rgn, brush);

      // Draw edges. This requires two corrections:
      // 1) Trapezoid dimensions don't include the right and bottom edges,
      //    so must use one pixel less on bottom (cybottom)
      // 2) the endpoint of LineTo is not included when drawing the line, so
      //    must add one pixel (cytop)
      pts[1].y--;       // correction #1: true bottom edge y-coord
      pts[2].y--;       // ...ditto
      pts[3].y--;       // correction #2: extend final LineTo
      dc.MoveTo(pts[0]);              // upper left
      dc.LineTo(pts[1]);              // bottom left
      dc.SelectPen(shadowPen);        // bottom line is shadow color
      dc.MoveTo(pts[1]);              // line is inside trapezoid bottom
      dc.LineTo(pts[2]);              // ...
      dc.SelectStockPen(BLACK_PEN);   // upstroke is black
      dc.LineTo(pts[3]);              // y-1 to include endpoint
      if( !bSelected ) {
         // If not highlighted, upstroke has a 3D shadow, one pixel inside
         pts[2].x--;    // offset left one pixel
         pts[3].x--;    // ...ditto
         dc.SelectPen(shadowPen);
         dc.MoveTo(pts[2]);
         dc.LineTo(pts[3]);
      }
      dc.SelectPen(hOldPen);

      TCITEM item = { 0 };
      TCHAR szText[128];
      item.mask = TCIF_TEXT;
      item.pszText = szText;
      item.cchTextMax = sizeof(szText)/sizeof(TCHAR);
      GetItem(lpNMCustomDraw->dwItemSpec, &item);

      // Draw text
      ::InflateRect(&rc, -(CXOFFSET + CXMARGIN), -CYMARGIN);
      dc.DrawText(item.pszText, ::lstrlen(item.pszText), &rc, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

      return CDRF_SKIPDEFAULT;
   }

   void _GetTrapezoid(const RECT& rc, POINT* pts) const
   {
      pts[0].x = rc.left;
      pts[0].y = rc.top;
      pts[1].x = rc.left + CXOFFSET;
      pts[1].y = rc.bottom;
      pts[2].x = rc.right - CXOFFSET - 1;
      pts[2].y = rc.bottom;
      pts[3].x = rc.right - 1, rc.top;
      pts[3].y = rc.top;
   }

   LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      // Initialize font
      if( !m_font.IsNull() ) m_font.DeleteObject();
      LOGFONT lf = { 0 };      
      lf.lfHeight = ::GetSystemMetrics(SM_CYHSCROLL) - CYMARGIN;
      lf.lfWeight = FW_NORMAL;
      lf.lfCharSet = DEFAULT_CHARSET;
      ::lstrcpy(lf.lfFaceName, _T("Arial"));
      m_font.CreateFontIndirect(&lf);
 /*     
      NONCLIENTMETRICS ncm = { 0 };
      ncm.cbSize = sizeof(ncm);
      ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
      ncm.lfSmCaptionFont.lfWeight = FW_NORMAL;
      m_font.CreateFontIndirect(&ncm.lfSmCaptionFont);
 */
      SetFont(m_font);

      TC_SIZES sizes;
      GetSizeSettings(&sizes);
      sizes.iPadding = CXOFFSET + 3;
      sizes.iMargin = -CXOFFSET;
      SetSizeSettings(&sizes);

      Invalidate();
      return 0;
   }

};

class CDotNetTabCtrl : 
   public CCustomTabCtrl<CDotNetTabCtrl>,
   public CCustomDraw<CDotNetTabCtrl>
{
public:
   DECLARE_WND_CLASS(_T("WTL_CoolDotNetTabCtrl"))

   CFont m_font;
   CFont m_fontBold;
   CBrush m_hbrBack;

   BEGIN_MSG_MAP(CDotNetTabCtrl)
      MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
      CHAIN_MSG_MAP(CCustomTabCtrl<CDotNetTabCtrl>)
      CHAIN_MSG_MAP_ALT(CCustomDraw<CDotNetTabCtrl>, 1)
      REFLECTED_NOTIFY_CODE_HANDLER(TCN_INITIALIZE, OnInitialize)
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()

   LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      CDCHandle dc((HDC)wParam);
      RECT rc;
      GetClientRect(&rc);
      HBRUSH hOldBrush = dc.SelectBrush(m_hbrBack);
      dc.PatBlt(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, PATCOPY);
      dc.SelectBrush(hOldBrush);
      return 1;
   }

   LRESULT OnInitialize(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
   {
      TC_SIZES sizes;
      GetSizeSettings(&sizes);
      sizes.iIndent = 6;
      sizes.iPadding = 0;
      sizes.iMargin = 2;
      sizes.iSelMargin = 4;
      SetSizeSettings(&sizes);
      return 0;
   }

   DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      CDCHandle dc( lpNMCustomDraw->hdc );
      dc.SetTextColor(::GetSysColor(COLOR_BTNTEXT));
      ::SetBkMode(lpNMCustomDraw->hdc, TRANSPARENT);

      return CDRF_NOTIFYITEMDRAW;   // We need per-item notifications
   }

   DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      CDCHandle dc( lpNMCustomDraw->hdc );
      bool bSelected = lpNMCustomDraw->uItemState & CDIS_SELECTED;
      RECT &rc = lpNMCustomDraw->rc;

      dc.FillRect(&rc, m_hbrBack);
      if( bSelected ) {
         // Tab is selected, so paint tab folder
         RECT rcTab = rc;
         rcTab.top += 5;
         rcTab.right--;
         dc.FillRect(&rcTab, (HBRUSH)(COLOR_BTNFACE+1));
         dc.SelectStockPen(WHITE_PEN);
         dc.MoveTo(rcTab.left, rcTab.bottom);
         dc.LineTo(rcTab.left, rcTab.top);
         dc.LineTo(rcTab.right, rcTab.top);
         dc.SelectStockPen(BLACK_PEN);
         dc.LineTo(rcTab.right, rcTab.bottom);
      }

      TCITEM item = { 0 };
      TCHAR szText[128];
      item.mask = TCIF_TEXT;
      item.pszText = szText;
      item.cchTextMax = sizeof(szText)/sizeof(TCHAR);
      GetItem(lpNMCustomDraw->dwItemSpec, &item);

      TC_SIZES sizes;
      GetSizeSettings(&sizes);

      HFONT hOldFont = dc.SelectFont(bSelected ? m_fontBold : m_font);
      RECT rcText = rc;
      ::InflateRect(&rcText, -sizes.iPadding, 0);
      rcText.bottom -= 3;
      dc.DrawText(item.pszText, ::lstrlen(item.pszText), &rcText, DT_CENTER|DT_BOTTOM|DT_SINGLELINE);
      dc.SelectFont(hOldFont);

      return CDRF_SKIPDEFAULT;
   }

   LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      // Initialize font
      if( !m_font.IsNull() ) m_font.DeleteObject();
      NONCLIENTMETRICS ncm = { 0 };
      ncm.cbSize = sizeof(ncm);
      ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
      ncm.lfSmCaptionFont.lfWeight = FW_NORMAL;
      m_font.CreateFontIndirect(&ncm.lfSmCaptionFont);
      ncm.lfSmCaptionFont.lfWeight = FW_BOLD;
      m_fontBold.CreateFontIndirect(&ncm.lfSmCaptionFont);
      SetFont(m_fontBold); // Bold font scales tabs correctly

      // Background brush
      if( !m_hbrBack.IsNull() ) m_hbrBack.DeleteObject();
      CWindowDC dc(NULL);
      int nBitsPerPixel = dc.GetDeviceCaps(BITSPIXEL);
      if( nBitsPerPixel > 8 ) {
         COLORREF clrBtnHilite = ::GetSysColor(COLOR_BTNHILIGHT);
         COLORREF clrBtnFace = ::GetSysColor(COLOR_BTNFACE);
         COLORREF clrLight = 
            RGB( GetRValue(clrBtnFace) + ((GetRValue(clrBtnHilite) - GetRValue(clrBtnFace)) / 2),
                 GetGValue(clrBtnFace) + ((GetGValue(clrBtnHilite) - GetGValue(clrBtnFace)) / 2),
                 GetBValue(clrBtnFace) + ((GetBValue(clrBtnHilite) - GetBValue(clrBtnFace)) / 2),
         );
         m_hbrBack.CreateSolidBrush(clrLight);
      }
      else {
         m_hbrBack =  CDCHandle::GetHalftoneBrush();
      }
      Invalidate();
      return 0;
   }
};


#endif // !defined(AFX_COOLTABCTRL_H__20010513_D899_C89D_D02E_0080AD509054__INCLUDED_)

