// DirBrowse.cpp - Version 1.0
//
// Author:  Lars Werner
//          lars@werner.no
//
// Description:
//     DirBrowse.cpp wraps the SHBrowseForFolder() and creates hyperlink text
//     shortcuts on the right of the browserwindow.
//
// History
//     Version 1.0 - 2006 March 13
//     - Initial public release
//
// This software is released into the public domain.  You are free to use it 
// in any way you like.
//
// This software is provided "as is" with no expressed or implied warranty.  
// I accept no liability for any damage or loss of business that this software 
// may cause.
//
///////////////////////////////////////////////////////////////////////////////

//Comment the stdafx.h to use this in plain win32 
#include "stdafx.h"

#ifndef __AFX_H__
#include <windows.h>
#include <crtdbg.h>
#include <tchar.h>
#include <stdio.h>
#include <shellapi.h>
#include <Commctrl.h>
#pragma comment(lib, "Comctl32.lib") //For mousetrack support (Win95 not supported!)
#endif

//Headers for the 
#include "Shlobj.h"
#include "io.h"

//Header for this class
#include "DirBrowse.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//*******************************************************************************************
//Global variable that handles the browserwindow between differnt static/global functions
CDirBrowse *pMe=NULL;

//*******************************************************************************************
//Creator of the the class

CDirBrowse::CDirBrowse(HWND hParent, HINSTANCE hInstance)
{
	//Store the parent
	Parent = hParent;
	pMe = this;

	//Store the instance
	hInst = hInstance;
		
	//Create a hyperlink class - based on the static
	WNDCLASS hc;
	hc.style = 0;
	hc.lpfnWndProc = (WNDPROC)CDirBrowse::HyperLinkWnd;
	hc.cbClsExtra = 0;
	hc.cbWndExtra = NULL;
	hc.hInstance = NULL;
	hc.hIcon = NULL;
	hc.hCursor = NULL;
	hc.hbrBackground = NULL;
	hc.lpszMenuName = NULL;
	hc.lpszClassName = STATIC_HYPERTEXT_WINDOW_CLASS;
	RegisterClass(&hc);

	//Now, try to find a hand icon...
	handcursor = ::LoadCursor(NULL, MAKEINTRESOURCE(32649)); //32649 == IDC_HAND
	if (handcursor == NULL)
	{
		TCHAR szWindowsDir[MAX_PATH];
		GetWindowsDirectory(szWindowsDir ,MAX_PATH);
		strcat(szWindowsDir,"\\winhlp32.exe");
		hModule = LoadLibrary(szWindowsDir);		
		if (hModule) 
			handcursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106));
	}

	//The top/left position of the window
	PositionX=-1;
	PositionY=-1;

	//Zero at startup
	DirBrowser=NULL;

	//Check the message thingy
	MessageIsOdd=FALSE;
	if(BROWSE_MESSAGE % 2 != 0)
	{
		MessageIsOdd=TRUE;
	}

	//Clear the paths (just for cleaner look :))
	for(int i=0;i<BROWSE_MAXENTRIES;i++)
		ZeroMemory(Path[i], sizeof(Path[i]));

	//Zero the items and read the registry to get the information
	nItems=0;
	this->ReadConfig();

	//Set variables that track if the mouse
	Hover=FALSE;
	Tracking=FALSE;
}

//*******************************************************************************************
//Destructor
CDirBrowse::~CDirBrowse()
{
	//Free the dll if we have loaded it...
	if (hModule != NULL)
		FreeLibrary(hModule);
}

//*******************************************************************************************
//Type: Public
//Desc: Shows the BrowseForFolder window modified with your variables
BOOL CDirBrowse::Show(char* szInitDir)
{
	TCHAR dname[MAX_PATH*2];
	IMalloc *imalloc; SHGetMalloc(&imalloc);
	BROWSEINFO bi; ZeroMemory(&bi,sizeof(bi));
	bi.hwndOwner=Parent;
	bi.pszDisplayName=dname;
	bi.lpszTitle = NULL;

	//Set the initalpath (if we have one :))
	bi.lParam    = (LPARAM) szInitDir;

	//Set the flags needed
	bi.ulFlags = BIF_RETURNONLYFSDIRS;
	
	//We have to use our own callback to handle everything
	bi.lpfn = BrowseCallbackProc;

	//Do the action
	ITEMIDLIST *pidl = SHBrowseForFolder(&bi);

	//Maybe we should do a little more error handling here, but it will do for now :)
	imalloc->Free(pidl);
	imalloc->Release();

	//Based on the pidl, we return the value
	if(pidl==NULL)
		return FALSE;
	else
		return TRUE;
}

//*******************************************************************************************
//Type: Public
//Desc: Returns the selected dir (after OK)
char* CDirBrowse::GetSelectedDir()
{
	return (char*)&szSelectedDir;
}

//*******************************************************************************************
//Type: Private - Static
//Desc: BrowseForFolder callback
int __stdcall CDirBrowse::BrowseCallbackProc(HWND  hwnd,UINT  uMsg,LPARAM  lParam,LPARAM  lpData)
{
	//Initialization callback message
	if(uMsg==BFFM_INITIALIZED)
	{
		//Store the window
		pMe->DirBrowser=hwnd;

		//Rectangles for getting the positions
		RECT ListViewRect,Dialog,Button;

		//Move the window to the last position or to center
		if(pMe->PositionX==-1&&pMe->PositionY==-1)
		{
			//Resize the window by it's variable
			RECT DesktopRect;
			HWND Desktop=GetDesktopWindow();
			GetWindowRect(Desktop, &DesktopRect);

			MoveWindow(hwnd, (DesktopRect.right/2)-(BROWSE_WIDTH/2) ,(DesktopRect.bottom/2)-(BROWSE_HEIGHT/2), BROWSE_WIDTH, BROWSE_HEIGHT, TRUE);
		}
		else
			MoveWindow(hwnd, pMe->PositionX ,pMe->PositionY, BROWSE_WIDTH, BROWSE_HEIGHT, TRUE);
		
		//Find the listview
		HWND ListView = FindWindowEx(hwnd,NULL,"SysTreeView32",NULL);

		//Find the buttons and get the dimensions
		HWND OKButton = FindWindowEx(hwnd, NULL, "Button", NULL);
		HWND CancelButton = FindWindowEx(hwnd, OKButton, "Button", NULL);
		GetClientRect(OKButton,&Button);

		//Gets the dimentions of the windows
		GetClientRect(hwnd,&Dialog);

		//Sets the listview controls dimentions
		SetWindowPos( ListView,0,BROWSE_BORDER_VERT , BROWSE_BORDER_HORI,(int)(BROWSE_WIDTH*BROWSE_LISTVIEW_SIZE),(Dialog.bottom-Dialog.top)-(Button.bottom-Button.top)-BROWSE_BORDER_HORI*2,0);

		//Set the style to show selection always :)
		::SetWindowLong(ListView, GWL_STYLE, ::GetWindowLong(ListView, GWL_STYLE) | TVS_SHOWSELALWAYS);

		//Get the TreeView rect
		GetClientRect(ListView,&ListViewRect);		
		pMe->RealListView = ListViewRect;

		//Set the inital directory (must be done AFTER the GetClientRect for the listview
		::SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);

		//Set the buttons
		SetWindowPos( OKButton, 0, ListViewRect.right-(Button.right-Button.left)*2, Dialog.bottom-BROWSE_BORDER_HORI-(Button.bottom-Button.top), (Button.right-Button.left), (Button.bottom-Button.top), 0);
		SetWindowPos( CancelButton, 0, ListViewRect.right-(Button.right-Button.left)+BROWSE_BORDER_VERT*2, Dialog.bottom-BROWSE_BORDER_HORI-(Button.bottom-Button.top), (Button.right-Button.left), (Button.bottom-Button.top), 0);

		//Find the static and hide it!
		HWND Static = FindWindowEx(hwnd,NULL,"STATIC",NULL);;
		//ShowWindow(Static, SW_HIDE);
		SendMessage(Static, WM_SETTEXT, (WPARAM)NULL, (LPARAM)BROWSE_TEXT_SHORTCUTS);
		MoveWindow(Static, ListViewRect.right + BROWSE_BORDER_VERT*3, BROWSE_BORDER_HORI, Dialog.right-ListViewRect.right-BROWSE_BORDER_VERT*4, BROWSE_FONTSIZE, TRUE);
		SetWindowLong(Static,GWL_STYLE, SS_CENTER|WS_BORDER|WS_VISIBLE);

		//This is the first selector that is shown
		HWND HyperLinkSelect=CreateWindowEx( 0,STATIC_HYPERTEXT_WINDOW_CLASS,"-> This is not set",SS_NOTIFY|WS_CHILD|WS_VISIBLE/*|WS_BORDER*/,ListViewRect.right + BROWSE_BORDER_VERT*3,((0)*(BROWSE_FONTSIZE/3+BROWSE_BORDER_HORI))+BROWSE_BORDER_HORI*2+BROWSE_FONTSIZE, 15,BROWSE_FONTSIZE,hwnd,0,pMe->hInst,NULL);
		SetWindowLong(HyperLinkSelect, GWL_ID, BROWSE_MESSAGE+(0));
		pMe->AddToolTip(HyperLinkSelect, pMe->hInst);

		//Add all the items that we have
		for(int i=0;i<pMe->nItems;i++)
			pMe->AddItem(i);
	}

	//Selection change message - store the selected directory
	if(uMsg==BFFM_SELCHANGED)
	{
		TCHAR szDir[MAX_PATH*2] = { 0 };

		// fail if non-filesystem
		BOOL bRet = SHGetPathFromIDList((LPITEMIDLIST) lParam, szDir);
		if (bRet)
		{
			//If the folder cannot be "read" then fail
			if (_taccess(szDir, 00) != 0)
			{
				bRet = FALSE;
			}
			else
			{
				SHFILEINFO sfi;
				::SHGetFileInfo((LPCTSTR)lParam, 0, &sfi, sizeof(sfi), 
						SHGFI_PIDL | SHGFI_ATTRIBUTES);

				// fail if pidl is a link
				if (sfi.dwAttributes & SFGAO_LINK)
					bRet = FALSE;
			}
		}

		// if invalid selection, disable the OK button
		if (!bRet)
		{
			::EnableWindow(GetDlgItem(hwnd, IDOK), FALSE);
			strcpy(pMe->szSelectedDir,"");
		}
		else
			strcpy(pMe->szSelectedDir, szDir);
    }
	
	return 0;
}

//*******************************************************************************************
//Type: Protected - Static
//Desc: HyperlinkClass callbackfunction
int CDirBrowse::HyperLinkWnd(HWND hwnd,WORD wMsg,WPARAM wParam,LPARAM lParam)
{
	//Get the ID
	LONG nID=GetWindowLong(hwnd, GWL_ID);	//The message for this entry
	BOOL MsgIsOdd=FALSE;					//This handles each button

	//Quick hack to make the info show correct :)
	if(pMe->MessageIsOdd == FALSE)
	{
		if(nID % 2 == 0)
			MsgIsOdd = FALSE;
		else
			MsgIsOdd = TRUE;
	}
	//They have to be split, since a else here makes the release version go nuts :)
	if(pMe->MessageIsOdd == TRUE)
	{
		if(nID % 2 == 0)
			MsgIsOdd = TRUE;
		else
			MsgIsOdd = FALSE;
	}

	//Ids that we use to combineS
	LONG ID = (nID - BROWSE_MESSAGE) / 2 ;			//This is the real array
	LONG RealID = ID;
	if(MsgIsOdd==FALSE)
		ID = (nID - BROWSE_MESSAGE) / 3 ;
	if(MsgIsOdd==TRUE)
		RealID = ID / 3;
	//Now check the message that we're gonna handle...
	switch (wMsg)  
	{
	case WM_DESTROY:
	case WM_QUIT:
	case WM_CLOSE:
		pMe->SaveConfig();
		break;
	case WM_LBUTTONDOWN:

		//If we have pressed a directory just select it!
		if(MsgIsOdd==TRUE)
			SendMessage( pMe->DirBrowser, BFFM_SETSELECTION, TRUE, (LONG)pMe->Path[ID] );
		else //Here we will handle to add/change a directory quickpick
		{
			if(strlen(pMe->szSelectedDir)!=0)
			{
				strcpy(pMe->Path[RealID], pMe->szSelectedDir);

				if(RealID==pMe->nItems)
				{
					//Adds a new item to the list. AddItem handles is we have exceeded our BROWSE_MAXENTRIES
					pMe->AddItem();
					pMe->nItems++;
				}

				//Set these variables to false before totally redraw :)
				pMe->Tracking=FALSE;
				pMe->Hover=FALSE;
				::RedrawWindow(pMe->DirBrowser, NULL,NULL, ( RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE  ) );
			}
			else
				MessageBox(pMe->DirBrowser, BROWSE_TEXT_HELP, BROWSE_TEXT_HELP_CAPTION, MB_OK);
		}

		//After a leftclick it seems like the tooltip is destroyed, a quick fix is to create a new
		pMe->AddToolTip(hwnd, pMe->hInst);
		break;
	case WM_PAINT:
		{
			HDC hDC; PAINTSTRUCT ps;
			hDC = ::BeginPaint(hwnd, &ps);

			RECT rect;
			::GetClientRect(hwnd, &rect);

			HFONT font = ::CreateFont( BROWSE_FONTSIZE, //height
										7, //average char width
										0, //angle of escapement
										0, //base-line orientation angle
										FW_NORMAL,	//font weight
										FALSE,		//italic
										TRUE,		//underline
										FALSE,		//strikeout
										ANSI_CHARSET,			//charset identifier
										OUT_DEFAULT_PRECIS,		//ouput precision
										CLIP_DEFAULT_PRECIS,	//clipping precision
										DEFAULT_QUALITY,	//output quality
										DEFAULT_PITCH,			//pitch and family
										BROWSE_FONTNAME);
			
			::SelectObject(hDC, font);
			if(MsgIsOdd==TRUE)
			{
				if(pMe->Hover==FALSE)
					::SetTextColor(hDC, BROWSE_DIRCOLOR);
				else
					::SetTextColor(hDC, BROWSE_DIRCOLORHOVER);
			}
			else
			{
				if(pMe->Hover==FALSE)
					::SetTextColor(hDC, BROWSE_SELECTCOLOR);
				else
					::SetTextColor(hDC, BROWSE_SELECTCOLORHOVER);
			}

			//Set the background to transparent, always...
			::SetBkMode(hDC, TRANSPARENT);

			//Now one of the items
			if(MsgIsOdd==TRUE)
				::DrawText(hDC, pMe->Path[ID], strlen(pMe->Path[ID]), &rect, DT_VCENTER | DT_LEFT | DT_PATH_ELLIPSIS | DT_NOPREFIX);
			else
				::DrawText(hDC, "->", 2, &rect, DT_VCENTER | DT_LEFT);
			
			//Delete the font and end the painting
			::DeleteObject(font);
			::EndPaint(hwnd, &ps);
						
			return TRUE;
		}
		case WM_SETCURSOR:
		{
			if (pMe->handcursor)
				::SetCursor(pMe->handcursor);
			break;
		}
		//Set the tooltip text here... Rememeber that BROWSE_TEXT_HELP_TOOLTIP can't exceed 80 chars by this code!
		case WM_NOTIFY:
		{
			LPNMHDR pnmh = (LPNMHDR) lParam ;
			if (pnmh->code == TTN_NEEDTEXT)
			{
				LPTOOLTIPTEXT lptData = (LPTOOLTIPTEXT) lParam;

				if(MsgIsOdd==TRUE)
					lptData->lpszText=(char*)&pMe->Path[ID];
				else
					strcpy(lptData->szText, BROWSE_TEXT_HELP_TOOLTIP);
			}
			break;
		}
		//Handle hover functions
		case WM_MOUSELEAVE:
			if(pMe->Tracking==TRUE)
			{
				TRACKMOUSEEVENT	tmeTME;

				tmeTME.cbSize = sizeof( TRACKMOUSEEVENT );				// Init Size
				tmeTME.dwFlags = TME_CANCEL;							// Init Flags
				tmeTME.hwndTrack = hwnd;								// Init Tracking Window
				tmeTME.dwHoverTime = 0;									// Init Hover Time

				_TrackMouseEvent( &tmeTME );							// Cancel Tracking
				pMe->Tracking=FALSE;
				pMe->Hover=FALSE;
				::RedrawWindow(hwnd, NULL, NULL, ( RDW_INVALIDATE | RDW_UPDATENOW ) );
			}
			break;
//		case WM_MOUSEHOVER:
//			TRACE("Mouse hoverting...\n");
//			break;
		//Handle hover functions
		case WM_MOUSEMOVE:
		if(pMe->Tracking==FALSE)
		{
			TRACKMOUSEEVENT	tmeTME;

			tmeTME.cbSize = sizeof( TRACKMOUSEEVENT );			// Init Size
			tmeTME.dwFlags = ( TME_LEAVE | TME_HOVER );			// Init Flags
			tmeTME.hwndTrack = hwnd;							// Init Tracking Window
			tmeTME.dwHoverTime = HOVER_DEFAULT;					// Set Hover Time To System Default

			_TrackMouseEvent( &tmeTME );						// Start Tracking

			pMe->Tracking=TRUE;
			pMe->Hover=TRUE;
			::RedrawWindow(hwnd, NULL, NULL, ( RDW_INVALIDATE | RDW_UPDATENOW ) );
		}
			break;
		default:
			DefWindowProc(hwnd, wMsg, wParam, lParam);
	}

	return TRUE;
}

//*******************************************************************************************
//Type: Private
//Desc: Adds a tooltip to any item with a hwnd
void CDirBrowse::AddToolTip(HWND hwndPar, HINSTANCE hInst)
{
	TOOLINFO ti;

	HWND hwndTT = CreateWindowEx(NULL,
	TOOLTIPS_CLASS,
	NULL,
	WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
	CW_USEDEFAULT, CW_USEDEFAULT,
	CW_USEDEFAULT, CW_USEDEFAULT,
	hwndPar,
	NULL, hInst, NULL);

	SetWindowPos(hwndTT, HWND_TOPMOST,
	0, 0, 0, 0,
	SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

	SendMessage(hwndTT, TTM_ACTIVATE, (WPARAM) TRUE, 0);

	ti.cbSize = sizeof(TOOLINFO);
	ti.hwnd = hwndPar;
	ti.hinst = hInst;
	ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND /*| TTF_TRACK*/;
	ti.uId = (UINT) hwndPar;
	ti.lParam = NULL;
	ti.lpszText = LPSTR_TEXTCALLBACK;

	//Add the tooltip to the parent :)
	SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO) &ti);
}

//*******************************************************************************************
//Type: Private
//Desc: Adds a static-hypertext-class to the BrowseForFolder window
void CDirBrowse::AddItem(int nItem)
{
	//Now use this instead
	if(nItem==-1)
		nItem=nItems;
	
	//Rectangles for getting the positions
	RECT ListViewRect,Dialog;
	ListViewRect = RealListView; //We use the stored since the scrollbars would make the client smaller

	//Gets the dimentions of the windows
	GetClientRect(DirBrowser,&Dialog);

	//Find the listview
	HWND ListView = FindWindowEx(DirBrowser,NULL,"SysTreeView32",NULL);

	//Create the control that 
	HWND HyperLink=CreateWindowEx( 0,STATIC_HYPERTEXT_WINDOW_CLASS,"(Dir) Text will be changed after creation!",WS_CHILD|WS_VISIBLE/*|WS_BORDER*/,ListViewRect.right+15+BROWSE_BORDER_VERT*4,((nItem*2)*(BROWSE_FONTSIZE/3+BROWSE_BORDER_HORI))+BROWSE_BORDER_HORI*2+BROWSE_FONTSIZE,Dialog.right - ListViewRect.right-BROWSE_BORDER_VERT*5-15,BROWSE_FONTSIZE,DirBrowser,0,pMe->hInst,NULL);
	SetWindowLong(HyperLink, GWL_ID, BROWSE_MESSAGE+(nItem*2)+1);

	//Create the next selector that can set the next directory (if we are allowed!)
	if(nItem<BROWSE_MAXENTRIES-1)
	{
		HWND HyperLinkSelect=CreateWindowEx( 0,STATIC_HYPERTEXT_WINDOW_CLASS,"->  Text will be changed after creation!",SS_NOTIFY|WS_CHILD|WS_VISIBLE/*|WS_BORDER*/,ListViewRect.right + BROWSE_BORDER_VERT*3,(((nItem*2)+2)*(BROWSE_FONTSIZE/3+BROWSE_BORDER_HORI))+BROWSE_BORDER_HORI*2+BROWSE_FONTSIZE, 15,BROWSE_FONTSIZE,DirBrowser,0,pMe->hInst,NULL);
		SetWindowLong(HyperLinkSelect, GWL_ID, BROWSE_MESSAGE+(nItem*2)+2);
		pMe->AddToolTip(HyperLinkSelect, pMe->hInst);
	}

	//Add tooltip
	pMe->AddToolTip(HyperLink, pMe->hInst);
}

//*******************************************************************************************
//Type: Private
//Desc: Reads the registry for the config based on the BROWSE_REGISTRY_ROOT and BROWSE_REGISTRY_PATH
void CDirBrowse::ReadConfig()
{
	HKEY Key = NULL;
	
	//Open the key, return if it does not exists
	if(RegOpenKeyEx(BROWSE_REGISTRY_ROOT, BROWSE_REGISTRY_PATH, NULL, KEY_ALL_ACCESS, &Key)!=ERROR_SUCCESS)
		return;

	//Variables to store data
    DWORD cbData=sizeof(DWORD);
	DWORD Data;
	
	//Now get the position values
	if(RegQueryValueEx( Key, "PosX", NULL, NULL, (LPBYTE)&Data, &cbData)==ERROR_SUCCESS)
		PositionX=Data;
	if(RegQueryValueEx( Key, "PosY", NULL, NULL, (LPBYTE)&Data, &cbData)==ERROR_SUCCESS)
		PositionY=Data;

	//Do a infinite loop until we don't get any data read from the registry
	nItems=0;
	while( true )
	{
		char Value[MAX_PATH];
		char szData[MAX_PATH];
		cbData = sizeof(szData);
		sprintf(Value, "QuickSelect%i", nItems);

		//Do not extend the size of the 
		if(nItems>=BROWSE_MAXENTRIES)
			break;

		//Read the registry and check if it is valid or leave
		if(RegQueryValueEx( Key, Value, NULL, NULL, (LPBYTE)&szData, &cbData)==ERROR_SUCCESS)
		{
			strcpy(Path[nItems], szData);
			nItems++;
		}
		else //Since we have failed to read the entry we're moving along
			break;
	}

	//Now close the key
	RegCloseKey(Key);
}

//*******************************************************************************************
//Type: Private
//Desc: Saves the registry for the config based on the BROWSE_REGISTRY_ROOT and BROWSE_REGISTRY_PATH
void CDirBrowse::SaveConfig()
{
	HKEY Key = NULL;
	//Open or create the registry before closing
	if(RegCreateKeyEx(BROWSE_REGISTRY_ROOT, BROWSE_REGISTRY_PATH, NULL, NULL, NULL, KEY_ALL_ACCESS, NULL, &Key, NULL)!=ERROR_SUCCESS)
		return;

	//Now save the window position
	RECT Dialog;
	::GetWindowRect(DirBrowser, &Dialog);

	//Save it
	RegSetValueEx(Key, "PosX", NULL, REG_DWORD, (CONST BYTE *)&Dialog.left, sizeof(LONG));
	RegSetValueEx(Key, "PosY", NULL, REG_DWORD, (CONST BYTE *)&Dialog.top, sizeof(LONG));

	//Now delete all the old entries
	for(int i=0;i<1024;i++) //Just a constant that is greater the maxentries :)
	{
		char Value[MAX_PATH];
		sprintf(Value, "QuickSelect%i", i);
		RegDeleteValue(Key, Value);
	}

	//Now store the directories
	for(int i=0;i<nItems;i++)
	{
		char Value[MAX_PATH];
		sprintf(Value, "QuickSelect%i", i);
		RegSetValueEx(Key, Value, NULL, REG_SZ, (CONST BYTE *)&Path[i], strlen(Path[i]));
	}

	//Now close it
	RegCloseKey(Key);
}

//*******************************************************************************************
