-
Notifications
You must be signed in to change notification settings - Fork 127
/
ClosableTabCtrl.cpp
561 lines (505 loc) · 17.6 KB
/
ClosableTabCtrl.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
//this file is part of eMule
//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net )
//
//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., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "stdafx.h"
#include "emule.h"
#include "ClosableTabCtrl.h"
#include "OtherFunctions.h"
#include "MenuCmds.h"
#include "UserMsgs.h"
#include "VisualStylesXP.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// _WIN32_WINNT >= 0x0501 (XP only)
#define _WM_THEMECHANGED 0x031A
#define _ON_WM_THEMECHANGED() \
{ _WM_THEMECHANGED, 0, 0, 0, AfxSig_l, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(void) > (_OnThemeChanged)) \
},
///////////////////////////////////////////////////////////////////////////////
// CClosableTabCtrl
IMPLEMENT_DYNAMIC(CClosableTabCtrl, CTabCtrl)
BEGIN_MESSAGE_MAP(CClosableTabCtrl, CTabCtrl)
ON_WM_LBUTTONUP()
ON_WM_LBUTTONDBLCLK()
ON_WM_MBUTTONUP()
ON_WM_CREATE()
ON_WM_SYSCOLORCHANGE()
ON_WM_CONTEXTMENU()
_ON_WM_THEMECHANGED()
ON_WM_CTLCOLOR_REFLECT()
ON_WM_CTLCOLOR()
ON_WM_ERASEBKGND()
ON_WM_MEASUREITEM()
ON_WM_MEASUREITEM_REFLECT()
END_MESSAGE_MAP()
CClosableTabCtrl::CClosableTabCtrl()
{
m_bCloseable = true;
memset(&m_iiCloseButton, 0, sizeof m_iiCloseButton);
m_ptCtxMenu.SetPoint(-1, -1);
}
CClosableTabCtrl::~CClosableTabCtrl()
{
}
void CClosableTabCtrl::GetCloseButtonRect(int iItem, const CRect& rcItem, CRect& rcCloseButton, bool bItemSelected, bool bVistaThemeActive)
{
rcCloseButton.top = rcItem.top + 2;
rcCloseButton.bottom = rcCloseButton.top + (m_iiCloseButton.rcImage.bottom - m_iiCloseButton.rcImage.top);
rcCloseButton.right = rcItem.right - 2;
rcCloseButton.left = rcCloseButton.right - (m_iiCloseButton.rcImage.right - m_iiCloseButton.rcImage.left);
if (bVistaThemeActive)
rcCloseButton.left -= 1; // the close button does not look 'symetric' with a width of 16, give it 17
if (bItemSelected) {
rcCloseButton.OffsetRect(-1, 0);
if (bVistaThemeActive) {
int iItems = GetItemCount();
if (iItems > 1 && iItem == iItems - 1)
rcCloseButton.OffsetRect(-2, 0);
}
}
else {
if (bVistaThemeActive) {
int iItems = GetItemCount();
if (iItems > 1 && iItem < iItems - 1)
rcCloseButton.OffsetRect(2, 0);
}
}
}
int CClosableTabCtrl::GetTabUnderPoint(CPoint point) const
{
int iTabs = GetItemCount();
for (int i = 0; i < iTabs; i++)
{
CRect rcItem;
GetItemRect(i, rcItem);
rcItem.InflateRect(2, 2); // get the real tab item rect
if (rcItem.PtInRect(point))
return i;
}
return -1;
}
int CClosableTabCtrl::GetTabUnderContextMenu() const
{
if (m_ptCtxMenu.x == -1 || m_ptCtxMenu.y == -1)
return -1;
return GetTabUnderPoint(m_ptCtxMenu);
}
bool CClosableTabCtrl::SetDefaultContextMenuPos()
{
int iTab = GetCurSel();
if (iTab != -1)
{
CRect rcItem;
if (GetItemRect(iTab, &rcItem))
{
rcItem.InflateRect(2, 2); // get the real tab item rect
m_ptCtxMenu.x = rcItem.left + rcItem.Width()/2;
m_ptCtxMenu.y = rcItem.top + rcItem.Height()/2;
return true;
}
}
return false;
}
void CClosableTabCtrl::OnMButtonUp(UINT nFlags, CPoint point)
{
if (m_bCloseable)
{
int iTab = GetTabUnderPoint(point);
if (iTab != -1) {
GetParent()->SendMessage(UM_CLOSETAB, (WPARAM)iTab);
return;
}
}
CTabCtrl::OnMButtonUp(nFlags, point);
}
void CClosableTabCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bCloseable)
{
int iTab = GetTabUnderPoint(point);
if (iTab != -1)
{
CRect rcItem;
GetItemRect(iTab, rcItem);
rcItem.InflateRect(2, 2); // get the real tab item rect
bool bVistaThemeActive = theApp.IsVistaThemeActive();
CRect rcCloseButton;
GetCloseButtonRect(iTab, rcItem, rcCloseButton, iTab == GetCurSel(), bVistaThemeActive);
// The visible part of our close icon is one pixel less on each side
if (!bVistaThemeActive) {
rcCloseButton.top += 1;
rcCloseButton.left += 1;
rcCloseButton.right -= 1;
rcCloseButton.bottom -= 1;
}
if (rcCloseButton.PtInRect(point)) {
GetParent()->SendMessage(UM_CLOSETAB, (WPARAM)iTab);
return;
}
}
}
CTabCtrl::OnLButtonUp(nFlags, point);
}
void CClosableTabCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
int iTab = GetTabUnderPoint(point);
if (iTab != -1) {
GetParent()->SendMessage(UM_DBLCLICKTAB, (WPARAM)iTab);
return;
}
CTabCtrl::OnLButtonDblClk(nFlags, point);
}
// It would be nice if there would the option to restrict the maximum width of a tab control.
// We would need that feature actually for almost all our tab controls. Especially for the
// search results list - those tab control labels can get quite large. But I did not yet a
// find a way to limit the width of tabs. Although MSDN says that an owner drawn
// tab control receives a WM_MEASUREITEM, I never got one.
// Vista: This gets never called for an owner drawn tab control
void CClosableTabCtrl::OnMeasureItem(int iCtlId, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
TRACE("CClosableTabCtrl::OnMeasureItem\n");
__super::OnMeasureItem(iCtlId, lpMeasureItemStruct);
}
// Vista: This gets never called for an owner drawn tab control
void CClosableTabCtrl::MeasureItem(LPMEASUREITEMSTRUCT)
{
TRACE("CClosableTabCtrl::MeasureItem\n");
}
void CClosableTabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
CRect rect(lpDIS->rcItem);
int nTabIndex = lpDIS->itemID;
if (nTabIndex < 0)
return;
TCHAR szLabel[256];
TC_ITEM tci;
tci.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_STATE;
tci.pszText = szLabel;
tci.cchTextMax = _countof(szLabel);
tci.dwStateMask = TCIS_HIGHLIGHTED;
if (!GetItem(nTabIndex, &tci))
return;
szLabel[_countof(szLabel) - 1] = _T('\0');
//TRACE("CClosableTabCtrl::DrawItem: item=%u, state=%08x, color=%08x, rc=%3d,%3d,%3dx%3d\n", nTabIndex, tci.dwState, GetTextColor(lpDIS->hDC), lpDIS->rcItem.left, lpDIS->rcItem.top, lpDIS->rcItem.right - lpDIS->rcItem.left, lpDIS->rcItem.bottom - lpDIS->rcItem.top);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
if (!pDC)
return;
CRect rcFullItem(lpDIS->rcItem);
bool bSelected = (lpDIS->itemState & ODS_SELECTED) != 0;
///////////////////////////////////////////////////////////////////////////////////////
// Adding support for XP Styles (Vista Themes) for owner drawn tab controls simply
// does *not* work under Vista. Maybe it works under XP (did not try), but that is
// meaningless because under XP a owner drawn tab control is already rendered *with*
// the proper XP Styles. So, for XP there is no need to care about the theme API at all.
//
// However, under Vista, a tab control which has the TCS_OWNERDRAWFIXED
// style gets additional 3D-borders which are applied by Vista *after* WM_DRAWITEM
// was processed. Thus, there is no known workaround available to prevent Vista from
// adding those old fashioned 3D-borders. We can render the tab control items within
// the WM_DRAWITEM handler in whatever style we want, but Vista will in each case
// overwrite the borders of each tab control item with old fashioned 3D-borders...
//
// To complete this experience, tab controls also do not support NMCUSTOMDRAW. So, the
// only known way to customize a tab control is by using TCS_OWNERDRAWFIXED which does
// however not work properly under Vista.
//
// The "solution" which is currently implemented to prevent Vista from drawing those
// 3D-borders is by using "ExcludeClipRect" to reduce the drawing area which is used
// by Windows after WM_DRAWITEM was processed. This "solution" is very sensitive to
// the used rectangles and offsets in general. Incrementing/Decrementing one of the
// "rcItem", "rcFullItem", etc. rectangles makes the entire "solution" flawed again
// because some borders would become visible again.
//
HTHEME hTheme = NULL;
int iPartId = TABP_TABITEM;
int iStateId = TIS_NORMAL;
bool bVistaHotTracked = false;
bool bVistaThemeActive = theApp.IsVistaThemeActive();
if (bVistaThemeActive)
{
// To determine if the current item is in 'hot tracking' mode, we need to evaluate
// the current foreground color - there is no flag which would indicate this state
// more safely. This applies only for Vista and for tab controls which have the
// TCS_OWNERDRAWFIXED style.
bVistaHotTracked = pDC->GetTextColor() == GetSysColor(COLOR_HOTLIGHT);
hTheme = g_xpStyle.OpenThemeData(m_hWnd, L"TAB");
if (hTheme)
{
if (bSelected) {
// get the real tab item rect
rcFullItem.left += 1;
rcFullItem.right -= 1;
rcFullItem.bottom -= 1;
}
else
rcFullItem.InflateRect(2, 2); // get the real tab item rect
CRect rcBk(rcFullItem);
if (bSelected)
{
iStateId = TTIS_SELECTED;
if (nTabIndex == 0) {
// First item
if (nTabIndex == GetItemCount() - 1)
iPartId = TABP_TOPTABITEMBOTHEDGE; // First & Last item
else
iPartId = TABP_TOPTABITEMLEFTEDGE;
}
else if (nTabIndex == GetItemCount() - 1) {
// Last item
iPartId = TABP_TOPTABITEMRIGHTEDGE;
}
else {
iPartId = TABP_TOPTABITEM;
}
}
else
{
rcBk.top += 2;
iStateId = bVistaHotTracked ? TIS_HOT : TIS_NORMAL;
if (nTabIndex == 0) {
// First item
if (nTabIndex == GetItemCount() - 1)
iPartId = TABP_TABITEMBOTHEDGE; // First & Last item
else
iPartId = TABP_TABITEMLEFTEDGE;
}
else if (nTabIndex == GetItemCount() - 1) {
// Last item
iPartId = TABP_TABITEMRIGHTEDGE;
}
else {
iPartId = TABP_TABITEM;
}
}
if (g_xpStyle.IsThemeBackgroundPartiallyTransparent(hTheme, iPartId, iStateId))
g_xpStyle.DrawThemeParentBackground(m_hWnd, *pDC, &rcFullItem);
g_xpStyle.DrawThemeBackground(hTheme, *pDC, iPartId, iStateId, &rcBk, NULL);
}
}
// Following background clearing is needed for:
// WinXP/Vista (when used without an application theme)
// Vista (when used with an application theme but without a theme for the tab control)
if ( (!g_xpStyle.IsThemeActive() || !g_xpStyle.IsAppThemed())
|| (hTheme == NULL && bVistaThemeActive) )
pDC->FillSolidRect(&lpDIS->rcItem, GetSysColor(COLOR_BTNFACE));
int iOldBkMode = pDC->SetBkMode(TRANSPARENT);
// Draw image on left side
CImageList *piml = GetImageList();
if (tci.iImage >= 0 && piml && piml->m_hImageList)
{
IMAGEINFO ii;
piml->GetImageInfo(0, &ii);
rect.left += bSelected ? 8 : 4;
piml->Draw(pDC, tci.iImage, CPoint(rect.left, rect.top + 2), ILD_TRANSPARENT);
rect.left += (ii.rcImage.right - ii.rcImage.left);
if (!bSelected)
rect.left += 4;
}
bool bCloseable = m_bCloseable;
if (bCloseable && GetParent()->SendMessage(UM_QUERYTAB, nTabIndex))
bCloseable = false;
// Draw 'Close button' at right side
if (bCloseable && m_ImgLstCloseButton.m_hImageList)
{
CRect rcCloseButton;
GetCloseButtonRect(nTabIndex, rect, rcCloseButton, bSelected, bVistaThemeActive);
HTHEME hThemeNC = bVistaThemeActive ? g_xpStyle.OpenThemeData(m_hWnd, _T("WINDOW")) : NULL;
if (hThemeNC) {
// Possible "Close" parts: WP_CLOSEBUTTON, WP_SMALLCLOSEBUTTON, WP_MDICLOSEBUTTON
int iPartId = WP_SMALLCLOSEBUTTON;
int iStateId = (bSelected || bVistaHotTracked) ? CBS_NORMAL : CBS_DISABLED;
if (g_xpStyle.IsThemeBackgroundPartiallyTransparent(hTheme, iPartId, iStateId))
g_xpStyle.DrawThemeParentBackground(m_hWnd, *pDC, &rcCloseButton);
g_xpStyle.DrawThemeBackground(hThemeNC, *pDC, iPartId, iStateId, rcCloseButton, NULL);
g_xpStyle.CloseThemeData(hThemeNC);
}
else {
m_ImgLstCloseButton.Draw(pDC, (bSelected || bVistaHotTracked) ? 0 : 1, rcCloseButton.TopLeft(), ILD_TRANSPARENT);
}
rect.right = rcCloseButton.left - 2;
if (bSelected)
rect.left += hTheme ? 4 : 2;
}
COLORREF crOldColor = CLR_NONE;
if (tci.dwState & TCIS_HIGHLIGHTED)
crOldColor = pDC->SetTextColor(RGB(192, 0, 0));
else if (bVistaHotTracked)
crOldColor = pDC->SetTextColor(GetSysColor(COLOR_BTNTEXT));
rect.top += bSelected ? 4 : 3;
// Vista: Tab control has troubles with determining the width of a tab if the
// label contains one '&' character. To get around this, we use the old code which
// replaces one '&' character with two '&' characters and we do not specify DT_NOPREFIX
// here when drawing the text.
//
// Vista: "DrawThemeText" can not be used in case we need a certain foreground color. Thus we always us
// "DrawText" to always get the same font and metrics (just for safety).
pDC->DrawText(szLabel, rect, DT_SINGLELINE | DT_TOP | DT_CENTER /*| DT_NOPREFIX*/);
if (crOldColor != CLR_NONE)
pDC->SetTextColor(crOldColor);
pDC->SetBkMode(iOldBkMode);
if (hTheme)
{
CRect rcClip(rcFullItem);
if (bSelected) {
rcClip.left -= 2 + 1;
rcClip.right += 2 + 1;
}
else {
rcClip.top += 2;
}
pDC->ExcludeClipRect(&rcClip);
g_xpStyle.CloseThemeData(hTheme);
}
}
void CClosableTabCtrl::PreSubclassWindow()
{
CTabCtrl::PreSubclassWindow();
InternalInit();
}
int CClosableTabCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CTabCtrl::OnCreate(lpCreateStruct) == -1)
return -1;
InternalInit();
return 0;
}
void CClosableTabCtrl::InternalInit()
{
ModifyStyle(0, TCS_OWNERDRAWFIXED);
#if 1
// Under Vista Aero, all tab controls get by default the TCS_HOTTRACK
// style even if it was not specified within the resource file. Though, to 'see'
// the hot tracking effect the control also need to get initialized explicitly with
// the WS_CLIPCHILDREN style within a *seperate* function call. Yes, there is no
// logic to all this, not at all. It simply is that way.
//
// So, do *not* "optimize" that code by using only one "ModifyStyle" function call.
// The 2nd function call to "ModifyStyle" is very much by intention!
//
// However, the hot tracking effect which is achived this way does not survive a
// theme change. After the theme is changed (regardless whether we switch between
// Vista themes or from/to a non-Vista theme), the hot tracking effect is gone even
// if we try to modify the styles again within OnThemeChanged...
if (theApp.IsVistaThemeActive())
ModifyStyle(0, WS_CLIPCHILDREN);
#else
// Remove the automatically applied hot tracking effect to avoid that the tab control
// may use it when it also sets the WS_CLIPCHILDREN (for other reasons) later.
ModifyStyle(TCS_HOTTRACK, 0);
#endif
SetAllIcons();
}
void CClosableTabCtrl::OnSysColorChange()
{
CTabCtrl::OnSysColorChange();
SetAllIcons();
}
void CClosableTabCtrl::SetAllIcons()
{
if (m_bCloseable)
{
const int iIconWidth = 16;
const int iIconHeight = 16;
m_ImgLstCloseButton.DeleteImageList();
m_ImgLstCloseButton.Create(iIconWidth, iIconHeight, theApp.m_iDfltImageListColorFlags | ILC_MASK, 0, 1);
m_ImgLstCloseButton.Add(CTempIconLoader(_T("CloseTabSelected"), iIconWidth, iIconHeight));
m_ImgLstCloseButton.Add(CTempIconLoader(_T("CloseTab"), iIconWidth, iIconHeight));
m_ImgLstCloseButton.GetImageInfo(0, &m_iiCloseButton);
Invalidate();
}
}
void CClosableTabCtrl::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
if (m_bCloseable)
{
if (point.x == -1 || point.y == -1) {
if (!SetDefaultContextMenuPos())
return;
point = m_ptCtxMenu;
ClientToScreen(&point);
}
else {
m_ptCtxMenu = point;
ScreenToClient(&m_ptCtxMenu);
}
int iTab = GetTabUnderPoint(m_ptCtxMenu);
if (iTab != -1)
{
if (GetParent()->SendMessage(UM_QUERYTAB, (WPARAM)iTab) == 0)
{
CMenu menu;
menu.CreatePopupMenu();
menu.AppendMenu(MF_STRING, MP_REMOVE, GetResString(IDS_FD_CLOSE));
menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
}
}
}
}
BOOL CClosableTabCtrl::OnCommand(WPARAM wParam, LPARAM lParam)
{
if (wParam == MP_REMOVE)
{
if (m_ptCtxMenu.x != -1 && m_ptCtxMenu.y != -1)
{
int iTab = GetTabUnderPoint(m_ptCtxMenu);
if (iTab != -1) {
GetParent()->SendMessage(UM_CLOSETAB, (WPARAM)iTab);
return TRUE;
}
}
}
return CTabCtrl::OnCommand(wParam, lParam);
}
LRESULT CClosableTabCtrl::_OnThemeChanged()
{
// Owner drawn tab control seems to have troubles with updating itself due to an XP theme change..
ModifyStyle(TCS_OWNERDRAWFIXED, 0); // Reset control style to not-owner drawn
Default(); // Process original WM_THEMECHANGED message
ModifyStyle(0, TCS_OWNERDRAWFIXED); // Apply owner drawn style again
return 0;
}
// Vista: This gets never called for an owner drawn tab control
HBRUSH CClosableTabCtrl::CtlColor(CDC* /*pDC*/, UINT /*nCtlColor*/)
{
// Change any attributes of the DC here
// Return a non-NULL brush if the parent's handler should not be called
return NULL;
}
// Vista: This gets never called for an owner drawn tab control
HBRUSH CClosableTabCtrl::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CTabCtrl::OnCtlColor(pDC, pWnd, nCtlColor);
// Change any attributes of the DC here
// Return a different brush if the default is not desired
return hbr;
}
// Vista: Can not be used to workaround the problems with owner drawn tab control
BOOL CClosableTabCtrl::OnEraseBkgnd(CDC* pDC)
{
return CTabCtrl::OnEraseBkgnd(pDC);
}
BOOL CClosableTabCtrl::DeleteItem(int nItem)
{
// if we remove a tab which would lead to scrolling back to other tabs, all those become hidden for... whatever reasons
// its easy enough wo work arround by scrolling to the first visible tab _before_ we delete the other one
SetCurSel(0);
return __super::DeleteItem(nItem);
}