From 48540940b6c28bb4378abfeb500ec45a625b37b6 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Tue, 15 May 2012 10:38:20 +0000 Subject: initial commit git-svn-id: http://svn.miranda-ng.org/main/trunk@2 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/tabsrmm/src/sidebar.cpp | 1232 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1232 insertions(+) create mode 100644 plugins/tabsrmm/src/sidebar.cpp (limited to 'plugins/tabsrmm/src/sidebar.cpp') diff --git a/plugins/tabsrmm/src/sidebar.cpp b/plugins/tabsrmm/src/sidebar.cpp new file mode 100644 index 0000000000..85dbf3e6c3 --- /dev/null +++ b/plugins/tabsrmm/src/sidebar.cpp @@ -0,0 +1,1232 @@ +/* + * 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-2010 by silvercircle _at_ gmail _dot_ com and contributors + * + * $Id: sidebar.cpp 12647 2010-09-09 22:17:49Z silvercircle $ + * + * the contact switch bar on the left (or right) side + * + */ + +#include "commonheaders.h" + +extern int TSAPI TBStateConvert2Flat(int state); +extern int TSAPI RBStateConvert2Flat(int state); +extern void TSAPI FillTabBackground(const HDC hdc, int iStateId, const TWindowData* dat, RECT* rc); + +TSideBarLayout CSideBar::m_layouts[CSideBar::NR_LAYOUTS] = { + { + _T("Like tabs, vertical text orientation"), + 26, 30, + SIDEBARLAYOUT_DYNHEIGHT | SIDEBARLAYOUT_VERTICALORIENTATION, + CSideBar::m_DefaultContentRenderer, + CSideBar::m_DefaultBackgroundRenderer, + NULL, + NULL, + SIDEBARLAYOUT_VERTICAL + }, + { + _T("Compact layout, horizontal buttons"), + 100, 24, + 0, + CSideBar::m_DefaultContentRenderer, + CSideBar::m_DefaultBackgroundRenderer, + NULL, + NULL, + SIDEBARLAYOUT_NORMAL + }, + { + _T("Advanced layout with avatars"), + 140, 40, + SIDEBARLAYOUT_NOCLOSEBUTTONS, + CSideBar::m_AdvancedContentRenderer, + CSideBar::m_DefaultBackgroundRenderer, + NULL, + NULL, + SIDEBARLAYOUT_NORMAL + }, + { + _T("Advanced with avatars, vertical orientation"), + 40, 40, + SIDEBARLAYOUT_DYNHEIGHT | SIDEBARLAYOUT_VERTICALORIENTATION | SIDEBARLAYOUT_NOCLOSEBUTTONS, + CSideBar::m_AdvancedContentRenderer, + CSideBar::m_DefaultBackgroundRenderer, + CSideBar::m_measureAdvancedVertical, + NULL, + SIDEBARLAYOUT_VERTICAL + } +}; + +CSideBarButton::CSideBarButton(const TWindowData *dat, CSideBar *sideBar) +{ + m_dat = dat; + m_id = reinterpret_cast(dat->hContact); // set the control id + m_sideBar = sideBar; + _create(); +} + +CSideBarButton::CSideBarButton(const UINT id, CSideBar *sideBar) +{ + m_dat = 0; + m_id = id; + m_sideBar = sideBar; + _create(); +} + +/** + * Internal method to create the button item and configure the associated button control + */ +void CSideBarButton::_create() +{ + m_hwnd = 0; + m_isTopAligned = true; + m_sz.cx = m_sz.cy = 0; + + m_hwnd = ::CreateWindowEx(0, _T("TSButtonClass"), _T(""), WS_CHILD | WS_TABSTOP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + 0, 0, 40, 40, m_sideBar->getScrollWnd(), reinterpret_cast(m_id), g_hInst, NULL); + + if(m_hwnd) { + ::SendMessage(m_hwnd, BUTTONSETASSIDEBARBUTTON, 0, (LPARAM)this); + ::SendMessage(m_hwnd, BUTTONSETASFLATBTN, 1, 1); + ::SendMessage(m_hwnd, BUTTONSETASFLATBTN + 10, 1, 1); + ::SendMessage(m_hwnd, BUTTONSETASFLATBTN + 12, 0, (LPARAM)m_sideBar->getContainer()); + m_buttonControl = (MButtonCtrl *)::GetWindowLongPtr(m_hwnd, 0); + } + else + delete this; + + if(m_id == IDC_SIDEBARUP || m_id == IDC_SIDEBARDOWN) + ::SetParent(m_hwnd, m_sideBar->getContainer()->hwnd); +} + +CSideBarButton::~CSideBarButton() +{ + if(m_hwnd) { + ::SendMessage(m_hwnd, BUTTONSETASSIDEBARBUTTON, 0, 0); // make sure, the button will no longer call us back + ::DestroyWindow(m_hwnd); + } +} + +void CSideBarButton::Show(const int showCmd) const +{ + ::ShowWindow(m_hwnd, showCmd); +} + +/** + * Measure the metrics for the current item. The side bar layouting will call this + * whenever a layout with a dynamic height is active). For fixed dimension layouts, + * m_elementWidth and m_elementHeight will be used. + * + * @return SIZE&: reference to the item's size member. The caller may use cx and cy values + * to determine further layouting actions. + */ +const SIZE& CSideBarButton::measureItem() +{ + SIZE sz; + + if(m_sideBarLayout->pfnMeasureItem) + m_sideBarLayout->pfnMeasureItem(this); // use the current layout's function, if available, else use default + else { + HDC dc = ::GetDC(m_hwnd); + TCHAR tszLabel[255]; + + HFONT oldFont = reinterpret_cast(::SelectObject(dc, ::GetStockObject(DEFAULT_GUI_FONT))); + + mir_sntprintf(tszLabel, 255, _T("%s"), m_dat->newtitle); + ::GetTextExtentPoint32(dc, tszLabel, lstrlen(tszLabel), &sz); + + sz.cx += 28; + if(m_dat->pContainer->dwFlagsEx & TCF_CLOSEBUTTON) + sz.cx += 20; + + if(m_sideBarLayout->dwFlags & CSideBar::SIDEBARLAYOUT_VERTICALORIENTATION) + m_sz.cy = sz.cx; + else + m_sz.cx = sz.cx; + + ::SelectObject(dc, oldFont); + ::ReleaseDC(m_hwnd, dc); + } + return(m_sz); +} +/** + * Render the button item. Callback from the button window procedure + * + * @param ctl MButtonCtrl *: pointer to the private button data structure + * @param hdc HDC: device context for painting + */ +void CSideBarButton::RenderThis(const HDC hdc) const +{ + RECT rc; + LONG cx, cy; + HDC hdcMem = 0; + bool fVertical = (m_sideBarLayout->dwFlags & CSideBar::SIDEBARLAYOUT_VERTICALORIENTATION) ? true : false; + HBITMAP hbmMem, hbmOld; + HANDLE hbp = 0; + + ::GetClientRect(m_hwnd, &rc); + + if(m_id == IDC_SIDEBARUP || m_id == IDC_SIDEBARDOWN) + fVertical = false; + + if(fVertical) { + cx = rc.bottom; + cy = rc.right; + } + else { + cx = rc.right; + cy = rc.bottom; + } + + hdcMem = ::CreateCompatibleDC(hdc); + + if(fVertical) { + RECT rcFlipped = {0,0,cx,cy}; + hbmMem = CSkin::CreateAeroCompatibleBitmap(rcFlipped, hdcMem); + rc = rcFlipped; + } + else + hbmMem = CSkin::CreateAeroCompatibleBitmap(rc, hdcMem); + + hbmOld = reinterpret_cast(::SelectObject(hdcMem, hbmMem)); + + HFONT hFontOld = reinterpret_cast(::SelectObject(hdcMem, ::GetStockObject(DEFAULT_GUI_FONT))); + + m_sideBarLayout->pfnBackgroundRenderer(hdcMem, &rc, this); + m_sideBarLayout->pfnContentRenderer(hdcMem, &rc, this); + + ::SelectObject(hdcMem, hFontOld); + + /* + * for vertical tabs, we did draw to a rotated rectangle, so we now must rotate the + * final bitmap back to it's original orientation + */ + + if(fVertical) { + ::SelectObject(hdcMem, hbmOld); + + FIBITMAP *fib = FIF->FI_CreateDIBFromHBITMAP(hbmMem); + FIBITMAP *fib_new = FIF->FI_RotateClassic(fib, 90.0f); + FIF->FI_Unload(fib); + ::DeleteObject(hbmMem); + hbmMem = FIF->FI_CreateHBITMAPFromDIB(fib_new); + FIF->FI_Unload(fib_new); + hbmOld =reinterpret_cast(::SelectObject(hdcMem, hbmMem)); + ::BitBlt(hdc, 0, 0, cy, cx, hdcMem, 0, 0, SRCCOPY); + ::SelectObject(hdcMem, hbmOld); + ::DeleteObject(hbmMem); + ::DeleteDC(hdcMem); + } + else { + ::BitBlt(hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY); + ::SelectObject(hdcMem, hbmOld); + ::DeleteObject(hbmMem); + ::DeleteDC(hdcMem); + } + return; +} + +/** + * render basic button content like nickname and icon. Used for the basic layouts + * only. Pretty much the same code as used for the tabs. + * + * @param hdc : target device context + * @param rcItem : rectangle to render into + */ +void CSideBarButton::renderIconAndNick(const HDC hdc, const RECT *rcItem) const +{ + HICON hIcon; + RECT rc = *rcItem; + DWORD dwTextFlags = DT_SINGLELINE | DT_VCENTER; + int stateId = m_buttonControl->stateId; + int iSize = 16; + const TContainerData *pContainer = m_sideBar->getContainer(); + + if(m_dat && pContainer) { + hIcon = m_dat->cache->getIcon(iSize); + + if (m_dat->mayFlashTab == FALSE || (m_dat->mayFlashTab == TRUE && m_dat->bTabFlash != 0) || !(pContainer->dwFlagsEx & TCF_FLASHICON)) { + DWORD ix = rc.left + 4; + DWORD iy = (rc.bottom + rc.top - iSize) / 2; + if (m_dat->dwFlagsEx & MWF_SHOW_ISIDLE && PluginConfig.m_IdleDetect) + CSkin::DrawDimmedIcon(hdc, ix, iy, iSize, iSize, hIcon, 180); + else + ::DrawIconEx(hdc, ix, iy, hIcon, iSize, iSize, 0, NULL, DI_NORMAL | DI_COMPAT); + } + + rc.left += (iSize + 7); + + /* + * draw the close button if enabled + */ + if(m_sideBar->getContainer()->dwFlagsEx & TCF_CLOSEBUTTON) { + if(m_sideBar->getHoveredClose() != this) + CSkin::m_default_bf.SourceConstantAlpha = 150; + + CMimAPI::m_MyAlphaBlend(hdc, rc.right - 20, (rc.bottom + rc.top - 16) / 2, 16, 16, CSkin::m_tabCloseHDC, + 0, 0, 16, 16, CSkin::m_default_bf); + + rc.right -= 19; + CSkin::m_default_bf.SourceConstantAlpha = 255; + } + + ::SetBkMode(hdc, TRANSPARENT); + + if (m_dat->mayFlashTab == FALSE || (m_dat->mayFlashTab == TRUE && m_dat->bTabFlash != 0) || !(pContainer->dwFlagsEx & TCF_FLASHLABEL)) { + bool fIsActive = (m_sideBar->getActiveItem() == this ? true : false); + COLORREF clr = 0; + dwTextFlags |= DT_WORD_ELLIPSIS; + + if (fIsActive || stateId == PBS_PRESSED) + clr = PluginConfig.tabConfig.colors[1]; + else if (stateId == PBS_HOT) + clr = PluginConfig.tabConfig.colors[3]; + else + clr = PluginConfig.tabConfig.colors[0]; + + CSkin::RenderText(hdc, m_buttonControl->hThemeButton, m_dat->newtitle, &rc, dwTextFlags, CSkin::m_glowSize, clr); + } + } +} + +/** + * test if we have the mouse pointer over our close button. + * @return: 1 if the pointer is inside the button's rect, -1 otherwise + */ +int CSideBarButton::testCloseButton() const +{ + if(m_id == IDC_SIDEBARUP || m_id == IDC_SIDEBARDOWN) // scroller buttons don't have a close button + return(-1); + + if(m_sideBar->getContainer()->dwFlagsEx & TCF_CLOSEBUTTON && !(getLayout()->dwFlags & CSideBar::SIDEBARLAYOUT_NOCLOSEBUTTONS)) { + POINT pt; + ::GetCursorPos(&pt); + ::ScreenToClient(m_hwnd, &pt); + RECT rc; + + ::GetClientRect(m_hwnd, &rc); + if(getLayout()->dwFlags & CSideBar::SIDEBARLAYOUT_VERTICALORIENTATION) { + rc.bottom = rc.top + 18; rc.top += 2; + rc.left += 2; rc.right -= 2; + if(::PtInRect(&rc, pt)) + return(1); + } + else { + rc.bottom -= 4; rc.top += 4; + rc.right -= 3; rc.left = rc.right - 16; + if(::PtInRect(&rc, pt)) + return(1); + } + } + return(-1); +} +/** + * call back from the button window procedure. Activate my session + */ +void CSideBarButton::activateSession() const +{ + if(m_dat) + ::SendMessage(m_dat->hwnd, DM_ACTIVATEME, 0, 0); // the child window will activate itself +} + +/** + * show the context menu (same as on tabs + */ +void CSideBarButton::invokeContextMenu() +{ + const TContainerData* pContainer = m_sideBar->getContainer(); + + if(pContainer) { + TSideBarNotify tsn = {0}; + tsn.nmHdr.code = NM_RCLICK; + tsn.nmHdr.idFrom = 5000; + tsn.nmHdr.hwndFrom = ::GetDlgItem(pContainer->hwnd, 5000); + tsn.dat = m_dat; + ::SendMessage(pContainer->hwnd, WM_NOTIFY, 0, reinterpret_cast(&tsn)); + } +} + +CSideBar::CSideBar(TContainerData *pContainer) +{ + m_pContainer = pContainer; + m_up = m_down = 0; + m_hwndScrollWnd = 0; + m_buttonlist.clear(); + m_activeItem = 0; + m_isVisible = true; + + Init(true); +} + +CSideBar::~CSideBar() +{ + destroyScroller(); + m_buttonlist.clear(); + + if(m_hwndScrollWnd) + ::DestroyWindow(m_hwndScrollWnd); +} + +void CSideBar::Init(const bool fForce) +{ + m_iTopButtons = m_iBottomButtons = 0; + m_topHeight = m_bottomHeight = 0; + m_firstVisibleOffset = 0; + m_totalItemHeight = 0; + + m_uLayout = (m_pContainer->dwFlagsEx & 0xff000000) >> 24; + m_uLayout = ((m_uLayout >= 0 && m_uLayout < NR_LAYOUTS) ? m_uLayout : 0); + + m_currentLayout = &m_layouts[m_uLayout]; + + m_dwFlags = m_currentLayout->dwFlags; + + m_dwFlags = (m_pContainer->dwFlagsEx & TCF_SBARLEFT ? m_dwFlags | SIDEBARORIENTATION_LEFT : m_dwFlags & ~SIDEBARORIENTATION_LEFT); + m_dwFlags = (m_pContainer->dwFlagsEx & TCF_SBARRIGHT ? m_dwFlags | SIDEBARORIENTATION_RIGHT : m_dwFlags & ~SIDEBARORIENTATION_RIGHT); + + if(m_pContainer->dwFlags & CNT_SIDEBAR) { + if(m_hwndScrollWnd == 0) + m_hwndScrollWnd = ::CreateWindowEx(0, _T("TS_SideBarClass"), _T(""), WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE | WS_CHILD, + 0, 0, m_width, 40, m_pContainer->hwnd, reinterpret_cast(5000), g_hInst, this); + + m_isActive = true; + m_isVisible = m_isActive ? m_isVisible : true; + createScroller(); + m_elementHeight = m_currentLayout->height; + m_elementWidth = m_currentLayout->width; + m_width = m_elementWidth + 4; + populateAll(); + if(m_activeItem) + setActiveItem(m_activeItem); + } + else { + destroyScroller(); + m_width = 0; + m_isActive = m_isVisible = false; + m_activeItem = 0; + + removeAll(); + if(m_hwndScrollWnd) + ::DestroyWindow(m_hwndScrollWnd); + m_hwndScrollWnd = 0; + } +} + +/** + * sets visibility status for the sidebar. An invisible sidebar (collapsed + * by the button in the message dialog) will remain active, it will, however + * not do any drawing or other things that are only visually important. + * + * @param fNewVisible : set the new visibility status + */ +void CSideBar::setVisible(bool fNewVisible) +{ + m_isVisible = fNewVisible; + + /* + * only needed on hiding. Layout() will do it when showing it + */ + + if(!m_isVisible) + showAll(SW_HIDE); + else { + m_up->Show(SW_SHOW); + m_down->Show(SW_SHOW); + } +} + +/** + * Create both scrollbar buttons which can be used to scroll the switchbar + * up and down. + */ +void CSideBar::createScroller() +{ + if(m_up == 0) + m_up = new CSideBarButton(IDC_SIDEBARUP, this); + if(m_down == 0) + m_down = new CSideBarButton(IDC_SIDEBARDOWN, this); + + m_up->setLayout(m_currentLayout); + m_down->setLayout(m_currentLayout); +} + +/** + * Destroy the scroller buttons. + */ +void CSideBar::destroyScroller() +{ + if(m_up) { + delete m_up; + m_up = 0; + } + if(m_down) { + delete m_down; + m_down = 0; + } +} + +/** + * remove all buttons from the current list + * Does not remove the sessions. This is basically only used when switching + * from a sidebar to a tabbed interface + */ +void CSideBar::removeAll() +{ + ButtonIterator item = m_buttonlist.begin(); + + while(item != m_buttonlist.end()) { + delete *item; + item++; + } + m_buttonlist.clear(); +} + +/** + * popuplate the side bar with all sessions inside the current window. Information + * is gathered from the tab control, which remains active (but invisible) when the + * switch bar is in use. + * + * This is needed when the user switches from tabs to a switchbar layout while a + * window is open. + */ +void CSideBar::populateAll() +{ + HWND hwndTab = ::GetDlgItem(m_pContainer->hwnd, IDC_MSGTABS); + + if(hwndTab) { + int iItems = (int)TabCtrl_GetItemCount(hwndTab); + TCITEM item = {0}; + + item.mask = TCIF_PARAM; + std::vector::iterator b_item; + + m_iTopButtons = 0; + + for(int i = 0; i < iItems; i++) { + TabCtrl_GetItem(hwndTab, i, &item); + if(item.lParam && ::IsWindow((HWND)item.lParam)) { + TWindowData *dat = (TWindowData *)::GetWindowLongPtr((HWND)item.lParam, GWLP_USERDATA); + if(dat) { + if((b_item = findSession(dat)) == m_buttonlist.end()) + addSession(dat, i); + else { + (*b_item)->setLayout(m_currentLayout); + if(m_dwFlags & SIDEBARLAYOUT_VERTICALORIENTATION) { + (*b_item)->measureItem(); + m_topHeight += ((*b_item)->getHeight() + 1); + } + else + m_topHeight += (m_elementHeight + 1); + } + } + } + } + } +} +/** + * Add a new session to the switchbar. + * + * @param dat _MessageWindowData *: session data for a client session. Must be fully initialized + * (that is, it can only be used after WM_INITIALOG completed). + * position: -1 = append, otherwise insert it at the given position + */ +void CSideBar::addSession(const TWindowData *dat, int position) +{ + if(!m_isActive) + return; + + CSideBarButton *item = new CSideBarButton(dat, this); + + item->setLayout(m_currentLayout); + + if(m_dwFlags & SIDEBARLAYOUT_DYNHEIGHT) { + SIZE sz = item->measureItem(); + m_topHeight += (sz.cy + 1); + } + else + m_topHeight += (m_elementHeight + 1); + + m_iTopButtons++; + if(position == -1 || (size_t)position >= m_buttonlist.size()) + m_buttonlist.push_back(item); + else { + ButtonIterator it = m_buttonlist.begin() + position; + m_buttonlist.insert(it, item); + } + SendDlgItemMessage(dat->hwnd, dat->bType == SESSIONTYPE_IM ? IDC_TOGGLESIDEBAR : IDC_CHAT_TOGGLESIDEBAR, BM_SETIMAGE, IMAGE_ICON, + (LPARAM)(m_dwFlags & SIDEBARORIENTATION_LEFT ? PluginConfig.g_buttonBarIcons[ICON_DEFAULT_LEFT] : PluginConfig.g_buttonBarIcons[ICON_DEFAULT_RIGHT])); + + Invalidate(); +} + +/** + * Remove a new session from the switchbar. + * + * @param dat _MessageWindowData *: session data for a client session. + */ +HRESULT CSideBar::removeSession(const TWindowData *dat) +{ + if(dat) { + std::vector::iterator item = findSession(dat); + + if(item != m_buttonlist.end()) { + m_iTopButtons--; + if(m_dwFlags & SIDEBARLAYOUT_DYNHEIGHT) { + SIZE sz = (*item)->getSize(); + m_topHeight -= (sz.cy + 1); + } + else + m_topHeight -= (m_elementHeight + 1); + delete *item; + m_buttonlist.erase(item); + Invalidate(); + return(S_OK); + } + } + return(S_FALSE); +} + +/** + * make sure the given item is visible in a scrolled switch bar + * + * @param item CSideBarButtonItem*: the item which must be visible in the switch bar + */ +void CSideBar::scrollIntoView(const CSideBarButton *item) +{ + LONG spaceUsed = 0, itemHeight; + bool fNeedLayout = false; + + if(!m_isActive) + return; + + if(item == 0) + item = m_activeItem; + + ButtonIterator it = m_buttonlist.begin(); + + while(it != m_buttonlist.end()) { + itemHeight = (*it)->getHeight(); + spaceUsed += (itemHeight + 1); + if(*it == item ) + break; + it++; + } + + RECT rc; + GetClientRect(m_hwndScrollWnd, &rc); + + if(m_topHeight <= rc.bottom) { + m_firstVisibleOffset = 0; + Layout(); + return; + } + if(it == m_buttonlist.end() || (it == m_buttonlist.begin() && m_firstVisibleOffset == 0)) { + Layout(); + return; // do nothing for the first item and .end() should not really happen + } + + if(spaceUsed <= rc.bottom && spaceUsed - (itemHeight + 1) >= m_firstVisibleOffset) { + Layout(); + return; // item fully visible, do nothing + } + + /* + * button partially or not at all visible at the top + */ + if(spaceUsed < m_firstVisibleOffset || spaceUsed - (itemHeight + 1) < m_firstVisibleOffset) { + m_firstVisibleOffset = spaceUsed - (itemHeight + 1); + fNeedLayout = true; + } else { + if(spaceUsed > rc.bottom) { // partially or not at all visible at the bottom + fNeedLayout = true; + m_firstVisibleOffset = spaceUsed - rc.bottom; + } + } + + //if(fNeedLayout) + Layout(); +} +/** + * Invalidate the button associated with the given session. + * + * @param dat _MessageWindowData*: Session data + */ +void CSideBar::updateSession(const TWindowData *dat) +{ + if(!m_isVisible || !m_isActive) + return; + + ButtonIterator item = findSession(dat); + if(item != m_buttonlist.end()) { + if(m_dwFlags & SIDEBARLAYOUT_DYNHEIGHT) { + LONG oldHeight = (*item)->getHeight(); + m_topHeight -= (oldHeight + 1); + SIZE sz = (*item)->measureItem(); + m_topHeight += (sz.cy + 1); + if(sz.cy != oldHeight) { + Invalidate(); + ::InvalidateRect((*item)->getHwnd(), NULL, TRUE); + } + else + ::InvalidateRect((*item)->getHwnd(), NULL, FALSE); + } + else + ::InvalidateRect((*item)->getHwnd(), NULL, FALSE); + } +} + +/** + * Sets the active session item + * called from the global update handler in msgdialog/group room window + * on every tab activation to ensure consistency + * + * @param dat _MessageWindowData*: session data + * + * @return The previously active item (that can be zero) + */ +const CSideBarButton* CSideBar::setActiveItem(const TWindowData *dat) +{ + ButtonIterator item = findSession(dat); + if(item != m_buttonlist.end()) { + return(setActiveItem(*item)); + } + return(0); +} + +/** + * Layout the buttons within the available space... ensure that buttons are + * set to invisible if there is not enough space. Also, update the state of + * the scroller buttons + * + * @param rc RECT*:the window rectangle + * + * @param fOnlyCalc bool: if false (default), window positions will be updated, otherwise, + * the method will only calculate the layout parameters. A final call to + * Layout() with the parameter set to false is required to perform the + * position update. + */ +void CSideBar::Layout(const RECT *rc, bool fOnlyCalc) +{ + if(!m_isVisible) + return; + + RECT rcWnd; + + rc = &rcWnd; + ::GetClientRect(m_hwndScrollWnd, &rcWnd); + + if(m_currentLayout->pfnLayout) { + m_currentLayout->pfnLayout(this, const_cast(rc)); + return; + } + + HDWP hdwp = ::BeginDeferWindowPos(1); + + int topCount = 0, bottomCount = 0; + size_t i = 0, j = 0; + BOOL topEnabled = FALSE, bottomEnabled = FALSE; + HWND hwnd; + LONG spaceUsed = 0; + DWORD dwFlags = SWP_NOZORDER|SWP_NOACTIVATE; + LONG iSpaceAvail = rc->bottom; + + m_firstVisibleOffset = max(0, m_firstVisibleOffset); + + ButtonIterator item = m_buttonlist.begin(); + + m_totalItemHeight = 0; + + LONG height = m_elementHeight; + + while (item != m_buttonlist.end()) { + hwnd = (*item)->getHwnd(); + + if(m_dwFlags & SIDEBARLAYOUT_DYNHEIGHT) + height = (*item)->getHeight(); + + if(spaceUsed > iSpaceAvail || m_totalItemHeight + height < m_firstVisibleOffset) { + ::ShowWindow(hwnd, SW_HIDE); + item++; + m_totalItemHeight += (height + 1); + continue; + } + + if ((*item)->isTopAligned()) { + if(m_totalItemHeight <= m_firstVisibleOffset) { // partially visible + if(!fOnlyCalc) + hdwp = ::DeferWindowPos(hdwp, hwnd, 0, 2, -(m_firstVisibleOffset - m_totalItemHeight), + m_elementWidth, height, SWP_SHOWWINDOW | dwFlags); + spaceUsed += ((height + 1) - (m_firstVisibleOffset - m_totalItemHeight)); + m_totalItemHeight += (height + 1); + } + else { + if(!fOnlyCalc) + hdwp = ::DeferWindowPos(hdwp, hwnd, 0, 2, spaceUsed, + m_elementWidth, height, SWP_SHOWWINDOW | dwFlags); + spaceUsed += (height + 1); + m_totalItemHeight += (height + 1); + } + }/* + else { + }*/ + item++; + } + topEnabled = m_firstVisibleOffset > 0; + bottomEnabled = (m_totalItemHeight - m_firstVisibleOffset > rc->bottom); + ::EndDeferWindowPos(hdwp); + + //_DebugTraceA("layout: total %d, used: %d, first visible: %d", m_totalItemHeight, spaceUsed, m_firstVisibleOffset); + if(!fOnlyCalc) { + RECT rcContainer; + ::GetClientRect(m_pContainer->hwnd, &rcContainer); + + LONG dx = m_dwFlags & SIDEBARORIENTATION_LEFT ? m_pContainer->tBorder_outer_left : + rcContainer.right - m_pContainer->tBorder_outer_right - (m_elementWidth + 4); + + ::SetWindowPos(m_up->getHwnd(), 0, dx, m_pContainer->tBorder_outer_top + m_pContainer->MenuBar->getHeight(), + m_elementWidth + 4, 14, dwFlags | SWP_SHOWWINDOW); + ::SetWindowPos(m_down->getHwnd(), 0, dx, (rcContainer.bottom - 14 - m_pContainer->statusBarHeight - 1), + m_elementWidth + 4, 14, dwFlags | SWP_SHOWWINDOW); + ::EnableWindow(m_up->getHwnd(), topEnabled); + ::EnableWindow(m_down->getHwnd(), bottomEnabled); + ::InvalidateRect(m_up->getHwnd(), NULL, FALSE); + ::InvalidateRect(m_down->getHwnd(), NULL, FALSE); + } +} + +inline void CSideBar::Invalidate() +{ + Layout(0); +} + +void CSideBar::showAll(int showCmd) +{ + ::ShowWindow(m_up->getHwnd(), showCmd); + ::ShowWindow(m_down->getHwnd(), showCmd); + + std::vector::iterator item = m_buttonlist.begin(); + + while(item != m_buttonlist.end()) + ::ShowWindow((*item++)->getHwnd(), showCmd); +} + +/** + * Helper function: find a button item associated to the given + * session data + * + * @param dat _MessageWindowData*: session information + * + * @return CSideBarButtonItem*: pointer to the found item. Zero, if none was found + */ +ButtonIterator CSideBar::findSession(const TWindowData *dat) +{ + if(dat) { + std::vector::iterator item = m_buttonlist.begin(); + + if(m_buttonlist.size() > 0) { + while(item != m_buttonlist.end()) { + if((*item)->getDat() == dat) + return(item); + item++; + } + } + } + return(m_buttonlist.end()); +} + +/** + * Helper function: find a button item associated to the given + * contact handle + * + * @param hContact HANDLE: contact's handle to look for + * + * @return CSideBarButtonItem*: pointer to the found item. Zero, if none was found + */ +ButtonIterator CSideBar::findSession(const HANDLE hContact) +{ + if(hContact) { + std::vector::iterator item = m_buttonlist.begin(); + + if(m_buttonlist.size() > 0) { + while(item != m_buttonlist.end()) { + if((*item)->getContactHandle() == hContact) + return(item); + item++; + } + } + } + return(m_buttonlist.end()); +} + +void CSideBar::processScrollerButtons(UINT commandID) +{ + if(!m_isActive || m_down == 0) + return; + + if(commandID == IDC_SIDEBARDOWN && ::IsWindowEnabled(m_down->getHwnd())) + m_firstVisibleOffset += 10; + else if(commandID == IDC_SIDEBARUP && ::IsWindowEnabled(m_up->getHwnd())) + m_firstVisibleOffset = max(0, m_firstVisibleOffset - 10); + + Layout(0); +} + +void CSideBar::resizeScrollWnd(LONG x, LONG y, LONG width, LONG height) const +{ + if(!m_isVisible || !m_isActive) { + ::ShowWindow(m_hwndScrollWnd, SW_HIDE); + return; + } + ::SetWindowPos(m_hwndScrollWnd, 0, x, y + 15, m_width, height - 30, + SWP_NOCOPYBITS | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_NOSENDCHANGING | SWP_DEFERERASE | SWP_ASYNCWINDOWPOS); +} + +void CSideBar::invalidateButton(const TWindowData* dat) +{ + if(m_isActive && m_isVisible) { + ButtonIterator it = findSession(dat); + + if(it != this->m_buttonlist.end()) + RedrawWindow((*it)->m_buttonControl->hwnd, 0, 0, RDW_INVALIDATE | RDW_UPDATENOW); + } +} + +/** + * the window procedure for the sidebar container window (the window which + * acts as a parent for the actual buttons). itself, this window is a child + * of the container window. + */ +LRESULT CALLBACK CSideBar::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_SIZE: + return(TRUE); + case WM_ERASEBKGND: { + HDC hdc = (HDC)wParam; + RECT rc; + ::GetClientRect(hwnd, &rc); + if (CSkin::m_skinEnabled) { + CSkinItem *item = &SkinItems[ID_EXTBKSIDEBARBG]; + + if(item->IGNORED) + CSkin::SkinDrawBG(hwnd, m_pContainer->hwnd, m_pContainer, &rc, hdc); + else + CSkin::DrawItem(hdc, &rc, item); + } + else if (M->isAero() || M->isVSThemed()) { + HDC hdcMem; + HANDLE hbp = 0; + HBITMAP hbm, hbmOld; + + if(CMimAPI::m_haveBufferedPaint) + hbp = CSkin::InitiateBufferedPaint(hdc, rc, hdcMem); + else { + hdcMem = ::CreateCompatibleDC(hdc); + hbm = CSkin::CreateAeroCompatibleBitmap(rc, hdcMem); + hbmOld = reinterpret_cast(::SelectObject(hdcMem, hbm)); + } + + if(M->isAero()) + ::FillRect(hdcMem, &rc, CSkin::m_BrushBack); + else + CSkin::FillBack(hdcMem, &rc); + + if(hbp) + CSkin::FinalizeBufferedPaint(hbp, &rc); + else { + ::BitBlt(hdc, 0, 0, rc.right, rc.bottom, hdcMem, 0, 0, SRCCOPY); + ::SelectObject(hdcMem, hbmOld); + ::DeleteObject(hbm); + ::DeleteDC(hdcMem); + } + } + else + ::FillRect(hdc, &rc, ::GetSysColorBrush(COLOR_3DFACE)); + return(1); + } + default: + break; + } + return(DefWindowProc(hwnd, msg, wParam, lParam)); +} + +LRESULT CALLBACK CSideBar::wndProcStub(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CSideBar *sideBar = (CSideBar *)::GetWindowLongPtr(hwnd, GWLP_USERDATA); + + if(sideBar) + return(sideBar->wndProc(hwnd, msg, wParam, lParam)); + + switch(msg) { + case WM_NCCREATE: { + CREATESTRUCT *cs = (CREATESTRUCT *)lParam; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + return(TRUE); + } + default: + break; + } + return(::DefWindowProc(hwnd, msg, wParam, lParam)); +} +/** + * paints the background for a switchbar item. It can paint aero, visual styles, skins or + * classic buttons (depending on os and current plugin settings). + * + * @param hdc HDC: target device context + * @param rc RECT*: target rectangle + * @param stateId the state identifier (normal, pressed, hot, disabled etc.) + */ +void __fastcall CSideBar::m_DefaultBackgroundRenderer(const HDC hdc, const RECT *rc, + const CSideBarButton *item) +{ + UINT id = item->getID(); + int stateId = item->m_buttonControl->stateId; + bool fIsActiveItem = (item->m_sideBar->getActiveItem() == item); + + if(CSkin::m_skinEnabled) { + TContainerData* pContainer = const_cast(item->m_sideBar->getContainer()); + int id = stateId == PBS_PRESSED || fIsActiveItem ? ID_EXTBKBUTTONSPRESSED : (stateId == PBS_HOT ? ID_EXTBKBUTTONSMOUSEOVER : ID_EXTBKBUTTONSNPRESSED); + CSkinItem* skinItem = &SkinItems[id]; + HWND hwnd; + + if(id == IDC_SIDEBARUP) + hwnd = item->m_sideBar->getScrollUp()->m_buttonControl->hwnd; + else if(id == IDC_SIDEBARDOWN) + hwnd = item->m_sideBar->getScrollDown()->m_buttonControl->hwnd; + else + hwnd = item->m_buttonControl->hwnd; + + CSkin::SkinDrawBG(hwnd, pContainer->hwnd, pContainer, const_cast(rc), hdc); + CSkin::DrawItem(hdc, rc, skinItem); + } + else if(M->isAero() || PluginConfig.m_fillColor) { + if(id == IDC_SIDEBARUP || id == IDC_SIDEBARDOWN) { + if(M->isAero()) + ::FillRect(hdc, const_cast(rc), CSkin::m_BrushBack); + else + CSkin::FillBack(hdc, const_cast(rc)); + + if(stateId == PBS_HOT || stateId == PBS_PRESSED) + DrawAlpha(hdc, const_cast(rc), 0xf0f0f0, 70, 0x000000, 0, 9, + 31, 4, 0); + else + DrawAlpha(hdc, const_cast(rc), 0xf0f0f0, 30, 0x707070, 0, 9, + 31, 4, 0); + } + else { + if(PluginConfig.m_fillColor) + FillTabBackground(hdc, stateId, item->getDat(), const_cast(rc)); + + CSkin::m_switchBarItem->setAlphaFormat(AC_SRC_ALPHA, + (stateId == PBS_HOT && !fIsActiveItem) ? 250 : (fIsActiveItem || stateId == PBS_PRESSED ? 250 : 230)); + CSkin::m_switchBarItem->Render(hdc, rc, true); + if(stateId == PBS_HOT || stateId == PBS_PRESSED || fIsActiveItem) { + RECT rcGlow = *rc; + rcGlow.top += 1; + rcGlow.bottom -= 2; + + CSkin::m_tabGlowTop->setAlphaFormat(AC_SRC_ALPHA, (stateId == PBS_PRESSED || fIsActiveItem) ? 180 : 100); + CSkin::m_tabGlowTop->Render(hdc, &rcGlow, true); + } + } + } + else if(M->isVSThemed()) { + RECT *rcDraw = const_cast(rc); + if(id == IDC_SIDEBARUP || id == IDC_SIDEBARDOWN) { + ::FillRect(hdc, rc, stateId == PBS_HOT ? ::GetSysColorBrush(COLOR_HOTLIGHT) : ::GetSysColorBrush(COLOR_3DFACE)); + ::InflateRect(rcDraw, -2, 0); + ::DrawEdge(hdc, rcDraw, EDGE_ETCHED, BF_SOFT|BF_RECT|BF_FLAT); + } + else { + CSkin::FillBack(hdc, rcDraw); + + if(CMimAPI::m_pfnIsThemeBackgroundPartiallyTransparent(item->m_buttonControl->hThemeToolbar, TP_BUTTON, stateId)) + CMimAPI::m_pfnDrawThemeParentBackground(item->getHwnd(), hdc, rcDraw); + + if(M->isAero() || PluginConfig.m_WinVerMajor >= 6) { + stateId = (fIsActiveItem ? PBS_PRESSED : PBS_HOT); + CMimAPI::m_pfnDrawThemeBackground(item->m_buttonControl->hThemeToolbar, hdc, 8, RBStateConvert2Flat(stateId), rcDraw, rcDraw); + } + else { + stateId = (fIsActiveItem ? PBS_PRESSED : PBS_HOT); + CMimAPI::m_pfnDrawThemeBackground(item->m_buttonControl->hThemeToolbar, hdc, TP_BUTTON, TBStateConvert2Flat(stateId), rcDraw, rcDraw); + } + } + } + else { + RECT *rcDraw = const_cast(rc); + if(!(id == IDC_SIDEBARUP || id == IDC_SIDEBARDOWN)) { + HBRUSH br = (stateId == PBS_HOT && !fIsActiveItem) ? ::GetSysColorBrush(COLOR_BTNSHADOW) : (fIsActiveItem || stateId == PBS_PRESSED ? ::GetSysColorBrush(COLOR_HOTLIGHT) : ::GetSysColorBrush(COLOR_3DFACE)); + ::FillRect(hdc, rc, br); + ::DrawEdge(hdc, rcDraw, (stateId == PBS_HOT && !fIsActiveItem) ? EDGE_ETCHED : (fIsActiveItem || stateId == PBS_PRESSED) ? EDGE_BUMP : EDGE_ETCHED, BF_RECT | BF_SOFT | BF_FLAT); + } + else { + ::FillRect(hdc, rc, stateId == PBS_HOT ? ::GetSysColorBrush(COLOR_HOTLIGHT) : ::GetSysColorBrush(COLOR_3DFACE)); + ::InflateRect(rcDraw, -2, 0); + ::DrawEdge(hdc, rcDraw, EDGE_ETCHED, BF_SOFT|BF_RECT|BF_FLAT); + } + } +} + +void __fastcall CSideBar::m_DefaultContentRenderer(const HDC hdc, const RECT *rcBox, + const CSideBarButton *item) +{ + UINT id = item->getID(); + const TWindowData* dat = item->getDat(); + int stateID = item->m_buttonControl->stateId; + + LONG cx = rcBox->right - rcBox->left; + LONG cy = rcBox->bottom - rcBox->top; + + if(id == IDC_SIDEBARUP || id == IDC_SIDEBARDOWN) { + ::DrawIconEx(hdc, (rcBox->left + rcBox->right) / 2 - 8, (rcBox->top + rcBox->bottom) / 2 - 8, id == IDC_SIDEBARUP ? PluginConfig.g_buttonBarIcons[26] : PluginConfig.g_buttonBarIcons[16], + 16, 16, 0, 0, DI_NORMAL); + if(!M->isAero() && stateID == PBS_HOT) + ::DrawEdge(hdc, const_cast(rcBox), BDR_INNER, BF_RECT | BF_SOFT | BF_FLAT); + } + else if(dat) + item->renderIconAndNick(hdc, rcBox); +} + +/** + * content renderer for the advanced side bar button layout. includes + * avatars + */ +void __fastcall CSideBar::m_AdvancedContentRenderer(const HDC hdc, const RECT *rcBox, + const CSideBarButton *item) +{ + UINT id = item->getID(); + const TWindowData* dat = item->getDat(); + int stateID = item->m_buttonControl->stateId; + + LONG cx = rcBox->right - rcBox->left; + LONG cy = rcBox->bottom - rcBox->top; + SIZE szFirstLine, szSecondLine; + + if(id == IDC_SIDEBARUP || id == IDC_SIDEBARDOWN) + m_DefaultContentRenderer(hdc, rcBox, item); + else if(dat) { + RECT rc = *rcBox; + + if(dat->ace && dat->ace->hbmPic) { // we have an avatar + double dNewHeight, dNewWidth; + LONG maxHeight = cy - 8; + bool fFree = false; + + Utils::scaleAvatarHeightLimited(dat->ace->hbmPic, dNewWidth, dNewHeight, maxHeight); + + HBITMAP hbmResized = CSkin::ResizeBitmap(dat->ace->hbmPic, dNewWidth, dNewHeight, fFree); + HDC dc = CreateCompatibleDC(hdc); + HBITMAP hbmOld = reinterpret_cast(::SelectObject(dc, hbmResized)); + + LONG xOff = (cx - maxHeight) + (maxHeight - (LONG)dNewWidth) / 2 - 4; + LONG yOff = (cy - (LONG)dNewHeight) / 2; + + CMimAPI::m_MyAlphaBlend(hdc, xOff, yOff, (LONG)dNewWidth, (LONG)dNewHeight, dc, 0, 0, (LONG)dNewWidth, (LONG)dNewHeight, CSkin::m_default_bf); + ::SelectObject(dc, hbmOld); + if(hbmResized != dat->ace->hbmPic) + ::DeleteObject(hbmResized); + ::DeleteDC(dc); + rc.right -= (maxHeight + 6); + } + + /* + * calculate metrics based on font configuration. Determine if we have enough + * space for both lines + */ + + rc.left += 3; + HFONT hOldFont = reinterpret_cast(::SelectObject(hdc, CInfoPanel::m_ipConfig.hFonts[IPFONTID_NICK])); + ::GetTextExtentPoint32A(hdc, "A", 1, &szFirstLine); + ::SelectObject(hdc, CInfoPanel::m_ipConfig.hFonts[IPFONTID_STATUS]); + ::GetTextExtentPoint32A(hdc, "A", 1, &szSecondLine); + szSecondLine.cy = max(szSecondLine.cy, 18); + + LONG required = szFirstLine.cy + szSecondLine.cy; + bool fSecondLine = (required < cy ? true : false); + + DWORD dtFlags = DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS | (!fSecondLine ? DT_VCENTER : 0); + + ::SelectObject(hdc, CInfoPanel::m_ipConfig.hFonts[IPFONTID_NICK]); + rc.top++; + ::SetBkMode(hdc, TRANSPARENT); + CSkin::RenderText(hdc, dat->hThemeIP, dat->cache->getNick(), &rc, + dtFlags, CSkin::m_glowSize, CInfoPanel::m_ipConfig.clrs[IPFONTID_NICK]); + + if(fSecondLine) { + int iSize; + HICON hIcon = dat->cache->getIcon(iSize); + + /* + * TODO support larger icons at a later time. This side bar button + * could use 32x32 icons as well. + */ + + rc.top = rc.bottom - szSecondLine.cy - 2; + ::DrawIconEx(hdc, rc.left, rc.top + (rc.bottom - rc.top) / 2 - 8, hIcon, 16, 16, 0, 0, DI_NORMAL); + rc.left += 18; + ::SelectObject(hdc, CInfoPanel::m_ipConfig.hFonts[IPFONTID_STATUS]); + CSkin::RenderText(hdc, dat->hThemeIP, dat->szStatus, &rc, + dtFlags | DT_VCENTER, CSkin::m_glowSize, CInfoPanel::m_ipConfig.clrs[IPFONTID_STATUS]); + } + ::SelectObject(hdc, hOldFont); + } +} + +/** + * measure callback for the advanced sidebar button layout (vertical mode + * with variable height buttons) + */ +const SIZE& __fastcall CSideBar::m_measureAdvancedVertical(CSideBarButton* item) +{ + const TWindowData* dat = item->getDat(); + + SIZE sz = {0}; + + if(dat) { + SIZE szFirstLine, szSecondLine; + + if(dat->ace && dat->ace->hbmPic) + sz.cy = item->getLayout()->width; + + HDC dc = ::GetDC(dat->hwnd); + + HFONT hOldFont = reinterpret_cast(::SelectObject(dc, CInfoPanel::m_ipConfig.hFonts[IPFONTID_NICK])); + ::GetTextExtentPoint32(dc, dat->cache->getNick(), lstrlen(dat->cache->getNick()), &szFirstLine); + ::SelectObject(dc, CInfoPanel::m_ipConfig.hFonts[IPFONTID_STATUS]); + ::GetTextExtentPoint32(dc, dat->szStatus, lstrlen(dat->szStatus), &szSecondLine); + ::SelectObject(dc, hOldFont); + ReleaseDC(dat->hwnd, dc); + + szSecondLine.cx += 18; // icon space + + sz.cy += max(szFirstLine.cx + 4, szSecondLine.cx + 4); + sz.cy += 2; + } + item->setSize(sz); + return(item->getSize()); +} -- cgit v1.2.3