// XMonoFontListCombo.cpp // // Author: Hans Dietrich // hdietrich@gmail.com // // Description: // XMonoFontListCombo.cpp implements CXMonoFontListCombo, a class used by // CXMonoFontDialog to display font names. // // License: // This software is released into the public domain. You are free to use // it in any way you like, except that you may not sell this source code. // // This software is provided "as is" with no expressed or implied warranty. // I accept no liability for any damage or loss of business that this // software may cause. // /////////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "XMonoFontListCombo.h" #include "XMonoFontDialog.h" #include "XFontSize.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #ifndef __noop #if _MSC_VER < 1300 #define __noop ((void)0) #endif #endif #undef TRACE #define TRACE __noop //============================================================================= // if you want to see the TRACE output, uncomment this line: //#include "XTrace.h" //============================================================================= #pragma warning(disable : 4996) // disable bogus deprecation warning #define GLYPH_SIZE 12 #define TIMER_INIT 1 static int CALLBACK EnumFontFamExProc(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *lpntme, DWORD FontType, LPARAM lParam); //============================================================================= BEGIN_MESSAGE_MAP(CXMonoFontListCombo, CComboBox) //============================================================================= //{{AFX_MSG_MAP(CXMonoFontListCombo) ON_WM_TIMER() ON_WM_CTLCOLOR() //}}AFX_MSG_MAP END_MESSAGE_MAP() //============================================================================= CXMonoFontListCombo::CXMonoFontListCombo() //============================================================================= { m_bShowMonospacedAsBold = TRUE; m_pDC = 0; m_dwFontFilter = 0; memset(&m_lfInitial, 0, sizeof(LOGFONT)); m_lfInitial.lfCharSet = DEFAULT_CHARSET; _tcscpy(m_lfInitial.lfFaceName, _T("Courier")); m_lfInitial.lfHeight = FontSize.GetFontHeight(8); memcpy(&m_lfCurrent, &m_lfInitial, sizeof(LOGFONT)); } //============================================================================= CXMonoFontListCombo::~CXMonoFontListCombo() //============================================================================= { if (m_BoldFont.GetSafeHandle()) m_BoldFont.DeleteObject(); m_FontType.DeleteImageList(); } //============================================================================= void CXMonoFontListCombo::PreSubclassWindow() //============================================================================= { TRACE(_T("in CXMonoFontListCombo::PreSubclassWindow\n")); TRACE(_T("m_dwFontFilter=%X\n"), m_dwFontFilter); CreateFontTypeImages(); CFont *pFont = GetFont(); if (!pFont) pFont = GetParent()->GetFont(); if (pFont) { LOGFONT lf; pFont->GetLogFont(&lf); lf.lfWeight = FW_BOLD; m_BoldFont.CreateFontIndirect(&lf); } // enumerate fonts, fill combo LOGFONT lf; memset(&lf, 0, sizeof(LOGFONT)); lf.lfCharSet = DEFAULT_CHARSET; m_pDC = GetDC(); // use one dc for enumeration if (m_pDC) { int nSavedDC = m_pDC->SaveDC(); EnumFontFamiliesEx(m_pDC->m_hDC, &lf, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)this, 0); TEXTMETRIC tm; m_pDC->GetTextMetrics(&tm); m_pDC->RestoreDC(nSavedDC); ReleaseDC(m_pDC); // set height of edit box and combo list entries int h = tm.tmHeight - tm.tmInternalLeading; SetItemHeight(0, h); SetItemHeight(-1, h); } m_pDC = 0; // select initial font int index = FindStringExact(-1, m_lfInitial.lfFaceName); if (index == CB_ERR) { if (GetCount() > 0) index = 0; } m_CurIndex = index; if (index >= 0) { SetCurSel(index); SetTimer(TIMER_INIT, 50, 0); } TRACE(_T("exiting CXMonoFontListCombo::PreSubclassWindow\n")); CComboBox::PreSubclassWindow(); } //============================================================================= void CXMonoFontListCombo::CreateFontTypeImages() //============================================================================= { ASSERT(m_FontType.GetSafeHandle() == 0); // We load the font type images from resource 38 in COMDLG32.DLL. // This bitmap is 100 x 24, and has 2 rows of 5 images HMODULE hModule = ::LoadLibraryEx(_T("COMDLG32.DLL"), NULL, DONT_RESOLVE_DLL_REFERENCES); ASSERT(hModule); if (!hModule) return; HBITMAP hBmp = (HBITMAP)::LoadImage(hModule, MAKEINTRESOURCE(38), IMAGE_BITMAP, 100, 24, LR_DEFAULTCOLOR); ::FreeLibrary(hModule); ASSERT(hBmp); if (!hBmp) return; CClientDC dcClient(this); // We create one image list for the selected and non-selected // glyphs. The images are: // 0 - TrueType non-selected // 1 - OpenType non-selected // 2 - TrueType selected // 3 - OpenType selected if (m_FontType.Create(GLYPH_SIZE, GLYPH_SIZE, ILC_COLOR32 | ILC_MASK, 4, 1)) { CDC dcMem; dcMem.CreateCompatibleDC(&dcClient); CBitmap *pBmp = CBitmap::FromHandle(hBmp); CBitmap *pOldBitmap = dcMem.SelectObject(pBmp); // dcMem now contains the 100 x 24 bitmap (2 rows) from COMDLG32.DLL. // The individual images are at 0, 20, 40, 60, and 80. // We want the images at 0 and 40 in each row. CDC dcBitmap1; dcBitmap1.CreateCompatibleDC(&dcClient); CBitmap bmp1; bmp1.CreateCompatibleBitmap(&dcClient, 2*GLYPH_SIZE, GLYPH_SIZE); // extract non-selected images from top row CBitmap *pOldBitmap1 = dcBitmap1.SelectObject(&bmp1); dcBitmap1.FillSolidRect(0, 0, 2*GLYPH_SIZE, GLYPH_SIZE, GetSysColor(COLOR_WINDOW)); // we want 1st (TrueType) and 3rd (OpenType) images dcBitmap1.BitBlt(0, 0, GLYPH_SIZE, GLYPH_SIZE, &dcMem, 3, 0, SRCCOPY); dcBitmap1.BitBlt(GLYPH_SIZE, 0, GLYPH_SIZE, GLYPH_SIZE, &dcMem, 43, 0, SRCCOPY); dcBitmap1.SelectObject(pOldBitmap1); m_FontType.Add(&bmp1, RGB(0,0,255)); // different mask color for each row // extract selected images from 2nd row dcBitmap1.SelectObject(&bmp1); dcBitmap1.FillSolidRect(0, 0, 2*GLYPH_SIZE, GLYPH_SIZE, GetSysColor(COLOR_WINDOW)); // we want 1st (TrueType) and 3rd (OpenType) images dcBitmap1.BitBlt(0, 0, GLYPH_SIZE, GLYPH_SIZE, &dcMem, 3, GLYPH_SIZE, SRCCOPY); dcBitmap1.BitBlt(GLYPH_SIZE, 0, GLYPH_SIZE, GLYPH_SIZE, &dcMem, 43, GLYPH_SIZE, SRCCOPY); dcBitmap1.SelectObject(pOldBitmap1); m_FontType.Add(&bmp1, RGB(255,0,255)); // different mask color for each row dcBitmap1.DeleteDC(); bmp1.DeleteObject(); dcMem.SelectObject(pOldBitmap); dcMem.DeleteDC(); } ::DeleteObject(hBmp); } //============================================================================= CXMonoFontListCombo& CXMonoFontListCombo::SetFont(LOGFONT& lf) //============================================================================= { memcpy(&m_lfInitial, &lf, sizeof(LOGFONT)); memcpy(&m_lfCurrent, &lf, sizeof(LOGFONT)); return *this; } //============================================================================= void CXMonoFontListCombo::DrawItem(LPDRAWITEMSTRUCT lpDIS) //============================================================================= { if (lpDIS->itemID == -1) return; ASSERT(lpDIS->CtlType == ODT_COMBOBOX); CRect rect = lpDIS->rcItem; int nSavedDC = ::SaveDC(lpDIS->hDC); CDC dc; dc.Attach(lpDIS->hDC); if (lpDIS->itemState & ODS_FOCUS) dc.DrawFocusRect(&rect); COLORREF crText = ::GetSysColor(COLOR_WINDOWTEXT); COLORREF crBackground = ::GetSysColor(COLOR_WINDOW); if (lpDIS->itemState & ODS_SELECTED) { crText = ::GetSysColor(COLOR_HIGHLIGHTTEXT); crBackground = ::GetSysColor(COLOR_HIGHLIGHT); } dc.SetBkMode(TRANSPARENT); dc.SetTextColor(crText); dc.FillSolidRect(&rect, crBackground); CString strFontName = _T(""); GetLBText(lpDIS->itemID, strFontName); // draw the font type glyph DWORD dwData = GetItemData(lpDIS->itemID); //TRACE(_T("dwData=0x%X for %s\n"), dwData, strFontName); CPoint point(rect.left+3, rect.top); int nImage = -1; if (dwData & XFONT_TRUETYPE) nImage = 0; else if (dwData & XFONT_OPENTYPE) nImage = 1; if (nImage != -1) { if (lpDIS->itemState & ODS_SELECTED) nImage += 2; if (m_FontType.GetSafeHandle()) m_FontType.Draw(&dc, nImage, point, ILD_TRANSPARENT); } if ((dwData & XFONT_MONOSPACED) && m_bShowMonospacedAsBold) { if (m_BoldFont.GetSafeHandle()) dc.SelectObject(&m_BoldFont); } rect.left += GLYPH_SIZE + 8; // draw the text dc.TextOut(rect.left, rect.top, strFontName); dc.Detach(); ::RestoreDC(lpDIS->hDC, nSavedDC); } //============================================================================= // This function is the enumeration callback for font names. // Its purpose is to fill the font combo with non-duplicate names, // and screen out unwanted fonts based on font filter. int CALLBACK EnumFontFamExProc(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *lpntme, DWORD FontType, LPARAM lParam) //============================================================================= { CXMonoFontListCombo *pCombo = (CXMonoFontListCombo *) lParam; ASSERT(lpelfe); ASSERT(lpntme); ASSERT(pCombo); if (!lpelfe || !lpntme || !pCombo) return FALSE; // something seriously wrong if (pCombo->FindStringExact(-1, lpelfe->elfLogFont.lfFaceName) != CB_ERR) { // already in combo return TRUE; // continue enumeration } // save font attributes - this will be stored in combobox item data DWORD dwData = 0; UINT fonttype = lpntme->ntmTm.tmPitchAndFamily & 0x6; if (fonttype == 0) dwData |= XFONT_RASTER; if (fonttype == 2) dwData |= XFONT_VECTOR; if (lpntme->ntmTm.tmCharSet == OEM_CHARSET) dwData |= XFONT_OEM; if (lpntme->ntmTm.tmCharSet == SYMBOL_CHARSET) dwData |= XFONT_SYMBOL; if (lpntme->ntmTm.tmWeight > 600) dwData |= XFONT_BOLD; if (lpntme->ntmTm.tmItalic) dwData |= XFONT_ITALIC; DWORD weight = lpntme->ntmTm.tmWeight; weight = weight << 16; dwData |= weight; UINT family = lpntme->ntmTm.tmPitchAndFamily & 0xF0; dwData |= family; // check if monospaced if (pCombo->m_pDC) { BOOL bMonoSpaced = FALSE; LOGFONT lf; memset(&lf, 0, sizeof(LOGFONT)); _tcsncpy(lf.lfFaceName, lpelfe->elfLogFont.lfFaceName, sizeof(lf.lfFaceName)/sizeof(TCHAR)-1); lf.lfFaceName[sizeof(lf.lfFaceName)/sizeof(TCHAR)-1] = 0; lf.lfWeight = FW_NORMAL; lf.lfCharSet = DEFAULT_CHARSET; HFONT hFont = ::CreateFontIndirect(&lf); ASSERT(hFont); if (hFont) { // font is monospaced if // (1) width of W == width of ! // (2) it is not a symbol font HFONT hFontOld = (HFONT)::SelectObject(pCombo->m_pDC->m_hDC, hFont); TEXTMETRIC tm; ::GetTextMetrics(pCombo->m_pDC->m_hDC, &tm); SIZE sizeChar1; ::GetTextExtentPoint32(pCombo->m_pDC->m_hDC, _T("W"), 1, &sizeChar1); SIZE sizeChar2; ::GetTextExtentPoint32(pCombo->m_pDC->m_hDC, _T("!"), 1, &sizeChar2); bMonoSpaced = sizeChar1.cx == sizeChar2.cx; if (tm.tmCharSet == SYMBOL_CHARSET) bMonoSpaced = FALSE; if (hFontOld) ::SelectObject(pCombo->m_pDC->m_hDC, hFontOld); ::DeleteObject(hFont); } if (bMonoSpaced) dwData |= XFONT_MONOSPACED; } BOOL bOkToAdd = TRUE; TRACE(_T("m_dwFontFilter=%X\n"), pCombo->m_dwFontFilter); if (bOkToAdd && ((pCombo->m_dwFontFilter & XFONT_SHOW_SYMBOL) == 0)) { // don't show symbol fonts if (dwData & XFONT_SYMBOL) bOkToAdd = FALSE; } if (bOkToAdd && ((pCombo->m_dwFontFilter & XFONT_SHOW_ITALIC) == 0)) { // don't show italic fonts if (dwData & XFONT_ITALIC) bOkToAdd = FALSE; } if (bOkToAdd && ((pCombo->m_dwFontFilter & XFONT_SHOW_BOLD) == 0)) { // don't show bold fonts if (dwData & XFONT_BOLD) bOkToAdd = FALSE; } if (bOkToAdd && ((pCombo->m_dwFontFilter & XFONT_SHOW_MONOSPACED) == 0)) { // don't show monospaced fonts if (dwData & XFONT_MONOSPACED) bOkToAdd = FALSE; } DWORD dwSpecial = XFONT_BOLD|XFONT_ITALIC|XFONT_SYMBOL|XFONT_MONOSPACED; if (bOkToAdd && ((pCombo->m_dwFontFilter & XFONT_SHOW_NORMAL) == 0)) { // don't show normal fonts if ((dwData & dwSpecial) == 0) bOkToAdd = FALSE; } if (bOkToAdd) { // According to docs, "The function uses the NEWTEXTMETRICEX structure // for TrueType fonts; and the TEXTMETRIC structure for other fonts." // In practice, it seems that a NEWTEXTMETRICEX struct is always returned, // but just to be safe we prepare for an exception. __try { if ((lpntme->ntmTm.ntmFlags & NTM_TT_OPENTYPE) || (lpntme->ntmTm.ntmFlags & NTM_PS_OPENTYPE)) { dwData |= XFONT_OPENTYPE; } else if (FontType & TRUETYPE_FONTTYPE) { dwData |= XFONT_TRUETYPE; } } __except(EXCEPTION_EXECUTE_HANDLER) { TRACE(_T("ERROR: exception in EnumFontFamExProc()\n")); } int index = pCombo->AddString(lpelfe->elfLogFont.lfFaceName); ASSERT(index >= 0); if (index >= 0) { VERIFY(pCombo->SetItemData(index, dwData) != CB_ERR); } TRACE(_T("_____ added %s [%s] FontType=0x%X ntmFlags=0x%X tmCharSet=%d tmPitchAndFamily=0x%X dwData=0x%X \n"), lpelfe->elfLogFont.lfFaceName, lpelfe->elfFullName, FontType, lpntme->ntmTm.ntmFlags, lpntme->ntmTm.tmCharSet, lpntme->ntmTm.tmPitchAndFamily, dwData); } return TRUE; } //============================================================================= void CXMonoFontListCombo::OnTimer(UINT nIDEvent) //============================================================================= { KillTimer(nIDEvent); if (nIDEvent == TIMER_INIT) { // send initial selection message GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CBN_SELCHANGE), (LPARAM)m_hWnd); if (m_CurIndex >= 0) { DWORD dwData = GetItemData(m_CurIndex); // set bold typeface in edit box SetBold(m_bShowMonospacedAsBold && (dwData & XFONT_MONOSPACED)); } } CComboBox::OnTimer(nIDEvent); } //============================================================================= HBRUSH CXMonoFontListCombo::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) //============================================================================= { if (nCtlColor == CTLCOLOR_EDIT) { if (m_edit.GetSafeHwnd() == NULL) { // subclass edit box m_edit.SubclassWindow(pWnd->GetSafeHwnd()); m_edit.SetReadOnly(TRUE); } } return CComboBox::OnCtlColor(pDC, pWnd, nCtlColor); } //============================================================================= LRESULT CXMonoFontListCombo::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) //============================================================================= { if (message == WM_CHAR) { // try to match first character of font name TCHAR s[2] = { 0 }; s[0] = (TCHAR) wParam; int index = FindString(-1, s); if (index >= 0) { SetCurSel(index); // tell parent that selection has changed GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), CBN_SELCHANGE), (LPARAM)m_hWnd); } return TRUE; } return CComboBox::WindowProc(message, wParam, lParam); }