//
// LISTVIEW.C
//
#include "sigverif.h"

HWND    g_hListView         = NULL;
HWND    g_hStatus           = NULL;
BOOL    g_bSortOrder[]      = { FALSE, FALSE, FALSE, FALSE, FALSE};
RECT    g_Rect;

// Initialize the image lists for the icons in the listview control.
BOOL WINAPI ListView_SetImageLists(HWND hwndList)
{
    SHFILEINFO      sfi;
    HIMAGELIST      himlSmall;
    HIMAGELIST      himlLarge;
    BOOL            bSuccess = TRUE;
    TCHAR           szDriveRoot[MAX_PATH];

    MyGetWindowsDirectory(szDriveRoot, cA(szDriveRoot));
    szDriveRoot[3] = 0;
    himlSmall = (HIMAGELIST)SHGetFileInfo((LPCTSTR)szDriveRoot, 
                                          0,
                                          &sfi, 
                                          sizeof(SHFILEINFO), 
                                          SHGFI_SYSICONINDEX | SHGFI_SMALLICON);

    himlLarge = (HIMAGELIST)SHGetFileInfo((LPCTSTR)szDriveRoot, 
                                          0,
                                          &sfi, 
                                          sizeof(SHFILEINFO), 
                                          SHGFI_SYSICONINDEX | SHGFI_LARGEICON);

    if (himlSmall && himlLarge) {
        ListView_SetImageList(hwndList, himlSmall, LVSIL_SMALL);
        ListView_SetImageList(hwndList, himlLarge, LVSIL_NORMAL);
    } else bSuccess = FALSE;

    return bSuccess;
}

//
// Insert everything from the g_App.lpFileList into the listview control.
//
void ListView_InsertItems(void)
{
    LPFILENODE  lpFileNode;
    LV_ITEM     lvi;
    TCHAR       szBuffer[MAX_PATH];
    LPTSTR      lpString;
    int         iRet;
    BOOL        bMirroredApp;

    bMirroredApp = (GetWindowLong(g_App.hDlg, GWL_EXSTYLE) & WS_EX_LAYOUTRTL);

    for (lpFileNode=g_App.lpFileList;lpFileNode;lpFileNode=lpFileNode->next) {

        if (lpFileNode->bScanned &&
            !lpFileNode->bSigned &&
            SetCurrentDirectory(lpFileNode->lpDirName)) {

            //
            // Initialize lvi and insert the filename and icon into the first column.
            //
            ZeroMemory(&lvi, sizeof(LV_ITEM));
            lvi.mask = LVIF_TEXT;
            lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
            lvi.iImage = lpFileNode->iIcon;
            lvi.lParam = (LPARAM) lpFileNode;
            lvi.iSubItem = 0;
            lvi.pszText = lpFileNode->lpFileName;
            lvi.iItem = MAX_INT;
            lvi.iItem = ListView_InsertItem(g_hListView, &lvi);

            //
            // Insert the directory name into the second column.
            //
            lvi.mask = LVIF_TEXT;
            lvi.iSubItem = 1;
            lvi.pszText = lpFileNode->lpDirName;
            ListView_SetItem(g_hListView, &lvi);

            //
            // Get the date format, so we are localizable...
            //
            MyLoadString(szBuffer, IDS_UNKNOWN);

            iRet = GetDateFormat(LOCALE_SYSTEM_DEFAULT, 
                                 bMirroredApp ? 
                                    DATE_RTLREADING | DATE_SHORTDATE :
                                    DATE_SHORTDATE,
                                 &lpFileNode->LastModified,
                                 NULL,
                                 NULL,
                                 0);

            if (iRet) {

                lpString = MALLOC((iRet + 1) * sizeof(TCHAR));

                if (lpString) {

                    iRet = GetDateFormat(LOCALE_SYSTEM_DEFAULT,
                                         bMirroredApp ?
                                            DATE_RTLREADING | DATE_SHORTDATE :
                                            DATE_SHORTDATE,
                                         &lpFileNode->LastModified,
                                         NULL,
                                         lpString,
                                         iRet);

                    if (iRet) {

                        lstrcpy(szBuffer, lpString);
                    }

                    FREE(lpString);
                }
            }

            lvi.mask = LVIF_TEXT;
            lvi.iSubItem = 2;
            lvi.pszText = szBuffer;
            ListView_SetItem(g_hListView, &lvi);

            //
            // Insert the filetype string into the fourth column.
            //
            if (lpFileNode->lpTypeName) {

                lstrcpy(szBuffer, lpFileNode->lpTypeName);

            } else {
                
                MyLoadString(szBuffer, IDS_UNKNOWN);
            }

            lvi.mask = LVIF_TEXT;
            lvi.iSubItem = 3;
            lvi.pszText = szBuffer;
            ListView_SetItem(g_hListView, &lvi);

            //
            // Insert the version string into the fifth column.
            //
            if (lpFileNode->lpVersion) {

                lstrcpy(szBuffer, lpFileNode->lpVersion);

            } else {
                
                MyLoadString(szBuffer, IDS_NOVERSION);
            }

            lvi.mask = LVIF_TEXT;
            lvi.iSubItem = 4;
            lvi.pszText = szBuffer;
            ListView_SetItem(g_hListView, &lvi);
        }
    }
}

//
// Initialize the listview dialog.  First, we are going to load the global icon resource.
// Then we are going to create a status window and the actual listview control.
// Then we need to add the four columns and work out their default widths.
//
BOOL ListView_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{   
    LV_COLUMN   lvc;
    RECT        rect, rectResultsText, rectStatusBar, rectCancelButton, rectClient;
    TCHAR       szBuffer[MAX_PATH];
    TCHAR       szBuffer2[MAX_PATH];
    INT         iCol = 0, iWidth = 0;

    //
    // Load the global icon resource
    //
    if (g_App.hIcon) {

        SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR) g_App.hIcon); 
    }

    //
    // Create the status window at the bottom of the dialog
    //
    g_hStatus = CreateStatusWindow(WS_CHILD | WS_VISIBLE,
                                   NULL,
                                   hwnd,
                                   (UINT) IDC_STATUSWINDOW);

    //
    // Load the status string and fill it in with the correct values.
    //
    MyLoadString(szBuffer, IDS_NUMFILES);
    wsprintf(szBuffer2, szBuffer,   g_App.dwFiles, g_App.dwSigned, g_App.dwUnsigned, 
             g_App.dwFiles - g_App.dwSigned - g_App.dwUnsigned);
    SendMessage(g_hStatus, WM_SETTEXT, (WPARAM) 0, (LPARAM) szBuffer2);

    GetWindowRect(hwnd, &g_Rect);
    GetClientRect(hwnd, &rectClient);

    //
    // Get the windows RECT values for the dialog, the static text, and the status window.
    // We will use these values to figure out where to put the listview and the columns.
    //
    GetWindowRect(hwnd, &rect);
    GetWindowRect(GetDlgItem(hwnd, IDC_RESULTSTEXT), &rectResultsText);
    GetWindowRect(g_hStatus, &rectStatusBar);
    GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rectCancelButton);

    MoveWindow(GetDlgItem(hwnd, IDCANCEL), 
               (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
                 ? (rect.right - rect.left) - (rectCancelButton.right - rectCancelButton.left) - (rect.right - rectResultsText.right)
                 : (rect.right - rectResultsText.left) - (rectCancelButton.right - rectCancelButton.left) - (( 2 * (rectResultsText.left - rect.left)) / 3),
               (rectClient.bottom - rectClient.top) - (rectStatusBar.bottom - rectStatusBar.top) - (rectCancelButton.bottom - rectCancelButton.top) - 10,
               rectCancelButton.right - rectCancelButton.left,
               rectCancelButton.bottom - rectCancelButton.top,
               TRUE);

    GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rectCancelButton);

    //
    // Create the listview window!  I am using some really screwey logic to figure out how
    // big to make the listview and where to put it, but it seems to work.
    //
    g_hListView = CreateWindowEx(WS_EX_CLIENTEDGE, 
                                 WC_LISTVIEW, TEXT(""), 
                                 WS_TABSTOP | WS_VSCROLL | WS_VISIBLE | WS_CHILD | WS_BORDER | 
                                 LVS_SINGLESEL | LVS_REPORT | LVS_AUTOARRANGE | LVS_SHAREIMAGELISTS,
                                 ((rectResultsText.left - rect.left) * 2) / 3,
                                 (rectResultsText.bottom - rectResultsText.top) * 2,
                                 (rect.right - rect.left) - 2 * (rectResultsText.left - rect.left),
                                 rectCancelButton.top - rectResultsText.bottom - 20,
                                 hwnd, 
                                 (HMENU) IDC_LISTVIEW, 
                                 g_App.hInstance, 
                                 NULL);


    //
    // If the CreateWindowEx failed, then bail.
    //
    if (!g_hListView) {
    
        return FALSE;
    }

    //
    // Initialize the icon lists
    //
    ListView_SetImageLists(g_hListView);

    //
    // Create the first listview column for the icon and the file name.
    //
    lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
    lvc.fmt = LVCFMT_LEFT;
    lvc.cx = (rectResultsText.right - rectResultsText.left) / 5;
    lvc.pszText = szBuffer;
    MyLoadString(szBuffer, IDS_COL_NAME);
    lvc.cchTextMax = MAX_PATH;
    ListView_InsertColumn(g_hListView, iCol++, &lvc);   

    //
    // Create the second listview column for the directory name.
    //
    iWidth += lvc.cx;
    lvc.cx = (rectResultsText.right - rectResultsText.left) / 4;
    MyLoadString(szBuffer, IDS_COL_FOLDER);
    ListView_InsertColumn(g_hListView, iCol++, &lvc);

    //
    // Create the third listview column for the date name.
    //
    iWidth += lvc.cx;
    lvc.cx = (rectResultsText.right - rectResultsText.left) / 6;
    lvc.fmt = LVCFMT_CENTER;
    MyLoadString(szBuffer, IDS_COL_DATE);
    ListView_InsertColumn(g_hListView, iCol++, &lvc);

    //
    // Create the fourth listview column for the filetype string.
    //
    iWidth += lvc.cx;
    lvc.cx = (rectResultsText.right - rectResultsText.left) / 6;
    lvc.fmt = LVCFMT_CENTER;
    MyLoadString(szBuffer, IDS_COL_TYPE);
    ListView_InsertColumn(g_hListView, iCol++, &lvc);

    //
    // Create the fifth listview column for the version string.
    //
    iWidth += lvc.cx;
    lvc.cx = (rectResultsText.right - rectResultsText.left) - iWidth - 5;
    lvc.fmt = LVCFMT_CENTER;
    MyLoadString(szBuffer, IDS_COL_VERSION);
    ListView_InsertColumn(g_hListView, iCol++, &lvc);

    //
    // Now that the columns are set up, insert all the files in g_App.lpFileList!
    //
    ListView_InsertItems();

    //
    // Initialize the sorting order array to all FALSE.
    //
    g_bSortOrder[0] = FALSE;
    g_bSortOrder[1] = FALSE;
    g_bSortOrder[2] = FALSE;
    g_bSortOrder[3] = FALSE;

    SetForegroundWindow(g_App.hDlg);
    SetForegroundWindow(hwnd);
    SetFocus(GetDlgItem(hwnd, IDCANCEL));

    return TRUE;
}

//
// This function checks to see how big the sizing rectangle will be.  If the user is trying
// to size the dialog to less than the values in g_Rect, then we will fix the rectangle values
//
BOOL ListView_OnSizing(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    RECT    rect;
    LPRECT  lpRect = (LPRECT) lParam;
    BOOL    bRet = FALSE;

    GetWindowRect(hwnd, &rect);

    if ((lpRect->right - lpRect->left) < (g_Rect.right - g_Rect.left)) {
        lpRect->left = rect.left;
        lpRect->right = lpRect->left + (g_Rect.right - g_Rect.left);
        bRet = TRUE;
    }

    if ((lpRect->bottom - lpRect->top) < (g_Rect.bottom - g_Rect.top)) {
        lpRect->top = rect.top;
        lpRect->bottom = lpRect->top + (g_Rect.bottom - g_Rect.top);
        bRet = TRUE;
    }

    return bRet;
}

//
// This function allows us to resize the listview control and status windows when the
// user resizes the results dialog.  Thankfully, we can make everything relative using
// the RECT values for the main dialog, the static text, and the status window.
//
void ListView_ResizeWindow(HWND hwnd)
{
    RECT    rect, rectResultsText, rectStatusBar, rectCancelButton, rectClient;
    BOOL    bMirroredApp;

    bMirroredApp = (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL);

    GetWindowRect(hwnd, &rect);

    if ((rect.right - rect.left) < (g_Rect.right - g_Rect.left)) {
        
        MoveWindow(hwnd,
                   rect.left,
                   rect.top,
                   g_Rect.right - g_Rect.left,
                   rect.bottom - rect.top,
                   TRUE);
    }

    if ((rect.bottom - rect.top) < (g_Rect.bottom - g_Rect.top)) {
        
        MoveWindow(hwnd,
                   rect.left,
                   rect.top,
                   rect.right - rect.left,
                   g_Rect.bottom - g_Rect.top,
                   TRUE);
    }

    GetClientRect(hwnd, &rectClient);
    GetWindowRect(GetDlgItem(hwnd, IDC_RESULTSTEXT), &rectResultsText);
    GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rectCancelButton);
    GetWindowRect(g_hStatus, &rectStatusBar);


    MoveWindow(g_hStatus,
               0,
               (rect.bottom - rect.top) - (rectStatusBar.bottom - rectStatusBar.top),
               rect.right - rect.left,
               rectStatusBar.bottom - rectStatusBar.top,
               TRUE);

    MoveWindow(GetDlgItem(hwnd, IDCANCEL), 
               (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
                 ? (rect.right - rect.left) - (rectCancelButton.right - rectCancelButton.left) - (rect.right - rectResultsText.right)
                 : (rect.right - rectResultsText.left) - (rectCancelButton.right - rectCancelButton.left) - (( 2 * (rectResultsText.left - rect.left)) / 3),
               (rectClient.bottom - rectClient.top) - (rectStatusBar.bottom - rectStatusBar.top) - (rectCancelButton.bottom - rectCancelButton.top) - 10,
               rectCancelButton.right - rectCancelButton.left,
               rectCancelButton.bottom - rectCancelButton.top,
               TRUE);

    GetWindowRect(GetDlgItem(hwnd, IDCANCEL), &rectCancelButton);

    MoveWindow(g_hListView,
               bMirroredApp
                 ? ((rect.right - rectResultsText.right) * 2) / 3
                 : ((rectResultsText.left - rect.left) * 2) / 3,
               (rectResultsText.bottom - rectResultsText.top) * 2,
               bMirroredApp
                 ? (rect.right - rect.left) - 2 * (rect.right - rectResultsText.right)
                 : (rect.right - rect.left) - 2 * (rectResultsText.left - rect.left),
               rectCancelButton.top - rectResultsText.bottom - 20,
               TRUE);

}

//
// This function is a callback that returns a value for ListView_SortItems.
// ListView_SortItems wants a negative, zero, or positive number.
// Since CompareString returns 1,2,3 we just subtract 2 from the return value.
//
// We use the g_bSortOrder array to figure out which way we have sorted in the past.
//
// Warning: we don't check for error values from CompareString
//
int CALLBACK ListView_CompareNames(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
    LPFILENODE  lpFileNode1;
    LPFILENODE  lpFileNode2;
    FILETIME    FileTime1, FileTime2;
    int         iResult = 2;

    //
    // Depending on the sort order, we swap the order of comparison
    //
    if (g_bSortOrder[lParamSort]) {
        lpFileNode2 = (LPFILENODE) lParam1;
        lpFileNode1 = (LPFILENODE) lParam2;
    } else {
        lpFileNode1 = (LPFILENODE) lParam1;
        lpFileNode2 = (LPFILENODE) lParam2;
    }

    switch (lParamSort) {
    // We are comparing the file names
    case 0: iResult = CompareString(LOCALE_SYSTEM_DEFAULT, 
                                    NORM_IGNORECASE | NORM_IGNOREWIDTH, 
                                    lpFileNode1->lpFileName, 
                                    -1, 
                                    lpFileNode2->lpFileName,
                                    -1);
        break;

        // We are comparing the directory names
    case 1: iResult = CompareString(LOCALE_SYSTEM_DEFAULT, 
                                    NORM_IGNORECASE | NORM_IGNOREWIDTH, 
                                    lpFileNode1->lpDirName, 
                                    -1, 
                                    lpFileNode2->lpDirName,
                                    -1);
        break;

        // We are comparing the LastWriteTime's between the two files.
    case 2: SystemTimeToFileTime(&lpFileNode1->LastModified, &FileTime1);
        SystemTimeToFileTime(&lpFileNode2->LastModified, &FileTime2);
        iResult = CompareFileTime(&FileTime1, &FileTime2);
        return iResult;

        break;

        // We are comparing the filetype strings
    case 3: iResult = CompareString(LOCALE_SYSTEM_DEFAULT, 
                                    NORM_IGNORECASE | NORM_IGNOREWIDTH, 
                                    lpFileNode1->lpTypeName, 
                                    -1, 
                                    lpFileNode2->lpTypeName,
                                    -1);
        break;

        // We are comparing the version strings
    case 4: iResult = CompareString(LOCALE_SYSTEM_DEFAULT, 
                                    NORM_IGNORECASE | NORM_IGNOREWIDTH, 
                                    lpFileNode1->lpVersion, 
                                    -1, 
                                    lpFileNode2->lpVersion,
                                    -1);
        break;
    }

    return(iResult - 2);
}

//
// This function handles the clicks on the column headers and calls ListView_SortItems with the
// ListView_CompareNames callback previously defined.  It then toggles the sortorder for that column.
//
LRESULT ListView_NotifyHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    NMHDR       *lpnmhdr = (NMHDR *) lParam;
    NM_LISTVIEW *lpnmlv = (NM_LISTVIEW *) lParam;

    switch (lpnmhdr->code) {
    case LVN_COLUMNCLICK:
        switch (lpnmlv->iSubItem) {
        case 0: 
        case 1:
        case 2: 
        case 3: 
        case 4: ListView_SortItems(lpnmlv->hdr.hwndFrom, ListView_CompareNames, (LPARAM) lpnmlv->iSubItem);
            g_bSortOrder[lpnmlv->iSubItem] = !(g_bSortOrder[lpnmlv->iSubItem]);
            break;
        }
        break;
    }

    return 0;
}

//
// The only thing we look for here is the IDCANCEL if the user hit ESCAPE
//
void ListView_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
    switch (id) {
    case IDCANCEL:
        SendMessage(hwnd, WM_CLOSE, 0, 0);
        break;
    }
}

INT_PTR CALLBACK ListView_DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    BOOL    fProcessed = TRUE;

    switch (uMsg) {
    HANDLE_MSG(hwnd, WM_INITDIALOG, ListView_OnInitDialog);
    HANDLE_MSG(hwnd, WM_COMMAND, ListView_OnCommand);

    case WM_NOTIFY:
        return ListView_NotifyHandler(hwnd, uMsg, wParam, lParam);

    case WM_CLOSE:
        if (g_hStatus) {

            DestroyWindow(g_hStatus);
            g_hStatus = NULL;
        }

        if (g_hListView) {

            DestroyWindow(g_hListView);
            g_hListView = NULL;
        }

        EndDialog(hwnd, ID_CLOSE);
        break;

    case WM_SIZING:
        fProcessed = ListView_OnSizing(hwnd, wParam, lParam);
        break;

    case WM_SIZE:
        ListView_ResizeWindow(hwnd);
        break;

    default: fProcessed = FALSE;
    }

    return fProcessed;
}
