/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ #include "stdafx.h" #include "MyRichEditCtrl.h" #include "MyRichEditCtrlResource.h" #include "Generic.h" #include "MyApp.h" #include "MyLog.h" #include "MyMenu.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // These defines support the various kinds of numbering availiable, and are // undocumented. #define PFN_NUMBER0 0 // No numbering. #define PFN_NUMBER1 2 // 1, 2, 3, ... #define PFN_NUMBER2 3 // a, b, c, ... #define PFN_NUMBER3 4 // A, B, C, ... #define PFN_NUMBER4 5 // i, ii, iii, ... #define PFN_NUMBER5 6 // I, II, III, ... ///////////////////////////////////////////////////////////////////////////// // MyRichEditCtrl // Construction. MyRichEditCtrl::MyRichEditCtrl() { } // Destruction. /* virtual */ MyRichEditCtrl::~MyRichEditCtrl() { } BEGIN_MESSAGE_MAP(MyRichEditCtrl, CRichEditCtrl) //{{AFX_MSG_MAP(MyRichEditCtrl) ON_WM_CONTEXTMENU() ON_WM_INITMENUPOPUP() ON_COMMAND(ID_RICHEDIT_COPY, OnEditCopy) ON_COMMAND(ID_RICHEDIT_CUT, OnEditCut) ON_COMMAND(ID_RICHEDIT_PASTE, OnEditPaste) ON_COMMAND(ID_RICHEDIT_SELECTALL, OnEditSelectAll) ON_WM_RBUTTONUP() //}}AFX_MSG_MAP // These come from the context menu, not the format bar. ON_COMMAND(ID_CHAR_BOLD, SetSelectionBold) ON_COMMAND(ID_CHAR_ITALIC, SetSelectionItalic) ON_COMMAND(ID_CHAR_UNDERLINE, SetSelectionUnderline) ON_COMMAND(ID_CHAR_COLOR, SelectColor) ON_COMMAND(ID_PARA_LEFT, SetParagraphLeft) ON_COMMAND(ID_PARA_CENTER, SetParagraphCenter) ON_COMMAND(ID_PARA_RIGHT, SetParagraphRight) ON_COMMAND(ID_PARA_NUMBER, CycleParagraphNumbered) ON_COMMAND(ID_PARA_BULLET, SetParagraphBulleted) ON_COMMAND(ID_PARA_OUTDENT, OutdentParagraph) ON_COMMAND(ID_PARA_INDENT, IndentParagraph) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MyRichEditCtrl message handlers // Set the default character format. void MyRichEditCtrl::SetDefaultFormat(const CString& sFontName, int nFontSizePoints) { VALIDATE; _ASSERTE(! sFontName.IsEmpty()); _ASSERTE(0 < nFontSizePoints); CHARFORMAT cf = { sizeof(cf) }; cf.dwMask = CFM_FACE | CFM_SIZE; lstrcpyn(cf.szFaceName, sFontName, LF_FACESIZE - 1); cf.yHeight = nFontSizePoints * TWIPS_PER_POINT; VERIFY(SetDefaultCharFormat(cf)); } // Append the sent text to the end, and apply the sent format to it. bool MyRichEditCtrl::AppendFormattedText(const CString& s, CHARFORMAT& cf) { VALIDATE; _ASSERTE(0 < cf.yHeight); bool bReturn(false); SetRedraw(false); HideSelection(true, /* bPermanent */ false); // Append the sent text to the output window. int nOldLen(GetTextLength()); SetSel(-1, -1); ReplaceSel(s); int nNewLen(GetTextLength()); // Select and format the just-added text. SetSel(nOldLen, nNewLen - 1); bReturn = SetSelectionCharFormat(cf); SetSel(-1, -1); // Done. HideSelection(false, /* bPermanent */ false); SetRedraw(true); ScrollToBottom(); return bReturn; } // Append to the control from whatever's on the clipboard. bool MyRichEditCtrl::AppendFromClipboard(CString& sClipboard, int nTwips) { VALIDATE; _ASSERTE(0 < nTwips); HRESULT hr(S_OK); bool bReturn(false); // Get the text on the clipboard. if (IsClipboardFormatAvailable(CF_TEXT)) { EC_B(OpenClipboard()); if (SUCCEEDED(hr)) { HANDLE hMem(NULL); EC_B(hMem = GetClipboardData(CF_TEXT)); if (SUCCEEDED(hr)) { char* psz = reinterpret_cast (malloc(GlobalSize(hMem))); _ASSERTE(psz); char* pszGlobal = reinterpret_cast (GlobalLock(hMem)); _ASSERTE(pszGlobal); lstrcpy(psz, pszGlobal); // Append it to the control. CHARFORMAT cf = { sizeof(cf) }; cf.yHeight = nTwips; sClipboard = psz; EC_B(AppendFormattedText(sClipboard + "\r\n", cf)); if (SUCCEEDED(hr)) { bReturn = true; } // Unlock the memory. EC_B(GlobalUnlock(hMem)); free(psz); psz = NULL; } EC_B_(CloseClipboard()); } } else { // Not an error. bReturn = true; } return bReturn; } // Scroll the contents to the bottom. Various versions of Windows have // different bugs; this works with them all. void MyRichEditCtrl::ScrollToBottom() { VALIDATE; SetRedraw(false); SCROLLINFO si = { sizeof(si) }; GetScrollInfo(SB_VERT, &si, SIF_ALL); int nTotalLines(GetLineCount()); int nUpLine(0); if (nTotalLines > 0 && si.nMax > 0 && si.nMax / nTotalLines > 0) { nUpLine = (si.nMax - si.nPos - (si.nPage - 1)) / (si.nMax / nTotalLines); } if (nUpLine > 0) { LineScroll(nUpLine); } SetRedraw(true); Invalidate(); } // This works for NT. void MyRichEditCtrl::OnRButtonUp(UINT nFlags, CPoint point) { VALIDATE; DOMYLOGD ("MyRichEditCtrl::OnRButtonUp().\n"); CWnd* pWnd = this; ClientToScreen(&point); OnContextMenu(pWnd, point); } // WM_CONTEXTMENU. This works for Windows 2000 (and ME, I think), but not NT. void MyRichEditCtrl::OnContextMenu(CWnd* pWnd, CPoint point) { VALIDATE; DOMYLOGD ("MyRichEditCtrl::OnContextMenu().\n"); HRESULT hr(S_OK); // Create the context menu. MyMenu menuContext; VERIFY(menuContext.LoadMenu(IDR_MYRICHEDITCTRL_MENU)); VERIFY(menuContext.LoadToolbar(IDR_MYRICHEDITCTRL_MENU)); VERIFY(menuContext.LoadToolbar(IDR_MYRICHEDITCTRL_FORMATBAR)); // Get the submenu. CMenu* pSubMenu = NULL; EC_B(pSubMenu = menuContext.GetSubMenu(0)); ASSERT_NULL_OR_VALID(pSubMenu, CMenu); // Display menu and track selection of items. EC_B(pSubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this)); } // WM_INITMENUPOPUP. void MyRichEditCtrl::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { VALIDATE; DOMYLOGD ("MyRichEditCtrl::OnInitMenuPopup().\n"); CRichEditCtrl::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu); long nStart(0L); long nEnd(0L); GetSel(nStart, nEnd); bool bSelected(nStart != nEnd); DWORD dwStyle(GetStyle()); bool bReadOnly(dwStyle & ES_READONLY); pPopupMenu->EnableMenuItem(ID_RICHEDIT_CUT, ! bReadOnly && bSelected ? MF_ENABLED : MF_GRAYED); pPopupMenu->EnableMenuItem(ID_RICHEDIT_COPY, bSelected ? MF_ENABLED : MF_GRAYED); pPopupMenu->EnableMenuItem(ID_RICHEDIT_PASTE, ! bReadOnly && ::IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED); pPopupMenu->EnableMenuItem(ID_RICHEDIT_UNDO, ! bReadOnly && CanUndo() ? MF_ENABLED : MF_GRAYED); pPopupMenu->EnableMenuItem(ID_RICHEDIT_SELECTALL, true ? MF_ENABLED : MF_GRAYED); } // User chose context menu item. void MyRichEditCtrl::OnEditCopy() { VALIDATE; Copy(); } // User chose context menu item. void MyRichEditCtrl::OnEditCut() { VALIDATE; Cut(); } // User chose context menu item. void MyRichEditCtrl::OnEditPaste() { VALIDATE; Paste(); } // User chose context menu item. void MyRichEditCtrl::OnEditSelectAll() { VALIDATE; SetSel(0, -1); } // Print and print preview. void MyRichEditCtrl::Print(bool bReallyPrint, CDC* pDC, CPrintInfo* pInfo, CRect* prcClip, LONG& lnNextCharToPrint) { VALIDATE; ASSERT_VALID(pDC); _ASSERTE(pInfo); _ASSERTE(prcClip); HRESULT hr(S_OK); if (pInfo->m_bPreview) { PrintPreview(pDC->m_hAttribDC, pDC->m_hDC, lnNextCharToPrint); } else if (bReallyPrint) { // We have to modify the style for at least Win2k so that the // background isn't gray. EC_V(ModifyStyleEx(0, WS_EX_TRANSPARENT)); // Rendering to the same DC we are measuring. Note that in print mode, // both m_hDC and m_hAttribDC point to the printer; but in print // preview mode, m_hDC points to the screen and m_AttribDC points to // the printer. FORMATRANGE fr = { 0 }; if (pInfo->m_bPreview) { _ASSERTE(pDC->m_hDC != pDC->m_hAttribDC); fr.hdc = pDC->m_hDC; // Device to render to: Screen fr.hdcTarget = pDC->m_hAttribDC; // Device to format for: Printer } else { _ASSERTE(pDC->m_hDC == pDC->m_hAttribDC); fr.hdc = pDC->m_hDC; // Device to render to: Printer fr.hdcTarget = pDC->m_hAttribDC; // Device to format for: Printer } // Set up the entire page (in twips) to be the full screen. int nPixelsPerLogicalInchX(GetDeviceCaps(fr.hdc, LOGPIXELSX)); int nPixelsPerLogicalInchY(GetDeviceCaps(fr.hdc, LOGPIXELSY)); fr.rcPage.left = 0; fr.rcPage.top = 0; fr.rcPage.right = (GetDeviceCaps(pDC->m_hDC, HORZRES) / nPixelsPerLogicalInchX) * TWIPS_PER_INCH; fr.rcPage.bottom = (GetDeviceCaps(pDC->m_hDC, VERTRES) / nPixelsPerLogicalInchY) * TWIPS_PER_INCH; // Set up area to render to. Convert the pixels in prcClip to twips. fr.rc.left = (prcClip->left * TWIPS_PER_INCH) / nPixelsPerLogicalInchX; fr.rc.top = (prcClip->top * TWIPS_PER_INCH) / nPixelsPerLogicalInchY; fr.rc.right = (prcClip->right * TWIPS_PER_INCH) / nPixelsPerLogicalInchX; fr.rc.bottom = (prcClip->bottom * TWIPS_PER_INCH) / nPixelsPerLogicalInchY; fr.rc.right = min(fr.rc.right, fr.rcPage.right); fr.rc.bottom = min(fr.rc.bottom, fr.rcPage.bottom); // Default the range of text to print as the entire document. fr.chrg.cpMin = lnNextCharToPrint; fr.chrg.cpMax = -1; DOMYLOGD ("Printing from char <%d> to <%d>.\n", lnNextCharToPrint, -1); // Print as much text as can fit on a page. The return value is // the index of the first character on the next page. lnNextCharToPrint = FormatRange(&fr, /* bDisplay */ false); VERIFY(DisplayBand(&fr.rc)); // Tell the control to release cached information. FormatRange(NULL, /* bDisplay */ true); // Must set the style back the way it was. EC_V(ModifyStyleEx(WS_EX_TRANSPARENT, 0)); } } // Return the RTF string of the text in the control. CString MyRichEditCtrl::GetRTF() { VALIDATE; EDITSTREAM es = { 0 }; es.pfnCallback = CBStreamOut; CString sRTF; es.dwCookie = reinterpret_cast (&sRTF); StreamOut(SF_RTF, es); return sRTF; } // Callback function to stream the RTF string out of the rich edit control. /* static */ DWORD CALLBACK MyRichEditCtrl::CBStreamOut(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) { //VALIDATE; CString *psEntry = reinterpret_cast (dwCookie); CString tmpEntry((CString) pbBuff); *psEntry += tmpEntry.Left(cb); return 0; } // Put the RTF string sRTF into the rich edit control. void MyRichEditCtrl::SetRTF(const CString& sRTF) { VALIDATE; SetRedraw(false); EDITSTREAM es = { 0 }; es.pfnCallback = CBStreamIn; es.dwCookie = reinterpret_cast (&sRTF); StreamIn(SF_RTF, es); SetRedraw(true); Invalidate(); } // Callback function to stream an RTF string into the rich edit control. /* static */ DWORD CALLBACK MyRichEditCtrl::CBStreamIn(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) { //VALIDATE; CString *pstr = reinterpret_cast (dwCookie); if (pstr->GetLength() < cb) { *pcb = pstr->GetLength(); memcpy(pbBuff, (LPCSTR) *pstr, *pcb); pstr->Empty(); } else { *pcb = cb; memcpy(pbBuff, (LPCSTR) *pstr, *pcb); *pstr = pstr->Right(pstr->GetLength() - cb); } return 0; } // bool MyRichEditCtrl::SelectionIsBold() const { VALIDATE; CHARFORMAT cf(GetCharFormat()); return (cf.dwEffects & CFM_BOLD); } // bool MyRichEditCtrl::SelectionIsItalic() const { VALIDATE; CHARFORMAT cf(GetCharFormat()); return (cf.dwEffects & CFM_ITALIC); } // bool MyRichEditCtrl::SelectionIsUnderline() const { VALIDATE; CHARFORMAT cf(GetCharFormat()); return (cf.dwEffects & CFM_UNDERLINE); } // CHARFORMAT MyRichEditCtrl::GetCharFormat(DWORD dwMask // = CFM_COLOR | CFM_FACE | // CFM_SIZE | CFM_BOLD | // CFM_ITALIC | CFM_UNDERLINE ) const { VALIDATE; CHARFORMAT cf = { sizeof(cf) }; cf.dwMask = dwMask; GetSelectionCharFormat(cf); return cf; } // void MyRichEditCtrl::SetCharStyle(int nMask, int nStyle, int nStart, int nEnd) { VALIDATE; CHARFORMAT cf = { sizeof(cf) }; GetSelectionCharFormat(cf); if (cf.dwMask & nMask) { cf.dwEffects ^= nStyle; } else { cf.dwEffects |= nStyle; } cf.dwMask = nMask; SetSelectionCharFormat(cf); } // void MyRichEditCtrl::SetSelectionBold() { VALIDATE; long start(0); long end(0); GetSel(start, end); SetCharStyle(CFM_BOLD, CFE_BOLD, start, end); } // void MyRichEditCtrl::SetSelectionItalic() { VALIDATE; long start(0); long end(0); GetSel(start, end); SetCharStyle(CFM_ITALIC, CFE_ITALIC, start, end); } // void MyRichEditCtrl::SetSelectionUnderline() { VALIDATE; long start(0); long end(0); GetSel(start, end); SetCharStyle(CFM_UNDERLINE, CFE_UNDERLINE, start, end); } // void MyRichEditCtrl::SetParagraphCenter() { VALIDATE; PARAFORMAT pf = { sizeof(pf) }; pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_CENTER; VERIFY(SetParaFormat(pf)); } // Indent 1/4 inch. void MyRichEditCtrl::IndentParagraph() { VALIDATE; PARAFORMAT pf = { sizeof(pf) }; pf.dwMask = PFM_OFFSET | PFM_OFFSETINDENT; pf.dxStartIndent = TWIPS_PER_INCH / 4.0; VERIFY(SetParaFormat(pf)); } // Outdent 1/4 inch. void MyRichEditCtrl::OutdentParagraph() { VALIDATE; PARAFORMAT2 pf; ::ZeroMemory(&pf, sizeof(pf)); pf.cbSize = sizeof(pf); pf.dwMask = PFM_OFFSET | PFM_OFFSETINDENT; pf.dxStartIndent = - TWIPS_PER_INCH / 4.0; VERIFY(SetParaFormat(pf)); } // void MyRichEditCtrl::SetParagraphLeft() { VALIDATE; PARAFORMAT pf = { sizeof(pf) }; pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_LEFT; VERIFY(SetParaFormat(pf)); } // void MyRichEditCtrl::SetParagraphRight() { VALIDATE; PARAFORMAT pf = { sizeof(pf) }; pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_RIGHT; VERIFY(SetParaFormat(pf)); } // bool MyRichEditCtrl::ParagraphIsCenter() const { VALIDATE; PARAFORMAT pf(GetParagraphFormat()); return pf.wAlignment == PFA_CENTER; } // bool MyRichEditCtrl::ParagraphIsLeft() const { VALIDATE; PARAFORMAT pf(GetParagraphFormat()); return pf.wAlignment == PFA_LEFT; } // bool MyRichEditCtrl::ParagraphIsRight() const { VALIDATE; PARAFORMAT pf(GetParagraphFormat()); return pf.wAlignment == PFA_RIGHT; } // PARAFORMAT MyRichEditCtrl::GetParagraphFormat() const { VALIDATE; PARAFORMAT pf = { sizeof(pf) }; pf.dwMask = PFM_ALIGNMENT | PFM_NUMBERING; GetParaFormat(pf); return pf; } // void MyRichEditCtrl::SetParagraphBulleted() { VALIDATE; PARAFORMAT pf(GetParagraphFormat()); if ((pf.dwMask & PFM_NUMBERING) && (pf.wNumbering == PFN_BULLET)) { pf.wNumbering = 0; pf.dxOffset = 0; pf.dxStartIndent = 0; pf.dwMask = PFM_NUMBERING | PFM_STARTINDENT | PFM_OFFSET; } else { pf.wNumbering = PFN_BULLET; pf.dwMask = PFM_NUMBERING; if (0 == pf.dxOffset) { pf.dxOffset = TWIPS_PER_INCH / 8.0; pf.dwMask = PFM_NUMBERING | PFM_STARTINDENT | PFM_OFFSET; } } VERIFY(SetParaFormat(pf)); } // bool MyRichEditCtrl::ParagraphIsBulleted() const { VALIDATE; PARAFORMAT pf(GetParagraphFormat()); return pf.wNumbering == PFN_BULLET; } // Cycles to the next numbering style. void MyRichEditCtrl::CycleParagraphNumbered() { VALIDATE; PARAFORMAT pf(GetParagraphFormat()); // If it's either a number or a bullet. if (pf.dwMask & PFM_NUMBERING) { switch (pf.wNumbering) { case PFN_BULLET : pf.wNumbering = PFN_NUMBER1; break; case PFN_NUMBER0: pf.wNumbering = PFN_NUMBER1; break; case PFN_NUMBER1: pf.wNumbering = PFN_NUMBER2; break; case PFN_NUMBER2: pf.wNumbering = PFN_NUMBER3; break; case PFN_NUMBER3: pf.wNumbering = PFN_NUMBER4; break; case PFN_NUMBER4: pf.wNumbering = PFN_NUMBER5; break; case PFN_NUMBER5: pf.wNumbering = PFN_NUMBER0; pf.dxOffset = 0; pf.dxStartIndent = 0; pf.dwMask |= PFM_STARTINDENT | PFM_OFFSET; break; default: _ASSERTE(! "Illegal default case"); break; } } else { // Neither a number nor a bullet. pf.wNumbering = PFN_NUMBER1; } // Ensure the mask is set. pf.dwMask |= PFM_NUMBERING; // Set the offset, if needed, and if we're not disabling numbering. if (0 == pf.dxOffset && pf.wNumbering != PFN_NUMBER0) { pf.dxOffset = TWIPS_PER_INCH / 8.0; pf.dwMask |= PFM_STARTINDENT | PFM_OFFSET; } // Reset the format. VERIFY(SetParaFormat(pf)); } // bool MyRichEditCtrl::ParagraphIsNumbered() const { VALIDATE; PARAFORMAT pf(GetParagraphFormat()); // Is neither nothing, nor a bullet, so it must be a number. return pf.wNumbering != PFN_NUMBER0 && pf.wNumbering != PFN_BULLET; } // void MyRichEditCtrl::SelectColor() { VALIDATE; CHARFORMAT cf(GetCharFormat()); if (cf.dwEffects & CFE_AUTOCOLOR) { cf.dwEffects -= CFE_AUTOCOLOR; } // Get a color from the common color dialog. CColorDialog dlg; if (IDOK == dlg.DoModal()) { cf.crTextColor = dlg.GetColor(); } cf.dwMask = CFM_COLOR; SetSelectionCharFormat(cf); } // void MyRichEditCtrl::SetFontName(const CString& sFontName) { VALIDATE; _ASSERTE(! sFontName.IsEmpty()); CHARFORMAT cf(GetCharFormat()); lstrcpy(cf.szFaceName, sFontName.Left(LF_FACESIZE - 1)); cf.dwMask = CFM_FACE; SetSelectionCharFormat(cf); } // void MyRichEditCtrl::SetFontSize(int nPointSize) { VALIDATE; _ASSERTE(0 < nPointSize); CHARFORMAT cf(GetCharFormat()); // Convert to twips. cf.yHeight = nPointSize * TWIPS_PER_POINT; cf.dwMask = CFM_SIZE; SetSelectionCharFormat(cf); } // CString MyRichEditCtrl::GetSelectionFontName(bool& bConsistent) const { VALIDATE; CHARFORMAT cf(GetCharFormat()); CString sName(cf.szFaceName); bConsistent = cf.dwMask & CFM_FACE; return sName; } // long MyRichEditCtrl::GetSelectionFontSize(bool& bConsistent) const { VALIDATE; CHARFORMAT cf(GetCharFormat()); long nSize(cf.yHeight / TWIPS_PER_POINT); bConsistent = cf.dwMask & CFM_SIZE; return nSize; } ///////////////////////////////////////////////////////////////////////////// // FormatBar // Constructor. FormatBar::FormatBar() { } // Destuctor. /* virtual */ FormatBar::~FormatBar() { } BEGIN_MESSAGE_MAP(FormatBar, CToolBar) //{{AFX_MSG_MAP(FormatBar) ON_WM_CREATE() ON_UPDATE_COMMAND_UI(ID_CHAR_BOLD, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_CHAR_COLOR, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_CHAR_ITALIC, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_CHAR_UNDERLINE, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_PARA_CENTER, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_PARA_LEFT, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_PARA_RIGHT, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_PARA_BULLET, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_PARA_NUMBER, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_PARA_INDENT, OnUpdateButton) ON_UPDATE_COMMAND_UI(ID_PARA_OUTDENT, OnUpdateButton) //}}AFX_MSG_MAP ON_CBN_SELENDOK(ID_FONTNAME, OnSelectFontName) ON_CBN_SELENDOK(ID_FONTSIZE, OnSelectFontSize) ON_CBN_KILLFOCUS(ID_FONTSIZE, OnSelectFontSize) ON_CONTROL_REFLECT(BN_CLICKED, OnClick) ON_MESSAGE(WM_APP_NOTES_UPDATE_UI, OnUpdateUI) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // FormatBar message handlers // Set the control. void FormatBar::SetRichEditCtrl(MyRichEditCtrl* pMyRichEditCtrl) { VALIDATE; ASSERT_VALID(pMyRichEditCtrl); m_pMyRichEditCtrl = pMyRichEditCtrl; } // Handle WM_CREATE. int FormatBar::OnCreate(LPCREATESTRUCT lpCreateStruct) { VALIDATE; int nReturn(-1); if (CToolBar::OnCreate(lpCreateStruct) != -1) { if (LoadToolBar(IDR_MYRICHEDITCTRL_FORMATBAR)) { // Determine the size required by the font comboboxes. CClientDC dc(this); HGDIOBJ hFont(::GetStockObject(DEFAULT_GUI_FONT)); if (hFont) { CFont font; if (font.Attach(hFont)) { dc.SelectObject(font); // Get the average char width. TEXTMETRIC tm; ::ZeroMemory(&tm, sizeof tm); if (dc.GetTextMetrics(&tm)) { int nCxChar(tm.tmAveCharWidth); int nCyChar(tm.tmHeight + tm.tmExternalLeading); // Create the Font Name combo. CRect rc; GetItemRect(CommandToIndex(ID_FONTNAME), &rc); rc.right = rc.left + ((LF_FACESIZE + 4) * nCxChar); rc.bottom = rc.top + (16 * nCyChar); // int nIndex(CommandToIndex(ID_FONTNAME)); if (-1 != nIndex) { SetButtonInfo(nIndex, ID_FONTNAME, TBBS_SEPARATOR, rc.Width()); UINT uiCreateStyle(WS_TABSTOP | WS_VISIBLE | WS_VSCROLL); if (m_comboFontName.Create(uiCreateStyle | CBS_DROPDOWNLIST | CBS_SORT, rc, this, ID_FONTNAME)) { m_comboFontName.SetFont(&font); ::EnumFontFamilies(dc.m_hDC, NULL, reinterpret_cast (EnumFontFamProc), reinterpret_cast (this)); // Create Font Size combobox. GetItemRect(CommandToIndex(ID_FONTSIZE), &rc); rc.right = rc.left + (10 * nCxChar); rc.bottom = rc.top + (16 * nCyChar); SetButtonInfo(CommandToIndex(ID_FONTSIZE), ID_FONTSIZE, TBBS_SEPARATOR, rc.Width()); if (m_comboFontSize.Create(uiCreateStyle | CBS_DROPDOWN | WS_HSCROLL, rc, this, ID_FONTSIZE)) { m_comboFontSize.LimitText(4); m_comboFontSize.SetFont(&font); const int naFontSizes[] = { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 32, 36, 42, 48, 56, 72, 128 }; CString sSize; nReturn = 0; for (int i = 0; i < sizeof(naFontSizes) / sizeof(naFontSizes[0]); i++) { sSize.Format("%d", naFontSizes[i]); VERIFY(CB_ERR != m_comboFontSize.AddString(sSize)); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CComboBox::Create", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CComboBox::Create", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CommandToIndex", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CDC::GetTextMetrics", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CFont::Attach", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "::GetStockObject", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "LoadToolBar", ::GetLastError(), Generic::GetSysErrorString()); } } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "CToolBar::OnCreate", ::GetLastError(), Generic::GetSysErrorString()); } return nReturn; } // Update the controls on the toolbar based on the text where the carat is. LRESULT FormatBar::OnUpdateUI(WPARAM wParam, LPARAM lParam) { VALIDATE; UNUSED_ALWAYS(wParam); UNUSED_ALWAYS(lParam); // Don't update the combo boxes if user is changing either of the font // attributes. CWnd *pWnd = GetFocus(); if (pWnd != &m_comboFontName && ! m_comboFontSize.IsChild(pWnd)) { // Turn it off to avoid flash. SetRedraw(false); // Update font name. bool bConsistent(false); CString sFontName(m_pMyRichEditCtrl->GetSelectionFontName(bConsistent)); if (bConsistent) { if (m_comboFontName.SelectString(-1, sFontName) == CB_ERR) { m_comboFontName.SetCurSel(-1); } } else { m_comboFontName.SetCurSel(-1); } // Update font size, long lnFontSize(m_pMyRichEditCtrl->GetSelectionFontSize(bConsistent)); if (bConsistent) { CString sSize; sSize.Format("%d", lnFontSize); m_comboFontSize.SetWindowText(sSize); } else { m_comboFontSize.SetCurSel(-1); } // Update the character attributes. VERIFY(GetToolBarCtrl().CheckButton(ID_CHAR_BOLD, m_pMyRichEditCtrl->SelectionIsBold())); VERIFY(GetToolBarCtrl().CheckButton(ID_CHAR_ITALIC, m_pMyRichEditCtrl->SelectionIsItalic())); VERIFY(GetToolBarCtrl().CheckButton(ID_CHAR_UNDERLINE, m_pMyRichEditCtrl->SelectionIsUnderline())); // Update the paragraph attributes. VERIFY(GetToolBarCtrl().CheckButton(ID_PARA_LEFT, m_pMyRichEditCtrl->ParagraphIsLeft())); VERIFY(GetToolBarCtrl().CheckButton(ID_PARA_CENTER, m_pMyRichEditCtrl->ParagraphIsCenter())); VERIFY(GetToolBarCtrl().CheckButton(ID_PARA_RIGHT, m_pMyRichEditCtrl->ParagraphIsRight())); VERIFY(GetToolBarCtrl().CheckButton(ID_PARA_BULLET, m_pMyRichEditCtrl->ParagraphIsBulleted())); VERIFY(GetToolBarCtrl().CheckButton(ID_PARA_NUMBER, m_pMyRichEditCtrl->ParagraphIsNumbered())); // Okay, turn back on. SetRedraw(true); } return 0; } // Returns 1 to continue enumeration. /* static */ int CALLBACK FormatBar::EnumFontFamProc(ENUMLOGFONT* lpelf, NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam) { UNUSED_ALWAYS(lpntm); UNUSED_ALWAYS(nFontType); FormatBar* pWnd = reinterpret_cast (lParam); ASSERT_POINTER(pWnd, FormatBar); // Add the font name to the combo. if (pWnd) { pWnd->m_comboFontName.AddString(lpelf->elfLogFont.lfFaceName); } return 1; } // Set the font name. void FormatBar::OnSelectFontName() { VALIDATE; CString sFontName; int nIndex(m_comboFontName.GetCurSel()); m_comboFontName.GetLBText(nIndex, sFontName); if (! sFontName.IsEmpty()) { m_pMyRichEditCtrl->SetFontName(sFontName); } } // Set the point size. void FormatBar::OnSelectFontSize() { VALIDATE; int nIndex(m_comboFontSize.GetCurSel()); CString sSize; if (nIndex != CB_ERR) { m_comboFontSize.GetLBText(nIndex, sSize); } else { m_comboFontSize.GetWindowText(sSize); } int nSize(_ttoi(sSize)); if (nSize) { m_pMyRichEditCtrl->SetFontSize(nSize); } } // User clicked somewhere on the format bar. void FormatBar::OnClick() { VALIDATE; // Get the command id of which button on the toolbar was clicked. CPoint pt; if (::GetCursorPos(&pt)) { // Get which button was clicked. ScreenToClient(&pt); int nIndex(GetToolBarCtrl().HitTest(&pt)); TBBUTTON tb = { sizeof(tb) }; VERIFY(GetToolBarCtrl().GetButton(nIndex, &tb)); // Handle each type. switch (tb.idCommand) { case ID_CHAR_BOLD: m_pMyRichEditCtrl->SetSelectionBold(); break; case ID_CHAR_ITALIC: m_pMyRichEditCtrl->SetSelectionItalic(); break; case ID_CHAR_UNDERLINE: m_pMyRichEditCtrl->SetSelectionUnderline(); break; case ID_CHAR_COLOR: m_pMyRichEditCtrl->SelectColor(); break; case ID_PARA_LEFT: m_pMyRichEditCtrl->SetParagraphLeft(); break; case ID_PARA_CENTER: m_pMyRichEditCtrl->SetParagraphCenter(); break; case ID_PARA_RIGHT: m_pMyRichEditCtrl->SetParagraphRight(); break; case ID_PARA_BULLET: m_pMyRichEditCtrl->SetParagraphBulleted(); break; case ID_PARA_NUMBER: m_pMyRichEditCtrl->CycleParagraphNumbered(); break; case ID_PARA_INDENT: m_pMyRichEditCtrl->IndentParagraph(); break; case ID_PARA_OUTDENT: m_pMyRichEditCtrl->OutdentParagraph(); break; default: _ASSERTE(! "Error"); DOMYLOGE ("Impossible default in FormatBar::OnClick().\n"); break; } // Tell the toolbar to update itself to reflect the change we just made. SendMessage(WM_APP_NOTES_UPDATE_UI); } else { _ASSERTE(! "Error"); DOMYLOGST(API_FAILURE), "::GetCursorPos", ::GetLastError(), Generic::GetSysErrorString()); } } // Enables the toolbar buttons always. void FormatBar::OnUpdateButton(CCmdUI* pCmdUI) { VALIDATE; pCmdUI->Enable(true); } // Convert the rich text in this control to a series of EMF files. void MyRichEditCtrl::PrintPreview(HDC hdcPrinter, HDC hdcScreen, LONG& lnNextCharToPrint) { VALIDATE; _ASSERTE(hdcPrinter); _ASSERTE(hdcScreen); _ASSERTE(0 <= lnNextCharToPrint); _ASSERTE(hdcPrinter != hdcScreen && "Print preview only!"); // Set mapping modes. int nMapModePrinterOld(SetMapMode(hdcPrinter, MM_TEXT)); _ASSERTE(nMapModePrinterOld); _ASSERTE(MM_TEXT == nMapModePrinterOld && "Printer was already in MM_TEXT"); int nMapModeScreenOld(SetMapMode(hdcScreen, MM_TEXT)); _ASSERTE(MM_ANISOTROPIC == nMapModeScreenOld && "Screen was in MM_ANISOTROPIC"); // Get the aspect ratios. CDC* pdcGrayArea = CDC::FromHandle(hdcScreen); ASSERT_VALID(pdcGrayArea); CWnd* pwndGrayArea = pdcGrayArea->GetWindow(); ASSERT_VALID(pwndGrayArea); CRect rcGrayArea; pwndGrayArea->GetClientRect(&rcGrayArea); DOMYLOGD ("rcGrayArea: left <%d>, top <%d>, right <%d>, bottom <%d>.\n", rcGrayArea.left, rcGrayArea.top, rcGrayArea.right, rcGrayArea.bottom); double dPrinterWidthPixels(::GetDeviceCaps(hdcPrinter, HORZRES)); double dPrinterHeightPixels(::GetDeviceCaps(hdcPrinter, VERTRES)); double dPrinterAspectRatio(dPrinterWidthPixels / dPrinterHeightPixels); double dWindowAspectRatio((double) rcGrayArea.Width() / (double) rcGrayArea.Height()); // Figure out out big the white "paper" area on the screen is in screen (as // opposed to printer, *not* as opposed to client) coordinates. We can do // this because we know the paper is always about as big as the "gray area" // in one of the two dimensions (but not in the other). So here, we figure // out the size of the white paper in the other dimension. // There are two cases: we have big margins on the top and bottom. CRect rcPaper; rcPaper.left = 0; rcPaper.top = 0; if (dWindowAspectRatio < dPrinterAspectRatio) { rcPaper.right = rcGrayArea.Width(); rcPaper.bottom = (double) rcPaper.Width() / dPrinterAspectRatio; } else { // ... or we have big margins on the left and right. rcPaper.bottom = rcGrayArea.Height(); rcPaper.right = (double) rcPaper.Height() * dPrinterAspectRatio; } DOMYLOGD ("rcPaper: left <%d>, top <%d>, right <%d>, bottom <%d>.\n", rcPaper.left, rcPaper.top, rcPaper.right, rcPaper.bottom); // Allow for a margins - these are arbitrary. Since the app size can // change, we have do these as a percent. double dMarginPercentLeft (0.02); double dMarginPercentTop (0.06); double dMarginPercentRight (0.12); double dMarginPercentBottom(0.12); double dMarginLeft ((double) rcPaper.Width() * dMarginPercentLeft); double dMarginTop ((double) rcPaper.Height() * dMarginPercentTop); double dMarginRight ((double) rcPaper.Width() * dMarginPercentRight); double dMarginBottom((double) rcPaper.Height() * dMarginPercentBottom); rcPaper.left += dMarginLeft; rcPaper.top += dMarginTop; rcPaper.right -= dMarginRight; rcPaper.bottom -= dMarginBottom; // Create all the metafiles. Loop while we've not reached the end of the // text in the control. CDWordArray dwaEMF; CDWordArray dwaStartChar; for (int nPage = 0, nStartChar = lnNextCharToPrint; nStartChar < GetTextLength(); ++nPage) { // Draw this portion of the RTF on the EMF. dwaStartChar.Add(nStartChar); HENHMETAFILE* phEMF = new HENHMETAFILE; *phEMF = RTFToEMF(hdcPrinter, nStartChar, &nStartChar); dwaEMF.Add(reinterpret_cast (phEMF)); } // Now draw the appropriate metafile to screen. for (int nEMF = 0; nEMF < dwaEMF.GetSize(); ++nEMF) { if (lnNextCharToPrint == dwaStartChar.GetAt(nEMF)) { HENHMETAFILE* phEMF = reinterpret_cast (dwaEMF.GetAt(nEMF)); VERIFY(::PlayEnhMetaFile(hdcScreen, *phEMF, &rcPaper)); if (nEMF + 1 < dwaStartChar.GetSize()) { lnNextCharToPrint = dwaStartChar.GetAt(nEMF + 1); } else { // This must be set so that the caller knows there's nothing // left to preview. lnNextCharToPrint = GetTextLength(); } break; } } // Clean up. for (nEMF = 0; nEMF < dwaEMF.GetSize(); ++nEMF) { HENHMETAFILE* phEMF = reinterpret_cast (dwaEMF.GetAt(nEMF)); VERIFY(::DeleteEnhMetaFile(*phEMF)); delete phEMF; phEMF = NULL; } dwaEMF.RemoveAll(); } // Write this RTF page to a metafile. HENHMETAFILE MyRichEditCtrl::RTFToEMF(HDC hdcPrinter, int nStartChar, int* pnEndChar) { VALIDATE; _ASSERTE(hdcPrinter); _ASSERTE(0 <= nStartChar); _ASSERTE(pnEndChar); // Create metafile rect in units of 0.01 mm (convert from twips). CRect rcMeta; rcMeta.left = 0; rcMeta.top = 0; rcMeta.right = GetDeviceCaps(hdcPrinter, HORZSIZE) * 100; rcMeta.bottom = GetDeviceCaps(hdcPrinter, VERTSIZE) * 100; // Create the EMF in memory. HDC hdcMetafile(::CreateEnhMetaFile(hdcPrinter, NULL, &rcMeta, NULL)); _ASSERTE(hdcMetafile); // Set up the page (convert 0.01 mm to twips). FORMATRANGE fr = { 0 }; fr.rcPage.left = ((double) rcMeta.left * TWIPS_PER_INCH) / MM_PER_INCH / 100.0; fr.rcPage.top = ((double) rcMeta.top * TWIPS_PER_INCH) / MM_PER_INCH / 100.0; fr.rcPage.right = ((double) rcMeta.right * TWIPS_PER_INCH) / MM_PER_INCH / 100.0; fr.rcPage.bottom = ((double) rcMeta.bottom * TWIPS_PER_INCH) / MM_PER_INCH / 100.0; // Set up no margins all around. fr.rc = fr.rcPage; // Set up the range of text to print as nStartChar to end of document fr.chrg.cpMin = nStartChar; fr.chrg.cpMax = -1; fr.hdc = hdcMetafile; // Device to render to. fr.hdcTarget = hdcPrinter; // Device to format for. // Tell the control to draw itself on our metafile DC. int nTextPrinted(FormatRange(&fr)); if (pnEndChar) { *pnEndChar = nTextPrinted; } return ::CloseEnhMetaFile(hdcMetafile); } // See: Microsoft KB Article Q280447: "BUG: Text from a Rich Edit Control Is // Truncated During Dialog Data Exchange (DDX)". Note that it's not virtual, so // this fix won't work polymorphically. void MyRichEditCtrl::SetWindowText(LPCTSTR lpszString) { VALIDATE; _ASSERTE(! "Do NOT call SetWindowText() on a CRichEditCtrl! Q280447"); CRichEditCtrl::SetWindowText(lpszString); }