/*
======================================================================
notes.c

Ernie Wright  19 May 05
MSVC 4.0

Display the Notes and Settings property sheet.
====================================================================== */

#include "qv.h"
#include <prsht.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>


typedef struct st_QVProperties {
   char           title[ 256 ];
   char           notes[ 1024 ];
   time_t         created;
   int            rendertime;
   int            index;
   int            framenum;
   LWSceneInfo    scene;
   float          black;
   float          white;
   int            fpixel;
   float          pedestal;
   float          limComposite;
   float          limChroma;
   int            encoding;
   int            btypes;
   int            bstyle;
   float          aspect;
   float          margin;
   int            changed;
   int            changed_all;
   int            leveldef, levelall;
   int            hotdef, hotall;
   int            safedef, safeall;
} QVProperties;

static QVProperties prop;
static int activePage;


/*
======================================================================
set_float(), get_float(), set_percent(), get_percent()

Dialog box procedures use these to get and set edit controls for
floating-point values.
====================================================================== */

static void set_float( HWND hwnd, int id, float x )
{
   char buf[ 24 ], *p, *q;

   sprintf( buf, "%.6f", x );
   if ( p = strrchr( buf, '.' )) {
      q = buf + strlen( buf ) - 1;
      while ( q > p + 1 && *q == '0' )
         *q-- = 0;
   }
   SetDlgItemText( hwnd, id, buf );
}


static float get_float( HWND hwnd, int id )
{
   char buf[ 24 ];

   GetDlgItemText( hwnd, id, buf, sizeof( buf ));
   return ( float ) atof( buf );
}


static void set_percent( HWND hwnd, int id, float x )
{
   char buf[ 24 ];

   sprintf( buf, "%g%%", x * 100.0f );
   SetDlgItemText( hwnd, id, buf );
}


static float get_percent( HWND hwnd, int id )
{
   char buf[ 24 ];

   GetDlgItemText( hwnd, id, buf, sizeof( buf ));
   return ( float ) atof( buf ) / 100.0f;
}


/*
======================================================================
floatChanged()

Check whether a new float value is sufficiently different from an old
value to be considered a change in value.  The threshold is the power
of 2 such that, if the magnitudes of the two values differ by more
than 2^-threshold, floatChanged() returns TRUE.  A threshold of 10,
for example, tests whether the values differ by more than 1 part in
1024.
====================================================================== */

int floatChanged( float old, float new, int threshold )
{
   float d;
   int ed, eo;

   d = new - old;
   if ( d == 0.0f ) return 0;
   frexp( d, &ed );
   frexp( old, &eo );
   return ed > eo - threshold;
}


/*
======================================================================
init_prop()

Copy settings in the Displayable and the QVConfig to a local structure
used by the dialog procedures to initialize controls.
====================================================================== */

static void init_prop( Displayable *disp, QVConfig *config )
{
   prop.changed = prop.changed_all = 0;
   prop.leveldef = prop.hotdef = prop.safedef = 0;
   prop.levelall = prop.hotall = prop.safeall = 0;

   strcpy( prop.title, disp->title );
   strcpy( prop.notes, disp->notes );
   prop.created = disp->image->created;
   prop.rendertime = disp->image->rendertime;
   prop.index = disp->image->index;
   prop.framenum = disp->image->framenum;
   prop.scene = disp->image->scene;

   prop.black  = disp->image->black;
   prop.white  = disp->image->white;
   prop.fpixel = config->pixelDisplayType;

   prop.pedestal     = disp->hotInfo.pedestal;
   prop.limComposite = disp->hotInfo.limComposite;
   prop.limChroma    = disp->hotInfo.limChroma;
   prop.encoding     = disp->hotInfo.encoding;

   prop.btypes = disp->safeInfo.types;
   prop.bstyle = disp->safeInfo.style;
   prop.aspect = disp->safeInfo.aspect;
   prop.margin = disp->safeInfo.margin;
}


/*
======================================================================
get_prop()

Copy settings from the local structure to the Displayable and the
QVConfig.  A change bit is set for each group of Displayable settings
that will necessitate an update to one of its DIBs.

Settings are copied to the QVConfig if and only if the Default box
for the settings is checked.  The settings don't have to change for
this copying to occur.  This allows the user to tell QV that he wants
the current Displayable's settings to be used as the default.

The Default boxes are initially unchecked on each invocation of the
property sheet.  The user must explicitly request the copying of the
settings to the QVConfig every time.  This prevents the user from
inadvertently changing the defaults on pages he doesn't visit.  If we
didn't do it this way, the defaults could change every time the user
invoked the property sheet for a different Displayable, without the
user even seeing the state of the Default checkbox on pages he didn't
visit.

A change bit is set for each Apply To Existing box that's checked, and
again, this is true regardless of whether the corresponding settings
have changed.  The unchanged settings might differ from those of the
other images, and the user may want those unchanged settings applied
to them.  But as with the Defaults, the Apply checkboxes are always
clear initially, and for the same reason.
====================================================================== */

static void get_prop( Displayable *disp, QVConfig *config )
{
   config->pixelDisplayType = prop.fpixel;
   strcpy( disp->title, prop.title );
   strcpy( disp->notes, prop.notes );

   if ( floatChanged( disp->image->black, prop.black, 10 ) ||
        floatChanged( disp->image->white, prop.white, 10 )) {
      prop.changed |= CHANGED_LEVELS;
      disp->image->black = prop.black;
      disp->image->white = prop.white;
   }
   if ( prop.leveldef ) {
      config->black = prop.black;
      config->white = prop.white;
   }
   if ( prop.levelall )
      prop.changed_all |= CHANGED_LEVELS;

   if ( floatChanged( disp->hotInfo.pedestal, prop.pedestal, 10 ) ||
        floatChanged( disp->hotInfo.limComposite, prop.limComposite, 10 ) ||
        floatChanged( disp->hotInfo.limChroma, prop.limChroma, 10 ) ||
        disp->hotInfo.encoding != prop.encoding ) {
      prop.changed |= CHANGED_HOT;
      disp->hotInfo.pedestal = prop.pedestal;
      disp->hotInfo.limComposite = prop.limComposite;
      disp->hotInfo.limChroma = prop.limChroma;
      disp->hotInfo.encoding = prop.encoding;
   }
   if ( prop.hotdef )
      config->hot = disp->hotInfo;
   if ( prop.hotall )
      prop.changed_all |= CHANGED_HOT;

   if ( floatChanged( disp->safeInfo.aspect, prop.aspect, 10 ) ||
        floatChanged( disp->safeInfo.margin, prop.margin, 10 ) ||
        disp->safeInfo.types != prop.btypes ||
        disp->safeInfo.style != prop.bstyle ) {
      prop.changed |= CHANGED_SAFE;
      disp->safeInfo.aspect = prop.aspect;
      disp->safeInfo.margin = prop.margin;
      disp->safeInfo.types = prop.btypes;
      disp->safeInfo.style = prop.bstyle;
   }
   if ( prop.safedef )
      config->safe = disp->safeInfo;
   if ( prop.safeall )
      prop.changed_all |= CHANGED_SAFE;
}


/*
======================================================================
init_notes(), get_notes(), NotesDlgProc()

Functions for the Notes property page.
====================================================================== */

static void init_notes( HWND hwnd )
{
   char *a, buf[ 32 ] = { 0 };

   SetDlgItemText( hwnd, NIDC_NAME, prop.title );
   SetDlgItemInt(  hwnd, NIDC_NUMBER, prop.index, TRUE );
   SetDlgItemText( hwnd, NIDC_NOTES, prop.notes );

   a = asctime( localtime( &prop.created ));
   strncpy( buf, a + 4, 15 );
   SetDlgItemText( hwnd, NIDC_CREATED, buf );

   sprintf( buf, "%dh %dm %d.%ds",
      ( prop.rendertime / 36000 ),
      ( prop.rendertime / 600 ) % 60,
      ( prop.rendertime / 10 ) % 60,
      prop.rendertime % 10 );
   SetDlgItemText( hwnd, NIDC_RENDTIME, buf );
}


static void get_notes( HWND hwnd )
{
   GetDlgItemText( hwnd, NIDC_NAME, prop.title, sizeof( prop.title ));
   GetDlgItemText( hwnd, NIDC_NOTES, prop.notes, sizeof( prop.notes ));
}


static void add_scene( HWND hwnd )
{
   static char notes[ 1024 ];
   static char buf[ 1024 ];
   char line[ 80 ], *on = "On", *off = "Off";

   sprintf( line, "Frame Number:  %d\r\n", prop.framenum );
   strcpy( buf, line );

   sprintf( line, "Points:  %d\r\n", prop.scene.numPoints );
   strcat( buf, line );

   sprintf( line, "Polygons:  %d\r\n", prop.scene.numPolygons );
   strcat( buf, line );

   sprintf( line, "AA Passes:  %d\r\n", prop.scene.maxSamplesPerPixel );
   strcat( buf, line );

   sprintf( line, "Raytrace Shadows:  %s\r\n",
      prop.scene.renderOpts & LWROPT_SHADOWTRACE ? on : off );
   strcat( buf, line );

   sprintf( line, "Raytrace Reflections:  %s\r\n",
      prop.scene.renderOpts & LWROPT_REFLECTTRACE ? on : off );
   strcat( buf, line );

   sprintf( line, "Raytrace Refraction:  %s\r\n",
      prop.scene.renderOpts & LWROPT_REFRACTTRACE ? on : off );
   strcat( buf, line );

   sprintf( line, "Recursion Depth:  %d\r\n", prop.scene.recursionDepth );
   strcat( buf, line );

   sprintf( line, "Field Rendering:  %s\r\n",
      prop.scene.renderOpts & LWROPT_FIELDS ? on : off );
   strcat( buf, line );

   sprintf( line, "Motion Blur:  %s\r\n",
      prop.scene.renderOpts & LWROPT_MOTIONBLUR ? on : off );
   strcat( buf, line );

   sprintf( line, "Depth of Field:  %s\r\n",
      prop.scene.renderOpts & LWROPT_DEPTHOFFIELD ? on : off );
   strcat( buf, line );

   GetDlgItemText( hwnd, NIDC_NOTES, notes, sizeof( notes ));
   if ( notes[ 0 ] )
      strcat( notes, "\r\n" );
   strcat( notes, buf );
   SetDlgItemText( hwnd, NIDC_NOTES, notes );
}


static INT_PTR  WINAPI
NotesDlgProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
   switch ( msg )
   {
      case WM_INITDIALOG:
         init_notes( hwnd );
         return TRUE;

      case WM_COMMAND:
         if ( LOWORD( wparam ) == NIDC_SCNINFO )
            add_scene( hwnd );
         break;

      case WM_NOTIFY:
         switch ((( LPNMHDR ) lparam )->code ) {
            case PSN_APPLY:
               get_notes( hwnd );
               return TRUE;
            case PSN_SETACTIVE:
               activePage = 0;
               return FALSE;
            default:
               break;
         }
         break;

      default:
         break;
   }

   return FALSE;
}


/*
======================================================================
init_levels(), get_levels(), LevelsDlgProc()

Functions for the Levels property page.
====================================================================== */

static void init_levels( HWND hwnd )
{
   CheckDlgButton( hwnd, NIDC_FPIXEL, prop.fpixel );
   set_float( hwnd, NIDC_BLACK, prop.black );
   set_float( hwnd, NIDC_WHITE, prop.white );
   CheckDlgButton( hwnd, NIDC_LEVELDEF, prop.leveldef );
   CheckDlgButton( hwnd, NIDC_LEVELALL, prop.levelall );
}


static void get_levels( HWND hwnd )
{
   prop.fpixel = IsDlgButtonChecked( hwnd, NIDC_FPIXEL );
   prop.black = get_float( hwnd, NIDC_BLACK );
   prop.white = get_float( hwnd, NIDC_WHITE );
   prop.leveldef = IsDlgButtonChecked( hwnd, NIDC_LEVELDEF );
   prop.levelall = IsDlgButtonChecked( hwnd, NIDC_LEVELALL );
}


static INT_PTR  WINAPI
LevelsDlgProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
   switch ( msg )
   {
      case WM_INITDIALOG:
         init_levels( hwnd );
         return TRUE;

      case WM_NOTIFY:
         switch ((( LPNMHDR ) lparam )->code ) {
            case PSN_APPLY:
               get_levels( hwnd );
               return TRUE;
            case PSN_SETACTIVE:
               activePage = 1;
               return FALSE;
            default:
               break;
         }
         break;

      default:
         break;
   }

   return FALSE;
}


/*
======================================================================
init_hots(), get_hots(), HotDlgProc()

Functions for the Hot Pixels property page.
====================================================================== */

static void init_hots( HWND hwnd )
{
   set_float( hwnd, NIDC_PEDESTAL, prop.pedestal );
   set_float( hwnd, NIDC_COMLIMIT, prop.limComposite );
   set_float( hwnd, NIDC_CHRLIMIT, prop.limChroma );
   CheckRadioButton( hwnd, NIDC_NTSC, NIDC_PAL,
      NIDC_NTSC + prop.encoding );
   CheckDlgButton( hwnd, NIDC_HOTDEF, prop.hotdef );
   CheckDlgButton( hwnd, NIDC_HOTALL, prop.hotall );
}


static void get_hots( HWND hwnd )
{
   prop.pedestal = get_float( hwnd, NIDC_PEDESTAL );
   prop.limComposite = get_float( hwnd, NIDC_COMLIMIT );
   prop.limChroma = get_float( hwnd, NIDC_CHRLIMIT );
   prop.encoding = IsDlgButtonChecked( hwnd, NIDC_NTSC ) ?
      HOT_NTSC : HOT_PAL;
   prop.hotdef = IsDlgButtonChecked( hwnd, NIDC_HOTDEF );
   prop.hotall = IsDlgButtonChecked( hwnd, NIDC_HOTALL );
}


static INT_PTR  WINAPI
HotDlgProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
   switch ( msg )
   {
      case WM_INITDIALOG:
         init_hots( hwnd );
         return TRUE;

      case WM_NOTIFY:
         switch ((( LPNMHDR ) lparam )->code ) {
            case PSN_APPLY:
               get_hots( hwnd );
               return TRUE;
            case PSN_SETACTIVE:
               activePage = 2;
               return FALSE;
            default:
               break;
         }
         break;

      default:
         break;
   }

   return FALSE;
}


/*
======================================================================
init_safe(), get_safe(), SafeDlgProc()

Functions for the Borders property page.
====================================================================== */

static void init_safe( HWND hwnd )
{
   CheckDlgButton( hwnd, NIDC_VIDEO, prop.btypes & SAFE_VIDEO );
   CheckDlgButton( hwnd, NIDC_BOX, prop.btypes & SAFE_BOX );
   set_float( hwnd, NIDC_ASPECT, prop.aspect );
   set_percent( hwnd, NIDC_MARGIN, prop.margin );
   CheckRadioButton( hwnd, NIDC_OUTLINE, NIDC_SOLID,
      NIDC_OUTLINE + prop.bstyle );
   CheckDlgButton( hwnd, NIDC_SAFEDEF, prop.safedef );
   CheckDlgButton( hwnd, NIDC_SAFEALL, prop.safeall );
}


static void get_safe( HWND hwnd )
{
   prop.btypes = 0;
   if ( IsDlgButtonChecked( hwnd, NIDC_VIDEO ))
      prop.btypes |= SAFE_VIDEO;
   if ( IsDlgButtonChecked( hwnd, NIDC_BOX ))
      prop.btypes |= SAFE_BOX;
   prop.aspect = get_float( hwnd, NIDC_ASPECT );
   prop.margin = get_percent( hwnd, NIDC_MARGIN );
   prop.bstyle = IsDlgButtonChecked( hwnd, NIDC_OUTLINE ) ?
      SAFE_OUTLINE : SAFE_SOLID;
   prop.safedef = IsDlgButtonChecked( hwnd, NIDC_SAFEDEF );
   prop.safeall = IsDlgButtonChecked( hwnd, NIDC_SAFEALL );
}


static INT_PTR  WINAPI
SafeDlgProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
   switch ( msg )
   {
      case WM_INITDIALOG:
         init_safe( hwnd );
         return TRUE;

      case WM_NOTIFY:
         switch ((( LPNMHDR ) lparam )->code ) {
            case PSN_APPLY:
               get_safe( hwnd );
               return TRUE;
            case PSN_SETACTIVE:
               activePage = 3;
               return FALSE;
            default:
               break;
         }
         break;

      default:
         break;
   }

   return FALSE;
}


/*
======================================================================
RemoveContextHelp()

Property sheet callback that removes the Help button from the property
sheet's titlebar.  The only way to do this is to turn off a bit within
the DLGTEMPLATE used by the property sheet.  Newer versions of Windows
use a DLGTEMPLATEEX, which isn't defined in my old headers.  Rather
than typedef the thing here, and deal with packing issues, I've just
hacked the lparam with the constant offsets to the fields I need.
====================================================================== */

static int CALLBACK RemoveContextHelp( HWND hwnd, UINT message, LPARAM lparam )
{
   switch (message) {
      case PSCB_PRECREATE:                                     // DGLTEMPLATEEX:
         if ( *((( WORD * ) lparam ) + 1 ) == 0xFFFF )         // .signature
            *((( DWORD * ) lparam ) + 3 ) &= ~DS_CONTEXTHELP;  // .style
         else
            (( LPDLGTEMPLATE ) lparam )->style &= ~DS_CONTEXTHELP;
         break;
      default:
         break;
   }
   return TRUE;
}


/*
======================================================================
CreatePropertySheet()

Create and display a property sheet with Notes, Levels, Hot Pixels,
and Borders pages.  The initial page is the one that was active when
the property sheet was last dismissed by pressing OK.
====================================================================== */

static INT_PTR CreatePropertySheet( HWND hwnd )
{
   extern HICON hiconsm;
   PROPSHEETHEADER psh;
   PROPSHEETPAGE psp[ 4 ];

   memset( &psh, 0, sizeof( PROPSHEETHEADER ));
   memset( psp, 0, 4 * sizeof( PROPSHEETPAGE ));

   psh.dwSize      = sizeof( PROPSHEETHEADER );
   psh.dwFlags     = PSH_NOAPPLYNOW | PSH_PROPSHEETPAGE | PSH_USECALLBACK |
                     PSH_USEHICON;
   psh.hwndParent  = hwnd;
   psh.hInstance   = ( HINSTANCE ) GetWindowLongPtr( hwnd, GWLP_HINSTANCE );
   psh.hIcon       = hiconsm;
   psh.pszCaption  = "Notes and Settings";
   psh.nPages      = 4;
   psh.nStartPage  = activePage;
   psh.ppsp        = psp;
   psh.pfnCallback = RemoveContextHelp;

   psp[ 0 ].dwSize      = sizeof( PROPSHEETPAGE );
   psp[ 0 ].hInstance   = psh.hInstance;
   psp[ 0 ].pszTemplate = "qvnotes";
   psp[ 0 ].pfnDlgProc  = NotesDlgProc;

   psp[ 1 ].dwSize      = sizeof( PROPSHEETPAGE );
   psp[ 1 ].hInstance   = psh.hInstance;
   psp[ 1 ].pszTemplate = "qvlevels";
   psp[ 1 ].pfnDlgProc  = LevelsDlgProc;

   psp[ 2 ].dwSize      = sizeof( PROPSHEETPAGE );
   psp[ 2 ].hInstance   = psh.hInstance;
   psp[ 2 ].pszTemplate = "qvhot";
   psp[ 2 ].pfnDlgProc  = HotDlgProc;

   psp[ 3 ].dwSize      = sizeof( PROPSHEETPAGE );
   psp[ 3 ].hInstance   = psh.hInstance;
   psp[ 3 ].pszTemplate = "qvsafe";
   psp[ 3 ].pfnDlgProc  = SafeDlgProc;

   return PropertySheet( &psh );
}


/*
======================================================================
propertiesDialog()

Let the user add notes, name the image, and change settings.  All
settings changes are applied to the current image.  The user can also
make them the default for future images, or apply them to existing
images.
====================================================================== */

int propertiesDialog( HWND hwnd, QVConfig *config, Displayable *disp )
{
	 INT_PTR result;

   init_prop( disp, config );
   result = CreatePropertySheet( hwnd );

   if ( result == TRUE ) {
      get_prop( disp, config );
      imvBusyCursor( hwnd, TRUE );
      if ( prop.changed_all )
         frameClone( disp, prop.changed_all );
      realizeDisplayableParts( disp, prop.changed );
      setPixelDisplayType( config->pixelDisplayType );
      imvBusyCursor( hwnd, FALSE );
   }
   return prop.changed;
}
