comparison foosdk/wtl/Include/atlctrlx.h @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 // Windows Template Library - WTL version 10.0
2 // Copyright (C) Microsoft Corporation, WTL Team. All rights reserved.
3 //
4 // This file is a part of the Windows Template Library.
5 // The use and distribution terms for this software are covered by the
6 // Microsoft Public License (http://opensource.org/licenses/MS-PL)
7 // which can be found in the file MS-PL.txt at the root folder.
8
9 #ifndef __ATLCTRLX_H__
10 #define __ATLCTRLX_H__
11
12 #pragma once
13
14 #ifndef __ATLAPP_H__
15 #error atlctrlx.h requires atlapp.h to be included first
16 #endif
17
18 #ifndef __ATLCTRLS_H__
19 #error atlctrlx.h requires atlctrls.h to be included first
20 #endif
21
22
23 ///////////////////////////////////////////////////////////////////////////////
24 // Classes in this file:
25 //
26 // CBitmapButtonImpl<T, TBase, TWinTraits>
27 // CBitmapButton
28 // CCheckListViewCtrlImpl<T, TBase, TWinTraits>
29 // CCheckListViewCtrl
30 // CHyperLinkImpl<T, TBase, TWinTraits>
31 // CHyperLink
32 // CWaitCursor
33 // CCustomWaitCursor
34 // CMultiPaneStatusBarCtrlImpl<T, TBase>
35 // CMultiPaneStatusBarCtrl
36 // CPaneContainerImpl<T, TBase, TWinTraits>
37 // CPaneContainer
38 // CSortListViewImpl<T>
39 // CSortListViewCtrlImpl<T, TBase, TWinTraits>
40 // CSortListViewCtrl
41 // CTabViewImpl<T, TBase, TWinTraits>
42 // CTabView
43
44 namespace WTL
45 {
46
47 ///////////////////////////////////////////////////////////////////////////////
48 // CBitmapButton - bitmap button implementation
49
50 // bitmap button extended styles
51 #define BMPBTN_HOVER 0x00000001
52 #define BMPBTN_AUTO3D_SINGLE 0x00000002
53 #define BMPBTN_AUTO3D_DOUBLE 0x00000004
54 #define BMPBTN_AUTOSIZE 0x00000008
55 #define BMPBTN_SHAREIMAGELISTS 0x00000010
56 #define BMPBTN_AUTOFIRE 0x00000020
57 #define BMPBTN_CHECK 0x00000040
58 #define BMPBTN_AUTOCHECK 0x00000080
59
60 // Note: BMPBTN_CHECK/BMPBTN_AUTOCHECK disables BN_DOUBLECLICKED,
61 // BMPBTN_AUTOFIRE doesn't work with BMPBTN_CHECK/BMPBTN_AUTOCHECK
62
63 template <class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits>
64 class ATL_NO_VTABLE CBitmapButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
65 {
66 public:
67 DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
68
69 enum
70 {
71 _nImageNormal = 0,
72 _nImagePushed,
73 _nImageFocusOrHover,
74 _nImageDisabled,
75
76 _nImageCount = 4,
77 };
78
79 enum
80 {
81 ID_TIMER_FIRST = 1000,
82 ID_TIMER_REPEAT = 1001
83 };
84
85 // Bitmap button specific extended styles
86 DWORD m_dwExtendedStyle;
87
88 CImageList m_ImageList;
89 int m_nImage[_nImageCount];
90
91 CToolTipCtrl m_tip;
92 LPTSTR m_lpstrToolTipText;
93
94 // Internal states
95 unsigned m_fMouseOver:1;
96 unsigned m_fFocus:1;
97 unsigned m_fPressed:1;
98 unsigned m_fChecked:1;
99
100
101 // Constructor/Destructor
102 CBitmapButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) :
103 m_dwExtendedStyle(dwExtendedStyle), m_ImageList(hImageList),
104 m_lpstrToolTipText(NULL),
105 m_fMouseOver(0), m_fFocus(0), m_fPressed(0), m_fChecked(0)
106 {
107 m_nImage[_nImageNormal] = -1;
108 m_nImage[_nImagePushed] = -1;
109 m_nImage[_nImageFocusOrHover] = -1;
110 m_nImage[_nImageDisabled] = -1;
111
112 #ifdef _DEBUG
113 if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && IsCheckMode())
114 ATLTRACE2(atlTraceUI, 0, _T("CBitmapButtonImpl - Check mode and BMPBTN_AUTOFIRE cannot be used together, BMPBTN_AUTOFIRE will be ignored.\n"));
115 #endif // _DEBUG
116 }
117
118 ~CBitmapButtonImpl()
119 {
120 if((m_dwExtendedStyle & BMPBTN_SHAREIMAGELISTS) == 0)
121 m_ImageList.Destroy();
122 delete [] m_lpstrToolTipText;
123 }
124
125 // overridden to provide proper initialization
126 BOOL SubclassWindow(HWND hWnd)
127 {
128 BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
129 if(bRet != FALSE)
130 {
131 T* pT = static_cast<T*>(this);
132 pT->Init();
133 }
134
135 return bRet;
136 }
137
138 // Attributes
139 DWORD GetBitmapButtonExtendedStyle() const
140 {
141 return m_dwExtendedStyle;
142 }
143
144 DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
145 {
146 DWORD dwPrevStyle = m_dwExtendedStyle;
147 if(dwMask == 0)
148 m_dwExtendedStyle = dwExtendedStyle;
149 else
150 m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
151
152 #ifdef _DEBUG
153 if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && IsCheckMode())
154 ATLTRACE2(atlTraceUI, 0, _T("CBitmapButtonImpl - Check mode and BMPBTN_AUTOFIRE cannot be used together, BMPBTN_AUTOFIRE will be ignored.\n"));
155 #endif // _DEBUG
156
157 return dwPrevStyle;
158 }
159
160 HIMAGELIST GetImageList() const
161 {
162 return m_ImageList;
163 }
164
165 HIMAGELIST SetImageList(HIMAGELIST hImageList)
166 {
167 HIMAGELIST hImageListPrev = m_ImageList;
168 m_ImageList = hImageList;
169 if(((m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0) && ::IsWindow(this->m_hWnd))
170 SizeToImage();
171
172 return hImageListPrev;
173 }
174
175 int GetToolTipTextLength() const
176 {
177 return (m_lpstrToolTipText == NULL) ? -1 : lstrlen(m_lpstrToolTipText);
178 }
179
180 bool GetToolTipText(LPTSTR lpstrText, int nLength) const
181 {
182 ATLASSERT(lpstrText != NULL);
183 if(m_lpstrToolTipText == NULL)
184 return false;
185
186 errno_t nRet = ATL::Checked::tcsncpy_s(lpstrText, nLength, m_lpstrToolTipText, _TRUNCATE);
187
188 return ((nRet == 0) || (nRet == STRUNCATE));
189 }
190
191 bool SetToolTipText(LPCTSTR lpstrText)
192 {
193 if(m_lpstrToolTipText != NULL)
194 {
195 delete [] m_lpstrToolTipText;
196 m_lpstrToolTipText = NULL;
197 }
198
199 if(lpstrText == NULL)
200 {
201 if(m_tip.IsWindow())
202 m_tip.Activate(FALSE);
203 return true;
204 }
205
206 int cchLen = lstrlen(lpstrText) + 1;
207 ATLTRY(m_lpstrToolTipText = new TCHAR[cchLen]);
208 if(m_lpstrToolTipText == NULL)
209 return false;
210
211 ATL::Checked::tcscpy_s(m_lpstrToolTipText, cchLen, lpstrText);
212 if(m_tip.IsWindow())
213 {
214 m_tip.Activate(TRUE);
215 m_tip.AddTool(this->m_hWnd, m_lpstrToolTipText);
216 }
217
218 return true;
219 }
220
221 bool GetCheck() const
222 {
223 return (m_fChecked == 1);
224 }
225
226 void SetCheck(bool bCheck, bool bUpdate = true)
227 {
228 m_fChecked = bCheck ? 1 : 0;
229
230 if(bUpdate)
231 {
232 this->Invalidate();
233 this->UpdateWindow();
234 }
235 }
236
237 // Operations
238 void SetImages(int nNormal, int nPushed = -1, int nFocusOrHover = -1, int nDisabled = -1)
239 {
240 if(nNormal != -1)
241 m_nImage[_nImageNormal] = nNormal;
242 if(nPushed != -1)
243 m_nImage[_nImagePushed] = nPushed;
244 if(nFocusOrHover != -1)
245 m_nImage[_nImageFocusOrHover] = nFocusOrHover;
246 if(nDisabled != -1)
247 m_nImage[_nImageDisabled] = nDisabled;
248 }
249
250 BOOL SizeToImage()
251 {
252 ATLASSERT(::IsWindow(this->m_hWnd) && (m_ImageList.m_hImageList != NULL));
253 int cx = 0;
254 int cy = 0;
255 if(!m_ImageList.GetIconSize(cx, cy))
256 return FALSE;
257 return this->ResizeClient(cx, cy);
258 }
259
260 // Overrideables
261 void DoPaint(CDCHandle dc)
262 {
263 ATLASSERT(m_ImageList.m_hImageList != NULL); // image list must be set
264 ATLASSERT(m_nImage[0] != -1); // main bitmap must be set
265
266 // set bitmap according to the current button state
267 bool bHover = IsHoverMode();
268 bool bPressed = (m_fPressed == 1) || (IsCheckMode() && (m_fChecked == 1));
269 int nImage = -1;
270 if(!this->IsWindowEnabled())
271 nImage = m_nImage[_nImageDisabled];
272 else if(bPressed)
273 nImage = m_nImage[_nImagePushed];
274 else if((!bHover && (m_fFocus == 1)) || (bHover && (m_fMouseOver == 1)))
275 nImage = m_nImage[_nImageFocusOrHover];
276
277 // if none is set, use default one
278 if(nImage == -1)
279 nImage = m_nImage[_nImageNormal];
280
281 // draw the button image
282 bool bAuto3D = (m_dwExtendedStyle & (BMPBTN_AUTO3D_SINGLE | BMPBTN_AUTO3D_DOUBLE)) != 0;
283 int xyPos = (bPressed && bAuto3D && (m_nImage[_nImagePushed] == -1)) ? 1 : 0;
284 m_ImageList.Draw(dc, nImage, xyPos, xyPos, ILD_NORMAL);
285
286 // draw 3D border if required
287 if(bAuto3D)
288 {
289 RECT rect = {};
290 this->GetClientRect(&rect);
291
292 if(bPressed)
293 dc.DrawEdge(&rect, ((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_SUNKENOUTER : EDGE_SUNKEN, BF_RECT);
294 else if(!bHover || (m_fMouseOver == 1))
295 dc.DrawEdge(&rect, ((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_RAISEDINNER : EDGE_RAISED, BF_RECT);
296
297 if(!bHover && (m_fFocus == 1))
298 {
299 ::InflateRect(&rect, -2 * ::GetSystemMetrics(SM_CXEDGE), -2 * ::GetSystemMetrics(SM_CYEDGE));
300 dc.DrawFocusRect(&rect);
301 }
302 }
303 }
304
305 // Message map and handlers
306 BEGIN_MSG_MAP(CBitmapButtonImpl)
307 MESSAGE_HANDLER(WM_CREATE, OnCreate)
308 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
309 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
310 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
311 MESSAGE_HANDLER(WM_PAINT, OnPaint)
312 MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
313 MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
314 MESSAGE_HANDLER(WM_KILLFOCUS, OnFocus)
315 MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
316 MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDblClk)
317 MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
318 MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
319 MESSAGE_HANDLER(WM_ENABLE, OnEnable)
320 MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
321 MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
322 MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
323 MESSAGE_HANDLER(WM_KEYUP, OnKeyUp)
324 MESSAGE_HANDLER(WM_TIMER, OnTimer)
325 MESSAGE_HANDLER(WM_UPDATEUISTATE, OnUpdateUiState)
326 END_MSG_MAP()
327
328 LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
329 {
330 T* pT = static_cast<T*>(this);
331 pT->Init();
332
333 bHandled = FALSE;
334 return 1;
335 }
336
337 LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
338 {
339 if(m_tip.IsWindow())
340 {
341 m_tip.DestroyWindow();
342 m_tip.m_hWnd = NULL;
343 }
344 bHandled = FALSE;
345 return 1;
346 }
347
348 LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
349 {
350 MSG msg = { this->m_hWnd, uMsg, wParam, lParam };
351 if(m_tip.IsWindow())
352 m_tip.RelayEvent(&msg);
353 bHandled = FALSE;
354 return 1;
355 }
356
357 LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
358 {
359 return 1; // no background needed
360 }
361
362 LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
363 {
364 T* pT = static_cast<T*>(this);
365 if(wParam != NULL)
366 {
367 pT->DoPaint((HDC)wParam);
368 }
369 else
370 {
371 CPaintDC dc(this->m_hWnd);
372 pT->DoPaint(dc.m_hDC);
373 }
374 return 0;
375 }
376
377 LRESULT OnFocus(UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
378 {
379 m_fFocus = (uMsg == WM_SETFOCUS) ? 1 : 0;
380 this->Invalidate();
381 this->UpdateWindow();
382 bHandled = FALSE;
383 return 1;
384 }
385
386 LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
387 {
388 LRESULT lRet = 0;
389 if(IsHoverMode())
390 this->SetCapture();
391 else
392 lRet = this->DefWindowProc(uMsg, wParam, lParam);
393 if(::GetCapture() == this->m_hWnd)
394 {
395 m_fPressed = 1;
396 this->Invalidate();
397 this->UpdateWindow();
398 }
399 if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && !IsCheckMode())
400 {
401 int nElapse = 250;
402 int nDelay = 0;
403 if(::SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &nDelay, 0))
404 nElapse += nDelay * 250; // all milli-seconds
405 this->SetTimer(ID_TIMER_FIRST, nElapse);
406 }
407 return lRet;
408 }
409
410 LRESULT OnLButtonDblClk(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
411 {
412 LRESULT lRet = 0;
413 if(!IsHoverMode() && !IsCheckMode())
414 lRet = this->DefWindowProc(uMsg, wParam, lParam);
415 if(::GetCapture() != this->m_hWnd)
416 this->SetCapture();
417 if(m_fPressed == 0)
418 {
419 m_fPressed = 1;
420 this->Invalidate();
421 this->UpdateWindow();
422 }
423 return lRet;
424 }
425
426 LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
427 {
428 if(((m_dwExtendedStyle & BMPBTN_AUTOCHECK) != 0) && (m_fPressed == 1))
429 SetCheck(!GetCheck(), false);
430
431 LRESULT lRet = 0;
432 if(!IsHoverMode() && !IsCheckMode())
433 lRet = this->DefWindowProc(uMsg, wParam, lParam);
434 if(::GetCapture() == this->m_hWnd)
435 {
436 if((IsHoverMode() || IsCheckMode()) && (m_fPressed == 1))
437 this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
438 ::ReleaseCapture();
439 }
440 return lRet;
441 }
442
443 LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
444 {
445 if(m_fPressed == 1)
446 {
447 m_fPressed = 0;
448 this->Invalidate();
449 this->UpdateWindow();
450 }
451 bHandled = FALSE;
452 return 1;
453 }
454
455 LRESULT OnEnable(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
456 {
457 this->Invalidate();
458 this->UpdateWindow();
459 bHandled = FALSE;
460 return 1;
461 }
462
463 LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
464 {
465 if(::GetCapture() == this->m_hWnd)
466 {
467 POINT ptCursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
468 this->ClientToScreen(&ptCursor);
469 RECT rect = {};
470 this->GetWindowRect(&rect);
471 unsigned int uPressed = ::PtInRect(&rect, ptCursor) ? 1 : 0;
472 if(m_fPressed != uPressed)
473 {
474 m_fPressed = uPressed;
475 this->Invalidate();
476 this->UpdateWindow();
477 }
478 }
479 else if(IsHoverMode() && m_fMouseOver == 0)
480 {
481 m_fMouseOver = 1;
482 this->Invalidate();
483 this->UpdateWindow();
484 StartTrackMouseLeave();
485 }
486 bHandled = FALSE;
487 return 1;
488 }
489
490 LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
491 {
492 if(m_fMouseOver == 1)
493 {
494 m_fMouseOver = 0;
495 this->Invalidate();
496 this->UpdateWindow();
497 }
498 return 0;
499 }
500
501 LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
502 {
503 if((wParam == VK_SPACE) && IsHoverMode())
504 return 0; // ignore if in hover mode
505 if((wParam == VK_SPACE) && (m_fPressed == 0))
506 {
507 m_fPressed = 1;
508 this->Invalidate();
509 this->UpdateWindow();
510 }
511 bHandled = FALSE;
512 return 1;
513 }
514
515 LRESULT OnKeyUp(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
516 {
517 if((wParam == VK_SPACE) && IsHoverMode())
518 return 0; // ignore if in hover mode
519 if((wParam == VK_SPACE) && (m_fPressed == 1))
520 {
521 m_fPressed = 0;
522 if((m_dwExtendedStyle & BMPBTN_AUTOCHECK) != 0)
523 SetCheck(!GetCheck(), false);
524 this->Invalidate();
525 this->UpdateWindow();
526 }
527 bHandled = FALSE;
528 return 1;
529 }
530
531 LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
532 {
533 ATLASSERT((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0);
534 switch(wParam) // timer ID
535 {
536 case ID_TIMER_FIRST:
537 this->KillTimer(ID_TIMER_FIRST);
538 if(m_fPressed == 1)
539 {
540 this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
541 int nElapse = 250;
542 int nRepeat = 40;
543 if(::SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &nRepeat, 0))
544 nElapse = 10000 / (10 * nRepeat + 25); // milli-seconds, approximated
545 this->SetTimer(ID_TIMER_REPEAT, nElapse);
546 }
547 break;
548 case ID_TIMER_REPEAT:
549 if(m_fPressed == 1)
550 this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
551 else if(::GetCapture() != this->m_hWnd)
552 this->KillTimer(ID_TIMER_REPEAT);
553 break;
554 default: // not our timer
555 break;
556 }
557 return 0;
558 }
559
560 LRESULT OnUpdateUiState(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
561 {
562 // If the control is subclassed or superclassed, this message can cause
563 // repainting without WM_PAINT. We don't use this state, so just do nothing.
564 return 0;
565 }
566
567 // Implementation
568 void Init()
569 {
570 // We need this style to prevent Windows from painting the button
571 this->ModifyStyle(0, BS_OWNERDRAW);
572
573 // create a tool tip
574 m_tip.Create(this->m_hWnd);
575 ATLASSERT(m_tip.IsWindow());
576 if(m_tip.IsWindow() && (m_lpstrToolTipText != NULL))
577 {
578 m_tip.Activate(TRUE);
579 m_tip.AddTool(this->m_hWnd, m_lpstrToolTipText);
580 }
581
582 if((m_ImageList.m_hImageList != NULL) && ((m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0))
583 SizeToImage();
584 }
585
586 BOOL StartTrackMouseLeave()
587 {
588 TRACKMOUSEEVENT tme = {};
589 tme.cbSize = sizeof(tme);
590 tme.dwFlags = TME_LEAVE;
591 tme.hwndTrack = this->m_hWnd;
592 return ::TrackMouseEvent(&tme);
593 }
594
595 bool IsHoverMode() const
596 {
597 return ((m_dwExtendedStyle & BMPBTN_HOVER) != 0);
598 }
599
600 bool IsCheckMode() const
601 {
602 return ((m_dwExtendedStyle & (BMPBTN_CHECK | BMPBTN_AUTOCHECK)) != 0);
603 }
604 };
605
606 class CBitmapButton : public CBitmapButtonImpl<CBitmapButton>
607 {
608 public:
609 DECLARE_WND_SUPERCLASS(_T("WTL_BitmapButton"), GetWndClassName())
610
611 CBitmapButton(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) :
612 CBitmapButtonImpl<CBitmapButton>(dwExtendedStyle, hImageList)
613 { }
614 };
615
616
617 ///////////////////////////////////////////////////////////////////////////////
618 // CCheckListCtrlView - list view control with check boxes
619
620 template <DWORD t_dwStyle, DWORD t_dwExStyle, DWORD t_dwExListViewStyle>
621 class CCheckListViewCtrlImplTraits
622 {
623 public:
624 static DWORD GetWndStyle(DWORD dwStyle)
625 {
626 return (dwStyle == 0) ? t_dwStyle : dwStyle;
627 }
628
629 static DWORD GetWndExStyle(DWORD dwExStyle)
630 {
631 return (dwExStyle == 0) ? t_dwExStyle : dwExStyle;
632 }
633
634 static DWORD GetExtendedLVStyle()
635 {
636 return t_dwExListViewStyle;
637 }
638 };
639
640 typedef CCheckListViewCtrlImplTraits<WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SHOWSELALWAYS, WS_EX_CLIENTEDGE, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT> CCheckListViewCtrlTraits;
641
642 template <class T, class TBase = CListViewCtrl, class TWinTraits = CCheckListViewCtrlTraits>
643 class ATL_NO_VTABLE CCheckListViewCtrlImpl : public ATL::CWindowImpl<T, TBase, TWinTraits >
644 {
645 public:
646 DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
647
648 // Attributes
649 static DWORD GetExtendedLVStyle()
650 {
651 return TWinTraits::GetExtendedLVStyle();
652 }
653
654 // Operations
655 BOOL SubclassWindow(HWND hWnd)
656 {
657 BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
658 if(bRet != FALSE)
659 {
660 T* pT = static_cast<T*>(this);
661 pT->Init();
662 }
663
664 return bRet;
665 }
666
667 void CheckSelectedItems(int nCurrItem)
668 {
669 // first check if this item is selected
670 LVITEM lvi = {};
671 lvi.iItem = nCurrItem;
672 lvi.iSubItem = 0;
673 lvi.mask = LVIF_STATE;
674 lvi.stateMask = LVIS_SELECTED;
675 this->GetItem(&lvi);
676 // if item is not selected, don't do anything
677 if(!(lvi.state & LVIS_SELECTED))
678 return;
679 // new check state will be reverse of the current state,
680 BOOL bCheck = !this->GetCheckState(nCurrItem);
681 int nItem = -1;
682 int nOldItem = -1;
683 while((nItem = this->GetNextItem(nOldItem, LVNI_SELECTED)) != -1)
684 {
685 if(nItem != nCurrItem)
686 this->SetCheckState(nItem, bCheck);
687 nOldItem = nItem;
688 }
689 }
690
691 // Implementation
692 void Init()
693 {
694 T* pT = static_cast<T*>(this);
695 (void)pT; // avoid level 4 warning
696 ATLASSERT((pT->GetExtendedLVStyle() & LVS_EX_CHECKBOXES) != 0);
697 this->SetExtendedListViewStyle(pT->GetExtendedLVStyle());
698 }
699
700 // Message map and handlers
701 BEGIN_MSG_MAP(CCheckListViewCtrlImpl)
702 MESSAGE_HANDLER(WM_CREATE, OnCreate)
703 MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
704 MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDown)
705 MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
706 END_MSG_MAP()
707
708 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
709 {
710 // first let list view control initialize everything
711 LRESULT lRet = this->DefWindowProc(uMsg, wParam, lParam);
712 if(lRet == 0)
713 {
714 T* pT = static_cast<T*>(this);
715 pT->Init();
716 }
717
718 return lRet;
719 }
720
721 LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
722 {
723 POINT ptMsg = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
724 LVHITTESTINFO lvh = {};
725 lvh.pt = ptMsg;
726 if((this->HitTest(&lvh) != -1) && (lvh.flags == LVHT_ONITEMSTATEICON) && (::GetKeyState(VK_CONTROL) >= 0))
727 {
728 T* pT = static_cast<T*>(this);
729 pT->CheckSelectedItems(lvh.iItem);
730 }
731 bHandled = FALSE;
732 return 1;
733 }
734
735 LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
736 {
737 if(wParam == VK_SPACE)
738 {
739 int nCurrItem = this->GetNextItem(-1, LVNI_FOCUSED);
740 if((nCurrItem != -1) && (::GetKeyState(VK_CONTROL) >= 0))
741 {
742 T* pT = static_cast<T*>(this);
743 pT->CheckSelectedItems(nCurrItem);
744 }
745 }
746 bHandled = FALSE;
747 return 1;
748 }
749 };
750
751 class CCheckListViewCtrl : public CCheckListViewCtrlImpl<CCheckListViewCtrl>
752 {
753 public:
754 DECLARE_WND_SUPERCLASS(_T("WTL_CheckListView"), GetWndClassName())
755 };
756
757
758 ///////////////////////////////////////////////////////////////////////////////
759 // CHyperLink - hyper link control implementation
760
761 #define HLINK_UNDERLINED 0x00000000
762 #define HLINK_NOTUNDERLINED 0x00000001
763 #define HLINK_UNDERLINEHOVER 0x00000002
764 #define HLINK_COMMANDBUTTON 0x00000004
765 #define HLINK_NOTIFYBUTTON 0x0000000C
766 #define HLINK_USETAGS 0x00000010
767 #define HLINK_USETAGSBOLD 0x00000030
768 #define HLINK_NOTOOLTIP 0x00000040
769 #define HLINK_AUTOCREATELINKFONT 0x00000080
770 #define HLINK_SINGLELINE 0x00000100
771
772 // Notes:
773 // - HLINK_USETAGS and HLINK_USETAGSBOLD are always left-aligned
774 // - When HLINK_USETAGSBOLD is used, the underlined styles will be ignored
775
776 template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
777 class ATL_NO_VTABLE CHyperLinkImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
778 {
779 public:
780 LPTSTR m_lpstrLabel;
781 LPTSTR m_lpstrHyperLink;
782
783 HCURSOR m_hCursor;
784 HFONT m_hFontLink;
785 HFONT m_hFontNormal;
786
787 RECT m_rcLink;
788 CToolTipCtrl m_tip;
789
790 COLORREF m_clrLink;
791 COLORREF m_clrVisited;
792
793 DWORD m_dwExtendedStyle; // Hyper Link specific extended styles
794
795 bool m_bPaintLabel:1;
796 bool m_bVisited:1;
797 bool m_bHover:1;
798 bool m_bInternalLinkFont:1;
799 bool m_bInternalNormalFont:1;
800
801
802 // Constructor/Destructor
803 CHyperLinkImpl(DWORD dwExtendedStyle = HLINK_UNDERLINED) :
804 m_lpstrLabel(NULL), m_lpstrHyperLink(NULL),
805 m_hCursor(NULL), m_hFontLink(NULL), m_hFontNormal(NULL),
806 m_clrLink(RGB(0, 0, 255)), m_clrVisited(RGB(128, 0, 128)),
807 m_dwExtendedStyle(dwExtendedStyle),
808 m_bPaintLabel(true), m_bVisited(false),
809 m_bHover(false), m_bInternalLinkFont(false), m_bInternalNormalFont(false)
810 {
811 ::SetRectEmpty(&m_rcLink);
812 }
813
814 ~CHyperLinkImpl()
815 {
816 delete [] m_lpstrLabel;
817 delete [] m_lpstrHyperLink;
818 }
819
820 // Attributes
821 DWORD GetHyperLinkExtendedStyle() const
822 {
823 return m_dwExtendedStyle;
824 }
825
826 DWORD SetHyperLinkExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
827 {
828 DWORD dwPrevStyle = m_dwExtendedStyle;
829 if(dwMask == 0)
830 m_dwExtendedStyle = dwExtendedStyle;
831 else
832 m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
833 return dwPrevStyle;
834 }
835
836 bool GetLabel(LPTSTR lpstrBuffer, int nLength) const
837 {
838 if(m_lpstrLabel == NULL)
839 return false;
840 ATLASSERT(lpstrBuffer != NULL);
841 if(nLength <= lstrlen(m_lpstrLabel))
842 return false;
843
844 ATL::Checked::tcscpy_s(lpstrBuffer, nLength, m_lpstrLabel);
845
846 return true;
847 }
848
849 bool SetLabel(LPCTSTR lpstrLabel)
850 {
851 delete [] m_lpstrLabel;
852 m_lpstrLabel = NULL;
853 int cchLen = lstrlen(lpstrLabel) + 1;
854 ATLTRY(m_lpstrLabel = new TCHAR[cchLen]);
855 if(m_lpstrLabel == NULL)
856 return false;
857
858 ATL::Checked::tcscpy_s(m_lpstrLabel, cchLen, lpstrLabel);
859 T* pT = static_cast<T*>(this);
860 pT->CalcLabelRect();
861
862 if(this->m_hWnd != NULL)
863 this->SetWindowText(lpstrLabel); // Set this for accessibility
864
865 return true;
866 }
867
868 bool GetHyperLink(LPTSTR lpstrBuffer, int nLength) const
869 {
870 if(m_lpstrHyperLink == NULL)
871 return false;
872 ATLASSERT(lpstrBuffer != NULL);
873 if(nLength <= lstrlen(m_lpstrHyperLink))
874 return false;
875
876 ATL::Checked::tcscpy_s(lpstrBuffer, nLength, m_lpstrHyperLink);
877
878 return true;
879 }
880
881 bool SetHyperLink(LPCTSTR lpstrLink)
882 {
883 delete [] m_lpstrHyperLink;
884 m_lpstrHyperLink = NULL;
885 int cchLen = lstrlen(lpstrLink) + 1;
886 ATLTRY(m_lpstrHyperLink = new TCHAR[cchLen]);
887 if(m_lpstrHyperLink == NULL)
888 return false;
889
890 ATL::Checked::tcscpy_s(m_lpstrHyperLink, cchLen, lpstrLink);
891 if(m_lpstrLabel == NULL)
892 {
893 T* pT = static_cast<T*>(this);
894 pT->CalcLabelRect();
895 }
896
897 if(m_tip.IsWindow())
898 {
899 m_tip.Activate(TRUE);
900 m_tip.AddTool(this->m_hWnd, m_lpstrHyperLink, &m_rcLink, 1);
901 }
902
903 return true;
904 }
905
906 HFONT GetLinkFont() const
907 {
908 return m_hFontLink;
909 }
910
911 void SetLinkFont(HFONT hFont)
912 {
913 if(m_bInternalLinkFont)
914 {
915 ::DeleteObject(m_hFontLink);
916 m_bInternalLinkFont = false;
917 }
918
919 m_hFontLink = hFont;
920
921 T* pT = static_cast<T*>(this);
922 pT->CalcLabelRect();
923 }
924
925 int GetIdealHeight() const
926 {
927 ATLASSERT(::IsWindow(this->m_hWnd));
928 if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
929 return -1;
930 if(!m_bPaintLabel)
931 return -1;
932
933 UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
934
935 CClientDC dc(this->m_hWnd);
936 RECT rect = {};
937 this->GetClientRect(&rect);
938 HFONT hFontOld = dc.SelectFont(m_hFontNormal);
939 RECT rcText = rect;
940 dc.DrawText(_T("NS"), -1, &rcText, DT_LEFT | uFormat | DT_CALCRECT);
941 dc.SelectFont(m_hFontLink);
942 RECT rcLink = rect;
943 dc.DrawText(_T("NS"), -1, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);
944 dc.SelectFont(hFontOld);
945 return __max(rcText.bottom - rcText.top, rcLink.bottom - rcLink.top);
946 }
947
948 bool GetIdealSize(SIZE& size) const
949 {
950 int cx = 0, cy = 0;
951 bool bRet = GetIdealSize(cx, cy);
952 if(bRet)
953 {
954 size.cx = cx;
955 size.cy = cy;
956 }
957 return bRet;
958 }
959
960 bool GetIdealSize(int& cx, int& cy) const
961 {
962 ATLASSERT(::IsWindow(this->m_hWnd));
963 if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
964 return false;
965 if(!m_bPaintLabel)
966 return false;
967
968 CClientDC dc(this->m_hWnd);
969 RECT rcClient = {};
970 this->GetClientRect(&rcClient);
971 RECT rcAll = rcClient;
972
973 if(IsUsingTags())
974 {
975 // find tags and label parts
976 LPTSTR lpstrLeft = NULL;
977 int cchLeft = 0;
978 LPTSTR lpstrLink = NULL;
979 int cchLink = 0;
980 LPTSTR lpstrRight = NULL;
981 int cchRight = 0;
982
983 const T* pT = static_cast<const T*>(this);
984 pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);
985
986 // get label part rects
987 UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
988
989 HFONT hFontOld = dc.SelectFont(m_hFontNormal);
990 RECT rcLeft = rcClient;
991 dc.DrawText(lpstrLeft, cchLeft, &rcLeft, DT_LEFT | uFormat | DT_CALCRECT);
992
993 dc.SelectFont(m_hFontLink);
994 RECT rcLink = { rcLeft.right, rcLeft.top, rcClient.right, rcClient.bottom };
995 dc.DrawText(lpstrLink, cchLink, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);
996
997 dc.SelectFont(m_hFontNormal);
998 RECT rcRight = { rcLink.right, rcLink.top, rcClient.right, rcClient.bottom };
999 dc.DrawText(lpstrRight, cchRight, &rcRight, DT_LEFT | uFormat | DT_CALCRECT);
1000
1001 dc.SelectFont(hFontOld);
1002
1003 int cyMax = __max(rcLeft.bottom, __max(rcLink.bottom, rcRight.bottom));
1004 ::SetRect(&rcAll, rcLeft.left, rcLeft.top, rcRight.right, cyMax);
1005 }
1006 else
1007 {
1008 HFONT hOldFont = NULL;
1009 if(m_hFontLink != NULL)
1010 hOldFont = dc.SelectFont(m_hFontLink);
1011 LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
1012 DWORD dwStyle = this->GetStyle();
1013 UINT uFormat = DT_LEFT;
1014 if (dwStyle & SS_CENTER)
1015 uFormat = DT_CENTER;
1016 else if (dwStyle & SS_RIGHT)
1017 uFormat = DT_RIGHT;
1018 uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
1019 dc.DrawText(lpstrText, -1, &rcAll, uFormat | DT_CALCRECT);
1020 if(m_hFontLink != NULL)
1021 dc.SelectFont(hOldFont);
1022 if (dwStyle & SS_CENTER)
1023 {
1024 int dx = (rcClient.right - rcAll.right) / 2;
1025 ::OffsetRect(&rcAll, dx, 0);
1026 }
1027 else if (dwStyle & SS_RIGHT)
1028 {
1029 int dx = rcClient.right - rcAll.right;
1030 ::OffsetRect(&rcAll, dx, 0);
1031 }
1032 }
1033
1034 cx = rcAll.right - rcAll.left;
1035 cy = rcAll.bottom - rcAll.top;
1036
1037 return true;
1038 }
1039
1040 // for command buttons only
1041 bool GetToolTipText(LPTSTR lpstrBuffer, int nLength) const
1042 {
1043 ATLASSERT(IsCommandButton());
1044 return GetHyperLink(lpstrBuffer, nLength);
1045 }
1046
1047 bool SetToolTipText(LPCTSTR lpstrToolTipText)
1048 {
1049 ATLASSERT(IsCommandButton());
1050 return SetHyperLink(lpstrToolTipText);
1051 }
1052
1053 // Operations
1054 BOOL SubclassWindow(HWND hWnd)
1055 {
1056 ATLASSERT(this->m_hWnd == NULL);
1057 ATLASSERT(::IsWindow(hWnd));
1058 if(m_hFontNormal == NULL)
1059 m_hFontNormal = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0L);
1060
1061 BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
1062 if(bRet != FALSE)
1063 {
1064 T* pT = static_cast<T*>(this);
1065 pT->Init();
1066 }
1067
1068 return bRet;
1069 }
1070
1071 bool Navigate()
1072 {
1073 ATLASSERT(::IsWindow(this->m_hWnd));
1074 bool bRet = true;
1075 if(IsNotifyButton())
1076 {
1077 NMHDR nmhdr = { this->m_hWnd, (UINT_PTR)this->GetDlgCtrlID(), NM_CLICK };
1078 this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr);
1079 }
1080 else if(IsCommandButton())
1081 {
1082 this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
1083 }
1084 else
1085 {
1086 ATLASSERT(m_lpstrHyperLink != NULL);
1087 DWORD_PTR dwRet = (DWORD_PTR)::ShellExecute(0, _T("open"), m_lpstrHyperLink, 0, 0, SW_SHOWNORMAL);
1088 bRet = (dwRet > 32);
1089 ATLASSERT(bRet);
1090 if(bRet)
1091 {
1092 m_bVisited = true;
1093 this->Invalidate();
1094 }
1095 }
1096 return bRet;
1097 }
1098
1099 void CreateLinkFontFromNormal()
1100 {
1101 if(m_bInternalLinkFont)
1102 {
1103 ::DeleteObject(m_hFontLink);
1104 m_bInternalLinkFont = false;
1105 }
1106
1107 CFontHandle font = (m_hFontNormal != NULL) ? m_hFontNormal : (HFONT)::GetStockObject(SYSTEM_FONT);
1108 LOGFONT lf = {};
1109 font.GetLogFont(&lf);
1110
1111 if(IsUsingTagsBold())
1112 lf.lfWeight = FW_BOLD;
1113 else if(!IsNotUnderlined())
1114 lf.lfUnderline = TRUE;
1115
1116 m_hFontLink = ::CreateFontIndirect(&lf);
1117 m_bInternalLinkFont = true;
1118 ATLASSERT(m_hFontLink != NULL);
1119 }
1120
1121 // Message map and handlers
1122 BEGIN_MSG_MAP(CHyperLinkImpl)
1123 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1124 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1125 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
1126 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1127 MESSAGE_HANDLER(WM_PAINT, OnPaint)
1128 MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
1129 MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
1130 MESSAGE_HANDLER(WM_KILLFOCUS, OnFocus)
1131 MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
1132 MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
1133 MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
1134 MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
1135 MESSAGE_HANDLER(WM_CHAR, OnChar)
1136 MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
1137 MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)
1138 MESSAGE_HANDLER(WM_ENABLE, OnEnable)
1139 MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
1140 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1141 MESSAGE_HANDLER(WM_UPDATEUISTATE, OnUpdateUiState)
1142 MESSAGE_HANDLER(WM_SIZE, OnSize)
1143 END_MSG_MAP()
1144
1145 LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1146 {
1147 T* pT = static_cast<T*>(this);
1148 pT->Init();
1149 return 0;
1150 }
1151
1152 LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
1153 {
1154 if(m_tip.IsWindow())
1155 {
1156 m_tip.DestroyWindow();
1157 m_tip.m_hWnd = NULL;
1158 }
1159
1160 if(m_bInternalLinkFont)
1161 {
1162 ::DeleteObject(m_hFontLink);
1163 m_hFontLink = NULL;
1164 m_bInternalLinkFont = false;
1165 }
1166
1167 if(m_bInternalNormalFont)
1168 {
1169 ::DeleteObject(m_hFontNormal);
1170 m_hFontNormal = NULL;
1171 m_bInternalNormalFont = false;
1172 }
1173
1174 bHandled = FALSE;
1175 return 1;
1176 }
1177
1178 LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1179 {
1180 MSG msg = { this->m_hWnd, uMsg, wParam, lParam };
1181 if(m_tip.IsWindow() && IsUsingToolTip())
1182 m_tip.RelayEvent(&msg);
1183 bHandled = FALSE;
1184 return 1;
1185 }
1186
1187 LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1188 {
1189 return 1; // no background painting needed (we do it all during WM_PAINT)
1190 }
1191
1192 LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
1193 {
1194 if(!m_bPaintLabel)
1195 {
1196 bHandled = FALSE;
1197 return 1;
1198 }
1199
1200 T* pT = static_cast<T*>(this);
1201 if(wParam != NULL)
1202 {
1203 pT->DoEraseBackground((HDC)wParam);
1204 pT->DoPaint((HDC)wParam);
1205 }
1206 else
1207 {
1208 CPaintDC dc(this->m_hWnd);
1209 pT->DoEraseBackground(dc.m_hDC);
1210 pT->DoPaint(dc.m_hDC);
1211 }
1212
1213 return 0;
1214 }
1215
1216 LRESULT OnFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
1217 {
1218 if(m_bPaintLabel)
1219 this->Invalidate();
1220 else
1221 bHandled = FALSE;
1222 return 0;
1223 }
1224
1225 LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
1226 {
1227 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
1228 if(((m_lpstrHyperLink != NULL) || IsCommandButton()) && ::PtInRect(&m_rcLink, pt))
1229 {
1230 ::SetCursor(m_hCursor);
1231 if(IsUnderlineHover())
1232 {
1233 if(!m_bHover)
1234 {
1235 m_bHover = true;
1236 this->InvalidateRect(&m_rcLink);
1237 this->UpdateWindow();
1238 StartTrackMouseLeave();
1239 }
1240 }
1241 }
1242 else
1243 {
1244 if(IsUnderlineHover())
1245 {
1246 if(m_bHover)
1247 {
1248 m_bHover = false;
1249 this->InvalidateRect(&m_rcLink);
1250 this->UpdateWindow();
1251 }
1252 }
1253 bHandled = FALSE;
1254 }
1255 return 0;
1256 }
1257
1258 LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1259 {
1260 if(IsUnderlineHover() && m_bHover)
1261 {
1262 m_bHover = false;
1263 this->InvalidateRect(&m_rcLink);
1264 this->UpdateWindow();
1265 }
1266 return 0;
1267 }
1268
1269 LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
1270 {
1271 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
1272 if(::PtInRect(&m_rcLink, pt))
1273 {
1274 this->SetFocus();
1275 this->SetCapture();
1276 }
1277 return 0;
1278 }
1279
1280 LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
1281 {
1282 if(GetCapture() == this->m_hWnd)
1283 {
1284 ReleaseCapture();
1285 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
1286 if(::PtInRect(&m_rcLink, pt))
1287 {
1288 T* pT = static_cast<T*>(this);
1289 pT->Navigate();
1290 }
1291 }
1292 return 0;
1293 }
1294
1295 LRESULT OnChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1296 {
1297 if((wParam == VK_RETURN) || (wParam == VK_SPACE))
1298 {
1299 T* pT = static_cast<T*>(this);
1300 pT->Navigate();
1301 }
1302 return 0;
1303 }
1304
1305 LRESULT OnGetDlgCode(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1306 {
1307 return DLGC_WANTCHARS;
1308 }
1309
1310 LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
1311 {
1312 POINT pt = {};
1313 GetCursorPos(&pt);
1314 this->ScreenToClient(&pt);
1315 if(((m_lpstrHyperLink != NULL) || IsCommandButton()) && ::PtInRect(&m_rcLink, pt))
1316 {
1317 return TRUE;
1318 }
1319 bHandled = FALSE;
1320 return FALSE;
1321 }
1322
1323 LRESULT OnEnable(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1324 {
1325 this->Invalidate();
1326 this->UpdateWindow();
1327 return 0;
1328 }
1329
1330 LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1331 {
1332 return (LRESULT)m_hFontNormal;
1333 }
1334
1335 LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
1336 {
1337 if(m_bInternalNormalFont)
1338 {
1339 ::DeleteObject(m_hFontNormal);
1340 m_bInternalNormalFont = false;
1341 }
1342
1343 bool bCreateLinkFont = m_bInternalLinkFont;
1344
1345 m_hFontNormal = (HFONT)wParam;
1346
1347 if(bCreateLinkFont || IsAutoCreateLinkFont())
1348 CreateLinkFontFromNormal();
1349
1350 T* pT = static_cast<T*>(this);
1351 pT->CalcLabelRect();
1352
1353 if((BOOL)lParam)
1354 {
1355 this->Invalidate();
1356 this->UpdateWindow();
1357 }
1358
1359 return 0;
1360 }
1361
1362 LRESULT OnUpdateUiState(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1363 {
1364 // If the control is subclassed or superclassed, this message can cause
1365 // repainting without WM_PAINT. We don't use this state, so just do nothing.
1366 return 0;
1367 }
1368
1369 LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
1370 {
1371 T* pT = static_cast<T*>(this);
1372 pT->CalcLabelRect();
1373 pT->Invalidate();
1374 return 0;
1375 }
1376
1377 // Implementation
1378 void Init()
1379 {
1380 ATLASSERT(::IsWindow(this->m_hWnd));
1381
1382 // Check if we should paint a label
1383 const int cchBuff = 8;
1384 TCHAR szBuffer[cchBuff] = {};
1385 if(::GetClassName(this->m_hWnd, szBuffer, cchBuff))
1386 {
1387 if(lstrcmpi(szBuffer, _T("static")) == 0)
1388 {
1389 this->ModifyStyle(0, SS_NOTIFY); // we need this
1390 DWORD dwStyle = this->GetStyle() & 0x000000FF;
1391 if((dwStyle == SS_ICON) || (dwStyle == SS_BLACKRECT) || (dwStyle == SS_GRAYRECT) ||
1392 (dwStyle == SS_WHITERECT) || (dwStyle == SS_BLACKFRAME) || (dwStyle == SS_GRAYFRAME) ||
1393 (dwStyle == SS_WHITEFRAME) || (dwStyle == SS_OWNERDRAW) ||
1394 (dwStyle == SS_BITMAP) || (dwStyle == SS_ENHMETAFILE))
1395 m_bPaintLabel = false;
1396 }
1397 }
1398
1399 // create or load a cursor
1400 m_hCursor = ::LoadCursor(NULL, IDC_HAND);
1401 ATLASSERT(m_hCursor != NULL);
1402
1403 // set fonts
1404 if(m_bPaintLabel)
1405 {
1406 if(m_hFontNormal == NULL)
1407 {
1408 m_hFontNormal = AtlCreateControlFont();
1409 m_bInternalNormalFont = true;
1410 }
1411
1412 if(m_hFontLink == NULL)
1413 CreateLinkFontFromNormal();
1414 }
1415
1416 // create a tool tip
1417 m_tip.Create(this->m_hWnd);
1418 ATLASSERT(m_tip.IsWindow());
1419
1420 // set label (defaults to window text)
1421 if(m_lpstrLabel == NULL)
1422 {
1423 int nLen = this->GetWindowTextLength();
1424 if(nLen > 0)
1425 {
1426 ATLTRY(m_lpstrLabel = new TCHAR[nLen + 1]);
1427 if(m_lpstrLabel != NULL)
1428 ATLVERIFY(this->GetWindowText(m_lpstrLabel, nLen + 1) > 0);
1429 }
1430 }
1431
1432 T* pT = static_cast<T*>(this);
1433 pT->CalcLabelRect();
1434
1435 // set hyperlink (defaults to label), or just activate tool tip if already set
1436 if((m_lpstrHyperLink == NULL) && !IsCommandButton())
1437 {
1438 if(m_lpstrLabel != NULL)
1439 SetHyperLink(m_lpstrLabel);
1440 }
1441 else
1442 {
1443 m_tip.Activate(TRUE);
1444 m_tip.AddTool(this->m_hWnd, m_lpstrHyperLink, &m_rcLink, 1);
1445 }
1446
1447 // set link colors
1448 if(m_bPaintLabel)
1449 {
1450 ATL::CRegKey rk;
1451 LONG lRet = rk.Open(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Internet Explorer\\Settings"));
1452 if(lRet == ERROR_SUCCESS)
1453 {
1454 const int cchValue = 12;
1455 TCHAR szValue[cchValue] = {};
1456 ULONG ulCount = cchValue;
1457 lRet = rk.QueryStringValue(_T("Anchor Color"), szValue, &ulCount);
1458 if(lRet == ERROR_SUCCESS)
1459 {
1460 COLORREF clr = pT->_ParseColorString(szValue);
1461 ATLASSERT(clr != CLR_INVALID);
1462 if(clr != CLR_INVALID)
1463 m_clrLink = clr;
1464 }
1465
1466 ulCount = cchValue;
1467 lRet = rk.QueryStringValue(_T("Anchor Color Visited"), szValue, &ulCount);
1468 if(lRet == ERROR_SUCCESS)
1469 {
1470 COLORREF clr = pT->_ParseColorString(szValue);
1471 ATLASSERT(clr != CLR_INVALID);
1472 if(clr != CLR_INVALID)
1473 m_clrVisited = clr;
1474 }
1475 }
1476 }
1477 }
1478
1479 static COLORREF _ParseColorString(LPTSTR lpstr)
1480 {
1481 int c[3] = { -1, -1, -1 };
1482 LPTSTR p = NULL;
1483 for(int i = 0; i < 2; i++)
1484 {
1485 for(p = lpstr; *p != _T('\0'); p = ::CharNext(p))
1486 {
1487 if(*p == _T(','))
1488 {
1489 *p = _T('\0');
1490 c[i] = _ttoi(lpstr);
1491 lpstr = &p[1];
1492 break;
1493 }
1494 }
1495 if(c[i] == -1)
1496 return CLR_INVALID;
1497 }
1498 if(*lpstr == _T('\0'))
1499 return CLR_INVALID;
1500 c[2] = _ttoi(lpstr);
1501
1502 return RGB(c[0], c[1], c[2]);
1503 }
1504
1505 bool CalcLabelRect()
1506 {
1507 if(!::IsWindow(this->m_hWnd))
1508 return false;
1509 if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
1510 return false;
1511
1512 CClientDC dc(this->m_hWnd);
1513 RECT rcClient = {};
1514 this->GetClientRect(&rcClient);
1515 m_rcLink = rcClient;
1516 if(!m_bPaintLabel)
1517 return true;
1518
1519 if(IsUsingTags())
1520 {
1521 // find tags and label parts
1522 LPTSTR lpstrLeft = NULL;
1523 int cchLeft = 0;
1524 LPTSTR lpstrLink = NULL;
1525 int cchLink = 0;
1526 LPTSTR lpstrRight = NULL;
1527 int cchRight = 0;
1528
1529 T* pT = static_cast<T*>(this);
1530 pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);
1531 ATLASSERT(lpstrLink != NULL);
1532 ATLASSERT(cchLink > 0);
1533
1534 // get label part rects
1535 HFONT hFontOld = dc.SelectFont(m_hFontNormal);
1536
1537 UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
1538
1539 RECT rcLeft = rcClient;
1540 if(lpstrLeft != NULL)
1541 dc.DrawText(lpstrLeft, cchLeft, &rcLeft, DT_LEFT | uFormat | DT_CALCRECT);
1542
1543 dc.SelectFont(m_hFontLink);
1544 RECT rcLink = rcClient;
1545 if(lpstrLeft != NULL)
1546 rcLink.left = rcLeft.right;
1547 dc.DrawText(lpstrLink, cchLink, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);
1548
1549 dc.SelectFont(hFontOld);
1550
1551 m_rcLink = rcLink;
1552 }
1553 else
1554 {
1555 HFONT hOldFont = NULL;
1556 if(m_hFontLink != NULL)
1557 hOldFont = dc.SelectFont(m_hFontLink);
1558 LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
1559 DWORD dwStyle = this->GetStyle();
1560 UINT uFormat = DT_LEFT;
1561 if (dwStyle & SS_CENTER)
1562 uFormat = DT_CENTER;
1563 else if (dwStyle & SS_RIGHT)
1564 uFormat = DT_RIGHT;
1565 uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
1566 dc.DrawText(lpstrText, -1, &m_rcLink, uFormat | DT_CALCRECT);
1567 if(m_hFontLink != NULL)
1568 dc.SelectFont(hOldFont);
1569 if (dwStyle & SS_CENTER)
1570 {
1571 int dx = (rcClient.right - m_rcLink.right) / 2;
1572 ::OffsetRect(&m_rcLink, dx, 0);
1573 }
1574 else if (dwStyle & SS_RIGHT)
1575 {
1576 int dx = rcClient.right - m_rcLink.right;
1577 ::OffsetRect(&m_rcLink, dx, 0);
1578 }
1579 }
1580
1581 return true;
1582 }
1583
1584 void CalcLabelParts(LPTSTR& lpstrLeft, int& cchLeft, LPTSTR& lpstrLink, int& cchLink, LPTSTR& lpstrRight, int& cchRight) const
1585 {
1586 lpstrLeft = NULL;
1587 cchLeft = 0;
1588 lpstrLink = NULL;
1589 cchLink = 0;
1590 lpstrRight = NULL;
1591 cchRight = 0;
1592
1593 LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
1594 int cchText = lstrlen(lpstrText);
1595 bool bOutsideLink = true;
1596 for(int i = 0; i < cchText; i++)
1597 {
1598 if(lpstrText[i] != _T('<'))
1599 continue;
1600
1601 if(bOutsideLink)
1602 {
1603 if(::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, &lpstrText[i], 3, _T("<A>"), 3) == CSTR_EQUAL)
1604 {
1605 if(i > 0)
1606 {
1607 lpstrLeft = lpstrText;
1608 cchLeft = i;
1609 }
1610 lpstrLink = &lpstrText[i + 3];
1611 bOutsideLink = false;
1612 }
1613 }
1614 else
1615 {
1616 if(::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, &lpstrText[i], 4, _T("</A>"), 4) == CSTR_EQUAL)
1617 {
1618 cchLink = i - 3 - cchLeft;
1619 if(lpstrText[i + 4] != 0)
1620 {
1621 lpstrRight = &lpstrText[i + 4];
1622 cchRight = cchText - (i + 4);
1623 break;
1624 }
1625 }
1626 }
1627 }
1628
1629 }
1630
1631 void DoEraseBackground(CDCHandle dc)
1632 {
1633 HBRUSH hBrush = (HBRUSH)this->GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)dc.m_hDC, (LPARAM)this->m_hWnd);
1634 if(hBrush != NULL)
1635 {
1636 RECT rect = {};
1637 this->GetClientRect(&rect);
1638 dc.FillRect(&rect, hBrush);
1639 }
1640 }
1641
1642 void DoPaint(CDCHandle dc)
1643 {
1644 if(IsUsingTags())
1645 {
1646 // find tags and label parts
1647 LPTSTR lpstrLeft = NULL;
1648 int cchLeft = 0;
1649 LPTSTR lpstrLink = NULL;
1650 int cchLink = 0;
1651 LPTSTR lpstrRight = NULL;
1652 int cchRight = 0;
1653
1654 T* pT = static_cast<T*>(this);
1655 pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);
1656
1657 // get label part rects
1658 RECT rcClient = {};
1659 this->GetClientRect(&rcClient);
1660
1661 dc.SetBkMode(TRANSPARENT);
1662 HFONT hFontOld = dc.SelectFont(m_hFontNormal);
1663
1664 UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
1665
1666 if(lpstrLeft != NULL)
1667 dc.DrawText(lpstrLeft, cchLeft, &rcClient, DT_LEFT | uFormat);
1668
1669 COLORREF clrOld = dc.SetTextColor(this->IsWindowEnabled() ? (m_bVisited ? m_clrVisited : m_clrLink) : (::GetSysColor(COLOR_GRAYTEXT)));
1670 if((m_hFontLink != NULL) && (!IsUnderlineHover() || (IsUnderlineHover() && m_bHover)))
1671 dc.SelectFont(m_hFontLink);
1672 else
1673 dc.SelectFont(m_hFontNormal);
1674
1675 dc.DrawText(lpstrLink, cchLink, &m_rcLink, DT_LEFT | uFormat);
1676
1677 dc.SetTextColor(clrOld);
1678 dc.SelectFont(m_hFontNormal);
1679 if(lpstrRight != NULL)
1680 {
1681 RECT rcRight = { m_rcLink.right, m_rcLink.top, rcClient.right, rcClient.bottom };
1682 dc.DrawText(lpstrRight, cchRight, &rcRight, DT_LEFT | uFormat);
1683 }
1684
1685 if(GetFocus() == this->m_hWnd)
1686 dc.DrawFocusRect(&m_rcLink);
1687
1688 dc.SelectFont(hFontOld);
1689 }
1690 else
1691 {
1692 dc.SetBkMode(TRANSPARENT);
1693 COLORREF clrOld = dc.SetTextColor(this->IsWindowEnabled() ? (m_bVisited ? m_clrVisited : m_clrLink) : (::GetSysColor(COLOR_GRAYTEXT)));
1694
1695 HFONT hFontOld = NULL;
1696 if((m_hFontLink != NULL) && (!IsUnderlineHover() || (IsUnderlineHover() && m_bHover)))
1697 hFontOld = dc.SelectFont(m_hFontLink);
1698 else
1699 hFontOld = dc.SelectFont(m_hFontNormal);
1700
1701 LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
1702
1703 DWORD dwStyle = this->GetStyle();
1704 UINT uFormat = DT_LEFT;
1705 if (dwStyle & SS_CENTER)
1706 uFormat = DT_CENTER;
1707 else if (dwStyle & SS_RIGHT)
1708 uFormat = DT_RIGHT;
1709 uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
1710
1711 dc.DrawText(lpstrText, -1, &m_rcLink, uFormat);
1712
1713 if(GetFocus() == this->m_hWnd)
1714 dc.DrawFocusRect(&m_rcLink);
1715
1716 dc.SetTextColor(clrOld);
1717 dc.SelectFont(hFontOld);
1718 }
1719 }
1720
1721 BOOL StartTrackMouseLeave()
1722 {
1723 TRACKMOUSEEVENT tme = {};
1724 tme.cbSize = sizeof(tme);
1725 tme.dwFlags = TME_LEAVE;
1726 tme.hwndTrack = this->m_hWnd;
1727 return ::TrackMouseEvent(&tme);
1728 }
1729
1730 // Implementation helpers
1731 bool IsUnderlined() const
1732 {
1733 return ((m_dwExtendedStyle & (HLINK_NOTUNDERLINED | HLINK_UNDERLINEHOVER)) == 0);
1734 }
1735
1736 bool IsNotUnderlined() const
1737 {
1738 return ((m_dwExtendedStyle & HLINK_NOTUNDERLINED) != 0);
1739 }
1740
1741 bool IsUnderlineHover() const
1742 {
1743 return ((m_dwExtendedStyle & HLINK_UNDERLINEHOVER) != 0);
1744 }
1745
1746 bool IsCommandButton() const
1747 {
1748 return ((m_dwExtendedStyle & HLINK_COMMANDBUTTON) != 0);
1749 }
1750
1751 bool IsNotifyButton() const
1752 {
1753 return ((m_dwExtendedStyle & HLINK_NOTIFYBUTTON) == HLINK_NOTIFYBUTTON);
1754 }
1755
1756 bool IsUsingTags() const
1757 {
1758 return ((m_dwExtendedStyle & HLINK_USETAGS) != 0);
1759 }
1760
1761 bool IsUsingTagsBold() const
1762 {
1763 return ((m_dwExtendedStyle & HLINK_USETAGSBOLD) == HLINK_USETAGSBOLD);
1764 }
1765
1766 bool IsUsingToolTip() const
1767 {
1768 return ((m_dwExtendedStyle & HLINK_NOTOOLTIP) == 0);
1769 }
1770
1771 bool IsAutoCreateLinkFont() const
1772 {
1773 return ((m_dwExtendedStyle & HLINK_AUTOCREATELINKFONT) == HLINK_AUTOCREATELINKFONT);
1774 }
1775
1776 bool IsSingleLine() const
1777 {
1778 return ((m_dwExtendedStyle & HLINK_SINGLELINE) == HLINK_SINGLELINE);
1779 }
1780 };
1781
1782 class CHyperLink : public CHyperLinkImpl<CHyperLink>
1783 {
1784 public:
1785 DECLARE_WND_CLASS(_T("WTL_HyperLink"))
1786 };
1787
1788
1789 ///////////////////////////////////////////////////////////////////////////////
1790 // CWaitCursor - displays a wait cursor
1791
1792 class CWaitCursor
1793 {
1794 public:
1795 // Data
1796 HCURSOR m_hWaitCursor;
1797 HCURSOR m_hOldCursor;
1798 bool m_bInUse;
1799
1800 // Constructor/destructor
1801 CWaitCursor(bool bSet = true, LPCTSTR lpstrCursor = IDC_WAIT, bool bSys = true) : m_hOldCursor(NULL), m_bInUse(false)
1802 {
1803 HINSTANCE hInstance = bSys ? NULL : ModuleHelper::GetResourceInstance();
1804 m_hWaitCursor = ::LoadCursor(hInstance, lpstrCursor);
1805 ATLASSERT(m_hWaitCursor != NULL);
1806
1807 if(bSet)
1808 Set();
1809 }
1810
1811 ~CWaitCursor()
1812 {
1813 Restore();
1814 }
1815
1816 // Methods
1817 bool Set()
1818 {
1819 if(m_bInUse)
1820 return false;
1821 m_hOldCursor = ::SetCursor(m_hWaitCursor);
1822 m_bInUse = true;
1823 return true;
1824 }
1825
1826 bool Restore()
1827 {
1828 if(!m_bInUse)
1829 return false;
1830 ::SetCursor(m_hOldCursor);
1831 m_bInUse = false;
1832 return true;
1833 }
1834 };
1835
1836
1837 ///////////////////////////////////////////////////////////////////////////////
1838 // CCustomWaitCursor - for custom and animated cursors
1839
1840 class CCustomWaitCursor : public CWaitCursor
1841 {
1842 public:
1843 // Constructor/destructor
1844 CCustomWaitCursor(ATL::_U_STRINGorID cursor, bool bSet = true, HINSTANCE hInstance = NULL) :
1845 CWaitCursor(false, IDC_WAIT, true)
1846 {
1847 if(hInstance == NULL)
1848 hInstance = ModuleHelper::GetResourceInstance();
1849 m_hWaitCursor = (HCURSOR)::LoadImage(hInstance, cursor.m_lpstr, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE);
1850
1851 if(bSet)
1852 Set();
1853 }
1854
1855 ~CCustomWaitCursor()
1856 {
1857 Restore();
1858 ::DestroyCursor(m_hWaitCursor);
1859 }
1860 };
1861
1862
1863 ///////////////////////////////////////////////////////////////////////////////
1864 // CMultiPaneStatusBarCtrl - Status Bar with multiple panes
1865
1866 template <class T, class TBase = CStatusBarCtrl>
1867 class ATL_NO_VTABLE CMultiPaneStatusBarCtrlImpl : public ATL::CWindowImpl< T, TBase >
1868 {
1869 public:
1870 DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
1871
1872 // Data
1873 enum { m_cxPaneMargin = 3 };
1874
1875 int m_nPanes;
1876 int* m_pPane;
1877
1878 // Constructor/destructor
1879 CMultiPaneStatusBarCtrlImpl() : m_nPanes(0), m_pPane(NULL)
1880 { }
1881
1882 ~CMultiPaneStatusBarCtrlImpl()
1883 {
1884 delete [] m_pPane;
1885 }
1886
1887 // Methods
1888 HWND Create(HWND hWndParent, LPCTSTR lpstrText, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP, UINT nID = ATL_IDW_STATUS_BAR)
1889 {
1890 return ATL::CWindowImpl< T, TBase >::Create(hWndParent, this->rcDefault, lpstrText, dwStyle, 0, nID);
1891 }
1892
1893 HWND Create(HWND hWndParent, UINT nTextID = ATL_IDS_IDLEMESSAGE, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP, UINT nID = ATL_IDW_STATUS_BAR)
1894 {
1895 const int cchMax = 128; // max text length is 127 for status bars (+1 for null)
1896 TCHAR szText[cchMax] = {};
1897 ::LoadString(ModuleHelper::GetResourceInstance(), nTextID, szText, cchMax);
1898 return Create(hWndParent, szText, dwStyle, nID);
1899 }
1900
1901 BOOL SetPanes(int* pPanes, int nPanes, bool bSetText = true)
1902 {
1903 ATLASSERT(::IsWindow(this->m_hWnd));
1904 ATLASSERT(nPanes > 0);
1905
1906 m_nPanes = nPanes;
1907 delete [] m_pPane;
1908 m_pPane = NULL;
1909
1910 ATLTRY(m_pPane = new int[nPanes]);
1911 ATLASSERT(m_pPane != NULL);
1912 if(m_pPane == NULL)
1913 return FALSE;
1914
1915 ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
1916 int* pPanesPos = buff.Allocate(nPanes);
1917 ATLASSERT(pPanesPos != NULL);
1918 if(pPanesPos == NULL)
1919 return FALSE;
1920
1921 ATL::Checked::memcpy_s(m_pPane, nPanes * sizeof(int), pPanes, nPanes * sizeof(int));
1922
1923 // get status bar DC and set font
1924 CClientDC dc(this->m_hWnd);
1925 HFONT hOldFont = dc.SelectFont(this->GetFont());
1926
1927 // get status bar borders
1928 int arrBorders[3] = {};
1929 this->GetBorders(arrBorders);
1930
1931 const int cchBuff = 128;
1932 TCHAR szBuff[cchBuff] = {};
1933 int cxLeft = arrBorders[0];
1934
1935 // calculate right edge of each part
1936 for(int i = 0; i < nPanes; i++)
1937 {
1938 if(pPanes[i] == ID_DEFAULT_PANE)
1939 {
1940 // make very large, will be resized later
1941 pPanesPos[i] = INT_MAX / 2;
1942 }
1943 else
1944 {
1945 ::LoadString(ModuleHelper::GetResourceInstance(), pPanes[i], szBuff, cchBuff);
1946 SIZE size = {};
1947 dc.GetTextExtent(szBuff, lstrlen(szBuff), &size);
1948 T* pT = static_cast<T*>(this);
1949 (void)pT; // avoid level 4 warning
1950 pPanesPos[i] = cxLeft + size.cx + arrBorders[2] + 2 * pT->m_cxPaneMargin;
1951 }
1952 cxLeft = pPanesPos[i];
1953 }
1954
1955 BOOL bRet = this->SetParts(nPanes, pPanesPos);
1956
1957 if(bRet && bSetText)
1958 {
1959 for(int i = 0; i < nPanes; i++)
1960 {
1961 if(pPanes[i] != ID_DEFAULT_PANE)
1962 {
1963 ::LoadString(ModuleHelper::GetResourceInstance(), pPanes[i], szBuff, cchBuff);
1964 SetPaneText(m_pPane[i], szBuff);
1965 }
1966 }
1967 }
1968
1969 dc.SelectFont(hOldFont);
1970 return bRet;
1971 }
1972
1973 bool GetPaneTextLength(int nPaneID, int* pcchLength = NULL, int* pnType = NULL) const
1974 {
1975 ATLASSERT(::IsWindow(this->m_hWnd));
1976 int nIndex = GetPaneIndexFromID(nPaneID);
1977 if(nIndex == -1)
1978 return false;
1979
1980 int nLength = this->GetTextLength(nIndex, pnType);
1981 if(pcchLength != NULL)
1982 *pcchLength = nLength;
1983
1984 return true;
1985 }
1986
1987 BOOL GetPaneText(int nPaneID, LPTSTR lpstrText, int* pcchLength = NULL, int* pnType = NULL) const
1988 {
1989 ATLASSERT(::IsWindow(this->m_hWnd));
1990 int nIndex = GetPaneIndexFromID(nPaneID);
1991 if(nIndex == -1)
1992 return FALSE;
1993
1994 int nLength = this->GetText(nIndex, lpstrText, pnType);
1995 if(pcchLength != NULL)
1996 *pcchLength = nLength;
1997
1998 return TRUE;
1999 }
2000
2001 #ifdef __ATLSTR_H__
2002 BOOL GetPaneText(int nPaneID, ATL::CString& strText, int* pcchLength = NULL, int* pnType = NULL) const
2003 {
2004 ATLASSERT(::IsWindow(this->m_hWnd));
2005 int nIndex = GetPaneIndexFromID(nPaneID);
2006 if(nIndex == -1)
2007 return FALSE;
2008
2009 int nLength = this->GetText(nIndex, strText, pnType);
2010 if(pcchLength != NULL)
2011 *pcchLength = nLength;
2012
2013 return TRUE;
2014 }
2015 #endif // __ATLSTR_H__
2016
2017 BOOL SetPaneText(int nPaneID, LPCTSTR lpstrText, int nType = 0)
2018 {
2019 ATLASSERT(::IsWindow(this->m_hWnd));
2020 int nIndex = GetPaneIndexFromID(nPaneID);
2021 if(nIndex == -1)
2022 return FALSE;
2023
2024 return this->SetText(nIndex, lpstrText, nType);
2025 }
2026
2027 BOOL GetPaneRect(int nPaneID, LPRECT lpRect) const
2028 {
2029 ATLASSERT(::IsWindow(this->m_hWnd));
2030 int nIndex = GetPaneIndexFromID(nPaneID);
2031 if(nIndex == -1)
2032 return FALSE;
2033
2034 return this->GetRect(nIndex, lpRect);
2035 }
2036
2037 BOOL SetPaneWidth(int nPaneID, int cxWidth)
2038 {
2039 ATLASSERT(::IsWindow(this->m_hWnd));
2040 ATLASSERT(nPaneID != ID_DEFAULT_PANE); // Can't resize this one
2041 int nIndex = GetPaneIndexFromID(nPaneID);
2042 if(nIndex == -1)
2043 return FALSE;
2044
2045 // get pane positions
2046 ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
2047 int* pPanesPos = buff.Allocate(m_nPanes);
2048 if(pPanesPos == NULL)
2049 return FALSE;
2050 this->GetParts(m_nPanes, pPanesPos);
2051 // calculate offset
2052 int cxPaneWidth = pPanesPos[nIndex] - ((nIndex == 0) ? 0 : pPanesPos[nIndex - 1]);
2053 int cxOff = cxWidth - cxPaneWidth;
2054 // find variable width pane
2055 int nDef = m_nPanes;
2056 for(int i = 0; i < m_nPanes; i++)
2057 {
2058 if(m_pPane[i] == ID_DEFAULT_PANE)
2059 {
2060 nDef = i;
2061 break;
2062 }
2063 }
2064 // resize
2065 if(nIndex < nDef) // before default pane
2066 {
2067 for(int i = nIndex; i < nDef; i++)
2068 pPanesPos[i] += cxOff;
2069
2070 }
2071 else // after default one
2072 {
2073 for(int i = nDef; i < nIndex; i++)
2074 pPanesPos[i] -= cxOff;
2075 }
2076 // set pane postions
2077 return this->SetParts(m_nPanes, pPanesPos);
2078 }
2079
2080 BOOL GetPaneTipText(int nPaneID, LPTSTR lpstrText, int nSize) const
2081 {
2082 ATLASSERT(::IsWindow(this->m_hWnd));
2083 int nIndex = GetPaneIndexFromID(nPaneID);
2084 if(nIndex == -1)
2085 return FALSE;
2086
2087 this->GetTipText(nIndex, lpstrText, nSize);
2088 return TRUE;
2089 }
2090
2091 BOOL SetPaneTipText(int nPaneID, LPCTSTR lpstrText)
2092 {
2093 ATLASSERT(::IsWindow(this->m_hWnd));
2094 int nIndex = GetPaneIndexFromID(nPaneID);
2095 if(nIndex == -1)
2096 return FALSE;
2097
2098 this->SetTipText(nIndex, lpstrText);
2099 return TRUE;
2100 }
2101
2102 BOOL GetPaneIcon(int nPaneID, HICON& hIcon) const
2103 {
2104 ATLASSERT(::IsWindow(this->m_hWnd));
2105 int nIndex = GetPaneIndexFromID(nPaneID);
2106 if(nIndex == -1)
2107 return FALSE;
2108
2109 hIcon = this->GetIcon(nIndex);
2110 return TRUE;
2111 }
2112
2113 BOOL SetPaneIcon(int nPaneID, HICON hIcon)
2114 {
2115 ATLASSERT(::IsWindow(this->m_hWnd));
2116 int nIndex = GetPaneIndexFromID(nPaneID);
2117 if(nIndex == -1)
2118 return FALSE;
2119
2120 return this->SetIcon(nIndex, hIcon);
2121 }
2122
2123 // Message map and handlers
2124 BEGIN_MSG_MAP(CMultiPaneStatusBarCtrlImpl< T >)
2125 MESSAGE_HANDLER(WM_SIZE, OnSize)
2126 END_MSG_MAP()
2127
2128 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
2129 {
2130 LRESULT lRet = this->DefWindowProc(uMsg, wParam, lParam);
2131 if((wParam != SIZE_MINIMIZED) && (m_nPanes > 0))
2132 {
2133 T* pT = static_cast<T*>(this);
2134 pT->UpdatePanesLayout();
2135 }
2136 return lRet;
2137 }
2138
2139 // Implementation
2140 BOOL UpdatePanesLayout()
2141 {
2142 // get pane positions
2143 ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
2144 int* pPanesPos = buff.Allocate(m_nPanes);
2145 ATLASSERT(pPanesPos != NULL);
2146 if(pPanesPos == NULL)
2147 return FALSE;
2148 int nRet = this->GetParts(m_nPanes, pPanesPos);
2149 ATLASSERT(nRet == m_nPanes);
2150 if(nRet != m_nPanes)
2151 return FALSE;
2152 // calculate offset
2153 RECT rcClient = {};
2154 this->GetClientRect(&rcClient);
2155 int cxOff = rcClient.right - pPanesPos[m_nPanes - 1];
2156 // Move panes left if size grip box is present
2157 if((this->GetStyle() & SBARS_SIZEGRIP) != 0)
2158 cxOff -= ::GetSystemMetrics(SM_CXVSCROLL) + ::GetSystemMetrics(SM_CXEDGE);
2159 // find variable width pane
2160 int i;
2161 for(i = 0; i < m_nPanes; i++)
2162 {
2163 if(m_pPane[i] == ID_DEFAULT_PANE)
2164 break;
2165 }
2166 // resize all panes from the variable one to the right
2167 if((i < m_nPanes) && (pPanesPos[i] + cxOff) > ((i == 0) ? 0 : pPanesPos[i - 1]))
2168 {
2169 for(; i < m_nPanes; i++)
2170 pPanesPos[i] += cxOff;
2171 }
2172 // set pane postions
2173 return this->SetParts(m_nPanes, pPanesPos);
2174 }
2175
2176 int GetPaneIndexFromID(int nPaneID) const
2177 {
2178 for(int i = 0; i < m_nPanes; i++)
2179 {
2180 if(m_pPane[i] == nPaneID)
2181 return i;
2182 }
2183
2184 return -1; // not found
2185 }
2186 };
2187
2188 class CMultiPaneStatusBarCtrl : public CMultiPaneStatusBarCtrlImpl<CMultiPaneStatusBarCtrl>
2189 {
2190 public:
2191 DECLARE_WND_SUPERCLASS(_T("WTL_MultiPaneStatusBar"), GetWndClassName())
2192 };
2193
2194
2195 ///////////////////////////////////////////////////////////////////////////////
2196 // CPaneContainer - provides header with title and close button for panes
2197
2198 // pane container extended styles
2199 #define PANECNT_NOCLOSEBUTTON 0x00000001
2200 #define PANECNT_VERTICAL 0x00000002
2201 #define PANECNT_FLATBORDER 0x00000004
2202 #define PANECNT_NOBORDER 0x00000008
2203 #define PANECNT_DIVIDER 0x00000010
2204 #define PANECNT_GRADIENT 0x00000020
2205
2206 template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
2207 class ATL_NO_VTABLE CPaneContainerImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CCustomDraw< T >
2208 {
2209 public:
2210 DECLARE_WND_CLASS_EX2(NULL, T, 0, -1)
2211
2212 // Constants
2213 enum
2214 {
2215 m_cxyBorder = 2,
2216 m_cxyTextOffset = 4,
2217 m_cxyBtnOffset = 1,
2218
2219 m_cchTitle = 80,
2220
2221 m_cxImageTB = 13,
2222 m_cyImageTB = 11,
2223 m_cxyBtnAddTB = 7,
2224
2225 m_cxToolBar = m_cxImageTB + m_cxyBtnAddTB + m_cxyBorder + m_cxyBtnOffset,
2226
2227 m_xBtnImageLeft = 6,
2228 m_yBtnImageTop = 5,
2229 m_xBtnImageRight = 12,
2230 m_yBtnImageBottom = 11,
2231
2232 m_nCloseBtnID = ID_PANE_CLOSE
2233 };
2234
2235 // Data members
2236 CToolBarCtrl m_tb;
2237 ATL::CWindow m_wndClient;
2238 int m_cxyHeader;
2239 TCHAR m_szTitle[m_cchTitle];
2240 DWORD m_dwExtendedStyle; // Pane container specific extended styles
2241 HFONT m_hFont;
2242 bool m_bInternalFont;
2243
2244
2245 // Constructor
2246 CPaneContainerImpl() : m_cxyHeader(0), m_dwExtendedStyle(0), m_hFont(NULL), m_bInternalFont(false)
2247 {
2248 m_szTitle[0] = 0;
2249 }
2250
2251 // Attributes
2252 DWORD GetPaneContainerExtendedStyle() const
2253 {
2254 return m_dwExtendedStyle;
2255 }
2256
2257 DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
2258 {
2259 DWORD dwPrevStyle = m_dwExtendedStyle;
2260 if(dwMask == 0)
2261 m_dwExtendedStyle = dwExtendedStyle;
2262 else
2263 m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
2264 if(this->m_hWnd != NULL)
2265 {
2266 T* pT = static_cast<T*>(this);
2267 bool bUpdate = false;
2268
2269 if(((dwPrevStyle & PANECNT_NOCLOSEBUTTON) != 0) && ((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) == 0)) // add close button
2270 {
2271 pT->CreateCloseButton();
2272 bUpdate = true;
2273 }
2274 else if(((dwPrevStyle & PANECNT_NOCLOSEBUTTON) == 0) && ((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) != 0)) // remove close button
2275 {
2276 pT->DestroyCloseButton();
2277 bUpdate = true;
2278 }
2279
2280 if((dwPrevStyle & PANECNT_VERTICAL) != (m_dwExtendedStyle & PANECNT_VERTICAL)) // change orientation
2281 {
2282 pT->CalcSize();
2283 bUpdate = true;
2284 }
2285
2286 if((dwPrevStyle & (PANECNT_FLATBORDER | PANECNT_NOBORDER)) !=
2287 (m_dwExtendedStyle & (PANECNT_FLATBORDER | PANECNT_NOBORDER))) // change border
2288 {
2289 bUpdate = true;
2290 }
2291
2292 if((dwPrevStyle & PANECNT_GRADIENT) != (m_dwExtendedStyle & PANECNT_GRADIENT)) // change background
2293 {
2294 bUpdate = true;
2295 }
2296
2297 if(bUpdate)
2298 pT->UpdateLayout();
2299 }
2300 return dwPrevStyle;
2301 }
2302
2303 HWND GetClient() const
2304 {
2305 return m_wndClient;
2306 }
2307
2308 HWND SetClient(HWND hWndClient)
2309 {
2310 HWND hWndOldClient = m_wndClient;
2311 m_wndClient = hWndClient;
2312 if(this->m_hWnd != NULL)
2313 {
2314 T* pT = static_cast<T*>(this);
2315 pT->UpdateLayout();
2316 }
2317 return hWndOldClient;
2318 }
2319
2320 BOOL GetTitle(LPTSTR lpstrTitle, int cchLength) const
2321 {
2322 ATLASSERT(lpstrTitle != NULL);
2323
2324 errno_t nRet = ATL::Checked::tcsncpy_s(lpstrTitle, cchLength, m_szTitle, _TRUNCATE);
2325
2326 return ((nRet == 0) || (nRet == STRUNCATE));
2327 }
2328
2329 BOOL SetTitle(LPCTSTR lpstrTitle)
2330 {
2331 ATLASSERT(lpstrTitle != NULL);
2332
2333 errno_t nRet = ATL::Checked::tcsncpy_s(m_szTitle, m_cchTitle, lpstrTitle, _TRUNCATE);
2334 bool bRet = ((nRet == 0) || (nRet == STRUNCATE));
2335 if(bRet && (this->m_hWnd != NULL))
2336 {
2337 T* pT = static_cast<T*>(this);
2338 pT->UpdateLayout();
2339 }
2340
2341 return bRet;
2342 }
2343
2344 int GetTitleLength() const
2345 {
2346 return lstrlen(m_szTitle);
2347 }
2348
2349 // Methods
2350 HWND Create(HWND hWndParent, LPCTSTR lpstrTitle = NULL, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
2351 DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
2352 {
2353 if(lpstrTitle != NULL)
2354 ATL::Checked::tcsncpy_s(m_szTitle, m_cchTitle, lpstrTitle, _TRUNCATE);
2355 return ATL::CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, this->rcDefault, NULL, dwStyle, dwExStyle, nID, lpCreateParam);
2356 }
2357
2358 HWND Create(HWND hWndParent, UINT uTitleID, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
2359 DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
2360 {
2361 if(uTitleID != 0U)
2362 ::LoadString(ModuleHelper::GetResourceInstance(), uTitleID, m_szTitle, m_cchTitle);
2363 return ATL::CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, this->rcDefault, NULL, dwStyle, dwExStyle, nID, lpCreateParam);
2364 }
2365
2366 BOOL SubclassWindow(HWND hWnd)
2367 {
2368 BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
2369 if(bRet != FALSE)
2370 {
2371 T* pT = static_cast<T*>(this);
2372 pT->Init();
2373
2374 RECT rect = {};
2375 this->GetClientRect(&rect);
2376 pT->UpdateLayout(rect.right, rect.bottom);
2377 }
2378
2379 return bRet;
2380 }
2381
2382 BOOL EnableCloseButton(BOOL bEnable)
2383 {
2384 ATLASSERT(::IsWindow(this->m_hWnd));
2385 T* pT = static_cast<T*>(this);
2386 (void)pT; // avoid level 4 warning
2387 return (m_tb.m_hWnd != NULL) ? m_tb.EnableButton(pT->m_nCloseBtnID, bEnable) : FALSE;
2388 }
2389
2390 void UpdateLayout()
2391 {
2392 RECT rcClient = {};
2393 this->GetClientRect(&rcClient);
2394 T* pT = static_cast<T*>(this);
2395 pT->UpdateLayout(rcClient.right, rcClient.bottom);
2396 }
2397
2398 // Message map and handlers
2399 BEGIN_MSG_MAP(CPaneContainerImpl)
2400 MESSAGE_HANDLER(WM_CREATE, OnCreate)
2401 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
2402 MESSAGE_HANDLER(WM_SIZE, OnSize)
2403 MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
2404 MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
2405 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
2406 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
2407 MESSAGE_HANDLER(WM_PAINT, OnPaint)
2408 MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
2409 MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
2410 MESSAGE_HANDLER(WM_COMMAND, OnCommand)
2411 FORWARD_NOTIFICATIONS()
2412 END_MSG_MAP()
2413
2414 LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
2415 {
2416 T* pT = static_cast<T*>(this);
2417 pT->Init();
2418
2419 return 0;
2420 }
2421
2422 LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
2423 {
2424 if(m_bInternalFont)
2425 {
2426 ::DeleteObject(m_hFont);
2427 m_hFont = NULL;
2428 m_bInternalFont = false;
2429 }
2430
2431 return 0;
2432 }
2433
2434 LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
2435 {
2436 T* pT = static_cast<T*>(this);
2437 pT->UpdateLayout(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
2438 return 0;
2439 }
2440
2441 LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
2442 {
2443 if(m_wndClient.m_hWnd != NULL)
2444 m_wndClient.SetFocus();
2445 return 0;
2446 }
2447
2448 LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
2449 {
2450 return (LRESULT)m_hFont;
2451 }
2452
2453 LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
2454 {
2455 if(m_bInternalFont)
2456 {
2457 ::DeleteObject(m_hFont);
2458 m_bInternalFont = false;
2459 }
2460
2461 m_hFont = (HFONT)wParam;
2462
2463 T* pT = static_cast<T*>(this);
2464 pT->CalcSize();
2465
2466 if((BOOL)lParam != FALSE)
2467 pT->UpdateLayout();
2468
2469 return 0;
2470 }
2471
2472 LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
2473 {
2474 T* pT = static_cast<T*>(this);
2475 pT->DrawPaneTitleBackground((HDC)wParam);
2476
2477 return 1;
2478 }
2479
2480 LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
2481 {
2482 T* pT = static_cast<T*>(this);
2483 if(wParam != NULL)
2484 {
2485 pT->DrawPaneTitle((HDC)wParam);
2486
2487 if(m_wndClient.m_hWnd == NULL) // no client window
2488 pT->DrawPane((HDC)wParam);
2489 }
2490 else
2491 {
2492 CPaintDC dc(this->m_hWnd);
2493 pT->DrawPaneTitle(dc.m_hDC);
2494
2495 if(m_wndClient.m_hWnd == NULL) // no client window
2496 pT->DrawPane(dc.m_hDC);
2497 }
2498
2499 return 0;
2500 }
2501
2502 LRESULT OnNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
2503 {
2504 if(m_tb.m_hWnd == NULL)
2505 {
2506 bHandled = FALSE;
2507 return 1;
2508 }
2509
2510 T* pT = static_cast<T*>(this);
2511 (void)pT; // avoid level 4 warning
2512 LPNMHDR lpnmh = (LPNMHDR)lParam;
2513 LRESULT lRet = 0;
2514
2515 // pass toolbar custom draw notifications to the base class
2516 if((lpnmh->code == NM_CUSTOMDRAW) && (lpnmh->hwndFrom == m_tb.m_hWnd))
2517 lRet = CCustomDraw< T >::OnCustomDraw(0, lpnmh, bHandled);
2518 // tooltip notifications come with the tooltip window handle and button ID,
2519 // pass them to the parent if we don't handle them
2520 else if((lpnmh->code == TTN_GETDISPINFO) && (lpnmh->idFrom == pT->m_nCloseBtnID))
2521 bHandled = pT->GetToolTipText(lpnmh);
2522 // only let notifications not from the toolbar go to the parent
2523 else if((lpnmh->hwndFrom != m_tb.m_hWnd) && (lpnmh->idFrom != pT->m_nCloseBtnID))
2524 bHandled = FALSE;
2525
2526 return lRet;
2527 }
2528
2529 LRESULT OnCommand(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
2530 {
2531 // if command comes from the close button, substitute HWND of the pane container instead
2532 if((m_tb.m_hWnd != NULL) && ((HWND)lParam == m_tb.m_hWnd))
2533 return this->GetParent().SendMessage(WM_COMMAND, wParam, (LPARAM)this->m_hWnd);
2534
2535 bHandled = FALSE;
2536 return 1;
2537 }
2538
2539 // Custom draw overrides
2540 DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
2541 {
2542 return CDRF_NOTIFYITEMDRAW; // we need per-item notifications
2543 }
2544
2545 DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
2546 {
2547 return CDRF_NOTIFYPOSTPAINT;
2548 }
2549
2550 DWORD OnItemPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
2551 {
2552 CDCHandle dc = lpNMCustomDraw->hdc;
2553 RECT& rc = lpNMCustomDraw->rc;
2554
2555 RECT rcImage = { m_xBtnImageLeft, m_yBtnImageTop, m_xBtnImageRight + 1, m_yBtnImageBottom + 1 };
2556 ::OffsetRect(&rcImage, rc.left, rc.top);
2557 T* pT = static_cast<T*>(this);
2558
2559 if((lpNMCustomDraw->uItemState & CDIS_DISABLED) != 0)
2560 {
2561 RECT rcShadow = rcImage;
2562 ::OffsetRect(&rcShadow, 1, 1);
2563 CPen pen1;
2564 pen1.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_3DHILIGHT));
2565 pT->DrawButtonImage(dc, rcShadow, pen1);
2566 CPen pen2;
2567 pen2.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_3DSHADOW));
2568 pT->DrawButtonImage(dc, rcImage, pen2);
2569 }
2570 else
2571 {
2572 if((lpNMCustomDraw->uItemState & CDIS_SELECTED) != 0)
2573 ::OffsetRect(&rcImage, 1, 1);
2574 CPen pen;
2575 pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNTEXT));
2576 pT->DrawButtonImage(dc, rcImage, pen);
2577 }
2578
2579 return CDRF_DODEFAULT; // continue with the default item painting
2580 }
2581
2582 // Implementation - overrideable methods
2583 void Init()
2584 {
2585 if(m_hFont == NULL)
2586 {
2587 // The same as AtlCreateControlFont() for horizontal pane
2588 LOGFONT lf = {};
2589 ATLVERIFY(::SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, 0) != FALSE);
2590 if(IsVertical())
2591 lf.lfEscapement = 900; // 90 degrees
2592 m_hFont = ::CreateFontIndirect(&lf);
2593 m_bInternalFont = true;
2594 }
2595
2596 T* pT = static_cast<T*>(this);
2597 pT->CalcSize();
2598
2599 if((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) == 0)
2600 pT->CreateCloseButton();
2601 }
2602
2603 void UpdateLayout(int cxWidth, int cyHeight)
2604 {
2605 ATLASSERT(::IsWindow(this->m_hWnd));
2606 RECT rect = {};
2607
2608 if(IsVertical())
2609 {
2610 ::SetRect(&rect, 0, 0, m_cxyHeader, cyHeight);
2611 if(m_tb.m_hWnd != NULL)
2612 m_tb.SetWindowPos(NULL, m_cxyBorder, m_cxyBorder + m_cxyBtnOffset, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
2613
2614 if(m_wndClient.m_hWnd != NULL)
2615 m_wndClient.SetWindowPos(NULL, m_cxyHeader, 0, cxWidth - m_cxyHeader, cyHeight, SWP_NOZORDER);
2616 else
2617 rect.right = cxWidth;
2618 }
2619 else
2620 {
2621 ::SetRect(&rect, 0, 0, cxWidth, m_cxyHeader);
2622 if(m_tb.m_hWnd != NULL)
2623 m_tb.SetWindowPos(NULL, rect.right - m_cxToolBar, m_cxyBorder + m_cxyBtnOffset, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
2624
2625 if(m_wndClient.m_hWnd != NULL)
2626 m_wndClient.SetWindowPos(NULL, 0, m_cxyHeader, cxWidth, cyHeight - m_cxyHeader, SWP_NOZORDER);
2627 else
2628 rect.bottom = cyHeight;
2629 }
2630
2631 this->InvalidateRect(&rect);
2632 }
2633
2634 void CreateCloseButton()
2635 {
2636 ATLASSERT(m_tb.m_hWnd == NULL);
2637 // create toolbar for the "x" button
2638 m_tb.Create(this->m_hWnd, this->rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CCS_NODIVIDER | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NOMOVEY | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT, 0);
2639 ATLASSERT(m_tb.IsWindow());
2640
2641 if(m_tb.m_hWnd != NULL)
2642 {
2643 T* pT = static_cast<T*>(this);
2644 (void)pT; // avoid level 4 warning
2645
2646 m_tb.SetButtonStructSize();
2647
2648 TBBUTTON tbbtn = {};
2649 tbbtn.idCommand = pT->m_nCloseBtnID;
2650 tbbtn.fsState = TBSTATE_ENABLED;
2651 tbbtn.fsStyle = BTNS_BUTTON;
2652 m_tb.AddButtons(1, &tbbtn);
2653
2654 m_tb.SetBitmapSize(m_cxImageTB, m_cyImageTB);
2655 m_tb.SetButtonSize(m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB);
2656
2657 if(IsVertical())
2658 m_tb.SetWindowPos(NULL, m_cxyBorder + m_cxyBtnOffset, m_cxyBorder + m_cxyBtnOffset, m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB + 1, SWP_NOZORDER | SWP_NOACTIVATE);
2659 else
2660 m_tb.SetWindowPos(NULL, 0, 0, m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB + 1, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
2661 }
2662 }
2663
2664 void DestroyCloseButton()
2665 {
2666 if(m_tb.m_hWnd != NULL)
2667 m_tb.DestroyWindow();
2668 }
2669
2670 void CalcSize()
2671 {
2672 T* pT = static_cast<T*>(this);
2673 CFontHandle font = pT->GetTitleFont();
2674 if(font.IsNull())
2675 font = (HFONT)::GetStockObject(SYSTEM_FONT);
2676 LOGFONT lf = {};
2677 font.GetLogFont(lf);
2678 if(IsVertical())
2679 {
2680 m_cxyHeader = m_cxImageTB + m_cxyBtnAddTB + m_cxyBorder + 1;
2681 }
2682 else
2683 {
2684 int cyFont = abs(lf.lfHeight) + m_cxyBorder + 2 * m_cxyTextOffset;
2685 int cyBtn = m_cyImageTB + m_cxyBtnAddTB + m_cxyBorder + 2 * m_cxyBtnOffset + 1;
2686 m_cxyHeader = __max(cyFont, cyBtn);
2687 }
2688 }
2689
2690 HFONT GetTitleFont() const
2691 {
2692 return m_hFont;
2693 }
2694
2695 BOOL GetToolTipText(LPNMHDR /*lpnmh*/)
2696 {
2697 return FALSE;
2698 }
2699
2700 void DrawPaneTitle(CDCHandle dc)
2701 {
2702 RECT rect = {};
2703 this->GetClientRect(&rect);
2704
2705 UINT uBorder = BF_LEFT | BF_TOP | BF_ADJUST;
2706 if(IsVertical())
2707 {
2708 rect.right = rect.left + m_cxyHeader;
2709 uBorder |= BF_BOTTOM;
2710 }
2711 else
2712 {
2713 rect.bottom = rect.top + m_cxyHeader;
2714 uBorder |= BF_RIGHT;
2715 }
2716
2717 if((m_dwExtendedStyle & PANECNT_NOBORDER) == 0)
2718 {
2719 if((m_dwExtendedStyle & PANECNT_FLATBORDER) != 0)
2720 uBorder |= BF_FLAT;
2721 dc.DrawEdge(&rect, EDGE_ETCHED, uBorder);
2722 }
2723
2724 if((m_dwExtendedStyle & PANECNT_DIVIDER) != 0)
2725 {
2726 uBorder = BF_FLAT | BF_ADJUST | (IsVertical() ? BF_RIGHT : BF_BOTTOM);
2727 dc.DrawEdge(&rect, BDR_SUNKENOUTER, uBorder);
2728 }
2729
2730 // draw title text
2731 dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
2732 dc.SetBkMode(TRANSPARENT);
2733 T* pT = static_cast<T*>(this);
2734 HFONT hFontOld = dc.SelectFont(pT->GetTitleFont());
2735
2736 if(IsVertical())
2737 {
2738 rect.top += m_cxyTextOffset;
2739 rect.bottom -= m_cxyTextOffset;
2740 if(m_tb.m_hWnd != NULL)
2741 rect.top += m_cxToolBar;;
2742
2743 RECT rcCalc = { rect.left, rect.bottom, rect.right, rect.top };
2744 int cxFont = dc.DrawText(m_szTitle, -1, &rcCalc, DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS | DT_CALCRECT);
2745 RECT rcText = {};
2746 rcText.left = (rect.right - rect.left - cxFont) / 2;
2747 rcText.right = rcText.left + (rect.bottom - rect.top);
2748 rcText.top = rect.bottom;
2749 rcText.bottom = rect.top;
2750 dc.DrawText(m_szTitle, -1, &rcText, DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS);
2751 }
2752 else
2753 {
2754 rect.left += m_cxyTextOffset;
2755 rect.right -= m_cxyTextOffset;
2756 if(m_tb.m_hWnd != NULL)
2757 rect.right -= m_cxToolBar;;
2758
2759 dc.DrawText(m_szTitle, -1, &rect, DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
2760 }
2761
2762 dc.SelectFont(hFontOld);
2763 }
2764
2765 void DrawPaneTitleBackground(CDCHandle dc)
2766 {
2767 RECT rect = {};
2768 this->GetClientRect(&rect);
2769 if(IsVertical())
2770 rect.right = m_cxyHeader;
2771 else
2772 rect.bottom = m_cxyHeader;
2773
2774 if((m_dwExtendedStyle & PANECNT_GRADIENT) != 0)
2775 dc.GradientFillRect(rect, ::GetSysColor(COLOR_WINDOW), ::GetSysColor(COLOR_3DFACE), IsVertical());
2776 else
2777 dc.FillRect(&rect, COLOR_3DFACE);
2778 }
2779
2780 // called only if pane is empty
2781 void DrawPane(CDCHandle dc)
2782 {
2783 RECT rect = {};
2784 this->GetClientRect(&rect);
2785 if(IsVertical())
2786 rect.left += m_cxyHeader;
2787 else
2788 rect.top += m_cxyHeader;
2789 if((this->GetExStyle() & WS_EX_CLIENTEDGE) == 0)
2790 dc.DrawEdge(&rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
2791 dc.FillRect(&rect, COLOR_APPWORKSPACE);
2792 }
2793
2794 // drawing helper - draws "x" button image
2795 void DrawButtonImage(CDCHandle dc, RECT& rcImage, HPEN hPen)
2796 {
2797 HPEN hPenOld = dc.SelectPen(hPen);
2798
2799 dc.MoveTo(rcImage.left, rcImage.top);
2800 dc.LineTo(rcImage.right, rcImage.bottom);
2801 dc.MoveTo(rcImage.left + 1, rcImage.top);
2802 dc.LineTo(rcImage.right + 1, rcImage.bottom);
2803
2804 dc.MoveTo(rcImage.left, rcImage.bottom - 1);
2805 dc.LineTo(rcImage.right, rcImage.top - 1);
2806 dc.MoveTo(rcImage.left + 1, rcImage.bottom - 1);
2807 dc.LineTo(rcImage.right + 1, rcImage.top - 1);
2808
2809 dc.SelectPen(hPenOld);
2810 }
2811
2812 bool IsVertical() const
2813 {
2814 return ((m_dwExtendedStyle & PANECNT_VERTICAL) != 0);
2815 }
2816 };
2817
2818 class CPaneContainer : public CPaneContainerImpl<CPaneContainer>
2819 {
2820 public:
2821 DECLARE_WND_CLASS_EX(_T("WTL_PaneContainer"), 0, -1)
2822 };
2823
2824
2825 ///////////////////////////////////////////////////////////////////////////////
2826 // CSortListViewCtrl - implements sorting for a listview control
2827
2828 // sort listview extended styles
2829 #define SORTLV_USESHELLBITMAPS 0x00000001
2830
2831 // Notification sent to parent when sort column is changed by user clicking header.
2832 #define SLVN_SORTCHANGED LVN_LAST
2833
2834 // A LPNMSORTLISTVIEW is sent with the SLVN_SORTCHANGED notification
2835 typedef struct tagNMSORTLISTVIEW
2836 {
2837 NMHDR hdr;
2838 int iNewSortColumn;
2839 int iOldSortColumn;
2840 } NMSORTLISTVIEW, *LPNMSORTLISTVIEW;
2841
2842 // Column sort types. Can be set on a per-column basis with the SetColumnSortType method.
2843 enum
2844 {
2845 LVCOLSORT_NONE,
2846 LVCOLSORT_TEXT, // default
2847 LVCOLSORT_TEXTNOCASE,
2848 LVCOLSORT_LONG,
2849 LVCOLSORT_DOUBLE,
2850 LVCOLSORT_DECIMAL,
2851 LVCOLSORT_DATETIME,
2852 LVCOLSORT_DATE,
2853 LVCOLSORT_TIME,
2854 LVCOLSORT_CUSTOM,
2855 LVCOLSORT_LAST = LVCOLSORT_CUSTOM
2856 };
2857
2858
2859 template <class T>
2860 class CSortListViewImpl
2861 {
2862 public:
2863 enum
2864 {
2865 m_cchCmpTextMax = 32, // overrideable
2866 m_cxSortImage = 16,
2867 m_cySortImage = 15,
2868 m_cxSortArrow = 11,
2869 m_cySortArrow = 6,
2870 m_iSortUp = 0, // index of sort bitmaps
2871 m_iSortDown = 1,
2872 m_nShellSortUpID = 133
2873 };
2874
2875 // passed to LVCompare functions as lParam1 and lParam2
2876 struct LVCompareParam
2877 {
2878 int iItem;
2879 DWORD_PTR dwItemData;
2880 union
2881 {
2882 long lValue;
2883 double dblValue;
2884 DECIMAL decValue;
2885 LPCTSTR pszValue;
2886 };
2887 };
2888
2889 // passed to LVCompare functions as the lParamSort parameter
2890 struct LVSortInfo
2891 {
2892 T* pT;
2893 int iSortCol;
2894 bool bDescending;
2895 };
2896
2897 bool m_bSortDescending;
2898 bool m_bCommCtrl6;
2899 int m_iSortColumn;
2900 CBitmap m_bmSort[2];
2901 int m_fmtOldSortCol;
2902 HBITMAP m_hbmOldSortCol;
2903 DWORD m_dwSortLVExtendedStyle;
2904 ATL::CSimpleArray<WORD> m_arrColSortType;
2905 bool m_bUseWaitCursor;
2906
2907 CSortListViewImpl() :
2908 m_bSortDescending(false),
2909 m_bCommCtrl6(false),
2910 m_iSortColumn(-1),
2911 m_fmtOldSortCol(0),
2912 m_hbmOldSortCol(NULL),
2913 m_dwSortLVExtendedStyle(SORTLV_USESHELLBITMAPS),
2914 m_bUseWaitCursor(true)
2915 {
2916 DWORD dwMajor = 0;
2917 DWORD dwMinor = 0;
2918 HRESULT hRet = ATL::AtlGetCommCtrlVersion(&dwMajor, &dwMinor);
2919 m_bCommCtrl6 = SUCCEEDED(hRet) && (dwMajor >= 6);
2920 }
2921
2922 // Attributes
2923 void SetSortColumn(int iCol)
2924 {
2925 T* pT = static_cast<T*>(this);
2926 ATLASSERT(::IsWindow(pT->m_hWnd));
2927 CHeaderCtrl header = pT->GetHeader();
2928 ATLASSERT(header.m_hWnd != NULL);
2929 ATLASSERT((iCol >= -1) && (iCol < m_arrColSortType.GetSize()));
2930
2931 int iOldSortCol = m_iSortColumn;
2932 m_iSortColumn = iCol;
2933 if(m_bCommCtrl6)
2934 {
2935 const int nMask = HDF_SORTUP | HDF_SORTDOWN;
2936 HDITEM hditem = { HDI_FORMAT };
2937 if((iOldSortCol != iCol) && (iOldSortCol >= 0) && header.GetItem(iOldSortCol, &hditem))
2938 {
2939 hditem.fmt &= ~nMask;
2940 header.SetItem(iOldSortCol, &hditem);
2941 }
2942 if((iCol >= 0) && header.GetItem(iCol, &hditem))
2943 {
2944 hditem.fmt &= ~nMask;
2945 hditem.fmt |= m_bSortDescending ? HDF_SORTDOWN : HDF_SORTUP;
2946 header.SetItem(iCol, &hditem);
2947 }
2948 return;
2949 }
2950
2951 if(m_bmSort[m_iSortUp].IsNull())
2952 pT->CreateSortBitmaps();
2953
2954 // restore previous sort column's bitmap, if any, and format
2955 HDITEM hditem = { HDI_BITMAP | HDI_FORMAT };
2956 if((iOldSortCol != iCol) && (iOldSortCol >= 0))
2957 {
2958 hditem.hbm = m_hbmOldSortCol;
2959 hditem.fmt = m_fmtOldSortCol;
2960 header.SetItem(iOldSortCol, &hditem);
2961 }
2962
2963 // save new sort column's bitmap and format, and add our sort bitmap
2964 if((iCol >= 0) && header.GetItem(iCol, &hditem))
2965 {
2966 if(iOldSortCol != iCol)
2967 {
2968 m_fmtOldSortCol = hditem.fmt;
2969 m_hbmOldSortCol = hditem.hbm;
2970 }
2971 hditem.fmt &= ~HDF_IMAGE;
2972 hditem.fmt |= HDF_BITMAP | HDF_BITMAP_ON_RIGHT;
2973 int i = m_bSortDescending ? m_iSortDown : m_iSortUp;
2974 hditem.hbm = m_bmSort[i];
2975 header.SetItem(iCol, &hditem);
2976 }
2977 }
2978
2979 int GetSortColumn() const
2980 {
2981 return m_iSortColumn;
2982 }
2983
2984 void SetColumnSortType(int iCol, WORD wType)
2985 {
2986 ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
2987 ATLASSERT((wType >= LVCOLSORT_NONE) && (wType <= LVCOLSORT_LAST));
2988 m_arrColSortType[iCol] = wType;
2989 }
2990
2991 WORD GetColumnSortType(int iCol) const
2992 {
2993 ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
2994 return m_arrColSortType[iCol];
2995 }
2996
2997 int GetColumnCount() const
2998 {
2999 const T* pT = static_cast<const T*>(this);
3000 ATLASSERT(::IsWindow(pT->m_hWnd));
3001 CHeaderCtrl header = pT->GetHeader();
3002 return header.m_hWnd != NULL ? header.GetItemCount() : 0;
3003 }
3004
3005 bool IsSortDescending() const
3006 {
3007 return m_bSortDescending;
3008 }
3009
3010 DWORD GetSortListViewExtendedStyle() const
3011 {
3012 return m_dwSortLVExtendedStyle;
3013 }
3014
3015 DWORD SetSortListViewExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
3016 {
3017 DWORD dwPrevStyle = m_dwSortLVExtendedStyle;
3018 if(dwMask == 0)
3019 m_dwSortLVExtendedStyle = dwExtendedStyle;
3020 else
3021 m_dwSortLVExtendedStyle = (m_dwSortLVExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
3022 return dwPrevStyle;
3023 }
3024
3025 // Operations
3026 bool DoSortItems(int iCol, bool bDescending = false)
3027 {
3028 T* pT = static_cast<T*>(this);
3029 ATLASSERT(::IsWindow(pT->m_hWnd));
3030 ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
3031
3032 WORD wType = m_arrColSortType[iCol];
3033 if(wType == LVCOLSORT_NONE)
3034 return false;
3035
3036 int nCount = pT->GetItemCount();
3037 if(nCount < 2)
3038 {
3039 m_bSortDescending = bDescending;
3040 SetSortColumn(iCol);
3041 return true;
3042 }
3043
3044 CWaitCursor waitCursor(false);
3045 if(m_bUseWaitCursor)
3046 waitCursor.Set();
3047
3048 LVCompareParam* pParam = NULL;
3049 ATLTRY(pParam = new LVCompareParam[nCount]);
3050 PFNLVCOMPARE pFunc = NULL;
3051 TCHAR pszTemp[pT->m_cchCmpTextMax] = {};
3052 bool bStrValue = false;
3053
3054 switch(wType)
3055 {
3056 case LVCOLSORT_TEXT:
3057 pFunc = (PFNLVCOMPARE)pT->LVCompareText;
3058 case LVCOLSORT_TEXTNOCASE:
3059 if(pFunc == NULL)
3060 pFunc = (PFNLVCOMPARE)pT->LVCompareTextNoCase;
3061 case LVCOLSORT_CUSTOM:
3062 {
3063 if(pFunc == NULL)
3064 pFunc = (PFNLVCOMPARE)pT->LVCompareCustom;
3065
3066 for(int i = 0; i < nCount; i++)
3067 {
3068 pParam[i].iItem = i;
3069 pParam[i].dwItemData = pT->GetItemData(i);
3070 pParam[i].pszValue = new TCHAR[pT->m_cchCmpTextMax];
3071 pT->GetItemText(i, iCol, (LPTSTR)pParam[i].pszValue, pT->m_cchCmpTextMax);
3072 pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
3073 }
3074 bStrValue = true;
3075 }
3076 break;
3077 case LVCOLSORT_LONG:
3078 {
3079 pFunc = (PFNLVCOMPARE)pT->LVCompareLong;
3080 for(int i = 0; i < nCount; i++)
3081 {
3082 pParam[i].iItem = i;
3083 pParam[i].dwItemData = pT->GetItemData(i);
3084 pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
3085 pParam[i].lValue = pT->StrToLong(pszTemp);
3086 pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
3087 }
3088 }
3089 break;
3090 case LVCOLSORT_DOUBLE:
3091 {
3092 pFunc = (PFNLVCOMPARE)pT->LVCompareDouble;
3093 for(int i = 0; i < nCount; i++)
3094 {
3095 pParam[i].iItem = i;
3096 pParam[i].dwItemData = pT->GetItemData(i);
3097 pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
3098 pParam[i].dblValue = pT->StrToDouble(pszTemp);
3099 pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
3100 }
3101 }
3102 break;
3103 case LVCOLSORT_DECIMAL:
3104 {
3105 pFunc = (PFNLVCOMPARE)pT->LVCompareDecimal;
3106 for(int i = 0; i < nCount; i++)
3107 {
3108 pParam[i].iItem = i;
3109 pParam[i].dwItemData = pT->GetItemData(i);
3110 pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
3111 pT->StrToDecimal(pszTemp, &pParam[i].decValue);
3112 pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
3113 }
3114 }
3115 break;
3116 case LVCOLSORT_DATETIME:
3117 case LVCOLSORT_DATE:
3118 case LVCOLSORT_TIME:
3119 {
3120 pFunc = (PFNLVCOMPARE)pT->LVCompareDouble;
3121 DWORD dwFlags = LOCALE_NOUSEROVERRIDE;
3122 if(wType == LVCOLSORT_DATE)
3123 dwFlags |= VAR_DATEVALUEONLY;
3124 else if(wType == LVCOLSORT_TIME)
3125 dwFlags |= VAR_TIMEVALUEONLY;
3126 for(int i = 0; i < nCount; i++)
3127 {
3128 pParam[i].iItem = i;
3129 pParam[i].dwItemData = pT->GetItemData(i);
3130 pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
3131 pParam[i].dblValue = pT->DateStrToDouble(pszTemp, dwFlags);
3132 pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
3133 }
3134 }
3135 break;
3136 default:
3137 ATLTRACE2(atlTraceUI, 0, _T("Unknown value for sort type in CSortListViewImpl::DoSortItems()\n"));
3138 break;
3139 } // switch(wType)
3140
3141 ATLASSERT(pFunc != NULL);
3142 LVSortInfo lvsi = { pT, iCol, bDescending };
3143 bool bRet = ((BOOL)pT->DefWindowProc(LVM_SORTITEMS, (WPARAM)&lvsi, (LPARAM)pFunc) != FALSE);
3144 for(int i = 0; i < nCount; i++)
3145 {
3146 DWORD_PTR dwItemData = pT->GetItemData(i);
3147 LVCompareParam* p = (LVCompareParam*)dwItemData;
3148 ATLASSERT(p != NULL);
3149 if(bStrValue)
3150 delete [] (TCHAR*)p->pszValue;
3151 pT->SetItemData(i, p->dwItemData);
3152 }
3153 delete [] pParam;
3154
3155 if(bRet)
3156 {
3157 m_bSortDescending = bDescending;
3158 SetSortColumn(iCol);
3159 }
3160
3161 if(m_bUseWaitCursor)
3162 waitCursor.Restore();
3163
3164 return bRet;
3165 }
3166
3167 void CreateSortBitmaps()
3168 {
3169 if((m_dwSortLVExtendedStyle & SORTLV_USESHELLBITMAPS) != 0)
3170 {
3171 bool bFree = false;
3172 LPCTSTR pszModule = _T("shell32.dll");
3173 HINSTANCE hShell = ::GetModuleHandle(pszModule);
3174
3175 if (hShell == NULL)
3176 {
3177 hShell = ::LoadLibrary(pszModule);
3178 bFree = true;
3179 }
3180
3181 if (hShell != NULL)
3182 {
3183 bool bSuccess = true;
3184 for(int i = m_iSortUp; i <= m_iSortDown; i++)
3185 {
3186 if(!m_bmSort[i].IsNull())
3187 m_bmSort[i].DeleteObject();
3188 m_bmSort[i] = (HBITMAP)::LoadImage(hShell, MAKEINTRESOURCE(m_nShellSortUpID + i),
3189 IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS);
3190 if(m_bmSort[i].IsNull())
3191 {
3192 bSuccess = false;
3193 break;
3194 }
3195 }
3196 if(bFree)
3197 ::FreeLibrary(hShell);
3198 if(bSuccess)
3199 return;
3200 }
3201 }
3202
3203 T* pT = static_cast<T*>(this);
3204 for(int i = m_iSortUp; i <= m_iSortDown; i++)
3205 {
3206 if(!m_bmSort[i].IsNull())
3207 m_bmSort[i].DeleteObject();
3208
3209 CDC dcMem;
3210 CClientDC dc(::GetDesktopWindow());
3211 dcMem.CreateCompatibleDC(dc.m_hDC);
3212 m_bmSort[i].CreateCompatibleBitmap(dc.m_hDC, m_cxSortImage, m_cySortImage);
3213 HBITMAP hbmOld = dcMem.SelectBitmap(m_bmSort[i]);
3214 RECT rc = { 0, 0, m_cxSortImage, m_cySortImage };
3215 pT->DrawSortBitmap(dcMem.m_hDC, i, &rc);
3216 dcMem.SelectBitmap(hbmOld);
3217 dcMem.DeleteDC();
3218 }
3219 }
3220
3221 void NotifyParentSortChanged(int iNewSortCol, int iOldSortCol)
3222 {
3223 T* pT = static_cast<T*>(this);
3224 int nID = pT->GetDlgCtrlID();
3225 NMSORTLISTVIEW nm = { { pT->m_hWnd, (UINT_PTR)nID, SLVN_SORTCHANGED }, iNewSortCol, iOldSortCol };
3226 ::SendMessage(pT->GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nm);
3227 }
3228
3229 // Overrideables
3230 int CompareItemsCustom(LVCompareParam* /*pItem1*/, LVCompareParam* /*pItem2*/, int /*iSortCol*/)
3231 {
3232 // pItem1 and pItem2 contain valid iItem, dwItemData, and pszValue members.
3233 // If item1 > item2 return 1, if item1 < item2 return -1, else return 0.
3234 return 0;
3235 }
3236
3237 void DrawSortBitmap(CDCHandle dc, int iBitmap, LPRECT prc)
3238 {
3239 dc.FillRect(prc, ::GetSysColorBrush(COLOR_BTNFACE));
3240 HBRUSH hbrOld = dc.SelectBrush(::GetSysColorBrush(COLOR_BTNSHADOW));
3241 CPen pen;
3242 pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNSHADOW));
3243 HPEN hpenOld = dc.SelectPen(pen);
3244 POINT ptOrg = { (m_cxSortImage - m_cxSortArrow) / 2, (m_cySortImage - m_cySortArrow) / 2 };
3245 if(iBitmap == m_iSortUp)
3246 {
3247 POINT pts[3] =
3248 {
3249 { ptOrg.x + m_cxSortArrow / 2, ptOrg.y },
3250 { ptOrg.x, ptOrg.y + m_cySortArrow - 1 },
3251 { ptOrg.x + m_cxSortArrow - 1, ptOrg.y + m_cySortArrow - 1 }
3252 };
3253 dc.Polygon(pts, 3);
3254 }
3255 else
3256 {
3257 POINT pts[3] =
3258 {
3259 { ptOrg.x, ptOrg.y },
3260 { ptOrg.x + m_cxSortArrow / 2, ptOrg.y + m_cySortArrow - 1 },
3261 { ptOrg.x + m_cxSortArrow - 1, ptOrg.y }
3262 };
3263 dc.Polygon(pts, 3);
3264 }
3265 dc.SelectBrush(hbrOld);
3266 dc.SelectPen(hpenOld);
3267 }
3268
3269 double DateStrToDouble(LPCTSTR lpstr, DWORD dwFlags)
3270 {
3271 ATLASSERT(lpstr != NULL);
3272 if((lpstr == NULL) || (lpstr[0] == _T('\0')))
3273 return 0;
3274
3275 USES_CONVERSION;
3276 HRESULT hRet = E_FAIL;
3277 DATE dRet = 0;
3278 if (FAILED(hRet = ::VarDateFromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, dwFlags, &dRet)))
3279 {
3280 ATLTRACE2(atlTraceUI, 0, _T("VarDateFromStr failed with result of 0x%8.8X\n"), hRet);
3281 dRet = 0;
3282 }
3283 return dRet;
3284 }
3285
3286 long StrToLong(LPCTSTR lpstr)
3287 {
3288 ATLASSERT(lpstr != NULL);
3289 if((lpstr == NULL) || (lpstr[0] == _T('\0')))
3290 return 0;
3291
3292 USES_CONVERSION;
3293 HRESULT hRet = E_FAIL;
3294 long lRet = 0;
3295 if (FAILED(hRet = ::VarI4FromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, &lRet)))
3296 {
3297 ATLTRACE2(atlTraceUI, 0, _T("VarI4FromStr failed with result of 0x%8.8X\n"), hRet);
3298 lRet = 0;
3299 }
3300 return lRet;
3301 }
3302
3303 double StrToDouble(LPCTSTR lpstr)
3304 {
3305 ATLASSERT(lpstr != NULL);
3306 if((lpstr == NULL) || (lpstr[0] == _T('\0')))
3307 return 0;
3308
3309 USES_CONVERSION;
3310 HRESULT hRet = E_FAIL;
3311 double dblRet = 0;
3312 if (FAILED(hRet = ::VarR8FromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, &dblRet)))
3313 {
3314 ATLTRACE2(atlTraceUI, 0, _T("VarR8FromStr failed with result of 0x%8.8X\n"), hRet);
3315 dblRet = 0;
3316 }
3317 return dblRet;
3318 }
3319
3320 bool StrToDecimal(LPCTSTR lpstr, DECIMAL* pDecimal)
3321 {
3322 ATLASSERT(lpstr != NULL);
3323 ATLASSERT(pDecimal != NULL);
3324 if((lpstr == NULL) || (pDecimal == NULL))
3325 return false;
3326
3327 USES_CONVERSION;
3328 HRESULT hRet = E_FAIL;
3329 if (FAILED(hRet = ::VarDecFromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, pDecimal)))
3330 {
3331 ATLTRACE2(atlTraceUI, 0, _T("VarDecFromStr failed with result of 0x%8.8X\n"), hRet);
3332 pDecimal->Lo64 = 0;
3333 pDecimal->Hi32 = 0;
3334 pDecimal->signscale = 0;
3335 return false;
3336 }
3337 return true;
3338 }
3339
3340 // Overrideable PFNLVCOMPARE functions
3341 static int CALLBACK LVCompareText(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
3342 {
3343 ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
3344
3345 LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
3346 LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
3347 LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
3348
3349 int nRet = lstrcmp(pParam1->pszValue, pParam2->pszValue);
3350 return pInfo->bDescending ? -nRet : nRet;
3351 }
3352
3353 static int CALLBACK LVCompareTextNoCase(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
3354 {
3355 ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
3356
3357 LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
3358 LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
3359 LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
3360
3361 int nRet = lstrcmpi(pParam1->pszValue, pParam2->pszValue);
3362 return pInfo->bDescending ? -nRet : nRet;
3363 }
3364
3365 static int CALLBACK LVCompareLong(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
3366 {
3367 ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
3368
3369 LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
3370 LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
3371 LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
3372
3373 int nRet = 0;
3374 if(pParam1->lValue > pParam2->lValue)
3375 nRet = 1;
3376 else if(pParam1->lValue < pParam2->lValue)
3377 nRet = -1;
3378 return pInfo->bDescending ? -nRet : nRet;
3379 }
3380
3381 static int CALLBACK LVCompareDouble(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
3382 {
3383 ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
3384
3385 LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
3386 LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
3387 LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
3388
3389 int nRet = 0;
3390 if(pParam1->dblValue > pParam2->dblValue)
3391 nRet = 1;
3392 else if(pParam1->dblValue < pParam2->dblValue)
3393 nRet = -1;
3394 return pInfo->bDescending ? -nRet : nRet;
3395 }
3396
3397 static int CALLBACK LVCompareCustom(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
3398 {
3399 ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
3400
3401 LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
3402 LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
3403 LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
3404
3405 int nRet = pInfo->pT->CompareItemsCustom(pParam1, pParam2, pInfo->iSortCol);
3406 return pInfo->bDescending ? -nRet : nRet;
3407 }
3408
3409 static int CALLBACK LVCompareDecimal(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
3410 {
3411 ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
3412
3413 LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
3414 LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
3415 LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
3416
3417 int nRet = (int)::VarDecCmp(&pParam1->decValue, &pParam2->decValue);
3418 nRet--;
3419 return pInfo->bDescending ? -nRet : nRet;
3420 }
3421
3422 BEGIN_MSG_MAP(CSortListViewImpl)
3423 MESSAGE_HANDLER(LVM_INSERTCOLUMN, OnInsertColumn)
3424 MESSAGE_HANDLER(LVM_DELETECOLUMN, OnDeleteColumn)
3425 NOTIFY_CODE_HANDLER(HDN_ITEMCLICKA, OnHeaderItemClick)
3426 NOTIFY_CODE_HANDLER(HDN_ITEMCLICKW, OnHeaderItemClick)
3427 MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
3428 END_MSG_MAP()
3429
3430 LRESULT OnInsertColumn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
3431 {
3432 T* pT = static_cast<T*>(this);
3433 LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam);
3434 if(lRet == -1)
3435 return -1;
3436
3437 WORD wType = 0;
3438 m_arrColSortType.Add(wType);
3439 int nCount = m_arrColSortType.GetSize();
3440 ATLASSERT(nCount == GetColumnCount());
3441
3442 for(int i = nCount - 1; i > lRet; i--)
3443 m_arrColSortType[i] = m_arrColSortType[i - 1];
3444 m_arrColSortType[(int)lRet] = LVCOLSORT_TEXT;
3445
3446 if(lRet <= m_iSortColumn)
3447 m_iSortColumn++;
3448
3449 return lRet;
3450 }
3451
3452 LRESULT OnDeleteColumn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
3453 {
3454 T* pT = static_cast<T*>(this);
3455 LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam);
3456 if(lRet == 0)
3457 return 0;
3458
3459 int iCol = (int)wParam;
3460 if(m_iSortColumn == iCol)
3461 m_iSortColumn = -1;
3462 else if(m_iSortColumn > iCol)
3463 m_iSortColumn--;
3464 m_arrColSortType.RemoveAt(iCol);
3465
3466 return lRet;
3467 }
3468
3469 LRESULT OnHeaderItemClick(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled)
3470 {
3471 LPNMHEADER p = (LPNMHEADER)pnmh;
3472 if(p->iButton == 0)
3473 {
3474 int iOld = m_iSortColumn;
3475 bool bDescending = (m_iSortColumn == p->iItem) ? !m_bSortDescending : false;
3476 if(DoSortItems(p->iItem, bDescending))
3477 NotifyParentSortChanged(p->iItem, iOld);
3478 }
3479 bHandled = FALSE;
3480 return 0;
3481 }
3482
3483 LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
3484 {
3485 if(wParam == SPI_SETNONCLIENTMETRICS)
3486 GetSystemSettings();
3487 bHandled = FALSE;
3488 return 0;
3489 }
3490
3491 void GetSystemSettings()
3492 {
3493 if(!m_bCommCtrl6 && !m_bmSort[m_iSortUp].IsNull())
3494 {
3495 T* pT = static_cast<T*>(this);
3496 pT->CreateSortBitmaps();
3497 if(m_iSortColumn != -1)
3498 SetSortColumn(m_iSortColumn);
3499 }
3500 }
3501
3502 };
3503
3504
3505 typedef ATL::CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_REPORT | LVS_SHOWSELALWAYS , WS_EX_CLIENTEDGE> CSortListViewCtrlTraits;
3506
3507 template <class T, class TBase = CListViewCtrl, class TWinTraits = CSortListViewCtrlTraits>
3508 class ATL_NO_VTABLE CSortListViewCtrlImpl: public ATL::CWindowImpl<T, TBase, TWinTraits>, public CSortListViewImpl<T>
3509 {
3510 public:
3511 DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
3512
3513 bool SortItems(int iCol, bool bDescending = false)
3514 {
3515 return this->DoSortItems(iCol, bDescending);
3516 }
3517
3518 BEGIN_MSG_MAP(CSortListViewCtrlImpl)
3519 MESSAGE_HANDLER(LVM_INSERTCOLUMN, CSortListViewImpl<T>::OnInsertColumn)
3520 MESSAGE_HANDLER(LVM_DELETECOLUMN, CSortListViewImpl<T>::OnDeleteColumn)
3521 NOTIFY_CODE_HANDLER(HDN_ITEMCLICKA, CSortListViewImpl<T>::OnHeaderItemClick)
3522 NOTIFY_CODE_HANDLER(HDN_ITEMCLICKW, CSortListViewImpl<T>::OnHeaderItemClick)
3523 MESSAGE_HANDLER(WM_SETTINGCHANGE, CSortListViewImpl<T>::OnSettingChange)
3524 END_MSG_MAP()
3525 };
3526
3527 class CSortListViewCtrl : public CSortListViewCtrlImpl<CSortListViewCtrl>
3528 {
3529 public:
3530 DECLARE_WND_SUPERCLASS(_T("WTL_SortListViewCtrl"), GetWndClassName())
3531 };
3532
3533
3534 ///////////////////////////////////////////////////////////////////////////////
3535 // CTabView - implements tab view window
3536
3537 // TabView Notifications
3538 #define TBVN_PAGEACTIVATED (0U-741)
3539 #define TBVN_CONTEXTMENU (0U-742)
3540 #define TBVN_TABCLOSEBTN (0U-743) // return 0 to close page, 1 to keep open
3541 // internal
3542 #define TBVN_CLOSEBTNMOUSELEAVE (0U-744)
3543
3544 // Notification data for TBVN_CONTEXTMENU
3545 struct TBVCONTEXTMENUINFO
3546 {
3547 NMHDR hdr;
3548 POINT pt;
3549 };
3550
3551 typedef TBVCONTEXTMENUINFO* LPTBVCONTEXTMENUINFO;
3552
3553
3554 // Helper class for tab item hover close button
3555 class CTabViewCloseBtn : public ATL::CWindowImpl<CTabViewCloseBtn>
3556 {
3557 public:
3558 DECLARE_WND_CLASS_EX(_T("WTL_TabView_CloseBtn"), 0, -1)
3559
3560 enum { _xyBtnImageLeftTop = 3, _xyBtnImageRightBottom = 9 };
3561
3562 bool m_bHover;
3563 bool m_bPressed;
3564 CToolTipCtrl m_tip;
3565
3566 CTabViewCloseBtn() : m_bHover(false), m_bPressed(false)
3567 { }
3568
3569 // Message map and handlers
3570 BEGIN_MSG_MAP(CTabViewCloseBtn)
3571 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
3572 MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
3573 MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
3574 MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
3575 MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
3576 MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
3577 MESSAGE_HANDLER(WM_PAINT, OnPaint)
3578 MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
3579 FORWARD_NOTIFICATIONS()
3580 END_MSG_MAP()
3581
3582 LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
3583 {
3584 MSG msg = { m_hWnd, uMsg, wParam, lParam };
3585 if(m_tip.IsWindow() != FALSE)
3586 m_tip.RelayEvent(&msg);
3587
3588 bHandled = FALSE;
3589 return 1;
3590 }
3591
3592 LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
3593 {
3594 SetCapture();
3595 m_bHover = false;
3596 m_bPressed = true;
3597 Invalidate(FALSE);
3598 UpdateWindow();
3599
3600 return 0;
3601 }
3602
3603 LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
3604 {
3605 if(::GetCapture() == m_hWnd)
3606 {
3607 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
3608 ClientToScreen(&pt);
3609 RECT rect = {};
3610 GetWindowRect(&rect);
3611 bool bPressed = (::PtInRect(&rect, pt) != FALSE);
3612 if(m_bPressed != bPressed)
3613 {
3614 m_bPressed = bPressed;
3615 Invalidate(FALSE);
3616 UpdateWindow();
3617 }
3618 }
3619 else
3620 {
3621 if(!m_bHover)
3622 {
3623 m_bHover = true;
3624 Invalidate(FALSE);
3625 UpdateWindow();
3626 }
3627
3628 TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, m_hWnd };
3629 ::TrackMouseEvent(&tme);
3630 }
3631
3632 return 0;
3633 }
3634
3635 LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
3636 {
3637 if(m_bHover)
3638 {
3639 m_bHover = false;
3640 Invalidate(FALSE);
3641 UpdateWindow();
3642 }
3643
3644 NMHDR nmhdr = { m_hWnd, (UINT_PTR)GetDlgCtrlID(), TBVN_CLOSEBTNMOUSELEAVE };
3645 GetParent().SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM)&nmhdr);
3646
3647 return 0;
3648 }
3649
3650 LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
3651 {
3652 if(::GetCapture() == m_hWnd)
3653 {
3654 bool bAction = m_bPressed;
3655 ReleaseCapture();
3656
3657 if(bAction)
3658 GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd);
3659 }
3660
3661 return 0;
3662 }
3663
3664 LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
3665 {
3666 if(m_bPressed)
3667 {
3668 m_bPressed = false;
3669 Invalidate(FALSE);
3670 UpdateWindow();
3671 }
3672
3673 return 0;
3674 }
3675
3676 LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
3677 {
3678 if(wParam != NULL)
3679 {
3680 DoPaint((HDC)wParam);
3681 }
3682 else
3683 {
3684 CPaintDC dc(this->m_hWnd);
3685 DoPaint(dc.m_hDC);
3686 }
3687
3688 return 0;
3689 }
3690
3691 // painting helper
3692 void DoPaint(CDCHandle dc)
3693 {
3694 RECT rect = {};
3695 GetClientRect(&rect);
3696
3697 RECT rcImage = { _xyBtnImageLeftTop, _xyBtnImageLeftTop, _xyBtnImageRightBottom + 1, _xyBtnImageRightBottom + 1 };
3698 ::OffsetRect(&rcImage, rect.left, rect.top);
3699 if(m_bPressed)
3700 ::OffsetRect(&rcImage, 1, 0);
3701
3702 // draw button frame and background
3703 CPen penFrame;
3704 penFrame.CreatePen(PS_SOLID, 0, ::GetSysColor((m_bHover || m_bPressed) ? COLOR_BTNTEXT : COLOR_BTNSHADOW));
3705 HPEN hPenOld = dc.SelectPen(penFrame);
3706
3707 CBrush brush;
3708 brush.CreateSysColorBrush(m_bPressed ? COLOR_BTNSHADOW : COLOR_WINDOW);
3709 HBRUSH hBrushOld = dc.SelectBrush(brush);
3710
3711 dc.Rectangle(&rect);
3712
3713 // draw button "X"
3714 CPen penX;
3715 penX.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNTEXT));
3716 dc.SelectPen(penX);
3717
3718 dc.MoveTo(rcImage.left, rcImage.top);
3719 dc.LineTo(rcImage.right, rcImage.bottom);
3720 dc.MoveTo(rcImage.left + 1, rcImage.top);
3721 dc.LineTo(rcImage.right + 1, rcImage.bottom);
3722
3723 dc.MoveTo(rcImage.left, rcImage.bottom - 1);
3724 dc.LineTo(rcImage.right, rcImage.top - 1);
3725 dc.MoveTo(rcImage.left + 1, rcImage.bottom - 1);
3726 dc.LineTo(rcImage.right + 1, rcImage.top - 1);
3727
3728 dc.SelectPen(hPenOld);
3729 dc.SelectBrush(hBrushOld);
3730 }
3731 };
3732
3733
3734 template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
3735 class ATL_NO_VTABLE CTabViewImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
3736 {
3737 public:
3738 DECLARE_WND_CLASS_EX2(NULL, T, 0, COLOR_APPWORKSPACE)
3739
3740 // Declarations and enums
3741 struct TABVIEWPAGE
3742 {
3743 HWND hWnd;
3744 LPTSTR lpstrTitle;
3745 LPVOID pData;
3746 };
3747
3748 struct TCITEMEXTRA
3749 {
3750 TCITEMHEADER tciheader;
3751 TABVIEWPAGE tvpage;
3752
3753 operator LPTCITEM() { return (LPTCITEM)this; }
3754 };
3755
3756 enum
3757 {
3758 m_nTabID = 1313,
3759 m_cxMoveMark = 6,
3760 m_cyMoveMark = 3,
3761 m_nMenuItemsMax = (ID_WINDOW_TABLAST - ID_WINDOW_TABFIRST + 1)
3762 };
3763
3764 enum { _nAutoScrollTimerID = 4321 };
3765
3766 enum AutoScroll
3767 {
3768 _AUTOSCROLL_NONE = 0,
3769 _AUTOSCROLL_LEFT = -1,
3770 _AUTOSCROLL_RIGHT = 1
3771 };
3772
3773 enum CloseBtn
3774 {
3775 _cxCloseBtn = 14,
3776 _cyCloseBtn = 13,
3777 _cxCloseBtnMargin = 4,
3778 _cxCloseBtnMarginSel = 1,
3779
3780 _nCloseBtnID = ID_PANE_CLOSE
3781 };
3782
3783 // Data members
3784 ATL::CContainedWindowT<CTabCtrl> m_tab;
3785 int m_cyTabHeight;
3786
3787 int m_nActivePage;
3788
3789 int m_nInsertItem;
3790 POINT m_ptStartDrag;
3791
3792 CMenuHandle m_menu;
3793
3794 int m_cchTabTextLength;
3795
3796 int m_nMenuItemsCount;
3797
3798 ATL::CWindow m_wndTitleBar;
3799 LPTSTR m_lpstrTitleBarBase;
3800 int m_cchTitleBarLength;
3801
3802 CImageList m_ilDrag;
3803
3804 AutoScroll m_AutoScroll;
3805 CUpDownCtrl m_ud;
3806
3807 CTabViewCloseBtn m_btnClose;
3808 int m_nCloseItem;
3809
3810 bool m_bDestroyPageOnRemove:1;
3811 bool m_bDestroyImageList:1;
3812 bool m_bActivePageMenuItem:1;
3813 bool m_bActiveAsDefaultMenuItem:1;
3814 bool m_bEmptyMenuItem:1;
3815 bool m_bWindowsMenuItem:1;
3816 bool m_bNoTabDrag:1;
3817 bool m_bNoTabDragAutoScroll:1;
3818 bool m_bTabCloseButton:1;
3819 // internal
3820 bool m_bTabCapture:1;
3821 bool m_bTabDrag:1;
3822 bool m_bInternalFont:1;
3823
3824 // Constructor/destructor
3825 CTabViewImpl() :
3826 m_tab(this, 1),
3827 m_cyTabHeight(0),
3828 m_nActivePage(-1),
3829 m_nInsertItem(-1),
3830 m_cchTabTextLength(30),
3831 m_nMenuItemsCount(10),
3832 m_lpstrTitleBarBase(NULL),
3833 m_cchTitleBarLength(100),
3834 m_AutoScroll(_AUTOSCROLL_NONE),
3835 m_nCloseItem(-1),
3836 m_bDestroyPageOnRemove(true),
3837 m_bDestroyImageList(true),
3838 m_bActivePageMenuItem(true),
3839 m_bActiveAsDefaultMenuItem(false),
3840 m_bEmptyMenuItem(false),
3841 m_bWindowsMenuItem(false),
3842 m_bNoTabDrag(false),
3843 m_bNoTabDragAutoScroll(false),
3844 m_bTabCloseButton(true),
3845 m_bTabCapture(false),
3846 m_bTabDrag(false),
3847 m_bInternalFont(false)
3848 {
3849 m_ptStartDrag.x = 0;
3850 m_ptStartDrag.y = 0;
3851 }
3852
3853 ~CTabViewImpl()
3854 {
3855 delete [] m_lpstrTitleBarBase;
3856 }
3857
3858 // Message filter function - to be called from PreTranslateMessage of the main window
3859 BOOL PreTranslateMessage(MSG* pMsg)
3860 {
3861 if(this->IsWindow() == FALSE)
3862 return FALSE;
3863
3864 BOOL bRet = FALSE;
3865
3866 // Check for TabView built-in accelerators (Ctrl+Tab/Ctrl+Shift+Tab - next/previous page)
3867 int nCount = GetPageCount();
3868 if(nCount > 0)
3869 {
3870 bool bControl = (::GetKeyState(VK_CONTROL) < 0);
3871 if((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_TAB) && bControl)
3872 {
3873 if(nCount > 1)
3874 {
3875 int nPage = m_nActivePage;
3876 bool bShift = (::GetKeyState(VK_SHIFT) < 0);
3877 if(bShift)
3878 nPage = (nPage > 0) ? (nPage - 1) : (nCount - 1);
3879 else
3880 nPage = ((nPage >= 0) && (nPage < (nCount - 1))) ? (nPage + 1) : 0;
3881
3882 SetActivePage(nPage);
3883 T* pT = static_cast<T*>(this);
3884 pT->OnPageActivated(m_nActivePage);
3885 }
3886
3887 bRet = TRUE;
3888 }
3889 }
3890
3891 // If we are doing drag-drop, check for Escape key that cancels it
3892 if(bRet == FALSE)
3893 {
3894 if(m_bTabCapture && (pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
3895 {
3896 ::ReleaseCapture();
3897 bRet = TRUE;
3898 }
3899 }
3900
3901 // Pass the message to the active page
3902 if(bRet == FALSE)
3903 {
3904 if(m_nActivePage != -1)
3905 bRet = (BOOL)::SendMessage(GetPageHWND(m_nActivePage), WM_FORWARDMSG, 0, (LPARAM)pMsg);
3906 }
3907
3908 return bRet;
3909 }
3910
3911 // Attributes
3912 int GetPageCount() const
3913 {
3914 ATLASSERT(::IsWindow(this->m_hWnd));
3915 return m_tab.GetItemCount();
3916 }
3917
3918 int GetActivePage() const
3919 {
3920 return m_nActivePage;
3921 }
3922
3923 void SetActivePage(int nPage)
3924 {
3925 ATLASSERT(::IsWindow(this->m_hWnd));
3926 ATLASSERT(IsValidPageIndex(nPage));
3927
3928 T* pT = static_cast<T*>(this);
3929
3930 this->SetRedraw(FALSE);
3931
3932 if(m_nActivePage != -1)
3933 ::ShowWindow(GetPageHWND(m_nActivePage), SW_HIDE);
3934 m_nActivePage = nPage;
3935 m_tab.SetCurSel(m_nActivePage);
3936 ::ShowWindow(GetPageHWND(m_nActivePage), SW_SHOW);
3937
3938 pT->UpdateLayout();
3939
3940 this->SetRedraw(TRUE);
3941 this->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
3942
3943 if(::GetFocus() != m_tab.m_hWnd)
3944 ::SetFocus(GetPageHWND(m_nActivePage));
3945
3946 pT->UpdateTitleBar();
3947 pT->UpdateMenu();
3948 }
3949
3950 HIMAGELIST GetImageList() const
3951 {
3952 ATLASSERT(::IsWindow(this->m_hWnd));
3953 return m_tab.GetImageList();
3954 }
3955
3956 HIMAGELIST SetImageList(HIMAGELIST hImageList)
3957 {
3958 ATLASSERT(::IsWindow(this->m_hWnd));
3959 return m_tab.SetImageList(hImageList);
3960 }
3961
3962 void SetWindowMenu(HMENU hMenu)
3963 {
3964 ATLASSERT(::IsWindow(this->m_hWnd));
3965
3966 m_menu = hMenu;
3967
3968 T* pT = static_cast<T*>(this);
3969 pT->UpdateMenu();
3970 }
3971
3972 void SetTitleBarWindow(HWND hWnd)
3973 {
3974 ATLASSERT(::IsWindow(this->m_hWnd));
3975
3976 delete [] m_lpstrTitleBarBase;
3977 m_lpstrTitleBarBase = NULL;
3978
3979 m_wndTitleBar = hWnd;
3980 if(hWnd == NULL)
3981 return;
3982
3983 int cchLen = m_wndTitleBar.GetWindowTextLength() + 1;
3984 ATLTRY(m_lpstrTitleBarBase = new TCHAR[cchLen]);
3985 if(m_lpstrTitleBarBase != NULL)
3986 {
3987 m_wndTitleBar.GetWindowText(m_lpstrTitleBarBase, cchLen);
3988 T* pT = static_cast<T*>(this);
3989 pT->UpdateTitleBar();
3990 }
3991 }
3992
3993 // Page attributes
3994 HWND GetPageHWND(int nPage) const
3995 {
3996 ATLASSERT(::IsWindow(this->m_hWnd));
3997 ATLASSERT(IsValidPageIndex(nPage));
3998
3999 TCITEMEXTRA tcix = {};
4000 tcix.tciheader.mask = TCIF_PARAM;
4001 m_tab.GetItem(nPage, tcix);
4002
4003 return tcix.tvpage.hWnd;
4004 }
4005
4006 LPCTSTR GetPageTitle(int nPage) const
4007 {
4008 ATLASSERT(::IsWindow(this->m_hWnd));
4009 ATLASSERT(IsValidPageIndex(nPage));
4010
4011 TCITEMEXTRA tcix = {};
4012 tcix.tciheader.mask = TCIF_PARAM;
4013 if(m_tab.GetItem(nPage, tcix) == FALSE)
4014 return NULL;
4015
4016 return tcix.tvpage.lpstrTitle;
4017 }
4018
4019 bool SetPageTitle(int nPage, LPCTSTR lpstrTitle)
4020 {
4021 ATLASSERT(::IsWindow(this->m_hWnd));
4022 ATLASSERT(IsValidPageIndex(nPage));
4023
4024 T* pT = static_cast<T*>(this);
4025
4026 int cchBuff = lstrlen(lpstrTitle) + 1;
4027 LPTSTR lpstrBuff = NULL;
4028 ATLTRY(lpstrBuff = new TCHAR[cchBuff]);
4029 if(lpstrBuff == NULL)
4030 return false;
4031
4032 ATL::Checked::tcscpy_s(lpstrBuff, cchBuff, lpstrTitle);
4033 TCITEMEXTRA tcix = {};
4034 tcix.tciheader.mask = TCIF_PARAM;
4035 if(m_tab.GetItem(nPage, tcix) == FALSE)
4036 return false;
4037
4038 ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
4039 LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
4040 if(lpstrTabText == NULL)
4041 return false;
4042
4043 delete [] tcix.tvpage.lpstrTitle;
4044
4045 pT->ShortenTitle(lpstrTitle, lpstrTabText, m_cchTabTextLength + 1);
4046
4047 tcix.tciheader.mask = TCIF_TEXT | TCIF_PARAM;
4048 tcix.tciheader.pszText = lpstrTabText;
4049 tcix.tvpage.lpstrTitle = lpstrBuff;
4050 if(m_tab.SetItem(nPage, tcix) == FALSE)
4051 return false;
4052
4053 pT->UpdateTitleBar();
4054 pT->UpdateMenu();
4055
4056 return true;
4057 }
4058
4059 LPVOID GetPageData(int nPage) const
4060 {
4061 ATLASSERT(::IsWindow(this->m_hWnd));
4062 ATLASSERT(IsValidPageIndex(nPage));
4063
4064 TCITEMEXTRA tcix = {};
4065 tcix.tciheader.mask = TCIF_PARAM;
4066 m_tab.GetItem(nPage, tcix);
4067
4068 return tcix.tvpage.pData;
4069 }
4070
4071 LPVOID SetPageData(int nPage, LPVOID pData)
4072 {
4073 ATLASSERT(::IsWindow(this->m_hWnd));
4074 ATLASSERT(IsValidPageIndex(nPage));
4075
4076 TCITEMEXTRA tcix = {};
4077 tcix.tciheader.mask = TCIF_PARAM;
4078 m_tab.GetItem(nPage, tcix);
4079 LPVOID pDataOld = tcix.tvpage.pData;
4080
4081 tcix.tvpage.pData = pData;
4082 m_tab.SetItem(nPage, tcix);
4083
4084 return pDataOld;
4085 }
4086
4087 int GetPageImage(int nPage) const
4088 {
4089 ATLASSERT(::IsWindow(this->m_hWnd));
4090 ATLASSERT(IsValidPageIndex(nPage));
4091
4092 TCITEMEXTRA tcix = {};
4093 tcix.tciheader.mask = TCIF_IMAGE;
4094 m_tab.GetItem(nPage, tcix);
4095
4096 return tcix.tciheader.iImage;
4097 }
4098
4099 int SetPageImage(int nPage, int nImage)
4100 {
4101 ATLASSERT(::IsWindow(this->m_hWnd));
4102 ATLASSERT(IsValidPageIndex(nPage));
4103
4104 TCITEMEXTRA tcix = {};
4105 tcix.tciheader.mask = TCIF_IMAGE;
4106 m_tab.GetItem(nPage, tcix);
4107 int nImageOld = tcix.tciheader.iImage;
4108
4109 tcix.tciheader.iImage = nImage;
4110 m_tab.SetItem(nPage, tcix);
4111
4112 return nImageOld;
4113 }
4114
4115 // Operations
4116 bool AddPage(HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL)
4117 {
4118 return InsertPage(GetPageCount(), hWndView, lpstrTitle, nImage, pData);
4119 }
4120
4121 bool InsertPage(int nPage, HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL)
4122 {
4123 ATLASSERT(::IsWindow(this->m_hWnd));
4124 ATLASSERT((nPage == GetPageCount()) || IsValidPageIndex(nPage));
4125
4126 T* pT = static_cast<T*>(this);
4127
4128 int cchBuff = lstrlen(lpstrTitle) + 1;
4129 LPTSTR lpstrBuff = NULL;
4130 ATLTRY(lpstrBuff = new TCHAR[cchBuff]);
4131 if(lpstrBuff == NULL)
4132 return false;
4133
4134 ATL::Checked::tcscpy_s(lpstrBuff, cchBuff, lpstrTitle);
4135
4136 ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
4137 LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
4138 if(lpstrTabText == NULL)
4139 return false;
4140
4141 pT->ShortenTitle(lpstrTitle, lpstrTabText, m_cchTabTextLength + 1);
4142
4143 this->SetRedraw(FALSE);
4144
4145 TCITEMEXTRA tcix = {};
4146 tcix.tciheader.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
4147 tcix.tciheader.pszText = lpstrTabText;
4148 tcix.tciheader.iImage = nImage;
4149 tcix.tvpage.hWnd = hWndView;
4150 tcix.tvpage.lpstrTitle = lpstrBuff;
4151 tcix.tvpage.pData = pData;
4152 int nItem = m_tab.InsertItem(nPage, tcix);
4153 if(nItem == -1)
4154 {
4155 delete [] lpstrBuff;
4156 this->SetRedraw(TRUE);
4157 return false;
4158 }
4159
4160 // adjust active page index, if inserted before it
4161 if(nPage <= m_nActivePage)
4162 m_nActivePage++;
4163
4164 SetActivePage(nItem);
4165 pT->OnPageActivated(m_nActivePage);
4166
4167 if(GetPageCount() == 1)
4168 pT->ShowTabControl(true);
4169
4170 pT->UpdateLayout();
4171
4172 this->SetRedraw(TRUE);
4173 this->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
4174
4175 return true;
4176 }
4177
4178 void RemovePage(int nPage)
4179 {
4180 ATLASSERT(::IsWindow(this->m_hWnd));
4181 ATLASSERT(IsValidPageIndex(nPage));
4182
4183 T* pT = static_cast<T*>(this);
4184
4185 this->SetRedraw(FALSE);
4186
4187 if(GetPageCount() == 1)
4188 pT->ShowTabControl(false);
4189
4190 if(m_bDestroyPageOnRemove)
4191 ::DestroyWindow(GetPageHWND(nPage));
4192 else
4193 ::ShowWindow(GetPageHWND(nPage), SW_HIDE);
4194 LPTSTR lpstrTitle = (LPTSTR)GetPageTitle(nPage);
4195 delete [] lpstrTitle;
4196
4197 ATLVERIFY(m_tab.DeleteItem(nPage) != FALSE);
4198
4199 if(m_nActivePage == nPage)
4200 {
4201 m_nActivePage = -1;
4202
4203 if(nPage > 0)
4204 {
4205 SetActivePage(nPage - 1);
4206 }
4207 else if(GetPageCount() > 0)
4208 {
4209 SetActivePage(nPage);
4210 }
4211 else
4212 {
4213 this->SetRedraw(TRUE);
4214 this->Invalidate();
4215 this->UpdateWindow();
4216 pT->UpdateTitleBar();
4217 pT->UpdateMenu();
4218 }
4219 }
4220 else
4221 {
4222 nPage = (nPage < m_nActivePage) ? (m_nActivePage - 1) : m_nActivePage;
4223 m_nActivePage = -1;
4224 SetActivePage(nPage);
4225 }
4226
4227 pT->OnPageActivated(m_nActivePage);
4228 }
4229
4230 void RemoveAllPages()
4231 {
4232 ATLASSERT(::IsWindow(this->m_hWnd));
4233
4234 if(GetPageCount() == 0)
4235 return;
4236
4237 T* pT = static_cast<T*>(this);
4238
4239 this->SetRedraw(FALSE);
4240
4241 pT->ShowTabControl(false);
4242
4243 for(int i = 0; i < GetPageCount(); i++)
4244 {
4245 if(m_bDestroyPageOnRemove)
4246 ::DestroyWindow(GetPageHWND(i));
4247 else
4248 ::ShowWindow(GetPageHWND(i), SW_HIDE);
4249 LPTSTR lpstrTitle = (LPTSTR)GetPageTitle(i);
4250 delete [] lpstrTitle;
4251 }
4252 m_tab.DeleteAllItems();
4253
4254 m_nActivePage = -1;
4255 pT->OnPageActivated(m_nActivePage);
4256
4257 this->SetRedraw(TRUE);
4258 this->Invalidate();
4259 this->UpdateWindow();
4260
4261 pT->UpdateTitleBar();
4262 pT->UpdateMenu();
4263 }
4264
4265 int PageIndexFromHwnd(HWND hWnd) const
4266 {
4267 int nIndex = -1;
4268
4269 for(int i = 0; i < GetPageCount(); i++)
4270 {
4271 if(GetPageHWND(i) == hWnd)
4272 {
4273 nIndex = i;
4274 break;
4275 }
4276 }
4277
4278 return nIndex;
4279 }
4280
4281 void BuildWindowMenu(HMENU hMenu, int nMenuItemsCount = 10, bool bEmptyMenuItem = true, bool bWindowsMenuItem = true, bool bActivePageMenuItem = true, bool bActiveAsDefaultMenuItem = false)
4282 {
4283 ATLASSERT(::IsWindow(this->m_hWnd));
4284
4285 CMenuHandle menu = hMenu;
4286 T* pT = static_cast<T*>(this);
4287 (void)pT; // avoid level 4 warning
4288 int nFirstPos = 0;
4289
4290 // Find first menu item in our range
4291 for(nFirstPos = 0; nFirstPos < menu.GetMenuItemCount(); nFirstPos++)
4292 {
4293 UINT nID = menu.GetMenuItemID(nFirstPos);
4294 if(((nID >= ID_WINDOW_TABFIRST) && (nID <= ID_WINDOW_TABLAST)) || (nID == ID_WINDOW_SHOWTABLIST))
4295 break;
4296 }
4297
4298 // Remove all menu items for tab pages
4299 BOOL bRet = TRUE;
4300 while(bRet != FALSE)
4301 bRet = menu.DeleteMenu(nFirstPos, MF_BYPOSITION);
4302
4303 // Add separator if it's not already there
4304 int nPageCount = GetPageCount();
4305 if((bWindowsMenuItem || (nPageCount > 0)) && (nFirstPos > 0))
4306 {
4307 CMenuItemInfo mii;
4308 mii.fMask = MIIM_TYPE;
4309 menu.GetMenuItemInfo(nFirstPos - 1, TRUE, &mii);
4310 if((nFirstPos <= 0) || ((mii.fType & MFT_SEPARATOR) == 0))
4311 {
4312 menu.AppendMenu(MF_SEPARATOR);
4313 nFirstPos++;
4314 }
4315 }
4316
4317 // Add menu items for all pages
4318 if(nPageCount > 0)
4319 {
4320 // Append menu items for all pages
4321 const int cchPrefix = 3; // 2 digits + space
4322 nMenuItemsCount = __min(__min(nPageCount, nMenuItemsCount), (int)m_nMenuItemsMax);
4323 ATLASSERT(nMenuItemsCount < 100); // 2 digits only
4324 if(nMenuItemsCount >= 100)
4325 nMenuItemsCount = 99;
4326
4327 for(int i = 0; i < nMenuItemsCount; i++)
4328 {
4329 LPCTSTR lpstrTitle = GetPageTitle(i);
4330 int nLen = lstrlen(lpstrTitle);
4331 ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
4332 LPTSTR lpstrText = buff.Allocate(cchPrefix + nLen + 1);
4333 ATLASSERT(lpstrText != NULL);
4334 if(lpstrText != NULL)
4335 {
4336 LPCTSTR lpstrFormat = (i < 9) ? _T("&%i %s") : _T("%i %s");
4337 _stprintf_s(lpstrText, cchPrefix + nLen + 1, lpstrFormat, i + 1, lpstrTitle);
4338 menu.AppendMenu(MF_STRING, ID_WINDOW_TABFIRST + i, lpstrText);
4339 }
4340 }
4341
4342 // Mark active page
4343 if(bActivePageMenuItem && (m_nActivePage != -1))
4344 {
4345 if(bActiveAsDefaultMenuItem)
4346 {
4347 menu.SetMenuDefaultItem((UINT)-1, TRUE);
4348 menu.SetMenuDefaultItem(nFirstPos + m_nActivePage, TRUE);
4349 }
4350 else
4351 {
4352 menu.CheckMenuRadioItem(nFirstPos, nFirstPos + nMenuItemsCount, nFirstPos + m_nActivePage, MF_BYPOSITION);
4353 }
4354 }
4355 }
4356 else
4357 {
4358 if(bEmptyMenuItem)
4359 {
4360 menu.AppendMenu(MF_BYPOSITION | MF_STRING, ID_WINDOW_TABFIRST, pT->GetEmptyListText());
4361 menu.EnableMenuItem(ID_WINDOW_TABFIRST, MF_GRAYED);
4362 }
4363
4364 // Remove separator if nothing else is there
4365 if(!bEmptyMenuItem && !bWindowsMenuItem && (nFirstPos > 0))
4366 {
4367 CMenuItemInfo mii;
4368 mii.fMask = MIIM_TYPE;
4369 menu.GetMenuItemInfo(nFirstPos - 1, TRUE, &mii);
4370 if((mii.fType & MFT_SEPARATOR) != 0)
4371 menu.DeleteMenu(nFirstPos - 1, MF_BYPOSITION);
4372 }
4373 }
4374
4375 // Add "Windows..." menu item
4376 if(bWindowsMenuItem)
4377 menu.AppendMenu(MF_BYPOSITION | MF_STRING, ID_WINDOW_SHOWTABLIST, pT->GetWindowsMenuItemText());
4378 }
4379
4380 BOOL SubclassWindow(HWND hWnd)
4381 {
4382 BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
4383 if(bRet != FALSE)
4384 {
4385 T* pT = static_cast<T*>(this);
4386 pT->CreateTabControl();
4387 pT->UpdateLayout();
4388 }
4389
4390 return bRet;
4391 }
4392
4393 // Message map and handlers
4394 BEGIN_MSG_MAP(CTabViewImpl)
4395 MESSAGE_HANDLER(WM_CREATE, OnCreate)
4396 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
4397 MESSAGE_HANDLER(WM_SIZE, OnSize)
4398 MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
4399 MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
4400 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
4401 MESSAGE_HANDLER(WM_TIMER, OnTimer)
4402 MESSAGE_HANDLER(WM_CONTEXTMENU, OnTabContextMenu)
4403 NOTIFY_HANDLER(m_nTabID, TCN_SELCHANGE, OnTabChanged)
4404 NOTIFY_ID_HANDLER(m_nTabID, OnTabNotification)
4405 NOTIFY_CODE_HANDLER(TTN_GETDISPINFO, OnTabGetDispInfo)
4406 FORWARD_NOTIFICATIONS()
4407 ALT_MSG_MAP(1) // tab control
4408 MESSAGE_HANDLER(WM_LBUTTONDOWN, OnTabLButtonDown)
4409 MESSAGE_HANDLER(WM_LBUTTONUP, OnTabLButtonUp)
4410 MESSAGE_HANDLER(WM_CAPTURECHANGED, OnTabCaptureChanged)
4411 MESSAGE_HANDLER(WM_MOUSEMOVE, OnTabMouseMove)
4412 MESSAGE_HANDLER(WM_MOUSELEAVE, OnTabMouseLeave)
4413 NOTIFY_HANDLER(T::_nCloseBtnID, TBVN_CLOSEBTNMOUSELEAVE, OnTabCloseBtnMouseLeave)
4414 COMMAND_HANDLER(T::_nCloseBtnID, BN_CLICKED, OnTabCloseBtnClicked)
4415 END_MSG_MAP()
4416
4417 LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
4418 {
4419 T* pT = static_cast<T*>(this);
4420 pT->CreateTabControl();
4421
4422 return 0;
4423 }
4424
4425 LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
4426 {
4427 RemoveAllPages();
4428
4429 if(m_bDestroyImageList)
4430 {
4431 CImageList il = m_tab.SetImageList(NULL);
4432 if(il.m_hImageList != NULL)
4433 il.Destroy();
4434 }
4435
4436 if(m_bInternalFont)
4437 {
4438 HFONT hFont = m_tab.GetFont();
4439 m_tab.SetFont(NULL, FALSE);
4440 ::DeleteObject(hFont);
4441 m_bInternalFont = false;
4442 }
4443
4444 m_ud.m_hWnd = NULL;
4445
4446 return 0;
4447 }
4448
4449 LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
4450 {
4451 T* pT = static_cast<T*>(this);
4452 pT->UpdateLayout();
4453 return 0;
4454 }
4455
4456 LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
4457 {
4458 if(m_nActivePage != -1)
4459 ::SetFocus(GetPageHWND(m_nActivePage));
4460 return 0;
4461 }
4462
4463 LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
4464 {
4465 return m_tab.SendMessage(WM_GETFONT);
4466 }
4467
4468 LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
4469 {
4470 if(m_bInternalFont)
4471 {
4472 HFONT hFont = m_tab.GetFont();
4473 m_tab.SetFont(NULL, FALSE);
4474 ::DeleteObject(hFont);
4475 m_bInternalFont = false;
4476 }
4477
4478 m_tab.SendMessage(WM_SETFONT, wParam, lParam);
4479
4480 T* pT = static_cast<T*>(this);
4481 m_cyTabHeight = pT->CalcTabHeight();
4482
4483 if((BOOL)lParam != FALSE)
4484 pT->UpdateLayout();
4485
4486 return 0;
4487 }
4488
4489 LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
4490 {
4491 if(wParam == _nAutoScrollTimerID)
4492 {
4493 T* pT = static_cast<T*>(this);
4494 pT->DoAutoScroll();
4495 }
4496 else
4497 {
4498 bHandled = FALSE;
4499 }
4500
4501 return 0;
4502 }
4503
4504 LRESULT OnTabContextMenu(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
4505 {
4506 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
4507 int nPage = m_nActivePage;
4508 bool bAction = false;
4509 if((HWND)wParam == m_tab.m_hWnd)
4510 {
4511 if((pt.x == -1) && (pt.y == -1)) // keyboard
4512 {
4513 RECT rect = {};
4514 m_tab.GetItemRect(m_nActivePage, &rect);
4515 pt.x = rect.left;
4516 pt.y = rect.bottom;
4517 m_tab.ClientToScreen(&pt);
4518 bAction = true;
4519 }
4520 else if(::WindowFromPoint(pt) == m_tab.m_hWnd)
4521 {
4522 TCHITTESTINFO hti = {};
4523 hti.pt = pt;
4524 this->ScreenToClient(&hti.pt);
4525 nPage = m_tab.HitTest(&hti);
4526
4527 bAction = true;
4528 }
4529 }
4530
4531 if(bAction)
4532 {
4533 T* pT = static_cast<T*>(this);
4534 pT->OnContextMenu(nPage, pt);
4535 }
4536 else
4537 {
4538 bHandled = FALSE;
4539 }
4540
4541 return 0;
4542 }
4543
4544 LRESULT OnTabChanged(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
4545 {
4546 if(m_bTabCloseButton && (m_btnClose.m_hWnd != NULL))
4547 {
4548 T* pT = static_cast<T*>(this);
4549 RECT rcClose = {};
4550 pT->CalcCloseButtonRect(m_nCloseItem, rcClose);
4551 m_btnClose.SetWindowPos(NULL, &rcClose, SWP_NOZORDER | SWP_NOACTIVATE);
4552 }
4553
4554 SetActivePage(m_tab.GetCurSel());
4555 T* pT = static_cast<T*>(this);
4556 pT->OnPageActivated(m_nActivePage);
4557
4558 return 0;
4559 }
4560
4561 LRESULT OnTabNotification(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
4562 {
4563 // nothing to do - this just blocks all tab control
4564 // notifications from being propagated further
4565 return 0;
4566 }
4567
4568 LRESULT OnTabGetDispInfo(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled)
4569 {
4570 LPNMTTDISPINFO pTTDI = (LPNMTTDISPINFO)pnmh;
4571 if(pTTDI->hdr.hwndFrom == m_tab.GetTooltips())
4572 {
4573 T* pT = static_cast<T*>(this);
4574 pT->UpdateTooltipText(pTTDI);
4575 }
4576 else
4577 {
4578 bHandled = FALSE;
4579 }
4580
4581 return 0;
4582 }
4583
4584 // Tab control message handlers
4585 LRESULT OnTabLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
4586 {
4587 if(!m_bNoTabDrag && (m_tab.GetItemCount() > 1))
4588 {
4589 m_bTabCapture = true;
4590 m_tab.SetCapture();
4591
4592 m_ptStartDrag.x = GET_X_LPARAM(lParam);
4593 m_ptStartDrag.y = GET_Y_LPARAM(lParam);
4594 }
4595
4596 bHandled = FALSE;
4597 return 0;
4598 }
4599
4600 LRESULT OnTabLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
4601 {
4602 if(m_bTabCapture)
4603 {
4604 if(m_bTabDrag)
4605 {
4606 T* pT = static_cast<T*>(this);
4607 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
4608 int nItem = pT->DragHitTest(pt);
4609 if(nItem != -1)
4610 MovePage(m_nActivePage, nItem);
4611 }
4612
4613 ::ReleaseCapture();
4614 }
4615
4616 bHandled = FALSE;
4617 return 0;
4618 }
4619
4620 LRESULT OnTabCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
4621 {
4622 if(m_bTabCapture)
4623 {
4624 m_bTabCapture = false;
4625
4626 if(m_bTabDrag)
4627 {
4628 m_bTabDrag = false;
4629
4630 T* pT = static_cast<T*>(this);
4631 if(!m_bNoTabDragAutoScroll)
4632 pT->StartStopAutoScroll(-1);
4633
4634 pT->DrawMoveMark(-1);
4635
4636 m_ilDrag.DragLeave(GetDesktopWindow());
4637 m_ilDrag.EndDrag();
4638
4639 m_ilDrag.Destroy();
4640 m_ilDrag.m_hImageList = NULL;
4641 }
4642 }
4643
4644 bHandled = FALSE;
4645 return 0;
4646 }
4647
4648 LRESULT OnTabMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
4649 {
4650 bHandled = FALSE;
4651
4652 if(m_bTabCapture)
4653 {
4654 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
4655
4656 if(!m_bTabDrag)
4657 {
4658 if((abs(m_ptStartDrag.x - GET_X_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CXDRAG)) ||
4659 (abs(m_ptStartDrag.y - GET_Y_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CYDRAG)))
4660 {
4661 T* pT = static_cast<T*>(this);
4662 pT->GenerateDragImage(m_nActivePage);
4663
4664 int cxCursor = ::GetSystemMetrics(SM_CXCURSOR);
4665 int cyCursor = ::GetSystemMetrics(SM_CYCURSOR);
4666 m_ilDrag.BeginDrag(0, -(cxCursor / 2), -(cyCursor / 2));
4667 POINT ptEnter = m_ptStartDrag;
4668 m_tab.ClientToScreen(&ptEnter);
4669 m_ilDrag.DragEnter(GetDesktopWindow(), ptEnter);
4670
4671 m_bTabDrag = true;
4672 }
4673 }
4674
4675 if(m_bTabDrag)
4676 {
4677 T* pT = static_cast<T*>(this);
4678 int nItem = pT->DragHitTest(pt);
4679
4680 pT->SetMoveCursor(nItem != -1);
4681
4682 if(m_nInsertItem != nItem)
4683 pT->DrawMoveMark(nItem);
4684
4685 if(!m_bNoTabDragAutoScroll)
4686 pT->StartStopAutoScroll(pt.x);
4687
4688 m_ilDrag.DragShowNolock((nItem != -1) ? TRUE : FALSE);
4689 m_tab.ClientToScreen(&pt);
4690 m_ilDrag.DragMove(pt);
4691
4692 bHandled = TRUE;
4693 }
4694 }
4695 else if(m_bTabCloseButton)
4696 {
4697 TCHITTESTINFO thti = {};
4698 thti.pt.x = GET_X_LPARAM(lParam);
4699 thti.pt.y = GET_Y_LPARAM(lParam);
4700
4701 int nItem = m_tab.HitTest(&thti);
4702 if(nItem >= 0)
4703 {
4704 ATLTRACE(_T("+++++ item = %i\n"), nItem);
4705
4706 T* pT = static_cast<T*>(this);
4707 if(m_btnClose.m_hWnd == NULL)
4708 {
4709 pT->CreateCloseButton(nItem);
4710 m_nCloseItem = nItem;
4711 }
4712 else if(m_nCloseItem != nItem)
4713 {
4714 RECT rcClose = {};
4715 pT->CalcCloseButtonRect(nItem, rcClose);
4716 m_btnClose.SetWindowPos(NULL, &rcClose, SWP_NOZORDER | SWP_NOACTIVATE);
4717 m_nCloseItem = nItem;
4718 }
4719
4720 TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, m_tab.m_hWnd };
4721 ::TrackMouseEvent(&tme);
4722 }
4723 }
4724
4725 return 0;
4726 }
4727
4728 LRESULT OnTabMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
4729 {
4730 bHandled = FALSE;
4731
4732 if(m_btnClose.m_hWnd != NULL)
4733 {
4734 POINT pt = {};
4735 ::GetCursorPos(&pt);
4736 RECT rect = {};
4737 m_btnClose.GetWindowRect(&rect);
4738 if(::PtInRect(&rect, pt) == FALSE)
4739 {
4740 m_nCloseItem = -1;
4741 T* pT = static_cast<T*>(this);
4742 pT->DestroyCloseButton();
4743 }
4744 else
4745 {
4746 bHandled = TRUE;
4747 }
4748 }
4749
4750 return 0;
4751 }
4752
4753 LRESULT OnTabCloseBtnMouseLeave(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
4754 {
4755 TCHITTESTINFO thti = {};
4756 ::GetCursorPos(&thti.pt);
4757 m_tab.ScreenToClient(&thti.pt);
4758 int nItem = m_tab.HitTest(&thti);
4759 if(nItem == -1)
4760 m_tab.SendMessage(WM_MOUSELEAVE);
4761
4762 return 0;
4763 }
4764
4765 LRESULT OnTabCloseBtnClicked(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
4766 {
4767 T* pT = static_cast<T*>(this);
4768 pT->OnTabCloseBtn(m_nCloseItem);
4769
4770 return 0;
4771 }
4772
4773 // Implementation helpers
4774 bool IsValidPageIndex(int nPage) const
4775 {
4776 return ((nPage >= 0) && (nPage < GetPageCount()));
4777 }
4778
4779 bool MovePage(int nMovePage, int nInsertBeforePage)
4780 {
4781 ATLASSERT(IsValidPageIndex(nMovePage));
4782 ATLASSERT(IsValidPageIndex(nInsertBeforePage));
4783
4784 if(!IsValidPageIndex(nMovePage) || !IsValidPageIndex(nInsertBeforePage))
4785 return false;
4786
4787 if(nMovePage == nInsertBeforePage)
4788 return true; // nothing to do
4789
4790 ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
4791 LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
4792 if(lpstrTabText == NULL)
4793 return false;
4794 TCITEMEXTRA tcix = {};
4795 tcix.tciheader.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
4796 tcix.tciheader.pszText = lpstrTabText;
4797 tcix.tciheader.cchTextMax = m_cchTabTextLength + 1;
4798 BOOL bRet = m_tab.GetItem(nMovePage, tcix);
4799 ATLASSERT(bRet != FALSE);
4800 if(bRet == FALSE)
4801 return false;
4802
4803 int nInsertItem = (nInsertBeforePage > nMovePage) ? nInsertBeforePage + 1 : nInsertBeforePage;
4804 int nNewItem = m_tab.InsertItem(nInsertItem, tcix);
4805 ATLASSERT(nNewItem == nInsertItem);
4806 if(nNewItem != nInsertItem)
4807 {
4808 ATLVERIFY(m_tab.DeleteItem(nNewItem));
4809 return false;
4810 }
4811
4812 if(nMovePage > nInsertBeforePage)
4813 ATLVERIFY(m_tab.DeleteItem(nMovePage + 1) != FALSE);
4814 else if(nMovePage < nInsertBeforePage)
4815 ATLVERIFY(m_tab.DeleteItem(nMovePage) != FALSE);
4816
4817 SetActivePage(nInsertBeforePage);
4818 T* pT = static_cast<T*>(this);
4819 pT->OnPageActivated(m_nActivePage);
4820
4821 return true;
4822 }
4823
4824 // Implementation overrideables
4825 bool CreateTabControl()
4826 {
4827 m_tab.Create(this->m_hWnd, this->rcDefault, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | TCS_TOOLTIPS, 0, m_nTabID);
4828 ATLASSERT(m_tab.m_hWnd != NULL);
4829 if(m_tab.m_hWnd == NULL)
4830 return false;
4831
4832 m_tab.SetFont(AtlCreateControlFont());
4833 m_bInternalFont = true;
4834
4835 m_tab.SetItemExtra(sizeof(TABVIEWPAGE));
4836
4837 T* pT = static_cast<T*>(this);
4838 m_cyTabHeight = pT->CalcTabHeight();
4839
4840 return true;
4841 }
4842
4843 int CalcTabHeight()
4844 {
4845 int nCount = m_tab.GetItemCount();
4846 TCHAR szText[] = _T("NS");
4847 TCITEMEXTRA tcix = {};
4848 tcix.tciheader.mask = TCIF_TEXT;
4849 tcix.tciheader.pszText = szText;
4850 int nIndex = m_tab.InsertItem(nCount, tcix);
4851
4852 RECT rect = { 0, 0, 1000, 1000 };
4853 m_tab.AdjustRect(FALSE, &rect);
4854
4855 RECT rcWnd = { 0, 0, 1000, rect.top };
4856 ::AdjustWindowRectEx(&rcWnd, m_tab.GetStyle(), FALSE, m_tab.GetExStyle());
4857
4858 int nHeight = rcWnd.bottom - rcWnd.top;
4859
4860 m_tab.DeleteItem(nIndex);
4861
4862 return nHeight;
4863 }
4864
4865 void ShowTabControl(bool bShow)
4866 {
4867 m_tab.ShowWindow(bShow ? SW_SHOWNOACTIVATE : SW_HIDE);
4868 T* pT = static_cast<T*>(this);
4869 pT->UpdateLayout();
4870 }
4871
4872 void UpdateLayout()
4873 {
4874 RECT rect = {};
4875 this->GetClientRect(&rect);
4876
4877 int cyOffset = 0;
4878 if(m_tab.IsWindow() && ((m_tab.GetStyle() & WS_VISIBLE) != 0))
4879 {
4880 m_tab.SetWindowPos(NULL, 0, 0, rect.right - rect.left, m_cyTabHeight, SWP_NOZORDER);
4881 cyOffset = m_cyTabHeight;
4882 }
4883
4884 if(m_nActivePage != -1)
4885 ::SetWindowPos(GetPageHWND(m_nActivePage), NULL, 0, cyOffset, rect.right - rect.left, rect.bottom - rect.top - cyOffset, SWP_NOZORDER);
4886 }
4887
4888 void UpdateMenu()
4889 {
4890 if(m_menu.m_hMenu != NULL)
4891 BuildWindowMenu(m_menu, m_nMenuItemsCount, m_bEmptyMenuItem, m_bWindowsMenuItem, m_bActivePageMenuItem, m_bActiveAsDefaultMenuItem);
4892 }
4893
4894 void UpdateTitleBar()
4895 {
4896 if(!m_wndTitleBar.IsWindow() || (m_lpstrTitleBarBase == NULL))
4897 return; // nothing to do
4898
4899 if(m_nActivePage != -1)
4900 {
4901 T* pT = static_cast<T*>(this);
4902 LPCTSTR lpstrTitle = pT->GetPageTitle(m_nActivePage);
4903 LPCTSTR lpstrDivider = pT->GetTitleDividerText();
4904 int cchBuffer = m_cchTitleBarLength + lstrlen(lpstrDivider) + lstrlen(m_lpstrTitleBarBase) + 1;
4905 ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
4906 LPTSTR lpstrPageTitle = buff.Allocate(cchBuffer);
4907 ATLASSERT(lpstrPageTitle != NULL);
4908 if(lpstrPageTitle != NULL)
4909 {
4910 pT->ShortenTitle(lpstrTitle, lpstrPageTitle, m_cchTitleBarLength + 1);
4911 ATL::Checked::tcscat_s(lpstrPageTitle, cchBuffer, lpstrDivider);
4912 ATL::Checked::tcscat_s(lpstrPageTitle, cchBuffer, m_lpstrTitleBarBase);
4913 }
4914 else
4915 {
4916 lpstrPageTitle = m_lpstrTitleBarBase;
4917 }
4918
4919 m_wndTitleBar.SetWindowText(lpstrPageTitle);
4920 }
4921 else
4922 {
4923 m_wndTitleBar.SetWindowText(m_lpstrTitleBarBase);
4924 }
4925 }
4926
4927 void DrawMoveMark(int nItem)
4928 {
4929 T* pT = static_cast<T*>(this);
4930
4931 if(m_nInsertItem != -1)
4932 {
4933 RECT rect = {};
4934 pT->GetMoveMarkRect(rect);
4935 m_tab.InvalidateRect(&rect);
4936 }
4937
4938 m_nInsertItem = nItem;
4939
4940 if(m_nInsertItem != -1)
4941 {
4942 CClientDC dc(m_tab.m_hWnd);
4943
4944 RECT rect = {};
4945 pT->GetMoveMarkRect(rect);
4946
4947 CPen pen;
4948 pen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_WINDOWTEXT));
4949 CBrush brush;
4950 brush.CreateSolidBrush(::GetSysColor(COLOR_WINDOWTEXT));
4951
4952 HPEN hPenOld = dc.SelectPen(pen);
4953 HBRUSH hBrushOld = dc.SelectBrush(brush);
4954
4955 int x = rect.left;
4956 int y = rect.top;
4957 POINT ptsTop[3] = { { x, y }, { x + m_cxMoveMark, y }, { x + (m_cxMoveMark / 2), y + m_cyMoveMark } };
4958 dc.Polygon(ptsTop, 3);
4959
4960 y = rect.bottom - 1;
4961 POINT ptsBottom[3] = { { x, y }, { x + m_cxMoveMark, y }, { x + (m_cxMoveMark / 2), y - m_cyMoveMark } };
4962 dc.Polygon(ptsBottom, 3);
4963
4964 dc.SelectPen(hPenOld);
4965 dc.SelectBrush(hBrushOld);
4966 }
4967 }
4968
4969 void GetMoveMarkRect(RECT& rect) const
4970 {
4971 m_tab.GetClientRect(&rect);
4972
4973 RECT rcItem = {};
4974 m_tab.GetItemRect(m_nInsertItem, &rcItem);
4975
4976 if(m_nInsertItem <= m_nActivePage)
4977 {
4978 rect.left = rcItem.left - m_cxMoveMark / 2 - 1;
4979 rect.right = rcItem.left + m_cxMoveMark / 2;
4980 }
4981 else
4982 {
4983 rect.left = rcItem.right - m_cxMoveMark / 2 - 1;
4984 rect.right = rcItem.right + m_cxMoveMark / 2;
4985 }
4986 }
4987
4988 void SetMoveCursor(bool bCanMove)
4989 {
4990 ::SetCursor(::LoadCursor(NULL, bCanMove ? IDC_ARROW : IDC_NO));
4991 }
4992
4993 void GenerateDragImage(int nItem)
4994 {
4995 ATLASSERT(IsValidPageIndex(nItem));
4996
4997 RECT rcItem = {};
4998 m_tab.GetItemRect(nItem, &rcItem);
4999 ::InflateRect(&rcItem, 2, 2); // make bigger to cover selected item
5000
5001 ATLASSERT(m_ilDrag.m_hImageList == NULL);
5002 m_ilDrag.Create(rcItem.right - rcItem.left, rcItem.bottom - rcItem.top, ILC_COLORDDB | ILC_MASK, 1, 1);
5003
5004 CClientDC dc(this->m_hWnd);
5005 CDC dcMem;
5006 dcMem.CreateCompatibleDC(dc);
5007 ATLASSERT(dcMem.m_hDC != NULL);
5008 dcMem.SetViewportOrg(-rcItem.left, -rcItem.top);
5009
5010 CBitmap bmp;
5011 bmp.CreateCompatibleBitmap(dc, rcItem.right - rcItem.left, rcItem.bottom - rcItem.top);
5012 ATLASSERT(bmp.m_hBitmap != NULL);
5013
5014 HBITMAP hBmpOld = dcMem.SelectBitmap(bmp);
5015 m_tab.SendMessage(WM_PRINTCLIENT, (WPARAM)dcMem.m_hDC);
5016 dcMem.SelectBitmap(hBmpOld);
5017
5018 ATLVERIFY(m_ilDrag.Add(bmp.m_hBitmap, RGB(255, 0, 255)) != -1);
5019 }
5020
5021 void ShortenTitle(LPCTSTR lpstrTitle, LPTSTR lpstrShortTitle, int cchShortTitle)
5022 {
5023 if(lstrlen(lpstrTitle) >= cchShortTitle)
5024 {
5025 LPCTSTR lpstrEllipsis = _T("...");
5026 int cchEllipsis = lstrlen(lpstrEllipsis);
5027 ATL::Checked::tcsncpy_s(lpstrShortTitle, cchShortTitle, lpstrTitle, cchShortTitle - cchEllipsis - 1);
5028 ATL::Checked::tcscat_s(lpstrShortTitle, cchShortTitle, lpstrEllipsis);
5029 }
5030 else
5031 {
5032 ATL::Checked::tcscpy_s(lpstrShortTitle, cchShortTitle, lpstrTitle);
5033 }
5034 }
5035
5036 void UpdateTooltipText(LPNMTTDISPINFO pTTDI)
5037 {
5038 ATLASSERT(pTTDI != NULL);
5039 pTTDI->lpszText = (LPTSTR)GetPageTitle((int)pTTDI->hdr.idFrom);
5040 }
5041
5042 int DragHitTest(POINT pt) const
5043 {
5044 RECT rect = {};
5045 this->GetClientRect(&rect);
5046 if(::PtInRect(&rect, pt) == FALSE)
5047 return -1;
5048
5049 m_tab.GetClientRect(&rect);
5050 TCHITTESTINFO hti = {};
5051 hti.pt.x = pt.x;
5052 hti.pt.y = rect.bottom / 2; // use middle to ignore
5053 int nItem = m_tab.HitTest(&hti);
5054 if(nItem == -1)
5055 {
5056 int nLast = m_tab.GetItemCount() - 1;
5057 RECT rcItem = {};
5058 m_tab.GetItemRect(nLast, &rcItem);
5059 if(pt.x >= rcItem.right)
5060 nItem = nLast;
5061 }
5062
5063 return nItem;
5064 }
5065
5066 void StartStopAutoScroll(int x)
5067 {
5068 AutoScroll scroll = _AUTOSCROLL_NONE;
5069 if(x != -1)
5070 {
5071 RECT rect = {};
5072 m_tab.GetClientRect(&rect);
5073 int dx = ::GetSystemMetrics(SM_CXVSCROLL);
5074 if((x >= 0) && (x < dx))
5075 {
5076 RECT rcItem = {};
5077 m_tab.GetItemRect(0, &rcItem);
5078 if(rcItem.left < rect.left)
5079 scroll = _AUTOSCROLL_LEFT;
5080 }
5081 else if((x >= (rect.right - dx)) && (x < rect.right))
5082 {
5083 RECT rcItem = {};
5084 m_tab.GetItemRect(m_tab.GetItemCount() - 1, &rcItem);
5085 if(rcItem.right > rect.right)
5086 scroll = _AUTOSCROLL_RIGHT;
5087 }
5088 }
5089
5090 if(scroll != _AUTOSCROLL_NONE)
5091 {
5092 if(m_ud.m_hWnd == NULL)
5093 m_ud = m_tab.GetWindow(GW_CHILD);
5094
5095 if(m_AutoScroll != scroll)
5096 {
5097 m_AutoScroll = scroll;
5098 this->SetTimer(_nAutoScrollTimerID, 300);
5099 }
5100 }
5101 else
5102 {
5103 this->KillTimer(_nAutoScrollTimerID);
5104 m_AutoScroll = _AUTOSCROLL_NONE;
5105 }
5106 }
5107
5108 void DoAutoScroll()
5109 {
5110 ATLASSERT(m_AutoScroll != _AUTOSCROLL_NONE);
5111
5112 int nMin = -1, nMax = -1;
5113 m_ud.GetRange(nMin, nMax);
5114 int nPos = m_ud.GetPos();
5115
5116 int nNewPos = -1;
5117 if((m_AutoScroll == _AUTOSCROLL_LEFT) && (nPos > nMin))
5118 nNewPos = nPos - 1;
5119 else if((m_AutoScroll == _AUTOSCROLL_RIGHT) && (nPos < nMax))
5120 nNewPos = nPos + 1;
5121 if(nNewPos != -1)
5122 {
5123 m_tab.SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, nNewPos));
5124 m_tab.SendMessage(WM_HSCROLL, MAKEWPARAM(SB_ENDSCROLL, 0));
5125
5126 POINT pt = {};
5127 ::GetCursorPos(&pt);
5128 m_tab.ScreenToClient(&pt);
5129 m_tab.SendMessage(WM_MOUSEMOVE, NULL, MAKELPARAM(pt.x, pt.y));
5130 }
5131 }
5132
5133 // Text for menu items and title bar - override to provide different strings
5134 static LPCTSTR GetEmptyListText()
5135 {
5136 return _T("(Empty)");
5137 }
5138
5139 static LPCTSTR GetWindowsMenuItemText()
5140 {
5141 return _T("&Windows...");
5142 }
5143
5144 static LPCTSTR GetTitleDividerText()
5145 {
5146 return _T(" - ");
5147 }
5148
5149 // Notifications - override to provide different behavior
5150 void OnPageActivated(int nPage)
5151 {
5152 NMHDR nmhdr = {};
5153 nmhdr.hwndFrom = this->m_hWnd;
5154 nmhdr.idFrom = nPage;
5155 nmhdr.code = TBVN_PAGEACTIVATED;
5156 this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr);
5157 }
5158
5159 void OnContextMenu(int nPage, POINT pt)
5160 {
5161 TBVCONTEXTMENUINFO cmi = {};
5162 cmi.hdr.hwndFrom = this->m_hWnd;
5163 cmi.hdr.idFrom = nPage;
5164 cmi.hdr.code = TBVN_CONTEXTMENU;
5165 cmi.pt = pt;
5166 this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&cmi);
5167 }
5168
5169 void OnTabCloseBtn(int nPage)
5170 {
5171 NMHDR nmhdr = {};
5172 nmhdr.hwndFrom = this->m_hWnd;
5173 nmhdr.idFrom = nPage;
5174 nmhdr.code = TBVN_TABCLOSEBTN;
5175 LRESULT lRet = this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr);
5176 if(lRet == 0) // default - close page
5177 {
5178 T* pT = static_cast<T*>(this);
5179 pT->RemovePage(m_nCloseItem);
5180 m_nCloseItem = -1;
5181 pT->DestroyCloseButton();
5182 }
5183 else
5184 {
5185 m_tab.SendMessage(WM_MOUSELEAVE);
5186 }
5187 }
5188
5189 // Close button overrideables
5190 void CreateCloseButton(int nItem)
5191 {
5192 ATLASSERT(m_btnClose.m_hWnd == NULL);
5193
5194 m_btnClose.m_bPressed = false;
5195
5196 T* pT = static_cast<T*>(this);
5197 RECT rcClose = {};
5198 pT->CalcCloseButtonRect(nItem, rcClose);
5199 m_btnClose.Create(m_tab.m_hWnd, rcClose, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, T::_nCloseBtnID);
5200 ATLASSERT(m_btnClose.IsWindow());
5201
5202 if(m_btnClose.m_hWnd != NULL)
5203 {
5204 // create a tool tip
5205 ATLASSERT(m_btnClose.m_tip.m_hWnd == NULL);
5206 m_btnClose.m_tip.Create(m_btnClose.m_hWnd);
5207 ATLASSERT(m_btnClose.m_tip.IsWindow());
5208
5209 if(m_btnClose.m_tip.IsWindow())
5210 {
5211 m_btnClose.m_tip.Activate(TRUE);
5212
5213 RECT rect = {};
5214 m_btnClose.GetClientRect(&rect);
5215 m_btnClose.m_tip.AddTool(m_btnClose.m_hWnd, LPSTR_TEXTCALLBACK, &rect, T::_nCloseBtnID);
5216 }
5217 }
5218 }
5219
5220 void DestroyCloseButton()
5221 {
5222 ATLASSERT(m_btnClose.m_hWnd != NULL);
5223
5224 if(m_btnClose.m_hWnd != NULL)
5225 {
5226 if(m_btnClose.m_tip.IsWindow())
5227 {
5228 m_btnClose.m_tip.DestroyWindow();
5229 m_btnClose.m_tip.m_hWnd = NULL;
5230 }
5231
5232 m_btnClose.DestroyWindow();
5233 }
5234 }
5235
5236 void CalcCloseButtonRect(int nItem, RECT& rcClose)
5237 {
5238 RECT rcItem = {};
5239 m_tab.GetItemRect(nItem, &rcItem);
5240
5241 int cy = (rcItem.bottom - rcItem.top - _cyCloseBtn) / 2;
5242 int cx = (nItem == m_tab.GetCurSel()) ? _cxCloseBtnMarginSel : _cxCloseBtnMargin;
5243 ::SetRect(&rcClose, rcItem.right - cx - _cxCloseBtn, rcItem.top + cy,
5244 rcItem.right - cx, rcItem.top + cy + _cyCloseBtn);
5245 }
5246 };
5247
5248 class CTabView : public CTabViewImpl<CTabView>
5249 {
5250 public:
5251 DECLARE_WND_CLASS_EX(_T("WTL_TabView"), 0, COLOR_APPWORKSPACE)
5252 };
5253
5254 } // namespace WTL
5255
5256 #endif // __ATLCTRLX_H__