Windows Printing: dict to DEVMODE to dict

HaO2022-01-31: To store the settings of a Windows printer, the DEVMODE structure must be stored. This structure may be stored as a memory block or as partly meaningful values.

The following sketch is an extract of a Windows printer driver with the DEVMODE structure converted to a dict and back. In the application, parts of the DEVMODE dict may be edited, like orientation and paper size.

There is also an interface to get the allowed values per key and the possible key list.

Enjoy, Harald

#pragma warning(disable : 4201 4214 4514)
#define STRICT
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
// Taget Windows Server 2003
#define WINVER 0x0502
#define _WIN32_WINNT 0x0502
// TCL Defines
#define DLL_BUILD
#define USE_TCL_STUBS

#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <tchar.h>

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "tcl.h"

//------------------------------------------------------------------------------
// >>>> Helper defines
//------------------------------------------------------------------------------
// Values of the Res variable
// Succes, result value set or not necessary
#define RET_OK 0
// Error and result set
#define RET_ERROR -1
// Printer i/o error
#define RET_ERROR_PRINTER_IO -2
// Out of memory error
#define RET_ERROR_MEMORY -3
// Parameter error
#define RET_ERROR_PARAMETER -4
// User abord
#define RET_ERROR_USER -5

//------------------------------------------------------------------------------
// >>>> File Global Variables
static BOOL fg_f_pdlg_initialized = FALSE;
static PRINTDLG fg_pdlg;

// subcommand pagesetup orientation Option list and indexes
static const char *fg_orient_sub_cmds[] = {
    "portrait", "landscape", NULL};
static const short fg_orient_i_command[] = {
    DMORIENT_PORTRAIT, DMORIENT_LANDSCAPE};

// subcommand pagesetup pagesize
static const char *fg_papersize_sub_cmds[] = {
    "Letter", "LetterSmall", "Tabloid", "Ledger", "Legal", "Statement",
    "Executive", "A3", "A4", "A4Small", "A5", "B4", "B5", "Folio", "Quarto",
    "10X14", "11X17", "Note", "Env_9", "Env_10", "Env_11", "Env_12", "Env_14",
    "CSheet", "DSheet", "ESheet", "Env_Dl", "Env_C5", "Env_C3", "Env_C4",
    "Env_C6", "Env_C65", "Env_B4", "Env_B5", "Env_B6", "Env_Italy",
    "Env_Monarch", "Env_Personal", "Fanfold_Us", "Fanfold_Std_German",
    "Fanfold_Lgl_German", "Iso_B4", "Japanese_Postcard", "9X11", "10X11",
    "15X11", "Env_Invite", "Reserved_48", "Reserved_49", "Letter_Extra",
    "Legal_Extra", "Tabloid_Extra", "A4_Extra", "Letter_Transverse",
    "A4_Transverse", "Letter_Extra_Transverse", "A_Plus", "B_Plus",
    "Letter_Plus", "A4_Plus", "A5_Transverse", "B5_Transverse", "A3_Extra",
    "A5_Extra", "B5_Extra", "A2", "A3_Transverse", "A3_Extra_Transverse",
    "Dbl_Japanese_Postcard", "A6", "JEnv_Kaku2", "JEnv_Kaku3", "JEnv_Chou3",
    "JEnv_Chou4", "Letter_Rotated", "A3_Rotated", "A4_Rotated", "A5_Rotated",
    "B4_JIS_Rotated", "B5_JIS_Rotated", "Japanese_Postcard_Rotated",
    "Dbl_Japanese_Postcard_Rotated", "A6_Rotated", "JEnv_Kaku2_Rotated",
    "JEnv_Kaku3_Rotated", "JEnv_Chou3_Rotated", "JEnv_Chou4_Rotated", "B6_JIS",
    "B6_Jis_Rotated", "12X11", "Jenv_You4", "Jenv_You4_Rotated", "P16K", "P32K",
    "P32Kbig", "PEnv_1", "PEnv_2", "PEnv_3", "PEnv_4", "PEnv_5", "PEnv_6",
    "PEnv_7", "PEnv_8", "PEnv_9", "PEnv_10", "P16K_Rotated", "P32K_Rotated",
    "P32Kbig_Rotated", "PEnv_1_Rotated", "PEnv_2_Rotated", "PEnv_3_Rotated",
    "PEnv_4_Rotated", "PEnv_5_Rotated", "PEnv_6_Rotated", "PEnv_7_Rotated",
    "PEnv_8_Rotated", "PEnv_9_Rotated", "PEnv_10_Rotated",
    "User",
    NULL };
static const short fg_papersize_i_command[] = {
    DMPAPER_LETTER,
    DMPAPER_LETTERSMALL,
    DMPAPER_TABLOID,
    DMPAPER_LEDGER,
    DMPAPER_LEGAL,
    DMPAPER_STATEMENT,
    DMPAPER_EXECUTIVE,
    DMPAPER_A3,
    DMPAPER_A4,
    DMPAPER_A4SMALL,
    DMPAPER_A5,
    DMPAPER_B4,
    DMPAPER_B5,
    DMPAPER_FOLIO,
    DMPAPER_QUARTO,
    DMPAPER_10X14,
    DMPAPER_11X17,
    DMPAPER_NOTE,
    DMPAPER_ENV_9,
    DMPAPER_ENV_10,
    DMPAPER_ENV_11,
    DMPAPER_ENV_12,
    DMPAPER_ENV_14,
    DMPAPER_CSHEET,
    DMPAPER_DSHEET,
    DMPAPER_ESHEET,
    DMPAPER_ENV_DL,
    DMPAPER_ENV_C5,
    DMPAPER_ENV_C3,
    DMPAPER_ENV_C4,
    DMPAPER_ENV_C6,
    DMPAPER_ENV_C65,
    DMPAPER_ENV_B4,
    DMPAPER_ENV_B5,
    DMPAPER_ENV_B6,
    DMPAPER_ENV_ITALY,
    DMPAPER_ENV_MONARCH,
    DMPAPER_ENV_PERSONAL,
    DMPAPER_FANFOLD_US,
    DMPAPER_FANFOLD_STD_GERMAN,
    DMPAPER_FANFOLD_LGL_GERMAN,
    DMPAPER_ISO_B4,
    DMPAPER_JAPANESE_POSTCARD,
    DMPAPER_9X11,
    DMPAPER_10X11,
    DMPAPER_15X11,
    DMPAPER_ENV_INVITE,
    DMPAPER_RESERVED_48,
    DMPAPER_RESERVED_49,
    DMPAPER_LETTER_EXTRA,
    DMPAPER_LEGAL_EXTRA,
    DMPAPER_TABLOID_EXTRA,
    DMPAPER_A4_EXTRA,
    DMPAPER_LETTER_TRANSVERSE,
    DMPAPER_A4_TRANSVERSE,
    DMPAPER_LETTER_EXTRA_TRANSVERSE,
    DMPAPER_A_PLUS,
    DMPAPER_B_PLUS,
    DMPAPER_LETTER_PLUS,
    DMPAPER_A4_PLUS,
    DMPAPER_A5_TRANSVERSE,
    DMPAPER_B5_TRANSVERSE,
    DMPAPER_A3_EXTRA,
    DMPAPER_A5_EXTRA,
    DMPAPER_B5_EXTRA,
    DMPAPER_A2,
    DMPAPER_A3_TRANSVERSE,
    DMPAPER_A3_EXTRA_TRANSVERSE,
    DMPAPER_DBL_JAPANESE_POSTCARD,
    DMPAPER_A6,
    DMPAPER_JENV_KAKU2,
    DMPAPER_JENV_KAKU3,
    DMPAPER_JENV_CHOU3,
    DMPAPER_JENV_CHOU4,
    DMPAPER_LETTER_ROTATED,
    DMPAPER_A3_ROTATED,
    DMPAPER_A4_ROTATED,
    DMPAPER_A5_ROTATED,
    DMPAPER_B4_JIS_ROTATED,
    DMPAPER_B5_JIS_ROTATED,
    DMPAPER_JAPANESE_POSTCARD_ROTATED,
    DMPAPER_DBL_JAPANESE_POSTCARD_ROTATED,
    DMPAPER_A6_ROTATED,
    DMPAPER_JENV_KAKU2_ROTATED,
    DMPAPER_JENV_KAKU3_ROTATED,
    DMPAPER_JENV_CHOU3_ROTATED,
    DMPAPER_JENV_CHOU4_ROTATED,
    DMPAPER_B6_JIS,
    DMPAPER_B6_JIS_ROTATED,
    DMPAPER_12X11,
    DMPAPER_JENV_YOU4,
    DMPAPER_JENV_YOU4_ROTATED,
    DMPAPER_P16K,
    DMPAPER_P32K,
    DMPAPER_P32KBIG,
    DMPAPER_PENV_1,
    DMPAPER_PENV_2,
    DMPAPER_PENV_3,
    DMPAPER_PENV_4,
    DMPAPER_PENV_5,
    DMPAPER_PENV_6,
    DMPAPER_PENV_7,
    DMPAPER_PENV_8,
    DMPAPER_PENV_9,
    DMPAPER_PENV_10,
    DMPAPER_P16K_ROTATED,
    DMPAPER_P32K_ROTATED,
    DMPAPER_P32KBIG_ROTATED,
    DMPAPER_PENV_1_ROTATED,
    DMPAPER_PENV_2_ROTATED,
    DMPAPER_PENV_3_ROTATED,
    DMPAPER_PENV_4_ROTATED,
    DMPAPER_PENV_5_ROTATED,
    DMPAPER_PENV_6_ROTATED,
    DMPAPER_PENV_7_ROTATED,
    DMPAPER_PENV_8_ROTATED,
    DMPAPER_PENV_9_ROTATED,
    DMPAPER_PENV_10_ROTATED,
    DMPAPER_USER
};

// subcommand pagesetup default source Option list and indexes
// Additional user values may be coded by positive integers
static const char *fg_default_source_sub_cmds[] = {
    "upper", "onlyone", "lower", "middle", "manual",
    "envelope", "envmanual", "auto", "tractor", "smallfmt",
    "largefmt", "largecapacity", "cassette", "formsource", NULL};
static const short fg_default_source_i_command[] = {
    DMBIN_UPPER, DMBIN_ONLYONE, DMBIN_LOWER, DMBIN_MIDDLE, DMBIN_MANUAL,
    DMBIN_ENVELOPE, DMBIN_ENVMANUAL, DMBIN_AUTO, DMBIN_TRACTOR, DMBIN_SMALLFMT,
    DMBIN_LARGEFMT, DMBIN_LARGECAPACITY, DMBIN_CASSETTE, DMBIN_FORMSOURCE};

// subcommand pagesetup print quality Option list and indexes
// Additional x resolution may be specified in DPI by positive values.
static const char *fg_print_quality_sub_cmds[] = {
    "high", "medium", "low", "draft", NULL};
static const short fg_print_quality_i_command[] = {
    DMRES_HIGH, DMRES_MEDIUM, DMRES_LOW, DMRES_DRAFT};
// subcommand pagesetup print color list and indexes
static const char *fg_color_sub_cmds[] = {
    "color", "monochrome", NULL};
static const short fg_color_i_command[] = {
    DMCOLOR_COLOR, DMCOLOR_MONOCHROME};
// subcommand pagesetup duplex print list and indexes
static const char *fg_duplex_sub_cmds[] = {
    "simplex", "vertical", "horizontal", NULL};
static const short fg_duplex_i_command[] = {
    DMDUP_SIMPLEX, DMDUP_VERTICAL, DMDUP_HORIZONTAL};
// subcommand pagesetup TTOptions list and indexes
static const char *fg_tt_option_sub_cmds[] = {
    "bitmap", "download", "subdev", "downloadoutline", NULL};
static const short fg_tt_option_i_command[] = {
    DMTT_BITMAP, DMTT_DOWNLOAD, DMTT_SUBDEV, DMTT_DOWNLOAD_OUTLINE};
// subcommand pagesetup NUp list and indexes
static const char *fg_n_up_sub_cmds[] = {
    "system", "oneup", NULL};
static const DWORD fg_n_up_i_command[] = {
    DMNUP_SYSTEM, DMNUP_ONEUP};
// subcommand pagesetup ICMMethod list and indexes
static const char *fg_icm_method_sub_cmds[] = {
    "none", "system","driver",
    "device", NULL};
static const DWORD fg_icm_method_i_command[] = {
    DMICMMETHOD_NONE, DMICMMETHOD_SYSTEM, DMICMMETHOD_DRIVER,
    DMICMMETHOD_DEVICE};
// subcommand pagesetup ICMIntent list and indexes
static const char *fg_icm_intent_sub_cmds[] = {
    "saturate", "contrast","colorimetric", "abscolorimetric", NULL};
static const DWORD fg_icm_intent_i_command[] = {
    DMICM_SATURATE, DMICM_CONTRAST, DMICM_COLORIMETRIC, DMICM_ABS_COLORIMETRIC};
// subcommand pagesetup MediaType list and indexes
static const char *fg_media_type_sub_cmds[] = {
    "standard", "transparency","glossy", NULL};
static const DWORD fg_media_type_i_command[] = {
    DMMEDIA_STANDARD, DMMEDIA_TRANSPARENCY, DMMEDIA_GLOSSY};
// subcommand pagesetup DitherType list and indexes
static const char *fg_dither_type_sub_cmds[] = {
    "none", "coarse", "fine", "lineart",
    "errordiffusion", "reserved6", "reserved7",
    "reserved8", "reserved9", "grayscale", NULL};
static const DWORD fg_dither_type_i_command[] = {
    DMDITHER_NONE, DMDITHER_COARSE, DMDITHER_FINE, DMDITHER_LINEART,
    DMDITHER_ERRORDIFFUSION, DMDITHER_RESERVED6, DMDITHER_RESERVED7,
    DMDITHER_RESERVED8, DMDITHER_RESERVED9, DMDITHER_GRAYSCALE};

// List of device dict index indexes
enum fg_device_dict_index {
    dli_devicename=0,
    dli_driverversion=1,
    dli_orientation=2,
    dli_papersize=3,
    dli_paperlength=4,
    dli_paperwidth=5,
    dli_scale=6,
    dli_copies=7,
    dli_defaultsource=8,
    dli_printquality=9,
    dli_color=10,
    dli_duplex=11,
    dli_yresolution=12,
    dli_ttoption=13,
    dli_collate=14,
    dli_formname=15,
    dli_nup=16,
    dli_icmmethod=17,
    dli_icmintent=18,
    dli_mediatype=19,
    dli_dithertype=20,
    dli_driverextra=21,
};
// List of devicedict indexes
// Indexes are in enum fg_device_dict_index
static const char *fg_device_index[] = {
    "devicename",       // 0
    "driverversion",    // 1
    "orientation",      // 2
    "papersize",        // 3
    "paperlength",      // 4
    "paperwidth",       // 5
    "scale",            // 6
    "copies",           // 7
    "defaultsource",    // 8
    "printquality",     // 9
    "color",            // 10
    "duplex",           // 11
    "yresolution",      // 12
    "truetypeoption",   // 13
    "collate",          // 14
    "formname",         // 15
    "nup",              // 16
    "icmmethod",        // 17
    "icmintent",        // 18
    "mediatype",        // 19
    "dithertype",       // 20
    "driverextra",      // 21
    NULL
};

enum fg_device_dict_spec_type {
    dds_list=0,
    dds_num=1,
    dds_float=2,
    dds_listnum=3,
    dds_binary=4,
    dds_yn=5,
    dds_text=6
};

static const char *fg_device_dict_spec_type_text[] = {
    "list",      // 0
    "num",        // 1
    "float",      // 2
    "listnum",       // 3
    "binary",        // 4
    "yn",           // 5
    "text"          // 6
};

struct DeviceDictChoice {
    enum fg_device_dict_spec_type Type;
    int Min;
    int Division;
    const char **Choices;
};

enum dds_max {
    dds_max_short=0,
    dds_max_word=1,
    dds_max_dword=2
};

static const long fg_dds_max_values[] = {
    32767,      // 0 SHORT
    65535,      // 1 WORD
    4294967,    // 2 DWORD
};

static const struct DeviceDictChoice fg_device_dict_choices[] = {
    {dds_text, 0, CCHDEVICENAME, NULL},                 // 0 DEVICENAME
    {dds_num, 0, dds_max_word, NULL},                   // 1 DRIVERVERSION
    {dds_list, 0, 0, fg_orient_sub_cmds},               // 2 ORIENTATION
    {dds_list, 0, 0, fg_papersize_sub_cmds},            // 3 PAPERSIZE
    {dds_float, 0, 10, NULL},                           // 4 PAPERLENGTH
    {dds_float, 0, 10, NULL},                           // 5 PAPERWODTH
    {dds_float, 0, 100, NULL},                          // 6 SCALE
    {dds_num, 0, dds_max_short, NULL},                  // 7 COPIES
    {dds_list, 0, 0, fg_default_source_sub_cmds},       // 8 DEFAULTSOURCE
    {dds_listnum, 0, 0, fg_print_quality_sub_cmds},     // 9 PRINTQUALITY
    {dds_list, 0, 0, fg_color_sub_cmds},                // 10 COLOR
    {dds_list, 0, 0, fg_duplex_sub_cmds},               // 11 DUPLEX
    {dds_num, 0, dds_max_short, NULL},                  // 12 YRESOLUTION
    {dds_list, 0, 0, fg_tt_option_sub_cmds},            // 13 TRUETYPEOPTION
    {dds_yn, 0, 0, NULL},                               // 14 COLLATE
    {dds_text,0,CCHFORMNAME, NULL},                     // 15 FORMNAME
    {dds_listnum,256, dds_max_dword, fg_n_up_sub_cmds}, // 16 NUP
    {dds_listnum,256, dds_max_dword, fg_icm_method_sub_cmds},// 17 ICMMETHOD
    {dds_listnum,256, dds_max_dword, fg_icm_intent_sub_cmds},// 18 ICMINTENT
    {dds_listnum,256, dds_max_dword, fg_media_type_sub_cmds},// 19 MEDIATYPE
    {dds_listnum,256, dds_max_dword, fg_dither_type_sub_cmds},// 20 DITHERTYPE
    {dds_binary,0, 0, NULL}                             // 21 DRIVEREXTRA
};

//------------------------------------------------------------------------------
// >>> local Prototypes
static int WinPrintCmd(ClientData clientData, Tcl_Interp *interp,
                int objc, Tcl_Obj *CONST objv[]);
static TCHAR * ReturnLockedDeviceName( HGLOBAL hDevNames );
static char GetDeviceName(
    Tcl_Interp *interp,
    HGLOBAL hDevNames,
    char Flags );
static char PrintPrinterSetup( Tcl_Interp *interp, TCHAR *pPrinter,
    Tcl_Obj * pDeviceDict);
static char CreateDevMode( Tcl_Interp *interp, TCHAR * pPrinter,
    Tcl_Obj * pDeviceDict, int fShowPropertySheet,
    int fDictOnlySameDriverVersion);
static char ListChoices(Tcl_Interp *interp, const char *ppChoiceList[]);
static char DeviceDictSpecGet(Tcl_Interp *interp, char *KeyIn);
static char LoadDefaultPrinter( );

//------------------------------------------------------------------------------
// >>>>> DLL entry point
//------------------------------------------------------------------------------
BOOL __declspec(dllexport) WINAPI DllEntryPoint(
    HINSTANCE hInstance,
    DWORD seginfo,
    LPVOID lpCmdLine)
{
  /* Don't do anything, so just return true */
  return TRUE;
}
//------------------------------------------------------------------------------
// >>>>> Initialisation Procedures
//------------------------------------------------------------------------------
int __declspec(dllexport) Winprint_Init (Tcl_Interp *Interp)
{
    if (Tcl_InitStubs(Interp, "8.1", 0) == NULL)
    {
        return TCL_ERROR;
    }
    Tcl_CreateObjCommand(Interp, "winprint", WinPrintCmd, (ClientData)NULL,
        (Tcl_CmdDeleteProc *)NULL);
    Tcl_PkgProvide (Interp, "winprint", "1.0");
    return TCL_OK;
}
//------------------------------------------------------------------------------
// Unload Procedures
//------------------------------------------------------------------------------
int __declspec(dllexport) Winprint_Unload (Tcl_Interp *Interp, int flags)
{
    PrintReset( 0 );
    return TCL_OK;
}
//------------------------------------------------------------------------------
// >>>>> Called routine
//------------------------------------------------------------------------------
// Decode tcl commands
int WinPrintCmd(ClientData unused, Tcl_Interp *interp, int objc,
             Tcl_Obj *CONST objv[])
{
    // Option list and indexes
    const char *subCmds[] = {
        "devicedictkeys", "devicedictspec", "printersetup"
        NULL};
    enum iCommand {
        iDeviceDictKeys, iDeviceDictSpec, iPrinterSetup,
        };

    // State variables
    // choice of option
    int Index;
    // Result flag
    char Res;
    Tcl_Obj *resultPtr;
    Tcl_DString sPar1;
    //--------------------------------------------------------------------------
    // > Check if option argument is given and decode it
    if (objc > 1)
    {
        if (TCL_ERROR ==
            Tcl_GetIndexFromObj(interp, objv[1], subCmds, "subcmd", 0, &Index))
            return TCL_ERROR;
    } else {
        Tcl_WrongNumArgs(interp, 1, objv, "subcmd");
        return TCL_ERROR;
    }
    //--------------------------------------------------------------------------
    // > Check parameters and give usage messages
    switch (Index) {
    case iDeviceDictSpec:
        if (objc != 3)
        {
            Tcl_WrongNumArgs(interp, 2, objv, "argument");
            return TCL_ERROR;
        }
        break;
    }
    //--------------------------------------------------------------------------
    // > Default result
    Res = RET_OK;
    //--------------------------------------------------------------------------
    // >> Intermediate decode tasks
    //--------------------------------------------------------------------------
    // > One String parameter
    // if this option is not given, a 0 pointer is present.
    Tcl_DStringInit(& sPar1);
    switch (Index) {
    case iPrinterSetup:
        if ( objc > 2 ) {
            char *pStr;
            int lStr;
            pStr = Tcl_GetStringFromObj(objv[2],&lStr);
            Tcl_WinUtfToTChar( pStr, lStr, &sPar1);
        } else {
            // As we need two 0's for a wide character to contain a zero,
            // we set this here.
            // The default single zero was interpreted as data.
            Tcl_WinUtfToTChar("", 0, &sPar1);
        }
    }
    //--------------------------------------------------------------------------
    // >> Decode parameters and invoke
    //--------------------------------------------------------------------------
    switch (Index) {
    case iDeviceDictKeys:
        return ListChoices( interp, fg_device_index );
        break;
    case iDeviceDictSpec:
        Res = DeviceDictSpecGet(interp, Tcl_GetStringFromObj(objv[2],NULL));
        break;
    case iPrinterSetup:
        {
            unsigned short MaxPage;
            double Double;
            Tcl_Obj * pDeviceDict;
            TCHAR *pPrinter;
            // > Argument 2: Printer is already in sPar or NULL
            // It is critical to check it for length, as the leading 0
            // is not sufficient, as TCHAR is 16 bit and two 0's are needed
            pPrinter = (TCHAR *)Tcl_DStringValue(& sPar1);

            // > PrinterSettingDict
            if ( objc > 3 )
                pDeviceDict = objv[3];
            else
                pDeviceDict = NULL;

            switch (Index) {
            case iPrinterSetup:
                if ( Res == RET_OK )
                    Res = PrintPrinterSetup( interp, pPrinter, pDeviceDict);
                break;
        }
        break;
    }
    //--------------------------------------------------------------------------
    // >> Free any intermediated strings
    //--------------------------------------------------------------------------
    // String parameter
    Tcl_DStringFree(& sPar1);
    //----------------------------------------------------------------------
    // >> format return value
    //----------------------------------------------------------------------
        resultPtr = Tcl_GetObjResult(interp);
    switch (Res)
    {
    case RET_OK:
        return TCL_OK;
    case RET_ERROR_PRINTER_IO:
        Tcl_SetStringObj( resultPtr, "Printer I/O error",-1);
        return TCL_ERROR;
    case RET_ERROR_MEMORY:
        Tcl_SetStringObj( resultPtr, "Out of memory",-1);
        return TCL_ERROR;
    case RET_ERROR_PARAMETER:
        Tcl_SetStringObj( resultPtr, "Wrong parameter",-1);
        return TCL_ERROR;
    case RET_ERROR_USER:
        Tcl_SetStringObj( resultPtr, "User abort",-1);
        return TCL_ERROR;
    default:
    case RET_ERROR:
        return TCL_ERROR;
    }
}
//------------------------------------------------------------------------------
// >>>>>> ReturnLockedDeviceName
//------------------------------------------------------------------------------
// Extract the device name from the hDevNames structure and return its pointer.
// > hDevNames must be unlocked un succes (which looses the return value).
static TCHAR * ReturnLockedDeviceName( HGLOBAL hDevNames )
{
    LPDEVNAMES pDevNames;
    pDevNames = (LPDEVNAMES) GlobalLock( hDevNames );
    if ( NULL == pDevNames )
        return NULL;
    if ( pDevNames->wDeviceOffset == 0)
    {
        GlobalUnlock( hDevNames );
        return NULL;
    }
    return ( (TCHAR *) pDevNames ) + ( pDevNames->wDeviceOffset );
}
//------------------------------------------------------------------------------
// >>>>> GetShortListStringObj
//------------------------------------------------------------------------------
static Tcl_Obj *GetShortListStringObj(
        short ValueCur, const short pValues[], const char *pStrings[])
{
    int IndexCur;
    for (IndexCur = 0; pStrings[IndexCur] != NULL ; IndexCur++) {
        if ( ValueCur == pValues[IndexCur] )
            return Tcl_NewStringObj(pStrings[IndexCur], -1 );
    }
    return NULL;
}
//------------------------------------------------------------------------------
// >>>>> GetDWordListStringObj
//------------------------------------------------------------------------------
static Tcl_Obj *GetDWordListStringObj(
        DWORD ValueCur, const DWORD pValues[], const char *pStrings[])
{
    int IndexCur;
    for (IndexCur = 0; pStrings[IndexCur] != NULL ; IndexCur++) {
        if ( ValueCur == pValues[IndexCur] )
            return Tcl_NewStringObj(pStrings[IndexCur], -1 );
    }
    return NULL;
}
//------------------------------------------------------------------------------
// >>>>> GetWideStringObj
//------------------------------------------------------------------------------
static Tcl_Obj * GetWideStringObj( WCHAR *pWideString, int CharacterCountMax)
{
    int LengthCur, PosCur;
    Tcl_DString Recoded;
    Tcl_Obj * pObj;
    // Find length of DeviceName
    LengthCur = CharacterCountMax;
    for (PosCur = 0; PosCur < CharacterCountMax; PosCur++) {
        if (pWideString[PosCur] == 0) {
            LengthCur = PosCur;
            break;
        }
    }
    Tcl_DStringInit(&Recoded);
    Tcl_WinTCharToUtf(pWideString, LengthCur*sizeof(WCHAR), &Recoded);
    pObj = Tcl_NewStringObj(
            Tcl_DStringValue(&Recoded),
            Tcl_DStringLength(&Recoded));
    Tcl_DStringFree(&Recoded);
    return pObj;
}
//------------------------------------------------------------------------------
// >>>>> DevmodeToDeviceDict
//------------------------------------------------------------------------------
static char DevmodeToDeviceDict(
        Tcl_Interp *interp, DEVMODE * pDevMode, Tcl_Obj *pDeviceDict)
{
    Tcl_Obj *pObj;
    int fUser = 0;
    //--------------------------------------------------------------------------
    // DeviceName
    if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_devicename],-1),
                GetWideStringObj(pDevMode->dmDeviceName, CCHDEVICENAME)))
        return RET_ERROR;
    //--------------------------------------------------------------------------
    // DriverVersion
    if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_driverversion],-1),
            Tcl_NewIntObj(pDevMode->dmDriverVersion) ))
        return RET_ERROR;
    //--------------------------------------------------------------------------
    // > Orientation
    if ( (pDevMode->dmFields & DM_ORIENTATION) != 0) {
        pObj = GetShortListStringObj(pDevMode->dmOrientation,
                fg_orient_i_command, fg_orient_sub_cmds);
        if (    pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_orientation],-1),pObj ))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // > PaperSize
    if ( (pDevMode->dmFields & DM_PAPERSIZE) != 0) {
        pObj = GetShortListStringObj(pDevMode->dmPaperSize,
                fg_papersize_i_command, fg_papersize_sub_cmds);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_papersize], -1), pObj))
                return RET_ERROR;
            // Remember a user size to get width and height even if not flagged
            fUser = (pDevMode->dmPaperSize == DMPAPER_USER);
        }
    }
    //--------------------------------------------------------------------------
    // Append Paper Length and Width. They are only senseful for PaperSize
    // equal "User". So, take the value if paper = user and the value is
    // senseful, e.g. positivie and above 1mm (10)
    if ( pDevMode->dmPaperLength > 10
            && (pDevMode->dmFields & DM_PAPERLENGTH || fUser)) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_paperlength],-1),
                Tcl_NewDoubleObj(pDevMode->dmPaperLength/10.0)))
            return RET_ERROR;
    }
    if ( pDevMode->dmPaperWidth > 10
            && (pDevMode->dmFields  & DM_PAPERWIDTH || fUser)) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_paperwidth],-1),
                Tcl_NewDoubleObj(pDevMode->dmPaperWidth/10.0)))
            return RET_ERROR;
    }
    //--------------------------------------------------------------------------
    // Append Scale factor
    if ( pDevMode->dmScale > 0 && (pDevMode->dmFields & DM_SCALE) != 0) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_scale],-1),
                Tcl_NewDoubleObj(pDevMode->dmScale/100.0) ))
            return RET_ERROR;
    }
    //--------------------------------------------------------------------------
    // Append copies
    if ( pDevMode->dmCopies > 0 && (pDevMode->dmFields & DM_COPIES) != 0) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_copies],-1),
                Tcl_NewIntObj(pDevMode->dmCopies) ))
            return RET_ERROR;
    }
    //--------------------------------------------------------------------------
    // Append defaultsource
    if ( (pDevMode->dmFields & DM_DEFAULTSOURCE) != 0) {
        pObj = GetShortListStringObj(pDevMode->dmDefaultSource,
                fg_default_source_i_command, fg_default_source_sub_cmds);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_defaultsource],-1),
                        pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append printquality
    if ( (pDevMode->dmFields & DM_PRINTQUALITY) != 0) {
        if (pDevMode->dmPrintQuality > 0) {
            // This is an X Dimension DPI Value
            pObj = Tcl_NewIntObj( pDevMode->dmPrintQuality );
        } else {
            pObj = GetShortListStringObj(pDevMode->dmPrintQuality,
                    fg_print_quality_i_command, fg_print_quality_sub_cmds);
        }
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_printquality],-1),
                        pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append Color
    if ( (pDevMode->dmFields & DM_COLOR) != 0) {
        pObj = GetShortListStringObj(pDevMode->dmColor,
                fg_color_i_command, fg_color_sub_cmds);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_color],-1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append Duplex
    if ( (pDevMode->dmFields & DM_DUPLEX) != 0) {
        pObj = GetShortListStringObj(pDevMode->dmDuplex,
                fg_duplex_i_command, fg_duplex_sub_cmds);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_duplex],-1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append YResolution
    if ( pDevMode->dmYResolution > 0
            && (pDevMode->dmFields & DM_YRESOLUTION) != 0) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_yresolution],-1),
                Tcl_NewIntObj(pDevMode->dmYResolution)))
            return RET_ERROR;
    }
    //--------------------------------------------------------------------------
    // Append TTOption
    if ( (pDevMode->dmFields & DM_TTOPTION) != 0) {
        pObj = GetShortListStringObj(pDevMode->dmTTOption,
                fg_tt_option_i_command, fg_tt_option_sub_cmds);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_ttoption],-1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append Collate
    if ( (pDevMode->dmFields & DM_COLLATE) != 0
            && ( pDevMode->dmCollate == DMCOLLATE_TRUE
                || pDevMode->dmCollate == DMCOLLATE_FALSE ) ) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_collate],-1),
                Tcl_NewBooleanObj(pDevMode->dmCollate == DMCOLLATE_TRUE)))
            return RET_ERROR;
    }
    //--------------------------------------------------------------------------
    // Append FormName
    if ( (pDevMode->dmFields & DM_FORMNAME) != 0) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_formname],-1),
                GetWideStringObj(pDevMode->dmFormName, CCHFORMNAME)))
            return RET_ERROR;
    }
    //--------------------------------------------------------------------------
    // Append Nup
    if ( (pDevMode->dmFields & DM_NUP) != 0) {
        pObj = GetDWordListStringObj(pDevMode->dmNup,
                fg_n_up_i_command, fg_n_up_sub_cmds);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_nup],-1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append ICMMethod
    if ( (pDevMode->dmFields & DM_ICMMETHOD) != 0) {
        pObj = GetDWordListStringObj(pDevMode->dmICMMethod,
                fg_icm_method_i_command, fg_icm_method_sub_cmds);
        if ( pObj == NULL && pDevMode->dmICMMethod >= 256)
            pObj = Tcl_NewLongObj(pDevMode->dmICMMethod);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_icmmethod],-1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append ICMIntent
    if ( (pDevMode->dmFields & DM_ICMINTENT) != 0) {
        pObj = GetDWordListStringObj(pDevMode->dmICMIntent,
                fg_icm_intent_i_command, fg_icm_intent_sub_cmds);
        if ( pObj == NULL && pDevMode->dmICMIntent >= 256 )
            pObj = Tcl_NewLongObj(pDevMode->dmICMIntent);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_icmintent],-1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append MediaType
    if ( (pDevMode->dmFields & DM_MEDIATYPE) != 0) {
        pObj = GetDWordListStringObj(pDevMode->dmMediaType,
                fg_media_type_i_command, fg_media_type_sub_cmds);
        if ( pObj == NULL && pDevMode->dmMediaType >= 256)
            pObj = Tcl_NewLongObj(pDevMode->dmMediaType);
        if ( pObj != NULL ) {
            if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                    Tcl_NewStringObj(fg_device_index[dli_mediatype],-1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append DitherType
    if ((pDevMode->dmFields & DM_DITHERTYPE) != 0) {
        pObj = GetDWordListStringObj(pDevMode->dmDitherType,
            fg_dither_type_i_command, fg_dither_type_sub_cmds);
        if (pObj == NULL && pDevMode->dmDitherType >= 256)
            pObj = Tcl_NewLongObj(pDevMode->dmDitherType);
        if ( pObj != NULL ) {
            if (TCL_OK != Tcl_DictObjPut(interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_dithertype], -1), pObj))
                return RET_ERROR;
        }
    }
    //--------------------------------------------------------------------------
    // Append Driver private blob
    if ( pDevMode->dmDriverExtra > 0 ) {
        if ( TCL_OK != Tcl_DictObjPut( interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_driverextra],-1),
                Tcl_NewByteArrayObj(((BYTE *) pDevMode) + (pDevMode->dmSize),
                    pDevMode->dmDriverExtra)))
            return RET_ERROR;
    }
    //--------------------------------------------------------------------------
    return RET_OK;
}
//------------------------------------------------------------------------------
// >>>>> DeviceDictToDevmode
//------------------------------------------------------------------------------
static char DeviceDictToDevmode(Tcl_Interp *interp, Tcl_Obj *pDeviceDict,
        DEVMODE * pDevMode, int fDictOnlySameDriverVersion)
{
    Tcl_Obj *pObj;
    int IndexCur;
    int IntCur;
    double DoubleCur;
    int fSameDriverVersionOrNotVerified = 1;
    int fDriverVersionVerified = 0;
    //--------------------------------------------------------------------------
    // > Initially, there are no fields set
    // pDevMode->dmFields = 0
    //--------------------------------------------------------------------------
    // Check Device Name
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_devicename],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        Tcl_Obj * pObjDevice;
        int DeviceNameLengthDict, DeviceNameLengthDevice;
        char * DeviceNameDict, * DeviceNameDevice;
        pObjDevice = GetWideStringObj(pDevMode->dmDeviceName, CCHDEVICENAME);
        DeviceNameDict = Tcl_GetStringFromObj(pObj, &DeviceNameLengthDict);
        DeviceNameDevice = Tcl_GetStringFromObj(pObjDevice,
                &DeviceNameLengthDevice);

        if (    DeviceNameLengthDevice == DeviceNameLengthDict
                && 0 == memcmp (DeviceNameDict, DeviceNameDevice,
                    DeviceNameLengthDevice))
            fDriverVersionVerified = 1;
        else
            fSameDriverVersionOrNotVerified = 0;
    }
    //--------------------------------------------------------------------------
    // Check Driver Version
    // Check if same printer name
    if (fDriverVersionVerified) {
        int VersionDict;
        if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
                Tcl_NewStringObj(fg_device_index[dli_driverversion],-1), &pObj))
            return RET_ERROR;
        if (pObj == NULL) {
            fDriverVersionVerified = 0;
        } else {
            if (TCL_OK != Tcl_GetIntFromObj(interp, pObj,&VersionDict))
                return RET_ERROR;
            if (VersionDict != pDevMode->dmDriverVersion ) {
                fSameDriverVersionOrNotVerified = 0;
                fDriverVersionVerified = 0;
            }
        }
    }
    //--------------------------------------------------------------------------
    // > Orientation
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_orientation],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_orient_sub_cmds, fg_device_index[dli_orientation], TCL_EXACT,
                &IndexCur))
            return RET_ERROR;

        pDevMode->dmOrientation = fg_orient_i_command[IndexCur];
        pDevMode->dmFields |= DM_ORIENTATION;
    }
    //--------------------------------------------------------------------------
    // > PaperSize
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_papersize],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_papersize_sub_cmds, fg_device_index[dli_papersize],
                TCL_EXACT, &IndexCur))
            return RET_ERROR;

        pDevMode->dmPaperSize = fg_papersize_i_command[IndexCur];
        pDevMode->dmFields |= DM_PAPERSIZE;
    }
    //--------------------------------------------------------------------------
    // Append Paper Length and Width. They are only senseful for PaperSize
    // equal "User".
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_paperlength],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetDoubleFromObj( interp, pObj, &DoubleCur))
            return RET_ERROR;

        pDevMode->dmPaperLength = (short)(floor(DoubleCur * 10+0.5));
        pDevMode->dmFields |= DM_PAPERLENGTH;
    }
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_paperwidth],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetDoubleFromObj( interp, pObj, &DoubleCur))
            return RET_ERROR;

        pDevMode->dmPaperWidth = (short)(floor(DoubleCur * 10+0.5));
        pDevMode->dmFields |= DM_PAPERWIDTH;
    }
    //--------------------------------------------------------------------------
    // > Get out here, if other driver version and this is checked.
    // > The following settings may not fit to the changed driver.
    if (!fSameDriverVersionOrNotVerified && fDictOnlySameDriverVersion)
        return RET_OK;
    //--------------------------------------------------------------------------
    // Append Scale factor
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_scale],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetDoubleFromObj( interp, pObj, &DoubleCur))
            return RET_ERROR;

        pDevMode->dmScale = (short)(floor(DoubleCur * 100+0.5));
        pDevMode->dmFields |= DM_SCALE;
    }
    //--------------------------------------------------------------------------
    // Append copies
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_copies],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIntFromObj( interp, pObj, &IntCur))
            return RET_ERROR;

        pDevMode->dmCopies = (short)IntCur;
        pDevMode->dmFields |= DM_COPIES;
    }
    //--------------------------------------------------------------------------
    // Append defaultsource
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_defaultsource],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_default_source_sub_cmds, fg_device_index[dli_defaultsource],
                TCL_EXACT, &IndexCur))
            return RET_ERROR;

        pDevMode->dmDefaultSource = fg_default_source_i_command[IndexCur];
        pDevMode->dmFields |= DM_DEFAULTSOURCE;
    }
    //--------------------------------------------------------------------------
    // Append printquality
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_printquality],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_print_quality_sub_cmds, fg_device_index[dli_printquality],
                TCL_EXACT, &IndexCur))
        {
            // Not in list-> check for int
            if (TCL_OK != Tcl_GetIntFromObj( interp, pObj, &IntCur))
                return RET_ERROR;
            pDevMode->dmPrintQuality = (short)IntCur;
        } else {
            pDevMode->dmPrintQuality = fg_print_quality_i_command[IndexCur];
        }
        pDevMode->dmFields |= DM_PRINTQUALITY;
    }
    //--------------------------------------------------------------------------
    // Append Color
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_color],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_color_sub_cmds, fg_device_index[dli_color],
                TCL_EXACT, &IndexCur))
            return RET_ERROR;

        pDevMode->dmColor = fg_color_i_command[IndexCur];
        pDevMode->dmFields |= DM_COLOR;
    }
    //--------------------------------------------------------------------------
    // Append Duplex
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_duplex],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_duplex_sub_cmds, fg_device_index[dli_duplex],
                TCL_EXACT, &IndexCur))
            return RET_ERROR;

        pDevMode->dmDuplex = fg_color_i_command[IndexCur];
        pDevMode->dmFields |= DM_DUPLEX;
    }
    //--------------------------------------------------------------------------
    // Append YResolution
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_yresolution],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIntFromObj( interp, pObj, &IntCur))
            return RET_ERROR;

        pDevMode->dmYResolution = (short)IntCur;
        pDevMode->dmFields |= DM_YRESOLUTION;
    }
    //--------------------------------------------------------------------------
    // Append TTOption
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_ttoption],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_tt_option_sub_cmds, fg_device_index[dli_ttoption],
                TCL_EXACT, &IndexCur))
            return RET_ERROR;

        pDevMode->dmTTOption = fg_tt_option_i_command[IndexCur];
        pDevMode->dmFields |= DM_TTOPTION;
    }
    //--------------------------------------------------------------------------
    // Append Collate
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_collate],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetBooleanFromObj( interp, pObj, &IntCur))
            return RET_ERROR;

        if (IntCur)
            pDevMode->dmCollate = DMCOLLATE_TRUE;
        else
            pDevMode->dmCollate = DMCOLLATE_FALSE;
        pDevMode->dmFields |= DM_COLLATE;
    }
    //--------------------------------------------------------------------------
    // Append FormName
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_formname],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        char * StrCur;
        int LengthCur;
        Tcl_DString Recoded;
        // Recode to UTF-16
        Tcl_DStringInit(&Recoded);
        StrCur = Tcl_GetStringFromObj(pObj, &LengthCur);
        Tcl_WinUtfToTChar(StrCur,LengthCur,&Recoded);
        // Pad to right length
        while (Tcl_DStringLength(&Recoded) < sizeof(WCHAR) * CCHFORMNAME)
            Tcl_DStringAppend(&Recoded, "\0\0", 2);
        // Truncate overflow bytes
        Tcl_DStringSetLength(&Recoded, sizeof(WCHAR) * CCHFORMNAME);

        memcpy(pDevMode->dmFormName, Tcl_DStringValue(&Recoded),
            sizeof(WCHAR) * CCHFORMNAME);
        pDevMode->dmFields |= DM_COLLATE;
    }
    //--------------------------------------------------------------------------
    // Append Nup
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_nup],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_n_up_sub_cmds, fg_device_index[dli_nup],
                TCL_EXACT, &IndexCur))
            return RET_ERROR;

        pDevMode->dmNup = fg_tt_option_i_command[IndexCur];
        pDevMode->dmFields |= DM_NUP;
    }
    //--------------------------------------------------------------------------
    // Append ICMMethod
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_icmmethod],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_icm_method_sub_cmds, fg_device_index[dli_icmmethod],
                TCL_EXACT, &IndexCur))
        {
            // Not in list-> check for int
            if (TCL_OK != Tcl_GetIntFromObj( interp, pObj, &IntCur))
                return RET_ERROR;
            pDevMode->dmICMMethod = (DWORD)IntCur;
        } else {
            pDevMode->dmICMMethod = fg_icm_method_i_command[IndexCur];
        }
        pDevMode->dmFields |= DM_ICMMETHOD;
    }
    //--------------------------------------------------------------------------
    // Append ICMIntent
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_icmintent],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_icm_intent_sub_cmds, fg_device_index[dli_icmintent],
                TCL_EXACT, &IndexCur))
        {
            // Not in list-> check for int
            if (TCL_OK != Tcl_GetIntFromObj( interp, pObj, &IntCur))
                return RET_ERROR;
            pDevMode->dmICMIntent = (DWORD)IntCur;
        } else {
            pDevMode->dmICMIntent = fg_icm_intent_i_command[IndexCur];
        }
        pDevMode->dmFields |= DM_ICMINTENT;
    }
    //--------------------------------------------------------------------------
    // Append MediaType
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_mediatype],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj( interp, pObj,
                fg_media_type_sub_cmds, fg_device_index[dli_mediatype],
                TCL_EXACT, &IndexCur))
        {
            // Not in list-> check for int
            if (TCL_OK != Tcl_GetIntFromObj( interp, pObj, &IntCur))
                return RET_ERROR;
            pDevMode->dmMediaType = (DWORD)IntCur;
        } else {
            pDevMode->dmMediaType = fg_media_type_i_command[IndexCur];
        }
        pDevMode->dmFields |= DM_MEDIATYPE;
    }
    //--------------------------------------------------------------------------
    // Append DitherType
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
        Tcl_NewStringObj(fg_device_index[dli_dithertype], -1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        if (TCL_OK != Tcl_GetIndexFromObj(interp, pObj,
            fg_dither_type_sub_cmds, fg_device_index[dli_dithertype],
            TCL_EXACT, &IndexCur))
        {
            // Not in list-> check for int
            if (TCL_OK != Tcl_GetIntFromObj(interp, pObj, &IntCur))
                return RET_ERROR;
            pDevMode->dmDitherType = (DWORD)IntCur;
        }
        else {
            pDevMode->dmDitherType = fg_dither_type_i_command[IndexCur];
        }
        pDevMode->dmFields |= DM_DITHERTYPE;
    }
    //--------------------------------------------------------------------------
    // Append Driver private blob
    // > Get out, if other driver version
    if (!fDriverVersionVerified)
        return RET_OK;
    if (TCL_OK != Tcl_DictObjGet(interp, pDeviceDict,
            Tcl_NewStringObj(fg_device_index[dli_driverextra],-1), &pObj))
        return RET_ERROR;
    if (pObj != NULL) {
        unsigned char *pByteCur;
        int LengthCur;
        pByteCur = Tcl_GetByteArrayFromObj(pObj,&LengthCur);
        // Only take the driver extra data, if:
        // - blob has right size
        // - it is from same driver version
        // - it is from same generic device name
        if (LengthCur == pDevMode->dmDriverExtra) {
            memcpy(((BYTE *) pDevMode) + (pDevMode->dmSize), pByteCur,
                LengthCur);
        }
    }
    //--------------------------------------------------------------------------
    return RET_OK;
}
//------------------------------------------------------------------------------
// >>>>> PrintPrinterSetup
//------------------------------------------------------------------------------
// Show the page setup dialogue box and return the users selection as tcl
// variables.
static char PrintPrinterSetup( Tcl_Interp *interp, TCHAR *pPrinter,
    Tcl_Obj * pDeviceDict)
{
    char Res;
    DEVMODE *pDevMode;
    //--------------------------------------------------------------------------
    PrintReset( 1 );
    //--------------------------------------------------------------------------
    Res = CreateDevMode( interp, pPrinter, pDeviceDict, 1 , 1);
    if ( RET_OK != Res )
        return Res;
    //--------------------------------------------------------------------------
    if ( fg_pdlg.hDevMode == NULL )
    {
        return RET_ERROR_PRINTER_IO;
    }
    //--------------------------------------------------------------------------
    pDevMode = GlobalLock( fg_pdlg.hDevMode );
    if ( NULL == pDevMode )
        return RET_ERROR_MEMORY;
    //--------------------------------------------------------------------------
    // > Orientation and paper size
    if ( Res == RET_OK )
    {
        Tcl_Obj * pDeviceDictOut;
        pDeviceDictOut = Tcl_NewObj();
        Tcl_IncrRefCount(pDeviceDictOut);
        Res = DevmodeToDeviceDict( interp, pDevMode, pDeviceDictOut);
        if ( Res == RET_OK ) {
            Tcl_SetObjResult( interp, pDeviceDictOut );
        }
        Tcl_DecrRefCount(pDeviceDictOut);
    }
    //--------------------------------------------------------------------------
    GlobalUnlock( fg_pdlg.hDevMode );
    //--------------------------------------------------------------------------
    return Res;
}
//------------------------------------------------------------------------------
// >>>>> CreateDevMode
//------------------------------------------------------------------------------
// Create a DevMode structure for the given settings
// The devmode structure is put in a moveable memory object.
// The handle is placed in fg_pdlg.hDevMode.
static char CreateDevMode( Tcl_Interp *interp,
    TCHAR * pPrinter, Tcl_Obj * pDeviceDict, int fShowPropertySheet,
    int fDictOnlySameDriverVersion)
{
    HANDLE hPrinter;
    DEVMODE* lpDevMode;
    LONG Size;
    DWORD fMode;
    char fDevNamesLocked;
    char Res;
    //--------------------------------------------------------------------------
    Res = RET_OK;
    //--------------------------------------------------------------------------
    // > If no printer given, use last or default printer
    //--------------------------------------------------------------------------
    if ( pPrinter == NULL || pPrinter[0] == '\0' )
    {
        if ( fg_pdlg.hDevNames == NULL )
        {
            Res = LoadDefaultPrinter( );
            if ( Res != RET_OK )
                return Res;
        }
        pPrinter = ReturnLockedDeviceName( fg_pdlg.hDevNames );
        fDevNamesLocked = 1;
    } else {
        fDevNamesLocked = 0;
    }
    //--------------------------------------------------------------------------
    // > Get Printer handle
    if ( FALSE == OpenPrinter( pPrinter, &hPrinter, NULL) )
    {
        hPrinter = NULL;
        Res = RET_ERROR_PRINTER_IO;
    }
    //--------------------------------------------------------------------------
    // > Get DevMode structure size
    if (Res == RET_OK )
    {
        Size = DocumentProperties( NULL, hPrinter, pPrinter, NULL, NULL, 0 );
        if ( Size < 0 )
            Res = RET_ERROR_PRINTER_IO;
    }
    //--------------------------------------------------------------------------
    // > Adjust or get new memory
    lpDevMode = NULL;
    if (Res == RET_OK )
    {
        if ( fg_pdlg.hDevMode != NULL )
            fg_pdlg.hDevMode = GlobalReAlloc( fg_pdlg.hDevMode, Size, GMEM_ZEROINIT);
        else
            fg_pdlg.hDevMode = GlobalAlloc( GMEM_MOVEABLE | GMEM_ZEROINIT, Size);
        lpDevMode = GlobalLock( fg_pdlg.hDevMode );
        if ( fg_pdlg.hDevMode == NULL || lpDevMode == NULL)
            Res = RET_ERROR_MEMORY;
    }
    //--------------------------------------------------------------------------
    // > Initialize if new
    if ( Res == RET_OK && lpDevMode->dmSize == 0 ) {
        // > Get default values
        if ( IDOK != DocumentProperties(
                NULL,
                hPrinter,
                pPrinter,
                lpDevMode,
                NULL,
                DM_OUT_BUFFER ) )
            Res = RET_ERROR_PRINTER_IO;
    }
    //--------------------------------------------------------------------------
    // > Set values
    if (Res == RET_OK && NULL != pDeviceDict)
        Res = DeviceDictToDevmode( interp, pDeviceDict, lpDevMode,
                fDictOnlySameDriverVersion );
    //--------------------------------------------------------------------------
    if (Res == RET_OK )
    {
        //----------------------------------------------------------------------
        // > Modify present and eventually show property dialogue
        fMode = DM_IN_BUFFER | DM_OUT_BUFFER;
        if ( fShowPropertySheet )
            fMode |= DM_IN_PROMPT;

        Size = DocumentProperties(
            NULL,
            hPrinter,
            pPrinter,
            lpDevMode,
            lpDevMode,
            fMode );

        if ( Size < 0 )
            Res = RET_ERROR_PRINTER_IO;
    }
    //--------------------------------------------------------------------------
    if ( fDevNamesLocked )
        GlobalUnlock( fg_pdlg.hDevNames );
    //--------------------------------------------------------------------------
    if ( hPrinter != NULL )
        ClosePrinter( hPrinter );
    //--------------------------------------------------------------------------
    if ( lpDevMode != NULL )
        GlobalUnlock( fg_pdlg.hDevMode );
    //--------------------------------------------------------------------------
    if ( Res != RET_OK )
    {
        GlobalFree( fg_pdlg.hDevMode );
        fg_pdlg.hDevMode = NULL;
    }
    //--------------------------------------------------------------------------
    // > User may press the cancel button when interactive
    if ( Res == RET_OK && fShowPropertySheet && Size == IDCANCEL )
        return RET_ERROR_USER;
    //--------------------------------------------------------------------------
    return Res;
}
//------------------------------------------------------------------------------
// > PrintReset
//------------------------------------------------------------------------------
// Free any resource which might be opened by a print command.
// Initialise the fg_pdlg structure.
static char PrintReset( char fPreserveDeviceData )
{
    int i;
    //--------------------------------------------------------------------------
    // >> Free members of the fg_pdlg structure
    //--------------------------------------------------------------------------
    if ( fg_f_pdlg_initialized )
    {
        if (fg_pdlg.hDC != NULL)
        {
            DeleteDC(fg_pdlg.hDC);
            fg_pdlg.hDC = NULL;
        }
        if ( ! fPreserveDeviceData )
        {
            //------------------------------------------------------------------
            // > Free any Device mode data
            if ( fg_pdlg.hDevMode != NULL )
            {
                GlobalFree( fg_pdlg.hDevMode );
                fg_pdlg.hDevMode = NULL;
            }
            //------------------------------------------------------------------
            // > Free any Device Names data
            if ( fg_pdlg.hDevNames != NULL )
            {
                GlobalFree( fg_pdlg.hDevNames );
                fg_pdlg.hDevNames = NULL;
            }
        }
    } else {
        //----------------------------------------------------------------------
        // > Initialise fg_pdlg structure
        //----------------------------------------------------------------------
        memset( &fg_pdlg, 0, sizeof( PRINTDLG ) );
        fg_pdlg.lStructSize = sizeof( PRINTDLG );
        fg_f_pdlg_initialized = TRUE;
    }
    return RET_OK;
}
//------------------------------------------------------------------------------
// >>>>> LoadDefaultPrinter
//------------------------------------------------------------------------------
// Load the default printer in the fg_pdlg structure.
static char LoadDefaultPrinter( )
{
    PrintReset( 1 );
    fg_pdlg.Flags = PD_RETURNDEFAULT ;
    if ( PrintDlg( &fg_pdlg ) == FALSE)
        return RET_ERROR_PRINTER_IO;
    if (  fg_pdlg.hDevNames == NULL)
        return RET_ERROR_PRINTER_IO;
    return RET_OK;
}
//------------------------------------------------------------------------------
// >>>>> ListChoices
//------------------------------------------------------------------------------
static char ListChoices(Tcl_Interp *interp, const char *ppChoiceList[])
{
    int Index;
    Tcl_Obj *lResult;
    //--------------------------------------------------------------------------
    // > Initialise return list
    lResult = Tcl_GetObjResult( interp );
    //--------------------------------------------------------------------------
    // Loop adding the printers to the list
    for ( Index = 0; ppChoiceList[Index] != NULL; Index++)
    {
        Tcl_Obj *ChoiceText;
        ChoiceText = Tcl_NewStringObj( ppChoiceList[Index], -1 );
        if ( TCL_OK != Tcl_ListObjAppendElement( interp, lResult, ChoiceText))
        {
            // > Error already set in interp
            return RET_ERROR;
        }
    }
    return RET_OK;
}
//------------------------------------------------------------------------------
// >>>>> DeviceDictSpecGet
//------------------------------------------------------------------------------
static char DeviceDictSpecGet(Tcl_Interp *interp, char *KeyIn)
{
    int IndexCur;
    Tcl_Obj *lResult;
    Tcl_Obj *pObj;
    enum fg_device_dict_spec_type TypeCur;
    int DivisionCur;
    //--------------------------------------------------------------------------
    // >> Search for the correct spec
    for (IndexCur = 0; NULL != fg_device_index[IndexCur];IndexCur++) {
        if ( 0 == strcmp(KeyIn,fg_device_index[IndexCur]) ) {
            lResult = Tcl_GetObjResult( interp );
            TypeCur = fg_device_dict_choices[IndexCur].Type;
            pObj = Tcl_NewStringObj(fg_device_dict_spec_type_text[TypeCur],-1 );
            if ( TCL_OK != Tcl_ListObjAppendElement( interp, lResult, pObj))
                return RET_ERROR;
            switch (TypeCur) {
            case dds_list:
                // list choice1 choice 2 ...
                return ListChoices(interp,
                        fg_device_dict_choices[IndexCur].Choices);
            case dds_listnum:
                // listnum min max choice1 choice 2...
            case dds_num:
                // num min max
                if ( TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewIntObj(
                                fg_device_dict_choices[IndexCur].Min))
                        || TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewLongObj(
                                fg_dds_max_values[
                                    fg_device_dict_choices[IndexCur].Division
                                ])))
                    return RET_ERROR;
                if (TypeCur == dds_listnum)
                    return ListChoices(interp,
                        fg_device_dict_choices[IndexCur].Choices);
                break;
            case dds_float:
                // float min max digits
                DivisionCur = fg_device_dict_choices[IndexCur].Division;
                if ( TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewDoubleObj(
                                floor(0.5 + (double)
                                    fg_device_dict_choices[IndexCur].Min
                                    / DivisionCur ) ) )
                        || TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewDoubleObj(
                                floor(0.5 + 32767.0 / (double) DivisionCur )))
                        || TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewIntObj( (int)log10(DivisionCur) ) ) )
                    return RET_ERROR;
                break;
            case dds_text:
                // text "" min max
                if ( TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewStringObj("",-1))
                        || TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewIntObj(0))
                        || TCL_OK != Tcl_ListObjAppendElement( interp, lResult,
                            Tcl_NewIntObj(
                                fg_device_dict_choices[IndexCur].Division ) ) )
                    return RET_ERROR;
                break;
            case dds_yn:
            case dds_binary:
                break;
            }
                        return RET_OK;
                }
    }
        return RET_ERROR_PARAMETER;
}