// ****************************************************************************
// OWL Extensions (OWLEXT) Class Library
// Copyright (C) 1998 by Dieter Windau
// All rights reserved
//
// HLinkCtl.cpp: implementation file
// Version:      1.0
// Date:         17.06.1998
// Author:       Dieter Windau
//
// THLinkCtrl is a freeware OWL class that supports Internet Hyperlinks from
// standard Windows applications just like they are displayed in Web browsers.
//
// You are free to use/modify this code but leave this header intact.
// May not be sold for profit.
//
// Tested with Borland C++ 5.02, OWL 5.02 under Windows NT 4.0 SP3 but I think
// the class would work with Windows 95 too.
// This file is provided "as is" with no expressed or implied warranty.
// Use at your own risk.
//
// This code is based on MFC class CHLinkCtrl by PJ Naughter
// Very special thanks to PJ Naughter:
//   EMail: pjn@indigo.ie
//   Web:   http://indigo.ie/~pjn
//
// Please send me bug reports, bug fixes, enhancements, requests, etc., and
// I'll try to keep this in next versions:
//   EMail: dieter.windau@usa.net
//   Web:   http://www.members.aol.com/SoftEngage
// ****************************************************************************
#include <owlext\pch.h>
#pragma hdrstop
 
#include <owlext/hlinkctl.h>
 
#ifndef HLINK_NOOLE
#define INITGUID
#endif
 
#ifndef HLINK_NOOLE
#include <initguid.h>
#endif
 
#include <winnetwk.h>
#include <winnls.h>
#include <shlobj.h>
 
#include <algorithm>
 
#ifndef HLINK_NOOLE
#include <intshcut.h>
#endif
 
#include <owlext/hlinkctl.rh>
 
namespace OwlExt {
 
using namespace owl;
 
// private function
TModule* FindResourceModule(TWindow* parent, TModule* module, TResId resId, LPCTSTR type);
 
// ***************************** THLinkCtrl ***********************************
DEFINE_RESPONSE_TABLE1(THLinkCtrl, TEdit)
EV_WM_CONTEXTMENU,
EV_WM_SETCURSOR,
EV_WM_ERASEBKGND,
EV_WM_PAINT,
EV_WM_LBUTTONDOWN,
EV_WM_SETFOCUS,
EV_WM_MOUSEMOVE,
EV_COMMAND(ID_POPUP_COPYSHORTCUT, EvCopyShortcut),
EV_COMMAND(ID_POPUP_PROPERTIES, EvProperties),
EV_COMMAND(ID_POPUP_OPEN, EvOpen),
#ifndef HLINK_NOOLE
EV_COMMAND(ID_POPUP_ADDTOFAVORITES, EvAddToFavorites),
EV_COMMAND(ID_POPUP_ADDTODESKTOP, EvAddToDesktop),
#endif
END_RESPONSE_TABLE;
 
THLinkCtrl::THLinkCtrl(TWindow* parent, int Id, LPCTSTR text, int x,
             int y, int w, int h, uint textLimit, bool multiline, TModule* module)
             :
TEdit(parent, Id, text, x, y, w, h, textLimit, multiline, module)
{
  Init();
}
 
THLinkCtrl::THLinkCtrl(TWindow* parent, int resourceId, uint textLimit,
             TModule* module)
             :
TEdit(parent, resourceId, textLimit, module)
{
  Init();
}
 
THLinkCtrl::~THLinkCtrl()
{
}
 
void THLinkCtrl::Init()
{
  m_Color = RGB(0, 0, 255);
  m_VisitedColor = RGB(128, 0, 128);
  m_HighlightColor = RGB(255, 0, 0);
 
  m_bShrinkToFit = true;
  m_bUseHighlight = true;
  m_bUseShadow = true;
  m_State = ST_NOT_VISITED;
  m_OldState = ST_NOT_VISITED;
  m_bUnderline = true;
  m_bShowingContext = false;
  m_bShowPopupMenu = true;
 
  // Load up the cursors
  m_hLinkCursor = (FindResourceModule(Parent,GetModule(),IDC_HLINK,RT_CURSOR))->LoadCursor(IDC_HLINK);
  m_hArrowCursor = ::LoadCursor(0, TResId(IDC_ARROW));
 
  // Control should be created read only
  CHECK(GetStyle() & ES_READONLY);
 
  //  Control should not be in the tab order
  CHECK(!(GetStyle() & WS_TABSTOP));
}
 
void THLinkCtrl::ShrinkToFitEditBox()
{
  TWindow* pParent = GetParentO();
  if (pParent == 0)
    return;
 
  TClientDC dc(GetHandle());
  const auto TextSize = dc.GetTextExtentPoint32(GetHyperLinkDescription());
  TRect ControlRect = GetWindowRect();
  pParent->ScreenToClient(ControlRect.TopLeft());
  pParent->ScreenToClient(ControlRect.BottomRight());
 
  int width = std::min(ControlRect.Size().cx, TextSize.cx);
  if (ControlRect.Size().cx != width){
    // we need to Shink the edit box
    if (GetStyle() & ES_RIGHT){
      // keep everything the same except the width. Set that to the width of text
      MoveWindow(ControlRect.right-width,
        ControlRect.top, width, ControlRect.Height());
    }
    else if (GetStyle() & ES_CENTER){
      MoveWindow(ControlRect.left+(ControlRect.Width()-width)/2,
        ControlRect.top, width, ControlRect.Height());
    }
    else
    {
      MoveWindow(ControlRect.left,
        ControlRect.top, width, ControlRect.Height());
    }
  }
}
 
void THLinkCtrl::SetHyperLink(const owl::tstring& sActualLink)
{
  SetActualHyperLink(sActualLink);
 
  // if the edit field has no caption, set the description with the hyperlink
  //
  owl::tstring str = GetHyperLinkDescription();
  if (_tcslen(str.c_str()) == 0)
    SetHyperLinkDescription(sActualLink);
 
  if (m_bShrinkToFit)
    ShrinkToFitEditBox();
}
 
void THLinkCtrl::SetHyperLink(const owl::tstring& sActualLink,
                const owl::tstring& sLinkDescription, bool bShrinkToFit)
{
  m_bShrinkToFit = bShrinkToFit;
  SetActualHyperLink(sActualLink);
  SetHyperLinkDescription(sLinkDescription);
  if (m_bShrinkToFit)
    ShrinkToFitEditBox();
}
 
void THLinkCtrl::SetActualHyperLink(const owl::tstring& sActualLink)
{
  m_sActualLink = sActualLink;
}
 
void THLinkCtrl::SetHyperLinkDescription(const owl::tstring& sLinkDescription)
{
  // if empty do nothing
  if (_tcslen(sLinkDescription.c_str()) != 0)
  {
    //  set the text on this control
    SetWindowText(sLinkDescription.c_str());
  }
}
 
owl::tstring THLinkCtrl::GetHyperLinkDescription() const
{
  int len = GetWindowTextLength();
  if (len > 0)
  {
    _TCHAR* str = new _TCHAR[len+1];
    GetWindowText(str, len+1);
    owl::tstring sDescription(str);
    delete[] str;
    return sDescription;
  }
  else
  {
    owl::tstring str(_T(""));
    return str;
  }
}
 
bool THLinkCtrl::EvSetCursor(THandle /*hWndCursor*/, uint /*hitTest*/,
               uint /*mouseMsg*/)
{
  if (m_bShowingContext)
    ::SetCursor(m_hArrowCursor);
  else
    ::SetCursor(m_hLinkCursor);
  return true;
}
 
void THLinkCtrl::EvLButtonDown(uint /*modKeys*/, const TPoint& /*point*/)
{
  PostMessage(WM_COMMAND, ID_POPUP_OPEN);
}
 
void THLinkCtrl::EvSetFocus(HWND /*hWndLostFocus*/)
{
  // TEdit::EvSetFocus(pOldWnd);  // Eat the message
}
 
void THLinkCtrl::EvOpen()
{
  if (Open())
    m_State = ST_VISITED;
}
 
void THLinkCtrl::SetLinkColor(const TColor& color)
{
  m_Color = color;
  UpdateWindow();
}
 
void THLinkCtrl::SetVisitedLinkColor(const TColor& color)
{
  m_VisitedColor = color;
  UpdateWindow();
}
 
void THLinkCtrl::SetHighlightLinkColor(const TColor& color)
{
  m_HighlightColor = color;
  UpdateWindow();
}
 
void THLinkCtrl::SetUseUnderlinedFont(bool bUnderline)
{
  m_bUnderline = bUnderline;
  UpdateWindow();
}
 
void THLinkCtrl::SetUseShadow(bool bUseShadow)
{
  m_bUseShadow = bUseShadow;
  UpdateWindow();
}
 
void THLinkCtrl::EvMouseMove(UINT modKeys, const TPoint& point)
{
  if (!m_bUseHighlight)
    return;
 
  TRect rc;
  GetClientRect(rc);
  if (rc.Contains(point)){
    if (m_State != ST_HIGHLIGHTED){
      SetCapture();
      HighLight(true);
    }
  }
  else{
    if (m_State == ST_HIGHLIGHTED){
      HighLight(false);
      ReleaseCapture();
    }
  }
  TEdit::EvMouseMove(modKeys, point);
}
 
void THLinkCtrl::HighLight(bool state)
{
  if (state){
    if (m_State != ST_HIGHLIGHTED){
      m_OldState = m_State;
      m_State = ST_HIGHLIGHTED;
      Invalidate();
    }
  }
  else{
    if (m_State == ST_HIGHLIGHTED){
      m_State = m_OldState;
      Invalidate();
    }
  }
}
 
bool THLinkCtrl::EvEraseBkgnd(HDC hdc)
{
  bool bSuccess = TEdit::EvEraseBkgnd(hdc);
 
  TRect r = GetClientRect();
  TDC dc(hdc);
 
  if ((m_State == ST_HIGHLIGHTED) && m_bUseShadow){
    // draw the drop shadow effect if highlighted
    TFont wFont(GetWindowFont());
    LOGFONT lf = wFont.GetObject();
    if (m_bUnderline)
      lf.lfUnderline = true;
    TFont font(lf);
    dc.SelectObject(font);
    int nOldMode = dc.SetBkMode(TRANSPARENT);
    _TCHAR sText[255];
    GetWindowText(sText, 255);
    TColor OldColor = dc.SetTextColor(GetSysColor(COLOR_3DSHADOW));
    r.Offset(2,2);
    uint16 format = DT_VCENTER;
    if (GetStyle() & ES_RIGHT)
    {
      format |= DT_RIGHT;
      r.right-=2;
    }
    else if (GetStyle() & ES_CENTER)
      format |= DT_CENTER;
    else
      format |= DT_LEFT;
    dc.DrawText(sText, static_cast<int>(_tcslen(sText)), r, format);
    dc.SetTextColor(OldColor);
    dc.SetBkMode(nOldMode);
    dc.RestoreFont();
  }
  else
  {
    dc.FillRect(r, TBrush(GetSysColor(COLOR_3DFACE)));
  }
 
  return bSuccess;
}
 
void THLinkCtrl::EvPaint()
{
  TPaintDC dc(GetHandle());
 
  // Make the font underline just like a URL
  TFont wFont = GetWindowFont();
  LOGFONT lf = wFont.GetObject();
  lf.lfUnderline = m_bUnderline;
  TFont font(lf);
  dc.SelectObject(font);
  int nOldMode = dc.SetBkMode(TRANSPARENT);
 
  TColor OldColor;
  switch (m_State){
    case ST_NOT_VISITED:
      OldColor = dc.SetTextColor(m_Color);
      break;
    case ST_VISITED:
      OldColor = dc.SetTextColor(m_VisitedColor);
      break;
    case ST_HIGHLIGHTED:
      OldColor = dc.SetTextColor(m_HighlightColor);
      break;
  }
 
  TRect r = GetClientRect();
  uint16 format = DT_VCENTER;
  if (GetStyle() & ES_RIGHT) {
    format |= DT_RIGHT;
    r.right-=2;
  }
  else if (GetStyle() & ES_CENTER)
    format |= DT_CENTER;
  else
    format |= DT_LEFT;
 
  dc.DrawText(GetHyperLinkDescription(),
    static_cast<int>(GetHyperLinkDescription().size()), r, format);
 
  dc.SetTextColor(OldColor);
  dc.SetBkMode(nOldMode);
  dc.RestoreFont();
}
 
void THLinkCtrl::EvContextMenu(HWND /*childHwnd*/, int x, int y)
{
  if (m_bShowPopupMenu == false)
    return;
 
  HighLight(false);
  ReleaseCapture();
 
  if (x == -1 && y == -1)
  {
    //  keystroke invocation
    TRect rect = GetClientRect();
    ClientToScreen(rect.TopLeft());
    x = rect.left + 5;
    y = rect.top + 5;
  }
 
  TMenu menu(*FindResourceModule(Parent,GetModule(),IDM_HLINK_POPUP,RT_MENU),
    IDM_HLINK_POPUP);
 
  HMENU pPopup = menu.GetSubMenu(0);
 
  PRECONDITION(pPopup);
  m_bShowingContext = true;
 
  ::TrackPopupMenu(pPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, GetHandle(), 0);
 
  m_bShowingContext = false;
}
 
void THLinkCtrl::EvCopyShortcut()
{
  if (::OpenClipboard(GetHandle()))
  {
    int nBytes = static_cast<int>(sizeof(TCHAR) * (m_sActualLink.size() + 1));
    HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, nBytes);
    TCHAR* pData = (TCHAR*) GlobalLock(hMem);
    _tcscpy(pData, (LPCTSTR) m_sActualLink.c_str());
    GlobalUnlock(hMem);
    SetClipboardData(CF_TEXT, hMem);
    CloseClipboard();
  }
}
 
void THLinkCtrl::EvProperties()
{
  ShowProperties();
}
 
void THLinkCtrl::ShowProperties() const
{
  THLinkSheet propSheet(GetParentO());
  propSheet.SetBuddy(this);
  propSheet.Execute();
}
 
bool THLinkCtrl::Open() const
{
  // Give the user some feedback
  TWaitCursor cursor;
 
#ifndef HLINK_NOOLE
  // First try to open using IUniformResourceLocator
  bool bSuccess = OpenUsingCom();
 
  // As a last resort try ShellExecuting the URL, may
  // even work on Navigator!
  if (!bSuccess)
    bSuccess = OpenUsingShellExecute();
#else
  bool bSuccess = OpenUsingShellExecute();
#endif
  return bSuccess;
}
 
#ifndef HLINK_NOOLE
bool THLinkCtrl::OpenUsingCom() const
{
  // Get the URL Com interface
  IUniformResourceLocator* pURL;
  HRESULT hres = CoCreateInstance(CLSID_InternetShortcut, 0,
    CLSCTX_INPROC_SERVER, IID_IUniformResourceLocator, (void**)&pURL);
  if (!SUCCEEDED(hres)){
    TRACE("Failed to get the IUniformResourceLocator interface\n");
    return false;
  }
 
  hres = pURL->SetURL(m_sActualLink.c_str(), IURL_SETURL_FL_GUESS_PROTOCOL);
  if (!SUCCEEDED(hres)){
    TRACE("Failed in call to SetURL\n");
    pURL->Release();
    return false;
  }
 
  //  Open the URL by calling InvokeCommand
  URLINVOKECOMMANDINFO ivci;
  ivci.dwcbSize = sizeof(URLINVOKECOMMANDINFO);
  ivci.dwFlags = IURL_INVOKECOMMAND_FL_ALLOW_UI;
  ivci.hwndParent = GetParentH();
  ivci.pcszVerb = _T("open");
  hres = pURL->InvokeCommand(&ivci);
  if (!SUCCEEDED(hres)){
    TRACE("Failed to invoke URL using InvokeCommand\n");
    pURL->Release();
    return false;
  }
 
  //  Release the pointer to IUniformResourceLocator.
  pURL->Release();
  return true;
}
#endif
 
bool THLinkCtrl::OpenUsingShellExecute() const
{
  HINSTANCE hRun = ShellExecute(GetParentH(), _T("open"),
    m_sActualLink.c_str(), 0, 0, SW_SHOW);
  if (reinterpret_cast<INT_PTR>(hRun) <= 32){
    TRACE("Failed to invoke URL using ShellExecute\n");
    return false;
  }
  return true;
}
 
#ifndef HLINK_NOOLE
bool THLinkCtrl::AddToSpecialFolder(int nFolder) const
{
  // Give the user some feedback
  TWaitCursor cursor;
 
  // Get the shell's allocator.
  IMalloc* pMalloc;
  if (!SUCCEEDED(SHGetMalloc(&pMalloc))){
    TRACE("Failed to get the shell's IMalloc interface\n");
    return false;
  }
 
  // Get the location of the special Folder required
  LPITEMIDLIST pidlFolder;
  HRESULT hres = SHGetSpecialFolderLocation(0, nFolder, &pidlFolder);
  if (!SUCCEEDED(hres)){
    TRACE("Failed in call to SHGetSpecialFolderLocation\n");
    pMalloc->Release();
    return false;
  }
 
  // convert the PIDL to a file system name and
  // add an extension of URL to create an Internet
  // Shortcut file
  TCHAR sFolder[_MAX_PATH];
  if (!SHGetPathFromIDList(pidlFolder, sFolder)){
    TRACE("Failed in call to SHGetPathFromIDList");
    pMalloc->Release();
    return false;
  }
  TCHAR sShortcutFile[_MAX_PATH];
 
  owl::tstring linkDescription = GetHyperLinkDescription();
  _tmakepath(sShortcutFile, 0, sFolder, linkDescription.c_str(), _T("URL"));
 
  // Free the pidl
  pMalloc->Free(pidlFolder);
 
  // Do the actual saving
  bool bSuccess = Save(sShortcutFile);
 
  // Release the pointer to IMalloc
  pMalloc->Release();
 
  return bSuccess;
}
#endif
 
#ifndef HLINK_NOOLE
void THLinkCtrl::EvAddToFavorites()
{
  AddToSpecialFolder(CSIDL_FAVORITES);
}
#endif
 
#ifndef HLINK_NOOLE
void THLinkCtrl::EvAddToDesktop()
{
  AddToSpecialFolder(CSIDL_DESKTOP);
}
#endif
 
#ifndef HLINK_NOOLE
bool THLinkCtrl::Save(const owl::tstring& sFilename) const
{
  // Get the URL Com interface
  IUniformResourceLocator* pURL;
  HRESULT hres = CoCreateInstance(CLSID_InternetShortcut, 0, CLSCTX_INPROC_SERVER, IID_IUniformResourceLocator, (void**) &pURL);
  if (!SUCCEEDED(hres))
  {
    TRACE("Failed to get the IUniformResourceLocator interface\n");
    return false;
  }
 
  hres = pURL->SetURL(m_sActualLink.c_str(), IURL_SETURL_FL_GUESS_PROTOCOL);
  if (!SUCCEEDED(hres))
  {
    TRACE("Failed in call to SetURL\n");
    pURL->Release();
    return false;
  }
 
  // Get the IPersistFile interface for
  // saving the shortcut in persistent storage.
  IPersistFile* ppf;
  hres = pURL->QueryInterface(IID_IPersistFile, (void **)&ppf);
  if (!SUCCEEDED(hres))
  {
    TRACE("Failed to get the IPersistFile interface\n");
    pURL->Release();
    return false;
  }
 
  // Save the shortcut via the IPersistFile::Save member function.
#ifndef _UNICODE
  wchar_t wsz[_MAX_PATH];
  MultiByteToWideChar(CP_ACP, 0, sFilename.c_str(), -1, wsz, _MAX_PATH);
  hres = ppf->Save(wsz, true);
#else
  hres = ppf->Save(sFilename.c_str(), true);
#endif
  if (!SUCCEEDED(hres))
  {
    TRACE("IPersistFile::Save failed!\n");
    ppf->Release();
    pURL->Release();
    return false;
  }
 
  // Release the pointer to IPersistFile.
  ppf->Release();
 
  // Release the pointer to IUniformResourceLocator.
  pURL->Release();
 
  return true;
}
#endif
 
THLinkPage::THLinkPage(TPropertySheet* parent)
:
TPropertyPage(parent, IDD_HLINK_PROPERTIES,0,0,
        FindResourceModule(parent,0,IDD_HLINK_PROPERTIES,RT_DIALOG))
{
}
 
THLinkPage::~THLinkPage()
{
}
 
void THLinkPage::SetupWindow()
{
  TPropertyPage::SetupWindow();
 
  PRECONDITION(m_pBuddy);
  ::SetWindowText(::GetDlgItem(GetHandle(), IDC_NAME),
    m_pBuddy->GetHyperLinkDescription().c_str());
  ::SetWindowText(::GetDlgItem(GetHandle(), IDC_URL),
    m_pBuddy->GetActualHyperLink().c_str());
}
 
THLinkSheet::THLinkSheet(TWindow* parent)
:
TPropertySheet(parent, 0)
{
  m_page1 = new THLinkPage(this);
  SetCaption(FindResourceModule(Parent,GetModule(),IDS_HLINK_PROPERTIES,
    RT_STRING)->LoadString(IDS_HLINK_PROPERTIES).c_str());
  ModifyStyle(0,PSH_NOAPPLYNOW);
}
 
THLinkSheet::~THLinkSheet()
{
}
} // OwlExt namespace
 
//==============================================================================

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: m_pBuddy.

V519 The 'm_bShowingContext' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 422, 426.

V601 The 'true' value is implicitly cast to the integer type.

V805 Decreased performance. It is inefficient to identify an empty string by using 'strlen(str) == 0' construct. A more efficient way is to check: str[0] == '\0'.

V805 Decreased performance. It is inefficient to identify an empty string by using 'strlen(str) != 0' construct. A more efficient way is to check: str[0] != '\0'.

V806 Decreased performance. The expression of strlen(MyStr.c_str()) kind can be rewritten as MyStr.length().

V806 Decreased performance. The expression of strlen(MyStr.c_str()) kind can be rewritten as MyStr.length().