/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_fpicker.hxx"

//------------------------------------------------------------------------
// includes
//------------------------------------------------------------------------

#include <tchar.h>
#include <osl/diagnose.h>
#include "../misc/WinImplHelper.hxx"
#include "FileOpenDlg.hxx"

//------------------------------------------------------------------------
// constants
//------------------------------------------------------------------------

namespace /* private */ 
{
    // we choose such large buffers because the size of
    // an single line edit field can be up to 32k; if
    // a user has a multi selection FilePicker and selects
    // a lot of files in a large directory we may reach this
    // limit and don't want to get out of memory;
    // another much more elegant way would be to subclass the
    // FileOpen dialog and overload the BM_CLICK event of the
    // OK button so that we determine the size of the text
    // currently in the edit field and resize our buffer
    // appropriately - in the future we will do this
    const size_t MAX_FILENAME_BUFF_SIZE  = 32000;
    const size_t MAX_FILETITLE_BUFF_SIZE = 32000;
    const size_t MAX_FILTER_BUFF_SIZE    = 4096;

    const LPTSTR CURRENT_INSTANCE = TEXT("CurrInst");
    
    //------------------------------------------
    // find an appropriate parent window
    //------------------------------------------
    
    inline bool is_current_process_window(HWND hwnd)
    {
        DWORD pid;        
        GetWindowThreadProcessId(hwnd, &pid);        
        return (pid == GetCurrentProcessId());
    }
        
    HWND choose_parent_window()
    {
        HWND hwnd_parent = GetForegroundWindow();                
        if (!is_current_process_window(hwnd_parent))
            hwnd_parent = GetDesktopWindow();
       
        return hwnd_parent;
    }
};

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

CFileOpenDialog::CFileOpenDialog( 
	bool bFileOpenDialog, 
	sal_uInt32 dwFlags, 
	sal_uInt32 dwTemplateId, 
	HINSTANCE hInstance) :
	m_hwndFileOpenDlg(0),
	m_hwndFileOpenDlgChild(0),
	m_bFileOpenDialog(bFileOpenDialog),	
	m_filterBuffer(MAX_FILTER_BUFF_SIZE),
	m_fileTitleBuffer(MAX_FILETITLE_BUFF_SIZE),
	m_helperBuffer(MAX_FILENAME_BUFF_SIZE),		
	m_fileNameBuffer(MAX_FILENAME_BUFF_SIZE),    
	m_pfnBaseDlgProc(0)
{
	// initialize the OPENFILENAME struct
	if (IsWindows2000Platform() || IsWindowsME())
	{		
		ZeroMemory(&m_ofn, sizeof(m_ofn));
		m_ofn.lStructSize = sizeof(m_ofn);
	}
	else // OSVER < Win2000
	{
        // the size of the OPENFILENAME structure is different 
        // under windows < win2000
		ZeroMemory(&m_ofn, _OPENFILENAME_SIZE_VERSION_400);
		m_ofn.lStructSize = _OPENFILENAME_SIZE_VERSION_400;			
	}

    // 0x02000000 for #97681, sfx will make the entry into
    // the recent document list
	m_ofn.Flags |= dwFlags | 
				   OFN_EXPLORER |
		           OFN_ENABLEHOOK |		                      
		           OFN_HIDEREADONLY |		               
				   OFN_PATHMUSTEXIST |
				   OFN_FILEMUSTEXIST |
                   OFN_OVERWRITEPROMPT |
				   OFN_ENABLESIZING |
                   OFN_DONTADDTORECENT; // 0x02000000 -> OFN_DONTADDTORECENT only available with new platform sdk
                   
    // it is a little hack but how else could
    // we get a parent window (using a vcl window?)
    m_ofn.hwndOwner = choose_parent_window();
	
	m_ofn.lpstrFile = reinterpret_cast<LPTSTR>(const_cast<sal_Unicode*>(m_fileNameBuffer.getStr()));
	m_ofn.nMaxFile  = m_fileNameBuffer.getCapacity();
	
	m_ofn.lpstrFileTitle = reinterpret_cast<LPTSTR>(const_cast<sal_Unicode*>(m_fileTitleBuffer.getStr()));
	m_ofn.nMaxFileTitle  = m_fileTitleBuffer.getCapacity();

	m_ofn.lpfnHook = CFileOpenDialog::ofnHookProc;

	// set a custom template

	if (dwTemplateId)
	{
		OSL_ASSERT(hInstance);

		m_ofn.Flags          |= OFN_ENABLETEMPLATE;
		m_ofn.lpTemplateName  = MAKEINTRESOURCE(dwTemplateId);
		m_ofn.hInstance       = hInstance;
	}

	// set a pointer to myself as ofn parameter
	m_ofn.lCustData = reinterpret_cast<long>(this);	
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

CFileOpenDialog::~CFileOpenDialog()
{
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::setTitle(const rtl::OUString& aTitle)
{
	m_dialogTitle = aTitle;
	m_ofn.lpstrTitle = reinterpret_cast<LPCTSTR>(m_dialogTitle.getStr());
}
	
//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void CFileOpenDialog::setFilter(const rtl::OUString& aFilter)
{	
    // Format is like 
    // "*.TXT" or multiple separate by ';' like "*.TXT;*.DOC;*.SXW"
    // Do not include spaces in the pattern string
	m_filterBuffer.ensureCapacity(aFilter.getLength());
	m_filterBuffer.setLength(0);
	m_filterBuffer.append(aFilter);
	m_ofn.lpstrFilter = reinterpret_cast<LPCTSTR>(m_filterBuffer.getStr());
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

bool CFileOpenDialog::setFilterIndex(sal_uInt32 aIndex)
{
	OSL_ASSERT(aIndex > 0);
	m_ofn.nFilterIndex = aIndex;
	return sal_True;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

sal_uInt32 CFileOpenDialog::getSelectedFilterIndex() const
{
	return m_ofn.nFilterIndex;
}
	
//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::setDefaultName(const rtl::OUString& aName)
{
	m_fileNameBuffer.setLength(0);
	m_fileNameBuffer.append(aName);
	m_ofn.lpstrFile = reinterpret_cast<LPTSTR>(const_cast<sal_Unicode*>(m_fileNameBuffer.getStr()));
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::setDisplayDirectory(const rtl::OUString& aDirectory)
{
	m_displayDirectory = aDirectory;
	m_ofn.lpstrInitialDir = reinterpret_cast<LPCTSTR>(m_displayDirectory.getStr());
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

rtl::OUString SAL_CALL CFileOpenDialog::getLastDisplayDirectory() const
{
	return m_displayDirectory;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

rtl::OUString SAL_CALL CFileOpenDialog::getFullFileName() const
{	
	return rtl::OUString(m_fileNameBuffer.getStr(), 
		_wcslenex(m_fileNameBuffer.getStr()));
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

rtl::OUString SAL_CALL CFileOpenDialog::getFileName() const
{	
	return rtl::OUString(m_fileTitleBuffer);
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

rtl::OUString CFileOpenDialog::getFileExtension()
{
	if (m_ofn.nFileExtension)
		return rtl::OUString(m_fileNameBuffer.getStr() + m_ofn.nFileExtension, 
			rtl_ustr_getLength(m_fileNameBuffer.getStr() + m_ofn.nFileExtension));

	return rtl::OUString();
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void CFileOpenDialog::setDefaultFileExtension(const rtl::OUString& aExtension)
{
	m_defaultExtension = aExtension;
	m_ofn.lpstrDefExt  = reinterpret_cast<LPCTSTR>(m_defaultExtension.getStr());
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::setMultiSelectionMode(bool bMode)
{
	if (bMode)
		m_ofn.Flags |= OFN_ALLOWMULTISELECT;
	else
		m_ofn.Flags &= ~OFN_ALLOWMULTISELECT;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

bool SAL_CALL CFileOpenDialog::getMultiSelectionMode() const
{
	return ((m_ofn.Flags & OFN_ALLOWMULTISELECT) > 0);
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

sal_Int16 SAL_CALL CFileOpenDialog::doModal()
{
	sal_Int16 nRC = -1;

	// pre-processing
	if (preModal())
	{
		bool bRet;
       
        if (m_bFileOpenDialog)
	        bRet = m_GetFileNameWrapper.getOpenFileName(
				reinterpret_cast<LPOPENFILENAME>(&m_ofn));
        else
            bRet = m_GetFileNameWrapper.getSaveFileName(
				reinterpret_cast<LPOPENFILENAME>(&m_ofn));

	    nRC = 1;
		
		if (!bRet)
			nRC = (0 == m_GetFileNameWrapper.commDlgExtendedError()) ? 0 : -1;

		// post-processing
		postModal(nRC);
	}

	return nRC;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

sal_uInt32 SAL_CALL CFileOpenDialog::getLastDialogError() const
{
	return CommDlgExtendedError();
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

bool SAL_CALL CFileOpenDialog::preModal()
{
	return sal_True;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::postModal(sal_Int16 nDialogResult)
{
	OSL_ASSERT((-1 <= nDialogResult) && (nDialogResult <= 1));
    
	if (1 == nDialogResult)
	{
		// Attention: assuming that nFileOffset is always greater 0 because under
		// Windows there is always a drive letter or a server in a complete path
		// the OPENFILENAME docu never says that nFileOffset can be 0
		m_displayDirectory = rtl::OUString(reinterpret_cast<const sal_Unicode*>(m_ofn.lpstrFile),m_ofn.nFileOffset);			
	}	        
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

rtl::OUString SAL_CALL CFileOpenDialog::getCurrentFilePath() const
{
	OSL_ASSERT(IsWindow(m_hwndFileOpenDlg));

	LPARAM nLen = SendMessage(
		m_hwndFileOpenDlg,
		CDM_GETFILEPATH,
		m_helperBuffer.getCapacity(),
		reinterpret_cast<LPARAM>(m_helperBuffer.getStr()));
		
	if (nLen > 0)
	{
		m_helperBuffer.setLength((nLen * sizeof(sal_Unicode)) - 1);
		return rtl::OUString(m_helperBuffer);
	}	
	return rtl::OUString();
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

rtl::OUString SAL_CALL CFileOpenDialog::getCurrentFolderPath() const
{
	OSL_ASSERT(IsWindow(m_hwndFileOpenDlg));
	
	LPARAM nLen = SendMessage(
		m_hwndFileOpenDlg,
		CDM_GETFOLDERPATH,
		m_helperBuffer.getCapacity(),
		reinterpret_cast<LPARAM>(m_helperBuffer.getStr()));
		
	if (nLen > 0)
	{
		m_helperBuffer.setLength((nLen * sizeof(sal_Unicode)) - 1);
		return rtl::OUString(m_helperBuffer);
	}	
	return rtl::OUString();
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

rtl::OUString SAL_CALL CFileOpenDialog::getCurrentFileName() const
{
	OSL_ASSERT(IsWindow(m_hwndFileOpenDlg));

	LPARAM nLen = SendMessage(
		m_hwndFileOpenDlg,
		CDM_GETSPEC,
		m_helperBuffer.getCapacity(),
		reinterpret_cast<LPARAM>(m_helperBuffer.getStr()));

	if (nLen > 0)
	{
		m_helperBuffer.setLength((nLen * sizeof(sal_Unicode)) - 1);
		return rtl::OUString(m_helperBuffer);
	}	
	return rtl::OUString();
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

sal_uInt32 SAL_CALL CFileOpenDialog::onShareViolation(const rtl::OUString&)
{
	return 0;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

sal_uInt32 SAL_CALL CFileOpenDialog::onFileOk()
{
	return 0;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::onSelChanged(HWND)
{
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::onHelp()
{
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::onInitDone()
{
    centerPositionToParent();
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::onFolderChanged()
{
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::onTypeChanged(sal_uInt32)
{
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

sal_uInt32 SAL_CALL CFileOpenDialog::onCtrlCommand(HWND, sal_uInt16, sal_uInt16)
{
	return 0;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

sal_uInt32 SAL_CALL CFileOpenDialog::onWMNotify( HWND, LPOFNOTIFY lpOfNotify )
{
	switch(lpOfNotify->hdr.code)
	{
	case CDN_SHAREVIOLATION:
		return onShareViolation(reinterpret_cast<const sal_Unicode*>(lpOfNotify->pszFile));
		
	case CDN_FILEOK:
		return onFileOk();

	case CDN_SELCHANGE:
		onSelChanged(lpOfNotify->hdr.hwndFrom);
		break;

	case CDN_HELP:
		onHelp();
		break;

	case CDN_INITDONE:
		onInitDone();
		break;

	case CDN_FOLDERCHANGE:
		onFolderChanged();
		break;

	case CDN_TYPECHANGE:
        m_ofn.nFilterIndex = lpOfNotify->lpOFN->nFilterIndex;
		onTypeChanged(lpOfNotify->lpOFN->nFilterIndex);
		break;
	}

	return 0;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::handleInitDialog(HWND hwndDlg, HWND hwndChild)
{
	m_hwndFileOpenDlg      = hwndDlg;
	m_hwndFileOpenDlgChild = hwndChild;
	
	OSL_ASSERT(GetParent(hwndChild) == hwndDlg);

	// calling virtual function which the 
	// client can overload
	onInitDialog(hwndDlg);
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

unsigned int CALLBACK CFileOpenDialog::ofnHookProc( 
	HWND hChildDlg, unsigned int uiMsg, WPARAM wParam, LPARAM lParam)
{
    HWND hwndDlg = GetParent(hChildDlg);
    CFileOpenDialog* pImpl = NULL;

	switch( uiMsg )
	{		
	case WM_INITDIALOG: 
        {
            _LPOPENFILENAME lpofn = reinterpret_cast<_LPOPENFILENAME>(lParam);
            pImpl = reinterpret_cast<CFileOpenDialog*>(lpofn->lCustData);            
            OSL_ASSERT(pImpl);
            
            // subclass the base dialog for WM_NCDESTROY processing 
            pImpl->m_pfnBaseDlgProc = 
		        reinterpret_cast<WNDPROC>( 
			        SetWindowLong(
			            hwndDlg, 
			            GWL_WNDPROC, 
                        reinterpret_cast<LONG>(CFileOpenDialog::BaseDlgProc)));                                                                                              
            // connect the instance handle to the window            
            SetProp(hwndDlg, CURRENT_INSTANCE, pImpl);
		    pImpl->handleInitDialog(hwndDlg, hChildDlg);
        }
		return 0;
		
	case WM_NOTIFY:
        {
            pImpl = getCurrentInstance(hwndDlg);
		    return pImpl->onWMNotify( 
			    hChildDlg, reinterpret_cast<LPOFNOTIFY>(lParam));
        }

	case WM_COMMAND:
        {
            pImpl = getCurrentInstance(hwndDlg);
            OSL_ASSERT(pImpl);

		    return pImpl->onCtrlCommand( 
			    hChildDlg, LOWORD(wParam), HIWORD(lParam));	
        }
	}

	return 0;
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

LRESULT CALLBACK CFileOpenDialog::BaseDlgProc( 
    HWND hWnd, UINT wMessage, WPARAM wParam, LPARAM lParam)
{
	CFileOpenDialog* pImpl = 0;
	
	if (WM_NCDESTROY == wMessage)
	{
		pImpl = reinterpret_cast<CFileOpenDialog*>(
			RemoveProp(hWnd,CURRENT_INSTANCE));
				
		SetWindowLong(hWnd, GWL_WNDPROC, 
			reinterpret_cast<LONG>(pImpl->m_pfnBaseDlgProc));
	}
	else
	{
		pImpl = getCurrentInstance(hWnd);
	}
	
	OSL_ASSERT(pImpl);
	
	return CallWindowProc(
		reinterpret_cast<WNDPROC>(pImpl->m_pfnBaseDlgProc),
		hWnd,wMessage,wParam,lParam);
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

CFileOpenDialog* SAL_CALL CFileOpenDialog::getCurrentInstance(HWND hwnd)
{
    OSL_ASSERT(IsWindow( hwnd));
    return reinterpret_cast<CFileOpenDialog*>( 
        GetProp(hwnd, CURRENT_INSTANCE));
}

//------------------------------------------------------------------------
// 
//------------------------------------------------------------------------

void SAL_CALL CFileOpenDialog::centerPositionToParent() const
{
    OSL_PRECOND(IsWindow(m_hwndFileOpenDlg), "no dialog window, call method only after or in onInitDone");
    
    HWND hwndParent = m_ofn.hwndOwner;

    if (!IsWindow(hwndParent))
        hwndParent = GetDesktopWindow();

    OSL_ASSERT(IsWindow(hwndParent));

    RECT rcPar;
    GetWindowRect(hwndParent, &rcPar);

    RECT rcDlg;
    GetWindowRect(m_hwndFileOpenDlg, &rcDlg);
    
    int lDlgW = rcDlg.right  - rcDlg.left;
    int lDlgH = rcDlg.bottom - rcDlg.top;

	int x = (rcPar.left + rcPar.right  - lDlgW) / 2;
	int y = (rcPar.top  + rcPar.bottom - lDlgH) / 2;
	
	HDC hdc = GetDC(m_hwndFileOpenDlg);

    int hResol = GetDeviceCaps(hdc, HORZRES);
    int vResol = GetDeviceCaps(hdc, VERTRES);

	ReleaseDC(m_hwndFileOpenDlg, hdc);

    if (x < 0)
        x = 0;
    else if ((x + lDlgW) > hResol)
        x = hResol - lDlgW;
	
    if (y < 0)
        y = 0;
    else if ((y + lDlgH) > vResol)
        y = vResol - lDlgH;

    SetWindowPos(
        m_hwndFileOpenDlg,
        NULL, x, y, 0, 0,
        SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE );    
}
