/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

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

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

#include <tchar.h>
#include "helppopupwindow.hxx"
#include <osl/diagnose.h>

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

using rtl::OUString;
using osl::Mutex;

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

namespace /* private */
{

    const LPTSTR CURRENT_INSTANCE = TEXT("CurrInst");

};

//------------------------------------------------------------------------
// defines
//------------------------------------------------------------------------

#define HELPPOPUPWND_CLASS_NAME TEXT("hlppopupwnd###")

const sal_Int32 MAX_CHARS_PER_LINE = 55;

const sal_Int32 SHADOW_WIDTH  = 6;
const sal_Int32 SHADOW_HEIGHT = 6;
const sal_Int32 SHADOW_OFFSET = 6;
const sal_Int32 YOFFSET       = 20;

const DWORD OUTER_FRAME_COLOR     = 0; // black
const sal_Int32 OUTER_FRAME_WIDTH = 1; // pixel

// it's the standard windows color of an inactive window border
const DWORD INNER_FRAME_COLOR     = 0xC8D0D4; 
const sal_Int32 INNER_FRAME_WIDTH = 1; // pixel

//---------------------------------------------------
// static member initialization
//---------------------------------------------------

osl::Mutex CHelpPopupWindow::s_Mutex;
ATOM CHelpPopupWindow::s_ClassAtom = 0;
sal_Int32 CHelpPopupWindow::s_RegisterWndClassCount = 0;

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

CHelpPopupWindow::CHelpPopupWindow( 
    HINSTANCE hInstance,    
    HWND hwndParent ) :    
    m_hMargins( 0 ),
    m_vMargins( 0 ),
    m_avCharWidth( 0 ),
    m_avCharHeight( 0 ),
    m_hwnd( NULL ),
    m_hwndParent( hwndParent ),
    m_hInstance( hInstance ),
    m_hBitmapShadow( NULL ),
    m_hBrushShadow( NULL )
{	
    m_bWndClassRegistered = RegisterWindowClass( ) ? sal_True : sal_False;           

    // create a pattern brush for the window shadow
    WORD aPattern[] = { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 };
        
    m_hBitmapShadow = CreateBitmap( 8, 8, 1, 1, aPattern );        
    m_hBrushShadow  = CreatePatternBrush( m_hBitmapShadow );
}

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

CHelpPopupWindow::~CHelpPopupWindow( )
{	
    // remember: we don't have to destroy the
    // preview window because it will be destroyed
    // by it's parent window (the FileOpen dialog)
    // but we have to unregister the window class
    if ( m_bWndClassRegistered )
        UnregisterWindowClass( );

    DeleteObject( m_hBitmapShadow );
    DeleteObject( m_hBrushShadow );
}

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

void SAL_CALL CHelpPopupWindow::setText( const rtl::OUString& aHelpText )
{
    m_HelpText = aHelpText;
}

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

void SAL_CALL CHelpPopupWindow::show( sal_Int32 x, sal_Int32 y )
{
    OSL_ENSURE( NULL == m_hwnd, "method should not be called twice in sequence" );

    // we create a window with length and heigth of 0
    // first in order to get a device context of this 
    // window, then we calculate the upper left corner
    // and the dimensions and resize the window

    m_hwnd = CreateWindowEx( 
        NULL,
        HELPPOPUPWND_CLASS_NAME,
        NULL,
        WS_POPUP,
        0,
        0,
        0,
        0,
        m_hwndParent,
        NULL,
        m_hInstance,
        (LPVOID)this );

    OSL_ENSURE( m_hwnd, "creating help popup window failed" );

    sal_Int32 cx_new;
    sal_Int32 cy_new;

    adjustWindowSize( &cx_new, &cy_new );
    adjustWindowPos( x, y, cx_new, cy_new );

    UpdateWindow( m_hwnd );
    ShowWindow( m_hwnd, SW_SHOW );
}

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

HWND SAL_CALL CHelpPopupWindow::setParent( HWND hwndNewParent )
{
    HWND oldParent = m_hwndParent;

    m_hwndParent = hwndNewParent;

    return oldParent;
}

//---------------------------------------------------
// calculates the necessary dimensions of the popup
// window including the margins etc.
//---------------------------------------------------

void SAL_CALL CHelpPopupWindow::calcWindowRect( LPRECT lprect )
{
    OSL_ASSERT( m_hwnd && lprect );
            
    SetRect( lprect, 0, 0, MAX_CHARS_PER_LINE * m_avCharWidth, 0 );

    HDC hdc = GetDC( m_hwnd );

    // set the font we are using later
    HGDIOBJ oldFont = SelectObject( 
        hdc, GetStockObject( DEFAULT_GUI_FONT ) );
    
    UINT nFormat = DT_WORDBREAK | DT_CALCRECT | DT_EXTERNALLEADING | DT_LEFT;

    if ( m_HelpText.getLength( ) <= MAX_CHARS_PER_LINE )
        nFormat |= DT_SINGLELINE;

    DrawText( 
      hdc, 
      reinterpret_cast<LPCTSTR>(m_HelpText.getStr( )), 
      m_HelpText.getLength( ), 
      lprect, 
      nFormat );
   
    // add the necessary space for the frames
    // and margins

    lprect->bottom += 
        m_vMargins + 
        SHADOW_HEIGHT + 
        OUTER_FRAME_WIDTH * 2 + 
        INNER_FRAME_WIDTH * 2;
   
    lprect->right += 
        SHADOW_WIDTH + 
        2 * m_avCharWidth + 
        OUTER_FRAME_WIDTH * 2 + 
        INNER_FRAME_WIDTH * 2;

    SelectObject( hdc, oldFont );

    ReleaseDC( m_hwnd, hdc );
}

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

void SAL_CALL CHelpPopupWindow::adjustWindowSize( sal_Int32* cx_new, sal_Int32* cy_new )
{
    OSL_ASSERT( cx_new && cy_new );

    RECT rect;
    calcWindowRect( &rect );

    // adjust the window size
    SetWindowPos(
        m_hwnd,
        NULL,
        0,
        0,
        rect.right,
        rect.bottom,
        SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER );

    *cx_new = rect.right;
    *cy_new = rect.bottom;
}

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

void SAL_CALL CHelpPopupWindow::adjustWindowPos( 
    sal_Int32 x, sal_Int32 y, sal_Int32 cx, sal_Int32 cy )
{            
    int   popX;
    int   popY;
    int   popWidth;
    int   popHeight;
    
    OSL_ASSERT( m_hwnd );

    HDC hdc = GetDC( m_hwnd );

    // assuming these are screen coordinates     
    popWidth  = cx;
    popHeight = cy;
    popX      = x - ( popWidth / 2 );
    popY      = y - YOFFSET;

    int xScreen = GetDeviceCaps( hdc, HORZRES );
    int yScreen = GetDeviceCaps( hdc, VERTRES );

    if (popX < 0)
        popX = 0;
    
    if (popY < 0)
        popY = 0;
        
    if ((popX + popWidth) > xScreen)
        popX = xScreen - popWidth;
        
    if ((popY + popHeight) > yScreen)
        popY = yScreen - popHeight;

    SetWindowPos(
        m_hwnd,
        NULL,
        popX,
        popY,
        0,
        0,
        SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE );        

    ReleaseDC( m_hwnd, hdc );
}

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

void SAL_CALL CHelpPopupWindow::onPaint( HWND hWnd, HDC hdc )
{
    RECT        rc;
    RECT        rect;
    HGDIOBJ     hpen, hpenOld;
    HGDIOBJ     hbrOld;        
    COLORREF    oldBkColor;
    COLORREF    oldTextColor;
    HGDIOBJ     oldFont;
    HGDIOBJ     oldBrush;
    HGDIOBJ     hBrush;
        
    GetClientRect( hWnd, &rc );

    // draw the black border 

    hBrush   = CreateSolidBrush( GetSysColor( COLOR_INFOBK ) );
    oldBrush = SelectObject( hdc, hBrush );
    
    hpen    = CreatePen( PS_SOLID, 0, OUTER_FRAME_COLOR );
    hpenOld = SelectObject( hdc, hpen );

    Rectangle(  hdc,
                rc.left   + OUTER_FRAME_WIDTH,
                rc.top    + OUTER_FRAME_WIDTH,
                rc.right  - SHADOW_WIDTH,
                rc.bottom - SHADOW_HEIGHT);

    SelectObject( hdc, oldBrush );
    SelectObject( hdc, hpenOld );

    DeleteObject( hBrush );
    DeleteObject( hpen );

    // draw a light gray border

    hBrush   = CreateSolidBrush( GetSysColor( COLOR_INFOBK ) );
    oldBrush = SelectObject( hdc, hBrush );

    hpen    = CreatePen( PS_SOLID, 0, INNER_FRAME_COLOR );
    hpenOld = SelectObject( hdc, hpen );

    Rectangle(  hdc,
                rc.left   + OUTER_FRAME_WIDTH + 1,
                rc.top    + OUTER_FRAME_WIDTH + 1,
                rc.right  - SHADOW_WIDTH  - OUTER_FRAME_WIDTH,
                rc.bottom - SHADOW_HEIGHT - OUTER_FRAME_WIDTH );

    SelectObject( hdc, oldBrush );
    SelectObject( hdc, hpenOld );

    DeleteObject( hBrush );
    DeleteObject( hpen );
          
    // Write some text to this window

    rect.left   = rc.left   + OUTER_FRAME_WIDTH + INNER_FRAME_WIDTH + 1 + m_hMargins;
    rect.top    = rc.top    + OUTER_FRAME_WIDTH + INNER_FRAME_WIDTH + 1 + m_vMargins / 2;
    rect.right  = rc.right  - SHADOW_WIDTH      - OUTER_FRAME_WIDTH - INNER_FRAME_WIDTH - m_hMargins;
    rect.bottom = rc.bottom - SHADOW_HEIGHT     - OUTER_FRAME_WIDTH - INNER_FRAME_WIDTH - m_vMargins / 2;

    oldBkColor   = SetBkColor( hdc, GetSysColor( COLOR_INFOBK ) );
    oldTextColor = SetTextColor( hdc, COLOR_INFOTEXT );
            
    oldFont = SelectObject( hdc, GetStockObject( DEFAULT_GUI_FONT ) );

    UINT nFormat = DT_WORDBREAK | DT_EXTERNALLEADING | DT_LEFT;
    
    if ( m_HelpText.getLength( ) <= MAX_CHARS_PER_LINE )
        nFormat |= DT_SINGLELINE;

    DrawText( 
        hdc, 
        (LPWSTR)m_HelpText.getStr( ), 
        m_HelpText.getLength( ), 
        &rect, 
        nFormat );

    SelectObject( hdc, oldFont );
    SetTextColor( hdc, oldTextColor );
    SetBkColor( hdc, oldBkColor );

    // set text color and text background color 
    // see MSDN PatBlt

    oldBkColor   = SetBkColor( hdc, RGB( 0, 0, 0 ) );
    oldTextColor = SetTextColor( hdc, RGB( 255, 255, 255 ) );

    // Get our brush for the shadow

    UnrealizeObject( m_hBrushShadow );
    hbrOld = SelectObject( hdc, m_hBrushShadow );

    // bottom shadow

    PatBlt(hdc,
           rc.left + SHADOW_OFFSET,
           rc.bottom - SHADOW_HEIGHT,
           rc.right - SHADOW_OFFSET - SHADOW_WIDTH,
           SHADOW_HEIGHT,
           0xA000C9);

    // right-side shadow

    PatBlt(hdc,
           rc.right - SHADOW_WIDTH,
           rc.top + SHADOW_OFFSET,
           SHADOW_WIDTH,
           rc.bottom - SHADOW_OFFSET,
           0xA000C9);

    SelectObject(hdc, hbrOld);
    SetTextColor( hdc, oldTextColor );
    SetBkColor( hdc, oldBkColor );
}

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

void SAL_CALL CHelpPopupWindow::onNcDestroy()
{
    m_hwnd = NULL;
}

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

void SAL_CALL CHelpPopupWindow::onCreate( HWND hwnd )
{
    m_hwnd = hwnd;

    HDC hdc = GetDC( m_hwnd );

    HGDIOBJ oldFont = SelectObject( 
        hdc, GetStockObject( DEFAULT_GUI_FONT ) );

    TEXTMETRIC tm;
    GetTextMetrics( hdc, &tm );

    m_avCharWidth  = tm.tmAveCharWidth;
    m_avCharHeight = tm.tmHeight;

    if ( 0 == m_hMargins )
        m_hMargins = m_avCharWidth;

    if ( 0 == m_vMargins ) 
        m_vMargins = m_avCharHeight;

    SelectObject( hdc, oldFont );

    ReleaseDC( m_hwnd, hdc );
}

//---------------------------------------------------
//
//---------------------------------------------------
    
LRESULT CALLBACK CHelpPopupWindow::WndProc( 
    HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    LRESULT lResult = 0;

    switch ( uMsg )
    {
        case WM_CREATE:
            {                                
                LPCREATESTRUCT lpcs = 
                    reinterpret_cast< LPCREATESTRUCT >( lParam );

                OSL_ASSERT( lpcs->lpCreateParams );
                
                CHelpPopupWindow* pImpl = reinterpret_cast< CHelpPopupWindow* >(
                    lpcs->lpCreateParams );
                
                // connect the instance handle to the window            
                SetProp( hWnd, CURRENT_INSTANCE, pImpl );                        

                pImpl->onCreate( hWnd );

                // capture mouse and keybord events
                SetCapture( hWnd );
             }
        break;

        case WM_PAINT:
            {
                CHelpPopupWindow* pImpl = reinterpret_cast< CHelpPopupWindow* >(
                GetProp( hWnd, CURRENT_INSTANCE ) );

                OSL_ASSERT( pImpl );

                PAINTSTRUCT ps;                

                BeginPaint(hWnd, &ps);
                pImpl->onPaint( hWnd, ps.hdc );
                EndPaint(hWnd, &ps);
            }
         break;

         case WM_NCDESTROY:
            {
                // RemoveProp returns the saved value on success
                CHelpPopupWindow* pImpl = reinterpret_cast< CHelpPopupWindow* >( 
                    RemoveProp( hWnd, CURRENT_INSTANCE ) );

                OSL_ASSERT( pImpl );

                pImpl->onNcDestroy();
            }
         break;

         case WM_LBUTTONDOWN:             
         case WM_KEYDOWN:
         case WM_SYSKEYDOWN:         
         case WM_MBUTTONDOWN:
         case WM_RBUTTONDOWN:
             ReleaseCapture();             
             DestroyWindow(hWnd);
         break;

         default:         
             return DefWindowProc(hWnd, uMsg, wParam, lParam);
       }

    return lResult;
}

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

ATOM SAL_CALL CHelpPopupWindow::RegisterWindowClass( )
{    
    osl::MutexGuard aGuard( s_Mutex );

    if ( 0 == s_ClassAtom )
    {     
        // register the window class 
        WNDCLASSEX wndClsEx;	

        ZeroMemory(&wndClsEx, sizeof(wndClsEx));

        wndClsEx.cbSize        = sizeof(wndClsEx);	    
        wndClsEx.lpfnWndProc   = CHelpPopupWindow::WndProc;
        wndClsEx.hInstance     = m_hInstance;
        wndClsEx.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wndClsEx.hbrBackground = (HBRUSH)GetStockObject( NULL_BRUSH );
        wndClsEx.lpszClassName = HELPPOPUPWND_CLASS_NAME;
                
        // register the preview window class
        // !!! Win95 -   the window class will be unregistered automaticly 
        //			     if the dll is unloaded
        //     Win2000 - the window class must be unregistered manually
        //				 if the dll is unloaded
        s_ClassAtom = RegisterClassEx( &wndClsEx );
        OSL_ASSERT(s_ClassAtom);        
    }

    // increment the register class counter
    // so that we keep track of the number
    // of class registrations
    if (0 != s_ClassAtom)
        s_RegisterWndClassCount++;

    return s_ClassAtom;
}

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

void SAL_CALL CHelpPopupWindow::UnregisterWindowClass( )
{
    osl::MutexGuard aGuard( s_Mutex );
    
    OSL_ASSERT( ( (0 != s_ClassAtom) && (s_RegisterWndClassCount > 0)) || 
                ( (0 == s_ClassAtom) && (0 == s_RegisterWndClassCount) ) );

    // update the register class counter 
    // and unregister the window class if
    // counter drops to zero    
    if ( 0 != s_ClassAtom )
    {
        s_RegisterWndClassCount--;
        OSL_ASSERT( s_RegisterWndClassCount >= 0 );
    }

    if ( 0 == s_RegisterWndClassCount )
    {
        if ( !UnregisterClass(
                 (LPCTSTR)MAKELONG( s_ClassAtom, 0 ), m_hInstance ) )
        {
            OSL_ENSURE( false, "unregister window class failed" );
        }

        s_ClassAtom = 0;
    }    
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
