/*++

Copyright (C) Microsoft Corporation, 1996 - 1999
All rights reserved.

Module Name:

    portslv.hxx

Abstract:

    Ports List View header

Author:

    Albert Ting (AlbertT)  17-Aug-1995
    Steve Kiraly (SteveKi) 29-Mar-1996

Revision History:

--*/

#include "precomp.hxx"
#pragma hdrstop

#include "portslv.hxx"

#if DBG
//#define DBG_PORTSINFO                  DBG_INFO
#define DBG_PORTSINFO                    DBG_NONE
#endif

MSG_ERRMAP gaMsgErrMapPorts[] = {
    ERROR_NOT_SUPPORTED, IDS_ERR_PORT_NOT_IMPLEMENTED,
    ERROR_ALREADY_EXISTS, IDS_ERR_PORT_ALREADY_EXISTS,
    0, 0
};

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

    Ports List view class.

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

TPortsLV::
TPortsLV(
    VOID
    ) : _bSelectionState( TRUE ),
        _bSingleSelection( TRUE ),
        _bTwoColumnMode( FALSE ),
        _iSelectedItem( -1 ),
        _ColumnSortState( kPortHeaderMax ),
        _uCurrentColumn( 0 ),
        _bAllowSelectionChange( FALSE ),
        _bHideFaxPorts( FALSE )
{
    vCreatePortDataList();
}

TPortsLV::
~TPortsLV(
    VOID
    )
{
    vDestroyPortDataList();
}

BOOL
TPortsLV::
bReloadPorts(
    IN LPCTSTR pszServerName,
    IN BOOL bSelectNewPort
    )
/*++

Routine Description:

    Read in the remote ports and put them in the listview.  If level
    2 fails, we will try 1.

Arguments:

    pszServerName - Pointer to server name.
    bSelectNewPort - Indicates if a new port is to be located and selected.
                     TRUE select new port, FALSE do not located new port.

Return Value:

    TRUE if ports list loaded, FALSE if error occurred.

--*/

{
    TStatusB bStatus( DBG_WARN, ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_LEVEL );

    DWORD           cbPorts = 0; 
    PPORT_INFO_2    pPorts  = NULL;
    DWORD           cPorts  = 0;
    DWORD           dwLevel = 2;

    //
    // Preserve the current check state.
    //
    TCHAR szPortList[kPortListMax];
    vGetPortList( szPortList, COUNTOF( szPortList ) );

    //
    // Enumerate the port starting at level 2.
    //
    bStatus DBGCHK = VDataRefresh::bEnumPortsMaxLevel( pszServerName, 
                                                       &dwLevel, 
                                                       (PVOID *)&pPorts, 
                                                       &cbPorts, 
                                                       &cPorts );

    //
    // If the ports list cannot be enumerated fail with an error.
    //
    if( !bStatus ){
        DBGMSG( DBG_WARN, ( "PortsLV.bReloadPorts: can't alloc %d %d\n", cbPorts, GetLastError( )));
        return FALSE;
    }

    //
    // If option to select newly added port was selected.
    //
    TString strNewPort;
    if( bSelectNewPort ){

        //
        // Located the newly added port.
        //
        bSelectNewPort = bLocateAddedPort( strNewPort, pPorts, cPorts, dwLevel );
        if( bSelectNewPort ){
            DBGMSG( DBG_TRACE, ("New port found " TSTR "\n", (LPCTSTR)strNewPort ) );
        }
    }

    //
    // Get the printers
    //
    PRINTER_INFO_2 *pPrinterInfo2   = NULL;
    DWORD           cPrinterInfo2   = 0;
    DWORD           cbPrinterInfo2  = 0;
    DWORD           dwFlags         = PRINTER_ENUM_NAME;
    
    bStatus DBGCHK = VDataRefresh::bEnumPrinters( 
                                                  dwFlags,
                                                  (LPTSTR)pszServerName,
                                                  2,
                                                  (PVOID *)&pPrinterInfo2,
                                                  &cbPrinterInfo2,
                                                  &cPrinterInfo2 );

    //
    // Delete current ports if they exist.
    //
    bStatus DBGCHK = ListView_DeleteAllItems( _hwndLV );

    //
    // Clear the item count.
    //
    _cLVPorts = 0;

    //
    // Delete all the port data items.
    //
    vDestroyPortDataList();

    TString strDescription;

    //
    // Add LPT?: ports
    //
    strDescription.bLoadString( ghInst, IDS_TEXT_PRINTERPORT );
    vInsertPortsByMask(
        cPorts, 
        pPorts, 
        cPrinterInfo2, 
        pPrinterInfo2,
        dwLevel,
        TEXT("lpt?:"),
        strDescription
        );

    //
    // Add COM?: ports
    //
    strDescription.bLoadString( ghInst, IDS_TEXT_SERIALPORT );
    vInsertPortsByMask(
        cPorts, 
        pPorts, 
        cPrinterInfo2, 
        pPrinterInfo2,
        dwLevel,
        TEXT("com?:"),
        strDescription
        );

    //
    // Add FILE: ports
    //
    strDescription.bLoadString( ghInst, IDS_TEXT_PRINTTOFILE );
    vInsertPortsByMask(
        cPorts, 
        pPorts, 
        cPrinterInfo2, 
        pPrinterInfo2,
        dwLevel,
        TEXT("file:"),
        strDescription
        );

    //
    // Add all the rest
    //
    vInsertPortsByMask(
        cPorts, 
        pPorts, 
        cPrinterInfo2, 
        pPrinterInfo2,
        dwLevel,
        NULL,
        NULL
        );

    //
    // Restore the previous check state.
    //
    vCheckPorts( szPortList );

    //
    // Select and check the newly added port.
    //
    if( bSelectNewPort ){

        //
        // Check off other selected ports
        //
        INT iItem = -1;

        do
        {
            iItem = ListView_GetNextItem( _hwndLV, iItem, LVNI_SELECTED );

            if( iItem != -1 )
            {
                ListView_SetItemState( _hwndLV, 
                                       iItem,
                                       0, 
                                       LVIS_SELECTED | LVIS_FOCUSED );
            }
        } while( iItem != -1 );

        //
        // New port is added select and scroll it into view.
        //
        vItemClicked( iSelectPort( strNewPort ) );
    }

    //
    // This arrays of numbers represents the percentages of
    // each column width from the total LV width. The sum 
    // of all numbers in the array should be equal to 100 
    // (100% = the total LV width)
    //
    static UINT arrColW2[] = { 50, 50 };
    static UINT arrColW3[] = { 18, 35, 47 };

    //
    // Adjust columns ...
    //
    if( !_bTwoColumnMode )
    {
        vAdjustHeaderColumns( _hwndLV, COUNTOF(arrColW3), arrColW3 );
    }
    else
    {
        vAdjustHeaderColumns( _hwndLV, COUNTOF(arrColW2), arrColW2 );
    }

    //
    // Release the enum ports and enum printer memory.
    //
    FreeMem( pPorts );
    FreeMem( pPrinterInfo2 );

    return TRUE;
}


BOOL
TPortsLV::
bLocateAddedPort( 
    IN LPCTSTR pszServerName,
    IN TString &strNewPort
    )
/*++

Routine Description:

    Located the first port which is not in the port list view.

Arguments:

    strPort - New port which has been added.

Return Value:

    TRUE success, FALSE error occurred.

--*/
{
    TStatusB        bStatus;
    DWORD           cbPorts = 0; 
    PPORT_INFO_2    pPorts  = NULL;
    DWORD           cPorts  = 0;
    DWORD           dwLevel = 2;

    //
    // Enumerate the port starting at level 2.
    //
    bStatus DBGCHK = VDataRefresh::bEnumPortsMaxLevel( pszServerName, 
                                                       &dwLevel, 
                                                       (PVOID *)&pPorts, 
                                                       &cbPorts, 
                                                       &cPorts );

    //
    // If the ports list cannot be enumerated fail with an error.
    //
    if( bStatus )
    {
        //
        // Located the newly added port.
        //
        bStatus DBGCHK = bLocateAddedPort( strNewPort, pPorts, cPorts, dwLevel );

        if( bStatus )
        {
            DBGMSG( DBG_TRACE, ("New port found " TSTR "\n", (LPCTSTR)strNewPort ) );
        }
    }

    //
    // Release the port memory.
    //
    FreeMem( pPorts );

    return bStatus;
}


BOOL
TPortsLV::
bLocateAddedPort(
    IN OUT  TString     &strPort,
    IN      PPORT_INFO_2 pPorts,
    IN      DWORD        cPorts,
    IN      DWORD        dwLevel
    )
/*++

Routine Description:

    Located the first port which is not in the port list view.

Arguments:

    strPort - New port which has been added.
    pPorts  - Points to a ports enum structure array.
    cPorts  - Number of elements in the ports enum array.
    dwLevel - Level of the ports enum structure array.

Return Value:

    TRUE success, FALSE error occurred.

--*/
{
    TStatusB bStatus;
    LPTSTR pszPort;

    bStatus DBGNOCHK = FALSE;

    //
    // Go through all the ports.
    //
    for( UINT i=0; i<cPorts; ++i ){

        if( dwLevel == 2 ){

            //
            // If we are to hide the fax ports then 
            // ignore newly added fax ports.
            //
            if( _bHideFaxPorts && bIsFaxPort( pPorts[i].pPortName, pPorts[i].pMonitorName ) )
            {
                DBGMSG( DBG_TRACE, ( "PortsLV.bLocateAddedPort: fax device being skipped.\n" ) );
                continue;
            }

            pszPort = pPorts[i].pPortName;

        } else {
            pszPort = ((PPORT_INFO_1)pPorts)[i].pName;
        }

        //
        // Look for a portname which is not in the list view.
        //
        if( iFindPort( pszPort ) < 0 ){

            //
            // Update the passed in string object.
            //
            bStatus DBGCHK = strPort.bUpdate( pszPort );
            break;
        }
    }
    return bStatus;
}

VOID
TPortsLV::
vSelectPort(
    IN LPCTSTR pszPort
    )
{
    SPLASSERT( pszPort );
    iSelectPort( pszPort );
}

VOID
TPortsLV::
vEnable(
    IN BOOL bRetainSelection
    )
{
    if( bRetainSelection )
    {
        if( _iSelectedItem != -1 )
        {
            ListView_SetItemState( _hwndLV, 
                                   _iSelectedItem,
                                   LVIS_SELECTED | LVIS_FOCUSED,
                                   LVIS_SELECTED | LVIS_FOCUSED );

            ListView_EnsureVisible( _hwndLV,
                                    _iSelectedItem,
                                    FALSE );
        }
    }
    EnableWindow( _hwndLV, TRUE );
}

VOID
TPortsLV::
vDisable(
    IN BOOL bRetainSelection
    )
{
    if( bRetainSelection )
    {
        _iSelectedItem = ListView_GetNextItem( _hwndLV, -1, LVNI_SELECTED );

        if( _iSelectedItem != -1 )
        {
            ListView_SetItemState( _hwndLV, 
                                   _iSelectedItem,
                                   0, 
                                   LVIS_SELECTED | LVIS_FOCUSED );
        }
    }
    EnableWindow( _hwndLV, FALSE );
}

VOID
TPortsLV::
vCheckPorts(
    IN OUT LPTSTR pszPortString CHANGE
    )

/*++

Routine Description:

    Set the ports in the listview based on the printers port string.

Arguments:

    pszPortName - List of ports, comma delimited.  When this is returns,
        pszPortName is the same on entry, but it's modified inside this
        function.

Return Value:

--*/

{
    //
    // We will walk though the port string, replacing commas with
    // NULLs, but we'll change them back.
    //
    LPTSTR psz = pszPortString;
    SPLASSERT( psz );

    LPTSTR pszPort;
    INT iItem;
    INT iItemFirst = kMaxInt;

    while( psz && *psz ){

        pszPort = psz;
        psz = _tcschr( psz, TEXT( ',' ));

        if( psz ){
            *psz = 0;
        }

        iItem = iCheckPort( pszPort );
        if( iItem == -1 ){
            DBGMSG( DBG_WARN,
                    ( "PortsLV.vCheckPort: Port "TSTR" not checked.\n", pszPort ));
        }

        if( iItem < iItemFirst ){
            iItemFirst = iItem;
        }

        if( psz ){
            *psz = TEXT( ',' );
            ++psz;
        }
    }

    if( iItemFirst == kMaxInt ){

        DBGMSG( DBG_PORTSINFO, ( "PortsLV.vCheckPorts: No ports selected.\n" ));
        iItemFirst = 0;
    }

    //
    // Select the first item and make sure it is visible.
    //
    ListView_SetItemState( _hwndLV,
                           iItemFirst,
                           LVIS_SELECTED | LVIS_FOCUSED,
                           LVIS_SELECTED | LVIS_FOCUSED );

    ListView_EnsureVisible( _hwndLV,
                            iItemFirst,
                            FALSE );

}

VOID
TPortsLV::
vSelectItem(
    INT iItem
    )

/*++

Routine Description:

    Selects an item in the ListView.

Arguments:

    iItem - Index of item to select.

Return Value:

--*/

{
    ListView_SetItemState( _hwndLV,
                           iItem,
                           LVIS_SELECTED,
                           LVIS_SELECTED );
}

BOOL
TPortsLV::
bSetUI(
    IN HWND     hwndLV,
    IN BOOL     bTwoColumnMode,
    IN BOOL     bSelectionState,
    IN BOOL     bAllowSelectionChange,
    IN HWND     hwnd,
    IN WPARAM   wmDoubleClickMsg,
    IN WPARAM   wmSingleClickMsg,
    IN WPARAM   wmDeleteKeyMsg
    )
{
    _hwndLV                 = hwndLV;
    _bTwoColumnMode         = bTwoColumnMode;
    _bSelectionState        = bSelectionState;
    _bAllowSelectionChange  = bAllowSelectionChange;
    _wmDoubleClickMsg       = wmDoubleClickMsg;
    _wmSingleClickMsg       = wmSingleClickMsg;                                 
    _wmDeleteKeyMsg         = wmDeleteKeyMsg;                                 
    _hwnd                   = hwnd;

    SPLASSERT( _hwndLV );

    if( _bSelectionState ){

        //
        // Add check boxes.
        //
        HIMAGELIST himlState = ImageList_Create( 16, 16, TRUE, 2, 0 );

        //
        // !! LATER !!
        // Should be created once then shared.
        //
        if( !himlState ){
            DBGMSG( DBG_ERROR, ( "PortsLV.bSetUI: ImageList_Create failed %d\n",
                    GetLastError( )));
            return FALSE;
        }

        //
        // Load the bitmap for the check states.
        //
        HBITMAP hbm =  LoadBitmap( ghInst, MAKEINTRESOURCE( IDB_CHECKSTATES ));

        if( !hbm ){
            DBGMSG( DBG_ERROR, ( "PortsLV.bSetUI: LoadBitmap failed %d\n",
                    GetLastError( )));
            return FALSE;
        }

        //
        // Add the bitmaps to the image list.
        //
        ImageList_AddMasked( himlState, hbm, RGB( 255, 0, 0 ));

        SendMessage( _hwndLV, LVM_SETIMAGELIST, LVSIL_STATE, (LPARAM)himlState );

        DeleteObject( hbm );

    }

    INT iNumColumns = _bTwoColumnMode ? 2 : kPortHeaderMax;

    //
    // Initialize the LV_COLUMN structure.
    //
    LV_COLUMN lvc;
    lvc.mask    = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
    lvc.fmt     = LVCFMT_LEFT;
    lvc.cx      = kPortHeaderWidthDefault;

    RECT rc;
    if( !GetClientRect( _hwndLV, &rc )){

        DBGMSG( DBG_WARN, ( "PortsLV.bSetUI: GetClientRect failed %d\n", GetLastError( )));
        return FALSE;
    }

    //
    // Calculate the column width, less the scroll bar width.
    //
    lvc.cx = ( ( rc.right - rc.left ) - GetSystemMetrics( SM_CYVSCROLL ) ) / iNumColumns;

    TStatusB bStatus;
    TString strHeader;

    for( INT iCol = 0; iCol < iNumColumns; ++iCol ){

        bStatus DBGCHK = strHeader.bLoadString( ghInst, IDS_PHEAD_BEGIN + iCol );
        lvc.pszText = (LPTSTR)(LPCTSTR)strHeader;
        lvc.iSubItem = iCol;

        if( ListView_InsertColumn( _hwndLV, iCol, &lvc ) == -1 ){

            DBGMSG( DBG_WARN, ( "PortsLV.bSetUI: LV_Insert failed %d\n", GetLastError( )));

            return FALSE;
        }
    }

    //
    // Enable full row selection.
    //
    DWORD dwExStyle = ListView_GetExtendedListViewStyle( _hwndLV );
  	ListView_SetExtendedListViewStyle( _hwndLV, dwExStyle | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP );

    //
    // !!LATER!!
    // We should read an override flag from the registry.
    //
    _bHideFaxPorts = TRUE;

    return TRUE;
}

VOID
TPortsLV::
vAddPortToListView(
    IN LPCTSTR pszName,
    IN LPCTSTR pszMonitor,
    IN LPCTSTR pszDescription,
    IN LPCTSTR pszPrinters
    )
{
    if( !pszName || !pszName[0] ){
        DBGMSG( DBG_WARN, 
                ( "PortsLV.vAddPortToListView: pszName "TSTR" invalid\n", 
                DBGSTR( pszName )));
        return;
    }

    //
    // If we are to hide the fax ports and this is a fax 
    // port then just return with out adding the port to 
    // the list view.
    //
    if( _bHideFaxPorts && bIsFaxPort( pszName, pszMonitor ) )
    {
        DBGMSG( DBG_TRACE, ( "PortsLV.vAddPortToListView: fax device being removed.\n" ) );
        return;
    }

    //
    // Add this port to the port data list.
    //
    TPortData *pPortData = AddPortDataList( pszName, pszMonitor, pszDescription, pszPrinters );

    LV_ITEM lvi;

    if( _bSelectionState ){
        lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_PARAM;
    } else {
        lvi.mask = LVIF_TEXT | LVIF_PARAM;
    }

    lvi.state       = kStateUnchecked;
    lvi.pszText     = (LPTSTR)pszName;
    lvi.iItem       = _cLVPorts;
    lvi.iSubItem    = 0;
    lvi.lParam      = (LPARAM)pPortData;

    ListView_InsertItem( _hwndLV, &lvi );
    ListView_SetItemText( _hwndLV, _cLVPorts, 1, (LPTSTR)pszDescription );

    if( !_bTwoColumnMode )
    {
        ListView_SetItemText( _hwndLV, _cLVPorts, 2, (LPTSTR)pszPrinters );
    }

    ++_cLVPorts;
}


VOID
TPortsLV::
vDeletePortFromListView(
    IN LPCTSTR pszName
    )
{

    //
    // Locate the port in the list view.
    //
    INT iItem = iFindPort ( pszName );

    if( iItem != -1 ){

        //
        // Delete this port from the port data list.
        //
        DeletePortDataList( pszName );

        ListView_DeleteItem( _hwndLV, iItem );

        //
        // Select next item.  If the item we just deleted is the last item,
        // we need to select the previous one.
        //
        // If we deleted the last item, leave it as is.
        //
        if( ListView_GetItemCount( _hwndLV ) == iItem && 
            iItem > 0 ) {
            --iItem;
        }

        ListView_SetItemState( _hwndLV, 
                               iItem, 
                               LVIS_SELECTED | LVIS_FOCUSED,
                               LVIS_SELECTED | LVIS_FOCUSED );
    }
}

INT
TPortsLV::
iFindPort(
    IN LPCTSTR pszPort
    )
/*++

Routine Description:

    Located the specified port name in the list view.

Arguments:

    pszPort - Port name to locate.

Return Value:

    iItem id if found, -1 if item was not found.

--*/
{
    SPLASSERT( pszPort );

    LV_FINDINFO lvfi;

    lvfi.flags = LVFI_STRING;
    lvfi.psz = pszPort;

    INT iItem = ListView_FindItem( _hwndLV, -1, &lvfi );

    if( iItem == -1 ){
        DBGMSG( DBG_WARN, ( "PortsLV.iFindPort: port "TSTR" not found\n", pszPort ));
    }

    return iItem;
}


INT
TPortsLV::
iCheckPort(
    IN LPCTSTR pszPort
    )
/*++

Routine Description:

    Places the check mark next to a port in the list view.

Arguments:

    pszPort - Port to check.

Return Value:

    iItem checked, -1 == error.

--*/
{
    //
    // Locate the port in the list view.
    //
    INT iItem = iFindPort ( pszPort );

    if( iItem != -1 ){

        //
        // Set the item selection state.
        //
        ListView_SetItemState( _hwndLV,
                               iItem,
                               kStateChecked,
                               kStateMask );

        //
        // Try and make as many ports visible as possible.
        //
        ListView_EnsureVisible( _hwndLV,
                                iItem,
                                FALSE );
    }

    return iItem;
}

INT
TPortsLV::
iSelectPort(
    IN LPCTSTR pszPort
    )
/*++

Routine Description:

    Select the port in the list view.

Arguments:

    pszPort - Port to check.

Return Value:

    iItem checked, -1 == error.

--*/
{
    //
    // Locate the port in the list view.
    //
    INT iItem = iFindPort ( pszPort );

    if( iItem != -1 ){

        //
        // Select the port specified by pszPort.
        //
        ListView_SetItemState( _hwndLV,
                               iItem,
                               LVIS_SELECTED | LVIS_FOCUSED,
                               LVIS_SELECTED | LVIS_FOCUSED );
        //
        // Try and make as many ports visible as possible.
        //
        ListView_EnsureVisible( _hwndLV,
                                iItem,
                                FALSE );
    }

    return iItem;
}

VOID
TPortsLV::
vGetPortList(
        OUT LPTSTR pszPortList,
    IN      COUNT cchPortList
    )
{
    INT cPorts = 0;
    DWORD i;

    LV_ITEM lvi;

    LPTSTR pszPort = pszPortList;
    DWORD cchSpaceLeft = cchPortList - 1;
    DWORD cchLen;
    lvi.iSubItem = 0;

    DWORD cItems = ListView_GetItemCount( _hwndLV );

    for( pszPortList[0] = 0, i=0; i<cItems; ++i ){

        if( ListView_GetItemState( _hwndLV, i, kStateMask ) & kStateChecked ){

            lvi.pszText = pszPort;
            lvi.cchTextMax = cchSpaceLeft;

            cchLen = (DWORD)SendMessage( _hwndLV,
                                         LVM_GETITEMTEXT,
                                         (WPARAM)i,
                                         (LPARAM)&lvi );

            if( cchLen + 1 > cchSpaceLeft ){

                DBGMSG( DBG_WARN, ( "PortsLV.iGetPorts: Out of string space!\n" ));
                return;
            }

            pszPort += cchLen;
            cchSpaceLeft -= cchLen+1;

            *pszPort = TEXT( ',' );
            ++pszPort;
            ++cPorts;
        }
    }

    //
    // If we had any ports, back up to remove the last comma.
    //
    if( cPorts ){
        --pszPort;
    }

    //
    // Null terminate.
    //
    *pszPort = 0;

}

BOOL
TPortsLV::
bReadUI(
    OUT TString &strPortString,
    IN  BOOL    bSelectedPort
    )
{
    TCHAR szPortList[kPortListMax];
    szPortList[0] = 0;

    //
    // If we are in single select mode just return
    // the selected port.
    //
    if( bSelectedPort )
    {
        (VOID)bGetSelectedPort( szPortList, COUNTOF( szPortList ) );
    }
    else
    {
        //
        // Get the list of check ports from the list view.
        //
        vGetPortList( szPortList, COUNTOF( szPortList ) );
    }

    //
    // Update the port list.
    //
    return strPortString.bUpdate( szPortList );

}

VOID
TPortsLV::
vItemClicked(
    IN INT iItem
    )

/*++

Routine Description:

    User clicked in listview.  Check if item state should
    be changed.

    The item will also be selected.

Arguments:

    iItem - Item that has been clicked.

Return Value:

--*/

{
    if( iItem == -1 ){
        DBGMSG( DBG_WARN, ( "PortsLV.vItemClicked: -1 passed in\n" ));
        return;
    }

    //
    // If in single selection mode clear all items checked and only
    // check the specified item.
    //
    if( _bSingleSelection ){

        DWORD cItems = ListView_GetItemCount( _hwndLV );

        for( UINT i=0; i<cItems; ++i ){

            if( ListView_GetItemState( _hwndLV, i, kStateMask ) & kStateChecked ){

                if( iItem == (INT)i ){
                    continue;
                } else {

                    ListView_SetItemState( _hwndLV,
                                           i,
                                           kStateUnchecked,
                                           kStateMask );
                }
            }
        }
    }

    //
    // Retrieve the old state, toggle it, then set it.
    //
    DWORD dwState = ListView_GetItemState( _hwndLV,
                                           iItem,
                                           kStateMask );

    //
    // When we are in single select mode we want to always check 
    // the currently selected item.
    //
    if( !_bSingleSelection )
    {
        dwState = ( dwState == kStateChecked ) ?
                      kStateUnchecked | LVIS_SELECTED | LVIS_FOCUSED :
                      kStateChecked | LVIS_SELECTED | LVIS_FOCUSED;
    }
    else
    {
        dwState = kStateChecked | LVIS_SELECTED | LVIS_FOCUSED;
    }

    //
    // Set the new item state.
    //
    ListView_SetItemState( _hwndLV,
                           iItem,
                           dwState,
                           kStateMask | LVIS_SELECTED | LVIS_FOCUSED );

}

COUNT
TPortsLV::
cSelectedPorts(
    VOID
    )
/*++

Routine Description:

    Returns the number of items which have a check mark
    next to them.

Arguments:

    None.

Return Value:

    Return the number of checked items.

--*/
{
    DWORD cItems = ListView_GetItemCount( _hwndLV );
    COUNT cItemsSelected = 0;
    DWORD i;

    for( i = 0; i < cItems; ++i )
    {
        if( ListView_GetItemState( _hwndLV, i, kStateMask ) & kStateChecked )
        {
            ++cItemsSelected;
        }
    }

    return cItemsSelected;
}

COUNT
TPortsLV::
cSelectedItems(
    VOID
    )
/*++

Routine Description:

    Returns the number of items which are currently selected.

Arguments:

    None.

Return Value:

    Return the number of selected items.

--*/
{
    DWORD cItems = ListView_GetItemCount( _hwndLV );
    COUNT cItemsSelected = 0;
    DWORD i;

    for( i = 0; i < cItems; ++i )
    {
        if( ListView_GetItemState( _hwndLV, i, LVIS_SELECTED ) & LVIS_SELECTED )
        {
            ++cItemsSelected;
        }
    }

    return cItemsSelected;
}

VOID
TPortsLV::
vRemoveAllChecks(
    VOID
    )
/*++

Routine Description:

    Removes all the check marks for the list view.

Arguments:

    None.

Return Value:

    Nothing.

--*/
{
    DWORD cItems = ListView_GetItemCount( _hwndLV );
    COUNT cItemsSelected = 0;
    DWORD i;

    for( i=0; i<cItems; ++i ){

        if( ListView_GetItemState( _hwndLV, i, kStateMask ) & kStateChecked ){

            ListView_SetItemState( _hwndLV,
                                   i,
                                   kStateUnchecked,
                                   kStateMask );
        }
    }
}

VOID
TPortsLV::
vSetFocus(
    VOID
    )
{
    SetFocus( _hwndLV );
}

BOOL
TPortsLV::
bGetSelectedPort(
    OUT LPTSTR pszPort,
    IN  COUNT cchPort
    )

/*++

Routine Description:

    Retrieve the currently selected port in the list view.

Arguments:

    pszPort - TCHAR array to receive port

    cchPort - COUNTOF port string.
    
Return Value:

    TRUE - success, FALSE = fail.

--*/

{
    INT iItem = ListView_GetNextItem( _hwndLV,
                                      -1,
                                      LVNI_SELECTED );
    if( iItem == -1 ){

        DBGMSG( DBG_WARN,
                ( "PrinterPort.bGetSelectedPort: Unable to retrieve next selected item %d\n",
                  GetLastError( )));
        return FALSE;
    }

    ListView_GetItemText( _hwndLV,
                          iItem,
                          0,
                          pszPort,
                          cchPort );
    return TRUE;
}

BOOL
TPortsLV::
bGetSelectedPort(
    OUT LPTSTR pszPort,
    IN  COUNT cchPort,
    INT *pItem
    )

/*++

Routine Description:

    Retrieve the currently selected port in the list view.

Arguments:

    pszPort - TCHAR array to receive port

    cchPort - COUNTOF port string.

    iItem - Index of the item with which to begin the search
    
Return Value:

    TRUE - success, FALSE = fail.
    iItem will be set to the new found index.

--*/

{
    INT iItem = *pItem;

    iItem = ListView_GetNextItem( _hwndLV,
                                  iItem,
                                  LVNI_SELECTED );
    if( iItem == -1 ){

        DBGMSG( DBG_WARN,
                ( "PrinterPort.bGetSelectedPort: Unable to retrieve next selected item %d\n",
                  GetLastError( )));
        return FALSE;
    }

    ListView_GetItemText( _hwndLV,
                          iItem,
                          0,
                          pszPort,
                          cchPort );

    *pItem = iItem;

    return TRUE;
}

BOOL
TPortsLV::
bHandleNotifyMessage(
    LPARAM lParam
    )
{
    BOOL bStatus = TRUE;
    LPNMHDR pnmh = (LPNMHDR)lParam;

    switch( pnmh->code )
    {
    case NM_DBLCLK:

        vHandleItemClicked( lParam );

        if( _wmDoubleClickMsg )                                 
        {
            PostMessage( _hwnd, WM_COMMAND, _wmDoubleClickMsg, 0 );
        }
        break;

    case NM_CLICK:

        vHandleItemClicked( lParam );

        if( _wmSingleClickMsg )                                 
        {
            PostMessage( _hwnd, WM_COMMAND, _wmSingleClickMsg, 0 );
        }
        break;

    case LVN_KEYDOWN:
        {
            LV_KEYDOWN* plvnkd = (LV_KEYDOWN *)lParam;

            if( _bSelectionState && _bAllowSelectionChange )
            {
                //
                // !! LATER !!
                //
                // Is this the best way to check whether the ALT
                // key is _not_ down?
                //
                if( plvnkd->wVKey == TEXT( ' ' ) &&
                    !( GetKeyState( VK_LMENU ) & 0x80000000 ) &&
                    !( GetKeyState( VK_RMENU ) & 0x80000000 ))
                {
                    vItemClicked( ListView_GetNextItem( _hwndLV, -1, LVNI_SELECTED ));
                }
            }

            //
            // If the delete key was used then post a message to 
            // appropriate window with the specified message.
            //
            if(plvnkd->wVKey == VK_DELETE )
            {
                if( _wmDeleteKeyMsg )                                 
                {
                    PostMessage( _hwnd, WM_COMMAND, _wmDeleteKeyMsg, 0 );
                }
            }
        }
        break;

    case LVN_COLUMNCLICK:
        {
            NM_LISTVIEW *pNm = (NM_LISTVIEW *)lParam;
            (VOID)bListViewSort( pNm->iSubItem );
        }
        break;

    default:
        bStatus = FALSE;
        break;
    }

    return bStatus;
}


VOID
TPortsLV::
vHandleItemClicked( 
    IN LPARAM lParam 
    )
{
    if( _bSelectionState && _bAllowSelectionChange )
    {
        LV_HITTESTINFO lvhti;
        DWORD dwPos = GetMessagePos();
        POINTS &pt = MAKEPOINTS(dwPos);

        lvhti.pt.x = pt.x;
        lvhti.pt.y = pt.y;

        MapWindowPoints( HWND_DESKTOP, _hwndLV, &lvhti.pt, 1 );

        INT iItem = ListView_HitTest( _hwndLV, &lvhti );

        if( iItem != -1 )
        {
            vItemClicked( iItem );
        }
    }
}

VOID
TPortsLV::
vInsertPortsByMask( 
    IN UINT cPorts,
    IN PORT_INFO_2 pPorts[],
    IN UINT cPrinters,
    IN PRINTER_INFO_2 pPrinters[],
    IN DWORD dwLevel,
    IN LPCTSTR pszTemplate,
    IN LPCTSTR pszDescription
    )
{
    TString         strPrinters;

    //
    // Go through the ports and add them.
    //
    for( UINT i=0; i<cPorts; ++i )
    {
        if( dwLevel == 2 )
        {
            //
            // Check if this port has been added
            //
            if( NULL == pPorts[i].pPortName )
            {
                continue;
            }

            //
            // Check if the port name matches the template
            //
            if( pszTemplate && !bMatchTemplate( pszTemplate, pPorts[i].pPortName ) )
            {
                continue;
            }

            //
            // Assign proper description 
            //
            LPCTSTR pszDescr = pszDescription;
            if( !pszDescr )
            {
                pszDescr = pPorts[i].pDescription;
            }

            //
            // If we have printers on this machine.
            //
            if( pPrinters && cPrinters )
            {
                vPrintersUsingPort( strPrinters, pPrinters, cPrinters, pPorts[i].pPortName );
            }                

            vAddPortToListView( pPorts[i].pPortName, pPorts[i].pMonitorName, pszDescr, strPrinters  );

            //
            // Mark the port as added
            //
            pPorts[i].pPortName = NULL;
        }
        else
        {
            //
            // Check if this port has been added
            //
            if( NULL == ((PPORT_INFO_1)pPorts)[i].pName )
            {
                continue;
            }

            //
            // Check if the port name matches the template
            //
            if( pszTemplate && !bMatchTemplate( pszTemplate, ((PPORT_INFO_1)pPorts)[i].pName ) )
            {
                continue;
            }

            //
            // If we have printers on this machine.
            //
            if( pPrinters && cPrinters )
            {
                vPrintersUsingPort( strPrinters, pPrinters, cPrinters, ((PPORT_INFO_1)pPorts)[i].pName );
            }                

            vAddPortToListView( ((PPORT_INFO_1)pPorts)[i].pName, gszNULL, gszNULL, strPrinters );

            //
            // Mark the port as added
            //
            ((PPORT_INFO_1)pPorts)[i].pName = NULL;
        }
    }
}

BOOL
TPortsLV::
bDeletePorts(
    IN HWND     hDlg,
    IN LPCTSTR  pszServer
    )
/*++

Routine Description:

    Delete selected ports for given print server.

Arguments:

    hDlg - dialog handle for port tab.
    pszServer - print server name.

Return Value:

    True if at least one port is deleted, false otherwise.

--*/

{
    TStatusB    bStatus;
    TCHAR       szPortName[TPortsLV::kPortNameMax];
    COUNT       cItems;
    INT         iItem = -1;
    INT         i;
    BOOL        bFailed = FALSE;
    BOOL        bDeleted = FALSE;

    //
    // Check whether multi ports are selected
    //
    cItems = cSelectedItems();

    if(cItems == 0)
    {
        return FALSE;
    }

    //
    // Get the first selected port name to compose the warning message
    //
    bStatus DBGCHK = bGetSelectedPort( szPortName, COUNTOF( szPortName ) );

    if( IDYES == iMessage( hDlg,
                           IDS_DELETE_PORT_TITLE,
                           cItems > 1 ? IDS_DELETE_PORTN : IDS_DELETE_PORT,
                           MB_YESNO | MB_ICONQUESTION,
                           kMsgNone,
                           NULL,
                           szPortName ))
    {
        //
        // Try to delete all selected items 
        //
        for( i = 0; i < (INT)cItems ; i++ )
        {
            //
            // Get each selected port name
            //
            bStatus DBGCHK = bGetSelectedPort( szPortName, COUNTOF( szPortName ), &iItem );

            SPLASSERT( bStatus );
            SPLASSERT( iItem != -1 );
            
            //
            // Attempt to delete the selected port.
            //
            bStatus DBGCHK = DeletePort( (LPTSTR)pszServer, hDlg, szPortName );

            if( bStatus )
            {
                //
                // Succeeded, refresh the ports UI by deleting the port.
                //
                vDeletePortFromListView( szPortName );

                //
                // Decrease the iItem because deleting one item in the list
                //
                iItem--;
                bDeleted = TRUE;
            } 
            else 
            {
                if( GetLastError() != ERROR_CANCELLED )
                {
                    bFailed = TRUE;
                }
            }
        }

        //
        // Only show an error message if the did not cancel the
        // the action.
        //
        if( bFailed )
        {
            iMessage( hDlg,
                      IDS_DELETE_PORT_TITLE,
                      cItems > 1 ? IDS_ERR_DELETE_PORTN : IDS_ERR_DELETE_PORT,
                      MB_OK | MB_ICONEXCLAMATION,
                      kMsgGetLastError,
                      gaMsgErrMapPorts);
        }

        bStatus DBGNOCHK = bDeleted;
    }
    else
    {
        bStatus DBGNOCHK = FALSE;
    }

    return bStatus;
}

BOOL
TPortsLV::
bConfigurePort(
    IN HWND     hDlg,
    IN LPCTSTR  pszServer
    )
{
    static MSG_ERRMAP aMsgErrMapPorts[] = {
        ERROR_INVALID_PARAMETER, IDS_ERR_PORT_DOES_NOT_EXIST,
        0, 0
    };

    TStatusB bStatus;
    TCHAR szPortName[TPortsLV::kPortNameMax];

    bStatus DBGCHK = bGetSelectedPort( szPortName, COUNTOF( szPortName ) );

    if( bStatus )
    {
        bStatus DBGCHK = ConfigurePort( (LPTSTR)pszServer,
                                        hDlg,
                                        szPortName );

        if( !bStatus )
        {
            if( GetLastError() != ERROR_CANCELLED )
            {
                iMessage( hDlg,
                          IDS_CONFIGURE_PORT_TITLE,
                          IDS_ERR_CONFIG_PORT,
                          MB_OK | MB_ICONEXCLAMATION,
                          kMsgGetLastError,
                          aMsgErrMapPorts );
            }
        }
    } 
    else 
    {
        DBGMSG( DBG_WARN, ( "PrinterPorts.vConfigure: failed %d\n", GetLastError( )));
    }

    return bStatus;
}

VOID
TPortsLV::
vPrintersUsingPort(
    IN OUT  TString        &strPrinters,
    IN      PRINTER_INFO_2 *pPrinterInfo,
    IN      DWORD           cPrinterInfo,
    IN      LPCTSTR         pszPortName
    )
/*++

Routine Description:

    Builds a comma separated string of all the printers
    using the specified port.

Arguments:

    strPrinters - TString refrence where to return resultant string.
    pPrinterInfo - Pointer to a printer info level 2 structure array.
    cPrinterInfo - Number of printers in the printer info 2 array.
    pszPortName - Pointer to string or port name to match.

Return Value:

    Nothing.

Notes:
    If no printer is using the specfied  port the string refrence
    will contain an empty string.

--*/
{
    SPLASSERT( pPrinterInfo );
    SPLASSERT( pszPortName );

    LPTSTR psz;
    LPTSTR pszPort;
    LPTSTR pszPrinter;
    UINT i;
        
    //
    // Clear the current printer buffer.
    //
    TStatusB bStatus;
    bStatus DBGCHK = strPrinters.bUpdate( NULL );

    //
    // Traverse the printer info array.
    //
    for( i = 0; i < cPrinterInfo; i++ ){

        for( psz = pPrinterInfo[i].pPortName; psz && *psz; ){

            //
            // Look for a comma if found terminate the port string.
            //
            pszPort = psz;
            psz = _tcschr( psz, TEXT( ',' ) );

            if( psz ){
                *psz = 0;
            }

            //
            // Check for a port match.
            //
            if( !_tcsicmp( pszPort, pszPortName ) ){

                //
                // Point to printer name.
                //
                pszPrinter = pPrinterInfo[i].pPrinterName;

                //
                // Strip the server name here.
                //
                if( pPrinterInfo[i].pPrinterName[0] == TEXT( '\\' ) &&
                    pPrinterInfo[i].pPrinterName[1] == TEXT( '\\' ) ){

                    //
                    // Locate the printer name.
                    //
                    pszPrinter = _tcschr( pPrinterInfo[i].pPrinterName+2, TEXT( '\\' ) );
                    pszPrinter = pszPrinter ? pszPrinter+1 : pPrinterInfo[i].pPrinterName;

                }

                //
                // If this is the first time do not place a comma separator.
                //
                if( !strPrinters.bEmpty() ){

                    bStatus DBGCHK = strPrinters.bCat( TEXT( ", " ) );

                    if( !bStatus ){
                        DBGMSG( DBG_WARN, ( "Error cat string line: %d file : %s.\n", __LINE__, __FILE__ ) );
                        break;
                    }
                }

                //
                // Tack on the printer name
                //
                bStatus DBGCHK = strPrinters.bCat( pszPrinter );

                if( !bStatus ){
                    DBGMSG( DBG_WARN, ( "Error cat string line : %d file : %s.\n", __LINE__, __FILE__ ) );
                    break;
                }
            }

            //
            // Replace the previous comma.
            //
            if( psz ){
                *psz = TEXT( ',' );
                ++psz;
            }
        }
    }
}

VOID
TPortsLV::
vSetSingleSelection(
    IN BOOL bSingleSelection
    )
/*++

Routine Description:

    Set the list view into single selection mode.

Arguments:

    bSingleSelection - TRUE single selection, FALSE multi selection.

Return Value:

    Nothing.

--*/
{
    _bSingleSelection = bSingleSelection;
}

BOOL
TPortsLV::
bGetSingleSelection(
    VOID
    )
/*++

Routine Description:

    Get the current list view selection mode.

Arguments:

    None.

Return Value:

    TURE in single selection mode, FALSE in multi selection mode.

--*/
{
    return _bSingleSelection;
}

VOID
TPortsLV::
vCreatePortDataList(
    VOID
    )
/*++

Routine Description:

    Initialize the port data list.

Arguments:

    None.

Return Value:

    Nothing.

--*/
{
    DBGMSG( DBG_TRACE, ( "PortsLV::vCreatePortDataList\n" ) );

    PortDataList_vReset();
}

VOID
TPortsLV::
vDestroyPortDataList(
    VOID
    )
/*++

Routine Description:

    Destroy the port data list.

Arguments:

    None.

Return Value:

    Nothing.

--*/
{
    DBGMSG( DBG_TRACE, ( "PortsLV::vDestroyPortDataList\n" ) );

    TIter Iter;
    TPortData *pPortData;

    for( PortDataList_vIterInit( Iter ), Iter.vNext(); Iter.bValid(); )
    {
        pPortData = PortDataList_pConvert( Iter );
        Iter.vNext();
        delete pPortData;
    }
}

BOOL
TPortsLV::
bListViewSort(
    IN UINT uColumn
    )
/*++

Routine Description:

    This function is called to sort the items.  The current
    column indes is specified to indicated witch column to 
    sort.

Arguments:

    Column index to sort.

Return Value:

    TRUE list is sorted, FALSE error occurred.

--*/
{
    DBGMSG( DBG_TRACE, ( "PortsLV::bListViewSort Column %d\n", uColumn ) );

    //
    // Set the surrent column number.
    //
    _uCurrentColumn = uColumn;

    //
    // Tell the list view to sort.
    //
    TStatusB bStatus;
    bStatus DBGCHK = ListView_SortItems( _hwndLV, iCompareProc, (LPARAM)this );

    //
    // Toggle the specified column sort state.
    //
    _ColumnSortState.bToggle( uColumn );

    return bStatus;
}

INT 
CALLBACK 
TPortsLV::
iCompareProc(
    IN LPARAM lParam1, 
    IN LPARAM lParam2, 
    IN LPARAM RefData
    )
/*++

Routine Description:

    List view defined compare routine.

Arguments:

    None.

Return Value:

    Nothing.

--*/
{
    TPortData   *pPortData1 = reinterpret_cast<TPortData *>( lParam1 );
    TPortData   *pPortData2 = reinterpret_cast<TPortData *>( lParam2 );
    TPortsLV    *pPortsLV   = reinterpret_cast<TPortsLV *>( RefData );
    INT         iResult     = 0;
    LPCTSTR     strName1    = NULL;
    LPCTSTR     strName2    = NULL;

    if( pPortsLV && pPortData1 && pPortData2 )
    {
        BOOL bStatus = TRUE;

        switch( pPortsLV->_uCurrentColumn )
        {

        case 0:
            strName1 = pPortData1->_strName;
            strName2 = pPortData2->_strName;
            break;

        case 1:
            strName1 = pPortData1->_strDescription;
            strName2 = pPortData2->_strDescription;
            break;

        case 2:
            strName1 = pPortData1->_strPrinters;
            strName2 = pPortData2->_strPrinters;
            break;

        default:
            bStatus = FALSE;
            break;
    
        }

        if( bStatus )
        {
            if( pPortsLV->_ColumnSortState.bRead( pPortsLV->_uCurrentColumn ) )
                iResult = _tcsicmp( strName2, strName1 );
            else
                iResult = _tcsicmp( strName1, strName2 );
        }
    }

    return iResult;
}

TPortsLV::TPortData *
TPortsLV::
AddPortDataList(
    IN LPCTSTR pszName,
    IN LPCTSTR pszMonitor,
    IN LPCTSTR pszDescription,
    IN LPCTSTR pszPrinters
    )
/*++

Routine Description:

    Add port to port data list.

Arguments:

    pszName         - pointer to port name.
    pszDescription  - pointer to description string.
    pszPrinters     - pointer to printers using this port string.

Return Value:

    TRUE port added to data list, FALSE error occurred.

--*/
{
    DBGMSG( DBG_TRACE, ( "PortsLV::AddPortDataList\n" ) );

    //
    // Allocate the port data.
    //
    TPortData *pPortData = new TPortData( pszName, pszMonitor, pszDescription, pszPrinters );

    //
    // If valid object created.
    //
    if( VALID_PTR( pPortData ) )
    {
        //
        // Add the port data to the list.
        //
        PortDataList_vAppend( pPortData );
    }
    else
    {
        //
        // The object may have been allocated, however failed construction.
        //
        delete pPortData;
        pPortData = NULL;
    }

    return pPortData;
}

BOOL
TPortsLV::
DeletePortDataList(
    IN LPCTSTR pszName
    )
/*++

Routine Description:

    Delete the specified port from the port data list.

Arguments:

    pszName - pointer to port name.

Return Value:

    TRUE port delete, FALSE error occurred.

--*/
{
    DBGMSG( DBG_TRACE, ( "PortsLV::DeletePortDataList\n" ) );

    BOOL bStatus = FALSE;
    TIter Iter;

    for( PortDataList_vIterInit( Iter ), Iter.vNext(); Iter.bValid(); Iter.vNext() )
    {
        TPortData *pPortData = PortDataList_pConvert( Iter );

        if( pPortData->_strName == pszName )
        {
            DBGMSG( DBG_TRACE, ( "PortsLV::DeletePortDataList Port "TSTR" deleted\n", pszName ) );
            delete pPortData;
            bStatus = TRUE;
            break;
        }                
    }

    return bStatus;
}

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

    Port Data helper class.

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

TPortsLV::TPortData::
TPortData(
    IN LPCTSTR pszName,
    IN LPCTSTR pszMonitor,
    IN LPCTSTR pszDescription,
    IN LPCTSTR pszPrinters
    )
{
    DBGMSG( DBG_TRACE, ( "PortsLV::TPortData ctor\n" ) );
    _strName.bUpdate( pszName );
    _strMonitor.bUpdate( pszMonitor );
    _strDescription.bUpdate( pszDescription );
    _strPrinters.bUpdate( pszPrinters );
}

TPortsLV::TPortData::
~TPortData(
    VOID
    )
{
    DBGMSG( DBG_TRACE, ( "PortsLV::TPortData dtor\n" ) );
    //
    // If we are linked then remove ourself.
    //
    if( PortData_bLinked() )
    {
        PortData_vDelinkSelf();
    }
}

BOOL
TPortsLV::TPortData::
bValid(
    VOID
    )
{
    return VALID_OBJ( _strName ) &&
           VALID_OBJ( _strMonitor ) &&
           VALID_OBJ( _strDescription ) &&
           VALID_OBJ( _strPrinters );

}