/*
* TWAIN32 Options UI
*
* Copyright 2006 CodeWeavers, Aric Stewart
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/

#include "config.h"

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>

#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "winnls.h"
#include "wingdi.h"
#include "prsht.h"
#include "twain.h"
#include "sane_i.h"
#include "wine/debug.h"
#include "resource.h"
#include "wine/unicode.h"

#ifdef SONAME_LIBSANE

WINE_DEFAULT_DEBUG_CHANNEL(twain);

#define ID_BASE 0x100
#define ID_EDIT_BASE 0x1000
#define ID_STATIC_BASE 0x2000

static INT_PTR CALLBACK DialogProc (HWND , UINT , WPARAM , LPARAM );
static INT CALLBACK PropSheetProc(HWND, UINT,LPARAM);

static int create_leading_static(HDC hdc, LPCSTR text,
        LPDLGITEMTEMPLATEW* template_out, int y, int id)
{
    LPDLGITEMTEMPLATEW tpl =  NULL;
    INT len;
    SIZE size;
    LPBYTE ptr;
    LONG base;

    *template_out = NULL;

    if (!text)
        return 0;

    base = GetDialogBaseUnits();

    len = MultiByteToWideChar(CP_ACP,0,text,-1,NULL,0);
    len *= sizeof(WCHAR);
    len += sizeof(DLGITEMTEMPLATE);
    len += 3*sizeof(WORD);

    tpl = HeapAlloc(GetProcessHeap(),0,len);
    tpl->style=WS_VISIBLE;
    tpl->dwExtendedStyle = 0;
    tpl->x = 4;
    tpl->y = y;
    tpl->id = ID_BASE;

    GetTextExtentPoint32A(hdc,text,lstrlenA(text),&size);

    tpl->cx =  MulDiv(size.cx,4,LOWORD(base));
    tpl->cy =  MulDiv(size.cy,8,HIWORD(base)) * 2;
    ptr = (LPBYTE)tpl + sizeof(DLGITEMTEMPLATE);
    *(LPWORD)ptr = 0xffff;
    ptr += sizeof(WORD);
    *(LPWORD)ptr = 0x0082;
    ptr += sizeof(WORD);
    ptr += MultiByteToWideChar(CP_ACP,0,text,-1,(LPWSTR)ptr,len) * sizeof(WCHAR);
    *(LPWORD)ptr = 0x0000;

    *template_out = tpl;
    return len;
}

static int create_trailing_edit(HDC hdc, LPDLGITEMTEMPLATEW* template_out, int id,
        int y, LPCSTR text,BOOL is_int)
{
    LPDLGITEMTEMPLATEW tpl =  NULL;
    INT len;
    LPBYTE ptr;
    SIZE size;
    LONG base;
    static const char int_base[] = "0000 xxx";
    static const char float_base[] = "0000.0000 xxx";

    base = GetDialogBaseUnits();

    len = MultiByteToWideChar(CP_ACP,0,text,-1,NULL,0);
    len *= sizeof(WCHAR);
    len += sizeof(DLGITEMTEMPLATE);
    len += 3*sizeof(WORD);

    tpl = HeapAlloc(GetProcessHeap(),0,len);
    tpl->style=WS_VISIBLE|ES_READONLY|WS_BORDER;
    tpl->dwExtendedStyle = 0;
    tpl->x = 1;
    tpl->y = y;
    tpl->id = id;

    if (is_int)
        GetTextExtentPoint32A(hdc,int_base,lstrlenA(int_base),&size);
    else
        GetTextExtentPoint32A(hdc,float_base,lstrlenA(float_base),&size);

    tpl->cx =  MulDiv(size.cx*2,4,LOWORD(base));
    tpl->cy =  MulDiv(size.cy,8,HIWORD(base)) * 2;

    ptr = (LPBYTE)tpl + sizeof(DLGITEMTEMPLATE);
    *(LPWORD)ptr = 0xffff;
    ptr += sizeof(WORD);
    *(LPWORD)ptr = 0x0081;
    ptr += sizeof(WORD);
    ptr += MultiByteToWideChar(CP_ACP,0,text,-1,(LPWSTR)ptr,len) * sizeof(WCHAR);
    *(LPWORD)ptr = 0x0000;

    *template_out = tpl;
    return len;
}


static int create_item(HDC hdc, const SANE_Option_Descriptor *opt,
        INT id, LPDLGITEMTEMPLATEW *template_out, int y, int *cx, int* count)
{
    LPDLGITEMTEMPLATEW tpl = NULL,rc = NULL;
    WORD class = 0xffff;
    DWORD styles = WS_VISIBLE;
    LPBYTE ptr = NULL;
    LPDLGITEMTEMPLATEW lead_static = NULL;
    LPDLGITEMTEMPLATEW trail_edit = NULL;
    DWORD leading_len = 0;
    DWORD trail_len = 0;
    DWORD local_len = 0;
    LPCSTR title = NULL;
    CHAR buffer[255];
    int padding = 0;
    int padding2 = 0;
    int base_x = 0;
    LONG base;
    int ctl_cx = 0;
    SIZE size;

    GetTextExtentPoint32A(hdc,"X",1,&size);
    base = GetDialogBaseUnits();
    base_x = MulDiv(size.cx,4,LOWORD(base));

    if (opt->type == SANE_TYPE_BOOL)
    {
        class = 0x0080; /* Button */
        styles |= BS_AUTOCHECKBOX;
        local_len += MultiByteToWideChar(CP_ACP,0,opt->title,-1,NULL,0);
        local_len *= sizeof(WCHAR);
        title = opt->title;
    }
    else if (opt->type == SANE_TYPE_INT)
    {
        SANE_Int i;

        psane_control_option(activeDS.deviceHandle, id-ID_BASE,
                SANE_ACTION_GET_VALUE, &i,NULL);

        sprintf(buffer,"%i",i);

        if (opt->constraint_type == SANE_CONSTRAINT_NONE)
        {
            class = 0x0081; /* Edit*/
            styles |= ES_NUMBER;
            title = buffer;
            local_len += MultiByteToWideChar(CP_ACP,0,title,-1,NULL,0);
            local_len *= sizeof(WCHAR);
        }
        else if (opt->constraint_type == SANE_CONSTRAINT_RANGE)
        {
            class = 0x0084; /* scroll */
            ctl_cx = 10 * base_x;
            trail_len += create_trailing_edit(hdc, &trail_edit, id +
                    ID_EDIT_BASE, y,buffer,TRUE);
        }
        else
        {
            class= 0x0085; /* Combo */
            ctl_cx = 10 * base_x;
            styles |= CBS_DROPDOWNLIST;
        }
        leading_len += create_leading_static(hdc, opt->title, &lead_static, y, 
                id+ID_STATIC_BASE);
    }
    else if (opt->type == SANE_TYPE_FIXED)
    {
        SANE_Fixed *i;
        double dd;

        i = HeapAlloc(GetProcessHeap(),0,opt->size*sizeof(SANE_Word));

        psane_control_option(activeDS.deviceHandle, id-ID_BASE,
                SANE_ACTION_GET_VALUE, i, NULL);

        dd = SANE_UNFIX(*i);
        sprintf(buffer,"%f",dd);
        HeapFree(GetProcessHeap(),0,i);

        if (opt->constraint_type == SANE_CONSTRAINT_NONE)
        {
            class = 0x0081; /* Edit */
            title = buffer;
            local_len += MultiByteToWideChar(CP_ACP,0,title,-1,NULL,0);
            local_len *= sizeof(WCHAR);
        }
        else if (opt->constraint_type == SANE_CONSTRAINT_RANGE)
        {
            class= 0x0084; /* scroll */
            ctl_cx = 10 * base_x;
            trail_len += create_trailing_edit(hdc, &trail_edit, id +
                    ID_EDIT_BASE, y,buffer,FALSE);
        }
        else
        {
            class= 0x0085; /* Combo */
            ctl_cx = 10 * base_x;
            styles |= CBS_DROPDOWNLIST;
        }
        leading_len += create_leading_static(hdc, opt->title, &lead_static, y,
                id+ID_STATIC_BASE);
    }
    else if (opt->type == SANE_TYPE_STRING)
    {
        if (opt->constraint_type == SANE_CONSTRAINT_NONE)
        {
            class = 0x0081; /* Edit*/
        }
        else
        {
            class= 0x0085; /* Combo */
            ctl_cx = opt->size * base_x;
            styles |= CBS_DROPDOWNLIST;
        }
        leading_len += create_leading_static(hdc, opt->title, &lead_static, y,
                id+ID_STATIC_BASE);
        psane_control_option(activeDS.deviceHandle, id-ID_BASE,
                SANE_ACTION_GET_VALUE, buffer,NULL);

        title = buffer;
        local_len += MultiByteToWideChar(CP_ACP,0,title,-1,NULL,0);
        local_len *= sizeof(WCHAR);
    }
    else if (opt->type == SANE_TYPE_BUTTON)
    {
        class = 0x0080; /* Button */
        local_len += MultiByteToWideChar(CP_ACP,0,opt->title,-1,NULL,0);
        local_len *= sizeof(WCHAR);
        title = opt->title;
    }
    else if (opt->type == SANE_TYPE_GROUP)
    {
        class = 0x0080; /* Button */
        styles |= BS_GROUPBOX;
        local_len += MultiByteToWideChar(CP_ACP,0,opt->title,-1,NULL,0);
        local_len *= sizeof(WCHAR);
        title = opt->title;
    }

    local_len += sizeof(DLGITEMTEMPLATE);
    if (title)
        local_len += 3*sizeof(WORD);
    else
        local_len += 4*sizeof(WORD);

    if (lead_static)
    {
        padding = leading_len % sizeof(DWORD);
        rc = HeapReAlloc(GetProcessHeap(),0,lead_static,leading_len+local_len + padding);
        tpl = (LPDLGITEMTEMPLATEW)((LPBYTE)rc + leading_len + padding);
    }   
    else
        rc = tpl = HeapAlloc(GetProcessHeap(),0,local_len);

    tpl->style=styles;
    tpl->dwExtendedStyle = 0;
    if (lead_static)
        tpl->x = lead_static->x + lead_static->cx + 1;
    else if (opt->type == SANE_TYPE_GROUP)
        tpl->x = 2;
    else
        tpl->x = 4;
    tpl->y = y;
    tpl->id = id;

    if (title)
    {
        GetTextExtentPoint32A(hdc,title,lstrlenA(title),&size);
        tpl->cx = size.cx;
        tpl->cy = size.cy;
    }
    else
    {
        if (lead_static)
            tpl->cy = lead_static->cy;
        else
            tpl->cy = 15;

        if (!ctl_cx)
            ctl_cx = 15;

        tpl->cx = ctl_cx;
    }
    ptr = (LPBYTE)tpl + sizeof(DLGITEMTEMPLATE);
    *(LPWORD)ptr = 0xffff;
    ptr += sizeof(WORD);
    *(LPWORD)ptr = class;
    ptr += sizeof(WORD);
    if (title)
    {
        ptr += MultiByteToWideChar(CP_ACP,0,title,-1,(LPWSTR)ptr,local_len) * sizeof(WCHAR);
    }
    else
    {
        *(LPWORD)ptr = 0x0000;
        ptr += sizeof(WORD);
    }

    *((LPWORD)ptr) = 0x0000;
    ptr += sizeof(WORD);

    if (trail_edit)
    {
        trail_edit->x = tpl->cx + tpl->x + 2;
        *cx = trail_edit->x + trail_edit->cx;

        padding2 = (leading_len + local_len + padding)% sizeof(DWORD);

        rc = HeapReAlloc(GetProcessHeap(),0,rc,leading_len+local_len + padding
                +padding2+trail_len);

        memcpy(((LPBYTE)rc) + leading_len + local_len + padding + padding2,
                trail_edit,trail_len);
    }   
    else
        *cx = tpl->cx + tpl->x;
    
    *template_out = rc;
    if (leading_len)
        *count = 2;
    else
        *count = 1;

    if (trail_edit)
        *count+=1;

    return leading_len + local_len + padding + padding2 + trail_len;
}


static LPDLGTEMPLATEW create_options_page(HDC hdc, int *from_index,
                                          SANE_Int optcount, BOOL split_tabs)
{
    int i;
    INT y = 2;
    LPDLGTEMPLATEW tpl = NULL;
    LPBYTE all_controls = NULL;
    DWORD control_len = 0;
    int max_cx = 0;
    int group_max_cx = 0;
    LPBYTE ptr;
    int group_offset = -1;
    INT control_count = 0;

    for (i = *from_index; i < optcount; i++)
    {
        LPDLGITEMTEMPLATEW item_tpl = NULL;
        const SANE_Option_Descriptor *opt;
        int len;
        int padding = 0;
        int x;
        int count;
        int hold_for_group = 0;

        opt = psane_get_option_descriptor(activeDS.deviceHandle, i);
        if (!opt)
            continue;
        if (opt->type == SANE_TYPE_GROUP && split_tabs)
        {
            if (control_len > 0)
            {
                *from_index = i - 1;
                goto exit;
            }
            else
            {
                *from_index = i;
                return NULL;
            }
        }
        if (!SANE_OPTION_IS_ACTIVE (opt->cap))
            continue;

        len = create_item(hdc, opt, ID_BASE + i, &item_tpl, y, &x, &count);

        control_count += count;

        if (!len)
        {
            continue;
        }

        hold_for_group = y;
        y+= item_tpl->cy + 1;
        max_cx = max(max_cx, x + 2);
        group_max_cx = max(group_max_cx, x );

        padding = len % sizeof(DWORD);

        if (all_controls)
        {
            LPBYTE newone;
            newone = HeapReAlloc(GetProcessHeap(),0,all_controls,
                    control_len + len + padding);
            all_controls = newone;
            memcpy(all_controls+control_len,item_tpl,len);
            memset(all_controls+control_len+len,0xca,padding);
            HeapFree(GetProcessHeap(),0,item_tpl);
        }
        else
        {
            if (!padding)
            {
                all_controls = (LPBYTE)item_tpl;
            }
            else
            {
                all_controls = HeapAlloc(GetProcessHeap(),0,len + padding);
                memcpy(all_controls,item_tpl,len);
                memset(all_controls+len,0xcb,padding);
                HeapFree(GetProcessHeap(),0,item_tpl);
            }
        }

        if (opt->type == SANE_TYPE_GROUP)
        {
            if (group_offset == -1)
            {
                group_offset  = control_len;
                group_max_cx = 0;
            }
            else
            {
                LPDLGITEMTEMPLATEW group =
                    (LPDLGITEMTEMPLATEW)(all_controls+group_offset);

                group->cy = hold_for_group - group->y;
                group->cx = group_max_cx;

                group = (LPDLGITEMTEMPLATEW)(all_controls+control_len);
                group->y += 2;
                y+=2;
                group_max_cx = 0;
                group_offset  = control_len;
            }
        }

        control_len += len + padding;
    }

    if ( group_offset && !split_tabs )
    {
        LPDLGITEMTEMPLATEW group =
            (LPDLGITEMTEMPLATEW)(all_controls+group_offset);
        group->cy = y - group->y;
        group->cx = group_max_cx;
        y+=2;
    }

    *from_index = i-1;
exit:

    tpl = HeapAlloc(GetProcessHeap(),0,sizeof(DLGTEMPLATE) + 3*sizeof(WORD) + 
            control_len);

    tpl->style = WS_VISIBLE | WS_OVERLAPPEDWINDOW;
    tpl->dwExtendedStyle = 0;
    tpl->cdit = control_count;
    tpl->x = 0;
    tpl->y = 0;
    tpl->cx = max_cx + 10;
    tpl->cy = y + 10;
    ptr = (LPBYTE)tpl + sizeof(DLGTEMPLATE);
    *(LPWORD)ptr = 0x0000;
    ptr+=sizeof(WORD);
    *(LPWORD)ptr = 0x0000;
    ptr+=sizeof(WORD);
    *(LPWORD)ptr = 0x0000;
    ptr+=sizeof(WORD);
    memcpy(ptr,all_controls,control_len);

    HeapFree(GetProcessHeap(),0,all_controls);

    return tpl;
}

BOOL DoScannerUI(void)
{
    HDC hdc;
    PROPSHEETPAGEW psp[10];
    int page_count= 0;
    PROPSHEETHEADERW psh;
    int index = 1;
    SANE_Status rc;
    SANE_Int optcount;
    UINT psrc;
    LPWSTR szCaption;
    DWORD len;

    hdc = GetDC(0);

    memset(&psp,0,sizeof(psp));
    rc = psane_control_option(activeDS.deviceHandle, 0, SANE_ACTION_GET_VALUE,
            &optcount, NULL);
    if (rc != SANE_STATUS_GOOD)
    {
        ERR("Unable to read number of options\n");
        return FALSE;
    }

    while (index < optcount)
    {
        const SANE_Option_Descriptor *opt;
        psp[page_count].u.pResource = create_options_page(hdc, &index,
                optcount, TRUE);
        opt = psane_get_option_descriptor(activeDS.deviceHandle, index);

        if (opt->type == SANE_TYPE_GROUP)
        {
            LPWSTR title = NULL;
            INT len;

            len = MultiByteToWideChar(CP_ACP,0,opt->title,-1,NULL,0);
            title = HeapAlloc(GetProcessHeap(),0,len * sizeof(WCHAR));
            MultiByteToWideChar(CP_ACP,0,opt->title,-1,title,len);

            psp[page_count].pszTitle = title;
        }

        if (psp[page_count].u.pResource)
        {
            psp[page_count].dwSize = sizeof(PROPSHEETPAGEW);
            psp[page_count].dwFlags =  PSP_DLGINDIRECT | PSP_USETITLE;
            psp[page_count].hInstance = SANE_instance;
            psp[page_count].pfnDlgProc = DialogProc;
            psp[page_count].lParam = (LPARAM)&activeDS;
            page_count ++;
        }
       
        index ++;
    }
 
    len = lstrlenA(activeDS.identity.Manufacturer)
         + lstrlenA(activeDS.identity.ProductName) + 2;
    szCaption = HeapAlloc(GetProcessHeap(),0,len *sizeof(WCHAR));
    MultiByteToWideChar(CP_ACP,0,activeDS.identity.Manufacturer,-1,
            szCaption,len);
    szCaption[lstrlenA(activeDS.identity.Manufacturer)] = ' ';
    MultiByteToWideChar(CP_ACP,0,activeDS.identity.ProductName,-1,
            &szCaption[lstrlenA(activeDS.identity.Manufacturer)+1],len);
    psh.dwSize = sizeof(PROPSHEETHEADERW);
    psh.dwFlags = PSH_PROPSHEETPAGE|PSH_PROPTITLE|PSH_USECALLBACK;
    psh.hwndParent = activeDS.hwndOwner;
    psh.hInstance = SANE_instance;
    psh.u.pszIcon = 0;
    psh.pszCaption = szCaption;
    psh.nPages = page_count;
    psh.u2.nStartPage = 0;
    psh.u3.ppsp = (LPCPROPSHEETPAGEW) &psp;
    psh.pfnCallback = PropSheetProc;

    psrc = PropertySheetW(&psh);

    for(index = 0; index < page_count; index ++)
    {
        HeapFree(GetProcessHeap(),0,(LPBYTE)psp[index].u.pResource);
        HeapFree(GetProcessHeap(),0,(LPBYTE)psp[index].pszTitle);
    }
    HeapFree(GetProcessHeap(),0,szCaption);
    
    if (psrc == IDOK)
        return TRUE;
    else
        return FALSE;
}

static void UpdateRelevantEdit(HWND hwnd, const SANE_Option_Descriptor *opt, 
        int index, int position)
{
    WCHAR buffer[244];
    HWND edit_w;
    int len;

    if (opt->type == SANE_TYPE_INT)
    {
        static const WCHAR formatW[] = {'%','i',0};
        INT si;

        if (opt->constraint.range->quant)
            si = position * opt->constraint.range->quant;
        else
            si = position;

        len = sprintfW( buffer, formatW, si );
    }
    else if  (opt->type == SANE_TYPE_FIXED)
    {
        static const WCHAR formatW[] = {'%','f',0};
        double s_quant, dd;

        s_quant = SANE_UNFIX(opt->constraint.range->quant);

        if (s_quant)
            dd = position * s_quant;
        else
            dd = position * 0.01;

        len = sprintfW( buffer, formatW, dd );
    }
    else return;

    buffer[len++] = ' ';
    LoadStringW( SANE_instance, opt->unit, buffer + len, sizeof(buffer)/sizeof(WCHAR) - len );

    edit_w = GetDlgItem(hwnd,index+ID_BASE+ID_EDIT_BASE);
    if (edit_w) SetWindowTextW(edit_w,buffer);

}

static BOOL UpdateSaneScrollOption(
        const SANE_Option_Descriptor *opt, int index, DWORD position)
{
    SANE_Status rc = SANE_STATUS_GOOD;  
    SANE_Int result = 0;

    if (opt->type == SANE_TYPE_INT)
    {
        SANE_Int si;

        if (opt->constraint.range->quant)
            si = position * opt->constraint.range->quant;
        else
            si = position;

        rc = psane_control_option (activeDS.deviceHandle,index,
            SANE_ACTION_SET_VALUE, &si, &result);

    }
    else if  (opt->type == SANE_TYPE_FIXED)
    {
        double s_quant, dd;
        SANE_Fixed *sf;

        s_quant = SANE_UNFIX(opt->constraint.range->quant);

        if (s_quant)
            dd = position * s_quant;
        else
            dd = position * 0.01;

        sf = HeapAlloc(GetProcessHeap(),0,opt->size*sizeof(SANE_Word));

        *sf = SANE_FIX(dd);

        rc = psane_control_option (activeDS.deviceHandle,index,
            SANE_ACTION_SET_VALUE, sf, &result);

        HeapFree(GetProcessHeap(),0,sf);
    }

    if(rc == SANE_STATUS_GOOD)
    {
        if (result & SANE_INFO_RELOAD_OPTIONS || 
                result & SANE_INFO_RELOAD_PARAMS || result & SANE_INFO_INEXACT) 
            return TRUE;
    }
    return FALSE;
}

static BOOL UpdateSaneBoolOption(int index, BOOL position)
{
    SANE_Status rc = SANE_STATUS_GOOD;  
    SANE_Int result = 0;
    SANE_Bool si;

    si = position;

    rc = psane_control_option (activeDS.deviceHandle,index,
            SANE_ACTION_SET_VALUE, &si, &result);

    if(rc == SANE_STATUS_GOOD)
    {
        if (result & SANE_INFO_RELOAD_OPTIONS || 
                result & SANE_INFO_RELOAD_PARAMS || result & SANE_INFO_INEXACT) 
            return TRUE;
    }
    return FALSE;
}

static BOOL UpdateSaneStringOption(int index, SANE_String value)
{
    SANE_Status rc = SANE_STATUS_GOOD;  
    SANE_Int result = 0;

    rc = psane_control_option (activeDS.deviceHandle,index,
            SANE_ACTION_SET_VALUE, value, &result);

    if(rc == SANE_STATUS_GOOD)
    {
        if (result & SANE_INFO_RELOAD_OPTIONS || 
                result & SANE_INFO_RELOAD_PARAMS || result & SANE_INFO_INEXACT) 
            return TRUE;
    }
    return FALSE;
}

static INT_PTR InitializeDialog(HWND hwnd)
{
    SANE_Status rc;
    SANE_Int optcount;
    HWND control;
    int i;

    rc = psane_control_option(activeDS.deviceHandle, 0, SANE_ACTION_GET_VALUE,
            &optcount, NULL);
    if (rc != SANE_STATUS_GOOD)
    {
        ERR("Unable to read number of options\n");
        return FALSE;
    }

    for ( i = 1; i < optcount; i++)
    {
        const SANE_Option_Descriptor *opt;

        control = GetDlgItem(hwnd,i+ID_BASE);

        if (!control)
            continue;

        opt = psane_get_option_descriptor(activeDS.deviceHandle, i);

        TRACE("%i %s %i %i\n",i,opt->title,opt->type,opt->constraint_type);
        
        if (!SANE_OPTION_IS_ACTIVE(opt->cap))
            EnableWindow(control,FALSE);
        else
            EnableWindow(control,TRUE);

        SendMessageA(control,CB_RESETCONTENT,0,0);
        /* initialize values */
        if (opt->type == SANE_TYPE_STRING && opt->constraint_type !=
                SANE_CONSTRAINT_NONE)
        {
            int j = 0;
            CHAR buffer[255];
            while (opt->constraint.string_list[j]!=NULL)
            {
                SendMessageA(control,CB_ADDSTRING,0,
                        (LPARAM)opt->constraint.string_list[j]);
                j++;
            }
            psane_control_option(activeDS.deviceHandle, i, SANE_ACTION_GET_VALUE, buffer,NULL);
            SendMessageA(control,CB_SELECTSTRING,0,(LPARAM)buffer);
        }
        else if (opt->type == SANE_TYPE_BOOL)
        {
            SANE_Bool b;
            psane_control_option(activeDS.deviceHandle, i,
                    SANE_ACTION_GET_VALUE, &b, NULL);
            if (b)
                SendMessageA(control,BM_SETCHECK,BST_CHECKED,0);

        }
        else if (opt->constraint_type == SANE_CONSTRAINT_RANGE)
        {
            if (opt->type == SANE_TYPE_INT)
            {
                SANE_Int si;
                int min,max;

                min = (SANE_Int)opt->constraint.range->min /
                    (((SANE_Int)opt->constraint.range->quant)?
                    (SANE_Int)opt->constraint.range->quant:1);

                max = (SANE_Int)opt->constraint.range->max /
                    (((SANE_Int)opt->constraint.range->quant)
                    ?(SANE_Int)opt->constraint.range->quant:1);

                SendMessageA(control,SBM_SETRANGE,min,max);

                psane_control_option(activeDS.deviceHandle, i,
                        SANE_ACTION_GET_VALUE, &si,NULL);
                if (opt->constraint.range->quant)
                    si = si / opt->constraint.range->quant;

                SendMessageW(control,SBM_SETPOS, si, TRUE);
                UpdateRelevantEdit(hwnd, opt, i, si);
            }
            else if (opt->type == SANE_TYPE_FIXED)
            {
                SANE_Fixed *sf;

                double dd;
                double s_min,s_max,s_quant;
                INT pos;
                int min,max;

                s_min = SANE_UNFIX(opt->constraint.range->min);
                s_max = SANE_UNFIX(opt->constraint.range->max);
                s_quant = SANE_UNFIX(opt->constraint.range->quant);

                if (s_quant)
                {
                    min = (s_min / s_quant);
                    max = (s_max / s_quant);
                }
                else
                {
                    min = s_min / 0.01;
                    max = s_max / 0.01;
                }

                SendMessageA(control,SBM_SETRANGE,min,max);


                sf = HeapAlloc(GetProcessHeap(),0,opt->size*sizeof(SANE_Word));
                psane_control_option(activeDS.deviceHandle, i,
                        SANE_ACTION_GET_VALUE, sf,NULL);

                dd = SANE_UNFIX(*sf);
                HeapFree(GetProcessHeap(),0,sf);

                if (s_quant)
                    pos = (dd / s_quant);
                else
                    pos = dd / 0.01;

                SendMessageW(control, SBM_SETPOS, pos, TRUE);
                
                UpdateRelevantEdit(hwnd, opt, i, pos);
            }
        }
    }

    return TRUE;
}

static INT_PTR ProcessScroll(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    int index;
    const SANE_Option_Descriptor *opt;
    WORD scroll;
    DWORD position;

    index = GetDlgCtrlID((HWND)lParam)- ID_BASE;
    if (index < 0)
        return FALSE;

    opt = psane_get_option_descriptor(activeDS.deviceHandle, index);

    if (!opt)
        return FALSE;

    scroll = LOWORD(wParam);

    switch (scroll)
    {
        case SB_THUMBTRACK:
        case SB_THUMBPOSITION:
        {
            SCROLLINFO si;
            si.cbSize = sizeof(SCROLLINFO);
            si.fMask = SIF_TRACKPOS;
            GetScrollInfo((HWND)lParam,SB_CTL, &si);
            position = si.nTrackPos;
        }
            break;
        case SB_LEFT:
        case SB_LINELEFT:
        case SB_PAGELEFT:
            position = SendMessageW((HWND)lParam,SBM_GETPOS,0,0);
            position--;
            break;
        case SB_RIGHT:
        case SB_LINERIGHT:
        case SB_PAGERIGHT:
            position = SendMessageW((HWND)lParam,SBM_GETPOS,0,0);
            position++;
            break;
        default:
            position = SendMessageW((HWND)lParam,SBM_GETPOS,0,0);
    }

    SendMessageW((HWND)lParam,SBM_SETPOS, position, TRUE);
    position = SendMessageW((HWND)lParam,SBM_GETPOS,0,0);

    UpdateRelevantEdit(hwnd, opt, index, position);
    if (UpdateSaneScrollOption(opt, index, position))
        InitializeDialog(hwnd);

    return TRUE;
}


static void ButtonClicked(HWND hwnd, INT id, HWND control)
{
    int index;
    const SANE_Option_Descriptor *opt;

    index = id - ID_BASE;
    if (index < 0)
        return;

    opt = psane_get_option_descriptor(activeDS.deviceHandle, index);

    if (!opt)
        return;

    if (opt->type == SANE_TYPE_BOOL)
    {
        BOOL r = SendMessageW(control,BM_GETCHECK,0,0)==BST_CHECKED;
        if (UpdateSaneBoolOption(index, r))
                InitializeDialog(hwnd);
    }
}

static void ComboChanged(HWND hwnd, INT id, HWND control)
{
    int index;
    int selection;
    int len;
    const SANE_Option_Descriptor *opt;
    SANE_String value;

    index = id - ID_BASE;
    if (index < 0)
        return;

    opt = psane_get_option_descriptor(activeDS.deviceHandle, index);

    if (!opt)
        return;

    selection = SendMessageW(control,CB_GETCURSEL,0,0);
    len = SendMessageW(control,CB_GETLBTEXTLEN,selection,0);

    len++;
    value = HeapAlloc(GetProcessHeap(),0,len);
    SendMessageA(control,CB_GETLBTEXT,selection,(LPARAM)value);

    if (opt->type == SANE_TYPE_STRING)
    {
        if (UpdateSaneStringOption(index, value))
                InitializeDialog(hwnd);
    }
}


static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_INITDIALOG:
            return InitializeDialog(hwndDlg);
        case WM_HSCROLL:
            return ProcessScroll(hwndDlg, wParam, lParam);
        case WM_NOTIFY:
            {
                LPPSHNOTIFY psn = (LPPSHNOTIFY)lParam;
                switch (((NMHDR*)lParam)->code)
                {
                    case PSN_APPLY:
                        if (psn->lParam == TRUE)
                        {
                            activeDS.currentState = 6;
                            activeDS.pendingEvent.TWMessage = MSG_XFERREADY;
                        }
                        break;
                    case PSN_QUERYCANCEL:
                        activeDS.pendingEvent.TWMessage = MSG_CLOSEDSREQ;
                        break;
                    case PSN_SETACTIVE:
                        InitializeDialog(hwndDlg);
                        break;
                }
            }
        case WM_COMMAND:
            {
                switch (HIWORD(wParam))
                {
                    case BN_CLICKED:
                        ButtonClicked(hwndDlg,LOWORD(wParam),
                                (HWND)lParam);
                        break;
                    case CBN_SELCHANGE:
                        ComboChanged(hwndDlg,LOWORD(wParam),
                                (HWND)lParam);
                }
            }
    }

    return FALSE;
}

static int CALLBACK PropSheetProc(HWND hwnd, UINT msg, LPARAM lParam)
{
    if (msg == PSCB_INITIALIZED)
    {
        /* rename OK button to Scan */
        HWND scan = GetDlgItem(hwnd,IDOK);
        SetWindowTextA(scan,"Scan");
    }
    return TRUE;
}


static INT_PTR CALLBACK ScanningProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    return FALSE;
}

HWND ScanningDialogBox(HWND dialog, LONG progress)
{
    if (!dialog)
        dialog = CreateDialogW(SANE_instance,
                (LPWSTR)MAKEINTRESOURCE(IDD_DIALOG1), NULL, ScanningProc);

    if (progress == -1)
    {
        EndDialog(dialog,0);
        return NULL;
    }

    RedrawWindow(dialog,NULL,NULL,
            RDW_INTERNALPAINT|RDW_UPDATENOW|RDW_ALLCHILDREN);

    return dialog;
}

#else  /* SONAME_LIBSANE */

BOOL DoScannerUI(void)
{
    return FALSE;
}

#endif  /* SONAME_LIBSANE */