1542 lines
53 KiB
C++
1542 lines
53 KiB
C++
/*******************************************************************************
|
|
* 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) ;
|
|
}
|
|
|
|
}
|
|
|
|
|