//*******************************************************************************************
//
// Filename : Sfview.cpp
//	
//				Implementation file for CSFView
//
// Copyright (c) 1994 - 1996 Microsoft Corporation. All rights reserved
//
//*******************************************************************************************

#include "Pch.H"

#include "SFView.H"
#include "SFVWnd.H"

#include "Resource.H"

#include "ThisGuid.H"

#include "SFView.H"

struct SFSTATE_HDR
{
    CLSID clsThis;
    SFSTATE sfState;
    UINT nCols;
} ;


CSFView::CSFView(IShellFolder *psf, IShellFolderViewCallback *psfvcb) :
    m_psf(psf), m_erFolder(psf), m_erCB(psfvcb), m_pCDB(NULL), m_cView(this),
    m_uState(SVUIA_DEACTIVATE), m_pcmSel(NULL), m_cAccel(IDA_MAIN)
{
    m_psfvcb = psfvcb;
    if (psfvcb)
    {
        psfvcb->AddRef();
    }
    
    psf->AddRef();
    
    m_aParamSort = DPA_Create(4);
    m_sfState.lParamSort = 0;
}


CSFView::~CSFView()
{
    ReleaseSelContextMenu();
}


STDMETHODIMP CSFView::QueryInterface(REFIID riid, void ** ppvObj)
{
    static const IID *apiid[] = { &IID_IShellView, NULL };
    LPUNKNOWN aobj[] = { (IShellView *)this };
    
    return(QIHelper(riid, ppvObj, apiid, aobj));
}


STDMETHODIMP_(ULONG) CSFView::AddRef()
{
    return(AddRefHelper());
}


STDMETHODIMP_(ULONG) CSFView::Release()
{
    return(ReleaseHelper());
}


STDMETHODIMP CSFView::GetWindow(HWND * lphwnd)
{
    return(E_NOTIMPL);
}


STDMETHODIMP CSFView::ContextSensitiveHelp(BOOL fEnterMode)
{
    return(E_NOTIMPL);
}


//*****************************************************************************
//
// CSFView::TranslateAccelerator
//
// Purpose:
//         Handle the accelerator keystrokes
//
//
// Parameters:
//        LPMSG lpmsg    -    message structure
//
//
// Comments:
//
//*****************************************************************************

STDMETHODIMP CSFView::TranslateAccelerator(LPMSG lpmsg)
{
    return(m_cAccel.TranslateAccelerator(m_cView, lpmsg) ? S_OK : S_FALSE);
}


STDMETHODIMP CSFView::EnableModeless(BOOL fEnable)
{
    return(E_NOTIMPL);
}


//*****************************************************************************
//
// CSFView:UIActivate
//
// Purpose:
//        The explorer calls this member function whenever the activation         
//  state of the view window is changed by a certain event that is           
//  NOT caused by the shell view itself.
//
//
// Parameters:
//
//        UINT uState    -    UI activate flag
//
// Comments:
//
//*****************************************************************************

STDMETHODIMP CSFView::UIActivate(UINT uState)
{
    if (uState)
    {
        OnActivate(uState);
    }
    else
    {
        OnDeactivate();
    }
    
    return S_OK;
}


STDMETHODIMP CSFView::Refresh()
{
    FillList(FALSE);
    return S_OK;// yes, this is potentially bad, but we risk app compat problems if we change it. 2000/02/10 -ccooney
}


//*****************************************************************************
//
// CSFView::CreateViewWindow
//
// Purpose:
//
//        called by IShellBrowser to create a contents pane window
//
// Parameters:
//    
//    IShellView  *lpPrevView    -    previous view
//    LPCFOLDERSETTINGS lpfs     -    folder settings for the view
//    IShellBrowser *psb         -    pointer to the shell browser
//    RECT * prcView             -    view Rectangle
//    HWND * phWnd               -    pointer to Window handle
//
//
// Comments:
//
//*****************************************************************************

STDMETHODIMP CSFView::CreateViewWindow(IShellView  *lpPrevView,
                                       LPCFOLDERSETTINGS lpfs, IShellBrowser  * psb,
                                       RECT * prcView, HWND  *phWnd)
{
    *phWnd = NULL;
    
    if ((HWND)m_cView)
    {
        return(E_UNEXPECTED);
    }
    
    m_fs = *lpfs;
    m_psb = psb;
    
    // get the main window handle from shell browser
    
    psb->GetWindow(&m_hwndMain);
    
    // bring up the contents pane
    
    if (!m_cView.DoModeless(IDD_VIEW, m_hwndMain))
    {
        return(E_OUTOFMEMORY);
    }
    
    *phWnd = m_cView;
    
    // map the current view mode into menu id and set the contents pane
    // view mode accordingly
    
    OnCommand(NULL, GET_WM_COMMAND_MPS(GetMenuIDFromViewMode(), 0, 0));
    
    AddColumns();
    
    RestoreViewState();
    
    // size the contents pane
    SetWindowPos(m_cView, NULL, prcView->left, prcView->top,
        prcView->right-prcView->left, prcView->bottom-prcView->top,
        SWP_NOZORDER|SWP_SHOWWINDOW);
    
    FillList(TRUE);
    
    return(NOERROR);
}


STDMETHODIMP CSFView::DestroyViewWindow()
{
    if (!(HWND)m_cView)
    {
        return(E_UNEXPECTED);
    }
    
    m_cView.DestroyWindow();
    
    return(NOERROR);
}


STDMETHODIMP CSFView::GetCurrentInfo(LPFOLDERSETTINGS lpfs)
{
    *lpfs = m_fs;
    
    return(NOERROR);
}


STDMETHODIMP CSFView::AddPropertySheetPages(DWORD dwReserved,
                                            LPFNADDPROPSHEETPAGE lpfn, LPARAM lparam)
{
    return(E_NOTIMPL);
}


STDMETHODIMP CSFView::SaveViewState()
{
    SFSTATE_HDR hdr;
    LPSTREAM pstm;
    
    HRESULT hres = m_psb->GetViewStateStream(STGM_WRITE, &pstm);
    if (FAILED(hres))
    {
        return(hres);
    }
    CEnsureRelease erStr(pstm);
    
    pstm->Write(&hdr, sizeof(hdr), NULL);
    
    hdr.clsThis = CLSID_ThisDll;
    hdr.sfState = m_sfState;
    hdr.nCols = SaveColumns(pstm);
    
    ULARGE_INTEGER libCurPosition;
    LARGE_INTEGER dlibMove;
    dlibMove.HighPart = 0;
    dlibMove.LowPart = 0;
    pstm->Seek(dlibMove, STREAM_SEEK_SET, &libCurPosition);
    
    hres = pstm->Write(&hdr, sizeof(hdr), NULL);
    
    return(hres);
}


STDMETHODIMP CSFView::SelectItem(LPCITEMIDLIST pidlItem, UINT uFlags)
{
    return(E_NOTIMPL);
}


STDMETHODIMP CSFView::GetItemObject(UINT uItem, REFIID riid,
                                    void **ppv)
{
    return(E_NOTIMPL);
}


int CSFView::AddObject(LPCITEMIDLIST pidl)
{
    // Check the commdlg hook to see if we should include this
    // object.
    if (IncludeObject(pidl) != S_OK)
    {
        return(-1);
    }
    
    return(m_cView.AddObject(pidl));
}


int CALLBACK CSFView::CompareIDs(LPVOID p1, LPVOID p2, LPARAM lParam)
{
    PFNDPACOMPARE pfnCheckAPI = CompareIDs;
    
    CSFView *pThis = (CSFView *)lParam;
    
    HRESULT hres = pThis->m_psf->CompareIDs(pThis->m_sfState.lParamSort,
        (LPITEMIDLIST)p1, (LPITEMIDLIST)p2);
    
    
    return (hres);
}

//*****************************************************************************
//
// CSFView::FillList
//
// Purpose:
//
//        Enumerates the objects in the namespace and fills up the
//        data structures
//
//
// Comments:
//
//*****************************************************************************

HRESULT CSFView::FillList(BOOL bInteractive)
{
    m_cView.DeleteAllItems();
    
    // Setup the enum flags.
    DWORD dwEnumFlags = SHCONTF_NONFOLDERS;
    if (ShowAllObjects())
    {
        dwEnumFlags |= SHCONTF_INCLUDEHIDDEN ;
    }
    
    if (!(m_fs.fFlags & FWF_NOSUBFOLDERS))
    {
        dwEnumFlags |= SHCONTF_FOLDERS;
    }
    
    // Create an enum object and get the IEnumIDList ptr
    LPENUMIDLIST peIDL;
    HRESULT hres = m_psf->EnumObjects(bInteractive ? m_hwndMain : NULL,
        dwEnumFlags, &peIDL);
    
    // Note the return may be S_FALSE which indicates no enumerator.
    // That's why we shouldn't use if (FAILED(hres))
    if (hres != S_OK)
    {
        if (hres == S_FALSE)
        {
            return(NOERROR);
        }
        
        return(hres);
    }
    CEnsureRelease erEnum(peIDL);
    
    HDPA hdpaNew = DPA_Create(16);
    if (!hdpaNew)
    {
        return(E_OUTOFMEMORY);
    }
    
    LPITEMIDLIST pidl;
    ULONG celt;
    
    // Enumerate the idlist and insert into the DPA
    
    while (peIDL->Next(1, &pidl, &celt) == S_OK)
    {
        if (DPA_InsertPtr(hdpaNew, 0x7fff, pidl) == -1)
        {
            m_cMalloc.Free(pidl);
        }
    }
    
    DPA_Sort(hdpaNew, CompareIDs, (LPARAM)this);
    
    int cNew = DPA_GetPtrCount(hdpaNew);
    for (int i=0; i<cNew; ++i)
    {
        LPITEMIDLIST pidl = (LPITEMIDLIST)DPA_GetPtr(hdpaNew, i);
        if (AddObject(pidl) < 0)
        {
            m_cMalloc.Free(pidl);
        }
    }
    
    return(NOERROR);
}


//*****************************************************************************
//
// CSFView::AddColumns
//
// Purpose:
//
//        Adds columns to the contents pane listview
//
// Comments:
//
//*****************************************************************************
void CSFView::AddColumns()
{
    UINT cxChar = m_cView.CharWidth();
    
    // add columns to the listview in the contents pane
    for (int i=0; ; ++i)
    {
        SFVCB_GETDETAILSOF_DATA gdo;
        gdo.pidl = NULL;
        
        // get the first column
        
        HRESULT hres = CallCB(SFVCB_GETDETAILSOF, i, (LPARAM)&gdo);
        if (hres != S_OK)
        {
            if (i != 0)
            {
                break;
            }
            
            // If there is no first column, fake one up
            gdo.fmt = LVCFMT_LEFT;
            gdo.cChar = 40;
            gdo.lParamSort = 0;
            gdo.str.uType = STRRET_CSTR;
            LoadString(g_ThisDll.GetInstance(), IDS_NAME, gdo.str.cStr, sizeof(gdo.str.cStr));
        }
        
        char szText[MAX_PATH];
        // init the column info for the details view ...
        LV_COLUMN col;
        col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
        col.fmt = gdo.fmt;
        col.cx = gdo.cChar * cxChar;
        col.pszText = szText;
        col.cchTextMax = sizeof(szText);
        col.iSubItem = i;
        
        StrRetToStr(szText, sizeof(szText), &gdo.str, NULL);
        
        // insert the column into the list view
        if (m_cView.InsertColumn(i, &col)>=0 && m_aParamSort)
        {
            DPA_InsertPtr(m_aParamSort, 0x7fff, (LPVOID)gdo.lParamSort);
        }
        
        if (hres != S_OK)
        {
            break;
        }
    }
}


//
// Save (and check) column header information
// Returns TRUE if the columns are the default width, FALSE otherwise
// Side effect: the stream pointer is left right after the last column
//
BOOL CSFView::SaveColumns(LPSTREAM pstm)
{
    UINT cxChar = m_cView.CharWidth();
    BOOL bDefaultCols = TRUE;
    
    for (int i=0; ; ++i)
    {
        SFVCB_GETDETAILSOF_DATA gdo;
        gdo.pidl = NULL;
        
        if (CallCB(SFVCB_GETDETAILSOF, i, (LPARAM)&gdo) != S_OK)
        {
            break;
        }
        
        LV_COLUMN col;
        col.mask = LVCF_WIDTH;
        
        if (!m_cView.GetColumn(i, &col))
        {
            // There is some problem, so just assume
            // default column widths
            bDefaultCols = TRUE;
            break;
        }
        
        if (col.cx != (int)(gdo.cChar * cxChar))
        {
            bDefaultCols = FALSE;
        }
        
        // HACK: I don't really care about column widths larger
        // than 64K
        if (FAILED(pstm->Write(&col.cx, sizeof(USHORT), NULL)))
        {
            // There is some problem, so just assume
            // default column widths
            bDefaultCols = TRUE;
            break;
        }
    }
    
    return(bDefaultCols ? 0 : i);
}


void CSFView::RestoreColumns(LPSTREAM pstm, int nCols)
{
    for (int i=0; i<nCols; ++i)
    {
        LV_COLUMN col;
        col.mask = LVCF_WIDTH;
        
        if (FAILED(pstm->Read(&col.cx, sizeof(USHORT), NULL)))
        {
            break;
        }
        
        m_cView.SetColumn(i, &col);
    }
}


void CSFView::RestoreViewState()
{
    SFSTATE_HDR hdr;
    
    LPSTREAM pstm;
    
    MergeToolBar();
    
    // get the stream for storing view specific info
    if (FAILED(m_psb->GetViewStateStream(STGM_READ, &pstm)))
    {
        return;
    }
    CEnsureRelease erStr(pstm);
    
    if (FAILED(pstm->Read(&hdr, sizeof(hdr), NULL)))
    {
        return;
    }
    
    // Validate the header
    if (hdr.clsThis != CLSID_ThisDll)
    {
        return;
    }
    
    m_sfState = hdr.sfState;
    RestoreColumns(pstm, hdr.nCols);
}


void CSFView::CheckToolbar()
{
    UINT idCmdCurView = GetMenuIDFromViewMode();
    
    for (UINT idCmd=IDC_VIEW_ICON; idCmd<=IDC_VIEW_DETAILS; ++idCmd)
    {
        m_psb->SendControlMsg(FCW_TOOLBAR, TB_CHECKBUTTON, idCmd,
            (LPARAM)(idCmd == idCmdCurView), NULL);
    }
}


void CSFView::MergeToolBar()
{
    enum
    {
        IN_STD_BMP = 0x4000,
            IN_VIEW_BMP = 0x8000,
    } ;
    static const TBBUTTON c_tbDefault[] =
    {
        { STD_COPY | IN_STD_BMP, IDC_EDIT_COPY, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0, -1},
        { 0,    0,	    TBSTATE_ENABLED, TBSTYLE_SEP, {0,0}, 0, -1 },
        // the bitmap indexes here are relative to the view bitmap
        { VIEW_LARGEICONS | IN_VIEW_BMP, IDC_VIEW_ICON,	        TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0L, -1 },
        { VIEW_SMALLICONS | IN_VIEW_BMP, IDC_VIEW_SMALLICON, 	TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0L, -1 },
        { VIEW_LIST       | IN_VIEW_BMP, IDC_VIEW_LIST,         TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0L, -1 },
        { VIEW_DETAILS    | IN_VIEW_BMP, IDC_VIEW_DETAILS,      TBSTATE_ENABLED, TBSTYLE_BUTTON, {0,0}, 0L, -1 },
    } ;
    
    LRESULT iStdBMOffset;
    LRESULT iViewBMOffset;
    TBADDBITMAP ab;
    ab.hInst = HINST_COMMCTRL;		// hinstCommctrl
    ab.nID   = IDB_STD_SMALL_COLOR;	// std bitmaps
    m_psb->SendControlMsg(FCW_TOOLBAR, TB_ADDBITMAP, 8, (LPARAM)&ab, &iStdBMOffset);
    
    ab.nID   = IDB_VIEW_SMALL_COLOR;	// std view bitmaps
    m_psb->SendControlMsg(FCW_TOOLBAR, TB_ADDBITMAP, 8, (LPARAM)&ab, &iViewBMOffset);
    
    TBBUTTON tbActual[ARRAYSIZE(c_tbDefault)];
    
    for (int i=0; i<ARRAYSIZE(c_tbDefault); ++i)
    {
        tbActual[i] = c_tbDefault[i];
        if (!(tbActual[i].fsStyle & TBSTYLE_SEP))
        {
            if (tbActual[i].iBitmap & IN_VIEW_BMP)
            {
                tbActual[i].iBitmap = (tbActual[i].iBitmap & ~IN_VIEW_BMP) + iViewBMOffset;
            }
            else if (tbActual[i].iBitmap & IN_STD_BMP)
            {
                tbActual[i].iBitmap = (tbActual[i].iBitmap & ~IN_STD_BMP) + iStdBMOffset;
            }
        }
    }
    
    m_psb->SetToolbarItems(tbActual, ARRAYSIZE(c_tbDefault), FCT_MERGE);
    
    CheckToolbar();
}


HRESULT CreateShellFolderView(IShellFolder *psf, IShellFolderViewCallback *psfvcb,
                              LPSHELLVIEW * ppsv)
{
    CSFView *pSFView = new CSFView(psf, psfvcb);
    if (!pSFView)
    {
        return(E_OUTOFMEMORY);
    }
    
    pSFView->AddRef();
    HRESULT hRes = pSFView->QueryInterface(IID_IShellView, (void **)ppsv);
    pSFView->Release();
    
    return(hRes);
}
