mrDarker
2025-08-13 5cc675212e96d87ebbf00f4fd7a8106b06a490ff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
////////////////////////////////////////////////////////////////////////////
// TitleTip.cpp : implementation file
//
// Adapted from code written by Zafir Anjum
//
// Modifed 10 Apr 1999  Now accepts a LOGFONT pointer and 
//                        a tracking rect in Show(...)  (Chris Maunder)
//         18 Apr 1999  Resource leak in Show fixed by Daniel Gehriger
//          7 Jan 2000  Added multiline capabilities, and the ability to
//                      specify the maximum length of the tip (Mark Findlay)
   
 
#include "stdafx.h"
#include "TitleTip.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
 
/////////////////////////////////////////////////////////////////////////////
// CTitleTip
    
CTitleTip::CTitleTip()
{
    // Register the window class if it has not already been registered.
    WNDCLASS wndcls;
    HINSTANCE hInst = AfxGetInstanceHandle();
    if(!(::GetClassInfo(hInst, TITLETIP_CLASSNAME, &wndcls)))
    {
        // otherwise we need to register a new class
        wndcls.style            = CS_SAVEBITS;
        wndcls.lpfnWndProc        = ::DefWindowProc;
        wndcls.cbClsExtra        = wndcls.cbWndExtra = 0;
        wndcls.hInstance        = hInst;
        wndcls.hIcon            = NULL;
        wndcls.hCursor            = LoadCursor( hInst, IDC_ARROW );
        wndcls.hbrBackground    = (HBRUSH)(COLOR_INFOBK + 1); 
        wndcls.lpszMenuName        = NULL;
        wndcls.lpszClassName    = TITLETIP_CLASSNAME;
 
        if (!AfxRegisterClass(&wndcls))
            AfxThrowResourceException();
    }
}
 
CTitleTip::~CTitleTip()
{
    if (::IsWindow(m_hWnd))
        DestroyWindow();
}
 
 
BEGIN_MESSAGE_MAP(CTitleTip, CWnd)
    //{{AFX_MSG_MAP(CTitleTip)
    ON_WM_MOUSEMOVE()
    ON_WM_PAINT()
    ON_WM_SYSKEYDOWN()
    ON_WM_KEYDOWN()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
 
/////////////////////////////////////////////////////////////////////////////
// CTitleTip message handlers
 
BOOL CTitleTip::Create(CWnd * pParentWnd)
{
//    ASSERT_VALID(pParentWnd);
 
    DWORD dwStyle = WS_BORDER | WS_POPUP; 
    DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
    m_pParentWnd = pParentWnd;
 
    return CreateEx(dwExStyle, TITLETIP_CLASSNAME, NULL, dwStyle, 
                    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
                    NULL, NULL, NULL );
}
 
 
// Show          - Show the titletip if needed
// rectTitle     - The rectangle within which the original 
//                    title is constrained - in client coordinates
// lpszTitleText - The text to be displayed
// xoffset         - Number of pixel that the text is offset from
//                   left border of the cell
void CTitleTip::Show(CRect rectTitle, LPCTSTR lpszTitleText, 
                     int xoffset /*=0*/, int nMaxChars /*=-1*/, 
                     LPRECT lpHoverRect /*=NULL*/,
                     LPLOGFONT lpLogFont /*=NULL*/,
                     DWORD dwFormat /*=...*/)
{
//    ASSERT( ::IsWindow( GetSafeHwnd() ) );
 
    if (rectTitle.IsRectEmpty())
        return;
 
    // If titletip is already displayed, don't do anything.
    if( IsWindowVisible() ) 
        return;
 
    m_rectHover = (lpHoverRect != NULL)? lpHoverRect : rectTitle;
    m_rectHover.right++; m_rectHover.bottom++;
 
    m_pParentWnd->ClientToScreen( m_rectHover );
    ScreenToClient( m_rectHover );
 
 
    // Do not display the titletip is app does not have focus
    if( GetFocus() == NULL )
        return;
 
    // Define the rectangle outside which the titletip will be hidden.
    // We add a buffer of one pixel around the rectangle
    m_rectTitle.top    = -1;
    m_rectTitle.left   = -xoffset-1;
    m_rectTitle.right  = rectTitle.Width()-xoffset;
    m_rectTitle.bottom = rectTitle.Height()+1;
 
    // Determine the width of the text
    m_pParentWnd->ClientToScreen( rectTitle );
 
    CClientDC dc(this);
    m_strTitle = _T("");
    //m_strTitle += _T(" ");
    m_strTitle += lpszTitleText; 
    //m_strTitle += _T(" ");
 
    CFont font, *pOldFont = NULL;
    if (lpLogFont)
    {
        font.CreateFontIndirect(lpLogFont);
        pOldFont = dc.SelectObject( &font );
    }
    else
    {
        // use same font as ctrl
        pOldFont = dc.SelectObject( m_pParentWnd->GetFont() );
    }
 
    CSize size = dc.GetTextExtent( m_strTitle );
 
    TEXTMETRIC tm;
    dc.GetTextMetrics(&tm);
    size.cx += tm.tmOverhang;
 
    dc.SelectObject( pOldFont );
 
    m_rectDisplay = rectTitle;
    m_rectDisplay.left += xoffset-1;
    m_rectDisplay.top  += 0;
    m_rectDisplay.right = m_rectDisplay.left + size.cx + xoffset;
    m_rectDisplay.bottom = m_rectDisplay.top + size.cy;
    
    // Do not display if the text fits within available space
    if ( m_rectDisplay.right <= rectTitle.right-xoffset )
        return;
 
    // We will use avg char width to set max tooltip width
    int nMaxTooltipWidth = -1;
    if (nMaxChars > 0)
    {
        int nMaxTooltipWidth = (tm.tmAveCharWidth * nMaxChars);
        if (nMaxTooltipWidth < 0)
            nMaxTooltipWidth *= -1;
 
        // Rect display to be set to max chars
        if (m_rectDisplay.Width() > nMaxTooltipWidth)
            m_rectDisplay.right = m_rectDisplay.left + nMaxTooltipWidth;
    }
 
    //***************************************************************************************
    //Adjust the dimensions of the rect to fit within the client
 
    // Get the coordinates of the parents client area. (In this case the ListView's client
    // area) and convert coordinates to those of the tooltip.
    CRect rectClient;
    m_pParentWnd->GetClientRect( rectClient );
    m_pParentWnd->ClientToScreen( rectClient );
 
 
    // ------------------------------------------------------------------------------
    // Use the screen's right edge as the right hand border, not the right edge of the client.
    // You can comment this out to use the right client as the border.
    CWindowDC wdc(NULL);
    rectClient.right = GetDeviceCaps(wdc, HORZRES) - 8;
    rectClient.bottom = GetDeviceCaps(wdc, VERTRES) - 8;
    //---------------------------------------------------------------------------------------
 
 
    //If the right edge exceeds the right edge of the client:
    //          see how much room there is to move the display to the left and adjust the
    //          rectangle that far to the left. If the rect still exceeds the right edge, clip
    //          the right edge to match the client right edge.
    //
    // Does the right display edge exceed the right client edge?
    if (m_rectDisplay.right > rectClient.right)
    {
        // establish what is available left shift wise and what is needed
        int nAvail = 0;
        int nNeeded = m_rectDisplay.right - rectClient.right;
 
        if (m_rectDisplay.left > rectClient.left)
            nAvail = m_rectDisplay.left - rectClient.left;
 
        // is there room to move left?
        if (nAvail >= nNeeded)
        {
            m_rectDisplay.OffsetRect(-nNeeded,0);  // yes, move all that is needed
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.right += nNeeded;
        }
        else
        {
            m_rectDisplay.OffsetRect(-nAvail,0);   // no, at least move to left edge of client
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.right += nAvail;
        }
 
        // Did we move enough? If not, clip right edge to match client right edge
        if (m_rectDisplay.right > rectClient.right)
            m_rectDisplay.right = rectClient.right;
    }
 
 
    //If the left edge exceeds the left edge of the client:
    //          see how much room there is to move the display to the right and adjust the
    //          rectangle that far to the right. If the rect still exceeds the left edge, clip
    //          the left edge to match the client left edge.
    //
    // Does the left display edge exceed the left client edge?
    if (m_rectDisplay.left < rectClient.left)
    {
        // establish what is available right shift wise and what is needed
        int nAvail = 0;
        int nNeeded = rectClient.left - m_rectDisplay.left;
 
        if (m_rectDisplay.right < rectClient.right)
            nAvail = rectClient.right - m_rectDisplay.right;
 
        // is there room to move left?
        if (nAvail >= nNeeded)
        {
            m_rectDisplay.OffsetRect(+nNeeded,0);  // yes, move all that is needed
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.left -= nNeeded;
        }
        else
        {
            m_rectDisplay.OffsetRect(+nAvail,0);   // no, at least move to left edge of client
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.left -= nAvail;
        }
 
        // Did we move enough? If not, clip left edge to match client left edge
        if (m_rectDisplay.left < rectClient.left)
            m_rectDisplay.left = rectClient.left;        
    }
 
 
    // if the calculated width > maxwidth set above then truncate 
    if (nMaxTooltipWidth > 0 && m_rectDisplay.Width() > nMaxTooltipWidth)
        m_rectDisplay.right = m_rectDisplay.left + nMaxTooltipWidth;
 
    //***************************************************************************************
   
    // Use a "work" rect to calculate the bottom. This work rect will be inset 
    // slightly from the rect we have just created so the tooltip does not touch
    // the sides.
    CRect rectCalc = m_rectDisplay;
 
    // rectCalc.top += 1;
 
    int nHeight = dc.DrawText(m_strTitle, rectCalc, dwFormat | DT_CALCRECT);
    m_dwFormat = dwFormat;
    
    // If this is a single line, shorten the display to get rid of any excess blank space
    if (nHeight == tm.tmHeight)
    {
        rectCalc.right = rectCalc.left + size.cx + 3;
    }
 
 
    m_rectDisplay.bottom = m_rectDisplay.top + nHeight;
 
    // ensure the tooltip does not exceed the bottom of the screen
    if (m_rectDisplay.bottom > rectClient.bottom)
    {
        m_rectDisplay.bottom = rectClient.bottom;
        rectCalc.bottom = rectClient.bottom; 
    }
 
    SetWindowPos( &wndTop, 
        m_rectDisplay.left, 
        m_rectDisplay.top, 
        m_rectDisplay.Width(), 
        m_rectDisplay.Height(),
        SWP_SHOWWINDOW|SWP_NOACTIVATE );
    SetCapture();
}
 
void CTitleTip::Hide()
{
      if (!::IsWindow(GetSafeHwnd()))
        return;
 
    if (GetCapture()->GetSafeHwnd() == GetSafeHwnd())
        ReleaseCapture();
 
    ShowWindow( SW_HIDE );
}
 
void CTitleTip::OnMouseMove(UINT nFlags, CPoint point) 
{
    if (!m_rectHover.PtInRect(point)) 
    {
        Hide();
        
        // Forward the message
        ClientToScreen( &point );
        CWnd *pWnd = WindowFromPoint( point );
        if ( pWnd == this ) 
            pWnd = m_pParentWnd;
        
        int hittest = (int)pWnd->SendMessage(WM_NCHITTEST,0,MAKELONG(point.x,point.y));
        
        if (hittest == HTCLIENT)
        {
            pWnd->ScreenToClient( &point );
            pWnd->PostMessage( WM_MOUSEMOVE, nFlags, MAKELONG(point.x,point.y) );
        } 
        else 
            pWnd->PostMessage( WM_NCMOUSEMOVE, hittest, MAKELONG(point.x,point.y) );
    }
}
 
BOOL CTitleTip::PreTranslateMessage(MSG* pMsg) 
{
    CWnd *pWnd;
    int hittest;
    switch (pMsg->message)
    {
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
        POINTS pts = MAKEPOINTS( pMsg->lParam );
        POINT  point;
        point.x = pts.x;
        point.y = pts.y;
        ClientToScreen( &point );
        pWnd = WindowFromPoint( point );
        if( pWnd == this ) 
            pWnd = m_pParentWnd;
 
        hittest = (int)pWnd->SendMessage(WM_NCHITTEST,0,MAKELONG(point.x,point.y));
 
        if (hittest == HTCLIENT)
        {
            pWnd->ScreenToClient( &point );
            pMsg->lParam = MAKELONG(point.x,point.y);
        } 
        else 
        {
            switch (pMsg->message) 
            {
                case WM_LBUTTONDOWN: 
                    pMsg->message = WM_NCLBUTTONDOWN;
                    break;
                case WM_RBUTTONDOWN: 
                    pMsg->message = WM_NCRBUTTONDOWN;
                    break;
                case WM_MBUTTONDOWN: 
                    pMsg->message = WM_NCMBUTTONDOWN;
                    break;
            }
            pMsg->wParam = hittest;
            pMsg->lParam = MAKELONG(point.x,point.y);
        }
 
        Hide();
        pWnd->PostMessage(pMsg->message,pMsg->wParam,pMsg->lParam);
        return TRUE;    
        
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
        Hide();
        m_pParentWnd->PostMessage( pMsg->message, pMsg->wParam, pMsg->lParam );
        return TRUE;
    }
 
    if( GetFocus() == NULL )
    {
        Hide();
        return TRUE;
    }
 
    return CWnd::PreTranslateMessage(pMsg);
}
 
void CTitleTip::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
 
    TEXTMETRIC tm;
    dc.GetTextMetrics(&tm);
 
    CFont *pFont = m_pParentWnd->GetFont();     // use same font as ctrl
    CFont *pFontDC = dc.SelectObject( pFont );
    int nHeight=0;
 
    CRect rect = m_rectDisplay;
    ScreenToClient(rect);
 
    dc.SetBkMode( TRANSPARENT ); 
 
    nHeight = dc.DrawText(m_strTitle, rect, m_dwFormat);
 
    dc.SelectObject( pFontDC );
 
    // Do not call CWnd::OnPaint() for painting messages
}
 
void CTitleTip::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
    Hide();    
    CWnd::OnSysKeyDown(nChar, nRepCnt, nFlags);
}
 
void CTitleTip::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
    Hide();    
    CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}