root/trunk/GUIComponents/XHyperLink.cpp

Revision 5, 17.5 kB (checked in by qbert, 3 years ago)

Initial ( and last :( ) commit

Line 
1 // XHyperLink.cpp  Version 1.0
2 //
3 // XHyperLink static control. Will open the default browser with the given URL
4 // when the user clicks on the link.
5 //
6 // Copyright (C) 1997 - 1999 Chris Maunder
7 // All rights reserved. May not be sold for profit.
8 //
9 // Thanks to Pål K. Tønder for auto-size and window caption changes.
10 //
11 // "GotoURL" function by Stuart Patterson
12 // As seen in the August, 1997 Windows Developer's Journal.
13 // Copyright 1997 by Miller Freeman, Inc. All rights reserved.
14 // Modified by Chris Maunder to use TCHARs instead of chars.
15 //
16 // "Default hand cursor" from Paul DiLascia's Jan 1998 MSJ article.
17 //
18 // 2/29/00 -- P. Shaffer standard font mod.
19 //
20 ///////////////////////////////////////////////////////////////////////////////
21 //
22 // Modified by:  Hans Dietrich
23 //               hdietrich2@hotmail.com
24 //
25 ///////////////////////////////////////////////////////////////////////////////
26
27 #include "stdafx.h"
28 #include "XHyperLink.h"
29 #include "atlconv.h"        // for Unicode conversion
30
31 #ifdef _DEBUG
32 #define new DEBUG_NEW
33 #undef THIS_FILE
34 static char THIS_FILE[] = __FILE__;
35 #endif
36
37 #define TOOLTIP_ID 1
38
39 // Uncomment following line to enable error message box for URL navigation
40 //#define XHYPERLINK_REPORT_ERROR
41
42
43 #ifndef IDC_HAND
44 #define IDC_HAND MAKEINTRESOURCE(32649)   // From WINUSER.H
45 #endif
46
47 // sends message to parent when hyperlink is clicked (see SetNotifyParent())
48 UINT WM_XHYPERLINK_CLICKED = ::RegisterWindowMessage(_T("WM_XHYPERLINK_CLICKED"));
49
50 ///////////////////////////////////////////////////////////////////////////////
51 // CXHyperLink
52
53 BEGIN_MESSAGE_MAP(CXHyperLink, CStatic)
54     //{{AFX_MSG_MAP(CXHyperLink)
55     ON_WM_CTLCOLOR_REFLECT()
56     ON_WM_SETCURSOR()
57     ON_WM_MOUSEMOVE()
58     ON_WM_TIMER()
59     ON_CONTROL_REFLECT(STN_CLICKED, OnClicked)
60     ON_WM_ERASEBKGND()
61     //}}AFX_MSG_MAP
62 END_MESSAGE_MAP()
63
64 ///////////////////////////////////////////////////////////////////////////////
65 // ctor
66 CXHyperLink::CXHyperLink()
67 {
68     m_hLinkCursor     = NULL;           // No cursor as yet
69     m_crLinkColour    = RGB(0,0,238);   // Blue
70     m_crVisitedColour = RGB(85,26,139); // Purple
71     m_crHoverColour   = RGB(255,0,0);   // Red
72     m_bOverControl    = FALSE;          // Cursor not yet over control
73     m_bVisited        = FALSE;          // Hasn't been visited yet.
74     m_nUnderline      = ulHover;        // Underline the link?
75     m_bAdjustToFit    = TRUE;           // Resize the window to fit the text?
76     m_strURL          = _T("");
77     m_nTimerID        = 100;
78     m_bNotifyParent   = FALSE;          // TRUE = notify parent
79     m_bIsURLEnabled   = TRUE;           // TRUE = navigate to url
80     m_bToolTip        = TRUE;           // TRUE = display tooltip
81     m_crBackground    = (UINT) -1;      // set to default (no bg color)
82     m_bAlwaysOpenNew  = FALSE;          // TRUE = always open new browser window
83 }
84
85 ///////////////////////////////////////////////////////////////////////////////
86 // dtor
87 CXHyperLink::~CXHyperLink()
88 {
89     TRACE(_T("in CXHyperLink::~CXHyperLink\n"));
90
91     if (m_hLinkCursor)
92         DestroyCursor(m_hLinkCursor);
93     m_hLinkCursor = NULL;
94     m_UnderlineFont.DeleteObject();
95     if (m_Brush.GetSafeHandle())
96         m_Brush.DeleteObject();
97 }
98
99 /////////////////////////////////////////////////////////////////////////////
100 // CXHyperLink overrides
101
102 ///////////////////////////////////////////////////////////////////////////////
103 // DestroyWindow
104 BOOL CXHyperLink::DestroyWindow()
105 {
106     KillTimer(m_nTimerID);
107     return CStatic::DestroyWindow();
108 }
109
110 ///////////////////////////////////////////////////////////////////////////////
111 // PreTranslateMessage
112 BOOL CXHyperLink::PreTranslateMessage(MSG* pMsg)
113 {
114     m_ToolTip.RelayEvent(pMsg);
115     return CStatic::PreTranslateMessage(pMsg);
116 }
117
118 ///////////////////////////////////////////////////////////////////////////////
119 // PreSubclassWindow
120 void CXHyperLink::PreSubclassWindow()
121 {
122     // We want to get mouse clicks via STN_CLICKED
123     DWORD dwStyle = GetStyle();
124     ::SetWindowLong(GetSafeHwnd(), GWL_STYLE, dwStyle | SS_NOTIFY);
125    
126     // Set the URL as the window text
127     if (m_strURL.IsEmpty())
128         GetWindowText(m_strURL);
129
130     // Check that the window text isn't empty. If it is, set it as the URL.
131     CString strWndText;
132     GetWindowText(strWndText);
133     if (strWndText.IsEmpty())
134     {
135         ASSERT(!m_strURL.IsEmpty());    // Window and URL both NULL. DUH!
136         SetWindowText(m_strURL);
137     }
138
139     CFont* pFont = GetFont();
140     if (!pFont)
141     {
142         HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
143         if (hFont == NULL)
144             hFont = (HFONT) GetStockObject(ANSI_VAR_FONT);
145         if (hFont)
146             pFont = CFont::FromHandle(hFont);
147     }
148     ASSERT(pFont->GetSafeHandle());
149
150     // Create the underline font
151     LOGFONT lf;
152     pFont->GetLogFont(&lf);
153     m_StdFont.CreateFontIndirect(&lf);
154     lf.lfUnderline = (BYTE) TRUE;
155     m_UnderlineFont.CreateFontIndirect(&lf);
156
157     PositionWindow();       // Adjust size of window to fit URL if necessary
158     SetDefaultCursor();     // Try and load up a "hand" cursor
159     SetUnderline();
160
161     // Create the tooltip
162     if (m_bToolTip)
163     {
164         CRect rect;
165         GetClientRect(rect);
166         m_ToolTip.Create(this);
167         m_ToolTip.AddTool(this, m_strURL, rect, TOOLTIP_ID);
168     }
169
170     CStatic::PreSubclassWindow();
171 }
172
173 /////////////////////////////////////////////////////////////////////////////
174 // CXHyperLink message handlers
175
176 ///////////////////////////////////////////////////////////////////////////////
177 // OnClicked
178 void CXHyperLink::OnClicked()
179 {
180     m_bOverControl = FALSE;
181     int result = HINSTANCE_ERROR + 1;
182     if (m_bIsURLEnabled)
183         result = (int)GotoURL(m_strURL, SW_SHOW, m_bAlwaysOpenNew);
184     m_bVisited = (result > HINSTANCE_ERROR);
185     if (!m_bVisited)
186     {
187         MessageBeep(MB_ICONEXCLAMATION);     // Unable to follow link
188         ReportError(result);
189     }
190     else
191         SetVisited();                       // Repaint to show visited colour
192
193     NotifyParent();
194 }
195
196 ///////////////////////////////////////////////////////////////////////////////
197 // CtlColor
198 #ifdef _DEBUG
199 HBRUSH CXHyperLink::CtlColor(CDC* pDC, UINT nCtlColor)
200 #else
201 HBRUSH CXHyperLink::CtlColor(CDC* pDC, UINT /*nCtlColor*/)
202 #endif
203 {
204     ASSERT(nCtlColor == CTLCOLOR_STATIC);
205
206     if (m_bOverControl)
207         pDC->SetTextColor(m_crHoverColour);
208     else if (m_bVisited)
209         pDC->SetTextColor(m_crVisitedColour);
210     else
211         pDC->SetTextColor(m_crLinkColour);
212
213     // transparent text.
214     pDC->SetBkMode(TRANSPARENT);
215
216     if (m_Brush.GetSafeHandle())
217     {
218         pDC->SetBkColor(m_crBackground);
219         return (HBRUSH) m_Brush;
220     }
221     else
222     {
223         return (HBRUSH)GetStockObject(NULL_BRUSH);
224     }
225 }
226
227 ///////////////////////////////////////////////////////////////////////////////
228 // OnMouseMove
229 void CXHyperLink::OnMouseMove(UINT nFlags, CPoint point)
230 {
231     if (!m_bOverControl)        // Cursor has just moved over control
232     {
233         m_bOverControl = TRUE;
234
235         if (m_nUnderline == ulHover)
236             SetFont(&m_UnderlineFont);
237         Invalidate();
238
239         SetTimer(m_nTimerID, 100, NULL);
240     }
241     CStatic::OnMouseMove(nFlags, point);
242 }
243
244 ///////////////////////////////////////////////////////////////////////////////
245 // OnTimer
246 void CXHyperLink::OnTimer(UINT nIDEvent)
247 {
248     CPoint p(GetMessagePos());
249     ScreenToClient(&p);
250
251     CRect rect;
252     GetClientRect(rect);
253     if (!rect.PtInRect(p))
254     {
255         m_bOverControl = FALSE;
256         KillTimer(m_nTimerID);
257
258         if (m_nUnderline != ulAlways)
259             SetFont(&m_StdFont);
260         rect.bottom+=10;
261         InvalidateRect(rect);
262     }
263    
264     CStatic::OnTimer(nIDEvent);
265 }
266
267 ///////////////////////////////////////////////////////////////////////////////
268 // OnSetCursor
269 BOOL CXHyperLink::OnSetCursor(CWnd* /*pWnd*/, UINT /*nHitTest*/, UINT /*message*/)
270 {
271     if (m_hLinkCursor)
272     {
273         ::SetCursor(m_hLinkCursor);
274         return TRUE;
275     }
276     return FALSE;
277 }
278
279 ///////////////////////////////////////////////////////////////////////////////
280 // OnEraseBkgnd
281 BOOL CXHyperLink::OnEraseBkgnd(CDC* pDC)
282 {
283     CRect rect;
284     GetClientRect(rect);
285     if (m_crBackground != (UINT)-1)
286         pDC->FillSolidRect(rect, m_crBackground);
287     else
288         pDC->FillSolidRect(rect, ::GetSysColor(COLOR_3DFACE));
289
290     return TRUE;
291 }
292
293 /////////////////////////////////////////////////////////////////////////////
294 // CXHyperLink operations
295
296 ///////////////////////////////////////////////////////////////////////////////
297 // SetURL
298 void CXHyperLink::SetURL(CString strURL)
299 {
300     m_strURL = strURL;
301
302     if (::IsWindow(GetSafeHwnd()))
303     {
304         PositionWindow();
305         m_ToolTip.UpdateTipText(strURL, this, TOOLTIP_ID);
306     }
307 }
308
309 ///////////////////////////////////////////////////////////////////////////////
310 // SetColours
311 void CXHyperLink::SetColours(COLORREF crLinkColour,
312                              COLORREF crVisitedColour, 
313                              COLORREF crHoverColour /* = -1 */)
314 { 
315     m_crLinkColour  = crLinkColour;
316     m_crVisitedColour = crVisitedColour;
317
318     if (crHoverColour == -1)
319         m_crHoverColour = ::GetSysColor(COLOR_HIGHLIGHT);
320     else
321         m_crHoverColour = crHoverColour;
322
323     if (::IsWindow(m_hWnd))
324         Invalidate();
325 }
326
327 ///////////////////////////////////////////////////////////////////////////////
328 // SetBackgroundColour
329 void CXHyperLink::SetBackgroundColour(COLORREF crBackground)
330 {
331     m_crBackground = crBackground;
332     if (m_Brush.GetSafeHandle())
333         m_Brush.DeleteObject();
334     m_Brush.CreateSolidBrush(m_crBackground);
335 }
336
337 ///////////////////////////////////////////////////////////////////////////////
338 // SetVisited
339 void CXHyperLink::SetVisited(BOOL bVisited /* = TRUE */)
340 { 
341     m_bVisited = bVisited;
342
343     if (::IsWindow(GetSafeHwnd()))
344         Invalidate();
345 }
346
347 ///////////////////////////////////////////////////////////////////////////////
348 // SetLinkCursor
349 void CXHyperLink::SetLinkCursor(HCURSOR hCursor)
350 { 
351     m_hLinkCursor = hCursor;
352     if (m_hLinkCursor == NULL)
353         SetDefaultCursor();
354 }
355
356 ///////////////////////////////////////////////////////////////////////////////
357 // SetUnderline
358 void CXHyperLink::SetUnderline(int nUnderline /*=ulHover*/)
359 {
360     if (m_nUnderline == nUnderline)
361         return;
362
363     if (::IsWindow(GetSafeHwnd()))
364     {
365         if (nUnderline == ulAlways)
366             SetFont(&m_UnderlineFont);
367         else
368             SetFont(&m_StdFont);
369
370         Invalidate();
371     }
372
373     m_nUnderline = nUnderline;
374 }
375
376 ///////////////////////////////////////////////////////////////////////////////
377 // SetAutoSize
378 void CXHyperLink::SetAutoSize(BOOL bAutoSize /* = TRUE */)
379 {
380     m_bAdjustToFit = bAutoSize;
381
382     if (::IsWindow(GetSafeHwnd()))
383         PositionWindow();
384 }
385
386 ///////////////////////////////////////////////////////////////////////////////
387 // SetWindowText
388 void CXHyperLink::SetWindowText(LPCTSTR lpszString)
389 {
390     ASSERT(lpszString);
391     if (!lpszString)
392         return;
393     CStatic::SetWindowText(_T(""));
394     RedrawWindow();
395     CStatic::SetWindowText(lpszString);
396     PositionWindow();
397 }
398
399 ///////////////////////////////////////////////////////////////////////////////
400 // PositionWindow
401
402 // Move and resize the window so that the window is the same size
403 // as the hyperlink text. This stops the hyperlink cursor being active
404 // when it is not directly over the text. If the text is left justified
405 // then the window is merely shrunk, but if it is centred or right
406 // justified then the window will have to be moved as well.
407 //
408 // Suggested by Pål K. Tønder
409 //
410 void CXHyperLink::PositionWindow()
411 {
412     if (!::IsWindow(GetSafeHwnd()) || !m_bAdjustToFit)
413         return;
414
415     // Get the current window position
416     CRect WndRect, ClientRect;
417     GetWindowRect(WndRect);
418     GetClientRect(ClientRect);
419
420     ClientToScreen(ClientRect);
421
422     CWnd* pParent = GetParent();
423     if (pParent)
424     {
425         pParent->ScreenToClient(WndRect);
426         pParent->ScreenToClient(ClientRect);
427     }
428
429     // Get the size of the window text
430     CString strWndText;
431     GetWindowText(strWndText);
432
433     CDC* pDC = GetDC();
434     CFont* pOldFont = pDC->SelectObject(&m_UnderlineFont);
435     CSize Extent = pDC->GetTextExtent(strWndText);
436     pDC->SelectObject(pOldFont);
437     ReleaseDC(pDC);
438
439     // Adjust for window borders
440     Extent.cx += WndRect.Width() - ClientRect.Width();
441     Extent.cy += WndRect.Height() - ClientRect.Height();
442
443     // Get the text justification via the window style
444     DWORD dwStyle = GetStyle();
445
446     // Recalc the window size and position based on the text justification
447     if (dwStyle & SS_CENTERIMAGE)
448         WndRect.DeflateRect(0, (WndRect.Height() - Extent.cy)/2);
449     else
450         WndRect.bottom = WndRect.top + Extent.cy;
451
452     if (dwStyle & SS_CENTER)
453         WndRect.DeflateRect((WndRect.Width() - Extent.cx)/2, 0);
454     else if (dwStyle & SS_RIGHT)
455         WndRect.left = WndRect.right - Extent.cx;
456     else // SS_LEFT = 0, so we can't test for it explicitly
457         WndRect.right = WndRect.left + Extent.cx;
458
459     // Move the window
460     SetWindowPos(NULL,
461                  WndRect.left, WndRect.top,
462                  WndRect.Width(), WndRect.Height(),
463                  SWP_NOZORDER);
464 }
465
466 /////////////////////////////////////////////////////////////////////////////
467 // CXHyperLink implementation
468
469 ///////////////////////////////////////////////////////////////////////////////
470 // SetDefaultCursor
471 void CXHyperLink::SetDefaultCursor()
472 {
473     if (m_hLinkCursor == NULL)              // No cursor handle - try to load one
474     {
475         // First try to load the Win98 / Windows 2000 hand cursor
476
477         TRACE(_T("loading from IDC_HAND\n"));
478         m_hLinkCursor = AfxGetApp()->LoadStandardCursor(IDC_HAND);
479
480         if (m_hLinkCursor == NULL)          // Still no cursor handle -
481                                             // load the WinHelp hand cursor
482         {
483             // The following appeared in Paul DiLascia's Jan 1998 MSJ articles.
484             // It loads a "hand" cursor from the winhlp32.exe module.
485
486             TRACE(_T("loading from winhlp32\n"));
487
488             // Get the windows directory
489             CString strWndDir;
490             GetWindowsDirectory(strWndDir.GetBuffer(MAX_PATH), MAX_PATH);
491             strWndDir.ReleaseBuffer();
492
493             strWndDir += _T("\\winhlp32.exe");
494
495             // This retrieves cursor #106 from winhlp32.exe, which is a hand pointer
496             HMODULE hModule = LoadLibrary(strWndDir);
497             if (hModule)
498             {
499                 HCURSOR hHandCursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106));
500                 if (hHandCursor)
501                     m_hLinkCursor = CopyCursor(hHandCursor);
502                 FreeLibrary(hModule);
503             }
504         }
505     }
506 }
507
508 ///////////////////////////////////////////////////////////////////////////////
509 // GetRegKey
510 LONG CXHyperLink::GetRegKey(HKEY key, LPCTSTR subkey, LPTSTR retdata)
511 {
512     HKEY hkey;
513     LONG retval = RegOpenKeyEx(key, subkey, 0, KEY_QUERY_VALUE, &hkey);
514
515     if (retval == ERROR_SUCCESS)
516     {
517         long datasize = MAX_PATH;
518         TCHAR data[MAX_PATH];
519         RegQueryValue(hkey, NULL, data, &datasize);
520         _tcscpy(retdata, data);
521         RegCloseKey(hkey);
522     }
523
524     return retval;
525 }
526
527 ///////////////////////////////////////////////////////////////////////////////
528 // ReportError
529 void CXHyperLink::ReportError(int nError)
530 {
531 #ifdef XHYPERLINK_REPORT_ERROR
532
533     CString str;
534     switch (nError)
535     {
536         case 0:                         str = "The operating system is out\nof memory or resources."; break;
537         case SE_ERR_PNF:                str = "The specified path was not found."; break;
538         case SE_ERR_FNF:                str = "The specified file was not found."; break;
539         case ERROR_BAD_FORMAT:          str = "The .EXE file is invalid\n(non-Win32 .EXE or error in .EXE image)."; break;
540         case SE_ERR_ACCESSDENIED:       str = "The operating system denied\naccess to the specified file."; break;
541         case SE_ERR_ASSOCINCOMPLETE:    str = "The filename association is\nincomplete or invalid."; break;
542         case SE_ERR_DDEBUSY:            str = "The DDE transaction could not\nbe completed because other DDE transactions\nwere being processed."; break;
543         case SE_ERR_DDEFAIL:            str = "The DDE transaction failed."; break;
544         case SE_ERR_DDETIMEOUT:         str = "The DDE transaction could not\nbe completed because the request timed out."; break;
545         case SE_ERR_DLLNOTFOUND:        str = "The specified dynamic-link library was not found."; break;
546         case SE_ERR_NOASSOC:            str = "There is no application associated\nwith the given filename extension."; break;
547         case SE_ERR_OOM:                str = "There was not enough memory to complete the operation."; break;
548         case SE_ERR_SHARE:              str = "A sharing violation occurred. ";
549         default:                        str.Format(_T("Unknown Error (%d) occurred."), nError); break;
550     }
551     str = "Unable to open hyperlink:\n\n" + str;
552     AfxMessageBox(str, MB_ICONEXCLAMATION | MB_OK);
553
554 #else
555
556     UNUSED_ALWAYS(nError);
557
558 #endif  // XHYPERLINK_REPORT_ERROR
559 }
560
561 ///////////////////////////////////////////////////////////////////////////////
562 // NotifyParent
563 void CXHyperLink::NotifyParent()
564 {
565     if (m_bNotifyParent)
566     {
567         CWnd *pParent = GetParent();
568         if (pParent && ::IsWindow(pParent->m_hWnd))
569         {
570             // wParam will contain control id
571             pParent->SendMessage(WM_XHYPERLINK_CLICKED, GetDlgCtrlID());
572         }
573     }
574 }
575
576 ///////////////////////////////////////////////////////////////////////////////
577 // GotoURL
578 HINSTANCE CXHyperLink::GotoURL(LPCTSTR url, int showcmd, BOOL bAlwaysOpenNew /*= FALSE*/)
579 {
580     // if no url then this is not an internet link
581     if (!url || url[0] == _T('\0'))
582         return (HINSTANCE) HINSTANCE_ERROR + 1;
583
584     TCHAR key[MAX_PATH*2];
585
586     // First try ShellExecute()
587     TCHAR *verb = _T("open");
588     if (bAlwaysOpenNew)
589         verb = _T("new");
590     HINSTANCE result = ShellExecute(NULL, verb, url, NULL,NULL, showcmd);
591
592     // If it failed, get the .htm regkey and lookup the program
593     if ((UINT)result <= HINSTANCE_ERROR)
594     {
595         if (GetRegKey(HKEY_CLASSES_ROOT, _T(".htm"), key) == ERROR_SUCCESS)
596         {
597             _tcscat(key, _T("\\shell\\open\\command"));
598
599             if (GetRegKey(HKEY_CLASSES_ROOT,key,key) == ERROR_SUCCESS)
600             {
601                 TCHAR *pos;
602                 pos = _tcsstr(key, _T("\"%1\""));
603                 if (pos == NULL)
604                 {                   // No quotes found
605                     pos = _tcsstr(key, _T("%1"));   // Check for %1, without quotes
606                     if (pos == NULL)                // No parameter at all...
607                         pos = key + _tcslen(key)-1;
608                     else
609                         *pos = _T('\0');                // Remove the parameter
610                 }
611                 else
612                 {
613                     *pos = _T('\0');                    // Remove the parameter
614                 }
615
616                 _tcscat(pos, _T(" "));
617                 _tcscat(pos, url);
618
619                 USES_CONVERSION;
620                 result = (HINSTANCE) WinExec(T2A(key),showcmd);
621             }
622         }
623     }
624
625     return result;
626 }
Note: See TracBrowser for help on using the browser.