/**************************************************************
 * 
 * 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"

#include <tchar.h>
#include "previewadapter.hxx"

#ifndef _COM_SUN_STAR_UI_DIALOG_FILEPREVIEWIMAGEFORMATS_HPP_
#include <com/sun/star/ui/dialogs/FilePreviewImageFormats.hpp>
#endif
#include "dibpreview.hxx"
#include "../misc/WinImplHelper.hxx"

#include <memory>
#include <stdexcept>

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

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;

//---------------------------------------------
// An impl class to hide implementation details 
// from clients
//---------------------------------------------

class CPreviewAdapterImpl
{
public:
	CPreviewAdapterImpl(HINSTANCE instance);

	virtual ~CPreviewAdapterImpl();
	
	virtual sal_Int32 SAL_CALL getTargetColorDepth();
    
	virtual sal_Int32 SAL_CALL getAvailableWidth();
    
	virtual sal_Int32 SAL_CALL getAvailableHeight();
    
	virtual void SAL_CALL setImage( sal_Int16 aImageFormat, const Any& aImage ) 
		throw (IllegalArgumentException,RuntimeException);

    virtual sal_Bool SAL_CALL setShowState(sal_Bool bShowState);
    
	virtual sal_Bool SAL_CALL getShowState();
	
	virtual void SAL_CALL setParent(HWND parent);
	
	virtual HWND SAL_CALL getParent();

	//-------------------------------------
	// parent notification handler
	//-------------------------------------

	virtual void SAL_CALL notifyParentShow(sal_Bool bShow);
	
	virtual void SAL_CALL notifyParentSizeChanged();

	virtual void SAL_CALL notifyParentWindowPosChanged();

protected:	
	virtual void SAL_CALL calcRightMargin();

	virtual void SAL_CALL rearrangeLayout();

	void SAL_CALL initializeActivePreview() throw(std::runtime_error);

	HWND SAL_CALL findFileListbox() const;

// member
protected:
	HINSTANCE					m_Instance;
	std::auto_ptr<PreviewBase>  m_Preview;
	HWND						m_FileDialog;
	int							m_RightMargin;

//prevent copy/assignment
private:
	CPreviewAdapterImpl(const CPreviewAdapterImpl&);
	CPreviewAdapterImpl& operator=(const CPreviewAdapterImpl&);
};

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

CPreviewAdapterImpl::CPreviewAdapterImpl(HINSTANCE instance) : 
	m_Instance(instance),
	m_Preview(new PreviewBase()), // create dummy preview (NULL-Object pattern)
	m_FileDialog(0),
	m_RightMargin(0)
{
}

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

CPreviewAdapterImpl::~CPreviewAdapterImpl()
{
}

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

sal_Int32 SAL_CALL CPreviewAdapterImpl::getTargetColorDepth()
{
	return m_Preview->getTargetColorDepth();
}

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

sal_Int32 SAL_CALL CPreviewAdapterImpl::getAvailableWidth()
{
	return m_Preview->getAvailableWidth();
}

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

sal_Int32 SAL_CALL CPreviewAdapterImpl::getAvailableHeight()
{
	return m_Preview->getAvailableHeight();
}

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

void SAL_CALL CPreviewAdapterImpl::setImage( sal_Int16 aImageFormat, const Any& aImage ) 
	throw (IllegalArgumentException,RuntimeException)
{
	m_Preview->setImage(aImageFormat,aImage);
}

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

sal_Bool SAL_CALL CPreviewAdapterImpl::setShowState( sal_Bool bShowState )
{
	sal_Bool bRet = m_Preview->setShowState(bShowState);
	rearrangeLayout();
	return bRet;
}

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

sal_Bool SAL_CALL CPreviewAdapterImpl::getShowState()
{
	return m_Preview->getShowState();
}

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

void SAL_CALL CPreviewAdapterImpl::setParent(HWND parent)
{
	OSL_PRECOND(IsWindow(parent),"Invalid FileDialog handle");

	m_FileDialog = parent;
	calcRightMargin();	
}

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

HWND SAL_CALL CPreviewAdapterImpl::getParent()
{
	return m_FileDialog;
}

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

void SAL_CALL CPreviewAdapterImpl::calcRightMargin()
{
	// Calculate the right reference margin
	//
	// Assumtions:
	// 1. This method will be called before the dialog becomes
	//    visible
	// 2. There exist a FileListbox with the id lst1 even
	//    if it is not visible like under Win2000/XP
	// 3. Initially this FileListbox has the appropriate size
	//    to fit within the FileListbox
	// 4. The margin between the right edge of the FileListbox
	//    and the right edge of the FileDialog will be constant
	//    even if the size of the dialog changes

	HWND flb = GetDlgItem(m_FileDialog,lst1);
	
	OSL_ENSURE(IsWindow(flb),"Filelistbox not found");

	RECT rcFlb;
	GetWindowRect(flb,&rcFlb);

	RECT rcFileDlg;
	GetWindowRect(m_FileDialog,&rcFileDlg);

	m_RightMargin = rcFileDlg.right - rcFlb.right;
}

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

void SAL_CALL CPreviewAdapterImpl::notifyParentShow(sal_Bool)
{
}

//-----------------------------------------
//
//-----------------------------------------
	
void SAL_CALL CPreviewAdapterImpl::notifyParentSizeChanged()
{
	rearrangeLayout();
}

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

void SAL_CALL CPreviewAdapterImpl::notifyParentWindowPosChanged()
{
}

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

void SAL_CALL CPreviewAdapterImpl::rearrangeLayout()
{
	// try to get a handle to the filelistbox
	// if there is no new-style filelistbox like 
	// in Win2000/XP there should be at least the
	// old listbox, so we take this one
	// lst1 - identifies the old-style filelistbox
	// lst2 - identifies the new-style filelistbox
	// see dlgs.h
	HWND flb_new = findFileListbox();

	// under Windows NT 4.0 the size of the old
	// filelistbox will be used as reference for
	// sizing the new filelistbox, so we have 
	// to change the size of it too
	HWND flb_old = GetDlgItem(m_FileDialog,lst1);

	RECT rcFlbNew;
	GetWindowRect(flb_new,&rcFlbNew);

	RECT rcFileDlg;	
	GetWindowRect(m_FileDialog,&rcFileDlg);
	rcFileDlg.right -= m_RightMargin;

	// the available area for the filelistbox should be
	// the left edge of the filelistbox and the right 
	// edge of the OK button, we take this as reference
	int height = rcFlbNew.bottom - rcFlbNew.top;
	int width  = rcFileDlg.right - rcFlbNew.left;
	
	HWND prvwnd = m_Preview->getWindowHandle();
	
	// we use GetWindowLong to ask for the visibility
	// of the preview window because IsWindowVisible
	// only returns true the specified window including
	// its parent windows are visible
	// this is not the case when we are called in response
	// to the WM_SHOWWINDOW message, somehow the WS_VISIBLE
	// style bit of the FileOpen dialog must be set after that
	// message
	LONG lStyle = GetWindowLong(prvwnd,GWL_STYLE);
	bool bIsVisible = ((lStyle & WS_VISIBLE) != 0);

	int cx = 0;

	if (IsWindow(prvwnd) && bIsVisible)
	{
		cx = width/2;

		// resize the filelistbox to the half of the 
		// available space
		bool bRet = SetWindowPos(flb_new,
			NULL, 0, 0, cx, height,
			SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
		
		bRet = SetWindowPos(flb_old,
			NULL, 0, 0, cx, height, 
			SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);

		// get the new dimensions of the filelistbox after
		// resizing and take the right,top corner as starting
		// point for the preview window
		GetWindowRect(flb_new,&rcFlbNew);		
		POINT pt = { rcFlbNew.right, rcFlbNew.top };
		ScreenToClient(m_FileDialog,&pt);
		
		// resize the preview window to fit within
		// the available space and set the window
		// to the top of the z-order else it will 
		// be invisible
		SetWindowPos(prvwnd,
			HWND_TOP, pt.x, pt.y, cx, height, SWP_NOACTIVATE);		
	}
	else
	{
		// resize the filelistbox to the maximum available 
		// space
		cx = rcFileDlg.right - rcFlbNew.left;
		
		// resize the old filelistbox
		SetWindowPos(flb_old,
			NULL, 0, 0, cx, height, 
			SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);

		// resize the new filelistbox
		SetWindowPos(flb_new,
			NULL, 0, 0, cx, height,
			SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );			
	}
}

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

void SAL_CALL CPreviewAdapterImpl::initializeActivePreview() throw(std::runtime_error)
{
	sal_Bool bShowState = m_Preview->getImaginaryShowState();
			
	sal_Int16 aImgFrmt;
	Any aImg;
	m_Preview->getImage(aImgFrmt,aImg);

	HWND flb = findFileListbox();
			
	PreviewBase* prv = new CDIBPreview(
		m_Instance, GetParent(flb), bShowState);
	
	m_Preview.reset(prv);		
			
	m_Preview->setImage(aImgFrmt,aImg);
}

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

HWND SAL_CALL CPreviewAdapterImpl::findFileListbox() const 
{
	// try to get a handle to the filelistbox
	// if there is no new-style filelistbox like 
	// in Win2000/XP there should be at least the
	// old listbox, so we take this one
	// lst1 - identifies the old-style filelistbox
	// lst2 - identifies the new-style filelistbox
	// see dlgs.h
	HWND flb = GetDlgItem(m_FileDialog,lst2);	
	if (!IsWindow(flb))		
		flb = GetDlgItem(m_FileDialog,lst1);

	return flb;
}


//##############################################################


//-----------------------------------------
// Special implementation for Win98
// because:
//
//-----------------------------------------

class CWin98PreviewAdapterImpl : public CPreviewAdapterImpl
{
public:
	CWin98PreviewAdapterImpl(HINSTANCE instance);

	virtual void SAL_CALL notifyParentWindowPosChanged();

protected:
	virtual void SAL_CALL rearrangeLayout();
	
	bool isValidToolbarDimension() const;

private:
	sal_Bool	m_PreviewActive;
	int			m_ToolbarPosX;
	int			m_ToolbarPosY;
	int			m_ToolbarWidth;
	int			m_ToolbarHeight;
};

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

CWin98PreviewAdapterImpl::CWin98PreviewAdapterImpl(HINSTANCE instance) :
	CPreviewAdapterImpl(instance),
	m_PreviewActive(sal_False),
	m_ToolbarPosX(0),
	m_ToolbarPosY(0),
	m_ToolbarWidth(0),
	m_ToolbarHeight(0)
{
}

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

void SAL_CALL CWin98PreviewAdapterImpl::notifyParentWindowPosChanged()
{
	try
	{
		// the reason for this condition is 
		// Windows 98
		// Under Windows 98 the message WM_SHOWWINDOW
		// will be sent only the first time the 
		// GetOpenFileName function is called within
		// the same process
		// so we must use another message to initialize
		// the preview window
		if (IsWindow(m_FileDialog) && !m_PreviewActive)
		{
			initializeActivePreview();			
			m_PreviewActive = sal_True;
			rearrangeLayout();
		}
		
		if (IsWindow(m_FileDialog) && !isValidToolbarDimension())
		{
			RECT rcStc1;
			GetWindowRect(GetDlgItem(m_FileDialog,stc1),&rcStc1);
	
			RECT rcCmb2;
			GetWindowRect(GetDlgItem(m_FileDialog,cmb2),&rcCmb2);

			// Assumption:
			// the toolbar position is only valid
			// if the left edge is greater or equal
			// than the right edge of the drives listbox
			// the stc1 static text is invisible at runtime
			// but will be used as reference for the position
			// and dimension of the toolbar
			if (rcStc1.left >= rcCmb2.right)			
			{
				// important: save the upper left corner in
				// client coordinates 
				POINT pt = {rcStc1.left,rcStc1.top};
				ScreenToClient(m_FileDialog,&pt);

				m_ToolbarPosX   = pt.x;
				m_ToolbarPosY   = pt.y;
				m_ToolbarWidth  = rcStc1.right - rcStc1.left;
				m_ToolbarHeight = rcStc1.bottom - rcStc1.top;				
			}
		}
	}
	catch(std::runtime_error&)
	{		
	}	
}

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

void SAL_CALL CWin98PreviewAdapterImpl::rearrangeLayout()
{
	CPreviewAdapterImpl::rearrangeLayout();
		
	// fix the position of the upper toolbar
	// because the FileDialog moves all windows
	// that are to the right of the FileListbox
	// so if we have changed the size of the 
	// FileListbox we would run into trouble else
	if (isValidToolbarDimension())
	{
		HWND hwndTlb = FindWindowEx(
			m_FileDialog,NULL,TEXT("ToolbarWindow32"),NULL);

		SetWindowPos(hwndTlb,
			HWND_TOP, 
			m_ToolbarPosX,
			m_ToolbarPosY,
			m_ToolbarWidth,
			m_ToolbarHeight,
			SWP_NOACTIVATE);			
	}
}

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

bool CWin98PreviewAdapterImpl::isValidToolbarDimension() const
{
	return (m_ToolbarPosX   > 0 && 
			m_ToolbarPosY   > 0 && 
			m_ToolbarWidth  > 0 && 
			m_ToolbarHeight > 0);
}

//##############################################################


//--------------------------------------------
// Implementation for Windows 95/NT/ME/2000/XP
// because:
// 
//--------------------------------------------

class CWin95NTPreviewAdapterImpl : public CPreviewAdapterImpl
{
public:
	CWin95NTPreviewAdapterImpl(HINSTANCE instance);

	virtual void SAL_CALL notifyParentShow(sal_Bool bShow);
};

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

CWin95NTPreviewAdapterImpl::CWin95NTPreviewAdapterImpl(HINSTANCE instance) :
	CPreviewAdapterImpl(instance)
{
}

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

void SAL_CALL CWin95NTPreviewAdapterImpl::notifyParentShow(sal_Bool bShow)
{
	try
	{
		if (bShow)
		{			
			initializeActivePreview();			
			rearrangeLayout();
		}
	}
	catch(std::runtime_error&)
	{		
	}	
}


//##############################################################


//-------------------------------
// ctor
//-------------------------------

CPreviewAdapter::CPreviewAdapter(HINSTANCE instance)	
{
	if (!IsWindows98())
		m_pImpl.reset(new CWin95NTPreviewAdapterImpl(instance));
	else
		m_pImpl.reset(new CWin98PreviewAdapterImpl(instance));
}

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

CPreviewAdapter::~CPreviewAdapter()
{
}

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

Sequence<sal_Int16> SAL_CALL CPreviewAdapter::getSupportedImageFormats()
{
	com::sun::star::uno::Sequence<sal_Int16> imgFormats(1);
    imgFormats[0] = ::com::sun::star::ui::dialogs::FilePreviewImageFormats::BITMAP;
    return imgFormats;
}

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

sal_Int32 SAL_CALL CPreviewAdapter::getTargetColorDepth()
{
	return m_pImpl->getTargetColorDepth();
}

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

sal_Int32 SAL_CALL CPreviewAdapter::getAvailableWidth()
{
	return m_pImpl->getAvailableWidth();
}

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

sal_Int32 SAL_CALL CPreviewAdapter::getAvailableHeight()
{
	return m_pImpl->getAvailableHeight();
}

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

void SAL_CALL CPreviewAdapter::setImage( sal_Int16 aImageFormat, const Any& aImage ) 
	throw (IllegalArgumentException, RuntimeException)
{
	m_pImpl->setImage(aImageFormat,aImage);
}

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

sal_Bool SAL_CALL CPreviewAdapter::setShowState( sal_Bool bShowState )
{
	return m_pImpl->setShowState(bShowState);		
}

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

sal_Bool SAL_CALL CPreviewAdapter::getShowState()
{
	return m_pImpl->getShowState();
}

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

void SAL_CALL CPreviewAdapter::setParent(HWND parent)
{
	m_pImpl->setParent(parent);
}

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

void SAL_CALL CPreviewAdapter::notifyParentShow(bool bShow)
{
	m_pImpl->notifyParentShow(bShow);
}

//-------------------------------
//
//-------------------------------
	
void SAL_CALL CPreviewAdapter::notifyParentSizeChanged()
{
	m_pImpl->notifyParentSizeChanged();
}

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

void SAL_CALL CPreviewAdapter::notifyParentWindowPosChanged()
{
	m_pImpl->notifyParentWindowPosChanged();
}
