////////////////////////////////////////////////////////////////
//
// class CMultiLineCaption
//
// Multi-line auto-wrap caption painter.
//
// To use:
// - call Install from your frame's OnCreate function.
// - Set a custom CaptionBackground if desired
// - Set custom TextAttributes if required
//
// If you are drawing custom caption buttons, you must handle WM_NCLBUTTONDOWN & co.
// yourself. CMultiLineCaption does not handle the mouse for custom caption buttons.
//
// Author: Dave Lorde (dlorde@cix.compulink.co.uk)
//
// Copyright 1999
//
////////////////////////////////////////////////////////////////
//
#include "StdAfx.h"
#include "MultiLineCaption.h"
#include "CaptionBackground.h"
#include "CaptionTextAttributes.h"
#include "SuppressStyle.h"
#include "AutoSelector.h"
#include <algorithm>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
using std::_cpp_max;
using std::_cpp_min;
IMPLEMENT_DYNAMIC(CMultiLineCaption, CCaption);
namespace
{
const int TextFormat = DT_CENTER|DT_WORDBREAK;
const int SingleLineTextFormat = DT_LEFT|DT_END_ELLIPSIS;
const int DrawTextFudgeFactor = 2;
const int TextLowerBorder = 2;
const UINT RestoreFromMinimized = 0x8124; // WindowPosChanged flags
const CString ellipsis("...");
}
///////////////
// Constructor
//
CMultiLineCaption::CMultiLineCaption(int maxLines)
:m_MaxLines(maxLines > 0 ? maxLines : 1),
m_InitialShow(true),
m_ActiveSysColor(GetSysColor(COLOR_ACTIVECAPTION)),
m_InactiveSysColor(GetSysColor(COLOR_INACTIVECAPTION))
{
}
//////////
// Adjust maximum lines for title wrapping, and refresh
//
void CMultiLineCaption::SetMaxLines(int maxLines)
{
m_MaxLines = maxLines > 0 ? maxLines : 1;
AdjustHeight();
Repaint(); // Redraw caption
}
////////////////
// Return maximum permitted lines for title
//
int CMultiLineCaption::GetMaxLines() const
{
return m_MaxLines;
}
//////////////////
// Message handler handles caption size-related messages
//
LRESULT CMultiLineCaption::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_NCACTIVATE:
return OnNcActivate(wp);
///////////
// Adjust height of client area according to text wrapping
//
case WM_SETTEXT:
OnSetText((LPCTSTR)lp);
return 0;
///////////
// Change colours if using system colours and SysColorChange
case WM_NCPAINT:
OnNcPaint(HRGN(wp));
return 0;
///////////
// Adjust height of client area according to required caption height
//
case WM_NCCALCSIZE:
return OnNcCalcSize((BOOL)wp, (LPNCCALCSIZE_PARAMS)lp);
////////////
// Ensure caption repaints smoothly when sized
//
case WM_SIZE:
OnSize( (UINT)wp, LOWORD(lp), HIWORD(lp) );
return 0;
///////////
// Prevent multi-line caption being overdrawn by client area
//
case WM_GETMINMAXINFO:
OnGetMinMaxInfo((LPMINMAXINFO)lp);
return 0;
///////////
// Refresh when activated or inactivated
//
case WM_MDIACTIVATE:
OnMDIActivate();
return 0;
///////////
// Refresh when sizing stopped
//
case WM_EXITSIZEMOVE:
OnExitSizeMove();
return 0;
///////////
// Refresh caption when window is tiled
//
case WM_WINDOWPOSCHANGED:
OnWindowPosChanged( (WINDOWPOS*) lp);
return 0;
}
////////////
// We don't handle it: pass to base class
//
return CCaption::WindowProc(msg, wp, lp);
}
//////////////////
// Handle WM_NCACTIVATE for main window
//
BOOL CMultiLineCaption::OnNcActivate(BOOL bActive)
{
BOOL result = CCaption::OnNcActivate(bActive);
Repaint(); // Redraw caption
return result;
}
void CMultiLineCaption::OnSetText(LPCTSTR lpStr)
{
CCaption::OnSetText(lpStr);
AdjustHeight();
Repaint(); // Redraw caption
}
void CMultiLineCaption::OnNcPaint(HRGN hRgn)
{
if (GetBackground()->IsUsingSystemColors())
{
if (m_ActiveSysColor != GetSysColor(COLOR_ACTIVECAPTION) ||
m_InactiveSysColor != GetSysColor(COLOR_INACTIVECAPTION))
{
m_ActiveSysColor = GetSysColor(COLOR_ACTIVECAPTION);
m_InactiveSysColor = GetSysColor(COLOR_INACTIVECAPTION);
Repaint();
}
}
CCaption::OnNcPaint(hRgn);
}
void CMultiLineCaption::OnSize( UINT nType, int cx, int cy )
{
Default();
Refresh();
}
void CMultiLineCaption::OnExitSizeMove()
{
Default();
Refresh();
}
/////////////
// Paint wrapped header text in rectangle
//
void CMultiLineCaption::PaintText(CDC* pDC)
{
ASSERT(m_pWndHooked);
ASSERT(m_pWndHooked);
CWnd& wnd = *m_pWndHooked;
// For minimised windows, forget all the multi-line stuff
if (wnd.IsIconic())
{
CCaption::PaintText(pDC);
return;
}
pDC->SetBkMode(TRANSPARENT); // draw on top of our background
// Get window text to paint
//
CString text;
wnd.GetWindowText(text);
// Set text color into device context
//
COLORREF textColor = GetTextColor(m_bActive);
pDC->SetTextColor(textColor);
// Get the rect to paint the text in
//
CRect paintRect(CalcTextRect(m_MaxLines > 1 ? TextFormat : SingleLineTextFormat));
// Get caption font and select into DC
//
AutoSelector a(pDC, GetFont(m_bActive));
// Paint text into rectangle
//
if (m_MaxLines > 1)
{
int height = pDC->DrawText(text, paintRect, TextFormat|DT_TOP);
// Unfortunately, DT_END_ELLIPSIS doesn't work with DT_WORDBREAK, so we must handle
// ellipsis manually... (unless you know better)
//
if (height > paintRect.Height()) // Text won't fit, so output truncation ellipsis
{
int lenEllipsis = pDC->GetTextExtent(ellipsis).cx;
paintRect.left = paintRect.right;
paintRect.right += lenEllipsis;
paintRect.top = paintRect.bottom - (GetLineHeight(pDC) + 2);
pDC->DrawText(ellipsis, paintRect, DT_SINGLELINE|DT_LEFT);
}
}
else // m_MaxLines == 1
{
pDC->DrawText(text, paintRect, SingleLineTextFormat);
}
}
//////////////
// Refresh caption when window is tiled
//
void CMultiLineCaption::OnWindowPosChanged( WINDOWPOS* lpwndpos )
{
Default(); // Let Windows handle it
// These flags together seem unique to windows being tiled
//
UINT tileFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS;
if ((lpwndpos->flags & tileFlags) == tileFlags)
{
Refresh();
}
}
void CMultiLineCaption::OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI )
{
Default(); // Let Windows handle it
// Ensure min height is correct - we mustn't allow a height less
// than the caption height
//
lpMMI->ptMinTrackSize.y =
max(lpMMI->ptMinTrackSize.y, GetCaptionHeight() + GetFrameSize().cy);
}
////////////////
// Refresh on activate/deactivate for windows with different font
// sizes for active and inactive caption
//
void CMultiLineCaption::OnMDIActivate()
{
AdjustHeight();
Repaint(); // Redraw caption
Default(); // Let Windows handle it
}
// Call Windows to do WM_NCCALCSIZE then set the top of the new client area to allow for
// multi-line caption
//
LRESULT CMultiLineCaption::OnNcCalcSize( BOOL bCalcValidRects, LPNCCALCSIZE_PARAMS lpncsp )
{
LRESULT res = Default(); // Let Windows handle it
CString text;
ASSERT(m_pWndHooked);
CWnd& wnd = *m_pWndHooked;
wnd.GetWindowText(text);
// When restoring from minimised (old client area left and right are both zero), CalcTextRect()
// won't calculate the new client area correctly because the current client area is not valid.
// In this case, skip it and let OnSize() trigger this function when the client area is valid.
//
BOOL wasIconic = lpncsp->rgrc[2].left == lpncsp->rgrc[2].right;
BOOL isZoomed = wnd.IsZoomed();
if (text.GetLength() > 0 && !isZoomed && !wasIconic)
{
// LPNCCALCSIZE_PARAMS are relative to parent client area (or screen origin if no parent),
// so add vertical offset of child frame into parent client area
//
lpncsp->rgrc[0].top = GetChildOffset() + GetCaptionHeight();
}
return res;
}
///////////////
// Return the rectangle occupied by caption text with the specified DrawText() format
//
CRect CMultiLineCaption::CalcTextRect(int textFormat)
{
CString text;
ASSERT(m_pWndHooked);
m_pWndHooked->GetWindowText(text);
// Get caption font and select into window DC
//
CWindowDC dc(m_pWndHooked);
AutoSelector a(&dc, GetFont(m_bActive));
// Size caption to exclude buttons & icon
//
CRect textRect = GetCaptionRect();
textRect.left += GetIconWidth(); // start text after icon
textRect.right -= GetButtonsWidth() + 4; // end text before buttons
// Use DrawText to calculate the height necessary to contain the text
//
int width = textRect.Width();
int height = dc.DrawText(text, textRect, textFormat | DT_CALCRECT | DT_EXTERNALLEADING);
// Restrict to specified maximun height
//
int maxHeight = GetLineHeight(&dc) * m_MaxLines;
if (height > maxHeight)
textRect.bottom = textRect.top + maxHeight;
// Restrict to minimum height
//
int minTextHeight = GetSystemMetrics(SM_CYCAPTION) - 6;
if (textRect.Height() < minTextHeight)
textRect.bottom = textRect.top + minTextHeight;
// I'll work out why I need this when I get more time... :-)
textRect.top -= DrawTextFudgeFactor;
// DrawText word-wrap fails to clip for very narrow rects, so ignore if necessary
//
if (textRect.Width() > width)
textRect.right = textRect.left + width;
return textRect;
}
///////////
// Returns the height of a line of text in the specified device context
//
LONG CMultiLineCaption::GetLineHeight(CDC* pDC)
{
TEXTMETRIC textMetric;
pDC->GetOutputTextMetrics(&textMetric);
return textMetric.tmHeight;
}
///////////
// Returns the total caption height needed to contain the text
//
int CMultiLineCaption::GetCaptionHeight()
{
return GetFrameSize().cy +
CalcTextRect(m_MaxLines > 1 ? TextFormat : SingleLineTextFormat).bottom +
TextLowerBorder;
}
///////////
// Return vertical offset of child frame into parent client area (for OnNcCalcSize).
//
int CMultiLineCaption::GetChildOffset()
{
CRect parentRect(0,0,0,0);
ASSERT(m_pWndHooked);
CWnd& wnd = *m_pWndHooked;
CWnd* pParent = wnd.GetParent();
if (pParent)
{
pParent->GetClientRect(parentRect );
pParent->ClientToScreen(parentRect);
}
CRect hookedRect;
wnd.GetWindowRect( &hookedRect );
return hookedRect.top - parentRect.top;
}
/////////////
// Override of base class function:
// Return caption drawing rectangle, the lower edge abutting the
// client area (as adjusted in OnNcCalcSize())
//
CRect CMultiLineCaption::GetCaptionRect()
{
ASSERT(m_pWndHooked);
CWnd& wnd = *m_pWndHooked;
// When minimised, draw normal size caption
if (wnd.IsIconic())
return CCaption::GetCaptionRect();
// Get client rect in screen co-ordinates
//
CRect captionRect;
wnd.GetClientRect(captionRect);
wnd.ClientToScreen(captionRect);
// Convert client rect to window co-ordinates
//
CRect windowRect;
wnd.GetWindowRect(windowRect);
captionRect -= windowRect.TopLeft();
// Set top and botton for caption
//
captionRect.bottom = captionRect.top; // bottom of caption is top of client window
captionRect.top = GetFrameSize().cy; // top of caption is window rect top + frame thickness
return captionRect;
}
///////////
// Ensure caption size is recalculated by triggering an OnNcCalcSize()
//
void CMultiLineCaption::Repaint()
{
ASSERT(m_pWndHooked);
CWnd& wnd = *m_pWndHooked;
// Trigger an OnNcCalcSize() to resize caption
//
Invalidate();
wnd.SetWindowPos(&CWnd::wndTopMost, 0,0,0,0,
SWP_FRAMECHANGED|SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOSIZE|SWP_NOMOVE);
}
///////////
// Redraw caption and client area, sizing window as required
void CMultiLineCaption::Refresh()
{
AdjustHeight();
Repaint(); // Redraw caption
ASSERT(m_pWndHooked);
CWnd& wnd = *m_pWndHooked;
wnd.Invalidate(); // paint client area
UpdateWindow(wnd.GetSafeHwnd());
}
void CMultiLineCaption::AdjustHeight()
{
// Size window to caption if caption is taller
//
ASSERT(m_pWndHooked);
CWnd& wnd = *m_pWndHooked;
CRect wndRect;
wnd.GetWindowRect(wndRect);
if (wndRect.Height() < GetCaptionHeight() + GetFrameSize().cy)
{
wnd.SetWindowPos(&CWnd::wndTopMost, 0,0,wndRect.Width(),
GetCaptionHeight() + GetFrameSize().cy,
SWP_FRAMECHANGED|SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOMOVE);
}
}