/*
======================================================================
mainwnd.c

Ernie Wright  14 Apr 05
MSVC 4.0

Support for the image viewer main window, including the functions
called by image viewer clients.
====================================================================== */

#include "qv.h"
#include <commctrl.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


LRESULT CALLBACK imvWndProc( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK imvDrawWndProc( HWND, UINT, WPARAM, LPARAM );

HICON hiconlg, hiconsm;

static HINSTANCE hinst;
static int current;
static QVConfig config;

static char *wndclass[] = {
   "QVImageViewMain",
   "QVImageViewDraw"
};


/*
======================================================================
add_image()

Insert an image and make it the one currently displayed.

INPUTS
   hwnd           the QV main window
   image          the new image

This is called when QV's main thread sends WndProc() a UM_ADDIMAGE
message.  If all the slots are full, the oldest image is discarded to
make room for the new one.
====================================================================== */

static int add_image( HWND hwnd, Image *image )
{
   int i;

   i = frameNextAvailable();
   if ( !frameIsEmpty( i ))
      frameClear( i );

   if ( !frameInit( image, &config, i ))
      return 0;
   current = i;
   frameSyncUI( hwnd, i, TRUE );
   return 1;
}


/*
======================================================================
remove_current()

Delete the current image.  Returns FALSE if there are no other images
to display, otherwise TRUE.
====================================================================== */

static int remove_current( HWND hwnd )
{
   int i;

   frameClear( current );
   i = frameNewest();
   if ( frameIsEmpty( i ))
      return 0;
   current = i;
   frameSyncUI( hwnd, i, TRUE );
   return 1;
}


/*
======================================================================
set_current()

Set the currently displayed image to the i-th image.
====================================================================== */

static void set_current( HWND hwnd, int i )
{
   if ( frameIsEmpty( i ))
      return;
   current = i;
   frameSyncUI( hwnd, current, TRUE );
}


/*
======================================================================
set_next()

Set the currently displayed image to the image at the next higher
index, wrapping to 0 until a non-empty image slot is found.  Has no
effect if there's only one image.
====================================================================== */

static void set_next( HWND hwnd )
{
   int i;

   for ( i = 1; i < 10; i++ )
      if ( !frameIsEmpty(( current + i ) % 10 )) {
         set_current( hwnd, ( current + i ) % 10 );
         break;
      }
}


/*
======================================================================
set_prev()

Set the currently displayed image to the image at the next lower
index, wrapping to the highest index until a non-empty image slot is
found.  Has no effect if there's only one image.
====================================================================== */

static void set_prev( HWND hwnd )
{
   int i;

   for ( i = 9; i > 0; i-- )
      if ( !frameIsEmpty(( current + i ) % 10 )) {
         set_current( hwnd, ( current + i ) % 10 );
         break;
      }
}


/*
======================================================================
toggle_channel()

Turns the specified channel display on or off.
====================================================================== */

static void toggle_channel( HWND hwnd, int channel )
{
   ImageInfo *im = frameGetInfo();
   if ( im->channel == channel )
      im->channel = CH_IMAGE;
   else
      im->channel = channel;
   frameSyncUI( hwnd, current, TRUE );
}


/*
======================================================================
toggle_hot()

Turns the hot pixel overlay on or off.
====================================================================== */

static void toggle_hot( HWND hwnd )
{
   ImageInfo *im = frameGetInfo();
   if ( im->mask & MASK_HOT )
      im->mask &= ~MASK_HOT;
   else
      im->mask |= MASK_HOT;
   frameSyncUI( hwnd, current, TRUE );
}


/*
======================================================================
toggle_safe()

Turns the safe outline overlay on or off.
====================================================================== */

static void toggle_safe( HWND hwnd )
{
   ImageInfo *im = frameGetInfo();
   if ( im->mask & MASK_SAFE )
      im->mask &= ~MASK_SAFE;
   else
      im->mask |= MASK_SAFE;
   frameSyncUI( hwnd, current, TRUE );
}


/*
======================================================================
imvCleanup()

Free resources allocated by the image viewer.

Should be called only once per program session, typically when the
program is preparing to quit.  This is also a convenient place to
save the config file.
====================================================================== */

void imvCleanup( void )
{
   printCleanup();
   saveQVConfigFile( &config );
   DeleteObject( hiconlg );
   DeleteObject( hiconsm );
}


/*
======================================================================
imvInit()

Initialize the viewer interface.

INPUTS
   hInstance      the DLL's instance handle

Should be called only once per program session, to register the
window classes used by the image viewer.
====================================================================== */

void imvInit( HINSTANCE hInstance )
{
   WNDCLASSEX wc;

   hinst = hInstance;
   hiconlg = LoadImage( hinst, "lgicon", IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR );
   hiconsm = LoadImage( hinst, "smicon", IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR );

   wc.cbSize        = sizeof( wc );
   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = hinst;

   wc.lpszClassName = wndclass[ 0 ];
   wc.lpfnWndProc   = imvWndProc;
   wc.hIcon         = hiconlg;
   wc.hIconSm       = hiconsm;
   wc.hCursor       = LoadCursor( NULL, IDC_ARROW );
   wc.hbrBackground = ( HBRUSH )( COLOR_BTNFACE + 1 );
   wc.lpszMenuName  = NULL;

   RegisterClassEx( &wc );

   wc.lpszClassName = wndclass[ 1 ];
   wc.lpfnWndProc   = imvDrawWndProc;
   wc.hIcon         = NULL;
   wc.hIconSm       = NULL;
   wc.hCursor       = LoadCursor( NULL, IDC_CROSS );
   wc.hbrBackground = ( HBRUSH )( COLOR_BTNSHADOW + 1 );
   wc.lpszMenuName  = NULL;

   RegisterClassEx( &wc );

   InitCommonControls();
}


/*
======================================================================
imvInitLW()

Initialize the LightWave-specific aspects of the viewer interface.

Should be called only once per LightWave session, from the plug-in
activation function or the Create() callback, to initialize components
that call LW global functions and to load QV's default settings.
====================================================================== */

int imvInitLW( GlobalFunc *global )
{
   if ( !initFileReq(  global )) return FALSE;
   if ( !initSave(     global )) return FALSE;
   if ( !initConfigIO( global )) return FALSE;
   loadQVConfigFile( hinst, &config );
   setPixelDisplayType( config.pixelDisplayType );
   return TRUE;
}


/*
======================================================================
imvGetConfig()

Return a pointer to the structure containing QV's settings.
====================================================================== */

QVConfig *imvGetConfig( void )
{
   return &config;
}


/*
======================================================================
imvCreateWindow()

Open an image viewer main window.
====================================================================== */

HWND imvCreateWindow( HWND hwndParent, Image *image )
{
   HWND hwnd;

   current = 0;
   if ( !frameInit( image, &config, 0 ))
      return 0;
   frameSyncInfo( 0 );

   hwnd = CreateWindowEx(
      WS_EX_APPWINDOW,
      wndclass[ 0 ],
      "Render Display - QV 4.0",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      hwndParent,  // NULL,
      NULL,
      hinst,
      NULL
   );

   if ( hwnd ) {
      frameSyncUI( hwnd, 0, FALSE );
      ShowWindow( hwnd, SW_SHOWNORMAL );
      UpdateWindow( hwnd );
   }

   return hwnd;
}


/*
======================================================================
set_cursor()

Change the mouse cursor image for a window.  This is public so that
the draw window can set the cursor for drag mode.
====================================================================== */

void set_cursor( HWND hwnd, const char *shape )
{
   HCURSOR hcursor;
   POINT pt;
   RECT r;

   hcursor = LoadCursor( NULL, shape );
   SetClassLongPtr( hwnd, GCLP_HCURSOR, ( LONG_PTR ) hcursor );
   GetCursorPos( &pt );
   ScreenToClient( hwnd, &pt );
   GetClientRect( hwnd, &r );
   if ( pt.x >= 0 && pt.x < r.right && pt.y >= 0 && pt.y < r.bottom )
      SetCursor( hcursor );
}


/*
======================================================================
imvBusyCursor()

Sets the mouse cursor for both the main and draw windows to IDC_WAIT,
if busy, or to their defaults otherwise.
====================================================================== */

void imvBusyCursor( HWND hwnd, int busy )
{
   if ( busy ) {
      set_cursor( hwnd, IDC_WAIT );
      set_cursor( GetDlgItem( hwnd, IDW_DRAWWND ), IDC_WAIT );
   }
   else {
      set_cursor( hwnd, IDC_ARROW );
      set_cursor( GetDlgItem( hwnd, IDW_DRAWWND ), IDC_CROSS );
   }
}


/*
======================================================================
addtext_on()

Turn on add text mode (the user has hit the Text button).
====================================================================== */

static void addtext_on( HWND hwnd )
{
   ImageInfo *im = frameGetInfo();
   im->addingtext = TRUE;
   set_cursor( GetDlgItem( hwnd, IDW_DRAWWND ), IDC_IBEAM );
   toolbarSetText( hwnd, TRUE );
}


/*
======================================================================
addText()

Turn off add text mode.  If draw is TRUE, the add text dialog is
displayed, and if this returns OK, the text is drawn at x, y.
====================================================================== */

void addText( HWND hwnd, int x, int y, int draw )
{
   ImageInfo *im = frameGetInfo();

   im->addingtext = FALSE;
   toolbarSetText( hwnd, FALSE );

   if ( draw ) {
      config.text.x = x;
      config.text.y = y;
      if ( textDialog( hwnd, &config.text )) {
         compositeText( &config.text, im->disp );
         frameSyncUI( hwnd, current, TRUE );
      }
   }
   set_cursor( GetDlgItem( hwnd, IDW_DRAWWND ), IDC_CROSS );
}


/*
======================================================================
handle_command()

Respond to WM_COMMAND messages (or keyboard equivalents).
====================================================================== */

static int handle_command( HWND hwnd, WPARAM wparam, LPARAM lparam )
{
   ImageInfo *im = frameGetInfo();
   int id = LOWORD( wparam );

   if ( id >= IDC_RADIO && id < IDC_RADIO + 10 ) {
      set_current( hwnd, id - IDC_RADIO );
      return 0;
   }

   switch ( id )
   {
      case IDC_SAVE:
         saveImage( hwnd );
         return 0;

      case IDC_COPY:
         copyImage( hwnd );
         break;

      case IDC_PRINT:
         printImage( hwnd );
         break;

      case IDC_DELETE:
         if ( !remove_current( hwnd ))
            SendMessage( hwnd, WM_CLOSE, 0, 0 );
         return 0;

      case IDC_FIT:
         if ( IsZoomed( hwnd ))
            ShowWindow( hwnd, SW_RESTORE );
         imvFitWindow( GetDlgItem( hwnd, IDW_DRAWWND ));
         return 0;

      case IDC_IMAGE:
      case IDC_ALPHA:
      case IDC_RED:
      case IDC_GREEN:
      case IDC_BLUE:
         toggle_channel( hwnd, id - IDC_IMAGE );
         return 0;

      case IDC_HOT:
         toggle_hot( hwnd );
         return 0;

      case IDC_SAFE:
         toggle_safe( hwnd );
         return 0;

      case IDC_TEXT:
         if ( im->addingtext )
            addText( hwnd, 0, 0, FALSE );
         else
            addtext_on( hwnd );
         return 0;

      case IDC_UNDO:
         undoText( im->disp );
         frameSyncUI( hwnd, current, TRUE );
         return 0;

      case IDC_NOTES:
         propertiesDialog( hwnd, &config, frameGetDisplayable( current ));
         frameSyncUI( hwnd, current, TRUE );
         return 0;

      default:
         break;
   }
   return 0;
}


/*
======================================================================
handle_key()

Respond to WM_CHAR messages.  In some cases this is done by calling
handle_command() with simulated WM_COMMAND parameters.
====================================================================== */

static int handle_key( HWND hwnd, WPARAM key )
{
   WPARAM id = 0;

   if ( key >= '0' && key <= '9' ) {
      id = IDC_RADIO + ( key - '0' + 9 ) % 10;
      return handle_command( hwnd, id, ( LPARAM ) GetDlgItem( hwnd, id ));
   }

   switch ( key ) {
      case 'j':  id = IDC_FIT;    break;
      case 'n':  id = IDC_NOTES;  break;
      case 's':  id = IDC_SAVE;   break;
      case 'c':  id = IDC_COPY;   break;
      case 'p':  id = IDC_PRINT;  break;
      case 'd':  id = IDC_DELETE; break;
      case 'i':  id = IDC_IMAGE;  break;
      case 'r':  id = IDC_RED;    break;
      case 'g':  id = IDC_GREEN;  break;
      case 'b':  id = IDC_BLUE;   break;
      case 'a':  id = IDC_ALPHA;  break;
      case 'h':  id = IDC_HOT;    break;
      case 'f':  id = IDC_SAFE;   break;
      case 't':  id = IDC_TEXT;   break;
      case 'u':  id = IDC_UNDO;   break;
      default:  break;
   }
   if ( id )
      return handle_command( hwnd, id, ( LPARAM ) GetDlgItem( hwnd, id ));

   switch ( key ) {
      case '+':  set_next( hwnd );  return 0;
      case '-':  set_prev( hwnd );  return 0;
      case 'q':
         SendMessage( hwnd, WM_CLOSE, 0, 0 );
         return 0;
      case '.':
         imvUpdateZoom( GetDlgItem( hwnd, IDW_DRAWWND ), NULL, WM_LBUTTONDOWN );
         return 0;
      case ',':
         imvUpdateZoom( GetDlgItem( hwnd, IDW_DRAWWND ), NULL, WM_RBUTTONDOWN );
         return 0;
      default:
         return 0;
   }
}


/*
======================================================================
handle_vkey()

Respond to WM_KEYDOWN messages.  These are generally for non-ANSI
keystrokes like the cursor keys.  For those, we send the draw window
simulated scrollbar messages.
====================================================================== */

static int handle_vkey( HWND hwnd, WPARAM key )
{
   HWND hframe;
   int smsg, swparam = -1;
   SHORT shift;
   ImageInfo *im = frameGetInfo();

   if ( key == VK_ESCAPE ) {
      if ( im->addingtext )
         addText( hwnd, 0, 0, FALSE );
      else
         SendMessage( hwnd, WM_CLOSE, 0, 0 );
      return 0;
   }

   if ( key == VK_DELETE )
      return handle_command( hwnd, IDC_DELETE,
         ( LPARAM ) GetDlgItem( hwnd, IDC_DELETE ));

   hframe = GetDlgItem( hwnd, IDW_DRAWWND );
   shift = GetKeyState( VK_SHIFT );

   if ( shift < 0 ) switch ( key ) {
      case VK_LEFT:  smsg = WM_HSCROLL;  swparam = SB_PAGEUP;    break;
      case VK_RIGHT: smsg = WM_HSCROLL;  swparam = SB_PAGEDOWN;  break;
      case VK_UP:    smsg = WM_VSCROLL;  swparam = SB_PAGEUP;    break;
      case VK_DOWN:  smsg = WM_VSCROLL;  swparam = SB_PAGEDOWN;  break;
      default:  return 0;
   }
   else switch ( key ) {
      case VK_LEFT:  smsg = WM_HSCROLL;  swparam = SB_LINEUP;    break;
      case VK_RIGHT: smsg = WM_HSCROLL;  swparam = SB_LINEDOWN;  break;
      case VK_HOME:  smsg = WM_HSCROLL;  swparam = SB_TOP;       break;
      case VK_END:   smsg = WM_HSCROLL;  swparam = SB_BOTTOM;    break;
      case VK_UP:    smsg = WM_VSCROLL;  swparam = SB_LINEUP;    break;
      case VK_DOWN:  smsg = WM_VSCROLL;  swparam = SB_LINEDOWN;  break;
      case VK_PRIOR: smsg = WM_VSCROLL;  swparam = SB_TOP;       break;
      case VK_NEXT:  smsg = WM_VSCROLL;  swparam = SB_BOTTOM;    break;
      default:  return 0;
   }

   if ( swparam != -1 )
      SendMessage( hframe, smsg, MAKELONG( swparam, 0 ), 0 );

   return 1;
}


/*
======================================================================
handle_notify()

Handle WM_NOTIFY messages sent by the toolbar's tooltips control.
====================================================================== */

static int handle_notify( HWND hwnd, WPARAM wparam, LPARAM lparam )
{
   return toolbarTipText( lparam );
}


/*
======================================================================
create_children()

Create child windows (the draw window, toolbar, radio buttons, status
bar).
====================================================================== */

static void create_children( HWND hwnd )
{
   toolbarCreate( hwnd );
   statusCreate( hwnd );
   radioCreate( hwnd );

   /* a borderless child window to contain the image; the actual size
      will be set in wmsize_children when the parent gets a WM_SIZE */

   CreateWindowEx( 0, wndclass[ 1 ], NULL,
      WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL,
      DRAWWND_L, DRAWWND_T, 100, 100,
      hwnd, ( HMENU ) IDW_DRAWWND, hinst, frameGetInfo() );
}


/*
======================================================================
wmsize_children()

Reposition/resize child windows when the main window is resized.
====================================================================== */

static void wmsize_children( HWND hwnd, WPARAM wparam, LPARAM lparam )
{
   int th, sh, rh;

   th = toolbarResize( hwnd );
   sh = statusResize( LOWORD( lparam ), HIWORD( lparam ));
   rh = radioResize( hwnd, LOWORD( lparam ), HIWORD( lparam ), sh );

   /* resize the draw window */

   MoveWindow( GetDlgItem( hwnd, IDW_DRAWWND ),
      DRAWWND_L,
      DRAWWND_T + th,
      LOWORD( lparam ) - ( DRAWWND_L + DRAWWND_R ),
      HIWORD( lparam ) - ( DRAWWND_T + DRAWWND_B + th + sh + rh ),
      TRUE );
}


/*
======================================================================
imvWndProc()

The window procedure for the main window.
====================================================================== */

LRESULT CALLBACK
imvWndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
{
   static time_t showtime;
   PAINTSTRUCT ps;
   MINMAXINFO *mmi;
   WINDOWPOS *wp;
   Image *image;
   static FILE *fp = NULL;

   switch ( message )
   {
      case WM_CREATE:
         create_children( hwnd );
         imvFitWindow( GetDlgItem( hwnd, IDW_DRAWWND ));
         /*
         fp = fopen( "qv.log", "w" );
         if ( fp )
            fprintf( fp, "%0.8x\n", hwnd );
         */
         return 0;

      case WM_GETMINMAXINFO:
         mmi = ( MINMAXINFO * ) lparam;
         mmi->ptMinTrackSize.x = MIN_MAINWND_X;
         mmi->ptMinTrackSize.y = MIN_MAINWND_Y;
         return 0;

      case WM_SIZE:
         wmsize_children( hwnd, wparam, lparam );
         return 0;

      case WM_WINDOWPOSCHANGING:
         wp = ( WINDOWPOS * ) lparam;
         if ( wp->flags & SWP_SHOWWINDOW )
            showtime = time( NULL );
         else if ( wp->flags == ( SWP_NOSIZE & SWP_NOMOVE )) {
            if ( time( NULL ) < showtime + 4 ) {
               wp->flags |= SWP_NOZORDER;
               return 0;
            }
         }
         /*
         if ( fp ) {
            fprintf( fp, "%0.8x %0.8x %4d %4d %4d %4d %0.8x\n",
               wp->hwnd, wp->hwndInsertAfter, wp->x, wp->y, wp->cx, wp->cy,
               wp->flags );
         } */
         break;

      case UM_ADDIMAGE:
         image = ( Image * ) lparam;
         if ( !add_image( hwnd, image )) {
            MessageBox( hwnd, "The new image couldn't be added.", "QV",
               MB_ICONERROR | MB_OK );
            SendMessage( hwnd, WM_CLOSE, 0, 0 );
            return 0;
         }
         if ( IsIconic( hwnd ))
            ShowWindow( hwnd, SW_RESTORE );
         return 0;

      case WM_PAINT:
         BeginPaint( hwnd, &ps );
         EndPaint( hwnd, &ps );
         return 0;

      case WM_COMMAND:
         SetFocus( hwnd );
         handle_command( hwnd, wparam, lparam );
         return 0;

      case WM_KEYDOWN:
         handle_vkey( hwnd, wparam );
         return 0;

      case WM_CHAR:
         handle_key( hwnd, wparam );
         return 0;

      case WM_NOTIFY:
         if ( handle_notify( hwnd, wparam, lparam ))
            return 0;
         break;

      case WM_DESTROY:
//         if ( fp ) fclose( fp );
         statusFree();
         frameClearAll();
         PostQuitMessage( 0 );
         return 0;

      case WM_CLOSE:
         break;

      default:
         break;
   }

   return DefWindowProc( hwnd, message, wparam, lparam );
}
