//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation 1991-1994
//
// File: printobj.c
//
//  This file contains the persistent-object-binding mechanism which is
// slightly different from OLE's binding.
//
// History:
//  06-04-93 GeorgeP     Copied from printer.c
//
//---------------------------------------------------------------------------

#include "shellprv.h"
#pragma  hdrstop

typedef struct tagPREVPRINTER
{
    TCHAR szPrinterName[MAXNAMELENBUFFER];
    HWND hwndStub;
} PREVPRINTER, *LPPREVPRINTER;


int FindPrinter(HDSA hPrevPrinters, LPCTSTR lpszPrinterName)
{
    int i = -1;

    if (hPrevPrinters)
    {
        for (i=DSA_GetItemCount(hPrevPrinters)-1; i>=0; --i)
        {
            LPPREVPRINTER pPrevPrinter;

            pPrevPrinter = DSA_GetItemPtr(hPrevPrinters, i);

            if (lstrcmpi(pPrevPrinter->szPrinterName, lpszPrinterName) == 0)
            {
                break;
            }
        }
    }

    return(i);
}


//
// if uAction IS NOT MSP_NEWDRIVER then:
//    installs a printer (uAction).  If successful, notifies the shell and
//    returns a pidl to the printer.  ILFree() is callers responsibility.
// otherwise, if uAction IS MSP_NEWDRIVER then:
//    installs a printer driver (uAction).  If successful, fills the new
//    driver's name into lpBuffer (ASSUMED >= MAXNAMELEN).
//    Always returns NULL.
// if uAction is MSP_TESTPAGEPARTIALPROMPT then:
//    executes the test page code
//    Always returns NULL.
//

HWND hwndPrinterSetup = NULL; // active printer setup window, if any
LPITEMIDLIST Printers_PrinterSetup(HWND hwndStub, UINT uAction, LPTSTR lpBuffer, LPCTSTR pszServer)
{
#ifndef WINNT // PRINTQ
    HINSTANCE hmMSPrint;
#endif
    PRINTERSETUPPROC32 pfnPrinterSetup;
    LPITEMIDLIST pidl = NULL;

    if (SHRestricted(REST_NOPRINTERADD))
    {
        ShellMessageBox(HINST_THISDLL, hwndStub, MAKEINTRESOURCE(IDS_RESTRICTIONS),
                MAKEINTRESOURCE(IDS_RESTRICTIONSTITLE), MB_OK|MB_ICONSTOP);
        return NULL;
    }

#ifndef WINNT
    //
    // Windows 95 permits a user to printer a test page from the help trouble 
    // shooter, with out any contextual information.  i.e. a printer has 
    // not been selected.  In this case they will prompt the user for a 
    // printer from a list of printers.  In Windows NT we do not support
    // printing a test page from the help trouble shooter.  
    //
    // In Windows NT we prevent multiple add printer wizards from within 
    // the bPrinterSetup API located in printui.dll, where as Windows 95
    // prevents multiple add printer wizards here using a global.
    // we only want one PrinterSetup window up at a time
    //
    if (uAction != MSP_TESTPAGEPARTIALPROMPT)
    {
        BOOL fRet = FALSE;

        ENTERCRITICAL;
        if (hwndPrinterSetup != NULL &&
            SetForegroundWindow(GetLastActivePopup(hwndPrinterSetup)))
        {
            fRet = TRUE;
        }
        else
        {
            hwndPrinterSetup = hwndStub;
        }
        LEAVECRITICAL;

        if (fRet)
            return NULL;
    }
#endif

#if WINNT // PRINTQ
    if (PrintUIDLL_Init())
    {
        DWORD cchBufLen = 0;
#else
    hmMSPrint = LoadLibrary(TEXT("MSPRINT2.DLL"));

    if ((UINT)hmMSPrint <= 32)
    {
        DebugMsg(DM_WARNING,TEXT("sh WN - Printers_PrinterSetup could not load MSPRINT2.DLL"));
        Assert(FALSE);
        return NULL;
    }

    // BUGBUG: pop up a message box on error?

    pfnPrinterSetup = (PRINTERSETUPPROC32)GetProcAddress(hmMSPrint, "PrinterSetup32");
    if (pfnPrinterSetup)
    {
        WORD cchBufLen = 0;
#endif // PRINTQ
        LPIDPRINTER pidp;

        if (lpBuffer)
            cchBufLen = lstrlen(lpBuffer) + 1;
        if (cchBufLen < ARRAYSIZE(pidp->cName))
            cchBufLen = ARRAYSIZE(pidp->cName);

        pidp = (void*)LocalAlloc(LPTR, SIZEOF(IDPRINTER) - SIZEOF(pidp->cName) + cchBufLen * SIZEOF(TCHAR));
        if (pidp)
        {
            pidp->cName[0] = TEXT('\0');
            if (lpBuffer)
                lstrcpy(pidp->cName, lpBuffer);

            //
            // We don't have to worry about PrinterSetup failing due to the
            // output buffer being too small.  It's the right size
            // (32 bytes for Win9x, MAXNAMELENBUFFER for WinNT).
            //
            // On Win9x, this is ANSI, on WinNT, setup expects UNICODE.
            //
            // Also, there are no alignment problems since pidp is
            // allocated by us.
            //

#ifdef WINNT // PRINTQ

            if (g_pfnPrinterSetup(hwndStub,LOWORD(uAction),cchBufLen,
                pidp->cName,&cchBufLen, pszServer))
            {
#else
            if (pfnPrinterSetup(hwndStub,LOWORD(uAction),cchBufLen,
                (LPBYTE)pidp->cName,&cchBufLen))
            {
#endif
                if (uAction == MSP_NEWDRIVER)
                {
                    Assert(lstrlen(pidp->cName) < MAXNAMELEN);
                    lstrcpy(lpBuffer, (LPTSTR)(pidp->cName));
                }
                else if (uAction == MSP_TESTPAGEPARTIALPROMPT)
                {
                    // nothing to do for this case
                }
                else if (uAction == MSP_REMOVEPRINTER)
                {
                    // a bit ugly, but we need to pass back success for this case
                    pidl = (LPITEMIDLIST)TRUE;
                }
#ifdef WINNT
                else if (uAction == MSP_NEWPRINTER_MODELESS)
                {
                    // a bit ugly, but we need to pass back success for this case
                    pidl = (LPITEMIDLIST)TRUE;
                }
#endif
                else
                {
                    LPCITEMIDLIST pidlParent;
                    pidlParent = GetSpecialFolderIDList(hwndStub,CSIDL_PRINTERS,FALSE);
                    if (pidlParent)
                    {
                        pidp->cb = FIELDOFFSET(IDPRINTER,cName) + (lstrlen(pidp->cName) + 1) * SIZEOF(TCHAR);
                        *(USHORT *)((LPBYTE)(pidp) + pidp->cb) = 0;

                        pidl = ILCombine(pidlParent, (LPCITEMIDLIST)pidp);
                    }
                }
            }
            else
                DebugMsg(DM_TRACE,TEXT("sh TR - PrinterSetup32() failed (%x)"), GetLastError());

            LocalFree((HLOCAL)pidp);
        }
    }
    else
        DebugMsg(DM_ERROR,TEXT("sh ER - GetProcAddress(MSPRINT32.DLL,PrinterSetup32) failed"));

    hwndPrinterSetup = NULL;
#ifndef WINNT // PRINTQ
    FreeLibrary(hmMSPrint);
#endif
    return(pidl);
}

//
// Printer_OneWindowAction calls lpfn iff it's not in use for printer lpName.
// If it's already in use for printer lpName, bring focus to that window.
//
void Printer_OneWindowAction(HWND hwndStub, LPCTSTR lpName, HDSA *lphdsa, LPFNPRINTACTION lpfn, LPARAM lParam, BOOL fModal)
{
    int i;
    PREVPRINTER sThisPrinter;
    LPPREVPRINTER pPrevPrinter;

    EnterCriticalSection(&g_csPrinters);

    // Initialize the DSA if we need to
    if (!*lphdsa)
    {
        *lphdsa = DSA_Create(SIZEOF(sThisPrinter), 4);
        if (!*lphdsa)
        {
            goto error_exit;
        }
    }

    // Bring window up if lpName is in use
    i = FindPrinter(*lphdsa, lpName);
    if (i >= 0)
    {
        HWND hwnd;

        if (fModal) {
            ShellMessageBox(HINST_THISDLL,
                hwndStub,
                MAKEINTRESOURCE(IDS_CANTOPENMODALPROP),
                MAKEINTRESOURCE(IDS_PRINTERS),
                MB_OK|MB_ICONERROR);
        }

        pPrevPrinter = DSA_GetItemPtr(*lphdsa, i);

        if (pPrevPrinter->hwndStub == NULL ||
            (hwnd = GetLastActivePopup(pPrevPrinter->hwndStub)) == NULL ||
            !ShowWindow(hwnd, SW_SHOW) ||
            !SetForegroundWindow(hwnd) )
        {
            // The window must have crashed before.  Remove it from the
            // hdsa and add it again.
            // WARNING: This window could be stuck waiting for a
            // PRINTER_INFO_2 before putting up the window. In this case, the
            // ShowWindow will fail and we'll wind up with two windows up.
            DSA_DeleteItem(*lphdsa, i);
        }
        else
        {
            // We brought the existing window to the front.  We're done.

            goto exit;
        }
    }

    // We're bringing up the window
    lstrcpy(sThisPrinter.szPrinterName, lpName);
    sThisPrinter.hwndStub = hwndStub;
    if (DSA_InsertItem(*lphdsa, 0x7fff, &sThisPrinter) < 0)
    {
        goto error_exit;
    }

    // Call the printer function.  Make sure winspool is loaded
    LeaveCriticalSection(&g_csPrinters);
    if (WinspoolDLL_Init())
    {
        lpfn(hwndStub, lpName, SW_SHOWNORMAL, lParam);
    }
    EnterCriticalSection(&g_csPrinters);

    // The window is gone
    // (Use sThisPrinter.szPrinterName since it may have been renamed)
    i = FindPrinter(*lphdsa, sThisPrinter.szPrinterName);
    if (i >= 0)
    {
        DSA_DeleteItem(*lphdsa, i);

        // Don't use up memory we don't need
        if (DSA_GetItemCount(*lphdsa) == 0)
        {
            DSA_Destroy(*lphdsa);
            *lphdsa = NULL;
        }
    }

    goto exit;

error_exit:
    // BUGBUG: do we want to put up an Out Of Memory error?
    DebugMsg(DM_WARNING, TEXT("sh WN - Printer_OneWindowAction() - Out Of Memory"));

exit:
    LeaveCriticalSection(&g_csPrinters);
    return;
}


extern HDSA hdsaPrintDef;

void PrintDef_UpdateHwnd(LPCTSTR lpszPrinterName, HWND hWnd)
{
    int i;
    LPPREVPRINTER pThisPrinter;

    EnterCriticalSection(&g_csPrinters);

    if (!hdsaPrintDef)
    {
        goto Error0;
    }

    i = FindPrinter(hdsaPrintDef, lpszPrinterName);
    if (i < 0)
    {
        goto Error0;
    }

    pThisPrinter = DSA_GetItemPtr(hdsaPrintDef, i);
    pThisPrinter->hwndStub = hWnd;

Error0:

    LeaveCriticalSection(&g_csPrinters);

}

void PrintDef_UpdateName(LPCTSTR lpszPrinterName, LPCTSTR lpszNewName)
{
    int i;
    LPPREVPRINTER pThisPrinter;

    EnterCriticalSection(&g_csPrinters);

    if (!hdsaPrintDef)
    {
        goto Error0;
    }

    i = FindPrinter(hdsaPrintDef, lpszPrinterName);
    if (i < 0)
    {
        goto Error0;
    }

    pThisPrinter = DSA_GetItemPtr(hdsaPrintDef, i);
    lstrcpy(pThisPrinter->szPrinterName, lpszNewName);

Error0:

    LeaveCriticalSection(&g_csPrinters);

}


void PrintDef_RefreshQueue(LPCTSTR lpszPrinterName)
{
    int i;
    LPPREVPRINTER pThisPrinter;

    EnterCriticalSection(&g_csPrinters);

    if (!hdsaPrintDef)
    {
        goto Error0;
    }

    if (lpszPrinterName)
    {
        i = FindPrinter(hdsaPrintDef, lpszPrinterName);
        if (i < 0)
        {
            goto Error0;
        }

        pThisPrinter = DSA_GetItemPtr(hdsaPrintDef, i);
        SendMessage(pThisPrinter->hwndStub, WM_COMMAND, ID_VIEW_REFRESH, 0L);
    }
    else
    {
        for (i=DSA_GetItemCount(hdsaPrintDef)-1; i>=0; --i)
        {
            pThisPrinter = DSA_GetItemPtr(hdsaPrintDef, i);
            SendMessage(pThisPrinter->hwndStub, WM_COMMAND, ID_VIEW_REFRESH, 0L);
        }
    }
Error0:
    LeaveCriticalSection(&g_csPrinters);
}


//---------------------------------------------------------------------------
//
// IDropTarget stuff
//

STDMETHODIMP CPrintObjs_DT_DragEnter(LPDROPTARGET pdt, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
    LPIDLDROPTARGET this = IToClass(CIDLDropTarget, dropt, pdt);

    // let the base-class process it now to save away pdwEffect
    CIDLDropTarget_DragEnter(pdt, pDataObj, grfKeyState, pt, pdwEffect);

    // We allow files to be dropped for printing
    // if it is from the bitbucket only DROEFFECT_MOVE will be set in *pdwEffect
    // so this will keep us from printing wastbasket items.

    if (this->dwData & DTID_HDROP)
        *pdwEffect &= DROPEFFECT_COPY;
    else
        *pdwEffect = DROPEFFECT_NONE;   // Default action is nothing

    this->dwEffectLastReturned = *pdwEffect;

    return S_OK;
}

void Printer_PrintHDROPFiles(HWND hwnd, HDROP hdrop, LPCITEMIDLIST pidlPrinter)
{
    DRAGINFO di;

    di.uSize = SIZEOF(di);
    if (DragQueryInfo(hdrop, &di))
    {
        LPTSTR lpFileName = di.lpFileList;
        int i = IDYES;

        // Printing more than one file at a time can easily fail.
        // Ask the user to confirm this operation.
        if (*lpFileName && *(lpFileName + lstrlen(lpFileName) + 1))
        {
            i = ShellMessageBox(HINST_THISDLL,
                NULL,
                MAKEINTRESOURCE(IDS_MULTIPLEPRINTFILE),
                MAKEINTRESOURCE(IDS_PRINTERS),
                MB_YESNO|MB_ICONINFORMATION);
        }

        if (i == IDYES)
        {
            // BUGBUG: It would be really nice to have a progress bar when
            // printing multiple files.  And there should definitely be a way
            // to cancel this operation. Oh well, we warned them...

            while (*lpFileName != TEXT('\0'))
            {
                Printer_PrintFile(hwnd, lpFileName, pidlPrinter);

                lpFileName += lstrlen(lpFileName) + 1;
            }
        }

        SHFree(di.lpFileList);
    }

}


//
// This is the entry of "drop thread"
//
DWORD WINAPI CPrintObjs_DT_DropThreadInit(LPVOID pv)
{
    LPPRNTHREADPARAM pthp = (LPPRNTHREADPARAM)pv;
    FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    STGMEDIUM medium;
    HRESULT hres;

    // DragEnter only allows files to be DROPEFFECT_COPYd
    Assert(SUCCEEDED(pthp->pDataObj->lpVtbl->QueryGetData(pthp->pDataObj, &fmte)));
    Assert(pthp->dwEffect == DROPEFFECT_COPY);

    hres = pthp->pDataObj->lpVtbl->GetData(pthp->pDataObj, &fmte, &medium);
    if (SUCCEEDED(hres))
    {
        Printer_PrintHDROPFiles(pthp->hwndOwner, medium.hGlobal, pthp->pidl);

        SHReleaseStgMedium(&medium);
    }

    pthp->pDataObj->lpVtbl->Release(pthp->pDataObj);
    ILFree(pthp->pidl);
    LocalFree((HLOCAL)pthp);

    return 0;
}


HRESULT PrintObj_DropPrint(LPDATAOBJECT pDataObj, HWND hwndOwner, DWORD dwEffect, LPCITEMIDLIST pidl, LPTHREAD_START_ROUTINE lpfn)
{
    HRESULT hres = E_OUTOFMEMORY;

    //
    // Note that we need to create another thread to avoid
    // blocking the source thread.
    //
    LPPRNTHREADPARAM pthp = (LPPRNTHREADPARAM)LocalAlloc(LPTR, SIZEOF(PRNTHREADPARAM));
    if (pthp)
    {
        extern HRESULT CIDLData_Clone(LPDATAOBJECT pdtobjIn, UINT acf[], UINT ccf, LPDATAOBJECT *ppdtobjOut);
        UINT acf[] = { CF_HDROP };
        hres = CIDLData_Clone(pDataObj, acf, ARRAYSIZE(acf), &pthp->pDataObj);
        if (SUCCEEDED(hres))
        {
            DWORD idThread;
            HANDLE hthread;

            pthp->hwndOwner = hwndOwner;
            pthp->dwEffect = dwEffect;
            pthp->pidl = ILClone(pidl);
            if (hwndOwner)
                ShellFolderView_GetAnchorPoint(hwndOwner, FALSE, &pthp->ptDrop);

            if (NULL != (hthread = CreateThread(NULL, 0, lpfn, pthp, 0, &idThread)))
            {
                // We don't need to communicate with this thread any more.
                // Close the handle and let it run and terminate itself.
                //
                CloseHandle(hthread);
                pthp = NULL;    // the thread will free it.
                hres = S_OK;
            }
            else
            {
                // Thread creation failed, we should release thread parameters.
                pthp->pDataObj->lpVtbl->Release(pthp->pDataObj);
                ILFree(pthp->pidl);
                hres = E_OUTOFMEMORY;
            }
        }

        if (pthp) {
            LocalFree((HLOCAL)pthp);
        }
    }

    return hres;
}

STDMETHODIMP _CPrintObjs_DT_Drop(LPDROPTARGET pdt, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect, LPTHREAD_START_ROUTINE lpfn)
{
    LPIDLDROPTARGET this = IToClass(CIDLDropTarget, dropt, pdt);
    HRESULT hres;

    *pdwEffect = DROPEFFECT_COPY;

    CIDLDropTarget_DragOver(pdt, grfKeyState, pt, pdwEffect);
    if (*pdwEffect)
        hres = CIDLDropTarget_DragDropMenu(this, DROPEFFECT_COPY, pDataObj,
                                           pt, pdwEffect, NULL, NULL, MENU_PRINTOBJ_DD, grfKeyState);
    else
        hres = S_FALSE;

    if (*pdwEffect)
    {
        hres = PrintObj_DropPrint(pDataObj, this->hwndOwner, *pdwEffect, this->pidl, lpfn);
    }

    CIDLDropTarget_DragLeave(pdt);
    return hres;
}

STDMETHODIMP CPrintObjs_DT_Drop(LPDROPTARGET pdt, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
    return _CPrintObjs_DT_Drop(pdt, pDataObj, grfKeyState, pt, pdwEffect, CPrintObjs_DT_DropThreadInit);
}


#pragma data_seg(".text", "CODE")
IDropTargetVtbl c_CPrintObjsDropTargetVtbl =
{
    CIDLDropTarget_QueryInterface,
    CIDLDropTarget_AddRef,
    CIDLDropTarget_Release,
    CPrintObjs_DT_DragEnter,
    CIDLDropTarget_DragOver,
    CIDLDropTarget_DragLeave,
    CPrintObjs_DT_Drop,
} ;
#pragma data_seg()