#include "ctlspriv.h" #include "tab.h" #define BMOVECURSORONCLICK FALSE #define BMOVECURSORONDRAG TRUE BOOL Tab_OnGetItemRect(PTC ptc, int iItem, LPRECT lprc); BOOL Tab_Init(HINSTANCE hinst) { WNDCLASS wc; wc.lpfnWndProc = Tab_WndProc; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = NULL; wc.hInstance = hinst; wc.lpszClassName = c_szTabControlClass; wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); wc.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; wc.cbWndExtra = sizeof(PTC); wc.cbClsExtra = 0; if (!RegisterClass(&wc) && !GetClassInfo(hinst, c_szTabControlClass, &wc)) return FALSE; return TRUE; } void Tab_VFlipRect(PTC ptc, LPRECT prc); void FlipRect(LPRECT prc); void VertInvalidateRect(HWND hwnd, LPRECT qrc, BOOL b, BOOL fVert); // Shared generic theme-aware code (exists in trackbar.c) void VertDrawEdge(HDC hdc, LPRECT qrc, UINT edgeType, UINT grfFlags, BOOL fVert, HTHEME hTheme, int iPartId, int iStateId); void VertPatBlt(HDC hdc1, int x1, int y1, int w, int h, DWORD rop, BOOL fVert, HTHEME hTheme, int iPartId, int iStateId); LRESULT TabDragCallback(HWND hwnd, UINT code, WPARAM wp, LPARAM lp) { PTC ptc = (PTC)GetWindowInt(hwnd, 0); LRESULT lres; switch (code) { case DPX_ENTER: case DPX_LEAVE: ptc->iDragTab = -1; ptc->dwDragDelay = 0; lres = 1; break; case DPX_DRAGHIT: if (lp) { BOOL fResetDelay = TRUE; int iTab; POINT pt; pt.x = ((POINTL *)lp)->x; pt.y = ((POINTL *)lp)->y; MapWindowPoints(NULL, ptc->ci.hwnd, &pt, 1); iTab = Tab_OnHitTest(ptc, pt.x, pt.y, NULL); if ((iTab != ptc->iSel)) { if (iTab >= 0) { DWORD dwHitTime = GetTickCount(); if (ptc->dwDragDelay == 0 || dwHitTime - ptc->dwDragDelay >= TAB_DRAGDELAY) { if (ptc->dwDragDelay) { ChangeSel(ptc, iTab, TRUE, BMOVECURSORONDRAG); // present no target if validation failed // this will prevent accidental drops if (ptc->iSel != iTab) iTab = -1; } else { ptc->dwDragDelay = dwHitTime | 1; // make sure value is not zero fResetDelay = FALSE; } } else if (iTab == ptc->iDragTab) fResetDelay = FALSE; } ptc->iDragTab = iTab; } if (fResetDelay) ptc->dwDragDelay = 0; lres = (LRESULT)iTab; } else lres = -1; break; case DPX_GETOBJECT: lres = (LRESULT)GetItemObject(&ptc->ci, TCN_GETOBJECT, &IID_IDropTarget, (LPNMOBJECTNOTIFY)lp); break; case DPX_SELECT: if (((int)wp) >= 0) { SendMessage(ptc->ci.hwnd, TCM_HIGHLIGHTITEM, wp, MAKELPARAM((lp != DROPEFFECT_NONE), 0)); } lres = 0; break; default: lres = -1; break; } return lres; } void VertSmoothScrollWindow(HWND hwnd, int dx, int dy, LPCRECT lprcSrc, LPCRECT lprcClip, HRGN hrgn, LPRECT lprcUpdate, UINT fuScroll, BOOL fVert, UINT uScrollMin) { RECT rcSrc; RECT rcClip; SMOOTHSCROLLINFO si; if (fVert) { SWAP(dx, dy, int); if (lprcSrc) { rcSrc = *lprcSrc; lprcSrc = &rcSrc; FlipRect(&rcSrc); } if (lprcClip) { rcClip = *lprcClip; lprcClip = &rcClip; FlipRect(&rcClip); } } si.cbSize=sizeof(si); si.fMask= SSIF_MINSCROLL; si.hwnd= hwnd; si.dx=dx; si.dy=dy; si.lprcSrc=lprcSrc; si.lprcClip=lprcClip; si.hrgnUpdate=hrgn; si.lprcUpdate=lprcUpdate; si.fuScroll=fuScroll; si.uMaxScrollTime=SSI_DEFAULT; si.cxMinScroll=uScrollMin; si.cyMinScroll= uScrollMin; si.pfnScrollProc = NULL; SmoothScrollWindow(&si); if (fVert) { if (lprcUpdate) FlipRect(lprcUpdate); } } void Tab_SmoothScrollWindow(PTC ptc, int dx, int dy, LPRECT lprcSrc, LPRECT lprcClip, HRGN hrgn, LPRECT lprcUpdate, UINT fuScroll, UINT uScrollMin) { RECT rcSrc; RECT rcClip; if (Tab_Bottom(ptc)) { dy *= -1; if (lprcSrc) { rcSrc = *lprcSrc; lprcSrc = &rcSrc; Tab_VFlipRect(ptc, lprcSrc); } if (lprcClip) { rcClip = *lprcClip; lprcClip = &rcClip; Tab_VFlipRect(ptc, lprcClip); } } VertSmoothScrollWindow(ptc->ci.hwnd, dx, dy, lprcSrc, lprcClip, hrgn, lprcUpdate, fuScroll, Tab_Vertical(ptc), uScrollMin); if (lprcUpdate) { Tab_VFlipRect(ptc, lprcClip); } } void Tab_InvalidateRect(PTC ptc, LPRECT prc, BOOL b) { RECT rc = *prc; Tab_VFlipRect(ptc, &rc); VertInvalidateRect((ptc)->ci.hwnd, &rc, b, Tab_Vertical(ptc)); } // Tab_DrawEdge is theme-aware void Tab_DrawEdge(HDC hdc, LPRECT prc, UINT uType, UINT uFlags, PTC ptc) { RECT rc = *prc; Tab_VFlipRect(ptc, &rc); if (Tab_Bottom(ptc)) { UINT uNewFlags; if (uFlags & BF_DIAGONAL) { uNewFlags = uFlags & ~(BF_RIGHT | BF_LEFT); if (uFlags & BF_LEFT) uNewFlags |= BF_RIGHT; if (uFlags & BF_RIGHT) uNewFlags |= BF_LEFT; } else { uNewFlags = uFlags & ~(BF_TOP | BF_BOTTOM); if (uFlags & BF_TOP) uNewFlags |= BF_BOTTOM; if (uFlags & BF_BOTTOM) uNewFlags |= BF_TOP; } uFlags = uNewFlags; } VertDrawEdge(hdc, &rc, uType, uFlags, Tab_Vertical(ptc), ptc->hTheme, ptc->iPartId, ptc->iStateId); } // Tab_PatBlt is theme aware void Tab_PatBlt(HDC hdc, int x1, int y1, int w, int h, UINT rop, PTC ptc) { RECT rc; rc.top = y1; rc.left = x1; rc.right = x1+w; rc.bottom = y1+h; Tab_VFlipRect(ptc, &rc); VertPatBlt(hdc, rc.left, rc.top, RECTWIDTH(rc) , RECTHEIGHT(rc), rop, Tab_Vertical(ptc), ptc->hTheme, ptc->iPartId, ptc->iStateId); } void NormalizeRect(LPRECT prc) { if (prc->right < prc->left) { SWAP(prc->right, prc->left, int); } if (prc->bottom < prc->top) { SWAP(prc->bottom, prc->top, int); } } void VFlipRect(LPRECT prcClient, LPRECT prc) { int iTemp = prc->bottom; prc->bottom = prcClient->bottom - (prc->top - prcClient->top); prc->top = prcClient->bottom - (iTemp - prcClient->top); } // diagonal flip. void Tab_DFlipRect(PTC ptc, LPRECT prc) { if (Tab_Vertical(ptc)) { FlipRect(prc); } } // vertical support is done much like the trackbar control. we're going // to flip the coordinate system. this means that tabs will be added from top down. void Tab_GetClientRect(PTC ptc, LPRECT prc) { GetClientRect(ptc->ci.hwnd, prc); Tab_DFlipRect(ptc, prc); } // vertical flip void Tab_VFlipRect(PTC ptc, LPRECT prc) { if (Tab_Bottom(ptc)) { RECT rcClient; Tab_GetClientRect(ptc, &rcClient); VFlipRect(&rcClient, prc); } } void Tab_VDFlipRect(PTC ptc, LPRECT prc) { Tab_VFlipRect(ptc, prc); Tab_DFlipRect(ptc, prc); } // real coordinates to tab coordinates void Tab_DVFlipRect(PTC ptc, LPRECT prc) { Tab_DFlipRect(ptc, prc); Tab_VFlipRect(ptc, prc); } #define Tab_ImageList_GetIconSize(ptc, pcx, pcy) VertImageList_GetIconSize((ptc)->himl, pcx, pcy, Tab_Vertical(ptc)) void VertImageList_GetIconSize(HIMAGELIST himl, LPINT pcx, LPINT pcy, BOOL fVert) { ImageList_GetIconSize(himl, pcx, pcy); if (fVert) { // if we're in vertical mode, the width is really the height. // we won't draw the bitmaps sideways. we'll rely on people // authoring them that way. int iTemp = *pcy; *pcy = *pcx; *pcx = iTemp; } } void VertImageList_Draw(HIMAGELIST himl, int iIndex, HDC hdc, int x, int y, UINT uFlags, BOOL fVert) { if (fVert) { int iTemp; iTemp = y; y = x; x = iTemp; // since we draw from the upper left, flipping the x/y axis means we still draw from the upper left. // all we need to do is swap x and y. we don't need to offset } ImageList_Draw( himl, iIndex, hdc, x, y, uFlags); } void Tab_ImageList_Draw(PTC ptc, int iImage, HDC hdc, int x, int y, UINT uFlags) { RECT rc; int cxImage, cyImage; Tab_ImageList_GetIconSize(ptc, &cxImage, &cyImage); if (Tab_Bottom(ptc)) { y += cyImage; } rc.top = rc.bottom = y; Tab_VFlipRect(ptc, &rc); y = rc.top; VertImageList_Draw((ptc)->himl, iImage, hdc, x, y, uFlags, Tab_Vertical(ptc)); } // Tab_DrawText is theme aware (RENDERS) void Tab_DrawText(HDC hdc, LPTSTR lpsz, int nCount, LPRECT lprc, UINT uFormat, PTC ptc) { RECT rcTemp = *lprc; Tab_VDFlipRect(ptc, &rcTemp); if (Tab_Vertical(ptc)) uFormat |= DT_BOTTOM; if (CCGetUIState(&(ptc->ci)) & UISF_HIDEACCEL) { uFormat |= DT_HIDEPREFIX; } // Use theme text renderer if possible if (ptc->hTheme) { DrawThemeText(ptc->hTheme, hdc, ptc->iPartId, ptc->iStateId, lpsz, nCount, uFormat, nCount, &rcTemp); } else { DrawText(hdc, lpsz, nCount, &rcTemp, uFormat); } } // Tab_DrawTextEx is theme aware (RENDERS) void Tab_DrawTextEx(HDC hdc, LPTSTR lpsz, int nCount, LPRECT lprc, UINT uFormat, LPDRAWTEXTPARAMS lpParams, PTC ptc) { RECT rcTemp = *lprc; Tab_VDFlipRect(ptc, &rcTemp); if (Tab_Vertical(ptc)) uFormat |= DT_BOTTOM; if (CCGetUIState(&(ptc->ci)) & UISF_HIDEACCEL) { uFormat |= DT_HIDEPREFIX; } // Use theme text renderer if possible if (ptc->hTheme) { DrawThemeText(ptc->hTheme, hdc, ptc->iPartId, ptc->iStateId, lpsz, nCount, uFormat | DT_CENTER, nCount, &rcTemp); } else { DrawTextEx(hdc, lpsz, nCount, &rcTemp, uFormat, lpParams); } } // Tab_ExtTextOut is theme aware (RENDERS) void Tab_ExtTextOut(HDC hdc, int x, int y, UINT uFlags, LPRECT prc, LPTSTR lpsz, UINT cch, CONST INT *pdw, PTC ptc) { RECT rcTemp; rcTemp.left = rcTemp.right = x; if (Tab_Bottom(ptc) && !Tab_Vertical(ptc)) { // first we need to move the top point because if we're drawing on Tab_Bottom, then // text won't extend down from y. y += ptc->tmHeight; } rcTemp.top = rcTemp.bottom = y; Tab_VDFlipRect(ptc, &rcTemp); x = rcTemp.left; y = rcTemp.bottom; rcTemp = *prc; Tab_VDFlipRect(ptc, &rcTemp); // Use theme text renderer if possible if (ptc->hTheme) { if (lpsz) { UINT uDTFlags = 0; RECT rc = { x, y, rcTemp.right, rcTemp.bottom }; if (!(uFlags & ETO_CLIPPED)) uDTFlags |= DT_NOCLIP; if (uFlags & ETO_RTLREADING) uDTFlags |= DT_RTLREADING; // Vertical text not supported DrawThemeText(ptc->hTheme, hdc, ptc->iPartId, ptc->iStateId, lpsz, cch, uDTFlags, 0, &rc); } } else { ExtTextOut(hdc, x, y, uFlags, &rcTemp, lpsz, cch, pdw); } } void VertDrawFocusRect(HDC hdc, LPRECT lprc, BOOL fVert) { RECT rc; rc = *lprc; if (fVert) FlipRect(&rc); DrawFocusRect(hdc, &rc); } // Tab_DrawFocusRect is theme aware void Tab_DrawFocusRect(HDC hdc, LPRECT lprc, PTC ptc) { RECT rc = *lprc; Tab_VFlipRect(ptc, &rc); VertDrawFocusRect(hdc, &rc, Tab_Vertical(ptc)); } void Tab_Scroll(PTC ptc, int dx, int iNewFirstIndex) { int i; int iMax; RECT rc; LPTABITEM pitem = NULL; // don't stomp on edge unless first item is selected rc.left = g_cxEdge; rc.right = ptc->cxTabs; // Dont scroll beyond tabs. rc.top = 0; rc.bottom = ptc->cyTabs + 2 * g_cyEdge; // Only scroll in the tab area // See if we can scroll the window... // DebugMsg(DM_TRACE, TEXT("Tab_Scroll dx=%d, iNew=%d\n\r"), dx, iNewFirstIndex); Tab_SmoothScrollWindow(ptc, dx, 0, NULL, &rc, NULL, NULL, SW_INVALIDATE | SW_ERASE, SSI_DEFAULT); // We also need to update the item rectangles and also // update the internal variables... iMax = Tab_Count(ptc) - 1; for (i = iMax; i >= 0; i--) { pitem = Tab_FastGetItemPtr(ptc, i); OffsetRect(&pitem->rc, dx, 0); } // If the previously last visible item is not fully visible // now, we need to invalidate it also. // if (ptc->iLastVisible > iMax) ptc->iLastVisible = iMax; for (i = ptc->iLastVisible; i>= 0; i--) { pitem = Tab_GetItemPtr(ptc, i); if (pitem) { if (pitem->rc.right <= ptc->cxTabs) break; Tab_InvalidateItem(ptc, ptc->iLastVisible, TRUE); } } if ((i == ptc->iLastVisible) && pitem) { // The last previously visible item is still fully visible, so // we need to invalidate to the right of it as there may have been // room for a partial item before, that will now need to be drawn. rc.left = pitem->rc.right; Tab_InvalidateRect(ptc, &rc, TRUE); } ptc->iFirstVisible = iNewFirstIndex; if (ptc->hwndArrows) SendMessage(ptc->hwndArrows, UDM_SETPOS, 0, MAKELPARAM(iNewFirstIndex, 0)); UpdateToolTipRects(ptc); } void Tab_OnHScroll(PTC ptc, HWND hwndCtl, UINT code, int pos) { // Now process the Scroll messages if (code == SB_THUMBPOSITION) { // // For now lets simply try to set that item as the first one // { // If we got here we need to scroll LPTABITEM pitem = Tab_GetItemPtr(ptc, pos); int dx = 0; if (pitem) dx = -pitem->rc.left + g_cxEdge; if (dx || !pitem) { Tab_Scroll(ptc, dx, pos); UpdateWindow(ptc->ci.hwnd); } } } } void Tab_OnSetRedraw(PTC ptc, BOOL fRedraw) { if (fRedraw) { ptc->flags |= TCF_REDRAW; RedrawAll(ptc, RDW_INVALIDATE); } else { ptc->flags &= ~TCF_REDRAW; } } // Tab_OnSetFont will always cache the font (even if themes are on, in which case this font will be // ignored). This is because dialog managers will set the tabs font on creation. If themes are // turned off and this font was never set, the default system font will be incorrectly used. void Tab_OnSetFont(PTC ptc, HFONT hfont, BOOL fRedraw) { ASSERT(ptc); if (!ptc->hfontLabel || hfont != ptc->hfontLabel) { if (ptc->flags & TCF_FONTCREATED) { DeleteObject(ptc->hfontLabel); ptc->flags &= ~TCF_FONTCREATED; ptc->hfontLabel = NULL; } if (!hfont) { // set back to system font ptc->hfontLabel = g_hfontSystem; } else { ptc->flags |= TCF_FONTSET; ptc->hfontLabel = hfont; ptc->ci.uiCodePage = GetCodePageForFont(hfont); } ptc->cxItem = ptc->cyTabs = RECOMPUTE; if (Tab_Vertical(ptc)) { // make sure that the font is drawn vertically LOGFONT lf; GetObject(ptc->hfontLabel, sizeof(lf), &lf); if (Tab_Bottom(ptc)) { lf.lfEscapement = 2700; } else { lf.lfEscapement = 900; // 90 degrees } lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; ptc->hfontLabel = CreateFontIndirect(&lf); if (ptc->hfontLabel != NULL) ptc->flags |= TCF_FONTCREATED; } if (ptc->hfontLabel != NULL) RedrawAll(ptc, RDW_INVALIDATE | RDW_ERASE); } } BOOL Tab_OnCreate(PTC ptc) { HDC hdc; DWORD exStyle = 0; ptc->hdpa = DPA_Create(4); if (!ptc->hdpa) return FALSE; // make sure we don't have invalid bits set if (!Tab_FixedWidth(ptc)) { ptc->ci.style &= ~(TCS_FORCEICONLEFT | TCS_FORCELABELLEFT); } if (Tab_Vertical(ptc)) { ptc->ci.style |= TCS_MULTILINE; //ptc->ci.style &= ~TCS_BUTTONS; } if (Tab_ScrollOpposite(ptc)) { ptc->ci.style |= TCS_MULTILINE; ptc->ci.style &= ~TCS_BUTTONS; } if (Tab_FlatButtons(ptc)) { ptc->dwStyleEx |= TCS_EX_FLATSEPARATORS; } // Initialize themes. No themes for owner drawn or button-style tab controls ptc->hTheme = (!Tab_OwnerDraw(ptc) && !Tab_DrawButtons(ptc)) ? OpenThemeData(ptc->ci.hwnd, L"Tab") : NULL; // Active hot state if themes are in use if (ptc->hTheme) { ptc->ci.style |= TCS_HOTTRACK; } // make us always clip siblings SetWindowLong(ptc->ci.hwnd, GWL_STYLE, WS_CLIPSIBLINGS | ptc->ci.style); ptc->flags = TCF_REDRAW; // enable redraw ptc->cbExtra = sizeof(LPARAM); // default extra size ptc->iSel = -1; ptc->iHot = -1; ptc->cxItem = ptc->cyTabs = RECOMPUTE; ptc->cxPad = g_cxEdge * 3; ptc->cyPad = (g_cyEdge * 3/2); ptc->iFirstVisible = 0; ptc->hwndArrows = NULL; ptc->iLastRow = -1; ptc->iNewSel = -1; ptc->iLastTopRow = -1; hdc = GetDC(NULL); ptc->iTabWidth = GetDeviceCaps(hdc, LOGPIXELSX); ReleaseDC(NULL, hdc); InitDitherBrush(); if (ptc->ci.style & TCS_TOOLTIPS) { TOOLINFO ti; // don't bother setting the rect because we'll do it below // in FlushToolTipsMgr; ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND; ti.hwnd = ptc->ci.hwnd; ti.uId = (UINT_PTR)ptc->ci.hwnd; ti.lpszText = 0; ptc->hwndToolTips = CreateWindowEx(exStyle, c_szSToolTipsClass, TEXT(""), WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, ptc->ci.hwnd, NULL, HINST_THISDLL, NULL); if (ptc->hwndToolTips) SendMessage(ptc->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); else ptc->ci.style &= ~(TCS_TOOLTIPS); } if (g_fDBCSInputEnabled) ptc->hPrevImc = ImmAssociateContext(ptc->ci.hwnd, 0L); // Setup theme state for methods called that render themes before first paint, setup state (TAB/BUTTON) if (ptc->hTheme) { ptc->iPartId = TABP_TABITEM; ptc->iStateId = TIS_NORMAL; } return TRUE; } void Tab_OnDestroy(PTC ptc) { int i; // Close theme if (ptc->hTheme) CloseThemeData(ptc->hTheme); if (g_fDBCSInputEnabled) ImmAssociateContext(ptc->ci.hwnd, ptc->hPrevImc); if ((ptc->ci.style & TCS_TOOLTIPS) && IsWindow(ptc->hwndToolTips)) { DestroyWindow(ptc->hwndToolTips); } for (i = 0; i < Tab_Count(ptc); i++) Tab_FreeItem(ptc, Tab_FastGetItemPtr(ptc, i)); DPA_Destroy(ptc->hdpa); if (ptc->hDragProxy) DestroyDragProxy(ptc->hDragProxy); if (ptc->flags & TCF_FONTCREATED) { DeleteObject(ptc->hfontLabel); } if (ptc) { SetWindowInt(ptc->ci.hwnd, 0, 0); NearFree((HLOCAL)ptc); } TerminateDitherBrush(); } // returns true if it actually moved void PutzRowToBottom(PTC ptc, int iRowMoving) { int i; LPTABITEM pitem; int dy; RECT rcTabs; Tab_GetClientRect(ptc, &rcTabs); if (Tab_ScrollOpposite(ptc)) { // in scroll mode, the iRow doesn't change. only the rc's do. int yOldTop; int yNewTop; int iLastTopRow = ptc->iLastTopRow == -1 ? ptc->iLastRow : ptc->iLastTopRow; if (iRowMoving == iLastTopRow) { if (ptc->iLastTopRow == -1) ptc->iLastTopRow = iRowMoving; return; // already at the bottom; } // this is the height of the tab's empty area... which is the amount // of space a tab must move to get from the top to the bottom dy = rcTabs.bottom - rcTabs.top - (ptc->cyTabs * (ptc->iLastRow + 1)) - g_cyEdge; for (i = Tab_Count(ptc) - 1; i >= 0; i--) { pitem = Tab_FastGetItemPtr(ptc, i); DebugMsg(DM_TRACE, TEXT("Putzing %s %d %d %d %d"), pitem->pszText, pitem->rc.left, pitem->rc.top, pitem->rc.right, pitem->rc.bottom); // save this for scrolling below if (pitem->iRow == iRowMoving) { yNewTop = pitem->rc.bottom; } else if (pitem->iRow == iLastTopRow) { yOldTop = pitem->rc.bottom; } if (pitem->iRow > iRowMoving) { // this item should be on the bottom if (pitem->iRow <= iLastTopRow) { // but it's not... OffsetRect(&pitem->rc, 0, dy); } } else { // this item should be on the top if (pitem->iRow > iLastTopRow) { // but it's not... so move it OffsetRect(&pitem->rc, 0, -dy); } } if ((pitem->iRow == iLastTopRow) && iLastTopRow > iRowMoving) { // in this case, we need to get the yOldTop AFTER it's moved. yOldTop = pitem->rc.bottom; } DebugMsg(DM_TRACE, TEXT("Putzing %s %d %d %d %d"), pitem->pszText, pitem->rc.left, pitem->rc.top, pitem->rc.right, pitem->rc.bottom); } if (ptc->iLastTopRow != -1) { // if it wasn't a full recalc, then we need to do some scrollwindow stuff. int dy; // first find the topmost parent dy = yOldTop - yNewTop; if (yNewTop > yOldTop) { rcTabs.top = yOldTop; rcTabs.bottom = yNewTop; } else { rcTabs.top = yNewTop; rcTabs.bottom = yOldTop; } Tab_SmoothScrollWindow(ptc, 0, dy, NULL, &rcTabs, NULL, NULL, SW_ERASE |SW_INVALIDATE, 1); InflateRect(&rcTabs, g_cxEdge, g_cyEdge); Tab_InvalidateRect(ptc, &rcTabs, FALSE); } ptc->iLastTopRow = iRowMoving; } else { if (iRowMoving == ptc->iLastRow) return; // already at the bottom; // no scrolling. just set the iRow var appropriatesly for (i = Tab_Count(ptc) -1 ;i >= 0; i--) { pitem = Tab_FastGetItemPtr(ptc, i); if (pitem->iRow > iRowMoving) { // if the row is higher than the row that's being selected, // it drops one. pitem->iRow--; dy = -ptc->cyTabs; } else if (pitem->iRow == iRowMoving) { // save this rcTabs.top = pitem->rc.top; // if it's on the row that's moving down, we assign it to iLastRow and //calculate how far it needs to go. dy = ptc->cyTabs * (ptc->iLastRow - iRowMoving); pitem->iRow = ptc->iLastRow; } else continue; pitem->rc.top += dy; pitem->rc.bottom += dy; } rcTabs.bottom = ptc->cyTabs * (ptc->iLastRow + 1); Tab_SmoothScrollWindow(ptc, 0, rcTabs.bottom - rcTabs.top, NULL, &rcTabs, NULL, NULL, SW_ERASE |SW_INVALIDATE, 1); UpdateWindow(ptc->ci.hwnd); // invalidate the little bit below the rcTabs.bottom += 2*g_cyEdge; rcTabs.top = rcTabs.bottom - 3 * g_cyEdge; Tab_InvalidateRect(ptc, &rcTabs, TRUE); } UpdateToolTipRects(ptc); } __inline int Tab_InterButtonGap(PTC ptc) { ASSERT(Tab_DrawButtons(ptc)); if (Tab_FlatButtons(ptc)) { return (g_cxEdge * 5); } else { return (g_cxEdge * 3)/2; } } // // BADNESS is the amount of unused space in the row // #define BADNESS(ptc, i) (ptc->cxTabs - Tab_FastGetItemPtr(ptc, i)->rc.right) // borrow one tab from the prevous row BOOL BorrowOne(PTC ptc, int iCurLast, int iPrevLast, int iBorrow) { LPTABITEM pitem, pitem2; int i; int dx; // is there room to move the prev item? (might now be if iPrev is huge) pitem = Tab_FastGetItemPtr(ptc, iPrevLast); pitem2 = Tab_FastGetItemPtr(ptc, iCurLast); // dx is the number of extra pixels that aren't part of the pitem->rc. // The non-button case of 2 * g_cxEdge is maniacally hard-coded // all over the place. Change it at your own risk. if (Tab_DrawButtons(ptc)) dx = Tab_InterButtonGap(ptc); else dx = 2 * g_cxEdge; // inflate by g_cxEdge // if the size of the item is greaterthan the badness if (BADNESS(ptc, iCurLast) < (pitem->rc.right - pitem->rc.left + dx)) return FALSE; // otherwise do it. // move this one down dx = pitem->rc.left - Tab_FastGetItemPtr(ptc, iPrevLast + 1)->rc.left; pitem->rc.left -= dx; pitem->rc.right -= dx; pitem->rc.top = pitem2->rc.top; pitem->rc.bottom = pitem2->rc.bottom; pitem->iRow = pitem2->iRow; // and move all the others over. dx = pitem->rc.right - pitem->rc.left; for(i = iPrevLast + 1 ; i <= iCurLast ; i++ ) { pitem = Tab_FastGetItemPtr(ptc, i); pitem->rc.left += dx; pitem->rc.right += dx; } if (iBorrow) { if (pitem->iRow > 1) { // borrow one from the next row up. // setup the new iCurLast as the one right before the one we moved // (the one we moved is now the current row's first // and hunt backwards until we find an iPrevLast iCurLast = iPrevLast - 1; while (iPrevLast-- && Tab_FastGetItemPtr(ptc, iPrevLast)->iRow == (pitem->iRow - 1)) { if (iPrevLast <= 0) { // sanity check return FALSE; } } return BorrowOne(ptc, iCurLast, iPrevLast, iBorrow - 1 ); } else return FALSE; } return TRUE; } // fill last row will fiddle around borrowing from the previous row(s) // to keep from having huge huge bottom tabs void FillLastRow(PTC ptc) { int hspace; int cItems = Tab_Count(ptc); int iPrevLast; int iBorrow = 0; // if not even two items, nothing to fill from if (cItems < 2) return; // find last item on previous row for (iPrevLast = cItems - 2; Tab_FastGetItemPtr(ptc, iPrevLast)->iRow == ptc->iLastRow; iPrevLast--) { // sanity check if (iPrevLast <= 0) { ASSERT(FALSE); return; } } while (iPrevLast && (hspace = BADNESS(ptc, cItems-1)) && (hspace > ((ptc->cxTabs/8) + BADNESS(ptc, iPrevLast)))) { // if borrow fails, bail if (!BorrowOne(ptc, cItems - 1, iPrevLast, iBorrow++)) return; iPrevLast--; } } void RightJustify(PTC ptc) { int i; LPTABITEM pitem; int j; int k; int n; int cItems = Tab_Count(ptc); int hspace, dwidth, dremainder, moved; // don't justify if only one row if (ptc->iLastRow < 1) return; FillLastRow(ptc); for ( i = 0; i < cItems; i++ ) { int iRow; pitem = Tab_FastGetItemPtr(ptc, i) ; iRow = pitem->iRow; // find the last item in this row for( j = i ; j < cItems; j++) { if(Tab_FastGetItemPtr(ptc, j)->iRow != iRow) break; } // count the number of items for(n=0,k=i ; k < j ; k++ ) { pitem = Tab_FastGetItemPtr(ptc, k); if (!(pitem->dwState & TCIS_HIDDEN)) n++; } // how much to fill hspace = ptc->cxTabs - Tab_FastGetItemPtr(ptc, j-1)->rc.right - g_cxEdge; dwidth = hspace/n; // amount to increase each by. dremainder = hspace % n; // the remnants moved = 0; // how much we've moved already for( ; i < j ; i++ ) { int iHalf = dwidth/2; pitem = Tab_FastGetItemPtr(ptc, i); if (!(pitem->dwState & TCIS_HIDDEN)) { pitem->rc.left += moved; pitem->xLabel += iHalf; pitem->xImage += iHalf; moved += dwidth + (dremainder ? 1 : 0); if ( dremainder ) dremainder--; pitem->rc.right += moved; } } i--; //dec because the outter forloop incs again. } } BOOL Tab_OnDeleteAllItems(PTC ptc) { int i; for (i = Tab_Count(ptc); i-- > 0; i) { if(ptc->hwndToolTips) { TOOLINFO ti; ti.cbSize = sizeof(ti); ti.hwnd = ptc->ci.hwnd; ti.uId = i; SendMessage(ptc->hwndToolTips, TTM_DELTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } Tab_FreeItem(ptc, Tab_FastGetItemPtr(ptc, i)); } DPA_DeleteAllPtrs(ptc->hdpa); ptc->cxItem = RECOMPUTE; // force recomputing of all tabs ptc->iSel = -1; ptc->iFirstVisible = 0; RedrawAll(ptc, RDW_INVALIDATE | RDW_ERASE); return TRUE; } BOOL Tab_OnSetItemExtra(PTC ptc, int cbExtra) { if (Tab_Count(ptc) >0 || cbExtra<0) return FALSE; ptc->cbExtra = cbExtra; return TRUE; } BOOL Tab_OnSetItem(PTC ptc, int iItem, const TC_ITEM* ptci) { TABITEM* pitem; UINT mask; BOOL fChanged = FALSE; BOOL fFullRedraw = FALSE; mask = ptci->mask; if (!mask) return TRUE; pitem = Tab_GetItemPtr(ptc, iItem); if (!pitem) return FALSE; if (mask & TCIF_TEXT) { if (!Str_Set(&pitem->pszText, ptci->pszText)) return FALSE; fFullRedraw = TRUE; fChanged = TRUE; pitem->etoRtlReading = (mask & TCIF_RTLREADING) ?ETO_RTLREADING :0; } if (mask & TCIF_IMAGE) { if (pitem->iImage == -1 || ptci->iImage == -1) { // went from no image to image... or vice versa // means needs full redraw fFullRedraw = TRUE; } pitem->iImage = ptci->iImage; fChanged = TRUE; } if ((mask & TCIF_PARAM) && ptc->cbExtra) { hmemcpy(pitem->DUMMYUNION_MEMBER(abExtra), &ptci->lParam, ptc->cbExtra); } if (mask & TCIF_STATE) { DWORD dwOldState = pitem->dwState; pitem->dwState = (ptci->dwState & ptci->dwStateMask) | (pitem->dwState & ~ptci->dwStateMask); if (dwOldState != pitem->dwState) fChanged = TRUE; if ((dwOldState ^ pitem->dwState) & TCIS_HIDDEN) fFullRedraw = TRUE; if ((ptci->dwStateMask & TCIS_BUTTONPRESSED) && !(ptci->dwState & TCIS_BUTTONPRESSED)) { // if they turned OFF being pushed and we were pushed because of // selection, nuke it now. if (ptc->iNewSel == iItem) { ptc->iNewSel = -1; fChanged = TRUE; } if (ptc->iSel == iItem) { ChangeSel(ptc, -1, TRUE, FALSE); fChanged = TRUE; } } } if (fChanged) { if (Tab_FixedWidth(ptc) || !fFullRedraw) { Tab_InvalidateItem(ptc, iItem, FALSE); } else { ptc->cxItem = ptc->cyTabs = RECOMPUTE; RedrawAll(ptc, RDW_INVALIDATE | RDW_NOCHILDREN | RDW_ERASE); } } return TRUE; } void Tab_OnMouseMove(PTC ptc, WPARAM fwKeys, int x, int y) { POINT pt; int iHit; pt.x=x; pt.y=y; iHit = Tab_OnHitTest(ptc, x, y, NULL); if (Tab_HotTrack(ptc)) { if (iHit != ptc->iHot) { Tab_InvalidateItem(ptc, iHit, FALSE); Tab_InvalidateItem(ptc, ptc->iHot, FALSE); ptc->iHot = iHit; } } if (fwKeys & MK_LBUTTON && Tab_DrawButtons(ptc)) { UINT uFlags; if (ptc->iNewSel == -1) return; if (iHit == ptc->iNewSel) { uFlags = TCF_DRAWSUNKEN; } else { uFlags = 0; } if ((ptc->flags & TCF_DRAWSUNKEN) != uFlags) { // the bit isn't what it should be ptc->flags ^= TCF_DRAWSUNKEN; // we need to invalidate on flat buttons because we go from one pixes to 2 pixel edge Tab_InvalidateItem(ptc, ptc->iNewSel, Tab_FlatButtons(ptc)); } } } void Tab_OnButtonUp(PTC ptc, int x, int y, BOOL fNotify) { BOOL fAllow = TRUE; if (fNotify) { // pass NULL for parent because W95 queryied each time and some // folks reparent fAllow = !SendNotifyEx(NULL, ptc->ci.hwnd, NM_CLICK, NULL, ptc->ci.bUnicode); } if (Tab_DrawSunken(ptc)) { // nothing selected (its empty) // only do this if something is selected... // otherwise we still do need to go below and release capture though if (ptc->iNewSel != -1) { if (Tab_OnHitTest(ptc, x, y, NULL) == ptc->iNewSel) { int iNewSel = ptc->iNewSel; // use iNewSel instead of ptc->iNewSel because the SendNotify could have nuked us if (fAllow) ChangeSel(ptc, iNewSel, TRUE, BMOVECURSORONCLICK); Tab_InvalidateItem(ptc, iNewSel, FALSE); } else { Tab_InvalidateItem(ptc, ptc->iNewSel, FALSE); Tab_InvalidateItem(ptc, ptc->iNewSel, FALSE); } // the changsel forces an updatewindow, // but we might have a border to unpaint(because of the TCF_DRAWSUNKEN // so we do another invalidate with just redraw ptc->flags &= ~TCF_DRAWSUNKEN; ptc->iNewSel = -1; } } // don't worry about checking DrawButtons because TCF_MOUSEDOWN // wouldn't be set otherwise. if (ptc->flags & TCF_MOUSEDOWN) { int iOldSel = ptc->iNewSel; ptc->flags &= ~TCF_MOUSEDOWN; // do this before release to avoid reentry ptc->iNewSel = -1; Tab_InvalidateItem(ptc, iOldSel, FALSE); CCReleaseCapture(&ptc->ci); } } int Tab_OnHitTest(PTC ptc, int x, int y, UINT *lpuFlags) { int i; int iLast = Tab_Count(ptc); RECT rc; POINT pt; UINT uTemp; rc.left = rc.right = x; rc.top = rc.bottom = y; Tab_DVFlipRect(ptc, &rc); pt.x = rc.left; pt.y = rc.top; if (!lpuFlags) lpuFlags = &uTemp; for (i = 0; i < iLast; i++) { LPTABITEM pitem = Tab_FastGetItemPtr(ptc, i); if (PtInRect(&pitem->rc, pt)) { // x now needs to be in pitem coordinates x -= pitem->rc.left; *lpuFlags = TCHT_ONITEM; if (!Tab_OwnerDraw(ptc)) { if ((x > pitem->xLabel) && x < pitem->xLabel + pitem->cxLabel) { *lpuFlags = TCHT_ONITEMLABEL; } else if (HASIMAGE(ptc, pitem)) { int cxImage, cyImage; Tab_ImageList_GetIconSize(ptc, &cxImage, &cyImage); if ((x > pitem->xImage) && (x < (pitem->xImage + cxImage))) *lpuFlags = TCHT_ONITEMICON; } } return i; } } *lpuFlags = TCHT_NOWHERE; return -1; } void Tab_DeselectAll(PTC ptc, BOOL fExcludeFocus) { int iMax = Tab_Count(ptc) - 1; int i; if (Tab_DrawButtons(ptc)) { for (i = iMax; i >= 0; i--) { LPTABITEM pitem; pitem = Tab_FastGetItemPtr(ptc, i); if (!fExcludeFocus || (pitem->dwState & TCIS_BUTTONPRESSED)) { TCITEM tci; tci.mask = TCIF_STATE; tci.dwStateMask = TCIS_BUTTONPRESSED; tci.dwState = 0; Tab_OnSetItem(ptc, i, &tci); } } } } void Tab_OnRButtonDown(PTC ptc, int x, int y, WPARAM keyFlags) { int i; int iOldSel = -1; if (Tab_Vertical(ptc)) { if (y > ptc->cxTabs) return; } else { if (x > ptc->cxTabs) return; // outside the range of the visible tabs } i = Tab_OnHitTest(ptc, x,y, NULL); // we don't swap x,y here because OnHitTest will if (i != -1) { if (Tab_DrawButtons(ptc) && Tab_MultiSelect(ptc)) { TCITEM tci; tci.mask = TCIF_STATE; tci.dwStateMask = TCIS_BUTTONPRESSED; Tab_OnGetItem(ptc, i, &tci); // as with the listview, don't deselect anything on right button if (!(tci.dwState & TCIS_BUTTONPRESSED)) { if (!(GetAsyncKeyState(VK_CONTROL) < 0)) { Tab_DeselectAll(ptc, FALSE); } // just toggle the pushed state. tci.dwState = TCIS_BUTTONPRESSED; Tab_OnSetItem(ptc, i, &tci); } } } } void Tab_OnLButtonDown(PTC ptc, int x, int y, WPARAM keyFlags) { int i; int iOldSel = -1; if (Tab_Vertical(ptc)) { if (y > ptc->cxTabs) return; } else { if (x > ptc->cxTabs) return; // outside the range of the visible tabs } i = Tab_OnHitTest(ptc, x,y, NULL); // we don't swap x,y here because OnHitTest will if (i != -1) { if (Tab_MultiSelect(ptc) && (GetAsyncKeyState(VK_CONTROL) < 0) && Tab_DrawButtons(ptc) ) { // just toggle the pushed state. TCITEM tci; tci.mask = TCIF_STATE; tci.dwStateMask = TCIS_BUTTONPRESSED; Tab_OnGetItem(ptc, i, &tci); tci.dwState ^= TCIS_BUTTONPRESSED; Tab_OnSetItem(ptc, i, &tci); } else { iOldSel = ptc->iSel; if ((!Tab_FocusNever(ptc)) && Tab_FocusOnButtonDown(ptc)) { SetFocus(ptc->ci.hwnd); } if (Tab_DrawButtons(ptc)) { ptc->iNewSel = i; ptc->flags |= (TCF_DRAWSUNKEN|TCF_MOUSEDOWN); SetCapture(ptc->ci.hwnd); // we need to invalidate on flat buttons because we go from one pixes to 2 pixel edge Tab_InvalidateItem(ptc, i, Tab_FlatButtons(ptc)); } else { iOldSel = ChangeSel(ptc, i, TRUE, BMOVECURSORONCLICK); } } } if ((!Tab_FocusNever(ptc)) && (iOldSel == i)) // reselect current selection // this also catches i == -1 because iOldSel started as -1 { SetFocus(ptc->ci.hwnd); UpdateWindow(ptc->ci.hwnd); } } TABITEM* Tab_CreateItem(PTC ptc, const TC_ITEM* ptci) { TABITEM* pitem; if (pitem = Alloc(sizeof(TABITEM)-sizeof(LPARAM)+ptc->cbExtra)) { if (ptci->mask & TCIF_IMAGE) pitem->iImage = ptci->iImage; else pitem->iImage = -1; pitem->xLabel = pitem->yLabel = RECOMPUTE; // If specified, copy extra block of memory. if (ptci->mask & TCIF_PARAM) { if (ptc->cbExtra) { hmemcpy(pitem->DUMMYUNION_MEMBER(abExtra), &ptci->lParam, ptc->cbExtra); } } if (ptci->mask & TCIF_TEXT) { if (!Str_Set(&pitem->pszText, ptci->pszText)) { Tab_FreeItem(ptc, pitem); return NULL; } pitem->etoRtlReading = (ptci->mask & TCIF_RTLREADING) ?ETO_RTLREADING :0; } } return pitem; } void Tab_UpdateArrows(PTC ptc, BOOL fSizeChanged) { RECT rc; BOOL fArrow; Tab_GetClientRect(ptc, &rc); if (IsRectEmpty(&rc)) return; // Nothing to do yet! // See if all of the tabs will fit. ptc->cxTabs = rc.right; // Assume can use whole area to paint if (Tab_MultiLine(ptc)) fArrow = FALSE; else { Tab_CalcPaintMetrics(ptc, NULL); fArrow = (ptc->cxItem >= rc.right); } if (!fArrow) { NoArrows: // Don't need arrows if (ptc->hwndArrows) { ShowWindow(ptc->hwndArrows, SW_HIDE); // Bug#94368:: This is overkill should only invalidate portion // that may be impacted, like the last displayed item.. InvalidateRect(ptc->ci.hwnd, NULL, TRUE); } if (ptc->iFirstVisible > 0) { Tab_OnHScroll(ptc, NULL, SB_THUMBPOSITION, 0); // Bug#94368:: This is overkill should only invalidate portion // that may be impacted, like the last displayed item.. InvalidateRect(ptc->ci.hwnd, NULL, TRUE); } } else { int cx; int cy; int iMaxBtnVal; int xSum; TABITEM * pitem; cy = ptc->cxyArrows; cx = cy * 2; ptc->cxTabs = rc.right - cx; // Make buttons square // See how many tabs we have to remove until the last tab becomes // fully visible. xSum = 0; // Number of pixels in removed tabs for (iMaxBtnVal=0; (ptc->cxTabs + xSum) < ptc->cxItem; iMaxBtnVal++) { pitem = Tab_GetItemPtr(ptc, iMaxBtnVal); if (!pitem) break; xSum += pitem->rc.right - pitem->rc.left; } // If we removed *all* the tabs, then put the last one back. // This happens if the last tab is so huge it doesn't fit into // the requisite space no matter how many tabs you remove. if (iMaxBtnVal >= Tab_Count(ptc)) { iMaxBtnVal = Tab_Count(ptc) - 1; } // If we don't need to remove any tabs, then we guessed wrong about // arrows. This can happen if there is exactly one tab that doesn't // fit in the requisite space. No arrow since there is nothing to // scroll to! // if (iMaxBtnVal <= 0) { ptc->cxTabs = rc.right; // Can use whole area to paint goto NoArrows; } if (!ptc->hwndArrows) { InvalidateRect(ptc->ci.hwnd, NULL, TRUE); ptc->hwndArrows = CreateUpDownControl (Tab_Vertical(ptc) ? (HDS_VERT | WS_CHILD) : (UDS_HORZ | WS_CHILD), 0, 0, 0, 0, ptc->ci.hwnd, 1, HINST_THISDLL, NULL, iMaxBtnVal, 0, ptc->iFirstVisible); } // DebugMsg(DM_TRACE, TEXT("Tabs_UpdateArrows iMax=%d\n\r"), iMaxBtnVal); if (ptc->hwndArrows) { rc.left = rc.right - cx; rc.top = ptc->cyTabs - cy; rc.bottom = ptc->cyTabs; Tab_VDFlipRect(ptc, &rc); if (fSizeChanged || !IsWindowVisible(ptc->hwndArrows)) SetWindowPos(ptc->hwndArrows, NULL, rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc), SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW); // Make sure the range is set SendMessage(ptc->hwndArrows, UDM_SETRANGE, 0, MAKELPARAM(iMaxBtnVal, 0)); } } } int Tab_OnInsertItem(PTC ptc, int iItem, const TC_ITEM* ptci) { TABITEM* pitem; int i; pitem = Tab_CreateItem(ptc, ptci); if (!pitem) return -1; i = iItem; i = DPA_InsertPtr(ptc->hdpa, i, pitem); if (i == -1) { Tab_FreeItem(ptc, pitem); return -1; } if (ptc->iSel < 0) ptc->iSel = i; else if (ptc->iSel >= i) ptc->iSel++; if (ptc->iFirstVisible > i) ptc->iFirstVisible++; ptc->cxItem = RECOMPUTE; // force recomputing of all tabs //Add tab to tooltips.. calculate the rect later if(ptc->hwndToolTips) { TOOLINFO ti; // don't bother setting the rect because we'll do it below // in FlushToolTipsMgr; ti.cbSize = sizeof(ti); ti.uFlags = ptci->mask & TCIF_RTLREADING ?TTF_RTLREADING :0; ti.hwnd = ptc->ci.hwnd; ti.uId = Tab_Count(ptc) - 1 ; ti.lpszText = LPSTR_TEXTCALLBACK; SendMessage(ptc->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } if (Tab_RedrawEnabled(ptc)) { RECT rcInval; LPTABITEM pitem; if (Tab_DrawButtons(ptc)) { if (Tab_FixedWidth(ptc)) { Tab_CalcPaintMetrics(ptc, NULL); if (i == Tab_Count(ptc) - 1) { Tab_InvalidateItem(ptc, i, FALSE); } else { pitem = Tab_GetItemPtr(ptc, i); GetClientRect(ptc->ci.hwnd, &rcInval); if (pitem) { rcInval.top = pitem->rc.top; if (ptc->iLastRow == 0) { rcInval.left = pitem->rc.left; } Tab_UpdateArrows(ptc, FALSE); RedrawWindow(ptc->ci.hwnd, &rcInval, NULL, RDW_INVALIDATE |RDW_NOCHILDREN); } } NotifyWinEvent(EVENT_OBJECT_CREATE, ptc->ci.hwnd, OBJID_CLIENT, i+1); return i; } } else { // in tab mode Clear the selected item because it may move // and it sticks high a bit. if (ptc->iSel > i) { // update now because invalidate erases // and the redraw below doesn't. Tab_InvalidateItem(ptc, ptc->iSel, TRUE); UpdateWindow(ptc->ci.hwnd); } } RedrawAll(ptc, RDW_INVALIDATE | RDW_NOCHILDREN); } NotifyWinEvent(EVENT_OBJECT_CREATE, ptc->ci.hwnd, OBJID_CLIENT, i+1); return i; } // Add/remove/replace item BOOL Tab_FreeItem(PTC ptc, TABITEM* pitem) { if (pitem) { Str_Set(&pitem->pszText, NULL); Free(pitem); } return FALSE; } void Tab_OnRemoveImage(PTC ptc, int iItem) { if (ptc->himl && iItem >= 0) { int i; LPTABITEM pitem; ImageList_Remove(ptc->himl, iItem); for( i = Tab_Count(ptc)-1 ; i >= 0; i-- ) { pitem = Tab_FastGetItemPtr(ptc, i); if (pitem->iImage > iItem) pitem->iImage--; else if (pitem->iImage == iItem) { pitem->iImage = -1; // if we now don't draw something, inval Tab_InvalidateItem(ptc, i, FALSE); } } } } BOOL Tab_OnDeleteItem(PTC ptc, int i) { TABITEM* pitem; UINT uRedraw; RECT rcInval; rcInval.left = -1; // special flag... if (i >= Tab_Count(ptc)) return FALSE; NotifyWinEvent(EVENT_OBJECT_DESTROY, ptc->ci.hwnd, OBJID_CLIENT, i+1); if (!Tab_DrawButtons(ptc) && (Tab_RedrawEnabled(ptc) || ptc->iSel >= i)) { // in tab mode, Clear the selected item because it may move // and it sticks high a bit. Tab_InvalidateItem(ptc, ptc->iSel, TRUE); } // if its fixed width, don't need to erase everything, just the last one if (Tab_FixedWidth(ptc)) { int j; uRedraw = RDW_INVALIDATE | RDW_NOCHILDREN; j = Tab_Count(ptc) -1; Tab_InvalidateItem(ptc, j, TRUE); // update optimization if (Tab_DrawButtons(ptc)) { if (i == Tab_Count(ptc) - 1) { rcInval.left = 0; uRedraw = 0; } else { pitem = Tab_GetItemPtr(ptc, i); GetClientRect(ptc->ci.hwnd, &rcInval); if (pitem) { rcInval.top = pitem->rc.top; if (ptc->iLastRow == 0) { rcInval.left = pitem->rc.left; } } } } } else { uRedraw = RDW_INVALIDATE | RDW_NOCHILDREN | RDW_ERASE; } pitem = DPA_DeletePtr(ptc->hdpa, i); if (!pitem) return FALSE; Tab_FreeItem(ptc, pitem); if (ptc->iSel == i) ptc->iSel = -1; // deleted the focus item else if (ptc->iSel > i) ptc->iSel--; // slide the foucs index down // maintain the first visible if (ptc->iFirstVisible > i) ptc->iFirstVisible--; ptc->cxItem = RECOMPUTE; // force recomputing of all tabs ptc->iLastTopRow = -1; if(ptc->hwndToolTips) { TOOLINFO ti; ti.cbSize = sizeof(ti); ti.hwnd = ptc->ci.hwnd; ti.uId = Tab_Count(ptc) ; SendMessage(ptc->hwndToolTips, TTM_DELTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } if (Tab_RedrawEnabled(ptc)) { if (rcInval.left == -1) { RedrawAll(ptc, uRedraw); } else { Tab_UpdateArrows(ptc, FALSE); if (uRedraw) RedrawWindow(ptc->ci.hwnd, &rcInval, NULL, uRedraw); } } return TRUE; } BOOL Tab_OnGetItem(PTC ptc, int iItem, TC_ITEM* ptci) { UINT mask = ptci->mask; const TABITEM* pitem = Tab_GetItemPtr(ptc, iItem); if (!pitem) { // NULL init the the tci struct incase there is no pitem. // This is incase the dude calling doesn't check the return // from this function. Bug # 7105 if (mask & TCIF_PARAM) ptci->lParam = 0; else if (mask & TCIF_TEXT) ptci->pszText = 0; else if (mask & TCIF_IMAGE) ptci->iImage = 0; return FALSE; } if (mask & TCIF_TEXT) { if (pitem->pszText) lstrcpyn(ptci->pszText, pitem->pszText, ptci->cchTextMax); else ptci->pszText = 0; } if (mask & TCIF_STATE) { ptci->dwState = pitem->dwState & ptci->dwStateMask; // REViEW... maybe we should maintain the state in the statemask... if (ptci->dwStateMask & TCIS_BUTTONPRESSED) { if ((ptc->iSel == iItem) || ((ptc->iNewSel == iItem) && Tab_DrawSunken(ptc))) { ptci->dwState |= TCIS_BUTTONPRESSED; } } } if ((mask & TCIF_PARAM) && ptc->cbExtra) hmemcpy(&ptci->lParam, pitem->DUMMYUNION_MEMBER(abExtra), ptc->cbExtra); if (mask & TCIF_IMAGE) ptci->iImage = pitem->iImage; // TC_ITEM does not have room for querying TCIF_RTLREADING !! // it only allows you to set it. // This is a hack to return info about tab item reading order if((mask & TCIF_RTLREADING) && !(mask & TCIF_TEXT)) { if(pitem->etoRtlReading) ptci->cchTextMax = 1; } return TRUE; } void Tab_InvalidateItem(PTC ptc, int iItem, BOOL bErase) { if (iItem != -1) { LPTABITEM pitem = Tab_GetItemPtr(ptc, iItem); if (pitem) { RECT rc = pitem->rc; if (rc.right > ptc->cxTabs) rc.right = ptc->cxTabs; // don't invalidate past our end InflateRect(&rc, g_cxEdge, g_cyEdge); if (Tab_FlatButtons(ptc)) { rc.right += 2 * g_cxEdge; } Tab_InvalidateRect(ptc, &rc, bErase); } } } BOOL RedrawAll(PTC ptc, UINT uFlags) { if (ptc && Tab_RedrawEnabled(ptc)) { Tab_UpdateArrows(ptc, FALSE); RedrawWindow(ptc->ci.hwnd, NULL, NULL, uFlags); return TRUE; } return FALSE; } int ChangeSel(PTC ptc, int iNewSel, BOOL bSendNotify, BOOL bUpdateCursorPos) { BOOL bErase; int iOldSel; HWND hwnd; SIZE screenDelta; RECT rcT; if (iNewSel == ptc->iSel) return ptc->iSel; if (bUpdateCursorPos && Tab_OnGetItemRect(ptc, iNewSel, &rcT)) { screenDelta.cx = rcT.left; screenDelta.cy = rcT.top; } else { screenDelta.cx = screenDelta.cy = 0; bUpdateCursorPos = FALSE; } hwnd = ptc->ci.hwnd; // make sure in range if (iNewSel < 0) { iOldSel = ptc->iSel; ptc->iSel = -1; } else if (iNewSel < Tab_Count(ptc)) { LPTABITEM pitem = Tab_GetItemPtr(ptc, iNewSel); ASSERT(pitem); if (!pitem) return -1; // // dont allow a hidden item to get the focus // // Bug#94368 this is not 100% correct, focus will only // work right if hidden items are at the begining // or end (user will not be able to arrow past it) // // currenly this is not a bad restriction // only desk.cpl uses this flag, and it // always hides the last item. // // if we make this a general flag we will need to // fix this. // if (pitem->dwState & TCIS_HIDDEN) return -1; // make sure this is a change that's wanted if (bSendNotify) { // pass NULL for parent because W95 queryied each time and some // folks reparent if (SendNotifyEx(NULL, hwnd, TCN_SELCHANGING, NULL, ptc->ci.bUnicode)) return ptc->iSel; } iOldSel = ptc->iSel; ptc->iSel = iNewSel; // See if we need to make sure the item is visible if (Tab_MultiLine(ptc)) { if( !Tab_DrawButtons(ptc) && ptc->iLastRow > 0 && iNewSel != -1) { // In multiLineTab Mode bring the row to the bottom. PutzRowToBottom(ptc, Tab_FastGetItemPtr(ptc, iNewSel)->iRow); } } else { // In single line mode, slide things over to show selection RECT rcClient; int xOffset = 0; int iNewFirstVisible = 0; GetClientRect(ptc->ci.hwnd, &rcClient); if (pitem->rc.left < g_cxEdge) { xOffset = -pitem->rc.left + g_cxEdge; // Offset to get back to zero iNewFirstVisible = iNewSel; } else if ((iNewSel != ptc->iFirstVisible) && (pitem->rc.right > ptc->cxTabs)) { // A little more tricky new to scroll each tab until we // fit on the end for (iNewFirstVisible = ptc->iFirstVisible; iNewFirstVisible < iNewSel;) { LPTABITEM pitemT = Tab_FastGetItemPtr(ptc, iNewFirstVisible); xOffset -= (pitemT->rc.right - pitemT->rc.left); iNewFirstVisible++; if ((pitem->rc.right + xOffset) < ptc->cxTabs) break; // Found our new top index } // If we end up being the first item shown make sure our left // end is showing correctly if (iNewFirstVisible == iNewSel) xOffset = -pitem->rc.left + g_cxEdge; } if (xOffset != 0) { Tab_Scroll(ptc, xOffset, iNewFirstVisible); } } } else return -1; Tab_DeselectAll(ptc, TRUE); // repaint opt: we don't need to erase for buttons because their paint covers all. bErase = (!Tab_DrawButtons(ptc) || Tab_FlatButtons(ptc)); if (bErase) UpdateWindow(hwnd); Tab_InvalidateItem(ptc, iOldSel, bErase); Tab_InvalidateItem(ptc, iNewSel, bErase); // mfc4.2 relies upon this update window. they do something that // forces the window invalid bit to be false on the TCN_SELCHANGE and // thereby making us lose this update window UpdateWindow(hwnd); if (bUpdateCursorPos && Tab_OnGetItemRect(ptc, iNewSel, &rcT)) { POINT ptCursor; screenDelta.cx = rcT.left - screenDelta.cx; screenDelta.cy = rcT.top - screenDelta.cy; GetCursorPos(&ptCursor); SetCursorPos(ptCursor.x + screenDelta.cx, ptCursor.y + screenDelta.cy); } // if they are buttons, we send the message on mouse up if (bSendNotify) { // pass NULL for parent because W95 queryied each time and some // folks reparent SendNotifyEx(NULL, hwnd, TCN_SELCHANGE, NULL, ptc->ci.bUnicode); } NotifyWinEvent(EVENT_OBJECT_SELECTION, hwnd, OBJID_CLIENT, ptc->iSel+1); // We might've been destroyed during the notify, but GetFocus // couldn't possibly return our hwnd in that case, so we're still safe. if (GetFocus() == hwnd) NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, ptc->iSel+1); return iOldSel; } // Tab_CalcTabHeight is theme aware void Tab_CalcTabHeight(PTC ptc, HDC hdc) { BOOL bReleaseDC = FALSE; if (ptc->cyTabs == RECOMPUTE) { TEXTMETRIC tm; int iYExtra; int cx = 0; int cy = 0; ZeroMemory(&tm, sizeof(TEXTMETRIC)); if (!hdc) { bReleaseDC = TRUE; hdc = GetDC(NULL); SelectObject(hdc, ptc->hfontLabel); } // Get metircs on theme font if (ptc->hTheme) { GetThemeTextMetrics(ptc->hTheme, hdc, ptc->iPartId, ptc->iStateId, &tm); } else { GetTextMetrics(hdc, &tm); } if (!ptc->fMinTabSet) { ptc->cxMinTab = tm.tmAveCharWidth * 6 + ptc->cxPad * 2; } ptc->cxyArrows = tm.tmHeight + 2 * g_cyEdge; if (ptc->himl) Tab_ImageList_GetIconSize(ptc, &cx, &cy); if (ptc->iTabHeight) { ptc->cyTabs = ptc->iTabHeight; if (Tab_DrawButtons(ptc)) iYExtra = 3 * g_cyEdge; // (for the top edge, button edge and room to drop down) else iYExtra = 2 * g_cyEdge - 1; } else { // the height is the max of image or label plus padding. // where padding is 2*cypad-edge but at lease an edges iYExtra = ptc->cyPad*2; if (iYExtra < 2*g_cyEdge) iYExtra = 2*g_cyEdge; if (!Tab_DrawButtons(ptc)) iYExtra -= (1 + g_cyEdge); // add an edge to the font height because we want a bit of // space under the text ptc->cyTabs = max(tm.tmHeight + g_cyEdge, cy) + iYExtra; } ptc->tmHeight = tm.tmHeight; // add one so that if it's odd, we'll round up. ptc->cyText = (ptc->cyTabs - iYExtra - tm.tmHeight + 1) / 2; ptc->cyIcon = (ptc->cyTabs - iYExtra - cy) / 2; if (bReleaseDC) { ReleaseDC(NULL, hdc); } } } void UpdateToolTipRects(PTC ptc) { if(ptc->hwndToolTips) { int i; TOOLINFO ti; int iMax; LPTABITEM pitem; ti.cbSize = sizeof(ti); ti.uFlags = 0; ti.hwnd = ptc->ci.hwnd; ti.lpszText = LPSTR_TEXTCALLBACK; for ( i = 0, iMax = Tab_Count(ptc); i < iMax; i++) { pitem = Tab_FastGetItemPtr(ptc, i); ti.uId = i; ti.rect = pitem->rc; Tab_VDFlipRect(ptc, &ti.rect); SendMessage(ptc->hwndToolTips, TTM_NEWTOOLRECT, 0, (LPARAM)((LPTOOLINFO)&ti)); } } } // Tab_GetTextExtentPoint is theme aware void Tab_GetTextExtentPoint(PTC ptc, HDC hdc, LPTSTR lpszText, int iCount, LPSIZE lpsize) { TCHAR szBuffer[128]; if (iCount < ARRAYSIZE(szBuffer) && !Tab_Vertical(ptc)) { StripAccelerators(lpszText, szBuffer, TRUE); lpszText = szBuffer; iCount = lstrlen(lpszText); } if (ptc->hTheme) { RECT rc = { 0 }; GetThemeTextExtent(ptc->hTheme, hdc, ptc->iPartId, ptc->iStateId, lpszText, iCount, 0, &rc, &rc); lpsize->cx = RECTWIDTH(rc); lpsize->cy = RECTHEIGHT(rc); } else { GetTextExtentPoint(hdc, lpszText, iCount, lpsize); } } void Tab_InvertRows(PTC ptc) { int i; int yTop = g_cyEdge; int yNew; int iNewRow; // we want the first item to be on the bottom. for (i = Tab_Count(ptc) - 1; i >= 0; i--) { LPTABITEM pitem = Tab_FastGetItemPtr(ptc, i); iNewRow = ptc->iLastRow - pitem->iRow; yNew = yTop + iNewRow * ptc->cyTabs; pitem->iRow = iNewRow; OffsetRect(&pitem->rc, 0, yNew - pitem->rc.top); } } // Tab_CalcPaintMetrics is theme aware void Tab_CalcPaintMetrics(PTC ptc, HDC hdc) { SIZE siz; LPTABITEM pitem; int i, x, y; int xStart; int iRow = 0; int cItems = Tab_Count(ptc); BOOL bReleaseDC = FALSE; if (ptc->cxItem == RECOMPUTE) { // if the font hasn't been created yet, let's do it now if (!ptc->hfontLabel) Tab_OnSetFont(ptc, NULL, FALSE); if (!hdc) { bReleaseDC = TRUE; hdc = GetDC(NULL); SelectObject(hdc, ptc->hfontLabel); } Tab_CalcTabHeight(ptc, hdc); if (Tab_DrawButtons(ptc)) { // start at the edge; xStart = 0; y = 0; } else { xStart = g_cxEdge; y = g_cyEdge; } x = xStart; for (i = 0; i < cItems; i++) { int cxImage = 0; int cy = 0; int cxBounds = 0; pitem = Tab_FastGetItemPtr(ptc, i); if (pitem->pszText) { Tab_GetTextExtentPoint(ptc, hdc, pitem->pszText, lstrlen(pitem->pszText), &siz); } else { siz.cx = 0; siz.cy = 0; } pitem->cxLabel = siz.cx; // if there's an image, count that too if (HASIMAGE(ptc, pitem)) { Tab_ImageList_GetIconSize(ptc, &cxImage, &cy); cxImage += ptc->cxPad; siz.cx += cxImage; } // If a theme is in use, inflate rect to accomidate full theme background (including margins) if (ptc->hTheme) { RECT rc = { 0, 0, siz.cx, siz.cy }; // Current content size GetThemeBackgroundExtent(ptc->hTheme, hdc, ptc->iPartId, ptc->iStateId, &rc, &rc); siz.cx = rc.right - rc.left; siz.cy = rc.bottom - rc.top; } cxBounds = siz.cx; if (Tab_FixedWidth(ptc)) { siz.cx = ptc->iTabWidth; } else { siz.cx += ptc->cxPad * 2; // Make sure the tab has a least a minimum width if (siz.cx < ptc->cxMinTab) siz.cx = ptc->cxMinTab; } // handle hidden items if (pitem->dwState & TCIS_HIDDEN) { siz.cx = 0; siz.cy = 0; } // should we wrap? if (Tab_MultiLine(ptc)) { // two cases to wrap around: // case 2: is our right edge past the end but we ourselves // are shorter than the width? // case 1: are we already past the end? (this happens if // the previous line had only one item and it was longer // than the tab's width. int iTotalWidth = ptc->cxTabs - g_cxEdge; if (x > iTotalWidth || (x+siz.cx >= iTotalWidth && (siz.cx < iTotalWidth))) { x = xStart; y += ptc->cyTabs; iRow++; if (Tab_DrawButtons(ptc)) y += ((g_cyEdge * 3)/2); } pitem->iRow = iRow; } pitem->rc.left = x; pitem->rc.right = x + siz.cx; pitem->rc.top = y; pitem->rc.bottom = ptc->cyTabs + y; if (!Tab_FixedWidth(ptc) || Tab_ForceLabelLeft(ptc) || Tab_ForceIconLeft(ptc)) { pitem->xImage = ptc->cxPad; } else { // in fixed width mode center it pitem->xImage = (siz.cx - cxBounds)/2; } if (pitem->xImage < g_cxEdge) pitem->xImage = g_cxEdge; if (Tab_ForceIconLeft(ptc)) { // Center the text in the space remaining after the icon // The math here gets kind of crazy so I'm going to draw // a picture. // // xImage // | // ->| |<- cxImage // +-----------------------------------------------+ // | @@@ text text text | // | @@@ | // +-----------------------------------------------+ // |<----------------- siz.cx -------------------->| // |<-magic->|<--cxLabel--->| // xLabel // // Therefore, // // remaining space = siz.cx - cxImage - xImage - cxLabel. // magic = remaining space / 2 // xLabel = xImage + cxImage + magic. // int cxImageTotal = pitem->xImage + cxImage; int cxRemaining = siz.cx - cxImageTotal - pitem->cxLabel; int cxMagic = cxRemaining / 2; pitem->xLabel = cxImageTotal + cxMagic; } else { // Place the text immediately after the icon pitem->xLabel = pitem->xImage + cxImage; } pitem->yImage = ptc->cyPad + ptc->cyIcon - (g_cyEdge/2); pitem->yLabel = ptc->cyPad + ptc->cyText - (g_cyEdge/2); x = pitem->rc.right; if (Tab_DrawButtons(ptc)) x += Tab_InterButtonGap(ptc); } ptc->cxItem = x; // total width of all tabs // if we added a line in non-button mode, we need to do a full refresh if (ptc->iLastRow != -1 && ptc->iLastRow != iRow && !Tab_DrawButtons(ptc)) { InvalidateRect(ptc->ci.hwnd, NULL, TRUE); } ptc->iLastRow = (cItems > 0) ? iRow : -1; if (Tab_MultiLine(ptc)) { if (!Tab_RaggedRight(ptc) && !Tab_FixedWidth(ptc)) RightJustify(ptc); if (Tab_ScrollOpposite(ptc)) { Tab_InvertRows(ptc); // if we have no selection, then the last row is the last top row if (ptc->iSel == -1) ptc->iLastTopRow = ptc->iLastRow; } if (!Tab_DrawButtons(ptc) && ptc->iSel != -1) { ptc->iLastTopRow = -1; PutzRowToBottom(ptc, Tab_FastGetItemPtr(ptc, ptc->iSel)->iRow); } } else if ( cItems > 0) { // adjust x's to the first visible int dx; pitem = Tab_GetItemPtr(ptc, ptc->iFirstVisible); if (pitem) { dx = -pitem->rc.left + g_cxEdge; for ( i = cItems - 1; i >=0 ; i--) { pitem = Tab_FastGetItemPtr(ptc, i); OffsetRect(&pitem->rc, dx, 0); } } } if (bReleaseDC) { ReleaseDC(NULL, hdc); } UpdateToolTipRects(ptc); } } // Tab_DoCorners is theme aware void Tab_DoCorners(HDC hdc, LPRECT prc, PTC ptc, BOOL fBottom) { RECT rc; COLORREF iOldColor; // Ignore for themes if (!ptc->hTheme) { iOldColor = SetBkColor(hdc, g_clrBtnFace); if (fBottom) { // lower right; rc = *prc; rc.left = rc.right - 2; rc.top = rc.bottom - 3; Tab_ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL, ptc); rc.bottom--; Tab_DrawEdge(hdc, &rc, EDGE_RAISED, BF_SOFT | BF_DIAGONAL_ENDBOTTOMLEFT, ptc); // lower left rc = *prc; rc.right = rc.left + 2; rc.top = rc.bottom - 3; Tab_ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL, ptc); rc.bottom--; Tab_DrawEdge(hdc, &rc, EDGE_RAISED, BF_SOFT | BF_DIAGONAL_ENDTOPLEFT, ptc); } else { // upper right rc = *prc; rc.left = rc.right - 2; rc.bottom = rc.top + 3; Tab_ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL, ptc); rc.top++; Tab_DrawEdge(hdc, &rc, EDGE_RAISED, BF_SOFT | BF_DIAGONAL_ENDBOTTOMRIGHT, ptc); // upper left rc = *prc; rc.right = rc.left + 2; rc.bottom = rc.top + 3; Tab_ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL, ptc); rc.top++; Tab_DrawEdge(hdc, &rc, EDGE_RAISED, BF_SOFT | BF_DIAGONAL_ENDTOPRIGHT, ptc); } } } void RefreshArrows(PTC ptc, HDC hdc) { RECT rcClip, rcArrows, rcIntersect; if (ptc->hwndArrows && IsWindowVisible(ptc->hwndArrows)) { GetClipBox(hdc, &rcClip); GetWindowRect(ptc->hwndArrows, &rcArrows); MapWindowRect(NULL, ptc->ci.hwnd, &rcArrows); if (IntersectRect(&rcIntersect, &rcClip, &rcArrows)) RedrawWindow(ptc->hwndArrows, NULL, NULL, RDW_INVALIDATE); } } // Tab_DrawBody is theme aware void Tab_DrawBody(HDC hdc, PTC ptc, LPTABITEM pitem, LPRECT lprc, int i, BOOL fTransparent, int dx, int dy) { BOOL fSelected = (i == ptc->iSel); if (i == ptc->iHot) { if ( !Tab_FlatButtons(ptc) ) { SetTextColor(hdc, GetSysColor(COLOR_HOTLIGHT)); } } if (Tab_OwnerDraw(ptc)) { DRAWITEMSTRUCT dis; WORD wID = (WORD) GetWindowID(ptc->ci.hwnd); dis.CtlType = ODT_TAB; dis.CtlID = wID; dis.itemID = i; dis.itemAction = ODA_DRAWENTIRE; if (fSelected) dis.itemState = ODS_SELECTED; else dis.itemState = 0; dis.hwndItem = ptc->ci.hwnd; dis.hDC = hdc; dis.rcItem = *lprc; Tab_VDFlipRect(ptc, &dis.rcItem); dis.itemData = (ptc->cbExtra <= sizeof(LPARAM)) ? (DWORD)pitem->DUMMYUNION_MEMBER(lParam) : (ULONG_PTR)(LPBYTE)&pitem->DUMMYUNION_MEMBER(abExtra); SendMessage( ptc->ci.hwndParent , WM_DRAWITEM, wID, (LPARAM)(DRAWITEMSTRUCT *)&dis); } else { // draw the text and image // draw even if pszText == NULL to blank it out int xLabel; int xIcon; BOOL fUseDrawText = FALSE; if (pitem->pszText) { // only use draw text if there's any underlining to do. // Draw text does not support vertical drawing, so only do this in horz mode if (!Tab_Vertical(ptc) && StrChr(pitem->pszText, CH_PREFIX)) { fUseDrawText = TRUE; } } // DrawTextEx will not clear the entire area, so we need to. // or if there's no text, we need to blank it out if ((fUseDrawText || !pitem->pszText) && !fTransparent) Tab_ExtTextOut(hdc, 0, 0, ETO_OPAQUE, lprc, NULL, 0, NULL, ptc); xLabel = pitem->rc.left + pitem->xLabel + dx; xIcon = pitem->rc.left + pitem->xImage + dx; if (pitem->pszText) { int xVertOffset = 0; int yOffset = 0; int oldMode; COLORREF oldBkColor; COLORREF oldTextColor; TEXTMETRIC tm; GetTextMetrics(hdc, &tm); if (tm.tmInternalLeading == 0) yOffset = 1; if (Tab_Vertical(ptc) && !Tab_Bottom(ptc)) { // add this offset because we need to draw from the bottom up xLabel += pitem->cxLabel; // if we're drawing vertically (on the left) // the icon needs to go below (flipped coordinate, on the right) if (HASIMAGE(ptc, pitem)) { int cxIcon = 0; int cyIcon = 0; int xLabelNew; Tab_ImageList_GetIconSize(ptc, &cxIcon, &cyIcon); xLabelNew = xIcon + pitem->cxLabel; xIcon = xLabel - cxIcon; xLabel = xLabelNew; } } if (pitem->dwState & TCIS_HIGHLIGHTED) { oldMode = SetBkMode (hdc, OPAQUE); oldBkColor = SetBkColor (hdc, g_clrHighlight); oldTextColor = SetTextColor (hdc, g_clrHighlightText); } if (fUseDrawText) { DRAWTEXTPARAMS dtp; int topPrev; dtp.cbSize = sizeof(DRAWTEXTPARAMS); dtp.iLeftMargin = xLabel - lprc->left; dtp.iRightMargin = 0; // There is no dtp.iTopMargin so we have to adjust the // rectangle instead. The opaqueing has already been done, // so isn't not a problem if we "miss" some pixels since // they've already been erased. topPrev = lprc->top; lprc->top = pitem->rc.top + pitem->yLabel + dy + yOffset; Tab_DrawTextEx(hdc, pitem->pszText, -1, lprc, DT_SINGLELINE | DT_TOP, &dtp, ptc); // Undo our changes to lprc before anybody (else) notices. lprc->top = topPrev; } else { UINT uETOFlags = (ETO_CLIPPED | pitem->etoRtlReading | (ptc->ci.dwExStyle & WS_EX_RTLREADING ? ETO_RTLREADING : 0 )); if (!fTransparent || (pitem->dwState & TCIS_HIGHLIGHTED)) uETOFlags |= ETO_OPAQUE; Tab_ExtTextOut(hdc, xLabel, pitem->rc.top + pitem->yLabel + dy + yOffset, uETOFlags, lprc, pitem->pszText, lstrlen(pitem->pszText), NULL, ptc); } if (pitem->dwState & TCIS_HIGHLIGHTED) { SetBkMode(hdc, oldMode); SetBkColor (hdc, oldBkColor); SetTextColor (hdc, oldTextColor); } } if (HASIMAGE(ptc, pitem)) { UINT uFlags = fTransparent ? ILD_TRANSPARENT : ILD_NORMAL; if (pitem->dwState & TCIS_HIGHLIGHTED) uFlags |= ILD_BLEND50; Tab_ImageList_Draw(ptc, pitem->iImage, hdc, xIcon, pitem->rc.top + pitem->yImage + dy, uFlags); } } if (i == ptc->iHot) { if ( !Tab_FlatButtons(ptc) ) { SetTextColor(hdc, g_clrBtnText); } } } // Tab_DrawItemFrame is theme aware void Tab_DrawItemFrame(PTC ptc, HDC hdc, UINT edgeType, LPTABITEM pitem, int i) { UINT uWhichEdges; BOOL fBottom = FALSE; if (Tab_DrawButtons(ptc)) { if (Tab_FlatButtons(ptc)) { if ((edgeType == EDGE_SUNKEN) || (edgeType == BDR_RAISEDINNER)) { uWhichEdges = BF_RECT; } else { if ((ptc->ci.style & TCS_HOTTRACK) && (i == ptc->iHot)) { edgeType = BDR_RAISEDINNER; uWhichEdges = BF_RECT; } else { HPEN hPen, hOldPen; RECT rcEdge; // Ignore for themes if (!ptc->hTheme) { CopyRect (&rcEdge, &pitem->rc); //InflateRect (&rcEdge, -g_cxEdge, -g_cyEdge); hPen = CreatePen (PS_SOLID, 2 * g_cyEdge, GetSysColor(COLOR_3DFACE)); hOldPen = SelectObject (hdc, hPen); // // Remove any border in the x direction // MoveToEx (hdc, rcEdge.left, rcEdge.top, NULL); LineTo (hdc, rcEdge.right, rcEdge.top); MoveToEx (hdc, rcEdge.left, rcEdge.bottom, NULL); LineTo (hdc, rcEdge.right, rcEdge.bottom); SelectObject (hdc, hOldPen); DeleteObject (hPen); // // Remove any border in the y direction // hPen = CreatePen (PS_SOLID, 2 * g_cxEdge, GetSysColor(COLOR_3DFACE)); hOldPen = SelectObject (hdc, hPen); MoveToEx (hdc, rcEdge.left, rcEdge.top, NULL); LineTo (hdc, rcEdge.left, rcEdge.bottom); MoveToEx (hdc, rcEdge.right, rcEdge.top, NULL); LineTo (hdc, rcEdge.right, rcEdge.bottom); SelectObject (hdc, hOldPen); DeleteObject (hPen); } goto DrawCorners; } } } else { uWhichEdges = BF_RECT | BF_SOFT; } } else { uWhichEdges = BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT; if (Tab_ScrollOpposite(ptc)) { ASSERT(ptc->iLastTopRow != -1); if (Tab_IsItemOnBottom(ptc, pitem)) { fBottom = TRUE; uWhichEdges = BF_LEFT | BF_BOTTOM | BF_RIGHT | BF_SOFT; } } } Tab_DrawEdge(hdc, &pitem->rc, edgeType, uWhichEdges, ptc); DrawCorners: if (!Tab_DrawButtons(ptc)) { Tab_DoCorners(hdc, &pitem->rc, ptc, fBottom); } else { if (Tab_FlatButtons(ptc) && Tab_FlatSeparators(ptc)) { RECT rcEdge; // Ignore in themes if (!ptc->hTheme) { CopyRect (&rcEdge, &pitem->rc); rcEdge.right += (3 * g_cxEdge); DrawEdge(hdc, &rcEdge, EDGE_ETCHED, BF_RIGHT); } } } } // Tab_Paint is theme aware (iPartId and iStateId are only set here) void Tab_Paint(PTC ptc, HDC hdcIn) { PAINTSTRUCT ps; HDC hdc; RECT rcClient, rcClipBox, rcTest, rcBody; int cItems, i; int fnNewMode = OPAQUE; LPTABITEM pitem; HWND hwnd = ptc->ci.hwnd; HBRUSH hbrOld = NULL; // Calling methods that render themes, setup state (TAB/BUTTON) if (ptc->hTheme) { ptc->iPartId = TABP_TABITEM; ptc->iStateId = TIS_NORMAL; } GetClientRect(hwnd, &rcClient); if (!rcClient.right) return; if (hdcIn) { hdc = hdcIn; ps.rcPaint = rcClient; } else { hdc = BeginPaint(hwnd, &ps); } // Fill background if themes are in use, WM_ERASEBKGND will be overridden to do nothing in this case if (ptc->hTheme) { if (CCSendPrint(&ptc->ci, hdc) == FALSE) { FillRect(hdc, &rcClient, g_hbrBtnFace); } } // select font first so metrics will have the right size if (!ptc->hfontLabel) Tab_OnSetFont(ptc, NULL, FALSE); SelectObject(hdc, ptc->hfontLabel); Tab_CalcPaintMetrics(ptc, hdc); // now put it in our native orientation if it was vertical Tab_DFlipRect(ptc, &rcClient); Tab_OnAdjustRect(ptc, FALSE, &rcClient); InflateRect(&rcClient, g_cxEdge * 2, g_cyEdge * 2); rcClient.top += g_cyEdge; // Draw pane (if applicable) if(!Tab_DrawButtons(ptc)) { DebugMsg(DM_TRACE, TEXT("Drawing at %d %d %d %d"), rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); // Calling a method that render themes, setup state (PANE) if (ptc->hTheme) { ptc->iPartId = TABP_PANE; ptc->iStateId = 0; } Tab_DrawEdge(hdc, &rcClient, EDGE_RAISED, BF_SOFT | BF_RECT, ptc); } // Draw tab items // Calling methods that render themes, setup state (TAB/BUTTON) if (ptc->hTheme) { ptc->iPartId = TABP_TABITEM; ptc->iStateId = TIS_NORMAL; } cItems = Tab_Count(ptc); if (cItems) { RefreshArrows(ptc, hdc); SetBkColor(hdc, g_clrBtnFace); SetTextColor(hdc, g_clrBtnText); if (!Tab_MultiLine(ptc)) IntersectClipRect(hdc, 0, 0, ptc->cxTabs, rcClient.bottom); GetClipBox(hdc, &rcClipBox); Tab_DVFlipRect(ptc, &rcClipBox); // draw all but the selected item for (i = ptc->iFirstVisible; i < cItems; i++) { // Calling methods that render themes, setup state (TAB/BUTTON, HOT state) if (ptc->hTheme) { ptc->iStateId = (i == ptc->iHot) ? TIS_HOT : TIS_NORMAL; } pitem = Tab_FastGetItemPtr(ptc, i); if (pitem->dwState & TCIS_HIDDEN) continue; if (!Tab_MultiLine(ptc)) { // if not multiline, and we're off the screen... we're done if (pitem->rc.left > ptc->cxTabs) break; } // should we bother drawing this? if (i != ptc->iSel || Tab_DrawButtons(ptc)) { if (IntersectRect(&rcTest, &rcClipBox, &pitem->rc)) { int dx = 0, dy = 0; // shift variables if button sunken; UINT edgeType; rcBody = pitem->rc; // Draw the edge around each item if(Tab_DrawButtons(ptc) && ((ptc->iNewSel == i && Tab_DrawSunken(ptc)) || (ptc->iSel == i) || (pitem->dwState & TCIS_BUTTONPRESSED))) { dx = g_cxEdge/2; dy = g_cyEdge/2; if (Tab_FlatButtons(ptc) && (ptc->iNewSel == i && Tab_DrawSunken(ptc))) { edgeType = BDR_RAISEDINNER; } else { edgeType = EDGE_SUNKEN; } } else { edgeType = EDGE_RAISED; } if (Tab_DrawButtons(ptc) && !Tab_OwnerDraw(ptc)) { // if drawing buttons, show selected by dithering background // which means we need to draw transparent. if (ptc->iSel == i) { // Calling methods that render themes, setup state (BUTTON, SELECTED state) if (ptc->hTheme) { ptc->iStateId = TIS_SELECTED; } fnNewMode = TRANSPARENT; SetBkMode(hdc, TRANSPARENT); hbrOld = SelectObject(hdc, g_hbrMonoDither); SetTextColor(hdc, g_clrBtnHighlight); Tab_PatBlt(hdc, pitem->rc.left, pitem->rc.top, pitem->rc.right - pitem->rc.left, pitem->rc.bottom - pitem->rc.top, PATCOPY, ptc); SetTextColor(hdc, g_clrBtnText); // Calling methods that render themes, setup state (TAB/BUTTON, HOT state) if (ptc->hTheme) { ptc->iStateId = (i == ptc->iHot) ? TIS_HOT : TIS_NORMAL; } } } InflateRect(&rcBody, -g_cxEdge, -g_cyEdge); if (!Tab_DrawButtons(ptc)) { // move the bottom (or top) by an edge to draw where the tab doesn't have an edge. // by doing this, we fill the entire area and don't need to do as many inval with erase if (Tab_IsItemOnBottom(ptc, pitem)) { rcBody.top -= g_cyEdge; } else { rcBody.bottom += g_cyEdge; } } // Draw background and content (for all items expect selected tab-style item) if (ptc->hTheme) { RECT rcc; GetClientRect(ptc->ci.hwnd, &rcc); // Determine what part is currently being rendered ptc->iPartId = TABP_TABITEM; if (pitem->rc.left == g_cxEdge) ptc->iPartId = TABP_TABITEMLEFTEDGE; // Ick: Aaron wants "slop" for determining the right tab edge. if ((pitem->rc.right >= (rcc.right - 2 * g_cxEdge)) || ((i + 1) == cItems)) ptc->iPartId = (ptc->iPartId == TABP_TABITEMLEFTEDGE) ? TABP_TABITEMBOTHEDGE : TABP_TABITEMRIGHTEDGE; if (pitem->rc.top == g_cyEdge) { switch (ptc->iPartId) { case TABP_TABITEM: ptc->iPartId = TABP_TOPTABITEM; break; case TABP_TABITEMLEFTEDGE: ptc->iPartId = TABP_TOPTABITEMLEFTEDGE; break; case TABP_TABITEMRIGHTEDGE: ptc->iPartId = TABP_TOPTABITEMRIGHTEDGE; break; case TABP_TABITEMBOTHEDGE: ptc->iPartId = TABP_TOPTABITEMBOTHEDGE; break; } } // Reverse order for themes // Edges Tab_DrawItemFrame(ptc, hdc, edgeType, pitem, i); // Content Tab_DrawBody(hdc, ptc, pitem, &rcBody, i, fnNewMode == TRANSPARENT, dx, dy); } else { // Content Tab_DrawBody(hdc, ptc, pitem, &rcBody, i, fnNewMode == TRANSPARENT, dx, dy); // Edges Tab_DrawItemFrame(ptc, hdc, edgeType, pitem, i); } if (fnNewMode == TRANSPARENT) { fnNewMode = OPAQUE; SelectObject(hdc, hbrOld); SetBkMode(hdc, OPAQUE); } } } } if (!Tab_MultiLine(ptc)) ptc->iLastVisible = i - 1; else ptc->iLastVisible = cItems - 1; // Calling methods that render themes, setup state (TAB, SELECTED state) if (ptc->hTheme) { ptc->iStateId = TIS_SELECTED; } // draw the selected one last to make sure it is on top pitem = Tab_GetItemPtr(ptc, ptc->iSel); if (pitem && (pitem->rc.left <= ptc->cxTabs)) { rcBody = pitem->rc; if (!Tab_DrawButtons(ptc)) { UINT uWhichEdges; InflateRect(&rcBody, g_cxEdge, g_cyEdge); if (IntersectRect(&rcTest, &rcClipBox, &rcBody)) { // Content if (ptc->hTheme) { RECT rcc; RECT rcBack = rcBody; GetClientRect(ptc->ci.hwnd, &rcc); // Determine what part is currently being rendered ptc->iPartId = TABP_TABITEM; if (pitem->rc.left == g_cxEdge) ptc->iPartId = TABP_TABITEMLEFTEDGE; if ((pitem->rc.right >= (rcc.right - 2 * g_cxEdge)) || ((i + 1) == cItems)) ptc->iPartId = (ptc->iPartId == TABP_TABITEMLEFTEDGE) ? TABP_TABITEMBOTHEDGE : TABP_TABITEMRIGHTEDGE; if (pitem->rc.top == g_cyEdge) { switch (ptc->iPartId) { case TABP_TABITEM: ptc->iPartId = TABP_TOPTABITEM; break; case TABP_TABITEMLEFTEDGE: ptc->iPartId = TABP_TOPTABITEMLEFTEDGE; break; case TABP_TABITEMRIGHTEDGE: ptc->iPartId = TABP_TOPTABITEMRIGHTEDGE; break; case TABP_TABITEMBOTHEDGE: ptc->iPartId = TABP_TOPTABITEMBOTHEDGE; break; } } Tab_DrawEdge(hdc, &rcBack, EDGE_RAISED, BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT, ptc); Tab_DrawBody(hdc, ptc, pitem, &rcBody, ptc->iSel, FALSE, 0,-g_cyEdge); } else { Tab_DrawBody(hdc, ptc, pitem, &rcBody, ptc->iSel, FALSE, 0,-g_cyEdge); rcBody.bottom--; //because of button softness Tab_DrawEdge(hdc, &rcBody, EDGE_RAISED, BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT, ptc); } // Edges Tab_DoCorners(hdc, &rcBody, ptc, FALSE); // draw that extra bit on the left or right side // if we're on the edge rcBody.bottom++; rcBody.top = rcBody.bottom-1; if (rcBody.right == rcClient.right) { uWhichEdges = BF_SOFT | BF_RIGHT; } else if (rcBody.left == rcClient.left) { uWhichEdges = BF_SOFT | BF_LEFT; } else { uWhichEdges = 0; } if (!ptc->hTheme) { if (uWhichEdges) Tab_DrawEdge(hdc, &rcBody, EDGE_RAISED, uWhichEdges, ptc); } } } } if (GetFocus() == hwnd) { if (!pitem && (ptc->iNewSel != -1)) { pitem = Tab_GetItemPtr(ptc, ptc->iNewSel); } if (pitem && !(CCGetUIState(&(ptc->ci))& UISF_HIDEFOCUS)) { rcBody = pitem->rc; if (Tab_DrawButtons(ptc)) InflateRect(&rcBody, -g_cxEdge, -g_cyEdge); else InflateRect(&rcBody, -(g_cxEdge/2), -(g_cyEdge/2)); Tab_DrawFocusRect(hdc, &rcBody, ptc); } } } if (hdcIn == NULL) EndPaint(hwnd, &ps); } int Tab_FindTab(PTC ptc, int iStart, UINT vk) { int iRow; int x; int i; LPTABITEM pitem = Tab_GetItemPtr(ptc, iStart); if (!pitem) { return(0); } iRow= pitem->iRow + ((vk == VK_UP) ? -1 : 1); x = (pitem->rc.right + pitem->rc.left) / 2; // find the and item on the iRow at horizontal x if (iRow > ptc->iLastRow || iRow < 0) return iStart; // this relies on the ordering of tabs from left to right , but // not necessarily top to bottom. for (i = Tab_Count(ptc) - 1 ; i >= 0; i--) { pitem = Tab_FastGetItemPtr(ptc, i); if (pitem->iRow == iRow) { if (pitem->rc.left < x) return i; } } // this should never happen.. we should have caught this case in the iRow check // right before the for loop. ASSERT(0); return iStart; } void Tab_SetCurFocus(PTC ptc, int iStart) { if (Tab_DrawButtons(ptc)) { if ((iStart >= 0) && (iStart < Tab_Count(ptc)) && (ptc->iNewSel != iStart)) { if (ptc->iNewSel != -1) Tab_InvalidateItem(ptc, ptc->iNewSel, FALSE); Tab_InvalidateItem(ptc, iStart, FALSE); ptc->iNewSel = iStart; ptc->flags |= TCF_DRAWSUNKEN; if (!Tab_MultiLine(ptc)) { // scroll into view if necessary RECT rc; do { Tab_OnGetItemRect(ptc, iStart, &rc); if (rc.right > ptc->cxTabs) { Tab_OnHScroll(ptc, NULL, SB_THUMBPOSITION, ptc->iFirstVisible + 1); } else if (rc.left < 0) { Tab_OnHScroll(ptc, NULL, SB_THUMBPOSITION, iStart); break; } else { break; } } while (1); } CCSendNotify(&ptc->ci, TCN_FOCUSCHANGE, NULL); NotifyWinEvent(EVENT_OBJECT_FOCUS, ptc->ci.hwnd, OBJID_CLIENT, iStart+1); } } else { int iOld = ptc->iSel; ChangeSel(ptc, iStart, TRUE, FALSE); if ((iOld != ptc->iSel) && (GetFocus() == ptc->ci.hwnd)) NotifyWinEvent(EVENT_OBJECT_FOCUS, ptc->ci.hwnd, OBJID_CLIENT, ptc->iSel+1); } } void Tab_OnKeyDown(PTC ptc, UINT vk, BOOL fDown, int cRepeat, UINT flags) { int iStart; TC_KEYDOWN nm; // Notify nm.wVKey = (WORD) vk; nm.flags = flags; // pass NULL for parent because W95 queryied each time and some // folks reparent SendNotifyEx(NULL, ptc->ci.hwnd, TCN_KEYDOWN, &nm.hdr, ptc->ci.bUnicode); if (Tab_DrawButtons(ptc)) { ptc->flags |= (TCF_DRAWSUNKEN|TCF_MOUSEDOWN); if (ptc->iNewSel != -1) { iStart = ptc->iNewSel; } else { iStart = ptc->iSel; } } else { iStart = ptc->iSel; } vk = RTLSwapLeftRightArrows(&ptc->ci, vk); if (Tab_Vertical(ptc)) { // remap arrow keys if we're in vertial mode switch(vk) { case VK_LEFT: vk = VK_DOWN; break; case VK_RIGHT: vk = VK_UP; break; case VK_DOWN: vk = VK_RIGHT; break; case VK_UP: vk = VK_LEFT; break; } } switch (vk) { case VK_LEFT: iStart--; break; case VK_RIGHT: iStart++; break; case VK_UP: case VK_DOWN: if (iStart != -1) { iStart = Tab_FindTab(ptc, iStart, vk); break; } // else fall through to set iStart = 0; case VK_HOME: iStart = 0; break; case VK_END: iStart = Tab_Count(ptc) - 1; break; case VK_SPACE: if (!Tab_DrawButtons(ptc)) return; // else fall through... in button mode space does selection case VK_RETURN: ChangeSel(ptc, iStart, TRUE, FALSE); ptc->iNewSel = -1; ptc->flags &= ~TCF_DRAWSUNKEN; //notify of navigation key usage CCNotifyNavigationKeyUsage(&(ptc->ci), UISF_HIDEFOCUS | UISF_HIDEACCEL); return; default: return; } if (iStart < 0) iStart = 0; Tab_SetCurFocus(ptc, iStart); //notify of navigation key usage CCNotifyNavigationKeyUsage(&(ptc->ci), UISF_HIDEFOCUS | UISF_HIDEACCEL); } void Tab_Size(PTC ptc) { ptc->cxItem = RECOMPUTE; Tab_UpdateArrows(ptc, TRUE); } BOOL Tab_OnGetItemRect(PTC ptc, int iItem, LPRECT lprc) { LPTABITEM pitem = Tab_GetItemPtr(ptc, iItem); BOOL fRet = FALSE; if (lprc) { Tab_CalcPaintMetrics(ptc, NULL); if (pitem) { // Make sure all the item rects are up-to-date *lprc = pitem->rc; fRet = TRUE; } else { lprc->top = 0; lprc->bottom = ptc->cyTabs; lprc->right = 0; lprc->left = 0; } Tab_VDFlipRect(ptc, lprc); } return fRet; } void Tab_StyleChanged(PTC ptc, UINT gwl, LPSTYLESTRUCT pinfo) { #define STYLE_MASK (TCS_BUTTONS | TCS_VERTICAL | TCS_MULTILINE | TCS_RAGGEDRIGHT | TCS_FIXEDWIDTH | TCS_FORCELABELLEFT | TCS_FORCEICONLEFT | TCS_BOTTOM | TCS_RIGHT | TCS_FLATBUTTONS | TCS_OWNERDRAWFIXED | TCS_HOTTRACK) if (ptc && (gwl == GWL_STYLE)) { DWORD dwChanged = (ptc->ci.style & STYLE_MASK) ^ (pinfo->styleNew & STYLE_MASK); // special case. this is "Insider Trading" app (by papyrus, now kanisa). they set the 3 on the low byte in ie3 comctl32 when it // had no meaning anyways. so we bail on that. if (ptc->ci.style == 0x50004000 && pinfo->styleNew == 0x54004003) return; if (dwChanged) { ptc->ci.style = (ptc->ci.style & ~STYLE_MASK) | (pinfo->styleNew & STYLE_MASK); // make sure we don't have invalid bits set if (!Tab_FixedWidth(ptc)) { ptc->ci.style &= ~(TCS_FORCEICONLEFT | TCS_FORCELABELLEFT); } ptc->cxItem = RECOMPUTE; ptc->cyTabs = RECOMPUTE; //if the left/right orientation changed // we need to re-create the font (if we own it) // becaus the text orientation needs to flip by 180 if ((dwChanged & TCS_VERTICAL) || ((dwChanged & TCS_RIGHT) && Tab_Vertical(ptc))) { if (!(ptc->flags & TCF_FONTSET)) Tab_OnSetFont(ptc, NULL, FALSE); } if (Tab_RedrawEnabled(ptc)) Tab_UpdateArrows(ptc, TRUE); RedrawAll(ptc, RDW_ERASE | RDW_INVALIDATE); } #define FOCUS_MASK (TCS_FOCUSONBUTTONDOWN | TCS_FOCUSNEVER) if ( (ptc->ci.style & FOCUS_MASK) ^ (pinfo->styleNew & FOCUS_MASK)) { ptc->ci.style = (ptc->ci.style & ~FOCUS_MASK) | (pinfo->styleNew & FOCUS_MASK); } } if (gwl == GWL_EXSTYLE) { ptc->ci.dwExStyle &= ~WS_EX_RTLREADING; ptc->ci.dwExStyle |= (pinfo->styleNew & WS_EX_RTLREADING); } } DWORD Tab_ExtendedStyleChange(PTC ptc, DWORD dwNewStyle, DWORD dwExMask) { DWORD dwOldStyle = ptc->dwStyleEx; if (ptc->hDragProxy) { DestroyDragProxy(ptc->hDragProxy); ptc->hDragProxy = NULL; } if (dwExMask) dwNewStyle = (ptc->dwStyleEx & ~ dwExMask) | (dwNewStyle & dwExMask); ptc->dwStyleEx = dwNewStyle; // do any invalidation or whatever is needed here. if ((dwOldStyle ^ dwNewStyle) & TCS_EX_FLATSEPARATORS) { InvalidateRect (ptc->ci.hwnd, NULL, TRUE); } if (ptc->dwStyleEx & TCS_EX_REGISTERDROP) ptc->hDragProxy = CreateDragProxy(ptc->ci.hwnd, TabDragCallback, TRUE); return dwOldStyle; } // // APPCOMPAT Assumes that the tab control is on top. Returns bogus values for // left, bottom or right. For app compat reasons, we can't change this // buggy behavior. (Apps might be relying on the wrong values and fixing them // up, so if we fix the function, they end up trying to "fix" something that // wasn't broken, thereby breaking it.) But we might want to add // TCM_ADJUSTRECT2 that can handle the left/right/bottom cases. // void Tab_OnAdjustRect(PTC ptc, BOOL fGrow, LPRECT prc) { int idy; Tab_CalcPaintMetrics(ptc, NULL); if (Tab_DrawButtons(ptc)) { if (Tab_Count(ptc)) { RECT rc; Tab_OnGetItemRect(ptc, Tab_Count(ptc) - 1, &rc); idy = rc.bottom; } else { idy = 0; } } else { idy = (ptc->cyTabs * (ptc->iLastRow + 1)); } if (fGrow) { // calc a larger rect from the smaller prc->top -= idy; InflateRect(prc, g_cxEdge * 2, g_cyEdge * 2); } else { prc->top += idy; // given the bounds, calc the "client" area InflateRect(prc, -g_cxEdge * 2, -g_cyEdge * 2); } if (Tab_ScrollOpposite(ptc)) { // the sizes are the same, it's just offset wrong vertically idy = ptc->cyTabs * (ptc->iLastRow - ptc->iLastTopRow); ASSERT(ptc->iLastTopRow != -1); if (!fGrow) { idy *= -1; } DebugMsg(DM_TRACE, TEXT("Tab_AdjustRect %d %d %d %d"), prc->left, prc->top, prc->right, prc->bottom); OffsetRect(prc, 0, idy); DebugMsg(DM_TRACE, TEXT("Tab_AdjustRect %d %d %d %d"), prc->left, prc->top, prc->right, prc->bottom); } } // Tab_WndProc is theme aware LRESULT CALLBACK Tab_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { PTC ptc = (PTC)GetWindowInt((hwnd), 0); if (ptc) { if ((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST) && Tab_HotTrack(ptc) && !ptc->fTrackSet) { TRACKMOUSEEVENT tme; ptc->fTrackSet = TRUE; tme.cbSize = sizeof(tme); tme.hwndTrack = ptc->ci.hwnd; tme.dwFlags = TME_LEAVE; TrackMouseEvent(&tme); } else { // Check for theme changes if (uMsg == WM_THEMECHANGED) { if (ptc->hTheme) CloseThemeData(ptc->hTheme); ptc->hTheme = (!Tab_OwnerDraw(ptc) && !Tab_DrawButtons(ptc)) ? OpenThemeData(ptc->ci.hwnd, L"Tab") : NULL; // Active hot state if themes are in use if (ptc->hTheme) ptc->ci.style |= TCS_HOTTRACK; else ptc->ci.style &= ~TCS_HOTTRACK; // Recompute metrics since font may have changed ptc->cxItem = RECOMPUTE; ptc->cyTabs = RECOMPUTE; InvalidateRect(ptc->ci.hwnd, NULL, TRUE); } } } else if (uMsg != WM_CREATE) goto DoDefault; switch (uMsg) { HANDLE_MSG(ptc, WM_HSCROLL, Tab_OnHScroll); case WM_MOUSELEAVE: Tab_InvalidateItem(ptc, ptc->iHot, FALSE); ptc->iHot = -1; ptc->fTrackSet = FALSE; break; case WM_CREATE: InitGlobalColors(); ptc = (PTC)NearAlloc(sizeof(TC)); if (!ptc) return -1; // fail the window create SetWindowPtr(hwnd, 0, ptc); CIInitialize(&ptc->ci, hwnd, (LPCREATESTRUCT)lParam); if (!Tab_OnCreate(ptc)) return -1; break; case WM_DESTROY: Tab_OnDestroy(ptc); break; case WM_SIZE: Tab_Size(ptc); break; case WM_SYSCOLORCHANGE: InitGlobalColors(); if (!(ptc->flags & TCF_FONTSET)) Tab_OnSetFont(ptc, NULL, FALSE); RedrawAll(ptc, RDW_INVALIDATE | RDW_ERASE); break; case WM_WININICHANGE: InitGlobalMetrics(wParam); if ((wParam == SPI_SETNONCLIENTMETRICS) || (!wParam && !lParam)) RedrawAll(ptc, RDW_INVALIDATE | RDW_ERASE); break; case WM_ERASEBKGND: // Background fill will happen in Tab_Paint if themes are active if (ptc->hTheme) return 1; goto DoDefault; case WM_PRINTCLIENT: case WM_PAINT: Tab_Paint(ptc, (HDC)wParam); break; case WM_STYLECHANGED: Tab_StyleChanged(ptc, (UINT) wParam, (LPSTYLESTRUCT)lParam); break; case WM_MOUSEMOVE: RelayToToolTips(ptc->hwndToolTips, hwnd, uMsg, wParam, lParam); Tab_OnMouseMove(ptc, wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); break; case WM_LBUTTONDOWN: RelayToToolTips(ptc->hwndToolTips, hwnd, uMsg, wParam, lParam); Tab_OnLButtonDown(ptc, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), wParam); break; case WM_LBUTTONDBLCLK: if (Tab_DrawButtons(ptc)) { MSG msg; // on the double click, grab capture until we get the lbutton up and // eat it. SetCapture(ptc->ci.hwnd); while (GetCapture() == ptc->ci.hwnd && !PeekMessage(&msg, ptc->ci.hwnd, WM_LBUTTONUP, WM_LBUTTONUP, PM_REMOVE)) { } CCReleaseCapture(&ptc->ci); } break; case WM_MBUTTONDOWN: SetFocus(hwnd); break; case WM_RBUTTONDOWN: Tab_OnRButtonDown(ptc, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), wParam); break; case WM_RBUTTONUP: // pass NULL for parent because W95 queryied each time and some // folks reparent if (!SendNotifyEx(NULL, ptc->ci.hwnd, NM_RCLICK, NULL, ptc->ci.bUnicode)) goto DoDefault; break; case WM_CAPTURECHANGED: lParam = -1L; // fall through to LBUTTONUP case WM_LBUTTONUP: if (uMsg == WM_LBUTTONUP) { RelayToToolTips(ptc->hwndToolTips, hwnd, uMsg, wParam, lParam); } Tab_OnButtonUp(ptc, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (uMsg == WM_LBUTTONUP)); break; case WM_SYSKEYDOWN: //notify of navigation key usage if (HIWORD(lParam) & KF_ALTDOWN) CCNotifyNavigationKeyUsage(&(ptc->ci), UISF_HIDEFOCUS | UISF_HIDEACCEL); goto DoDefault; case WM_KEYDOWN: HANDLE_WM_KEYDOWN(ptc, wParam, lParam, Tab_OnKeyDown); break; case WM_KILLFOCUS: if (ptc->iNewSel != -1) { int iOldSel = ptc->iNewSel; ptc->iNewSel = -1; Tab_InvalidateItem(ptc, iOldSel, FALSE); ptc->flags &= ~TCF_DRAWSUNKEN; } // fall through case WM_SETFOCUS: Tab_InvalidateItem(ptc, ptc->iSel, Tab_OwnerDraw(ptc)); if ((uMsg == WM_SETFOCUS) && (ptc->iSel != -1)) NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, ptc->iSel+1); break; case WM_GETDLGCODE: return DLGC_WANTARROWS | DLGC_WANTCHARS; HANDLE_MSG(ptc, WM_SETREDRAW, Tab_OnSetRedraw); HANDLE_MSG(ptc, WM_SETFONT, Tab_OnSetFont); case WM_GETFONT: return (LRESULT)ptc->hfontLabel; case WM_NOTIFYFORMAT: return CIHandleNotifyFormat(&ptc->ci, lParam); case WM_NOTIFY: { LPNMHDR lpNmhdr = (LPNMHDR)(lParam); // // We are just going to pass this on to the // real parent. Note that -1 is used as // the hwndFrom. This prevents SendNotifyEx // from updating the NMHDR structure. // SendNotifyEx(GetParent(ptc->ci.hwnd), (HWND) -1, lpNmhdr->code, lpNmhdr, ptc->ci.bUnicode); } break; case WM_UPDATEUISTATE: if (CCOnUIState(&(ptc->ci), WM_UPDATEUISTATE, wParam, lParam)) { if (UISF_HIDEFOCUS == HIWORD(wParam)) { // We erase only if we are removing the focus rect or the accel Tab_InvalidateItem(ptc, ptc->iSel, (UIS_CLEAR == LOWORD(wParam)) ? TRUE : FALSE); } else { if ((UISF_HIDEFOCUS | UISF_HIDEACCEL) & HIWORD(wParam)) { int i; for (i = ptc->iFirstVisible; i <= ptc->iLastVisible; ++i) { Tab_InvalidateItem(ptc, i, (UIS_CLEAR == LOWORD(wParam)) ? TRUE : FALSE); } } } } goto DoDefault; case TCM_SETITEMEXTRA: return (LRESULT)Tab_OnSetItemExtra(ptc, (int)wParam); case TCM_GETITEMCOUNT: return (LRESULT)Tab_Count(ptc); case TCM_SETITEMA: { LRESULT lResult; TC_ITEMW * pItemW; if (!lParam) { return FALSE; } pItemW = ThunkItemAtoW(ptc, (TC_ITEMA*)lParam); if (!pItemW) { return FALSE; } lResult = (LRESULT)Tab_OnSetItem(ptc, (int)wParam, pItemW); FreeItemW(pItemW); return lResult; } case TCM_SETITEM: if (!lParam) { return FALSE; } return (LRESULT)Tab_OnSetItem(ptc, (int)wParam, (const TC_ITEM*)lParam); case TCM_GETITEMA: { LRESULT lResult; TC_ITEMW * pItemW; LPWSTR pszTextW = NULL; TC_ITEMA * pItemA = (TC_ITEMA*)lParam; if (!ptc || !pItemA) { return FALSE; } pItemW = GlobalAlloc (GPTR, sizeof(TC_ITEMW) + ptc->cbExtra); if (!pItemW) { return FALSE; } if (pItemA->mask & TCIF_TEXT) { pszTextW = GlobalAlloc (GPTR, pItemA->cchTextMax * sizeof (TCHAR)); if (!pszTextW) { GlobalFree (pItemW); return FALSE; } pItemW->pszText = pszTextW; } pItemW->mask = pItemA->mask; pItemW->cchTextMax = pItemA->cchTextMax; pItemW->dwStateMask = pItemA->dwStateMask; lResult = (LRESULT)Tab_OnGetItem(ptc, (int)wParam, pItemW); if (!ThunkItemWtoA (ptc, pItemW, pItemA)) { lResult = (LRESULT)FALSE; } if (pszTextW) { GlobalFree (pszTextW); } GlobalFree (pItemW); return lResult; } case TCM_GETITEM: if (!ptc || !lParam) { return FALSE; } return (LRESULT)Tab_OnGetItem(ptc, (int)wParam, (TC_ITEM*)lParam); case TCM_INSERTITEMA: { LRESULT lResult; TC_ITEMW * pItemW; if (!lParam) { return FALSE; } pItemW = ThunkItemAtoW(ptc, (TC_ITEMA*)lParam); if (!pItemW) { return FALSE; } lResult = (LRESULT)Tab_OnInsertItem(ptc, (int)wParam, pItemW); FreeItemW(pItemW); return lResult; } case TCM_INSERTITEM: if (!lParam) { return FALSE; } return (LRESULT)Tab_OnInsertItem(ptc, (int)wParam, (const TC_ITEM*)lParam); case TCM_DELETEITEM: return (LRESULT)Tab_OnDeleteItem(ptc, (int)wParam); case TCM_DELETEALLITEMS: return (LRESULT)Tab_OnDeleteAllItems(ptc); case TCM_SETCURFOCUS: Tab_SetCurFocus(ptc, (int) wParam); break; case TCM_GETCURFOCUS: if (ptc->iNewSel != -1) return ptc->iNewSel; // else fall through case TCM_GETCURSEL: return ptc->iSel; case TCM_SETCURSEL: return (LRESULT)ChangeSel(ptc, (int)wParam, FALSE, FALSE); case TCM_GETTOOLTIPS: return (LRESULT)ptc->hwndToolTips; case TCM_SETTOOLTIPS: ptc->hwndToolTips = (HWND)wParam; break; case TCM_ADJUSTRECT: if (lParam) { RECT* prc = (RECT *)lParam; Tab_DVFlipRect(ptc, prc); Tab_OnAdjustRect(ptc, BOOLFROMPTR( wParam), (LPRECT)lParam); Tab_VDFlipRect(ptc, prc); } else return -1; break; case TCM_GETITEMRECT: return Tab_OnGetItemRect(ptc, (int)wParam, (LPRECT)lParam); case TCM_SETIMAGELIST: { HIMAGELIST himlOld = ptc->himl; ptc->himl = (HIMAGELIST)lParam; ptc->cxItem = ptc->cyTabs = RECOMPUTE; RedrawAll(ptc, RDW_INVALIDATE | RDW_ERASE); return (LRESULT)himlOld; } case TCM_GETIMAGELIST: return (LRESULT)ptc->himl; case TCM_REMOVEIMAGE: Tab_OnRemoveImage(ptc, (int)wParam); break; case TCM_SETITEMSIZE: { int iOldWidth = ptc->iTabWidth; int iOldHeight = ptc->iTabHeight; int iNewWidth = LOWORD(lParam); int iNewHeight = HIWORD(lParam); if (ptc->himl) { int cx, cy; Tab_ImageList_GetIconSize(ptc, &cx, &cy); if (iNewWidth < (cx + (2*g_cxEdge))) iNewWidth = cx + (2*g_cxEdge); } ptc->iTabWidth = iNewWidth; ptc->iTabHeight = iNewHeight; if (iNewWidth != iOldWidth || iNewHeight != iOldHeight) { ptc->cxItem = RECOMPUTE; ptc->cyTabs = RECOMPUTE; RedrawAll(ptc, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW); } return (LRESULT)MAKELONG(iOldWidth, iOldHeight); } case TCM_SETPADDING: ptc->cxPad = GET_X_LPARAM(lParam); ptc->cyPad = GET_Y_LPARAM(lParam); break; case TCM_GETROWCOUNT: Tab_CalcPaintMetrics(ptc, NULL); return (LRESULT)ptc->iLastRow + 1; case TCM_SETMINTABWIDTH: { int iOld = ptc->cxMinTab; if ((int)lParam >= 0) { ptc->cxMinTab = (int)lParam; ptc->fMinTabSet = TRUE; } else { ptc->fMinTabSet = FALSE; } ptc->cyTabs = RECOMPUTE; ptc->cxItem = RECOMPUTE; InvalidateRect(ptc->ci.hwnd, NULL, TRUE); return iOld; } case TCM_DESELECTALL: Tab_DeselectAll(ptc, BOOLFROMPTR( wParam)); break; case TCM_SETEXTENDEDSTYLE: return Tab_ExtendedStyleChange(ptc, (DWORD) lParam, (DWORD) wParam); case TCM_GETEXTENDEDSTYLE: return ptc->dwStyleEx; case TCM_HITTEST: { LPTC_HITTESTINFO lphitinfo = (LPTC_HITTESTINFO)lParam; return Tab_OnHitTest(ptc, lphitinfo->pt.x, lphitinfo->pt.y, &lphitinfo->flags); } case TCM_HIGHLIGHTITEM: { LPTABITEM pitem = Tab_GetItemPtr(ptc, (int)wParam); if (pitem) { BOOL fHighlight = LOWORD(lParam) != 0; // Don't do anything if state hasn't changed. if (fHighlight == ((pitem->dwState & TCIS_HIGHLIGHTED) != 0)) break; if (fHighlight) pitem->dwState |= TCIS_HIGHLIGHTED; else pitem->dwState &= ~TCIS_HIGHLIGHTED; Tab_InvalidateItem(ptc, (int)wParam, TRUE); return TRUE; } break; } case WM_NCHITTEST: { POINT pt; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ScreenToClient(ptc->ci.hwnd, &pt); if (Tab_OnHitTest(ptc, pt.x, pt.y, NULL) == -1) return(HTTRANSPARENT); else { goto DoDefault; } } case WM_GETOBJECT: if( lParam == OBJID_QUERYCLASSNAMEIDX ) return MSAA_CLASSNAMEIDX_TAB; break; default: { LRESULT lres; if (CCWndProc(&ptc->ci, uMsg, wParam, lParam, &lres)) return lres; } DoDefault: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0L; } // // ANSI <=> UNICODE thunks // TC_ITEMW * ThunkItemAtoW (PTC ptc, TC_ITEMA * pItemA) { TC_ITEMW *pItemW; UINT cbTextW; INT iResult; pItemW = (TC_ITEMW *) GlobalAlloc (GPTR, sizeof(TC_ITEMW) + ptc->cbExtra); if (!pItemW) { return NULL; } pItemW->mask = pItemA->mask; pItemW->dwState = pItemA->dwState; pItemW->dwStateMask = pItemA->dwStateMask; if ((pItemA->mask & TCIF_TEXT) && pItemA->pszText) { cbTextW = lstrlenA(pItemA->pszText) + 1; pItemW->pszText = (LPWSTR)GlobalAlloc (GPTR, cbTextW * sizeof(TCHAR)); if (!pItemW->pszText) { GlobalFree (pItemW); return NULL; } iResult = MultiByteToWideChar (CP_ACP, 0, pItemA->pszText, -1, pItemW->pszText, cbTextW); if (!iResult) { if (GetLastError()) { GlobalFree (pItemW->pszText); GlobalFree (pItemW); return NULL; } } } pItemW->cchTextMax = pItemA->cchTextMax; if (pItemA->mask & TCIF_IMAGE) { pItemW->iImage = pItemA->iImage; } if (pItemA->mask & TCIF_PARAM) { hmemcpy(&pItemW->lParam, &pItemA->lParam, ptc->cbExtra); } return (pItemW); } BOOL ThunkItemWtoA (PTC ptc, TC_ITEMW * pItemW, TC_ITEMA * pItemA) { INT iResult; if (!pItemA) { return FALSE; } pItemA->mask = pItemW->mask; pItemA->dwState = pItemW->dwState; pItemA->dwStateMask = pItemW->dwStateMask; if ((pItemW->mask & TCIF_TEXT) && pItemW->pszText && pItemW->cchTextMax) { iResult = WideCharToMultiByte (CP_ACP, 0, pItemW->pszText, -1, pItemA->pszText, pItemW->cchTextMax, NULL, NULL); if (!iResult) { if (GetLastError()) { return FALSE; } } } pItemA->cchTextMax = pItemW->cchTextMax; if (pItemW->mask & TCIF_IMAGE) { pItemA->iImage = pItemW->iImage; } if (pItemW->mask & TCIF_PARAM) { hmemcpy(&pItemA->lParam, &pItemW->lParam, ptc->cbExtra); } return TRUE; } BOOL FreeItemW (TC_ITEMW *pItemW) { if ((pItemW->mask & TCIF_TEXT) && pItemW->pszText) { GlobalFree (pItemW->pszText); } GlobalFree (pItemW); return TRUE; } BOOL FreeItemA (TC_ITEMA *pItemA) { if ((pItemA->mask & TCIF_TEXT) && pItemA->pszText) { GlobalFree (pItemA->pszText); } GlobalFree (pItemA); return TRUE; }