/*******************************************************************************
 * pvmisc.cpp
 *
 * This module implements miscellaneous routines for the Windows build of POV.
 *
 * Author: Christopher J. Cason.
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/windows/pvmisc.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

#define POVWIN_FILE
#define _WIN32_IE COMMONCTRL_VERSION

#include <windows.h>
#include <htmlhelp.h>
#include <shellapi.h>
#include <setjmp.h>
#include <string.h>
#include <direct.h>
#include <io.h>
#include <commctrl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <crtdbg.h>
#include <fstream>
#include <iostream>
// #include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>

#include "pvengine.h"
#include "pvedit.h"
#include "resource.h"
#include "pvguiext.h"
#include "povray.h"

#ifdef RTR_SUPPORT
  #include "rtrsupport.h"
#endif

// this must be the last file included
#include "syspovdebug.h"

namespace povwin
{

extern int                                  alert_sound ;
extern int                                  message_xchar ;
extern int                                  message_ychar ;
extern int                                  renderwin_left ;
extern int                                  renderwin_top ;
extern int                                  io_restrictions ;
extern int                                  bandWidths [8] ;
extern int                                  screen_origin_x ;
extern int                                  screen_origin_y ;
extern int                                  virtual_screen_width ;
extern int                                  virtual_screen_height ;
extern int                                  renderwin_transparency ;
extern char                                 message_font_name [256] ;
extern char                                 ourPath [_MAX_PATH] ;
extern char                                 helpPath [_MAX_PATH] ;
extern char                                 lastRenderName [_MAX_PATH] ;
extern char                                 lastBitmapName [_MAX_PATH] ;
extern char                                 lastRenderPath [_MAX_PATH] ;
extern char                                 lastBitmapPath [_MAX_PATH] ;
extern char                                 lastQueuePath [_MAX_PATH] ;
extern char                                 lastSecondaryIniFilePath [_MAX_PATH] ;
extern char                                 SecondaryRenderIniFileName [_MAX_PATH] ;
extern char                                 SecondaryRenderIniFileSection [64] ;
extern char                                 background_file [_MAX_PATH ] ;
extern char                                 tool_commands [MAX_TOOLCMD] [MAX_TOOLCMDTEXT] ;
extern char                                 tool_help [MAX_TOOLCMD] [MAX_TOOLHELPTEXT] ;
extern char                                 source_file_name [_MAX_PATH] ;
extern char                                 ToolIniFileName [_MAX_PATH] ;
extern char                                 command_line [_MAX_PATH * 3] ;
extern char                                 queued_files [MAX_QUEUE] [_MAX_PATH] ;
extern char                                 RegionStr [] ;
extern char                                 engineHelpPath [_MAX_PATH] ;
extern char                                 render_complete_sound [_MAX_PATH] ;
extern char                                 parse_error_sound [_MAX_PATH] ;
extern char                                 render_error_sound [_MAX_PATH] ;
extern char                                 FontPath [_MAX_PATH] ;
extern void                                 *CurrentEditor ;
extern bool                                 render_complete_sound_enabled ;
extern bool                                 parse_error_sound_enabled  ;
extern bool                                 render_error_sound_enabled ;
extern bool                                 keep_messages ;
extern bool                                 alert_on_completion ;
extern bool                                 save_settings ;
extern bool                                 running_demo ;
extern bool                                 fast_scroll ;
extern bool                                 MakeRenderwinActive ;
extern bool                                 renderwin_destroyed ;
extern bool                                 no_shellout_wait ;
extern bool                                 tile_background ;
extern bool                                 debugging ;
extern bool                                 no_palette_warn ;
extern bool                                 HideRenderWithMain ;
extern bool                                 RenderwinIsChild ;
extern bool                                 IsW95UserInterface ;
extern bool                                 use_16bit_editor ;
extern bool                                 system_noactive ;
extern bool                                 IsWin32 ;
extern bool                                 one_instance ;
extern bool                                 use_toolbar ;
extern bool                                 use_tooltips ;
extern bool                                 editors_enabled ;
extern bool                                 expert_menus ;
extern bool                                 drop_to_editor ;
extern bool                                 render_auto_close ;
extern bool                                 ExtensionsEnabled ;
extern bool                                 use_taskbar ;
extern bool                                 allow_rw_source ;
extern bool                                 no_shell_outs ;
extern bool                                 hide_newuser_help ;
extern bool                                 IsW98 ;
extern bool                                 IsW2k ;
extern bool                                 preserve_bitmap ;
extern bool                                 check_new_version ;
extern bool                                 check_news ;
extern bool                                 send_system_info ;
extern bool                                 homeInferred ;
extern bool                                 AutoAppendPaths ;
extern bool                                 PreventSleep;
extern unsigned                             message_font_size ;
extern unsigned                             message_font_weight ;
extern unsigned                             screen_width ;
extern unsigned                             screen_height ;
extern unsigned                             screen_depth ;
extern unsigned                             renderwin_8bits ;
extern unsigned                             auto_render ;
extern unsigned                             queued_file_count ;
extern unsigned                             renderwin_flags ;
extern unsigned                             render_priority ;
extern unsigned                             Duty_Cycle ;
extern unsigned                             on_completion ;
extern unsigned                             window_count ;
extern HWND                                 main_window ;
extern HWND                                 toolbar_window ;
extern HWND                                 toolbar_cmdline ;
extern HWND                                 toolbar_combobox ;
extern HWND                                 rebar_window ;
extern HMENU                                hMainMenu ;
extern HMENU                                hToolsMenu ;
extern COLORREF                             background_colour ;
extern COLORREF                             text_colours[3] ;
extern HH_AKLINK                            hh_aklink ;
extern WINDOWPLACEMENT                      mainwin_placement ;
extern CRITICAL_SECTION                     critical_section ;

#define MAX_DIRSPEC 70
char *WriteDirSpecs [MAX_DIRSPEC] ;
char *ReadDirSpecs [MAX_DIRSPEC] ;

typedef struct
{
  unsigned    id ;
  bool        *varptr ;
  bool        rval ;
  bool        autowrite ;
  char        *section ;
  char        *entry ;
  bool        defval ;
} toggle_struct ;

toggle_struct toggles [] =
{
// rval should be true if no special processing is required within handle_main_command
//  ID                       VarPtr                         RVal AutoWrite  Section        Name                       DefVal
  { CM_SAVE_SETTINGS,       (bool *) &save_settings,        false, true,  "General",       "SaveSettingsOnExit"      , true  },
  { CM_PRESERVEMESSAGES,    (bool *) &keep_messages,        true , true,  "Messages",      "KeepMessages"            , false },
  { CM_FORCE8BITS,          (bool *) &renderwin_8bits,      false, true,  "RenderWindow",  "Use8BitMode"             , false },
  { CM_RENDERACTIVE,        (bool *) &MakeRenderwinActive,  true , true,  "RenderWindow",  "MakeActive"              , true  },
  { CM_RENDERABOVEMAIN,     (bool *) &RenderwinIsChild,     false, true,  "RenderWindow",  "KeepAboveMain"           , true  },
  { CM_RENDERHIDE,          (bool *) &HideRenderWithMain,   true , true,  "RenderWindow",  "HideWhenMainMinimized"   , true  },
  { CM_ALERT,               (bool *) &alert_on_completion,  true , true,  "Renderer",      "AlertOnCompletion"       , true  },
  { CM_AUTORENDER,          (bool *) &auto_render,          true , true,  "Renderer",      "AutoRender"              , true  },
  { CM_PREVENTSLEEP,        (bool *) &PreventSleep,         true , true,  "Renderer",      "PreventSleep"            , true  },
  { CM_SHELLOUTWAIT,        (bool *) &no_shellout_wait,     true , true,  "Renderer",      "NoShelloutWait"          , false },
  { CM_TILEDBACKGROUND,     (bool *) &tile_background,      false, true,  "General",       "TileBackground"          , false },
  { CM_SYSTEMNOACTIVE,      (bool *) &system_noactive,      true,  true,  "Renderer",      "SystemNoActive"          , false },
  { CM_SINGLEINSTANCE,      (bool *) &one_instance,         false, false, "General",       "OneInstance"             , true  },
  { CM_USETOOLBAR,          (bool *) &use_toolbar,          false, true,  "MainWindow",    "UseToolbar"              , true  },
  { CM_USETOOLTIPS,         (bool *) &use_tooltips,         true,  true,  "MainWindow",    "UseTooltips"             , true  },
  { CM_RENDERAUTOCLOSE,     (bool *) &render_auto_close,    true,  true,  "RenderWindow",  "AutoClose"               , false },
  { CM_USEEXTENSIONS,       (bool *) &ExtensionsEnabled,    true,  true,  "GUIExtensions", "UseExtensions"           , true  },
  { CM_RW_SOURCE,           (bool *) &allow_rw_source,      true,  true,  "Scripting",     "ReadWriteSourceDir"      , true  },
  { CM_NO_SHELLOUTS,        (bool *) &no_shell_outs,        true,  true,  "Scripting",     "NoShellOuts"             , true  },
  { CM_HIDENEWUSERHELP,     (bool *) &hide_newuser_help,    false, true,  "General",       "HideNewUserHelp"         , false },
  { CM_PRESERVERENDERBITMAP,(bool *) &preserve_bitmap,      true,  true,  "RenderWindow",  "PreserveBitmap"          , true  },
  { CM_CHECKNEWVERSION,     (bool *) &check_new_version,    true,  true,  "General",       "CheckNewVersion"         , true  },
  { CM_SENDSYSTEMINFO,      (bool *) &send_system_info,     true,  true,  "General",       "SendSystemInfo"          , true  },
  { -1,                     (bool *) NULL,                  false, true,  "",              ""                        , false }
} ;

#ifdef MAP_INI_TO_REGISTRY

bool PutHKCU(const char *Section, const char *Name, const char *Value)
{
  char                  path[1024] = "Software\\" REGKEY "\\" REGVERKEY "\\Windows\\Engine\\";
  HKEY                  hKey ;

  strcat(path, Section);
  if (RegCreateKeyEx(HKEY_CURRENT_USER, path, 0, "", REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
    return (false) ;
  if (Value != NULL)
    RegSetValueEx(hKey, Name, 0, REG_SZ, (BYTE *) Value, (DWORD) strlen(Value) + 1) ;
  else
    RegDeleteValue(hKey, Name);
  RegCloseKey(hKey) ;
  return true;
}

bool PutHKCU(const char *Section, const char *Name, const string& Value)
{
  return PutHKCU(Section, Name, Value.c_str());
}

bool PutHKCU(const char *Section, const char *Name, unsigned Value)
{
  char                  path[1024] = "Software\\" REGKEY "\\" REGVERKEY "\\Windows\\Engine\\";
  HKEY                  hKey ;

  strcat(path, Section);
  if (RegCreateKeyEx (HKEY_CURRENT_USER, path, 0, "", REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
    return (false) ;
  RegSetValueEx(hKey, Name, 0, REG_DWORD, (BYTE *) &Value, sizeof(DWORD)) ;
  RegCloseKey(hKey);
  return true;
}

unsigned GetHKCU(const char *Section, const char *Name, unsigned DefaultValue)
{
  char      path[1024] = "Software\\" REGKEY "\\" REGVERKEY "\\Windows\\Engine\\";
  HKEY      key;
  DWORD     type;
  DWORD     len = sizeof(DWORD);
  DWORD     result = DefaultValue;

  strcat(path, Section);
  if (RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &key) == ERROR_SUCCESS)
  {
    if (RegQueryValueEx(key, Name, 0, &type, (BYTE *) &result, &len) != ERROR_SUCCESS || type != REG_DWORD)
      result = DefaultValue;
    RegCloseKey(key) ;
  }
  return result;
}

size_t GetHKCU(const char *Section, const char *Name, const char *DefaultValue, char *Buffer, unsigned MaxLength)
{
  char      path[1024] = "Software\\" REGKEY "\\" REGVERKEY "\\Windows\\Engine\\";
  HKEY      key;
  DWORD     type;
  DWORD     len = MaxLength;

  strcat(path, Section);
  if (RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &key) == ERROR_SUCCESS)
  {
    if (RegQueryValueEx(key, Name, 0, &type, (BYTE *) Buffer, &len) != ERROR_SUCCESS || type != REG_SZ)
      strcpy(Buffer, DefaultValue);
    RegCloseKey(key) ;
  }
  else
    strcpy(Buffer, DefaultValue);
  return strlen(Buffer);
}

#else // MAP_INI_TO_REGISTRY

bool PutHKCU(const char *section, const char *entry, const char *value)
{
  WritePrivateProfileString(section, entry, value, EngineIniFileName);
  return true;
}

bool PutHKCU(const char *section, const char *entry, const string& value)
{
  WritePrivateProfileString(section, entry, value.c_str(), EngineIniFileName);
  return true;
}

bool PutHKCU(const char *section, const char *entry, unsigned int value)
{
  char        str [32] ;

  sprintf (str, "%u", value) ;
  WritePrivateProfileString(section, entry, str, EngineIniFileName);
  return true;
}

unsigned GetHKCU(const char *section, const char *name, unsigned defval)
{
  return GetPrivateProfileInt(section, name, defval, EngineIniFileName);
}

size_t GetHKCU(const char *section, const char *name, const char *defval, char *buffer, unsigned maxlen)
{
  return GetPrivateProfileString(section, name, defval, buffer, maxlen, EngineIniFileName);
}

#endif

bool GetDontShowAgain (const char *Name)
{
  return (GetHKCU("DontShowAgain", Name, 0) != 0) ;
}

void PutDontShowAgain (const char *Name, bool dontShow)
{
  PutHKCU("DontShowAgain", Name, dontShow ? 1 : 0) ;
}

bool process_toggles (WPARAM wParam)
{
  toggle_struct         *t ;

  for (t = toggles ; t->id != -1 ; t++)
  {
    if (t->id == LOWORD (wParam))
    {
      *t->varptr = !*t->varptr ;
      PVCheckMenuItem (t->id, *t->varptr ? MF_CHECKED : MF_UNCHECKED) ;
      return (t->rval) ;
    }
  }
  return (false) ;
}

void set_toggles (void)
{
  toggle_struct         *t ;

  for (t = toggles ; (int) t->id != -1 ; t++)
    PVCheckMenuItem (t->id, *t->varptr ? MF_CHECKED : MF_UNCHECKED) ;
}

void read_toggles (void)
{
  toggle_struct         *t ;

  for (t = toggles ; (int) t->id != -1 ; t++)
    *t->varptr = (GetHKCU(t->section, t->entry, t->defval) != 0);
}

void write_toggles (void)
{
  toggle_struct         *t ;

  for (t = toggles ; (int) t->id != -1 ; t++)
    if (t->autowrite)
      PutHKCU(t->section, t->entry, *t->varptr) ;
}

bool fileExists (const char *filename)
{
  struct stat statBuf ;

  if ((stat (filename, &statBuf) != 0) || ((statBuf.st_mode & _S_IFREG) == 0))
    return (false) ;
  return (true) ;
}

bool dirExists (const char *filename)
{
  char        str[_MAX_PATH];
  struct stat statBuf ;

  if (filename[0] == '\0')
    return false;
  if (hasTrailingPathSeparator(filename))
  {
    strcpy(str, filename);
    str[strlen(filename) - 1] = '\0';
    return stat(str, &statBuf) == 0 && (statBuf.st_mode & _S_IFDIR) != 0;
  }

  return stat(filename, &statBuf) == 0 && (statBuf.st_mode & _S_IFDIR) != 0;
}

void read_dir_restriction (char *iniSection, char **store)
{
  int         i ;
  char        EntryName [16] ;
  char        str1 [_MAX_PATH] ;
  char        str2 [_MAX_PATH] ;
  char        *s ;

  // we can afford to be generous with respect to errors here - if an entry
  // doesn't make it into the list, it won't reduce security, since the system
  // defaults to denying access.
  for (i = 0 ; i < MAX_DIRSPEC - 1 ; i++)
  {
    sprintf (EntryName, "%d", i) ;
    GetHKCU(iniSection, EntryName, "", str1, sizeof (str1)) ;
    if (str1 [0] == '\0')
      continue ;
    if (_strnicmp (str1, "%INSTALLDIR%", 12) == 0)
    {
      strcpy (str2, str1) ;
      strcpy (str1, BinariesPath) ;
      trimTrailingPathSeparator (str1) ;
      strcat (str1, str2 + 12) ;
    }
    else if (_strnicmp (str1, "%PROFILEDIR%", 12) == 0)
    {
      strcpy (str2, str1) ;
      strcpy (str1, DocumentsPath) ;
      trimTrailingPathSeparator (str1) ;
      strcat (str1, str2 + 12) ;
    }
    else if (_strnicmp (str1, "%FONTDIR%", 9) == 0)
    {
      strcpy (str2, str1 + 9) ;
      strcpy (str1, FontPath) ;
      trimTrailingPathSeparator (str1) ;
      strcat (str1, str2) ;
    }
    appendPathSeparator (str1) ;
    if (GetFullPathName (str1, sizeof (str2), str2, &s) == 0)
      continue ;
    _strupr (str2) ;
    if ((s = (char *) malloc (strlen (str2) + 1)) == NULL)
      continue ;
    strcpy (s, str2) ;
    *store++ = s ;
  }
}

void read_dir_restrictions (void)
{
  io_restrictions = GetHKCU("Scripting", "IO Restrictions", 1) ;
  PVCheckMenuRadioItem (CM_IO_NO_RESTRICTIONS, CM_IO_RESTRICT_READWRITE, io_restrictions + CM_IO_NO_RESTRICTIONS) ;
  read_dir_restriction ("Permitted Input Paths", ReadDirSpecs) ;
  read_dir_restriction ("Permitted Output Paths", WriteDirSpecs) ;
}

void clear_dir_restrictions (void)
{
  for (char **dirspec = ReadDirSpecs ; *dirspec != NULL ; dirspec++)
    free (*dirspec) ;
  for (char **dirspec = WriteDirSpecs ; *dirspec != NULL ; dirspec++)
    free (*dirspec) ;
  memset (ReadDirSpecs, 0, sizeof (ReadDirSpecs)) ;
  memset (WriteDirSpecs, 0, sizeof (WriteDirSpecs)) ;
}

void read_INI_settings (void)
{
  char        str [_MAX_PATH] ;

  mainwin_placement.showCmd = GetHKCU("MainWindow", "ShowCmd", SW_SHOWNORMAL) ;
  mainwin_placement.ptMinPosition.x = GetHKCU("MainWindow", "MinPositionX", -1) ;
  mainwin_placement.ptMinPosition.y = GetHKCU("MainWindow", "MinPositionY", -1) ;
  mainwin_placement.ptMaxPosition.x = GetHKCU("MainWindow", "MaxPositionX", -1) ;
  mainwin_placement.ptMaxPosition.y = GetHKCU("MainWindow", "MaxPositionY", -1) ;
  mainwin_placement.rcNormalPosition.left = GetHKCU("MainWindow", "NormalPositionLeft", 128) ;
  mainwin_placement.rcNormalPosition.top = GetHKCU("MainWindow", "NormalPositionTop", 128) ;
  mainwin_placement.rcNormalPosition.right = GetHKCU("MainWindow", "NormalPositionRight", -1) ;
  mainwin_placement.rcNormalPosition.bottom = GetHKCU("MainWindow", "NormalPositionBottom", -1) ;
  renderwin_left = GetHKCU("RenderWindow", "NormalPositionX", 256) ;
  renderwin_top = GetHKCU("RenderWindow", "NormalPositionY", 256) ;
  renderwin_flags = GetHKCU("RenderWindow", "Flags", 0) ;
  renderwin_transparency = GetHKCU("RenderWindow", "Transparency", 128) ;
  GetHKCU("Messages", "Font", "Lucida Console", message_font_name, sizeof (message_font_name)) ;
  message_font_size = GetHKCU("Messages", "FontSize", 8) ;
  message_font_weight = GetHKCU("Messages", "FontWeight", FW_NORMAL) ;
  fast_scroll = (GetHKCU("Messages", "FastScroll", false) != 0);
  alert_sound = GetHKCU("Renderer", "AlertSound", MB_ICONASTERISK) ;
  render_priority = GetHKCU("Renderer", "Priority", CM_RENDERPRIORITY_NORMAL) ;
  Duty_Cycle = GetHKCU("Renderer", "DutyCycle", 9) ;
  on_completion = GetHKCU("Renderer", "Completion", CM_COMPLETION_NOTHING) ;
  no_palette_warn = (GetHKCU("General", "NoPaletteWarn", false) != 0);
  GetHKCU("General", "SecondaryINISection", "[512x384, No AA]", SecondaryRenderIniFileSection, sizeof (SecondaryRenderIniFileSection)) ;

  // some paths change between betas; since the INI file is not created or removed
  // by the installer, it may refer to old paths. fix this here and further on by
  // checking to make sure the path exists.
  GetHKCU("General", "LastRenderName", "", lastRenderName, sizeof (lastRenderName)) ;
  GetHKCU("General", "LastRenderPath", "", lastRenderPath, sizeof (lastRenderPath)) ;
  if (!dirExists(lastRenderPath))
    lastRenderPath[0] = '\0';
  GetHKCU("General", "LastQueuePath", "", lastQueuePath, sizeof (lastQueuePath)) ;
  if (!dirExists(lastQueuePath))
    lastQueuePath[0] = '\0';
  GetHKCU("General", "LastBitmapName", "*.bmp", lastBitmapName, sizeof (lastBitmapName)) ;
  if (strchr(lastBitmapName, '*') == NULL && !fileExists(lastBitmapName))
    strcpy(lastBitmapName, "*.bmp");
  GetHKCU("General", "LastBitmapPath", "", lastBitmapPath, sizeof (lastBitmapPath)) ;
  if (!dirExists(lastBitmapPath))
    lastBitmapPath[0] = '\0';

  sprintf (str, "%sini", DocumentsPath) ;
  GetHKCU("General", "LastINIPath", str, lastSecondaryIniFilePath, sizeof (lastSecondaryIniFilePath)) ;
  if (!dirExists (lastSecondaryIniFilePath))
    strcpy (lastSecondaryIniFilePath, str) ;
  sprintf (str, "%sini\\quickres.ini", DocumentsPath) ;
  GetHKCU("General", "SecondaryINIFile", str, SecondaryRenderIniFileName, sizeof (SecondaryRenderIniFileName)) ;
  if (!fileExists (SecondaryRenderIniFileName))
    strcpy (SecondaryRenderIniFileName, str) ;
  GetHKCU("General", "BackgroundFile", screen_depth > 8 ? "0" : "1", background_file, sizeof (background_file)) ;

  text_colours[0] = GetHKCU("General", "TextColour", RGB (255, 255, 255)) ;
  text_colours[1] = GetHKCU("General", "WarningColour", RGB (255, 255, 0)) ;
  text_colours[2] = GetHKCU("General", "ErrorColour", RGB (0, 255, 255)) ;
  background_colour = GetHKCU("General", "BackgroundColour", RGB (31, 0, 63)) ;
  if (!debugging)
    debugging = (GetHKCU("General", "Debug", 0) != 0);
  drop_to_editor = (GetHKCU("General", "DropToEditor", 1) != 0);
  editors_enabled = (GetHKCU("General", "UseEditors", 1) != 0);
  for (int i = 0 ; i < 6 ; i++)
  {
    sprintf (str, "Band%dWidth", i) ;
    bandWidths [i] = GetHKCU("ToolBar", str, 0) ;
  }
  read_toggles() ;
  read_dir_restrictions() ;

  sprintf (str, "%ssounds\\Render Finished.wav", BinariesPath) ;
  GetHKCU("Sounds", "RenderCompleteSound", str, render_complete_sound, _MAX_PATH) ;
  if (!fileExists(render_complete_sound))
    strcpy(render_complete_sound, str);

  sprintf (str, "%ssounds\\Parse Error.wav", BinariesPath) ;
  GetHKCU("Sounds", "ParseErrorSound", str, parse_error_sound, _MAX_PATH) ;
  if (!fileExists(parse_error_sound))
    strcpy(parse_error_sound, str);

  sprintf (str, "%ssounds\\Render Cancelled.wav", BinariesPath) ;
  GetHKCU("Sounds", "RenderErrorSound", str, render_error_sound, _MAX_PATH) ;
  if (!fileExists(render_error_sound))
    strcpy(render_error_sound, str);

  render_complete_sound_enabled = GetHKCU("Sounds", "RenderCompleteSoundEnabled", TRUE) != 0 ;
  parse_error_sound_enabled = GetHKCU("Sounds", "ParseErrorSoundEnabled", TRUE) != 0 ;
  render_error_sound_enabled = GetHKCU("Sounds", "RenderErrorSoundEnabled", TRUE) != 0 ;

  AutoAppendPaths = GetHKCU("General", "AutoAppendPaths", TRUE) != 0 ;

#ifdef RTR_SUPPORT
  GetHKCU("RTR", "VideoSource", "", str, sizeof (str)) ;
  SetVideoSourceName(str);
#endif
}

void write_INI_settings (bool noreset)
{
  char                  *s ;
  char                  str [sizeof (command_line)] ;
  REBARBANDINFO         rebarInfo ;

  if (noreset)
    strcpy (str, command_line) ;
  if (RegionStr [0] != '\0')
  {
    if ((s = strstr (command_line, RegionStr)) != NULL)
      strcpy (s, s + strlen (RegionStr)) ;
    else if ((s = strstr (command_line, RegionStr + 1)) != NULL)
      strcpy (s, s + strlen (RegionStr) - 1) ;
    if (!noreset)
      SendMessage (toolbar_cmdline, WM_SETTEXT, 0, (LPARAM) command_line) ;
  }
  PutHKCU("General", "CommandLine", command_line) ;
  if (noreset)
    strcpy (command_line, str) ;

  rebarInfo.cbSize = sizeof (REBARBANDINFO) ;
  rebarInfo.fMask = RBBIM_SIZE ;
  for (int i = 0 ; i < 6 ; i++)
  {
    sprintf (str, "Band%dWidth", i) ;
    // under XP64 the SendMessage() call returns TRUE but the cx value is not altered.
    // so we detect that and avoid writing the data unless the problem is fixed.
    rebarInfo.cx = 0x12345678 ;
    if (SendMessage (rebar_window, RB_GETBANDINFO, i, (LPARAM) (LPREBARBANDINFO) &rebarInfo))
      if (rebarInfo.cx != 0x12345678)
        PutHKCU("ToolBar", str, rebarInfo.cx) ;
  }

  PutHKCU("MainWindow", "ShowCmd", mainwin_placement.showCmd) ;
  PutHKCU("MainWindow", "NormalPositionLeft", mainwin_placement.rcNormalPosition.left) ;
  PutHKCU("MainWindow", "NormalPositionTop", mainwin_placement.rcNormalPosition.top) ;
  PutHKCU("MainWindow", "NormalPositionRight", mainwin_placement.rcNormalPosition.right) ;
  PutHKCU("MainWindow", "NormalPositionBottom", mainwin_placement.rcNormalPosition.bottom) ;
  PutHKCU("RenderWindow", "NormalPositionX", renderwin_left) ;
  PutHKCU("RenderWindow", "NormalPositionY",  renderwin_top) ;
  PutHKCU("RenderWindow", "Flags",  renderwin_flags) ;
  PutHKCU("RenderWindow", "Transparency",  renderwin_transparency) ;
  PutHKCU("Messages", "FontSize", message_font_size) ;
  PutHKCU("Messages", "FontWeight", message_font_weight) ;
  PutHKCU("Renderer", "AlertSound", alert_sound) ;
  PutHKCU("Renderer", "Completion", on_completion) ;
  PutHKCU("Renderer", "Priority", render_priority) ;
  PutHKCU("Renderer", "DutyCycle", Duty_Cycle) ;
  PutHKCU("General", "TextColour", text_colours[0]) ;
  PutHKCU("General", "WarningColour", text_colours[1]) ;
  PutHKCU("General", "ErrorColour", text_colours[2]) ;
  PutHKCU("General", "BackgroundColour", background_colour) ;
  PutHKCU("General", "DropToEditor", drop_to_editor) ;
  PutHKCU("Messages", "Font", message_font_name) ;
  PutHKCU("General", "LastRenderName",  lastRenderName) ;
  PutHKCU("General", "LastRenderPath", lastRenderPath) ;
  PutHKCU("General", "LastQueuePath", lastQueuePath) ;
  PutHKCU("General", "SecondaryINISection", SecondaryRenderIniFileSection) ;
  PutHKCU("General", VERSIONVAL, POV_RAY_VERSION COMPILER_VER "." PVENGINE_VER) ;
  PutHKCU("Sounds", "RenderCompleteSoundEnabled", render_complete_sound_enabled) ;
  PutHKCU("Sounds", "ParseErrorSoundEnabled", parse_error_sound_enabled) ;
  PutHKCU("Sounds", "RenderErrorSoundEnabled", render_error_sound_enabled) ;
  PutHKCU("General", "LastBitmapName",  lastBitmapName) ;
  PutHKCU("General", "LastBitmapPath", lastBitmapPath) ;
  PutHKCU("General", "LastINIPath", lastSecondaryIniFilePath) ;
  PutHKCU("General", "SecondaryINIFile", SecondaryRenderIniFileName) ;
  PutHKCU("General", "BackgroundFile", background_file) ;
  PutHKCU("Sounds", "RenderCompleteSound", render_complete_sound) ;
  PutHKCU("Sounds", "ParseErrorSound", parse_error_sound) ;
  PutHKCU("Sounds", "RenderErrorSound", render_error_sound) ;
  write_toggles () ;

#ifdef RTR_SUPPORT
  PutHKCU("RTR", "VideoSource", GetVideoSourceName().c_str()) ;
#endif
}

#ifdef BOOST_REGEX_NO_LONGER_BROKEN

// can't use regex right now: on exit from povwin, the atexit handler registered by
// boost calls something in the regex library that causes an exception. this happens
// even if all we ever did was declare a single regex (e.g. 'boost::regex foo("bar");')
// and never use it.
//
// TODO: add replace regex to alter paths from old to new where appropriate
void cloneOldIni(string oldPath, string newPath)
{
  bool                      keep = false;
  smatch                    what;
  string                    sectionName;
  string                    line;
  const regex               section("^\\[\\s*([^\\]]*)\\s*\\].*");
  const regex               entry("^([^=\\s]+)\\s*=.*");
  const regex               skipEntries("runcount|itsabouttime|commandline|lastinipath|secondaryinifile|version|backgroundfile|.*sound");
  const regex               copiedSections("general|editor|tipoftheday|toolbar|mainwindow|renderwindow|messages|renderer|lastrender|dontshowagain|sounds");
  map<string, bool>         hadSection;

  line = oldPath + "ini\\pvengine.ini";
  std::ifstream inF(line.c_str());
  if (inF.bad())
    return;
  line = newPath + "ini\\pvengine.ini";
  ofstream outF(line.c_str());
  if (outF.bad())
    return;

  outF << "[Permitted Input Paths]" << endl;
  outF << "1=%INSTALLDIR%" << endl;
  outF << "2=%PROFILEDIR%" << endl;
  outF << "3=%FONTDIR%" << endl;
  outF << endl << "[Permitted Output Paths]" << endl;
  outF << "1=%PROFILEDIR%\\Insert Menu" << endl;

  // some INI files I've seen have duplicate sections. during the copy we filter out any such.
  while (getline(inF, line))
  {
    trim(line);
    string str(to_lower_copy(line));
    if (str.empty())
      continue;
    if (regex_match(str, what, section))
    {
      keep = !hadSection[sectionName = what[1]] && regex_match(sectionName, copiedSections);
      hadSection[sectionName] = true;
      if (keep)
        outF << endl << line << endl;
      continue;
    }
    if (keep && regex_match(str, what, entry) && regex_match(static_cast<string>(what[1]), skipEntries))
      continue;
    if (keep)
      outF << line << endl;
  }
  inF.close();

  outF << "[GUIExtensions]" << endl << endl;
  outF << "[Scripting]" << endl << endl;
  outF << "[RTR]" << endl << endl;
  outF << "[Info]" << endl << endl;
  outF.close();
}

#else

void cloneOldIni(string oldPath, string newPath)
{
  bool                      keep = false;
  size_t                    pos;
  string                    sectionName;
  string                    line;
  string                    oldPathLC = boost::to_lower_copy(oldPath);
  const string              keepSections("general^toolbar^mainwindow^renderwindow^renderer^lastrender^dontshowagain^editor^");
  const string              skipEntries("runcount^itsabouttime^commandline^version^backgroundcolour^textcolour^"
                                        "tilebackground^bigsplash^savesettingsonexit^");
  std::ifstream             inF;
  map<string, bool>         hadSection;

  if (oldPath != "")
  {
    line = oldPath + "ini\\pvengine.ini";
    inF.open(line.c_str());
    if (inF.bad())
      return;
  }
  line = newPath + "ini\\pvengine.ini";
  ofstream outF(line.c_str());
  if (outF.bad())
  {
    if (oldPath != "")
      inF.close();
    return;
  }

  outF << "[Permitted Input Paths]" << endl;
  outF << "1=%INSTALLDIR%" << endl;
  outF << "2=%PROFILEDIR%" << endl;
  outF << "3=%FONTDIR%" << endl;
  outF << endl << "[Permitted Output Paths]" << endl;
  outF << "1=%PROFILEDIR%\\Insert Menu" << endl;
  outF << endl << "[GUIExtensions]" << endl << endl;
  outF << "[Scripting]" << endl << endl;
  outF << "[RTR]" << endl << endl;
  outF << "[Info]" << endl << endl;

  if (oldPath == "")
  {
    outF.close();
    return;
  }

  // some INI files I've seen have duplicate sections. during the copy we filter out any such.
  while (getline(inF, line))
  {
    boost::trim(line);
    string str(boost::to_lower_copy(line));
    if (str.empty())
      continue;
    if (str[0] == '[' && (pos = str.find(']')) != string::npos)
    {
      sectionName = boost::trim_copy(str.substr(1, pos - 1));
      keep = !hadSection[sectionName] && keepSections.find(sectionName + "^") != string::npos;
      hadSection[sectionName] = true;
      if (keep)
        outF << endl << line << endl;
      continue;
    }
    if (keep)
    {
      // any INI entry that refers to the previous install dir is ignored
      if (str.find(oldPathLC) != string::npos)
        continue;

      if ((pos = str.find('=')) != string::npos)
      {
        str.erase(pos);
        boost::trim(str);

        // skip other entries we don't want to copy into the new install's INI
        if (skipEntries.find(str + "^") != string::npos)
          continue;
      }
      outF << line << endl;
    }
  }
  inF.close();
  outF << endl;
  outF.close();
}
#endif

void GetRelativeClientRect (HWND hParent, HWND hChild, RECT *rect)
{
  POINT       *points = (POINT *) rect ;

  GetWindowRect (hChild, rect) ;
  ScreenToClient (hParent, points++) ;
  ScreenToClient (hParent, points) ;
}

void CenterWindowRelative (HWND hRelativeTo, HWND hTarget, bool bRepaint, bool checkBorders)
{
  int         difference ;
  int         width ;
  int         height ;
  int         x ;
  int         y ;
  int         twidth ;
  int         theight ;
  RECT        relativeToRect ;
  RECT        targetRect ;

  if (hRelativeTo != NULL && IsWindowVisible (hRelativeTo))
  {
    WINDOWPLACEMENT wp ;
    wp.length = sizeof (WINDOWPLACEMENT) ;
    GetWindowPlacement (hRelativeTo, &wp) ;
    if (wp.showCmd == SW_SHOWMINIMIZED)
      hRelativeTo = GetDesktopWindow () ;
  }
  else
    hRelativeTo = GetDesktopWindow () ;
  GetWindowRect (hRelativeTo, &relativeToRect) ;
  GetWindowRect (hTarget, &targetRect) ;
  width = targetRect.right - targetRect.left ;
  height = targetRect.bottom - targetRect.top ;
  difference = relativeToRect.right - relativeToRect.left - width ;
  x = relativeToRect.left + difference / 2 ;
  difference = relativeToRect.bottom - relativeToRect.top - height ;
  y = relativeToRect.top + difference / 2 ;
  MoveWindow (hTarget, x, y, width, height, bRepaint && !checkBorders) ;
  if (checkBorders)
  {
    GetWindowRect (hTarget, &targetRect) ;
    if (targetRect.left < screen_origin_x)
      OffsetRect (&targetRect, screen_origin_x - targetRect.left, 0) ;
    if (targetRect.top < screen_origin_y)
      OffsetRect (&targetRect, 0, screen_origin_y - targetRect.top) ;
    if (targetRect.right > virtual_screen_width + screen_origin_x)
      OffsetRect (&targetRect, -(targetRect.right - (virtual_screen_width + screen_origin_x)), 0) ;
    if (targetRect.bottom > virtual_screen_height + screen_origin_y)
      OffsetRect (&targetRect, 0, -(targetRect.bottom - (virtual_screen_height + screen_origin_y))) ;
    twidth = targetRect.right - targetRect.left ;
    theight = targetRect.bottom - targetRect.top ;
    MoveWindow (hTarget, targetRect.left, targetRect.top, twidth, theight, bRepaint) ;
  }
}

void FitWindowInWindow (HWND hRelativeTo, HWND hTarget)
{
  int         rwidth ;
  int         rheight ;
  int         twidth ;
  int         theight ;
  int         x ;
  int         y ;
  RECT        relativeToRect ;
  RECT        targetRect ;

  GetWindowRect (hTarget, &targetRect) ;

  if (hRelativeTo == NULL)
  {
    if (targetRect.right > virtual_screen_width + screen_origin_x)
      OffsetRect (&targetRect, -(targetRect.right - (virtual_screen_width + screen_origin_x)), 0) ;
    if (targetRect.bottom > virtual_screen_height + screen_origin_y)
      OffsetRect (&targetRect, 0, -(targetRect.bottom - (virtual_screen_height + screen_origin_y))) ;
    if (targetRect.left < screen_origin_x)
      OffsetRect (&targetRect, screen_origin_x - targetRect.left, 0) ;
    if (targetRect.top < screen_origin_y)
      OffsetRect (&targetRect, 0, screen_origin_y - targetRect.top) ;
    twidth = targetRect.right - targetRect.left ;
    theight = targetRect.bottom - targetRect.top ;
    MoveWindow (hTarget, targetRect.left, targetRect.top, twidth, theight, true) ;
    return ;
  }

  // if window is not visible GetWindowRect() is not reliable.
  if (!IsWindowVisible (hRelativeTo))
    hRelativeTo = GetDesktopWindow () ;
  GetWindowRect (hRelativeTo, &relativeToRect) ;

  twidth = targetRect.right - targetRect.left ;
  theight = targetRect.bottom - targetRect.top ;
  rwidth = relativeToRect.right - relativeToRect.left ;
  rheight = relativeToRect.bottom - relativeToRect.top ;
  x = targetRect.left ;
  y = targetRect.top ;

  if (twidth > rwidth)
    twidth = rwidth ;
  if (theight > rheight)
    theight = rheight ;
  if (x < relativeToRect.left)
    x = relativeToRect.left ;
  if (y < relativeToRect.top)
    y = relativeToRect.top ;
  if (x + twidth > relativeToRect.right)
    x = relativeToRect.right - twidth ;
  if (y + theight > relativeToRect.bottom)
    y = relativeToRect.bottom - theight ;

  MoveWindow (hTarget, x, y, twidth, theight, true) ;
}

void CenterOffset (HWND win, int id, int offx, int offy, int offw, int offh)
{
  int         x ;
  int         y ;
  int         w ;
  int         h ;
  RECT        R ;
  HWND        dlg = GetDlgItem (win, id) ;
  POINT       P ;

  if (dlg)
  {
    GetWindowRect (dlg, &R) ;

    P.x = R.left ;
    P.y = R.top ;

    ScreenToClient (win, &P) ;

    x = P.x ;
    y = P.y ;
    w = R.right - R.left ;
    h = R.bottom - R.top  ;

    SetWindowPos (dlg, NULL, x + offx, y + offy, w + offw, h + offh, SWP_NOZORDER) ;
  }
}

void SetupExplorerDialog (HWND win)
{
  int         dx ;
  int         dy ;
  RECT        winPos ;

  win = GetParent (win) ;
  GetWindowRect (win, &winPos) ;

  dx = screen_width * 2 / 3 - (winPos.right  - winPos.left) ;
  dy = screen_height * 2 / 3 - (winPos.bottom - winPos.top) ;

  SetWindowPos (win, NULL, 0, 0, screen_width * 2 / 3, screen_height * 2 / 3, SWP_NOZORDER | SWP_NOACTIVATE) ;
  CenterWindowRelative (main_window, win, true, true) ;
  FitWindowInWindow (NULL, win) ;

  CenterOffset (win, 1091,    0,  0,   0,  0) ;
  CenterOffset (win, 1137,    0,  0,   0,  0) ;
  CenterOffset (win, 1088,    0,  0,   0,  0) ;
  CenterOffset (win, 1120,    0,  0,  dx, dy) ;
  CenterOffset (win, 1090,    0, dy,   0,  0) ;
  CenterOffset (win, 1152,    0, dy,  dx,  0) ;
  CenterOffset (win, 1089,    0, dy,   0,  0) ;
  CenterOffset (win, 1136,    0, dy,  dx,  0) ;
  CenterOffset (win, 1040,    0, dy,   0,  0) ;
  CenterOffset (win,    1, dx  , dy,   0,  0) ;
  CenterOffset (win,    2, dx  , dy,   0,  0) ;
  CenterOffset (win, 1038, dx  , dy,   0,  0) ;
}

FileType get_file_type (const char *filename)
{
  char        ext [_MAX_EXT] ;

  splitfn (filename, NULL, NULL, ext) ;
  _strupr (ext) ;

  if (strcmp (ext, ".POV") == 0)
    return (filePOV) ;
  else if (strcmp (ext, ".INC") == 0)
    return (filePOV) ;
  else if (strcmp (ext, ".MCR") == 0)
    return (filePOV) ;
  else if (strcmp (ext, ".MAC") == 0)
    return (filePOV) ;
  else if (strcmp (ext, ".INI") == 0)
    return (fileINI) ;
  else if (strcmp (ext, ".PPM") == 0)
    return (filePPM) ;
  else if (strcmp (ext, ".PGM") == 0)
    return (filePGM) ;
  else if (strcmp (ext, ".PBM") == 0)
    return (filePBM) ;
  else if (strcmp (ext, ".PNG") == 0)
    return (filePNG) ;
  else if (strcmp (ext, ".GIF") == 0)
    return (fileGIF) ;
  else if (strcmp (ext, ".BMP") == 0)
    return (fileBMP) ;
  else if (strcmp (ext, ".EXR") == 0)
    return (fileEXR) ;
  else
    return (fileUnknown) ;
}

bool is_non_primary_file(const char *filename)
{
  char        ext [_MAX_EXT] ;

  splitfn (filename, NULL, NULL, ext) ;
  _strupr (ext) ;

  if (strcmp (ext, ".POV") == 0)
    return (false) ;
  if (strcmp (ext, ".INI") == 0)
    return (false) ;
  return true;
}

char *get_full_name (char *s)
{
  char                  dir [_MAX_PATH + 1] ;
  static char           str [_MAX_PATH] ;

  if (*s == 0)
    return (s) ;
  splitpath (s, str, NULL) ;
  if (str [0] == '\0')
  {
#ifdef TIME_WARP
    // workaround for suspected Win32s bug
    SetCurrentDirectory (".") ;
#endif
    GetCurrentDirectory (sizeof (dir), dir) ;
    joinPath (str, dir, s) ;
    return (str) ;
  }
  return (s) ;
}

void update_menu_for_render (bool rendering)
{
  PVEnableMenuItem (CM_FILERENDER, MF_ENABLED) ;
  PVEnableMenuItem (CM_STOPRENDER, MF_ENABLED) ;

  if (rendering)
  {
    PVModifyMenu (CM_FILERENDER, MF_STRING, CM_FILERENDER, "&Stop Rendering\tAlt+G") ;
    PVEnableMenuItem (CM_COMMANDLINE, MF_GRAYED) ;
    PVEnableMenuItem (CM_RENDERINSERT, MF_GRAYED) ;
    PVEnableMenuItem (CM_SOURCEFILE, MF_GRAYED) ;
    PVEnableMenuItem (CM_DEMO, MF_GRAYED) ;
    PVEnableMenuItem (CM_BENCHMARK, MF_GRAYED) ;
    PVEnableMenuItem (CM_FORCE8BITS, MF_GRAYED) ;
    PVEnableMenuItem (CM_RENDERTHREADCOUNT, MF_GRAYED) ;
    SendMessage (toolbar_window, TB_CHECKBUTTON, (WPARAM) CM_RENDERSLEEP, 0L) ;
    SendMessage (toolbar_window, TB_HIDEBUTTON, (WPARAM) CM_FILERENDER, MAKELONG (1, 0)) ;
    SendMessage (toolbar_window, TB_HIDEBUTTON, (WPARAM) CM_STOPRENDER, MAKELONG (0, 0)) ;
    EnableWindow (toolbar_cmdline, false) ;
  }
  else
  {
    PVEnableMenuItem (CM_SOURCEFILE, MF_ENABLED) ;
    PVEnableMenuItem (CM_COMMANDLINE, MF_ENABLED) ;
    PVEnableMenuItem (CM_RENDERINSERT, MF_ENABLED) ;
    PVEnableMenuItem (CM_DEMO, MF_ENABLED) ;
    PVEnableMenuItem (CM_BENCHMARK, MF_ENABLED) ;
    PVModifyMenu (CM_FILERENDER, MF_STRING, CM_FILERENDER, "&Start Rendering\tAlt+G") ;
    PVEnableMenuItem (CM_FORCE8BITS, MF_ENABLED) ;
    PVEnableMenuItem (CM_RENDERSLEEP, MF_GRAYED) ;
    PVEnableMenuItem (CM_RENDERTHREADCOUNT, MF_ENABLED) ;
    SendMessage (toolbar_window, TB_CHECKBUTTON, (WPARAM) CM_RENDERSLEEP, 0L) ;
    SendMessage (toolbar_window, TB_HIDEBUTTON, (WPARAM) CM_FILERENDER, MAKELONG (0, 0)) ;
    SendMessage (toolbar_window, TB_HIDEBUTTON, (WPARAM) CM_STOPRENDER, MAKELONG (1, 0)) ;
    EnableWindow (toolbar_cmdline, true) ;
  }
  DrawMenuBar (main_window) ;
}

void update_queue_status (bool write_files)
{
  int         i ;
  char        str [64] ;

  if (queued_file_count == 0)
  {
    PVModifyMenu (CM_CLEARQUEUE, MF_STRING, CM_CLEARQUEUE, "C&lear Queue (no entries)") ;
    PVEnableMenuItem (CM_CLEARQUEUE, MF_GRAYED) ;
  }
  else
  {
    sprintf (str, "C&lear Queue (%d %s)", queued_file_count, queued_file_count == 1 ? "entry" : "entries") ;
    PVModifyMenu (CM_CLEARQUEUE, MF_STRING, CM_CLEARQUEUE, str) ;
    PVEnableMenuItem (CM_CLEARQUEUE, queued_file_count ? MF_ENABLED : MF_GRAYED) ;
  }
  if (write_files)
  {
    PutHKCU("FileQueue", "QueueCount", queued_file_count) ;
    for (i = 0 ; i < MAX_QUEUE ; i++)
    {
      sprintf (str, "QueuedFile%d", i) ;
      PutHKCU("FileQueue", str, i < queued_file_count ? queued_files [i] : NULL) ;
    }
  }
}

void resize_listbox_dialog (HWND hDlg, int idLb, int chars)
{
  int         difference ;
  HWND        hLb ;
  HWND        hBtn ;
  RECT        lbRect ;
  RECT        btnRect ;
  RECT        dlgRect ;

  hLb = GetDlgItem (hDlg, idLb) ;
  hBtn = GetDlgItem (hDlg, IDOK) ;
  GetRelativeClientRect (hDlg, hLb, &lbRect) ;
  GetRelativeClientRect (hDlg, hBtn, &btnRect) ;
  GetWindowRect (hDlg, &dlgRect) ;
  difference = message_xchar * (chars + 2) - (lbRect.right - lbRect.left) ;
  lbRect.right += difference ;
  MoveWindow (hLb, lbRect.left, lbRect.top, lbRect.right - lbRect.left, lbRect.bottom - lbRect.top, true) ;
  btnRect.left += difference / 2 ;
  btnRect.right += difference / 2 ;
  MoveWindow (hBtn, btnRect.left, btnRect.top, btnRect.right - btnRect.left, btnRect.bottom - btnRect.top, true) ;
  dlgRect.right += difference ;
  MoveWindow (hDlg, dlgRect.left, dlgRect.top, dlgRect.right - dlgRect.left, dlgRect.bottom - dlgRect.top, true) ;
}

#if 0
char *save_demo_file (void)
{
  GetTempPath (sizeof (filename), filename) ;
  strcat (filename, "POVDEMO.$$$") ;
  if ((hrsc = FindResource (hInst, MAKEINTRESOURCE (ID_DEMOFILE), RT_RCDATA)) == NULL)
  {
    PovMessageBox ("Cannot locate file resource\r\n(internal error)", "Cannot run demo") ;
    return (NULL) ;
  }
  if ((hglobal = LoadResource (hInst, hrsc)) == NULL)
  {
    PovMessageBox ("Cannot load file resource", "Cannot run demo") ;
    return (NULL) ;
  }
  if ((s = LockResource (hglobal)) == NULL)
  {
    PovMessageBox ("Cannot lock file resource", "Cannot run demo") ;
    return (NULL) ;
  }
  size = SizeofResource (hInst, hrsc) ;
  if ((outH = _lcreat (filename, 0)) == HFILE_ERROR)
  {
    PovMessageBox ("Cannot create temporary file", "Cannot run demo") ;
    return (NULL) ;
  }

  if (_lwrite (outH, s, size) != size)
  {
    PovMessageBox ("Cannot write temporary file", "Cannot run demo") ;
    return (NULL) ;
  }
  _lclose (outH) ;
}
#endif

int splitfn (const char *filename, char *path, char *name, char *ext)
{
  char        *s ;
  char        str [_MAX_PATH] ;
  char        *fn = strncpy (str, filename, _MAX_PATH) ;

  str[_MAX_PATH - 1] = '\0';
  if (path != NULL)
    *path = '\0' ;
  if (name != NULL)
    *name = '\0' ;
  if (ext != NULL)
    *ext = '\0' ;

  if ((s = findLastPathSeparator(fn)) != NULL)
  {
    *s++ = '\0' ;
    if (path)
    {
      strcpy (path, fn) ;
      strcat (path, "\\");
    }
    fn = s;
  }

  if ((s = strrchr (fn, '.')) != NULL)
  {
    if (ext)
      strcpy (ext, s) ;
    *s = '\0' ;
  }

  if (name)
    strcpy (name, fn) ;

  return (0) ;
}

void splitpath (const char *filename, char *path, char *name)
{
  char        str [_MAX_PATH] ;

  splitfn (filename, path, name, str) ;
  if (name != NULL)
    strcat (name, str) ;
}

void load_tool_menu (char *iniFilename)
{
  int         i ;
  int         count ;
  char        str [32] ;
  char        entry [256] ;
  char        *s ;

  memset (tool_commands, 0, sizeof (tool_commands)) ;
  memset (tool_help, 0, sizeof (tool_help)) ;

  DeleteMenu (hToolsMenu, 1, MF_BYCOMMAND) ;
  for (i = 0 ; i < MAX_TOOLCMD ; i++)
    DeleteMenu (hToolsMenu, CM_FIRSTTOOL + i, MF_BYCOMMAND) ;

  for (i = count = 0 ; i < MAX_TOOLCMD ; i++)
  {
    sprintf (str, "Item%d", i) ;

    GetPrivateProfileString ("Command", str, "", entry, sizeof (entry) - 1, iniFilename) ;
    if (strlen (entry) == 0)
      continue ;
    s = entry ;
    while (*s == ' ')
      s++ ;
    if (strlen (s) >= MAX_TOOLCMDTEXT)
    {
      message_printf ("Tool command %s is too long\n", str) ;
      s [MAX_TOOLCMDTEXT - 1] = '\0' ;
    }
    strcpy (tool_commands [count], s) ;

    GetPrivateProfileString ("Menu", str, "", entry, sizeof (entry) - 1, iniFilename) ;
    if (strlen (entry) == 0)
    {
      message_printf ("Tool menu entry %s is missing\n", str) ;
      continue ;
    }
    s = entry ;
    while (*s == ' ')
      s++ ;
    if (strlen (s) > 31)
    {
      message_printf ("Tool menu entry %s is too long\n", str) ;
      continue ;
    }
    if (count == 0)
      AppendMenu (hToolsMenu, MF_SEPARATOR, 1, "-") ;
    AppendMenu (hToolsMenu, MF_STRING, CM_FIRSTTOOL + count, s) ;

    GetPrivateProfileString ("Help", str, "", entry, sizeof (entry) - 1, iniFilename) ;
    if (strlen (entry) == 0)
    {
      count++ ;
      continue ;
    }
    s = entry ;
    while (*s == ' ')
      s++ ;
    if (strlen (s) >= MAX_TOOLHELPTEXT)
    {
      message_printf ("Tool help %s is too long\n", str) ;
      s [MAX_TOOLHELPTEXT - 1] = '\0' ;
    }
    strcpy (tool_help [count++], s) ;
  }
  message_printf ("Loaded %d %s into Tool Menu.\n", count, count != 1 ? "tools" : "tool") ;
}

char *parse_tool_command (char *command)
{
  char                  *s ;
  char                  ExternalStr [512] ;
  static char           str [512] ;

  str [0] = '\0' ;
  while (*command == ' ')
    command++ ;
  strcpy (ExternalStr, command) ;
  ExternalParseToolCommand (ExternalStr) ;
  if (strlen (ExternalStr))
    command = ExternalStr ;
  for (s = str ; *command ; command++)
  {
    if (strlen (str) >= sizeof (str) - _MAX_PATH)
      break ;
    if (*command == '%')
    {
      if (*++command == '%')
      {
        *s++ = *command ;
        continue ;
      }
      switch (toupper (*command))
      {
        case 'I' :
             s += sprintf (s, "%sini\\", DocumentsPath) ;
             break ;

        case 'T' :
             s += sprintf (s, "%s", ToolIniFileName) ;
             break ;

        case 'H' :
             s += sprintf (s, "%s", BinariesPath) ;
             break ;

        case 'P' :
             s += sprintf (s, "%s", DocumentsPath) ;
             break ;

        case 'R' :
             s += joinPath (s, lastRenderPath, lastRenderName) ;
             break ;

        case 'S' :
             s += sprintf (s, "%s", source_file_name) ;
             break ;

        case 'N' :
             s += sprintf (s, "%s", SecondaryRenderIniFileName) ;
             break ;

        case 'D' :
             s += GetHKCU("LastRender", "CurrentDirectory", "", s, _MAX_PATH) ;
             break ;

        case '0' :
             s += GetHKCU("LastRender", "SourceFile", "", s, _MAX_PATH) ;
             break ;

        case '1' :
             s += GetHKCU("LastRender", "OutputFile", "", s, _MAX_PATH) ;
             break ;

        case '2' :
             s += GetHKCU("LastRender", "SceneFile", "", s, _MAX_PATH) ;
             break ;

        case '4' :
             s += GetHKCU("LastRender", "IniOutputFile", "", s, _MAX_PATH) ;
             break ;
      }
      continue ;
    }
    if (s == str && isspace (*command))
      continue ;
    *s++ = *command ;
  }
  *s = '\0' ;
  return (str) ;
}

char *get_elapsed_time (int seconds)
{
  static char str [19] ;

  str [0] = '\0' ;
  sprintf (str, "%ud %02uh %02um %02us",
           seconds / 86400,
           seconds % 86400 / 3600,
           seconds % 3600 / 60,
           seconds % 60) ;
  return (str) ;
}

char *clean (char *s)
{
  static char           str [_MAX_PATH] ;

  while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n' )
    s++ ;
  s = strncpy (str, s, sizeof (str) - 1) ;
  if (*s == '\0')
    return (str) ;
  for (s += strlen (s) - 1 ; *s == ' ' || *s == '\t' || *s == '\r' || *s == '\n' ; s--)
    *s = '\0' ;
  return (str) ;
}

void extract_ini_sections (char *filename, HWND hwnd)
{
  char                  str [256] ;
  char                  *s1 ;
  char                  *s2 ;
  FILE                  *inF ;
  static char           inbuf [16384] ;

  /*
  ** flush the INI file cache
  */
  SendMessage (hwnd, CB_RESETCONTENT, 0, 0L) ;

  if ((inF = fopen (filename, "rt")) == NULL)
    return ;

  setvbuf (inF, inbuf, _IOFBF, sizeof (inbuf)) ;
  while (fgets (str, sizeof (str), inF) != NULL)
  {
    s1 = clean (str) ;
    if (*s1 == '[')
    {
      if ((s2 = strchr (s1, ']')) != NULL)
      {
        *++s2  = '\0' ;
        SendMessage (hwnd, CB_ADDSTRING, 0, (LPARAM) s1) ;
      }
    }
  }
  fclose (inF) ;
  SendMessage (hwnd, CB_SETCURSEL, 0, 0L) ;
}

int select_combo_item_ex (HWND hwnd, char *s)
{
  int         i ;
  char        str [256] ;

  if ((i = SendMessage (hwnd, CB_GETCOUNT, 0, 0)) < 0)
    return (i) ;
  while (i--)
  {
    SendMessage (hwnd, CB_GETLBTEXT, i, (LPARAM) str) ;
    if (strcmp (s, str) == 0)
    {
      SendMessage (hwnd, CB_SETCURSEL, i, 0) ;
      return (i) ;
    }
  }
  return (-1) ;
}

void extract_ini_sections_ex (char *filename, HWND hwnd)
{
  extract_ini_sections (filename, hwnd) ;
}

bool PovInvalidateRect (HWND hWnd, CONST RECT *lpRect, bool bErase)
{
  if (hWnd != NULL)
    return (InvalidateRect (hWnd, lpRect, bErase) != 0) ;
  return (0) ;
}

bool TaskBarAddIcon (HWND hwnd, UINT uID, HICON hicon, LPSTR lpszTip)
{
  NOTIFYICONDATA        tnid ;

  memset (&tnid, 0, sizeof (tnid)) ;
  tnid.cbSize = sizeof(NOTIFYICONDATA) ;
  tnid.hWnd = hwnd ;
  tnid.uID = uID ;
  tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP ;
  tnid.uCallbackMessage = TASKBAR_NOTIFY_MESSAGE ;
  tnid.hIcon = hicon ;
  lstrcpyn (tnid.szTip, lpszTip, sizeof(tnid.szTip)) ;

  return (Shell_NotifyIcon (NIM_ADD, &tnid) != 0) ;
}

bool TaskBarModifyIcon (HWND hwnd, UINT uID, LPSTR lpszTip)
{
  NOTIFYICONDATA        tnid ;

  memset (&tnid, 0, sizeof (tnid)) ;
  tnid.cbSize = sizeof(NOTIFYICONDATA) ;
  tnid.hWnd = hwnd ;
  tnid.uID = uID ;
  tnid.uFlags = NIF_TIP ;
  lstrcpyn (tnid.szTip, lpszTip, sizeof(tnid.szTip)) ;

  return (Shell_NotifyIcon (NIM_MODIFY, &tnid) != 0) ;
}

bool TaskBarDeleteIcon (HWND hwnd, UINT uID)
{
  NOTIFYICONDATA        tnid ;

  memset (&tnid, 0, sizeof (tnid)) ;
  tnid.cbSize = sizeof(NOTIFYICONDATA) ;
  tnid.hWnd = hwnd ;
  tnid.uID = uID ;

  return (Shell_NotifyIcon (NIM_DELETE, &tnid) != 0) ;
}

bool ShowRestrictionMessage (char *Message, char *Dir)
{
  int         i ;
  char        str [8192] ;

  // the main window code will expect this to be already set up if it gets a WM_HELP
  hh_aklink.pszKeywords = "I/O Restrictions" ;

  if (Dir == NULL)
  {
    sprintf (str, "%s\nPress the HELP button to learn about I/O Restrictions.", Message) ;
    MessageBox (main_window, str, "I/O Restriction Activated", MB_ICONERROR | MB_OK | MB_HELP) ;
    return (false) ;
  }

  sprintf (str, "%s\n"
                "Press OK to grant temporary read/write permission in that directory (and its subdirectories).\n"
                "Press CANCEL to halt the render.\n\n"
                "Press the HELP button to learn about I/O Restrictions.",
                Message) ;

  if (MessageBox (main_window, str, "I/O Restriction Activated", MB_ICONSTOP | MB_OKCANCEL | MB_HELP | MB_DEFBUTTON2) != IDOK)
    return (false) ;

  for (i = 0 ; i < MAX_DIRSPEC - 1 ; i++)
    if (WriteDirSpecs [i] == NULL)
      break ;
  if (i == MAX_DIRSPEC - 1)
  {
    MessageBox (main_window, "Ran out of room to store directory grant permission", "I/O Restriction Error", MB_ICONERROR | MB_OK) ;
    // return true anyhow
    return (true) ;
  }

  WriteDirSpecs [i] = _strdup (Dir) ;
  return (true) ;
}

}


