/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "HeadSpin.h"
#include "max.h"
#include "resource.h"
#include "hsTemplates.h"

#include "pfGUISkinComp.h"
#include "plGUICompClassIDs.h"

#include "MaxMain/plMaxNodeBase.h"
#include "MaxMain/MaxCompat.h"


pfGUISkinEditProc   *pfGUISkinEditProc::fInstance = nil;
int                 pfGUISkinEditProc::fZoom = 3;       // So re-opening the dialog will keep the same zoom level

extern HINSTANCE hInstance;

pfGUISkinEditProc::pfGUISkinEditProc( plGUISkinComp *comp ) 
{
    fInstance = this; 
    fComp = comp;

    fDblDC = nil;
    fDblBitmap = nil;

    fXOffset = fYOffset = 0;

    fCurrPBRefSet = plGUISkinComp::kRefUpLeftCorner;

    fDefPen = CreatePen( PS_SOLID, 3, RGB( 200, 0, 0 ) );
    fOtherPen = CreatePen( PS_DOT, 1, RGB( 200, 0, 0 ) );

    fDragging = false;
    fDragTimer = 0;

    // Back up the old settings
    IParamBlock2 *pb = fComp->GetParamBlockByID( plComponent::kBlkComp );
    for( int i = 0; i < pfGUISkin::kNumElements; i++ )
    {
        int id = i * 4 + plGUISkinComp::kRefUpLeftCorner;
        fBackups[ i ].fX = pb->GetInt( id + 0 ); 
        fBackups[ i ].fY = pb->GetInt( id + 1 ); 
        fBackups[ i ].fWidth = pb->GetInt( id + 2 ); 
        fBackups[ i ].fHeight = pb->GetInt( id + 3 ); 
    }
}

pfGUISkinEditProc::~pfGUISkinEditProc() 
{
    fInstance = nil;
    DeleteObject( fDefPen );
    IKillDblBuffer();
}

void    pfGUISkinEditProc::IJustDrawOneRect( int whichElement, IParamBlock2 *pb, HDC hDC, HPEN whichPen, int refToIgnore )
{
    whichElement = ( whichElement * 4 ) + plGUISkinComp::kRefUpLeftCorner;

    if( whichElement == refToIgnore )
        return;

    RECT    r;
    SetRect( &r, pb->GetInt( whichElement + 0 ),
                 pb->GetInt( whichElement + 1 ),
                 pb->GetInt( whichElement + 0 ) + pb->GetInt( whichElement + 2 ),
                 pb->GetInt( whichElement + 1 ) + pb->GetInt( whichElement + 3 ) );
    
    r.left *= fZoom; r.right *= fZoom; r.top *= fZoom; r.bottom *= fZoom;

    SelectObject( hDC, whichPen );
    int rop2 = SetROP2( hDC, R2_NOTXORPEN );

    MoveToEx( hDC, r.left, r.top, nil );
    LineTo( hDC, r.right, r.top );
    LineTo( hDC, r.right, r.bottom );
    LineTo( hDC, r.left, r.bottom );
    LineTo( hDC, r.left, r.top );

    SetROP2( hDC, rop2 );
}

void    pfGUISkinEditProc::IRefreshDblBuffer( void )
{
    // Image buffer is where we keep our resized image. Dbl buffer is where we draw our bounds
    if( fDblDC == nil )
        IInitDblBuffer();
    else
    {
        // Copy the zoomed image as our backdrop
        BitBlt( fDblDC, 0, 0, fDblWidth, fDblHeight, fImageDC, 0, 0, SRCCOPY );

        RECT    r;
        IParamBlock2    *pb = fComp->GetParamBlockByID( plComponent::kBlkComp );
        if( pb != nil )
        {
            // Draw all the other elements other than our current one
            for( int i = 0; i < pfGUISkin::kNumElements; i++ )
                IJustDrawOneRect( i, pb, fDblDC, fOtherPen, fCurrPBRefSet );

            // Now draw the bounds of our current element
            SetRect( &r, pb->GetInt( fCurrPBRefSet + 0 ),
                         pb->GetInt( fCurrPBRefSet + 1 ),
                         pb->GetInt( fCurrPBRefSet + 0 ) + pb->GetInt( fCurrPBRefSet + 2 ),
                         pb->GetInt( fCurrPBRefSet + 1 ) + pb->GetInt( fCurrPBRefSet + 3 ) );
            
            // While we have it here, go ahead and update our status text for this element
            char str[ 256 ];
            sprintf( str, "Left: %d\nTop: %d\nWidth: %d\nHeight: %d\n", r.left, r.top, r.right - r.left, r.bottom - r.top );
            SetDlgItemText( fHWnd, IDC_GUI_INFO, str );
                            
            r.left *= fZoom; r.right *= fZoom; r.top *= fZoom; r.bottom *= fZoom;

            SelectObject( fDblDC, fDefPen );
            int rop2 = SetROP2( fDblDC, R2_NOTXORPEN );

            MoveToEx( fDblDC, r.left, r.top, nil );
            LineTo( fDblDC, r.right, r.top );
            LineTo( fDblDC, r.right, r.bottom );
            LineTo( fDblDC, r.left, r.bottom );
            LineTo( fDblDC, r.left, r.top );

            SetROP2( fDblDC, rop2 );

            fCurrElemRect = r;
            MapWindowPoints( GetDlgItem( fHWnd, IDC_GUI_PREVIEW ), fHWnd, (POINT *)&fCurrElemRect, 2 );
            OffsetRect( &fCurrElemRect, -fXOffset, -fYOffset );
        }
    }
}

void    pfGUISkinEditProc::IRefreshImageBuffer( void )
{
    IInitDblBuffer();

    plLayerTex *layer = fComp->GetSkinBitmap();
    PBBitmap *pbBMap = layer->GetPBBitmap();

    if( pbBMap->bm == nil )
        pbBMap->Load();
    if( pbBMap->bm != nil )
    {
        // Copy into a new temp bitmap that is the right format for us to read
        Bitmap *newBM;
        BitmapInfo bi;
        bi.SetName( _T("y879873b") );
        bi.SetType( BMM_TRUE_32 );
        bi.SetFlags( MAP_HAS_ALPHA );
        bi.SetWidth( fDblWidth );
        bi.SetHeight( fDblHeight );
        newBM = TheManager->Create( &bi );

        BMM_Color_64 foo = BMMCOLOR(0, 0, 0, 0);
        newBM->CopyImage( pbBMap->bm, COPY_IMAGE_RESIZE_LO_QUALITY, foo, nil );

        // Now copy from our newly created bitmap into our DC....way slow :(
        BITMAPINFO *bitInfo = newBM->ToDib( 24, nil, false );
        if( bitInfo != nil )
        {
            SetDIBitsToDevice( fImageDC, 0, 0, fDblWidth, fDblHeight,
                                0, 0, 0, fDblHeight,
                                ( (uint8_t *)bitInfo ) + bitInfo->bmiHeader.biSize,
                                bitInfo,
                                DIB_RGB_COLORS );
        }

        newBM->DeleteThis();
    }

    IRefreshDblBuffer();
}

void    pfGUISkinEditProc::IInitDblBuffer( void )
{
    if( fDblDC == NULL )
    {
        int     width, height;
        HDC     desk = GetDC( NULL );

        plLayerTex *layer = fComp->GetSkinBitmap();
        PBBitmap *pbBMap = layer->GetPBBitmap();
        if( pbBMap == nil )
            return;
        width = pbBMap->bi.Width() * fZoom;
        height = pbBMap->bi.Height() * fZoom;
//              GetClientRect( fHWnd, &r );
//              width = r.right - r.left;
//              height = r.bottom - r.top;

        //  Note: For some strange reason, grabbing the HDC of the window doesn't do
        //  any good, 'cause it's black-and-white (right, THAT makes sense). Grabbing
        //  the desktop DC works, however.

        fDblDC = CreateCompatibleDC( desk );
        fDblBitmap = CreateCompatibleBitmap( desk/*fDblDC*/, width, height );
        SelectObject( fDblDC, fDblBitmap );

        fImageDC = CreateCompatibleDC( desk );
        fImageBitmap = CreateCompatibleBitmap( desk/*fDblDC*/, width, height );
        SelectObject( fImageDC, fImageBitmap );

        ReleaseDC( NULL, desk );

        fDblWidth = width;
        fDblHeight = height;

        ISetScrollRanges();
        IRefreshImageBuffer();
    }
}

void    pfGUISkinEditProc::IKillDblBuffer( void )
{
    if( fDblDC != NULL )
    {
        SelectObject( fDblDC, (HBITMAP)NULL );
        DeleteObject( fDblBitmap );
        DeleteDC( fDblDC );
    }

    if( fImageDC != NULL )
    {
        SelectObject( fImageDC, (HBITMAP)NULL );
        DeleteObject( fImageBitmap );
        DeleteDC( fImageDC );
    }

    fDblDC = fImageDC = nil;
    fDblBitmap = fImageBitmap = nil;
}

void    pfGUISkinEditProc::ISetScrollRanges( void )
{
    SCROLLINFO  info;


    int visW = fPreviewRect.right - fPreviewRect.left;
    int visH = fPreviewRect.bottom - fPreviewRect.top;

    if( visW < fDblWidth )
    {
        if( fXOffset > fDblWidth - visW )
            fXOffset = fDblWidth - visW;
        else if( fXOffset < 0 )
            fXOffset = 0;

        info.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
        info.nMin = 0;
        info.nMax = fDblWidth;// - visW;
        info.nPage = visW;
        info.nPos = fXOffset;
        info.cbSize = sizeof( info );

        SetScrollInfo( GetDlgItem( fHWnd, IDC_GUI_HORZSCROLL ), SB_CTL, &info, true );
        ShowWindow( GetDlgItem( fHWnd, IDC_GUI_HORZSCROLL ), true );
    }
    else
    {
        ShowWindow( GetDlgItem( fHWnd, IDC_GUI_HORZSCROLL ), false );
        fXOffset = 0;
    }

    if( visH < fDblHeight )
    {
        if( fYOffset > fDblHeight - visH )
            fYOffset = fDblHeight - visH;
        else if( fYOffset < 0 )
            fYOffset = 0;

        info.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
        info.nMin = 0;
        info.nMax = fDblHeight;// - visH;
        info.nPage = visH;
        info.nPos = fYOffset;
        info.cbSize = sizeof( info );

        SetScrollInfo( GetDlgItem( fHWnd, IDC_GUI_VERTSCROLL ), SB_CTL, &info, true );
        ShowWindow( GetDlgItem( fHWnd, IDC_GUI_VERTSCROLL ), true );
    }
    else
    {
        ShowWindow( GetDlgItem( fHWnd, IDC_GUI_VERTSCROLL ), false );
        fYOffset = 0;
    }
}

bool    pfGUISkinEditProc::IPointWithinRange( int x, int y, int ptX, int ptY )
{
    if( x > ptX - kRangeSlop && x < ptX + kRangeSlop &&
        y > ptY - kRangeSlop && y < ptY + kRangeSlop )
        return true;
    return false;
}

bool    pfGUISkinEditProc::IPointWithinVertRange( int x, int y, int ptX, int ptY1, int ptY2 )
{
    if( x > ptX - kRangeSlop && x < ptX + kRangeSlop &&
        y > ptY1 - kRangeSlop && y < ptY2 + kRangeSlop )
        return true;
    return false;
}

bool    pfGUISkinEditProc::IPointWithinHorzRange( int x, int y, int ptX1, int ptX2, int ptY )
{
    if( x > ptX1 - kRangeSlop && x < ptX2 + kRangeSlop &&
        y > ptY - kRangeSlop && y < ptY + kRangeSlop )
        return true;
    return false;
}

uint8_t   pfGUISkinEditProc::IGetDragTypeFlags( int x, int y )
{
    // Corners
    if( IPointWithinRange( x, y, fCurrElemRect.left, fCurrElemRect.top ) )
        return kLeft | kTop;
    if( IPointWithinRange( x, y, fCurrElemRect.right, fCurrElemRect.top ) )
        return kRight | kTop;
    if( IPointWithinRange( x, y, fCurrElemRect.right, fCurrElemRect.bottom ) )
        return kRight | kBottom;
    if( IPointWithinRange( x, y, fCurrElemRect.left, fCurrElemRect.bottom ) )
        return kLeft | kBottom;

    // Edges
    if( IPointWithinVertRange( x, y, fCurrElemRect.left, fCurrElemRect.top, fCurrElemRect.bottom ) )
        return kLeft;
    if( IPointWithinVertRange( x, y, fCurrElemRect.right, fCurrElemRect.top, fCurrElemRect.bottom ) )
        return kRight;

    if( IPointWithinHorzRange( x, y, fCurrElemRect.left, fCurrElemRect.right, fCurrElemRect.top ) )
        return kTop;
    if( IPointWithinHorzRange( x, y, fCurrElemRect.left, fCurrElemRect.right, fCurrElemRect.bottom ) )
        return kBottom;

    // The middle
    if( x >= fCurrElemRect.left && x <= fCurrElemRect.right && y >= fCurrElemRect.top && y <= fCurrElemRect.bottom )
        return kDragAll;

    return 0;
}

INT_PTR CALLBACK    pfGUISkinEditProc::DlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
    return fInstance->DialogProc( hDlg, msg, wParam, lParam );
}

INT_PTR CALLBACK    pfGUISkinEditProc::DialogProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT     pInfo;
    RECT            r;
    HDC             hDC;
    int             maxDim, i, j, x, y;
    bool            timerActive = false;


    static struct plElemPair
    {
        pfGUISkin::Elements el;
        const char          *name;
    } sElemPairs[] = { { pfGUISkin::kUpLeftCorner,          "Upper-left Corner" },
                       { pfGUISkin::kTopSpan,               "Top Span" },
                       { pfGUISkin::kUpRightCorner,         "Upper-right Corner" },
                       { pfGUISkin::kRightSpan,             "Right Span" },
                       { pfGUISkin::kLowerRightCorner,      "Lower-right Corner" },
                       { pfGUISkin::kBottomSpan,            "Bottom Span" },
                       { pfGUISkin::kLowerLeftCorner,       "Lower-left Corner" },
                       { pfGUISkin::kLeftSpan,              "Left Span" },
                       { pfGUISkin::kMiddleFill,            "Middle Fill" },
                       { pfGUISkin::kSelectedFill,          "Selected Middle Fill" },
                       { pfGUISkin::kSubMenuArrow,          "Sub-Menu Arrow" },
                       { pfGUISkin::kSelectedSubMenuArrow,  "Selected Sub-Menu Arrow" },
                       { pfGUISkin::kTreeButtonClosed,      "Tree-view Button, Closed" },
                       { pfGUISkin::kTreeButtonOpen,        "Tree-view Button, Open" },
                       { pfGUISkin::kNumElements, nil } };


    fHWnd = hDlg;

    switch( msg )
    {
        case WM_INITDIALOG:

            // Get preview rect
            GetClientRect( GetDlgItem( hDlg, IDC_GUI_PREVIEW ), &fPreviewRect );
            MapWindowPoints( GetDlgItem( hDlg, IDC_GUI_PREVIEW ), hDlg, (POINT *)&fPreviewRect, 2 );

            SendDlgItemMessage( hDlg, IDC_GUI_ZIN, BM_SETIMAGE, (WPARAM)IMAGE_ICON, 
                                        (LPARAM)LoadImage( hInstance, MAKEINTRESOURCE( IDI_ZOOMIN ), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR ) );
            SendDlgItemMessage( hDlg, IDC_GUI_ZOUT, BM_SETIMAGE, (WPARAM)IMAGE_ICON, 
                                        (LPARAM)LoadImage( hInstance, MAKEINTRESOURCE( IDI_ZOOMOUT ), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR ) );

            // Fill element list
            SendDlgItemMessage( hDlg, IDC_GUI_ELEMENTS, LB_RESETCONTENT, 0, 0 );
            for( i = 0; sElemPairs[ i ].el != pfGUISkin::kNumElements; i++ )
            {
                int idx = SendDlgItemMessage( hDlg, IDC_GUI_ELEMENTS, LB_ADDSTRING, 0, (LPARAM)sElemPairs[ i ].name );
                SendDlgItemMessage( hDlg, IDC_GUI_ELEMENTS, LB_SETITEMDATA, (WPARAM)idx, (LPARAM)sElemPairs[ i ].el );
                if( sElemPairs[ i ].el == pfGUISkin::kUpLeftCorner )
                    j = idx;
            }
            SendDlgItemMessage( hDlg, IDC_GUI_ELEMENTS, LB_SETCURSEL, j, 0 );

            fOrigCursor = LoadCursor( nil, IDC_ARROW );//GetCursor();

            break;

        case WM_COMMAND:
            if( LOWORD( wParam ) == IDCANCEL )
            {
                // Since we've been editing the PB directly, we have to now restore them
                // to their original values
                IParamBlock2 *pb = fComp->GetParamBlockByID( plComponent::kBlkComp );
                for( int i = 0; i < pfGUISkin::kNumElements; i++ )
                {
                    int id = i * 4 + plGUISkinComp::kRefUpLeftCorner;
                    pb->SetValue( id + 0, 0, (int)fBackups[ i ].fX );
                    pb->SetValue( id + 1, 0, (int)fBackups[ i ].fY ); 
                    pb->SetValue( id + 2, 0, (int)fBackups[ i ].fWidth );
                    pb->SetValue( id + 3, 0, (int)fBackups[ i ].fHeight );
                }
                EndDialog( hDlg, 1 );
            }
            else if( LOWORD( wParam ) == IDOK )
                EndDialog( hDlg, 0 );
            else if( LOWORD( wParam ) == IDC_GUI_ZIN )
            {
                fXOffset /= fZoom;  fYOffset /= fZoom;
                fZoom++;
                fXOffset *= fZoom;  fYOffset *= fZoom;

                IKillDblBuffer();
                IRefreshImageBuffer();
                InvalidateRect( hDlg, &fPreviewRect, false );
            }
            else if( LOWORD( wParam ) == IDC_GUI_ZOUT )
            {
                if( fZoom > 1 )
                {
                    fXOffset /= fZoom;  fYOffset /= fZoom;
                    fZoom--;
                    fXOffset *= fZoom;  fYOffset *= fZoom;

                    IKillDblBuffer();
                    IRefreshImageBuffer();
                    InvalidateRect( hDlg, &fPreviewRect, false );
                }
            }
            else if( LOWORD( wParam ) == IDC_GUI_ELEMENTS )
            {
                int idx = SendDlgItemMessage( hDlg, IDC_GUI_ELEMENTS, LB_GETCURSEL, 0, 0 );
                fCurrPBRefSet = SendDlgItemMessage( hDlg, IDC_GUI_ELEMENTS, LB_GETITEMDATA, (WPARAM)idx, 0 ) * 4 + plGUISkinComp::kRefUpLeftCorner;

                IRefreshDblBuffer();
                InvalidateRect( hDlg, &fPreviewRect, false );
            }
            return true;

        case WM_CLOSE:
            EndDialog( hDlg, 0 );
            return true;

        case WM_HSCROLL:
            OffsetRect( &fCurrElemRect, fXOffset, fYOffset );
            switch( LOWORD( wParam ) )
            {
                case SB_PAGEUP:     fXOffset -= 300; break;
                case SB_PAGEDOWN:   fXOffset += 300; break;
                case SB_LINEUP:     fXOffset -= 16; break;
                case SB_LINEDOWN:   fXOffset += 16; break;
                case SB_THUMBPOSITION:
                case SB_THUMBTRACK:
                    fXOffset = HIWORD( wParam );
                    break;
            }
            maxDim = fDblWidth - ( fPreviewRect.right - fPreviewRect.left );
            if( fXOffset < 0 )
                fXOffset = 0;
            else if( fXOffset > maxDim )
                fXOffset = maxDim;
            SetScrollPos( GetDlgItem( hDlg, IDC_GUI_HORZSCROLL ), SB_CTL, fXOffset, true );

            OffsetRect( &fCurrElemRect, -fXOffset, -fYOffset );
            InvalidateRect( hDlg, &fPreviewRect, false );
            break;

        case WM_VSCROLL:
            OffsetRect( &fCurrElemRect, fXOffset, fYOffset );
            switch( LOWORD( wParam ) )
            {
                case SB_PAGEUP:     fYOffset -= 300; break;
                case SB_PAGEDOWN:   fYOffset += 300; break;
                case SB_LINEUP:     fYOffset -= 16; break;
                case SB_LINEDOWN:   fYOffset += 16; break;
                case SB_THUMBPOSITION:
                case SB_THUMBTRACK:
                    fYOffset = HIWORD( wParam );
                    break;
            }
            maxDim = fDblHeight - ( fPreviewRect.bottom - fPreviewRect.top );
            if( fYOffset < 0 )
                fYOffset = 0;
            else if( fYOffset > maxDim )
                fYOffset = maxDim;
            SetScrollPos( GetDlgItem( hDlg, IDC_GUI_VERTSCROLL ), SB_CTL, fYOffset, true );

            OffsetRect( &fCurrElemRect, -fXOffset, -fYOffset );
            InvalidateRect( hDlg, &fPreviewRect, false );
            break;

        case WM_PAINT:
            {
                BeginPaint( hDlg, &pInfo );
                hDC = (HDC)pInfo.hdc;

                if( fDblDC == NULL )
                    IInitDblBuffer();

                int width = fDblWidth;
                int height = fDblHeight;
                if( width > fPreviewRect.right - fPreviewRect.left )
                    width = fPreviewRect.right - fPreviewRect.left;
                if( height > fPreviewRect.bottom - fPreviewRect.top )
                    height = fPreviewRect.bottom - fPreviewRect.top;

                BitBlt( hDC, fPreviewRect.left, fPreviewRect.top, width, height, fDblDC, fXOffset, fYOffset, SRCCOPY );

                r = fPreviewRect;
                r.left += width;
                FillRect( hDC, &r, ColorMan()->GetBrush( kBackground ) );

                r = fPreviewRect;
                r.top += height;
                FillRect( hDC, &r, ColorMan()->GetBrush( kBackground ) );

                EndPaint( hDlg, &pInfo );
            }
            break;

        case WM_LBUTTONDOWN:
            SetCapture( hDlg );
            fDragging = true;
            fDragType = IGetDragTypeFlags( GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
            if( fDragType == kDragAll )
            {
                fDragOffsetX = fCurrElemRect.left - GET_X_LPARAM( lParam );
                fDragOffsetY = fCurrElemRect.top - GET_Y_LPARAM( lParam );
            }
            else if( fDragType == 0 )
            {
                fDragOffsetX = GET_X_LPARAM( lParam ) + fXOffset;
                fDragOffsetY = GET_Y_LPARAM( lParam ) + fYOffset;
            }
            else
                fDragOffsetX = fDragOffsetY = 0;

            break;

        case WM_LBUTTONUP:
            ReleaseCapture();
            fDragging = false;
            break;

        case WM_TIMER:
            // We do the same processing as MOUSEMOVE, but we need to make sure we have the right
            // mouse position first
            {
                POINT pt;
                GetCursorPos( &pt );
                MapWindowPoints( nil, hDlg, &pt, 1 );
                lParam = MAKELPARAM( pt.x, pt.y );
            }
            // Fall thru...

        case WM_MOUSEMOVE:
            x = GET_X_LPARAM( lParam );
            y = GET_Y_LPARAM( lParam );

            if( fDragging )
            {
                if( fDragType == 0 )
                {
                    fXOffset = fDragOffsetX - x;
                    fYOffset = fDragOffsetY - y;
                    ISetScrollRanges(); // Will clamp offset \for us
                }
                else
                {
                    // Translate x and y into bitmap space
                    POINT pt;
                    pt.x = x;
                    pt.y = y;

                    if( PtInRect( &fPreviewRect, pt ) )
                    {
                        MapWindowPoints( hDlg, GetDlgItem( hDlg, IDC_GUI_PREVIEW ), &pt, 1 );

                        pt.x += fDragOffsetX;
                        pt.y += fDragOffsetY;

                        // Note the + 1/2 zoom so it's the closest pixel by center, not by area
                        x = ( pt.x + fXOffset + ( fZoom >> 1 ) ) / fZoom;
                        y = ( pt.y + fYOffset + ( fZoom >> 1 ) ) / fZoom;

                        // Set depending on our current drag flags
                        // Note the logic here: if we drag left, we want width and left changing,
                        // if we drag both, just left, if we drag right, just width
                        IParamBlock2 *pb = fComp->GetParamBlockByID( plComponent::kBlkComp );
                        if( fDragType & kLeft )
                        {
                            if( fDragType & kRight )
                                pb->SetValue( fCurrPBRefSet + 0, 0, (int)x );
                            else 
                            {
                                int old = pb->GetInt( fCurrPBRefSet + 0 ) + pb->GetInt( fCurrPBRefSet + 2 );

                                pb->SetValue( fCurrPBRefSet + 0, 0, (int)x );
                                pb->SetValue( fCurrPBRefSet + 2, 0, (int)old - x );
                            }
                        }
                        else if( fDragType & kRight )
                            pb->SetValue( fCurrPBRefSet + 2, 0, (int)x - pb->GetInt( fCurrPBRefSet + 0 ) );

                        if( fDragType & kTop )
                        {
                            if( fDragType & kBottom )
                                pb->SetValue( fCurrPBRefSet + 1, 0, (int)y );
                            else 
                            {
                                int old = pb->GetInt( fCurrPBRefSet + 1 ) + pb->GetInt( fCurrPBRefSet + 3 );

                                pb->SetValue( fCurrPBRefSet + 1, 0, (int)y );
                                pb->SetValue( fCurrPBRefSet + 3, 0, (int)old - y );
                            }
                        }
                        else if( fDragType & kBottom )
                            pb->SetValue( fCurrPBRefSet + 3, 0, (int)y - pb->GetInt( fCurrPBRefSet + 1 ) );

                        // Clamp width and height
                        if( pb->GetInt( fCurrPBRefSet + 2 ) < 0 )
                            pb->SetValue( fCurrPBRefSet + 2, 0, (int)0 );
                        if( pb->GetInt( fCurrPBRefSet + 3 ) < 0 )
                            pb->SetValue( fCurrPBRefSet + 3, 0, (int)0 );

                        // Clamp X and Y
                        if( pb->GetInt( fCurrPBRefSet + 0 ) < 0 )
                            pb->SetValue( fCurrPBRefSet + 0, 0, (int)0 );
                        else if( pb->GetInt( fCurrPBRefSet + 0 ) + pb->GetInt( fCurrPBRefSet + 2 ) > fDblWidth / fZoom )
                            pb->SetValue( fCurrPBRefSet + 0, 0, (int)fDblWidth / fZoom - pb->GetInt( fCurrPBRefSet + 2 ) );

                        if( pb->GetInt( fCurrPBRefSet + 1 ) < 0 )
                            pb->SetValue( fCurrPBRefSet + 1, 0, (int)0 );
                        else if( pb->GetInt( fCurrPBRefSet + 1 ) + pb->GetInt( fCurrPBRefSet + 3 ) > fDblHeight / fZoom )
                            pb->SetValue( fCurrPBRefSet + 1, 0, (int)fDblHeight / fZoom - pb->GetInt( fCurrPBRefSet + 3 ) );
                    }
                    else
                    {
                        // Mouse is outside our preview, so scroll if possible
                        int dX = ( x < fPreviewRect.left ) ? x - fPreviewRect.left : ( x > fPreviewRect.right ) ? x - fPreviewRect.right : 0;
                        int dY = ( y < fPreviewRect.top ) ? y - fPreviewRect.top : ( y > fPreviewRect.bottom ) ? y - fPreviewRect.bottom : 0;

                        fXOffset += dX;
                        fYOffset += dY;         
                        OffsetRect( &fCurrElemRect, -dX, -dY );

                        ISetScrollRanges(); // Will clamp origin for us

                        // Don't actually drag our bounds, 'cause we're scrolling

                        // Note: since we only get MOUSEMOVE when, gee, the mouse moves, if we've scrolled over, it'll only
                        // do it once and then wait for the mouse to nudge again. We'd rather it keep going until the user
                        // moves the mouse again, so we create a timer that calls us back in n somethingths so we can check again
                        if( fDragTimer == 0 )
                            fDragTimer = SetTimer( hDlg, 0, 200, nil );
                        timerActive = true;     // So we don't kill it at the end here...
                    }
                }

                IRefreshDblBuffer();
                InvalidateRect( hDlg, &fPreviewRect, false );
            }
            else
            {
                uint8_t dragType = IGetDragTypeFlags( x, y );
                HCURSOR cursor;
                switch( dragType )
                {
                    case kLeft | kTop:      
                    case kRight | kBottom:
                        cursor = LoadCursor( nil, IDC_SIZENWSE );
                        break;
                    case kLeft | kBottom:       
                    case kRight | kTop:
                        cursor = LoadCursor( nil, IDC_SIZENESW );
                        break;
                    case kLeft:     
                    case kRight:
                        cursor = LoadCursor( nil, IDC_SIZEWE );
                        break;
                    case kTop:      
                    case kBottom:
                        cursor = LoadCursor( nil, IDC_SIZENS );
                        break;
                    case kLeft | kTop | kRight | kBottom:       
                        cursor = LoadCursor( nil, IDC_SIZEALL );
                        break;
                    default:
                        {
                            POINT pt;
                            pt.x = x;
                            pt.y = y;
                            if( PtInRect( &fPreviewRect, pt ) )
                                cursor = LoadCursor( nil, IDC_HAND );
                            else
                                cursor = fOrigCursor;
                        }
                        break;
                }
                
                SetCursor( cursor );

                if( !timerActive )
                {
                    // No longer need our trick timer, so kill it
                    KillTimer( hDlg, fDragTimer );
                    fDragTimer = 0;
                }
            }
            break;
    }

    return false;
}

plGUISkinComp   *plGUISkinComp::GetGUIComp( INode *node )
{
    if( node == nil )
        return nil;

    plComponentBase *base = ( ( plMaxNodeBase *)node )->ConvertToComponent();
    if( base == nil )
        return nil;

    if( base->ClassID() == GUI_SKIN_CLASSID )
        return (plGUISkinComp *)base;

    return nil;
}

