// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 1993, 1994  Microsoft Corporation.  All Rights Reserved.
//
//  MODULE:   listview.c
//
//  PURPOSE:  Handles all listview manipulation.
//
//  FUNCTIONS:
//    CreateListView         - Creates a list view control in report view
//    ListView_InitColumns   - Initializes the list view columns
//    AddProcessItem         - Adds an item to the process list view
//    AddThreadItem          - Adds an item to the thread list view
//    ListView_OnGetDispInfo - Handles the WM_NOTIFY/LV_GETDISPINFO message
//    ListView_OnClick       - Handles the WM_NOTIFY/NM_CLICK message
//    ListView_OnDeleteItem  - Frees memory when an item is deleted
//    ListView_OnColumnClick - Sorts the list view based on the column clicked
//    ListView_CompareProc   - Comparison callback for sorting the list view
// 	  LV_Subclass			 - Subclass procedure for the thread list view
                            
//  COMMENTS:
//

#include <windows.h>            // required for all Windows applications
#include <commctrl.h>
#include <string.h>
#include "listview.h"
#include "procthrd.h"
#include "globals.h"            // prototypes specific to this application

FARPROC g_lpfnThreadListView;

//-------------------------------------------------------------------------
// Useful application-global variables defined here!
APPCOLUMNDEF acdProcess[] = 
{
	{IDS_PROCESS, 		100},
	{IDS_PID,			75},
	{IDS_PRIORITY,		75},
	{IDS_THREADCNT,		90},
	{IDS_FULLPATH,		250}
};

APPCOLUMNDEF acdThread[] = 
{
	{IDS_THREAD,		100},
	{IDS_TPID,			100},
	{IDS_TPRIORITY,		100}
};
	 

//
//  FUNCTION: CreateListView(HWND, DWORD, UINT, UINT, UINT)
//
//  PURPOSE:  Creates a list view control
//
//  PARAMETERS:
//    hwndParent - Handle of the parent window.
//    dwStyle    - Style flags for listview control
//    idString   - Identifier of the first string table resource entry for
//				   the new control's column titles
//    cColumns   - Number of columns to add            
//    idCtl      - Control identifier
//
//  RETURN VALUE:
//    The window handle of the newly created listview control if successful, 
//    or NULL if a new listview control could not be created.
//
//

HWND CreateListView (HWND hwndParent, DWORD dwStyle, APPCOLUMNDEF acd[], UINT cColumns, UINT idCtl)
{
    HWND hwndLV   = NULL;
	RECT rcClient = {0};

    // Make sure the common controls DLL is loaded.
    InitCommonControls();

    // Create the control so that it fills the client area of the parent 
    // window.  The size of the control will be updated in the parent's 
	// WM_SIZE message handler
	GetClientRect(hwndParent, &rcClient);

    hwndLV = CreateWindow(WC_LISTVIEW, 
                          "", 
                          dwStyle,
                          rcClient.left, 
                          rcClient.top,
    		              rcClient.right, 
    		              rcClient.bottom,
                          hwndParent, 
                          (HMENU)idCtl, 
                          hInst, 
                          NULL);
    if (hwndLV == NULL)
        return NULL;

    // Initialize the image lists and add columns
    if (!ListView_InitColumns(hwndLV, acd, cColumns)) 
    {
        DestroyWindow(hwndLV);
        return NULL;
    }
    return hwndLV;
}


//
//  FUNCTION: ListView_InitColumns(HWND, UINT, UINT)
//
//  PURPOSE:  Adds columns to the specified list view.
//
//  PARAMETERS:
//    hwndLV    - Handle to the listview control.
//    idString  - ID in the string table where the column names begin
//    cColumns  - Number of columns to add                                                
//
//  RETURN VALUE:
//    TRUE  - Success, all the columns were added          
//    FALSE - Failure, an error occured while adding one of the columns
//
//  COMMENTS:
//
//

BOOL WINAPI ListView_InitColumns (HWND hwndLV, APPCOLUMNDEF acd[], UINT cColumns)
{
	LV_COLUMN lvc          = {0};
	UINT      iCol         = 0;
    char      szTemp[64];

	// Initialize the LV_COLUMN struct
	lvc.mask    = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
	lvc.fmt     = LVCFMT_LEFT;
	lvc.pszText = szTemp;             

	// Add the columns
	for (iCol=0; iCol < cColumns; iCol++)
	{
		lvc.iSubItem = iCol;
		lvc.cx = acd[iCol].cxColumn;
		LoadString(hInst, acd[iCol].idString, szTemp, sizeof(szTemp));
		if (ListView_InsertColumn(hwndLV, iCol, &lvc) == -1)
			return (FALSE);
	}
	return (TRUE);
}


//
//  FUNCTION: AddProcessItem(HWND, PINFO pi)
//
//  PURPOSE:  Add an item and subitems to the process list view.
//
//  PARAMETERS:
//    hwndLV  - Handle of the list view control
//    pi      - Application-defined MYITEM structure containing the text
//              of the item and its corresponding subitems
//
//  RETURN VALUE:
//    TRUE if the item is added successfully, FALSE otherwise.
//
//

BOOL AddProcessItem(HWND hwndLV, PINFO pi)
{
	LV_ITEM lvi      = {0};
	PPINFO  ppi      = {0};
	int     iSubItem = 0;

	lvi.mask      = LVIF_TEXT | LVIF_STATE | LVIF_PARAM;
	lvi.iItem     = 0;
	lvi.iSubItem  = 0;
	lvi.state     = 0;
	lvi.stateMask = 0;
	lvi.pszText   = LPSTR_TEXTCALLBACK;
	lvi.iImage    = 0;	

	// Because we're using the LPSTR_TEXTCALLBACK type for the strings,
	// we need to save the PINFO structure in the lParam of the item. So,
	// here we allocate the memory for the structure, and we free the
	// memory when we get the notification that the item is being deleted.
	ppi = (PPINFO)malloc(sizeof(PINFO));
    if (!ppi)
        return FALSE;

	CopyMemory(ppi, &pi, sizeof(PINFO));

	lvi.lParam = (LPARAM)ppi;

	if (ListView_InsertItem(hwndLV, &lvi) == -1)
		return FALSE;

	// set up all the sub items to use LBSTR_TEXTCALLBACK
	for (iSubItem = 1; iSubItem < CPROCESSCOLUMNS; iSubItem++)
		ListView_SetItemText(hwndLV, 0, iSubItem, LPSTR_TEXTCALLBACK);

	return TRUE;
}


//
//  FUNCTION: AddThreadItem(HWND, TINFO ti)
//
//  PURPOSE:  Add an item and subitems to the thread list view.
//
//  PARAMETERS:
//    hwndLV  - Handle of the list view control
//    ti      - Application-defined MYITEM structure containing the text
//              of the item and its corresponding subitems
//
//  RETURN VALUE:
//    TRUE if the item is added successfully, FALSE otherwise.
//
//

BOOL AddThreadItem(HWND hwndLV, TINFO ti)
{
	LV_ITEM lvi      = {0};
	PTINFO  pti      = {0};
	int     iSubItem = 0;

	lvi.mask      = LVIF_TEXT | LVIF_STATE | LVIF_PARAM;
	lvi.iItem     = 0;
	lvi.iSubItem  = 0;
	lvi.state     = 0;
	lvi.stateMask = 0;
	lvi.pszText   = LPSTR_TEXTCALLBACK;
	lvi.iImage    = 0;

	// Because we're using the LPSTR_TEXTCALLBACK type for the strings,
	// we need to save the PINFO structure in the lParam of the item. So,
	// here we allocate the memory for the structure, and we free the
	// memory when we get the notification that the item is being deleted.
	pti = (PTINFO)malloc(sizeof(TINFO));
	if (!pti)
		return FALSE;

	CopyMemory(pti, &ti, sizeof(TINFO));
	lvi.lParam = (LPARAM)pti;

	if (ListView_InsertItem(hwndLV, &lvi) == -1)
		return FALSE;

	// set up all the sub items to use LPSTR_TEXTCALLBACK as well
	for (iSubItem = 1; iSubItem < CTHREADCOLUMNS; iSubItem++)
		ListView_SetItemText(hwndLV, 0, iSubItem, LPSTR_TEXTCALLBACK);

	return TRUE;
}


//
//  FUNCTION: ListView_OnGetDispInfo(int, LV_DISPINFO *)
//
//  PURPOSE:  Handle the WM_NOTIFY/LV_GETDISPINFO notification by returning
//			  the string for the item/subitem being asked for.
//
//  PARAMETERS:
//    idFrom - Identifier of the control this is being asked for
//    plvdi  - Specifies which data is being asked for 
//
//  RETURN VALUE:
//    Always returns 0.
//
//  COMMENTS:
//	  The function will return the string in the plvdi->item.pszText field
//

LRESULT ListView_OnGetDispInfo(int idFrom, LV_DISPINFO *plvdi)
{
static	char szText[128];

	switch (idFrom)
	{
		case IDC_PROCESS:
		{
			PINFO *pi = (PPINFO)(plvdi->item.lParam);

			switch (plvdi->item.iSubItem)
			{
				case 0:						// module name
					plvdi->item.pszText = pi->szModName;
					break;

				case 1:						// process id
					wsprintf(szText, "%X", pi->pid);
					plvdi->item.pszText = szText;
				   	break;

				case 2:						// base priority
					wsprintf(szText, "%d", pi->pcPriClassBase);
					plvdi->item.pszText = szText;
					break;

				case 3:                     // thread count
					wsprintf(szText, "%d", pi->cntThreads);
					plvdi->item.pszText = szText;
					break;

				case 4:
					plvdi->item.pszText = pi->szFullPath;
					break;

				default:
					break;
			}
			break;
		} // case IDC_PROCESS

		case IDC_THREAD:
		{
			PTINFO pti = (PTINFO)(plvdi->item.lParam);

			switch (plvdi->item.iSubItem)
			{
				case 0:						// thread ID
					wsprintf(szText, "%X", pti->tid);
					plvdi->item.pszText = szText;
					break;

				case 1:						// owning PID
					wsprintf(szText, "%X", pti->pidOwner);
					plvdi->item.pszText = szText;
					break;

				case 2:						// thread priority
					wsprintf(szText, "%d", pti->tpBasePri);
					plvdi->item.pszText = szText;
					break;

				default:
					break;
			}
			break;
		} // case IDC_THREAD
	}
	return (0);
}


//
//  FUNCTION: ListView_OnClick(int, NM_LISTVIEW *)
//
//  PURPOSE:  Handle the WM_NOTIFY/NM_CLICK message by retrieving the 
//			  item information for the process item clicked on and 
//			  refresh the threads associated with the process item.
//
//  PARAMETERS:
//    idFrom  - Identifier of the control sending this notification
//    pnmlv   - Control specific information about the notification
//
//  RETURN VALUE:
//    TRUE  - Success
//    FALSE - Initialization failed
//
//  COMMENTS:
//
//

LRESULT ListView_OnClick(int idFrom, NM_LISTVIEW *pnmlv)
{
	LV_HITTESTINFO lvhti         = {0};
	LV_ITEM        lvi           = {0};
	int	           iItemClicked  = 0;
	BOOL           fErr          = FALSE;
	POINT          point         = {0};

	// only handle this message if it is sent from the process list view
	if (idFrom != IDC_PROCESS)
		return (0);		 	

	// Find out where the cursor is
	GetCursorPos(&point);
	ScreenToClient(g_hwndProcess, &point);

	// Now do a hittest to find out which item this is
	lvhti.pt     = point;
	iItemClicked = ListView_HitTest(g_hwndProcess, &lvhti);

	// Sset up the LV_ITEM structure to get the process id
	lvi.iItem    = iItemClicked;
	lvi.iSubItem = 0;
	lvi.mask     = LVIF_PARAM;

	fErr = ListView_GetItem(g_hwndProcess, &lvi);
	if (TRUE == fErr) 	// If we got the item...
		RefreshThreadList(g_hwndThread, ((PPINFO)(lvi.lParam))->pid);
	
	return (0);
}


//
//  FUNCTION: ListView_OnDeleteItem(int, NM_LISTVIEW *)
//
//  PURPOSE:  Handle the WM_NOTIFY/LV_DELETEITEM by freeing the structure
//			  stored with the item.
//
//  PARAMETERS:
//    idFrom  - Identifier of the control sending the notification
//    pnmlv   - Control specific information about the notification, 
//				specifically which item is being freed.
//
//  RETURN VALUE:
//    Always returns 0  
//
//  COMMENTS:
//

LRESULT ListView_OnDeleteItem(int idFrom, NM_LISTVIEW *pnmlv)
{
	LV_ITEM lvi  = {0};
	BOOL    fErr = FALSE;

	// set up the LV_ITEM structure to get the pointer stored in the lParam
	lvi.iItem    = pnmlv->iItem;
	lvi.iSubItem = 0;
	lvi.mask     = LVIF_PARAM;

	// Get the LV_ITEM structure from the appropriate control 
	fErr = ListView_GetItem(pnmlv->hdr.hwndFrom, &lvi);
	if (TRUE == fErr)  					// If we got the item										    
		free((VOID *)lvi.lParam);		// free the structure

	return (0);
}


//
//  FUNCTION: ListView_OnColumnClick(int, NM_LISTVIEW *)
//
//  PURPOSE:  Handle the WM_NOTIFY/LVN_COLUMNCLICK notification by sorting
//			  the control based on the column clicked
//
//  PARAMETERS:
//    idFrom  - Identifier of the control sending the notification
//    pnmlv   - More information about the notification, specifically which
//				column was clicked on.
//
//  RETURN VALUE:
//	  Always returns 0.
//
//  COMMENTS:
//

LRESULT ListView_OnColumnClick(int idFrom, NM_LISTVIEW *pnmlv)
{
	SORTINFO si = {0};

	si.idFrom   = idFrom;
	si.iSubItem = pnmlv->iSubItem;

	ListView_SortItems(pnmlv->hdr.hwndFrom, ListView_CompareProc,
			           (LPARAM)&si);
	return (0);
}


//
//  FUNCTION: ListView_CompareProc(LPARAM, LPARAM, LPARAM)
//
//  PURPOSE:  Callback used for column sorting in a listview
//
//  PARAMETERS:  
//    lParam1 	 - lParam of the first item being compared (from LV_ITEM)
//    lParam2 	 - lParam of the second item being compared (from LV_ITEM)
//	  lParamSort - lParam passed in from the ListView_SortItems()
//
//  RETURN VALUE:
//    -1 - lParam1 should come before lParam2
//     0 - lParam1 and lParam2 are the same
//	   1 - lParam1 should come after lParam2
//
//  COMMENTS:
//

int CALLBACK ListView_CompareProc(LPARAM lParam1, 
                                  LPARAM lParam2, 
		                          LPARAM lParamSort)
{
	PSORTINFO psi          = (PSORTINFO)lParamSort;
	int       iReturn      = 0;
	char      szTemp1[32];
	char      szTemp2[32];

	if (psi->idFrom == IDC_PROCESS)
	{
		PPINFO ppi1 = (PPINFO)lParam1;
		PPINFO ppi2 = (PPINFO)lParam2;

		switch (psi->iSubItem)
		{
			case 0:							// module name
				iReturn = lstrcmpi(ppi1->szModName, ppi2->szModName);
				break;

			case 1:							// process ID
				wsprintf(szTemp1, "%X", ppi1->pid);
				wsprintf(szTemp2, "%X", ppi2->pid);
				iReturn = lstrcmpi(szTemp1, szTemp2);
				break;

			case 2:							// base priority
				if (ppi1->pcPriClassBase < ppi2->pcPriClassBase)	
					iReturn = -1;
				else
					if (ppi1->pcPriClassBase > ppi2->pcPriClassBase)
						iReturn = 1;
					else					// same, so fall back on name
						iReturn = lstrcmpi(ppi1->szModName, 
								           ppi2->szModName);
				break;

			case 3:                          // thread count
				if (ppi1->cntThreads < ppi2->cntThreads)	
					iReturn = -1;
				else
					if (ppi1->cntThreads > ppi2->cntThreads)
						iReturn = 1;
					else					// same, so fall back on name
						iReturn = lstrcmpi(ppi1->szModName, 
								           ppi2->szModName);
				break;

			case 4:                         // full path name
				iReturn = lstrcmpi(ppi1->szFullPath, ppi2->szFullPath);
				if (iReturn == 0)
					iReturn = lstrcmpi(ppi1->szModName, ppi2->szModName);
				break;					

			default:
				iReturn = -1;
		}
	} // if(idFrom)
	else
	{
		PTINFO pti1 = (PTINFO)lParam1;
		PTINFO pti2 = (PTINFO)lParam2;

		switch (psi->iSubItem)
		{
			case 0:							// thread id
				wsprintf(szTemp1, "%X", pti1->tid);
				wsprintf(szTemp2, "%X", pti2->tid);
				iReturn = lstrcmpi(szTemp1, szTemp2);
				break;

			case 1:							// owning pid
				wsprintf(szTemp1, "%X", pti1->pidOwner);
				wsprintf(szTemp2, "%X", pti2->pidOwner);
				iReturn = lstrcmpi(szTemp1, szTemp2);
				break;

			case 2:							// tpBasePri
				if (pti1->tpBasePri < pti2->tpBasePri)	
					iReturn = -1;
				else
					if (pti1->tpBasePri > pti2->tpBasePri)
						iReturn = 1;
					else					// same, so fall back on tid
					{
						wsprintf(szTemp1, "%X", pti1->tid);
						wsprintf(szTemp2, "%X", pti2->tid);
						iReturn = lstrcmpi(szTemp1, szTemp2);
					}
				break;

			default:
				iReturn = -1;
		} 
	} // else

	return iReturn;
}
				

//
//  FUNCTION: ListView_GetSelectedPid(HWND, LPSTR, int)
//
//  PURPOSE:  Retrieve the PID for the selected item in the list view			  
//
//  PARAMETERS:
//    
//	  hwndLV     - handle to the list view control
//     lpszPath  - Buffer to contain full path of selected process's 
//                 executable file
//     cch       - Size in bytes of lpszPath
//
//  RETURN VALUE:
//	  The PID of the selected item in the list view control
//
//  COMMENTS:
//

DWORD ListView_GetSelectedPid(HWND hwndLV, LPSTR lpszPath, int cch)
{
    int cbPathLen;
	int index;			// index of the selected item
	LV_ITEM lvi;

	// get the index of the selected item
	index = ListView_GetNextItem(hwndLV, -1, LVNI_ALL | LVNI_SELECTED);
	if (index == -1)
		return 0;
		
	// fill out the LV_ITEM struct so we can get the PINFO struct stored
	// in the lParam for the selected item
	lvi.iItem = index;
	lvi.iSubItem = 0;
	lvi.mask = LVIF_PARAM;
	ListView_GetItem(hwndLV, &lvi);

    cbPathLen = min (cch, sizeof(((PPINFO)(lvi.lParam))->szFullPath));
    CopyMemory(lpszPath, ((PPINFO)(lvi.lParam))->szFullPath, cbPathLen);

	// return the PID
	return (DWORD)((PPINFO)(lvi.lParam))->pid;
}



//
//  FUNCTION: LV_Subclass(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Subclass procedure so the thread list view can reject the focus
//
//  PARAMETERS:
//
//    hwnd      - Window handle  (Unused)
//    uMessage  - WM_SETFOCUS    
//    wparam    - handle of the window losing focus
//    lparam    - Extra data     (Unused)
//
//  RETURN VALUE:
//
//    return 0 if the message is WM_SETFOCUS, otherwise the message is passed
//	  on to the default List view window procedure
//
//  COMMENTS:
//
//

LRESULT CALLBACK LV_Subclass(HWND hwnd, UINT uMsg, WPARAM wParam,
        LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SETFOCUS:
			// set the focus back to the the control losing the focus
            SetFocus((HWND)wParam);
            return 0;
    }

	// if this is not the WM_SETFOCUS message, send it to the original
	// window procedure
    return CallWindowProc(g_lpfnThreadListView, hwnd, uMsg, wParam, 
            lParam);
}
