From f920ef497f3299ae24fe783ce03bdd93b419f764 Mon Sep 17 00:00:00 2001 From: Kirill Volinsky Date: Fri, 18 May 2012 22:02:50 +0000 Subject: plugins folders renaming git-svn-id: http://svn.miranda-ng.org/main/trunk@60 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/Tabsrmm/src/tabctrl.cpp | 1592 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1592 insertions(+) create mode 100644 plugins/Tabsrmm/src/tabctrl.cpp (limited to 'plugins/Tabsrmm/src/tabctrl.cpp') diff --git a/plugins/Tabsrmm/src/tabctrl.cpp b/plugins/Tabsrmm/src/tabctrl.cpp new file mode 100644 index 0000000000..2aadbd91f5 --- /dev/null +++ b/plugins/Tabsrmm/src/tabctrl.cpp @@ -0,0 +1,1592 @@ +/* astyle --force-indent=tab=4 --brackets=linux --indent-switches + * --pad=oper --one-line=keep-blocks --unpad=paren + * + * Miranda IM: the free IM client for Microsoft* Windows* + * + * Copyright 2000-2009 Miranda ICQ/IM project, + * all portions of this codebase are copyrighted to the people + * listed in contributors.txt. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * you should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * part of tabSRMM messaging plugin for Miranda. + * + * (C) 2005-2009 by silvercircle _at_ gmail _dot_ com and contributors + * + * $Id: tabctrl.cpp 12643 2010-09-09 03:57:16Z silvercircle $ + * + * a custom tab control, skinable, aero support, single/multi row, button + * tabs support, proper rendering for bottom row tabs and more. + * + */ + +#include "commonheaders.h" +#pragma hdrstop + +static WNDPROC OldTabControlClassProc; +extern ButtonSet g_ButtonSet; + +static LRESULT CALLBACK TabControlSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +#define FIXED_TAB_SIZE 100 + +/* + * register the new tab control as a window class (TSTabCtrlClass) + */ + +int TSAPI RegisterTabCtrlClass(void) +{ + WNDCLASSEXA wc; + WNDCLASSEX wce; + + ZeroMemory(&wc, sizeof(wc)); + wc.cbSize = sizeof(wc); + wc.lpszClassName = "TSTabCtrlClass"; + wc.lpfnWndProc = TabControlSubclassProc; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.cbWndExtra = sizeof(struct TabControlData *); + wc.hbrBackground = 0; + wc.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_PARENTDC; + RegisterClassExA(&wc); + + ZeroMemory(&wce, sizeof(wce)); + wce.cbSize = sizeof(wce); + wce.lpszClassName = _T("TSStatusBarClass"); + wce.lpfnWndProc = StatusBarSubclassProc; + wce.hCursor = LoadCursor(NULL, IDC_ARROW); + wce.cbWndExtra = sizeof(void*); + wce.hbrBackground = 0; + wce.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_PARENTDC; + RegisterClassEx(&wce); + + ZeroMemory(&wce, sizeof(wce)); + wce.cbSize = sizeof(wce); + wce.lpszClassName = _T("TS_SideBarClass"); + wce.lpfnWndProc = CSideBar::wndProcStub;; + wce.hCursor = LoadCursor(NULL, IDC_ARROW); + wce.cbWndExtra = sizeof(void*); + wce.hbrBackground = 0; + wce.style = CS_GLOBALCLASS;// | CS_DBLCLKS; // | CS_PARENTDC; + RegisterClassEx(&wce); + + ZeroMemory(&wce, sizeof(wce)); + wce.cbSize = sizeof(wce); + wce.lpszClassName = _T("TSHK"); + wce.lpfnWndProc = HotkeyHandlerDlgProc; + wce.hCursor = LoadCursor(NULL, IDC_ARROW); + wce.cbWndExtra = sizeof(void*); + wce.hbrBackground = 0; + wce.style = CS_GLOBALCLASS;// | CS_DBLCLKS; // | CS_PARENTDC; + RegisterClassEx(&wce); + + return 0; +} + +static int TabCtrl_TestForCloseButton(const TabControlData *tabdat, HWND hwnd, POINT& pt) +{ + TCHITTESTINFO tci = {0}; + tci.pt.x = pt.x; + tci.pt.y = pt.y; + int iTab; + + ScreenToClient(hwnd, &tci.pt); + iTab = TabCtrl_HitTest(hwnd, &tci); + if(iTab != -1) { + RECT rcTab; + + if(tci.flags & TCHT_NOWHERE) + return(-1); + + TabCtrl_GetItemRect(hwnd, iTab, &rcTab); + if(tabdat->dwStyle & TCS_BUTTONS) { + rcTab.right -= 1; + rcTab.left = rcTab.right - 18; + } + else { + rcTab.left = rcTab.right - 18; + rcTab.right -= 5; + } + rcTab.bottom -= 4; + rcTab.top += 4; + if(PtInRect(&rcTab, tci.pt)) + return(iTab); + } + return(-1); +} + +/* + * tabctrl helper function + * Finds leftmost down item. + */ + +static UINT FindLeftDownItem(HWND hwnd) +{ + RECT rctLeft = {100000, 0, 0, 0}, rctCur; + int nCount = TabCtrl_GetItemCount(hwnd) - 1; + UINT nItem = 0; + int i; + + for (i = 0;i < nCount;i++) { + TabCtrl_GetItemRect(hwnd, i, &rctCur); + if (rctCur.left > 0 && rctCur.left <= rctLeft.left) { + if (rctCur.bottom > rctLeft.bottom) { + rctLeft = rctCur; + nItem = i; + } + } + } + return nItem; +} + +/* + * tab control color definitions, including the database setting key names + */ + +static struct colOptions { + UINT defclr; + char *szKey; + char *szSkinnedKey; +} tabcolors[] = { + COLOR_BTNTEXT, "tab_txt_normal", "S_tab_txt_normal", + COLOR_BTNTEXT, "tab_txt_active", "S_tab_txt_active", + COLOR_HOTLIGHT, "tab_txt_hottrack", "S_tab_txt_hottrack", + COLOR_HOTLIGHT, "tab_txt_unread", "S_tab_txt_unread", + COLOR_3DFACE, "tab_bg_normal", "tab_bg_normal", + COLOR_3DFACE, "tab_bg_active", "tab_bg_active", + COLOR_3DFACE, "tab_bg_hottrack", "tab_bg_hottrack", + COLOR_3DFACE, "tab_bg_unread", "tab_bg_unread", + 0, 0, NULL, NULL +}; + +/* + * hints for drawing functions + */ + +#define HINT_ACTIVATE_RIGHT_SIDE 1 +#define HINT_ACTIVE_ITEM 2 +#define FLOAT_ITEM_HEIGHT_SHIFT 2 +#define ACTIVE_ITEM_HEIGHT_SHIFT 2 +#define SHIFT_FROM_CUT_TO_SPIN 4 +#define HINT_TRANSPARENT 16 +#define HINT_HOTTRACK 32 + +static void TSAPI DrawCustomTabPage(HDC hdc, RECT& rcClient) +{ + HBRUSH brOld = reinterpret_cast(::SelectObject(hdc, CSkin::m_BrushFill)); + HPEN hPen = ::CreatePen(PS_SOLID, 1, PluginConfig.m_cRichBorders); + HPEN hPenOld = reinterpret_cast(::SelectObject(hdc, hPen)); + ::Rectangle(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); + ::SelectObject(hdc, hPenOld); + ::SelectObject(hdc, brOld); + ::DeleteObject(hPen); +} + +void TSAPI FillTabBackground(const HDC hdc, int iStateId, const TWindowData* dat, RECT* rc) +{ + unsigned clrIndex; + + if(dat && dat->mayFlashTab) + clrIndex = 7; + else + clrIndex = (iStateId == PBS_PRESSED ? 5 : (iStateId == PBS_HOT ? 6 : 4)); + + if(PluginConfig.tabConfig.colors[clrIndex] != PluginConfig.m_fillColor) + FillRect(hdc, rc, PluginConfig.tabConfig.m_brushes[clrIndex - 4]); + else + CSkin::FillBack(hdc, rc); +} + +/* + * draws the item contents (icon and label) + * it obtains the label and icon handle directly from the message window data + * no image list is used and necessary, the message window dialog procedure has to provide a valid + * icon handle in dat->hTabIcon + */ + +static void DrawItem(TabControlData *tabdat, HDC dc, RECT *rcItem, int nHint, int nItem, TWindowData* dat) +{ + int iSize = 16; + DWORD dwTextFlags = DT_SINGLELINE | DT_VCENTER/* | DT_NOPREFIX*/; + BOOL leftMost = FALSE; + + if (dat) { + HICON hIcon; + COLORREF clr = 0; + HFONT oldFont; + DWORD dwStyle = tabdat->dwStyle; + int oldMode = 0; + unsigned clrIndex = 0; + + InflateRect(rcItem, -2, -2); + + if(dat->mayFlashTab) + clrIndex = 3; + else + clrIndex = (nHint & HINT_ACTIVE_ITEM ? 1 : (nHint & HINT_HOTTRACK ? 2 : 0)); + + clr = PluginConfig.tabConfig.colors[clrIndex]; + + oldMode = SetBkMode(dc, TRANSPARENT); + + if (!(dwStyle & TCS_BOTTOM)) + OffsetRect(rcItem, 0, 1); + + if (dat->dwFlags & MWF_ERRORSTATE) + hIcon = PluginConfig.g_iconErr; + else if (dat->mayFlashTab) + hIcon = dat->iFlashIcon; + else { + if (dat->si && dat->iFlashIcon) { + int sizeX, sizeY; + + hIcon = dat->iFlashIcon; + Utils::getIconSize(hIcon, sizeX, sizeY); + iSize = sizeX; + } else if (dat->hTabIcon == dat->hTabStatusIcon && dat->hXStatusIcon) + hIcon = dat->hXStatusIcon; + else + hIcon = dat->hTabIcon; + } + + + if (dat->mayFlashTab == FALSE || (dat->mayFlashTab == TRUE && dat->bTabFlash != 0) || !(dat->pContainer->dwFlagsEx & TCF_FLASHICON)) { + DWORD ix = rcItem->left + tabdat->m_xpad - 1; + DWORD iy = (rcItem->bottom + rcItem->top - iSize) / 2; + if (dat->dwFlagsEx & MWF_SHOW_ISIDLE && PluginConfig.m_IdleDetect) + CSkin::DrawDimmedIcon(dc, ix, iy, iSize, iSize, hIcon, 180); + else + DrawIconEx(dc, ix, iy, hIcon, iSize, iSize, 0, NULL, DI_NORMAL | DI_COMPAT); + } + + rcItem->left += (iSize + 2 + tabdat->m_xpad); + + if(tabdat->fCloseButton) { + if(tabdat->iHoveredCloseIcon != nItem) + CSkin::m_default_bf.SourceConstantAlpha = 150; + + CMimAPI::m_MyAlphaBlend(dc, rcItem->right - 16 - tabdat->m_xpad, (rcItem->bottom + rcItem->top - 16) / 2, 16, 16, CSkin::m_tabCloseHDC, + 0, 0, 16, 16, CSkin::m_default_bf); + + rcItem->right -= (18 + tabdat->m_xpad); + CSkin::m_default_bf.SourceConstantAlpha = 255; + } + + if (dat->mayFlashTab == FALSE || (dat->mayFlashTab == TRUE && dat->bTabFlash != 0) || !(dat->pContainer->dwFlagsEx & TCF_FLASHLABEL)) { + oldFont = (HFONT)SelectObject(dc, (HFONT)SendMessage(tabdat->hwnd, WM_GETFONT, 0, 0)); + if (tabdat->dwStyle & TCS_BUTTONS || !(tabdat->dwStyle & TCS_MULTILINE)) { // || (tabdat->m_moderntabs && leftMost)) { + rcItem->right -= tabdat->m_xpad; + dwTextFlags |= DT_WORD_ELLIPSIS; + } + CSkin::RenderText(dc, dwStyle & TCS_BUTTONS ? tabdat->hThemeButton : tabdat->hTheme, dat->newtitle, rcItem, dwTextFlags, CSkin::m_glowSize, clr); + SelectObject(dc, oldFont); + } + if (oldMode) + SetBkMode(dc, oldMode); + } +} + +/* + * draws the item rect (the "tab") in *classic* style (no visual themes + */ + +static RECT rcTabPage = {0}; + +static void DrawItemRect(struct TabControlData *tabdat, HDC dc, RECT *rcItem, int nHint, int iItem, const TWindowData* dat) +{ + POINT pt; + DWORD dwStyle = tabdat->dwStyle; + + rcItem->bottom -= 1; + if (rcItem->left >= 0) { + + /* + * draw "button style" tabs... raised edge for hottracked, sunken edge for active (pushed) + * otherwise, they get a normal border + */ + + if (dwStyle & TCS_BUTTONS) { + BOOL bClassicDraw = (tabdat->m_VisualStyles == FALSE); + + // draw frame controls for button or bottom tabs + if (dwStyle & TCS_BOTTOM) + rcItem->top++; + + rcItem->right += 6; + if(tabdat->fAeroTabs) { + if(M->isAero()) { + InflateRect(rcItem, 2, 0); + FillRect(dc, rcItem, CSkin::m_BrushBack); + } + else if(dat) { + int iStateId = (nHint & HINT_ACTIVE_ITEM ? PBS_PRESSED : 0) | (nHint & HINT_HOTTRACK ? PBS_HOT : 0); + + InflateRect(rcItem, 1, 0); + FillTabBackground(dc, iStateId, dat, rcItem); + } + CSkin::m_switchBarItem->setAlphaFormat(AC_SRC_ALPHA, nHint & HINT_ACTIVE_ITEM ? 255 : 200); + CSkin::m_switchBarItem->Render(dc, rcItem, true); + + + if(nHint & HINT_ACTIVE_ITEM || nHint & HINT_HOTTRACK) { + RECT rcGlow = *rcItem; + + if(dwStyle & TCS_BOTTOM) + rcGlow.top++; + else + rcGlow.bottom--; + + tabdat->helperGlowItem->setAlphaFormat(AC_SRC_ALPHA, nHint & HINT_ACTIVE_ITEM ? 200 : 150); + tabdat->helperGlowItem->Render(dc, &rcGlow, true); + } + } + else if(bClassicDraw) { + if (CSkin::m_skinEnabled) { + CSkinItem *item = nHint & HINT_ACTIVE_ITEM ? &SkinItems[ID_EXTBKBUTTONSPRESSED] : (nHint & HINT_HOTTRACK ? &SkinItems[ID_EXTBKBUTTONSMOUSEOVER] : &SkinItems[ID_EXTBKBUTTONSNPRESSED]); + + if (!item->IGNORED) { + CSkin::SkinDrawBG(tabdat->hwnd, tabdat->pContainer->hwnd, tabdat->pContainer, rcItem, dc); + CSkin::DrawItem(dc, rcItem, item); + } else + goto b_nonskinned; + } else { +b_nonskinned: + if (nHint & HINT_ACTIVE_ITEM) + DrawEdge(dc, rcItem, EDGE_ETCHED, BF_RECT | BF_SOFT); + else if (nHint & HINT_HOTTRACK) + DrawEdge(dc, rcItem, EDGE_BUMP, BF_RECT | BF_MONO | BF_SOFT); + else + DrawEdge(dc, rcItem, EDGE_RAISED, BF_RECT | BF_SOFT); + } + } else { + if(M->isAero() && !(dwStyle & TCS_BOTTOM)) + FillRect(dc, rcItem, CSkin::m_BrushBack); + else + CSkin::FillBack(dc, rcItem); + CMimAPI::m_pfnDrawThemeBackground(tabdat->hThemeButton, dc, 1, nHint & HINT_ACTIVE_ITEM ? 3 : (nHint & HINT_HOTTRACK ? 2 : 1), rcItem, rcItem); + } + return; + } + SelectObject(dc, PluginConfig.tabConfig.m_hPenLight); + + if (nHint & HINT_ACTIVE_ITEM) { + if (dwStyle & TCS_BOTTOM) { + if (!CSkin::m_skinEnabled) + CSkin::FillBack(dc, rcItem); + rcItem->bottom += 2; + } else { + rcItem->bottom += 2; + if (!CSkin::m_skinEnabled) + CSkin::FillBack(dc, rcItem); + rcItem->bottom--; + rcItem->top -= 2; + } + if (CSkin::m_skinEnabled) { + CSkinItem *item = &SkinItems[dwStyle & TCS_BOTTOM ? ID_EXTBKTABITEMACTIVEBOTTOM : ID_EXTBKTABITEMACTIVE]; + if (!item->IGNORED) { + rcItem->left += item->MARGIN_LEFT; + rcItem->right -= item->MARGIN_RIGHT; + rcItem->top += item->MARGIN_TOP; + rcItem->bottom -= item->MARGIN_BOTTOM; + CSkin::DrawItem(dc, rcItem, item); + return; + } + } + } + if (CSkin::m_skinEnabled) { + CSkinItem *item = &SkinItems[dwStyle & TCS_BOTTOM ? (nHint & HINT_HOTTRACK ? ID_EXTBKTABITEMHOTTRACKBOTTOM : ID_EXTBKTABITEMBOTTOM) : + (nHint & HINT_HOTTRACK ? ID_EXTBKTABITEMHOTTRACK : ID_EXTBKTABITEM)]; + if (!item->IGNORED) { + if (dwStyle & TCS_BOTTOM) + rcItem->top = (rcItem->top > rcTabPage.bottom + 5) ? --rcItem->top : rcItem->top; + else + rcItem->bottom++; + //rcItem->bottom = (rcItem->bottom < rcTabPage.top - 5) ? ++rcItem->bottom : rcItem->bottom; + + rcItem->left += item->MARGIN_LEFT; + rcItem->right -= item->MARGIN_RIGHT; + CSkin::DrawItem(dc, rcItem, item); + return; + } + } + if (dwStyle & TCS_BOTTOM) { + MoveToEx(dc, rcItem->left, rcItem->top - (nHint & HINT_ACTIVE_ITEM ? 1 : 0), &pt); + LineTo(dc, rcItem->left, rcItem->bottom - 2); + LineTo(dc, rcItem->left + 2, rcItem->bottom); + SelectObject(dc, PluginConfig.tabConfig.m_hPenShadow); + LineTo(dc, rcItem->right - 3, rcItem->bottom); + + LineTo(dc, rcItem->right - 1, rcItem->bottom - 2); + LineTo(dc, rcItem->right - 1, rcItem->top - 1); + MoveToEx(dc, rcItem->right - 2, rcItem->top, &pt); + SelectObject(dc, PluginConfig.tabConfig.m_hPenItemShadow); + LineTo(dc, rcItem->right - 2, rcItem->bottom - 1); + MoveToEx(dc, rcItem->right - 3, rcItem->bottom - 1, &pt); + LineTo(dc, rcItem->left + 2, rcItem->bottom - 1); + } else { + MoveToEx(dc, rcItem->left, rcItem->bottom, &pt); + LineTo(dc, rcItem->left, rcItem->top + 2); + LineTo(dc, rcItem->left + 2, rcItem->top); + LineTo(dc, rcItem->right - 2, rcItem->top); + SelectObject(dc, PluginConfig.tabConfig.m_hPenItemShadow); + + MoveToEx(dc, rcItem->right - 2, rcItem->top + 1, &pt); + LineTo(dc, rcItem->right - 2, rcItem->bottom + 1); + SelectObject(dc, PluginConfig.tabConfig.m_hPenShadow); + MoveToEx(dc, rcItem->right - 1, rcItem->top + 2, &pt); + LineTo(dc, rcItem->right - 1, rcItem->bottom + 1); + } + } +} + +static int DWordAlign(int n) +{ + int rem = n % 4; + if (rem) + n += (4 - rem); + return n; +} + +static HRESULT DrawThemesPartWithAero(const TabControlData *tabdat, HDC hDC, int iPartId, int iStateId, LPRECT prcBox, TWindowData* dat) +{ + HRESULT hResult = 0; + bool fAero = M->isAero(); + + if(tabdat->fAeroTabs) { + if(tabdat->dwStyle & TCS_BOTTOM) + prcBox->top += (fAero ? 2 : iStateId == PBS_PRESSED ? (M->isVSThemed() ? 1 : -1) : 0); + else if (!fAero) + prcBox->bottom -= (iStateId == PBS_PRESSED ? (M->isVSThemed() ? 1 : -1) : 0); + + if(fAero) + FillRect(hDC, prcBox, CSkin::m_BrushBack); + else if(dat) + FillTabBackground(hDC, iStateId, dat, prcBox); + + tabdat->helperItem->setAlphaFormat(AC_SRC_ALPHA, iStateId == PBS_PRESSED ? 255 : (fAero ? 240 : 255)); + tabdat->helperItem->Render(hDC, prcBox, true); + tabdat->helperGlowItem->setAlphaFormat(AC_SRC_ALPHA, iStateId == PBS_PRESSED ? 220 : 180); + + if(iStateId != PBS_NORMAL) + tabdat->helperGlowItem->Render(hDC, prcBox, true); + } + else if(CMimAPI::m_pfnDrawThemeBackground) { + if (tabdat->hTheme != 0) + hResult = CMimAPI::m_pfnDrawThemeBackground(tabdat->hTheme, hDC, iPartId, iStateId, prcBox, NULL); + } + + return hResult; +} +/* + * draws a theme part (identifier in uiPartNameID) using the given clipping rectangle + */ + +static HRESULT DrawThemesPart(const TabControlData *tabdat, HDC hDC, int iPartId, int iStateId, LPRECT prcBox) +{ + HRESULT hResult = 0; + + if (CMimAPI::m_pfnDrawThemeBackground == 0) + return 0; + + if (tabdat->hTheme != 0) + hResult = CMimAPI::m_pfnDrawThemeBackground(tabdat->hTheme, hDC, iPartId, iStateId, prcBox, NULL); + + return hResult; +} + +/* + * draw a themed tab item. either a tab or the body pane + * handles image mirroring for tabs at the bottom + */ + +static void DrawThemesXpTabItem(HDC pDC, int ixItem, RECT *rcItem, UINT uiFlag, struct TabControlData *tabdat, TWindowData* dat) +{ + BOOL bBody = (uiFlag & 1) ? TRUE : FALSE; + BOOL bSel = (uiFlag & 2) ? TRUE : FALSE; + BOOL bHot = (uiFlag & 4) ? TRUE : FALSE; + BOOL bBottom = (uiFlag & 8) ? TRUE : FALSE; // mirror + SIZE szBmp; + HDC dcMem; + HBITMAP bmpMem, pBmpOld; + RECT rcMem; + BITMAPINFO biOut; + BITMAPINFOHEADER *bihOut; + int nBmpWdtPS; + int nSzBuffPS; + LPBYTE pcImg = NULL, pcImg1 = NULL; + int nStart = 0, nLenSub = 0; + szBmp.cx = rcItem->right - rcItem->left; + szBmp.cy = rcItem->bottom - rcItem->top; + + /* + * for top row tabs, it's easy. Just draw to the provided dc (it's a mem dc already) + */ + + if (!bBottom) { + if (bBody) { + if(PluginConfig.m_bIsVista) { + rcItem->right += 2; // hide right tab sheet shadow (only draw the actual border line) + rcItem->bottom += 1; + } + DrawThemesPart(tabdat, pDC, 9, 0, rcItem); // TABP_PANE id = 9 + } else { + int iStateId = bSel ? 3 : (bHot ? 2 : 1); // leftmost item has different part id + DrawThemesPartWithAero(tabdat, pDC, rcItem->left < 20 ? 2 : 1, iStateId, rcItem, dat); + } + return; + } + else if(tabdat->fAeroTabs && !bBody) { + int iStateId = bSel ? 3 : (bHot ? 2 : 1); // leftmost item has different part id + DrawThemesPartWithAero(tabdat, pDC, rcItem->left < 20 ? 2 : 1, iStateId, rcItem, dat); + return; + } + + /* + * remaining code is for bottom tabs only. + */ + + dcMem = CreateCompatibleDC(pDC); + bmpMem = CreateCompatibleBitmap(pDC, szBmp.cx, szBmp.cy); + + pBmpOld = (HBITMAP)SelectObject(dcMem, bmpMem); + + rcMem.left = rcMem.top = 0; + rcMem.right = szBmp.cx; + rcMem.bottom = szBmp.cy; + + ZeroMemory(&biOut, sizeof(BITMAPINFO)); // Fill local pixel arrays + bihOut = &biOut.bmiHeader; + + bihOut->biSize = sizeof(BITMAPINFOHEADER); + bihOut->biCompression = BI_RGB; + bihOut->biPlanes = 1; + bihOut->biBitCount = 24; // force as RGB: 3 bytes, 24 bits + bihOut->biWidth = szBmp.cx; + bihOut->biHeight = szBmp.cy; + + nBmpWdtPS = DWordAlign(szBmp.cx * 3); + nSzBuffPS = ((nBmpWdtPS * szBmp.cy) / 8 + 2) * 8; + + /* + * blit the background to the memory dc, so that transparent tabs will draw properly + * for bottom tabs, it's more complex, because the background part must not be mirrored + * the body part does not need that (filling with the background color is much faster + * and sufficient for the tab "page" part. + */ + + if (!bSel) + CSkin::FillBack(dcMem, &rcMem); + else { + /* + * mirror the background horizontally for bottom selected tabs (they can overwrite others) + * needed, because after drawing the theme part the images will again be mirrored + * to "flip" the tab item. + */ + BitBlt(dcMem, 0, 0, szBmp.cx, szBmp.cy, pDC, rcItem->left, rcItem->top, SRCCOPY); + + pcImg1 = (BYTE *)mir_alloc(nSzBuffPS); + + if (pcImg1) { + GetDIBits(pDC, bmpMem, nStart, szBmp.cy - nLenSub, pcImg1, &biOut, DIB_RGB_COLORS); + bihOut->biHeight = -szBmp.cy; // to mirror bitmap is eough to use negative height between Get/SetDIBits + SetDIBits(pDC, bmpMem, nStart, szBmp.cy - nLenSub, pcImg1, &biOut, DIB_RGB_COLORS); + mir_free(pcImg1); + } + } + + /* + * body may be *large* so rotating the final image can be very slow. + * workaround: draw the skin item (tab pane) into a small dc, rotate this (small) image and render + * it to the final DC with the IMG_RenderItem() routine. + */ + + if (bBody) { + HDC hdcTemp = CreateCompatibleDC(pDC); + HBITMAP hbmTemp = CreateCompatibleBitmap(pDC, 100, 50); + HBITMAP hbmTempOld = (HBITMAP)SelectObject(hdcTemp, hbmTemp); + RECT rcTemp = {0}; + + rcTemp.right = 100; + rcTemp.bottom = 50; + + bihOut->biWidth = 100; + bihOut->biHeight = 50; + + nBmpWdtPS = DWordAlign(100 * 3); + nSzBuffPS = ((nBmpWdtPS * 50) / 8 + 2) * 8; + + CSkin::FillBack(hdcTemp, &rcTemp); + DrawThemesPart(tabdat, hdcTemp, 9, 0, &rcTemp); // TABP_PANE id = 9 + pcImg = (BYTE *)mir_alloc(nSzBuffPS); + if (pcImg) { // get bits: + GetDIBits(hdcTemp, hbmTemp, nStart, 50 - nLenSub, pcImg, &biOut, DIB_RGB_COLORS); + bihOut->biHeight = -50; + SetDIBits(hdcTemp, hbmTemp, nStart, 50 - nLenSub, pcImg, &biOut, DIB_RGB_COLORS); + mir_free(pcImg); + } + CImageItem tempItem(10, 10, 10, 10, hdcTemp, 0, IMAGE_FLAG_DIVIDED | IMAGE_FILLSOLID, + GetSysColorBrush(COLOR_3DFACE), 255, 30, 80, 50, 100); + + if(PluginConfig.m_bIsVista) { // hide right tab sheet shadow (only draw the actual border line) + rcItem->right += 2; + } + + tempItem.Render(pDC, rcItem, true); + tempItem.Clear(); + SelectObject(hdcTemp, hbmTempOld); + DeleteObject(hbmTemp); + DeleteDC(hdcTemp); + + SelectObject(dcMem, pBmpOld); + DeleteObject(bmpMem); + DeleteDC(dcMem); + return; + } else { + int iStateId = bSel ? 3 : (bHot ? 2 : 1); + DrawThemesPart(tabdat, dcMem, rcItem->left < 20 ? 2 : 1, iStateId, &rcMem); + } + + bihOut->biHeight = szBmp.cy; + pcImg = (BYTE *)mir_alloc(nSzBuffPS); + + if (pcImg) { // get bits: + GetDIBits(pDC, bmpMem, nStart, szBmp.cy - nLenSub, pcImg, &biOut, DIB_RGB_COLORS); + bihOut->biHeight = -szBmp.cy; + SetDIBits(pDC, bmpMem, nStart, szBmp.cy - nLenSub, pcImg, &biOut, DIB_RGB_COLORS); + mir_free(pcImg); + } + + /* + * finally, blit the result to the destination dc + */ + + BitBlt(pDC, rcItem->left, rcItem->top, szBmp.cx, szBmp.cy, dcMem, 0, 0, SRCCOPY); + SelectObject(dcMem, pBmpOld); + DeleteObject(bmpMem); + DeleteDC(dcMem); +} + +static POINT ptMouseT = {0}; + +static LRESULT CALLBACK TabControlSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct TabControlData *tabdat = 0; + + tabdat = (struct TabControlData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (tabdat) { + if (tabdat->pContainer == NULL) + tabdat->pContainer = (TContainerData *)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); + tabdat->dwStyle = GetWindowLongPtr(hwnd, GWL_STYLE); + } + + switch (msg) { + case WM_NCCREATE: { + WNDCLASSEXA wcl = {0}; + + wcl.cbSize = sizeof(wcl); + GetClassInfoExA(g_hInst, "SysTabControl32", &wcl); + + tabdat = (struct TabControlData *)mir_alloc(sizeof(struct TabControlData)); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)tabdat); + ZeroMemory((void *)tabdat, sizeof(struct TabControlData)); + tabdat->hwnd = hwnd; + tabdat->cx = GetSystemMetrics(SM_CXSMICON); + tabdat->cy = GetSystemMetrics(SM_CYSMICON); + tabdat->fTipActive = FALSE; + tabdat->iHoveredCloseIcon = -1; + SendMessage(hwnd, EM_THEMECHANGED, 0, 0); + OldTabControlClassProc = wcl.lpfnWndProc; + return TRUE; + } + case EM_THEMECHANGED: + tabdat->m_xpad = M->GetByte("x-pad", 3); + tabdat->m_VisualStyles = FALSE; + if (PluginConfig.m_bIsXP && M->isVSAPIState()) { + if (CMimAPI::m_pfnIsThemeActive != 0) + if (CMimAPI::m_pfnIsThemeActive()) { + tabdat->m_VisualStyles = TRUE; + if (tabdat->hTheme != 0 && CMimAPI::m_pfnCloseThemeData != 0) { + CMimAPI::m_pfnCloseThemeData(tabdat->hTheme); + CMimAPI::m_pfnCloseThemeData(tabdat->hThemeButton); + } + if (CMimAPI::m_pfnOpenThemeData != 0) { + if ((tabdat->hTheme = CMimAPI::m_pfnOpenThemeData(hwnd, L"TAB")) == 0 || (tabdat->hThemeButton = CMimAPI::m_pfnOpenThemeData(hwnd, L"BUTTON")) == 0) + tabdat->m_VisualStyles = FALSE; + } + } + } + return 0; + case EM_SEARCHSCROLLER: { + HWND hwndChild; + /* + * search the updown control (scroll arrows) to subclass it... + * the control is dynamically created and may not exist as long as it is + * not needed. So we have to search it everytime we need to paint. However, + * it is sufficient to search it once. So this message is called, whenever + * a new tab is inserted + */ + + if ((hwndChild = FindWindowEx(hwnd, 0, _T("msctls_updown32"), NULL)) != 0) + DestroyWindow(hwndChild); + return 0; + } + case EM_VALIDATEBOTTOM: { + BOOL bClassicDraw = (tabdat->m_VisualStyles == FALSE); + if ((tabdat->dwStyle & TCS_BOTTOM) && !bClassicDraw && PluginConfig.tabConfig.m_bottomAdjust != 0) + InvalidateRect(hwnd, NULL, FALSE); + break; + } + case EM_REFRESHWITHOUTCLIP: + if (TabCtrl_GetItemCount(hwnd) > 1) + return 0; + else { + tabdat->bRefreshWithoutClip = TRUE; + RedrawWindow(hwnd, NULL, NULL, RDW_UPDATENOW | RDW_NOCHILDREN | RDW_INVALIDATE); + tabdat->bRefreshWithoutClip = FALSE; + return 0; + } + case TCM_INSERTITEM: + case TCM_DELETEITEM: + tabdat->iHoveredCloseIcon = -1; + if (!(tabdat->dwStyle & TCS_MULTILINE) || tabdat->dwStyle & TCS_BUTTONS) { + LRESULT result; + RECT rc; + int iTabs = TabCtrl_GetItemCount(hwnd); + if (iTabs >= 1 && msg == TCM_INSERTITEM) { + TabCtrl_GetItemRect(hwnd, 0, &rc); + TabCtrl_SetItemSize(hwnd, 10, rc.bottom - rc.top); + } + result = CallWindowProc(OldTabControlClassProc, hwnd, msg, wParam, lParam); + TabCtrl_GetItemRect(hwnd, 0, &rc); + SendMessage(hwnd, WM_SIZE, 0, 0); + return result; + } + break; + case WM_DESTROY: + if (tabdat) { + if (tabdat->hTheme != 0 && CMimAPI::m_pfnCloseThemeData != 0) { + CMimAPI::m_pfnCloseThemeData(tabdat->hTheme); + CMimAPI::m_pfnCloseThemeData(tabdat->hThemeButton); + } + } + break; + case WM_NCDESTROY: + if(tabdat) { + mir_free(tabdat); + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0L); + } + break; + case WM_MBUTTONDOWN: { + POINT pt; + GetCursorPos(&pt); + SendMessage(GetParent(hwnd), DM_CLOSETABATMOUSE, 0, (LPARAM)&pt); + return 1; + } + case WM_SETCURSOR: { + POINT pt; + + GetCursorPos(&pt); + SendMessage(GetParent(hwnd), msg, wParam, lParam); + if (abs(pt.x - ptMouseT.x) < 4 && abs(pt.y - ptMouseT.y) < 4) + return 1; + ptMouseT = pt; + if (tabdat->fTipActive) { + KillTimer(hwnd, TIMERID_HOVER_T); + CallService("mToolTip/HideTip", 0, 0); + tabdat->fTipActive = FALSE; + } + KillTimer(hwnd, TIMERID_HOVER_T); + if(tabdat->pContainer && (!tabdat->pContainer->SideBar->isActive() && (TabCtrl_GetItemCount(hwnd) > 1 || !(tabdat->pContainer->dwFlags & CNT_HIDETABS)))) + SetTimer(hwnd, TIMERID_HOVER_T, 750, 0); + break; + } + case WM_SIZE: { + int iTabs = TabCtrl_GetItemCount(hwnd); + + if (!tabdat->pContainer) + break; + + if (!(tabdat->dwStyle & TCS_MULTILINE)) { + RECT rcClient, rc; + DWORD newItemSize; + if (iTabs > (tabdat->pContainer->dwFlags & CNT_HIDETABS ? 1 : 0)) { + GetClientRect(hwnd, &rcClient); + TabCtrl_GetItemRect(hwnd, iTabs - 1, &rc); + newItemSize = (rcClient.right - 6) - (tabdat->dwStyle & TCS_BUTTONS ? (iTabs) * 10 : 0); + newItemSize = newItemSize / iTabs; + if (newItemSize < PluginConfig.tabConfig.m_fixedwidth) { + TabCtrl_SetItemSize(hwnd, newItemSize, rc.bottom - rc.top); + } else { + TabCtrl_SetItemSize(hwnd, PluginConfig.tabConfig.m_fixedwidth, rc.bottom - rc.top); + } + SendMessage(hwnd, EM_SEARCHSCROLLER, 0, 0); + } + } else if (tabdat->dwStyle & TCS_BUTTONS && iTabs > 0) { + RECT rcClient, rcItem; + int nrTabsPerLine; + GetClientRect(hwnd, &rcClient); + TabCtrl_GetItemRect(hwnd, 0, &rcItem); + nrTabsPerLine = (rcClient.right) / PluginConfig.tabConfig.m_fixedwidth; + if (iTabs >= nrTabsPerLine && nrTabsPerLine > 0) + TabCtrl_SetItemSize(hwnd, ((rcClient.right - 6) / nrTabsPerLine) - (tabdat->dwStyle & TCS_BUTTONS ? 8 : 0), rcItem.bottom - rcItem.top); + else + TabCtrl_SetItemSize(hwnd, PluginConfig.tabConfig.m_fixedwidth, rcItem.bottom - rcItem.top); + } + break; + } + case WM_LBUTTONDBLCLK: { + POINT pt; + GetCursorPos(&pt); + SendMessage(GetParent(hwnd), DM_CLOSETABATMOUSE, 0, (LPARAM)&pt); + break; + } + case WM_RBUTTONDOWN: + KillTimer(hwnd, TIMERID_HOVER_T); + CallService("mToolTip/HideTip", 0, 0); + tabdat->fTipActive = FALSE; + break; + + case WM_LBUTTONDOWN: { + TCHITTESTINFO tci = {0}; + + KillTimer(hwnd, TIMERID_HOVER_T); + CallService("mToolTip/HideTip", 0, 0); + tabdat->fTipActive = FALSE; + + if (GetKeyState(VK_CONTROL) & 0x8000) { + tci.pt.x = (short)LOWORD(GetMessagePos()); + tci.pt.y = (short)HIWORD(GetMessagePos()); + if (DragDetect(hwnd, tci.pt) && TabCtrl_GetItemCount(hwnd) > 1) { + int i; + tci.flags = TCHT_ONITEM; + + ScreenToClient(hwnd, &tci.pt); + i = TabCtrl_HitTest(hwnd, &tci); + if (i != -1) { + TCITEM tc; + struct TWindowData *dat = NULL; + + tc.mask = TCIF_PARAM; + TabCtrl_GetItem(hwnd, i, &tc); + dat = (struct TWindowData *)GetWindowLongPtr((HWND)tc.lParam, GWLP_USERDATA); + if (dat) { + tabdat->bDragging = TRUE; + tabdat->iBeginIndex = i; + tabdat->hwndDrag = (HWND)tc.lParam; + tabdat->dragDat = dat; + tabdat->fSavePos = TRUE; + tabdat->himlDrag = ImageList_Create(16, 16, ILC_MASK | (PluginConfig.m_bIsXP ? ILC_COLOR32 : ILC_COLOR16), 1, 0); + ImageList_AddIcon(tabdat->himlDrag, dat->hTabIcon); + ImageList_BeginDrag(tabdat->himlDrag, 0, 8, 8); + ImageList_DragEnter(hwnd, tci.pt.x, tci.pt.y); + SetCapture(hwnd); + } + return TRUE; + } + } + } + + if (GetKeyState(VK_MENU) & 0x8000) { + tci.pt.x = (short)LOWORD(GetMessagePos()); + tci.pt.y = (short)HIWORD(GetMessagePos()); + if (DragDetect(hwnd, tci.pt) && TabCtrl_GetItemCount(hwnd) > 1) { + int i; + tci.flags = TCHT_ONITEM; + + ScreenToClient(hwnd, &tci.pt); + i = TabCtrl_HitTest(hwnd, &tci); + if (i != -1) { + TCITEM tc; + TWindowData *dat = NULL; + + tc.mask = TCIF_PARAM; + TabCtrl_GetItem(hwnd, i, &tc); + dat = (TWindowData *)GetWindowLongPtr((HWND)tc.lParam, GWLP_USERDATA); + if (dat) { + tabdat->bDragging = TRUE; + tabdat->iBeginIndex = i; + tabdat->hwndDrag = (HWND)tc.lParam; + tabdat->dragDat = dat; + tabdat->himlDrag = ImageList_Create(16, 16, ILC_MASK | (PluginConfig.m_bIsXP ? ILC_COLOR32 : ILC_COLOR16), 1, 0); + tabdat->fSavePos = FALSE; + ImageList_AddIcon(tabdat->himlDrag, dat->hTabIcon); + ImageList_BeginDrag(tabdat->himlDrag, 0, 8, 8); + ImageList_DragEnter(hwnd, tci.pt.x, tci.pt.y); + SetCapture(hwnd); + } + return TRUE; + } + } + } + if(tabdat->fCloseButton) { + POINT pt; + GetCursorPos(&pt); + + if(TabCtrl_TestForCloseButton(tabdat, hwnd, pt) != -1) + return(TRUE); + } + } + break; + + case WM_CAPTURECHANGED: { + tabdat->bDragging = FALSE; + ImageList_DragLeave(hwnd); + ImageList_EndDrag(); + if (tabdat->himlDrag) { + ImageList_RemoveAll(tabdat->himlDrag); + ImageList_Destroy(tabdat->himlDrag); + tabdat->himlDrag = 0; + } + } + break; + + case WM_MOUSEMOVE: { + if (tabdat->bDragging) { + TCHITTESTINFO tci = {0}; + tci.pt.x = (short)LOWORD(GetMessagePos()); + tci.pt.y = (short)HIWORD(GetMessagePos()); + ScreenToClient(hwnd, &tci.pt); + ImageList_DragMove(tci.pt.x, tci.pt.y); + } + if(tabdat->fCloseButton) { + POINT pt; + + GetCursorPos(&pt); + int iOldHovered = tabdat->iHoveredCloseIcon; + tabdat->iHoveredCloseIcon = TabCtrl_TestForCloseButton(tabdat, hwnd, pt); + if(tabdat->iHoveredCloseIcon != iOldHovered) + InvalidateRect(hwnd, NULL, FALSE); + } + } + break; + + case WM_LBUTTONUP: { + CallWindowProc(OldTabControlClassProc, hwnd, msg, wParam, lParam); + if (tabdat->bDragging && ReleaseCapture()) { + TCHITTESTINFO tci = {0}; + int i; + tci.pt.x = (short)LOWORD(GetMessagePos()); + tci.pt.y = (short)HIWORD(GetMessagePos()); + tci.flags = TCHT_ONITEM; + tabdat->bDragging = FALSE; + ImageList_DragLeave(hwnd); + ImageList_EndDrag(); + + ScreenToClient(hwnd, &tci.pt); + i = TabCtrl_HitTest(hwnd, &tci); + if (i != -1 && i != tabdat->iBeginIndex) + RearrangeTab(tabdat->hwndDrag, tabdat->dragDat, MAKELONG(i, 0xffff), tabdat->fSavePos); + tabdat->hwndDrag = (HWND) - 1; + tabdat->dragDat = NULL; + if (tabdat->himlDrag) { + ImageList_RemoveAll(tabdat->himlDrag); + ImageList_Destroy(tabdat->himlDrag); + tabdat->himlDrag = 0; + } + } + if(tabdat->fCloseButton) { + POINT pt; + + GetCursorPos(&pt); + int iItem = TabCtrl_TestForCloseButton(tabdat, hwnd, pt); + if(iItem != -1) + SendMessage(GetParent(hwnd), DM_CLOSETABATMOUSE, 0, (LPARAM)&pt); + } + } + break; + + case WM_ERASEBKGND: + if (tabdat->pContainer && (CSkin::m_skinEnabled || M->isAero())) + return TRUE; + return(0); + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdcreal, hdc; + RECT rectTemp, rctPage, rctActive, rcItem, rctClip, rctOrig; + RECT rectUpDn = {0, 0, 0, 0}; + int nCount = TabCtrl_GetItemCount(hwnd), i; + TCITEM item = {0}; + int iActive, hotItem; + POINT pt; + DWORD dwStyle = tabdat->dwStyle; + HPEN hPenOld = 0; + UINT uiFlags = 1; + UINT uiBottom = 0; + TCHITTESTINFO hti; + HBITMAP bmpMem, bmpOld; + DWORD cx, cy; + bool isAero = M->isAero(); + HANDLE hpb = 0; + BOOL bClassicDraw = !isAero && (tabdat->m_VisualStyles == FALSE || CSkin::m_skinEnabled); + if(GetUpdateRect(hwnd, NULL, TRUE) == 0) + break; + + item.mask = TCIF_PARAM; + + tabdat->fAeroTabs = (CSkin::m_fAeroSkinsValid && (isAero || PluginConfig.m_fillColor)) ? TRUE : FALSE; + tabdat->fCloseButton = tabdat->pContainer ? (tabdat->pContainer->dwFlagsEx & TCF_CLOSEBUTTON ? TRUE : FALSE) : FALSE; + + tabdat->helperDat = 0; + + if(tabdat->fAeroTabs && tabdat->pContainer) { + TWindowData *dat = (TWindowData *)GetWindowLongPtr(tabdat->pContainer->hwndActive, GWLP_USERDATA); + if(dat) { + tabdat->helperDat = dat; + } + else + tabdat->fAeroTabs = 0; + + tabdat->helperItem = (dwStyle & TCS_BOTTOM) ? CSkin::m_tabBottom : CSkin::m_tabTop; + tabdat->helperGlowItem = (dwStyle & TCS_BOTTOM) ? CSkin::m_tabGlowBottom : CSkin::m_tabGlowTop; + } + else + tabdat->fAeroTabs = FALSE; + + hdcreal = BeginPaint(hwnd, &ps); + + /* + * switchbar is active, don't paint a single pixel, the tab control won't be visible at all + * same when we have only ONE tab and do not want it to be visible because of the container + * option "Show tab bar only when needed". + */ + + if((tabdat->pContainer->dwFlags & CNT_SIDEBAR) || (nCount == 1 && tabdat->pContainer->dwFlags & CNT_HIDETABS)) { + if(nCount == 0) + FillRect(hdcreal, &ps.rcPaint, GetSysColorBrush(COLOR_3DFACE)); // avoid flickering/ugly black background during container creation + EndPaint(hwnd, &ps); + return(0); + } + + GetClientRect(hwnd, &rctPage); + rctOrig = rctPage; + iActive = TabCtrl_GetCurSel(hwnd); + TabCtrl_GetItemRect(hwnd, iActive, &rctActive); + cx = rctPage.right - rctPage.left; + cy = rctPage.bottom - rctPage.top; + + /* + * draw everything to a memory dc to avoid flickering + */ + + if(CMimAPI::m_haveBufferedPaint) + hpb = tabdat->hbp = CSkin::InitiateBufferedPaint(hdcreal, rctPage, hdc); + else { + hdc = CreateCompatibleDC(hdcreal); + bmpMem = tabdat->fAeroTabs ? CSkin::CreateAeroCompatibleBitmap(rctPage, hdcreal) : CreateCompatibleBitmap(hdcreal, cx, cy); + bmpOld = (HBITMAP)SelectObject(hdc, bmpMem); + } + + if (nCount == 1 && tabdat->pContainer->dwFlags & CNT_HIDETABS) + rctClip = rctPage; + + if (CSkin::m_skinEnabled) + CSkin::SkinDrawBG(hwnd, tabdat->pContainer->hwnd, tabdat->pContainer, &rctPage, hdc); + else + CSkin::FillBack(hdc, &rctPage); + + if (dwStyle & TCS_BUTTONS) { + RECT rc1; + TabCtrl_GetItemRect(hwnd, nCount - 1, &rc1); + if (dwStyle & TCS_BOTTOM) { + rctPage.bottom = rc1.top; + uiBottom = 8; + } else { + rctPage.top = rc1.bottom + 2; + uiBottom = 0; + } + } else { + if (dwStyle & TCS_BOTTOM) { + rctPage.bottom = rctActive.top; + uiBottom = 8; + } else { + rctPage.top = rctActive.bottom; + uiBottom = 0; + } + } + + if (nCount > 1 || !(tabdat->pContainer->dwFlags & CNT_HIDETABS)) { + rctClip = rctPage; + InflateRect(&rctClip, -tabdat->pContainer->tBorder, -tabdat->pContainer->tBorder); + } + else + ZeroMemory(&rctClip, sizeof(RECT)); + + hPenOld = (HPEN)SelectObject(hdc, PluginConfig.tabConfig.m_hPenLight); + /* + * visual style support + */ + + CopyRect(&rcTabPage, &rctPage); + if (!tabdat->bRefreshWithoutClip) + ExcludeClipRect(hdc, rctClip.left, rctClip.top, rctClip.right, rctClip.bottom); + else + ZeroMemory(&rctClip, sizeof(RECT)); + if ((!bClassicDraw || PluginConfig.m_fillColor) && IntersectRect(&rectTemp, &rctPage, &ps.rcPaint) && !CSkin::m_skinEnabled) { + RECT rcClient = rctPage; + if (dwStyle & TCS_BOTTOM) { + rcClient.bottom = rctPage.bottom; + uiFlags |= uiBottom; + } else + rcClient.top = rctPage.top; + if(PluginConfig.m_fillColor) + DrawCustomTabPage(hdc, rcClient); + else + DrawThemesXpTabItem(hdc, -1, &rcClient, uiFlags, tabdat, 0); // TABP_PANE=9,0,'TAB' + if (tabdat->bRefreshWithoutClip) + goto skip_tabs; + } else { + if (IntersectRect(&rectTemp, &rctPage, &ps.rcPaint)) { + if (CSkin::m_skinEnabled) { + CSkinItem *item = &SkinItems[ID_EXTBKTABPAGE]; + + if (!item->IGNORED) { + DrawAlpha(hdc, &rctPage, item->COLOR, item->ALPHA, item->COLOR2, item->COLOR2_TRANSPARENT, + item->GRADIENT, item->CORNER, item->BORDERSTYLE, item->imageItem); + goto page_done; + } + } + + if (tabdat->bRefreshWithoutClip) + goto skip_tabs; + + if (dwStyle & TCS_BUTTONS) { + rectTemp = rctPage; + if (dwStyle & TCS_BOTTOM) { + rectTemp.top--; + rectTemp.bottom--; + } else { + rectTemp.bottom--; + rectTemp.top++; + } + if(PluginConfig.m_fillColor) + DrawCustomTabPage(hdc, rectTemp); + else { + MoveToEx(hdc, rectTemp.left, rectTemp.bottom, &pt); + LineTo(hdc, rectTemp.left, rectTemp.top + 1); + LineTo(hdc, rectTemp.right - 1, rectTemp.top + 1); + SelectObject(hdc, PluginConfig.tabConfig.m_hPenShadow); + LineTo(hdc, rectTemp.right - 1, rectTemp.bottom); + LineTo(hdc, rectTemp.left, rectTemp.bottom); + } + } else { + rectTemp = rctPage; + if(PluginConfig.m_fillColor) + DrawCustomTabPage(hdc, rectTemp); + else { + MoveToEx(hdc, rectTemp.left, rectTemp.bottom - 1, &pt); + LineTo(hdc, rectTemp.left, rectTemp.top); + + if (dwStyle & TCS_BOTTOM) { + LineTo(hdc, rectTemp.right - 1, rectTemp.top); + SelectObject(hdc, PluginConfig.tabConfig.m_hPenShadow); + LineTo(hdc, rectTemp.right - 1, rectTemp.bottom - 1); + LineTo(hdc, rctActive.right, rectTemp.bottom - 1); + MoveToEx(hdc, rctActive.left - 2, rectTemp.bottom - 1, &pt); + LineTo(hdc, rectTemp.left - 1, rectTemp.bottom - 1); + SelectObject(hdc, PluginConfig.tabConfig.m_hPenItemShadow); + MoveToEx(hdc, rectTemp.right - 2, rectTemp.top + 1, &pt); + LineTo(hdc, rectTemp.right - 2, rectTemp.bottom - 2); + LineTo(hdc, rctActive.right, rectTemp.bottom - 2); + MoveToEx(hdc, rctActive.left - 2, rectTemp.bottom - 2, &pt); + LineTo(hdc, rectTemp.left, rectTemp.bottom - 2); + } else { + if (rctActive.left >= 0) { + LineTo(hdc, rctActive.left, rctActive.bottom); + if (IsRectEmpty(&rectUpDn)) + MoveToEx(hdc, rctActive.right, rctActive.bottom, &pt); + else { + if (rctActive.right >= rectUpDn.left) + MoveToEx(hdc, rectUpDn.left - SHIFT_FROM_CUT_TO_SPIN + 2, rctActive.bottom + 1, &pt); + else + MoveToEx(hdc, rctActive.right, rctActive.bottom, &pt); + } + LineTo(hdc, rectTemp.right - 2, rctActive.bottom); + } else { + RECT rectItemLeftmost; + UINT nItemLeftmost = FindLeftDownItem(hwnd); + TabCtrl_GetItemRect(hwnd, nItemLeftmost, &rectItemLeftmost); + LineTo(hdc, rectTemp.right - 2, rctActive.bottom); + } + SelectObject(hdc, PluginConfig.tabConfig.m_hPenItemShadow); + LineTo(hdc, rectTemp.right - 2, rectTemp.bottom - 2); + LineTo(hdc, rectTemp.left, rectTemp.bottom - 2); + + SelectObject(hdc, PluginConfig.tabConfig.m_hPenShadow); + MoveToEx(hdc, rectTemp.right - 1, rctActive.bottom, &pt); + LineTo(hdc, rectTemp.right - 1, rectTemp.bottom - 1); + LineTo(hdc, rectTemp.left - 2, rectTemp.bottom - 1); + } + } + } + } + } +page_done: + /* + * if aero is active _and_ the infopanel is visible in the current window, we "flatten" out the top area + * of the tab page by overpainting it black (thus it will appear transparent) + */ + if(isAero && tabdat->helperDat) { + RECT rcLog, rcPage; + POINT pt; + + GetClientRect(hwnd, &rcPage); + if(dwStyle & TCS_BOTTOM) { + GetWindowRect(tabdat->helperDat->hwnd, &rcLog); + pt.y = rcLog.bottom; + pt.x = rcLog.left; + ScreenToClient(hwnd, &pt); + rcPage.top = pt.y + ((nCount > 1 || !(tabdat->helperDat->pContainer->dwFlags & CNT_HIDETABS)) ? tabdat->helperDat->pContainer->tBorder : 0); + FillRect(hdc, &rcPage, CSkin::m_BrushBack); + rcPage.top = 0; + } + GetWindowRect(GetDlgItem(tabdat->helperDat->hwnd, tabdat->helperDat->bType == SESSIONTYPE_IM ? IDC_LOG : IDC_CHAT_LOG), &rcLog); + pt.y = rcLog.top; + pt.x = rcLog.left; + ScreenToClient(hwnd, &pt); + rcPage.bottom = pt.y; + FillRect(hdc, &rcPage, CSkin::m_BrushBack); + } + + uiFlags = 0; + /* + * figure out hottracked item (if any) + */ + + if (tabdat->bRefreshWithoutClip) + goto skip_tabs; + + GetCursorPos(&hti.pt); + ScreenToClient(hwnd, &hti.pt); + hti.flags = 0; + hotItem = TabCtrl_HitTest(hwnd, &hti); + for (i = 0; i < nCount; i++) { + TWindowData* dat = 0; + + if (i != iActive) { + TabCtrl_GetItem(hwnd, i, &item); + if(item.lParam) + dat = (TWindowData *)GetWindowLongPtr((HWND)item.lParam, GWLP_USERDATA); + TabCtrl_GetItemRect(hwnd, i, &rcItem); + if (!bClassicDraw && uiBottom) { + rcItem.top -= PluginConfig.tabConfig.m_bottomAdjust; + rcItem.bottom -= PluginConfig.tabConfig.m_bottomAdjust; + } + if (IntersectRect(&rectTemp, &rcItem, &ps.rcPaint) || bClassicDraw) { + int nHint = 0; + if (!bClassicDraw && !(dwStyle & TCS_BUTTONS)) { + DrawThemesXpTabItem(hdc, i, &rcItem, uiFlags | uiBottom | (i == hotItem ? 4 : 0), tabdat, dat); + DrawItem(tabdat, hdc, &rcItem, nHint | (i == hotItem ? HINT_HOTTRACK : 0), i, dat); + } else { + if(tabdat->fAeroTabs && !CSkin::m_skinEnabled && !(dwStyle & TCS_BUTTONS)) + DrawThemesPartWithAero(tabdat, hdc, 0, (i == hotItem ? PBS_HOT : PBS_NORMAL), &rcItem, dat); + else + DrawItemRect(tabdat, hdc, &rcItem, nHint | (i == hotItem ? HINT_HOTTRACK : 0), i, dat); + DrawItem(tabdat, hdc, &rcItem, nHint | (i == hotItem ? HINT_HOTTRACK : 0), i, dat); + } + } + } + } + /* + * draw the active item + */ + if (!bClassicDraw && uiBottom) { + rctActive.top -= PluginConfig.tabConfig.m_bottomAdjust; + rctActive.bottom -= PluginConfig.tabConfig.m_bottomAdjust; + } + if (rctActive.left >= 0) { + TWindowData* dat = 0; + int nHint = 0; + + rcItem = rctActive; + TabCtrl_GetItem(hwnd, iActive, &item); + if(item.lParam) + dat = (TWindowData *)GetWindowLongPtr((HWND)item.lParam, GWLP_USERDATA); + + if (!bClassicDraw && !(dwStyle & TCS_BUTTONS)) { + InflateRect(&rcItem, 2, 2); + DrawThemesXpTabItem(hdc, iActive, &rcItem, 2 | uiBottom, tabdat, dat); + DrawItem(tabdat, hdc, &rcItem, nHint | HINT_ACTIVE_ITEM, iActive, dat); + } else { + if (!(dwStyle & TCS_BUTTONS)) { + if (iActive == 0) { + rcItem.right += 2; + rcItem.left--; + } else + InflateRect(&rcItem, 2, 0); + } + if(tabdat->fAeroTabs && !CSkin::m_skinEnabled && !(dwStyle & TCS_BUTTONS)) { + if(dwStyle & TCS_BOTTOM) + rcItem.bottom+= 2; + else + rcItem.top-= 2; + DrawThemesPartWithAero(tabdat, hdc, 0, PBS_PRESSED, &rcItem, dat); + } + else + DrawItemRect(tabdat, hdc, &rcItem, HINT_ACTIVATE_RIGHT_SIDE | HINT_ACTIVE_ITEM | nHint, iActive, dat); + DrawItem(tabdat, hdc, &rcItem, HINT_ACTIVE_ITEM | nHint, iActive, dat); + } + } +skip_tabs: + if (hPenOld) + SelectObject(hdc, hPenOld); + + /* + * finally, bitblt the contents of the memory dc to the real dc + */ + //if(!tabdat->pContainer->bSkinned) + if (!tabdat->bRefreshWithoutClip) + ExcludeClipRect(hdcreal, rctClip.left, rctClip.top, rctClip.right, rctClip.bottom); + + if(hpb) + CSkin::FinalizeBufferedPaint(hpb, &rctOrig); + //CMimAPI::m_pfnEndBufferedPaint(hpb, TRUE); + else { + BitBlt(hdcreal, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY); + SelectObject(hdc, bmpOld); + DeleteObject(bmpMem); + DeleteDC(hdc); + } + EndPaint(hwnd, &ps); + return 0; + } + case WM_TIMER: { + if (wParam == TIMERID_HOVER_T && M->GetByte("d_tooltips", 0)) { + POINT pt; + CLCINFOTIP ti = {0}; + ti.cbSize = sizeof(ti); + + KillTimer(hwnd, TIMERID_HOVER_T); + GetCursorPos(&pt); + if (abs(pt.x - ptMouseT.x) < 5 && abs(pt.y - ptMouseT.y) < 5) { + TCITEM item = {0}; + int nItem = 0; + struct TWindowData *dat = 0; + + ti.ptCursor = pt; + //ScreenToClient(hwnd, &pt); + + item.mask = TCIF_PARAM; + nItem = GetTabItemFromMouse(hwnd, &pt); + if (nItem >= 0 && nItem < TabCtrl_GetItemCount(hwnd)) { + TabCtrl_GetItem(hwnd, nItem, &item); + /* + * get the message window data for the session to which this tab item belongs + */ + + if (IsWindow((HWND)item.lParam) && item.lParam != 0) + dat = (struct TWindowData *)GetWindowLongPtr((HWND)item.lParam, GWLP_USERDATA); + if (dat) { + tabdat->fTipActive = TRUE; + ti.isGroup = 0; + ti.hItem = dat->hContact; + ti.isTreeFocused = 0; + CallService("mToolTip/ShowTip", 0, (LPARAM)&ti); + } + } + } + } + break; + } + case WM_MOUSEWHEEL: { + short amount = (short)(HIWORD(wParam)); + if (lParam != -1) + break; + if (amount > 0) + SendMessage(GetParent(hwnd), DM_SELECTTAB, DM_SELECT_PREV, 0); + else if (amount < 0) + SendMessage(GetParent(hwnd), DM_SELECTTAB, DM_SELECT_NEXT, 0); + InvalidateRect(hwnd, NULL, FALSE); + break; + } + case WM_USER + 100: { + if (tabdat->fTipActive) { + tabdat->fTipActive = FALSE; + CallService("mToolTip/HideTip", 0, 0); + } + } + } + return CallWindowProc(OldTabControlClassProc, hwnd, msg, wParam, lParam); +} + +/* + * load the tab control configuration data (colors, fonts, flags... + */ + +void TSAPI ReloadTabConfig() +{ + NONCLIENTMETRICS nclim; + int i = 0; + + PluginConfig.tabConfig.m_hPenLight = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT)); + PluginConfig.tabConfig.m_hPenShadow = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)); + PluginConfig.tabConfig.m_hPenItemShadow = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)); + + nclim.cbSize = sizeof(nclim); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &nclim, 0); + PluginConfig.tabConfig.m_hMenuFont = CreateFontIndirect(&nclim.lfMessageFont); + + while (tabcolors[i].szKey != NULL) { + PluginConfig.tabConfig.colors[i] = M->GetDword(CSkin::m_skinEnabled ? tabcolors[i].szSkinnedKey : tabcolors[i].szKey, GetSysColor(tabcolors[i].defclr)); + i++; + } + PluginConfig.tabConfig.m_brushes[0] = CreateSolidBrush(PluginConfig.tabConfig.colors[4]); + PluginConfig.tabConfig.m_brushes[1] = CreateSolidBrush(PluginConfig.tabConfig.colors[5]); + PluginConfig.tabConfig.m_brushes[2] = CreateSolidBrush(PluginConfig.tabConfig.colors[6]); + PluginConfig.tabConfig.m_brushes[3] = CreateSolidBrush(PluginConfig.tabConfig.colors[7]); + + PluginConfig.tabConfig.m_bottomAdjust = (int)M->GetDword("bottomadjust", 0); + PluginConfig.tabConfig.m_fixedwidth = M->GetDword("fixedwidth", FIXED_TAB_SIZE); + + PluginConfig.tabConfig.m_fixedwidth = (PluginConfig.tabConfig.m_fixedwidth < 60 ? 60 : PluginConfig.tabConfig.m_fixedwidth); +} + +void TSAPI FreeTabConfig() +{ + int i; + + if(PluginConfig.tabConfig.m_hPenItemShadow) + DeleteObject(PluginConfig.tabConfig.m_hPenItemShadow); + + if(PluginConfig.tabConfig.m_hPenLight) + DeleteObject(PluginConfig.tabConfig.m_hPenLight); + + if(PluginConfig.tabConfig.m_hPenShadow) + DeleteObject(PluginConfig.tabConfig.m_hPenShadow); + + if(PluginConfig.tabConfig.m_hMenuFont) + DeleteObject(PluginConfig.tabConfig.m_hMenuFont); + + for(i = 0; i < 4; i++) { + if(PluginConfig.tabConfig.m_brushes[i]) { + DeleteObject(PluginConfig.tabConfig.m_brushes[i]); + PluginConfig.tabConfig.m_brushes[i] = 0; + } + } + ZeroMemory(&PluginConfig.tabConfig, sizeof(myTabCtrl)); +} + +/* + * options dialog for setting up tab options + */ + +static bool tconfig_init = false; + +INT_PTR CALLBACK DlgProcTabConfig(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: { + tconfig_init = false; + TranslateDialogDefault(hwndDlg); + SendMessage(hwndDlg, WM_USER + 100, 0, 0); + tconfig_init = true; + return TRUE; + } + case WM_USER + 100: { + DWORD dwFlags = M->GetDword("tabconfig", TCF_DEFAULT); + int i = 0; + + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPIN, UDM_SETRANGE, 0, MAKELONG(10, 0)); + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPIN, UDM_SETPOS, 0, (int)M->GetByte(CSkin::m_skinEnabled ? "S_tborder" : "tborder", 2)); + SetDlgItemInt(hwndDlg, IDC_TABBORDER, (int)M->GetByte(CSkin::m_skinEnabled ? "S_tborder" : "tborder", 2), FALSE);; + + SendDlgItemMessage(hwndDlg, IDC_BOTTOMTABADJUSTSPIN, UDM_SETRANGE, 0, MAKELONG(3, -3)); + SendDlgItemMessage(hwndDlg, IDC_BOTTOMTABADJUSTSPIN, UDM_SETPOS, 0, PluginConfig.tabConfig.m_bottomAdjust); + SetDlgItemInt(hwndDlg, IDC_BOTTOMTABADJUST, PluginConfig.tabConfig.m_bottomAdjust, TRUE); + + SendDlgItemMessage(hwndDlg, IDC_TABWIDTHSPIN, UDM_SETRANGE, 0, MAKELONG(400, 50)); + SendDlgItemMessage(hwndDlg, IDC_TABWIDTHSPIN, UDM_SETPOS, 0, PluginConfig.tabConfig.m_fixedwidth); + SetDlgItemInt(hwndDlg, IDC_TABWIDTH, PluginConfig.tabConfig.m_fixedwidth, TRUE); + + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTER, UDM_SETRANGE, 0, MAKELONG(50, 0)); + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTERRIGHT, UDM_SETRANGE, 0, MAKELONG(50, 0)); + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTERTOP, UDM_SETRANGE, 0, MAKELONG(40, 0)); + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTERBOTTOM, UDM_SETRANGE, 0, MAKELONG(40, 0)); + + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTER, UDM_SETPOS, 0, (int)M->GetByte(CSkin::m_skinEnabled ? "S_tborder_outer_left" : "tborder_outer_left", 2)); + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTERRIGHT, UDM_SETPOS, 0, (int)M->GetByte(CSkin::m_skinEnabled ? "S_tborder_outer_right" : "tborder_outer_right", 2)); + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTERTOP, UDM_SETPOS, 0, (int)M->GetByte(CSkin::m_skinEnabled ? "S_tborder_outer_top" : "tborder_outer_top", 2)); + SendDlgItemMessage(hwndDlg, IDC_TABBORDERSPINOUTERBOTTOM, UDM_SETPOS, 0, (int)M->GetByte(CSkin::m_skinEnabled ? "S_tborder_outer_bottom" : "tborder_outer_bottom", 2)); + + SendDlgItemMessage(hwndDlg, IDC_SPIN1, UDM_SETRANGE, 0, MAKELONG(10, 1)); + SendDlgItemMessage(hwndDlg, IDC_SPIN3, UDM_SETRANGE, 0, MAKELONG(10, 1)); + SendDlgItemMessage(hwndDlg, IDC_SPIN1, UDM_SETPOS, 0, (LPARAM)M->GetByte("y-pad", 3)); + SendDlgItemMessage(hwndDlg, IDC_SPIN3, UDM_SETPOS, 0, (LPARAM)M->GetByte("x-pad", 4)); + SetDlgItemInt(hwndDlg, IDC_TABPADDING, (int)M->GetByte("y-pad", 3), FALSE);; + SetDlgItemInt(hwndDlg, IDC_HTABPADDING, (int)M->GetByte("x-pad", 4), FALSE);; + return 0; + } + case WM_NOTIFY: + switch (((LPNMHDR) lParam)->idFrom) { + case 0: + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: { + int i = 0; + BOOL translated; + int fixedWidth; + + struct TContainerData *pContainer = pFirstContainer; + + M->WriteByte(SRMSGMOD_T, "y-pad", (BYTE)(GetDlgItemInt(hwndDlg, IDC_TABPADDING, NULL, FALSE))); + M->WriteByte(SRMSGMOD_T, "x-pad", (BYTE)(GetDlgItemInt(hwndDlg, IDC_HTABPADDING, NULL, FALSE))); + M->WriteByte(SRMSGMOD_T, "tborder", (BYTE) GetDlgItemInt(hwndDlg, IDC_TABBORDER, &translated, FALSE)); + M->WriteByte(SRMSGMOD_T, CSkin::m_skinEnabled ? "S_tborder_outer_left" : "tborder_outer_left", (BYTE) GetDlgItemInt(hwndDlg, IDC_TABBORDEROUTER, &translated, FALSE)); + M->WriteByte(SRMSGMOD_T, CSkin::m_skinEnabled ? "S_tborder_outer_right" : "tborder_outer_right", (BYTE) GetDlgItemInt(hwndDlg, IDC_TABBORDEROUTERRIGHT, &translated, FALSE)); + M->WriteByte(SRMSGMOD_T, CSkin::m_skinEnabled ? "S_tborder_outer_top" : "tborder_outer_top", (BYTE) GetDlgItemInt(hwndDlg, IDC_TABBORDEROUTERTOP, &translated, FALSE)); + M->WriteByte(SRMSGMOD_T, CSkin::m_skinEnabled ? "S_tborder_outer_bottom" : "tborder_outer_bottom", (BYTE) GetDlgItemInt(hwndDlg, IDC_TABBORDEROUTERBOTTOM, &translated, FALSE)); + M->WriteDword(SRMSGMOD_T, "bottomadjust", GetDlgItemInt(hwndDlg, IDC_BOTTOMTABADJUST, &translated, TRUE)); + + fixedWidth = GetDlgItemInt(hwndDlg, IDC_TABWIDTH, &translated, FALSE); + fixedWidth = (fixedWidth < 60 ? 60 : fixedWidth); + M->WriteDword(SRMSGMOD_T, "fixedwidth", fixedWidth); + FreeTabConfig(); + ReloadTabConfig(); + while (pContainer) { + TabCtrl_SetPadding(GetDlgItem(pContainer->hwnd, IDC_MSGTABS), GetDlgItemInt(hwndDlg, IDC_HTABPADDING, NULL, FALSE), GetDlgItemInt(hwndDlg, IDC_TABPADDING, NULL, FALSE)); + RedrawWindow(GetDlgItem(pContainer->hwnd, IDC_MSGTABS), NULL, NULL, RDW_INVALIDATE | RDW_ERASE); + pContainer = pContainer->pNextContainer; + } + return TRUE; + } + } + break; + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_TABWIDTH: + case IDC_TABPADDING: + case IDC_HTABPADDING: + case IDC_TABBORDER: + case IDC_TABBORDEROUTER: + case IDC_TABBORDEROUTERBOTTOM: + case IDC_TABBORDEROUTERRIGHT: + case IDC_TABBORDEROUTERTOP: + if (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus()) + return TRUE; + break; + + default: + break; + + } + if(tconfig_init) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + + case WM_DESTROY: + tconfig_init = false; + } + return FALSE; +} + -- cgit v1.2.3