/* $Header: d:/cvsroot/tads/html/win32/tadswin.h,v 1.4 1999/07/11 00:46:48 MJRoberts Exp $ */

/* 
 *   Copyright (c) 1997 by Michael J. Roberts.  All Rights Reserved.
 *   
 *   Please see the accompanying license file, LICENSE.TXT, for information
 *   on using and copying this software.  
 */
/*
Name
  tadswin.h - TADS window classes for win32
Function
  These classes define Win32 window objects.
Notes
  
Modified
  09/16/97 MJRoberts  - Creation
*/

#include <Ole2.h>
#include <Windows.h>
#include <commctrl.h>
#include <Shlobj.h>

#ifndef TADSWIN_H
#define TADSWIN_H

#ifndef TADSHTML_H
#include "tadshtml.h"
#endif


/* ------------------------------------------------------------------------ */
/*
 *   Our special version of the Win32 API call GetTextExtentPoint32.  This
 *   API call is buggy on Windows 2000; some people have encountered odd
 *   problems that we traced to this API, and MS tech support has confirmed
 *   that the API has a problem calculating the size of some realized fonts.
 *   
 *   It seems that we can work around the problem by using the alternative
 *   API call GetTextExtentExPoint instead; that function seems to work
 *   consistently even in Win2k.  The only problem is that
 *   GetTextExtentExPoint is documented as being considerably slower than
 *   GetTextExtentPoint32.  So, we test the OS version, using
 *   GetTextExtentExPoint if we're on Win2k, and GetTextExtentPoint32
 *   everywhere else.  
 */
void ht_GetTextExtentPoint32(HDC dc, const textchar_t *txt, size_t len,
                             SIZE *txtsiz);


/* ------------------------------------------------------------------------ */
/*
 *   Command status codes
 *   
 *   TADSCMD_DO_NOT_CHANGE indicates that the status of a menu item or
 *   toolbar button should not be changed from what it's already set to.
 *   This can be used as the return value from CTadsWin::check_command(),
 *   for example, when the routine knows that the value has already been
 *   set properly elsewhere and does not need to be updated during the
 *   normal menu setup.  
 */
enum TadsCmdStat_t
{
    TADSCMD_UNKNOWN = 0,                            /* unrecognized command */
    TADSCMD_ENABLED = 1,                                 /* command enabled */
    TADSCMD_DISABLED = 2,                               /* command disabled */
    TADSCMD_CHECKED = 3,                    /* command enabled and selected */
    TADSCMD_DISABLED_CHECKED = 4,          /* command disabled and selected */
    TADSCMD_INDETERMINATE = 5,/* command enabled, state indeterminate/mixed */
    TADSCMD_DISABLED_INDETERMINATE = 6,    /* disabled, state indeterminate */
    TADSCMD_DEFAULT = 7,                  /* default item - implies enabled */
    TADSCMD_DO_NOT_CHANGE = 8        /* leave the status as it is currently */
};


/* ------------------------------------------------------------------------ */
/*
 *   Convert an HTML_color_t to a COLORREF, and vice versa 
 */
inline COLORREF HTML_color_to_COLORREF(HTML_color_t htmlcolor)
{
    return RGB(HTML_color_red(htmlcolor),
               HTML_color_green(htmlcolor),
               HTML_color_blue(htmlcolor));
}

inline HTML_color_t COLORREF_to_HTML_color(COLORREF rgb)
{
    return HTML_make_color(GetRValue(rgb), GetGValue(rgb), GetBValue(rgb));
}


/* ------------------------------------------------------------------------ */
/*
 *   System Window Class Interface.  Each of our window objects must have
 *   one of these helper objects to provide the interface to its
 *   underlying system window class.
 *   
 *   The main reason we need this plug-in object is to allow mixing of our
 *   class hierarchy and the Windows class hierarchy (such as it is).
 *   This helper bridges the gap created by the way Windows exposes
 *   specific features of its different window classes directly in the
 *   API; for example, our code must call one of a number of default
 *   window procedures, depending on what type of system window we're
 *   creating (MDI child, MDI frame, normal window).  This helper
 *   encapsulates the system API specializations for each type of
 *   underlying system window, so that we can use any type of underlying
 *   system window with any class in our hierarchy.
 *   
 *   The base syswin class is appropriate for any generic system window
 *   (i.e., any window that uses DefWindowProc as its default window
 *   procedure).  Do not use this class for MDI Frame or Child windows.  
 */
class CTadsSyswin
{
public:
    CTadsSyswin(class CTadsWin *win) { win_ = win; }
    
    /* process creation of the window */
    virtual void syswin_do_create() {}

    /* process destruction of the window */
    virtual void syswin_do_destroy() {}

    /* destroy this window handle (use instead of DestroyWindow) */
    virtual void syswin_destroy_handle(HWND hwnd)
    {
        /* by default, use DestroyWindow */
        DestroyWindow(hwnd);
    }

    /* forget my window long for 'this' */
    virtual void forget_this_ptr(HWND hwnd)
    {
        /* forget my window pointer */
        SetWindowLong(hwnd, 0, 0L);
    }

    /* set the window's menu */
    virtual void syswin_set_win_menu(HMENU menu, int win_menu_index);

    /*
     *   Determine if we should always pass a message to the default
     *   handler.  Certain types of system window classes require that
     *   certain messages always be passed to their default handler, even
     *   when the message is also handled by the user code.  
     */
    virtual int syswin_always_pass_message(int msg) const { return FALSE; }

    /*
     *   Handle a WM_SIZE event.  Returns true if this routine has fully
     *   handled the event, false if not.  By default, we'll simply return
     *   false to let the Windows default message handler get the message. 
     */
    virtual int syswin_do_resize(int /*mode*/, int /*x*/, int /*y*/)
        { return FALSE; }

    /* pass a message to the default window procedure */
    virtual LRESULT syswin_call_defwinproc(HWND hwnd, UINT msg,
                                           WPARAM wpar, LPARAM lpar)
    {
        /* call the standard default window procedure */
        return DefWindowProc(hwnd, msg, wpar, lpar);
    }

    /*
     *   Get the handle of the window to use when creating children of
     *   this window.  For standard windows, this is simply the handle of
     *   this window; for MDI frames, this is the handle of the client
     *   rather than of this window.  
     */
    virtual HWND syswin_get_parent_of_children() const;

    /*
     *   Create the system window object 
     */
    virtual HWND syswin_create_system_window(DWORD ex_style,
                                             const textchar_t *wintitle,
                                             DWORD style,
                                             int x, int y, int wid, int ht,
                                             HWND parent, HMENU menu,
                                             HINSTANCE inst, void *param);

    /* determine if I'm maximized */
    virtual int syswin_is_maximized(class CTadsWin *parent) const;

protected:
    /* the associated window object */
    class CTadsWin *win_;
};


/* ------------------------------------------------------------------------ */
/*
 *   System Window Class Interface for MDI Frame windows 
 */
class CTadsSyswinMdiFrame: public CTadsSyswin
{
public:
    /*
     *   Create the MDI Frame interface object.  win_menu_idx is the index
     *   within the window's main menu of the "Window" menu; if this is
     *   less than zero, we'll take it as the offset from the *last* menu
     *   (-1 is the last menu, -2 is the second to last menu, etc).
     *   win_menu_cmd is the first command in a reserved range of commands
     *   for the child windows in the "Window" menu.
     *   
     *   If 'win_resizes_client_area' is true, we will refrain from
     *   resizing the client area when the window is resized; we will
     *   expect that the window itself handles this on resize.  
     */
    CTadsSyswinMdiFrame(class CTadsWin *win,
                        class CTadsWin *client_win,
                        int win_menu_idx, int win_menu_cmd,
                        int win_resizes_client_area)
        : CTadsSyswin(win)
    {
        /* save the MDI creation parameters */
        win_menu_idx_ = win_menu_idx;
        win_menu_cmd_ = win_menu_cmd;

        /* remember the window object for the client window */
        client_win_ = client_win;

        /* we haven't created the client's system window yet */
        client_handle_ = 0;

        /* note the window-resizes-client status */
        win_resizes_client_ = win_resizes_client_area;
    }
    
    /* get the MDI client window handle */
    HWND get_client_handle() const { return client_handle_; }

    /* process creation */
    void syswin_do_create();

    /* process destruction */
    void syswin_do_destroy();

    /* set the window's menu */
    virtual void syswin_set_win_menu(HMENU menu, int win_menu_index);

    /* handle a resize event */
    int syswin_do_resize(int mode, int x, int y);

    /*
     *   Create the system window object 
     */
    virtual HWND syswin_create_system_window(DWORD ex_style,
                                             const textchar_t *wintitle,
                                             DWORD style,
                                             int x, int y, int wid, int ht,
                                             HWND parent, HMENU menu,
                                             HINSTANCE inst, void *param);

    /*
     *   Determine if we should always pass a message to the default
     *   handler.  MDI Frame windows are required to pass certain messages
     *   to the default handler, even when they handle those messages
     *   themselves.  
     */
    virtual int syswin_always_pass_message(int msg) const
    {
        return (msg == WM_COMMAND || msg == WM_MENUCHAR
                || msg == WM_SETFOCUS || msg == WM_SIZE);
    }

    /* pass a message to the default window procedure */
    virtual LRESULT syswin_call_defwinproc(HWND hwnd, UINT msg,
                                           WPARAM wpar, LPARAM lpar)
    {
        /* call the MDI frame handler */
        return DefFrameProc(hwnd, client_handle_, msg, wpar, lpar);
    }

    /*
     *   Get the handle of the window to use when creating children of
     *   this window.  For MDI frames, this is the handle of the client
     *   rather than of the frame.  
     */
    virtual HWND syswin_get_parent_of_children() const
        { return client_handle_; }

protected:
    /* CTadsWin object for the client window, if present */
    class CTadsWin *client_win_;
    
    /* handle of the MDI client window */
    HWND client_handle_;

    /* index in the main menu bar of the "Window" menu */
    int win_menu_idx_;

    /* first command ID for child windows in the "Window" menu */
    int win_menu_cmd_;

    /* flag: our window handles client resizing */
    unsigned int win_resizes_client_ : 1;
};

/* ------------------------------------------------------------------------ */
/*
 *   System Window Class Interface for MDI Client windows 
 */
class CTadsSyswinMdiClient: public CTadsSyswin
{
public:
    /* 
     *   save the base class information for the internal Windows
     *   MDICLIENT class - this is called during class registration 
     */
    static void save_base_class_info(const WNDCLASS *wc)
    {
        /* remember the base class window procedure */
        base_proc_ = wc->lpfnWndProc;

        /* remember the size of the base class window extra data */
        base_wnd_extra_ = wc->cbWndExtra;
    }

    /* instantiate */
    CTadsSyswinMdiClient(class CTadsWin *win)
        : CTadsSyswin(win)
    {
    }

    /* forget my window long for 'this' */
    virtual void forget_this_ptr(HWND hwnd)
    {
        /* forget my window pointer */
        SetWindowLong(hwnd, base_wnd_extra_ + 0, 0L);
    }

    /* pass a message to the default window procedure */
    virtual LRESULT syswin_call_defwinproc(HWND hwnd, UINT msg,
                                           WPARAM wpar, LPARAM lpar)
    {
        /* call the base class procedure */
        return CallWindowProc((WNDPROC)base_proc_, hwnd, msg, wpar, lpar);
    }

    /* message handler ("superclass" window procedure) */
    static LRESULT CALLBACK message_handler(HWND hwnd, UINT msg,
                                            WPARAM wpar, LPARAM lpar);

    /* create the system window object */
    virtual HWND syswin_create_system_window(DWORD ex_style,
                                             const textchar_t *wintitle,
                                             DWORD style,
                                             int x, int y, int wid, int ht,
                                             HWND parent, HMENU menu,
                                             HINSTANCE inst, void *param);

    /* always pass certain messages to the internal MDI client window */
    virtual int syswin_always_pass_message(int msg) const
    {
        return (msg == WM_SETFOCUS || msg == WM_SIZE || msg == WM_MOVE);
    }

protected:
    /* base class (MDICLIENT) window procedure */
    static WNDPROC base_proc_;

    /* base class (MDICLIENT) window extra data size */
    static int base_wnd_extra_;
};


/* ------------------------------------------------------------------------ */
/*
 *   System Window Class Interface for MDI Child windows
 */
class CTadsSyswinMdiChild: public CTadsSyswin
{
public:
    CTadsSyswinMdiChild(class CTadsWin *win)
        : CTadsSyswin(win)
    {
    }

    /* destroy this window handle - process via the MDI client */
    virtual void syswin_destroy_handle(HWND hwnd)
    {
        /* ask the client window to destroy this child window */
        SendMessage(GetParent(hwnd), WM_MDIDESTROY, (WPARAM)hwnd, 0);
    }

    /* pass a message to the default window procedure */
    virtual LRESULT syswin_call_defwinproc(HWND hwnd, UINT msg,
                                           WPARAM wpar, LPARAM lpar);

    /* 
     *   MDI child windows must pass certain additional messages to the
     *   default handler 
     */
    virtual int syswin_always_pass_message(int msg) const
    {
        return (msg == WM_CHILDACTIVATE
                || msg == WM_GETMINMAXINFO
                || msg == WM_MENUCHAR
                || msg == WM_MOVE
                || msg == WM_SETFOCUS
                || msg == WM_SIZE
                || msg == WM_SYSCOMMAND);
    }

    /*
     *   Create the system window object 
     */
    virtual HWND syswin_create_system_window(DWORD ex_style,
                                             const textchar_t *wintitle,
                                             DWORD style,
                                             int x, int y, int wid, int ht,
                                             HWND parent, HMENU menu,
                                             HINSTANCE inst, void *param);

    /* determine if I'm maximized */
    virtual int syswin_is_maximized(class CTadsWin *parent) const;
};


/* ------------------------------------------------------------------------ */
/*
 *   Basic window class.  This class can be used as a base class for most
 *   types of Win32 windows. 
 */

/* maximum number of toolbars that can be registered for status updating */
const int TADSWIN_TBMAX = 5;

/* button click record */
struct BtnClick_t
{
    BtnClick_t() { time_ = 0; x_ = y_ = 0; cnt_ = 0; }
    
    /*
     *   Check a click to see if it's a multiple click.  We'll return the
     *   click count, and remember the statistics for the next click.
     *   'lpar' is the LPARAM value from the WM_xBUTTONDOWN message.  
     */
    int get_click_count(LPARAM lpar);
    
    long time_;                                   /* time of the last click */
    int x_, y_;                               /* position of the last click */
    int cnt_;                             /* number of clicks of last click */
};

/*
 *   Command descriptor for CTadsWin::check_command().  This describes the
 *   command we're checking, and indicates the source of the check, if it's a
 *   menu bar.  
 */
struct check_cmd_info
{
    /* generate a check for a menu item */
    check_cmd_info(int id, HMENU m, int idx)
    {
        command_id = id;
        menu = m;
        item_idx = idx;
    }

    /* generate a check for something other than a menu item */
    check_cmd_info(int id)
    {
        command_id = id;
        menu = 0;
        item_idx = 0;
    }
    
    /* the command we're checking */
    int command_id;

    /* the menu item that's generating the check */
    HMENU menu;
    int item_idx;
};


class CTadsWin: public IDropSource, public IDropTarget
{
public:
    /* static class initialization/termination */
    static void class_init(class CTadsApp *app);
    static void class_terminate();

    /* creation/deletion */
    CTadsWin();
    virtual ~CTadsWin();

    /*
     *   Destroy the window.  Use this method instead of calling the
     *   DestroyWindow() function in the Windows API - this will use the
     *   proper destruction mechanism (for example, MDI child windows need
     *   special handling). 
     */
    void destroy_handle() { sysifc_->syswin_destroy_handle(handle_); }

    /* get my parent window */
    CTadsWin *get_parent() const { return parent_; }

    /* 
     *   set my parent window - this only sets our internal parent, not
     *   the Windows parent handle; a caller that reparents this window
     *   must take care of reparenting the Windows handle separately 
     */
    void set_parent(CTadsWin *parent) { parent_ = parent; }

    /*
     *   Get the window frame position for persistence purposes.  We find the
     *   nearest enclosing popup or MDI-child window, and gets its window
     *   rectangle.  If the window is an MDI child, we return its position
     *   relative to the MDI client; otherwise we return the position in
     *   screen coordinates.  Returns the window handle whose position we
     *   retrieved.  
     */
    HWND get_frame_pos(RECT *pos) { return get_frame_pos(handle_, pos); }

    /* get the frame position for a particular window handle */
    HWND get_frame_pos(HWND hwnd, RECT *pos);

    /* 
     *   Mark this as a frame window/get the frame status.  By default, we're
     *   a frame window if we're an MDI child or our style doesn't include
     *   WS_CHILD.  However, we can add a property to explicitly mark this as
     *   a frame window. 
     */
    void mark_as_frame()
        { SetProp(handle_, "CTadsWin.isFrame", (HANDLE)TRUE); }
    int is_frame_window() { return is_frame_window(handle_); }
    int is_frame_window(HWND hwnd)
    {
        /* 
         *   we're a frame if we're an MDI child, or we're a top-level
         *   (non-child) window, or we have the explicit frame marker
         *   property 
         */
        return ((GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) == 0
                || is_mdi_child(hwnd)
                || GetProp(hwnd, "CTadsWin.isFrame"));
    }

    /* is this an MDI child window? */
    int is_mdi_child() { return is_mdi_child(handle_); }
    int is_mdi_child(HWND hwnd)
        { return (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_MDICHILD) != 0; }

    /* 
     *   Create the operating system window object.  Returns zero on
     *   success, non-zero on failure.  This routine uses the default
     *   system window interface object, so this call can only be used to
     *   create standard (non-MDI) windows.  
     */
    virtual int create_system_window(CTadsWin *parent, int show,
                                     const char *title, const RECT *pos)
    {
        return create_system_window(parent, show, title, pos,
                                    new CTadsSyswin(this));
    }

    /*
     *   Create the OS window object, using a particular system window
     *   interface.  Returns zero on success, non-zero on failure.
     *   
     *   The window takes ownership of the system interface object; the
     *   window will automatically delete the system interface object when
     *   the window is deleted.  Since the window owns the system
     *   interface object, each window must have its own unique instance
     *   of a system interface object -- windows cannot share these
     *   objects.  
     */
    virtual int create_system_window(CTadsWin *parent, int show,
                                     const char *title, const RECT *pos,
                                     CTadsSyswin *sysifc);

    /* create a system window given an explicit parent handle */
    int create_system_window(CTadsWin *parent, HWND parent_hwnd,
                             int show, const char *title,
                             const RECT *pos, CTadsSyswin *sysifc);

    /*
     *   Skip creating the system window.  This should be called when it's
     *   determined that we won't want to be opening a system window for this
     *   object after all.  This drops our self-reference on behalf of the
     *   system window, and should recursively call the same function on any
     *   subwindow objects we've created.  
     */
    virtual void skip_create_system_window()
    {
        /* 
         *   release our self-reference that we applied in anticipation of
         *   create_system_window() eventually being called 
         */
        Release();
    }

    /*
     *   Register window classes.  This routine must be called during
     *   program initialization so that we can identify our window class
     *   to the system when creating new instances of the window.  
     */
    static void register_win_class(class CTadsApp *app);

    /* get the window handle */
    HWND get_handle() { return handle_; }

    /* determine if I'm maximized */
    int is_win_maximized() const;

    /*
     *   Get the window handle to use as the parent handle when creating
     *   children of this window.  For most types of system windows, this
     *   will simply return my own handle; certain system window types,
     *   such as MDI Frame windows, will return a child handle.  
     */
    HWND get_parent_of_children() const
        { return sysifc_->syswin_get_parent_of_children(); }

    /*
     *   Set the window's menu.  Clients should use this rather than
     *   calling SetMenu directly.  If the menu has a "Window" menu (which
     *   is generally only applicable to MDI frame windows),
     *   win_menu_index is a non-zero value giving the index within the
     *   menu bar of the window menu: a positive value indicates an offset
     *   from the first menu (1 = first menu, 2 = second menu), and a
     *   negative value indicates an offset from the last menu (-1 = last
     *   [rightmost] menu, -2 = next to last menu).  0 indicates that
     *   there is no "Window" menu.  
     */
    void set_win_menu(HMENU menu, int win_menu_index)
        { sysifc_->syswin_set_win_menu(menu, win_menu_index); }

    /*
     *   Get my menu.  By default, we'll simply use the actual system menu
     *   for the window.  If the window uses a custom control to implement
     *   the menu, this must be overridden to return the actual menu.  
     */
    virtual HMENU get_win_menu() { return GetMenu(handle_); }

    /* invalidate the window, or a portion thereof */
    void inval();
    void inval(const RECT *area);

    /* determine the number of bits per pixel on the display */
    int get_bits_per_pixel()
    {
        /* simply return the bits per pixel of the desktop window */
        return get_desk_bits_per_pixel();
    }

    /* get the bits per pixel of the desktop window */
    static int get_desk_bits_per_pixel();

    /* Get our device context.  This is only valid during drawing. */
    HDC gethdc() { return hdc_; }

    /* 
     *   Get our display device context.  When we're drawing into a memory
     *   DC, we'll still keep track of the actual display device context;
     *   this is necessary on palette devices so that the palette can be
     *   syncrhonized between the memory DC and the device DC. 
     */
    HDC get_display_hdc() { return display_hdc_; }


    /* 
     *   Process a palette instantiation request.  Windows sends this
     *   message to a top-level window when it's about to become active,
     *   so that it can realize its palette as a foreground window.  By
     *   default, we do nothing; subclasses should override this to
     *   realize their palettes as appropriate.  Windows which contain
     *   subwindows which have palettes should generally call this method
     *   on their subwindows.  This routine should return true if the
     *   window realizes a new palette.  
     */
    virtual int do_querynewpalette() { return FALSE; }

    /*
     *   Process a palette change notification.  Windows sends this
     *   message to all top-level windows when the system palette changes.
     *   This routine should update this window, if needed, to adjust for
     *   the new palette.  Note that, by default, we'll only call this
     *   method on top-level windows; windows that contain subwindows with
     *   palettes should generally call this method on their subwindows.
     *   By default, this routine does nothing; subclasses should override
     *   it as needed to update for a palette change.
     *   
     *   Note that this routine should return without doing any work if
     *   called on the initiating window, since an infinite loop would
     *   result if the initating window responded to this message with
     *   another palette change.  
     */
    virtual void do_palettechanged(HWND /*initiating_window*/) { }
    
    /* 
     *   process a notification message; the return value is returned
     *   directly from the window procedure 
     */
    virtual int do_notify(int control_id, int notify_code, LPNMHDR nm);

    /* process a context-menu message */
    virtual int do_context_menu(HWND /*hwnd*/, int /*x*/, int /*y*/)
        { return FALSE; }

    /* process a command */
    virtual int do_command(int /*notify_code*/, int /*command_id*/,
                           HWND /*ctl*/)
        { return FALSE; }

    /* check the status of a command */
    virtual TadsCmdStat_t check_command(const check_cmd_info *)
        { return TADSCMD_UNKNOWN; }

    /* is the given command enabled? */
    int check_command_enabled(int command_id);

    /* 
     *   Check the status of a system command.  This is used for any command
     *   with an ID 0xF000 or above, which are reserved for use by Windows
     *   for system menu commands (SC_xxx).  By default, we return "do not
     *   change" to keep the latest system status update in effect.
     *   
     *   'sysmenu' indicates whether or not this is being checked for showing
     *   the system menu, popped up by a click on the window menu box.  
     */
    virtual TadsCmdStat_t check_sys_command(
        int sysmenu, const check_cmd_info *)
        { return TADSCMD_DO_NOT_CHANGE; }

    /*
     *   During a check_command() routine, call this to update the menu item
     *   title to the given resource string, with the optional printf-style
     *   arguments. 
     */
    void check_command_change_menu(const check_cmd_info *info,
                                   int res_str_id, ...);

    /* 
     *   Update toolbar button states with the current command status.  We do
     *   this automatically on a timer that runs every half second by
     *   default, but this can be called explicitly if desired to update the
     *   toolbar status immediately at certain times.  By default, we call
     *   this right after calling do_command(), if do_command() did anything,
     *   to ensure that the toolbar is updated quickly after a user action
     *   that might have triggered a change. 
     */
    void update_toolbar_buttons();

    /* handle a mouse-wheel scroll event */
    virtual int do_mousewheel(int keys, int dist, int x, int y)
    {
        /* 
         *   if we're controlling another winodw, pass the message to that
         *   window; otherwise, do nothing, since the basic window doesn't
         *   have any scrolling capabilities defined
         */
        if (scroll_win_ != 0)
            return scroll_win_->do_mousewheel(keys, dist, x, y);
        else
            return FALSE;
    }

    /*
     *   Handle scrollbar events - if vert is true, it's a vertical
     *   scrollbar, otherwise it's a horizontal scrollbar.  By default, we
     *   ignore this method.  
     */
    virtual void do_scroll(int vert, HWND sb_handle,
                           int scroll_code, long pos, int use_pos)
    {
        /* 
         *   If we're controlling scrolling in another window, pass the
         *   message to that window; otherwise, do nothing, since the
         *   basic window doesn't have any ability to scroll.  
         */
        if (scroll_win_ != 0)
        {
            /* there's an external scrolling window - let it handle it */
            scroll_win_->do_scroll(vert, sb_handle,
                                   scroll_code, pos, use_pos);
        }
    }

    /* 
     *   Set the external scrolling window.  We will control scrolling in
     *   this window via child scrollbar controls contained in this
     *   window.  (Normally, a window contains its scrollbars as its own
     *   child controls; in some cases, however, it may be desirable for
     *   one window to contain scrollbars for another window, in order to
     *   achieve special layout effects.  For example, a parent window may
     *   want to contain the scrollbars for one of its children, so that
     *   the scrollbars can be positioned specially for other controls
     *   contained in the border around the child window.)
     */
    void set_scroll_win(CTadsWin *scroll_win)
    {
        CTadsWin *old_scroll_win;

        /* remember the old scrolling window for a moment */
        old_scroll_win = scroll_win_;
        
        /* set the new scrolling window */
        scroll_win_ = scroll_win;

        /* 
         *   Add a reference to the new window, and remove a reference
         *   from the old one.  We keep a reference on the window so that
         *   we can be sure it doesn't get deleted as long as we have a
         *   reference to it.  
         */
        if (scroll_win_ != 0)
            scroll_win_->AddRef();
        if (old_scroll_win != 0)
            old_scroll_win->Release();
    }

    /*
     *   Draw a bitmap, given a bitmap handle 
     */
    void draw_hbitmap(HBITMAP bmp, const RECT *dstrc,
                      const RECT *srcrc);

    /*
     *   Add a toolbar idle status processor.  We'll set up a timer
     *   routine that will scan each toolbar periodically and update the
     *   status of each button to correspond to the current status for its
     *   associated command.  Up to TADSWIN_TBMAX toolbar status
     *   processors can be set.  
     */
    void add_toolbar_proc(HWND toolbar);

    /* 
     *   Remove a toolbar status processor.  If a toolbar is registered
     *   with add_toolbar_proc(), it must be deregistered here before the
     *   toolbar is destroyed. 
     */
    void rem_toolbar_proc(HWND toolbar);

    /*
     *   Process a private activation message from the parent window.
     *   Certain types of container windows might want to notify child
     *   windows of "artificial" activation (in other words, activation
     *   that is not considered by Windows to be true activation, and thus
     *   doesn't result in a WM_ACTIVATE, WM_NCACTIVATE, WM_MDIACTIVATE,
     *   or any other similar message).  For example, the docking window
     *   container can use this.  
     */
    virtual void do_parent_activate(int /*active*/) { }

    /*
     *   IDropSource implementation 
     */
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ifc);
    ULONG STDMETHODCALLTYPE AddRef() { return ++ole_refcnt_; }
    ULONG STDMETHODCALLTYPE Release();
    HRESULT STDMETHODCALLTYPE
        QueryContinueDrag(BOOL esc_pressed, DWORD key_state);
    HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD effect);

    /*
     *   IDropTarget implementation (shares IUnknown with IDropSource) 
     */
    HRESULT STDMETHODCALLTYPE DragEnter(IDataObject __RPC_FAR *pDataObj,
                                        DWORD grfKeyState, POINTL pt,
                                        DWORD __RPC_FAR *pdwEffect);
    HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt,
                                       DWORD __RPC_FAR *pdwEffect);
    HRESULT STDMETHODCALLTYPE DragLeave();
    HRESULT STDMETHODCALLTYPE Drop(IDataObject __RPC_FAR *pDataObj,
                                   DWORD grfKeyState, POINTL pt,
                                   DWORD __RPC_FAR *pdwEffect);

    /*
     *   IDropTargetHelper pass-through routines.  Implementations of the
     *   IDropTarget interface methods should generally call the
     *   corresponding entries here as their first action, to let the helper
     *   intervene in the drag processing.
     */
    void DropHelper_Enter(IDataObject *obj, DWORD ks, POINTL ptl, DWORD *eff)
    {
        if (drop_target_helper_ != 0)
        {
            POINT pt = { ptl.x, ptl.y };
            drop_target_helper_->DragEnter(handle_, obj, &pt, *eff);
        }
    }
    void DropHelper_Over(DWORD ks, POINTL ptl, DWORD *eff)
    {
        if (drop_target_helper_ != 0)
        {
            POINT pt = { ptl.x, ptl.y };
            drop_target_helper_->DragOver(&pt, *eff);
        }
    }
    void DropHelper_Leave()
    {
        if (drop_target_helper_ != 0)
            drop_target_helper_->DragLeave();
    }
    void DropHelper_Drop(IDataObject *obj, DWORD ks, POINTL ptl, DWORD *eff)
    {
        if (drop_target_helper_ != 0)
        {
            POINT pt = { ptl.x, ptl.y };
            drop_target_helper_->Drop(obj, &pt, *eff);
        }
    }

    /* 
     *   IDropTargetHelper Show() interface.  When a drop target needs to
     *   scroll or otherwise repaint its window during a drag/drop operation,
     *   it should call this with show == FALSE before doing the scrolling or
     *   repainting, and then call this again with show == TRUE after that's
     *   done.  This ensures that there's no interference between our own
     *   painting and the painting done by the helper to provide the visual
     *   overlay of the object being dragged.
     */
    void DropHelper_Show(int show)
    {
        if (drop_target_helper_ != 0)
            drop_target_helper_->Show(show);
    }

    /* names of window classes registered with the system */
    static const char win_class_name[];
    static const char mdichild_win_class_name[];
    static const char mdiframe_win_class_name[];
    static const char mdiclient_win_class_name[];

    /* common message handler for all window types */
    virtual LRESULT common_msg_handler(HWND hwnd, UINT msg,
                                       WPARAM wpar, LPARAM lpar);

    /* determine whether the shift key is down */
    static int get_shift_key()
        { return (GetKeyState(VK_SHIFT) & 0x8000) != 0; }

    /* determine whether the control key is down */
    static int get_ctl_key()
        { return (GetKeyState(VK_CONTROL) & 0x8000) != 0; }

    /* determine whether the mouse buttons are down */
    static int get_lbtn_key()
        { return (GetKeyState(VK_LBUTTON) & 0x8000) != 0; }
    static int get_mbtn_key()
        { return (GetKeyState(VK_MBUTTON) & 0x8000) != 0; }
    static int get_rbtn_key()
        { return (GetKeyState(VK_RBUTTON) & 0x8000) != 0; }

    /* 
     *   synthesize a set of MK_xxx flags for the current key state - this
     *   provides flags equivalent to the values that WM_LBUTTONDOWN passes
     *   in its WPARAM parameter 
     */
    static int get_key_mk_state()
    {
        return (get_shift_key() ? MK_SHIFT : 0)
            + (get_ctl_key() ? MK_CONTROL : 0)
            + (get_lbtn_key() ? MK_LBUTTON : 0)
            + (get_rbtn_key() ? MK_RBUTTON : 0)
            + (get_mbtn_key() ? MK_MBUTTON : 0);
    }

    /*
     *   Size of the MENUITEMINFO structure, for use in GetMenuItemInfo and
     *   the like.  On win95 and NT4, the API's are intolerant of the larger
     *   structure size defined in the newer headers from Microsoft, so we
     *   must avoid using the true structure size and hard-code the old
     *   structure size instead.  This is an OS bug, but what can you do...  
     */
    static size_t menuiteminfo_size_;

    /*
     *   Size of REBARBANDINFO structure.  Versions prior to Vista are
     *   intolerant of the larger Vista structure size. 
     */
    static size_t rebarbandinfo_size_;

    /* size of IMAGELISTDRAWPARAMS structure (varies by Windows version) */
    static size_t imagelistdrawparams_size_;

protected:
    /*
     *   Allocate/free a timer.  Whenever an application wants to set a
     *   system timer with Windows, it must use a unique identifier for
     *   the timer.  alloc_timer_id() allocates timer identifier values;
     *   it returns zero if no more timer ID's are available.  When the
     *   timer is no longer needed, the identifier should be freed with
     *   free_timer_id().  These routines are static because timer ID's
     *   are global to the application.  
     */
    static int alloc_timer_id();
    static void free_timer_id(int id);
    
    /* track a popup context menu */
    void track_context_menu(HMENU menuhdl, int x, int y)
    {
        /* track the menu, using the standard top-left alignment flags */
        track_context_menu_ext(menuhdl, x, y, TPM_TOPALIGN | TPM_LEFTALIGN);
    }

    /* track a popup menu, specifying full flags */
    int track_context_menu_ext(HMENU menuhdl, int x, int y, DWORD flags);

    /* track the system menu as a context menu */
    void track_system_context_menu(int x, int y)
        { track_system_context_menu_ext(x, y, TPM_TOPALIGN | TPM_LEFTALIGN); }
    int track_system_context_menu_ext(int x, int y, DWORD flags)
    {
        /* track the default system context menu for the window */
        return track_system_context_menu_ext(
            x, y, flags, GetSystemMenu(handle_, FALSE));
    }
    int track_system_context_menu_ext(int x, int y, DWORD flags, HMENU menu);

    /*
     *   Begin/end tracking a popup menu.  While we're tracking a popup
     *   menu, we'll ignore WM_SETCURSOR messages, and simply set the
     *   cursor to an arrow.  Note that a caller that uses
     *   track_context_menu() to run a popup menu doesn't need to call
     *   these routines; these only need to be used if the caller shows a
     *   popup menu directly through the Windows API.  
     */
    void begin_tracking_popup_menu() { tracking_popup_menu_ = TRUE; }
    void end_tracking_popup_menu() { tracking_popup_menu_ = FALSE; }

    /*
     *   Initialize a submenu so that all of the items in the submenu use
     *   radio button checkmarks rather than ordinary checkmarks.  This
     *   should be used to initialize a menu that displays mutually
     *   exclusive choices. 
     */
    void set_menu_radiocheck(HMENU submenu);

    /* 
     *   set the radio button checkmark style on a range of menu items,
     *   inclusive of the first and last index 
     */
    void set_menu_radiocheck(HMENU submenu, int first, int last);

    /* set a single menu item to radio-check mdoe */
    void set_menuitem_radiocheck(HMENU menu, int cmd);

    /* message handler for standard (non-MDI) windows */
    static LRESULT CALLBACK std_message_handler(HWND hwnd, UINT msg,
                                                WPARAM wpar, LPARAM lpar);

    /* message handler for MDI frame windows */
    static LRESULT CALLBACK mdiframe_message_handler(
        HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar);

    /* message handler for MDI child windows */
    static LRESULT CALLBACK mdichild_message_handler(
        HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar);
    
    /* paint the window */
    virtual int do_paint();

    /*
     *   Get the window style flags, and the extended style flags (for
     *   window creation).  The default flags are suitable for a main
     *   window with a document border, resizability, a caption, and a
     *   system menu. 
     */
    virtual DWORD get_winstyle()
        { return (WS_OVERLAPPEDWINDOW | WS_BORDER
                  | WS_CAPTION | WS_SYSMENU
                  | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
                  | WS_SIZEBOX); }

    virtual DWORD get_winstyle_ex() { return WS_EX_WINDOWEDGE; }

    /*
     *   Paint the content area.  Most windows will not need to override
     *   do_paint(), which does the standard painting setup, but will
     *   instead override only this routine, which does the actual
     *   drawing.  The default routine simply erases the area with a white
     *   background. 
     */
    virtual void do_paint_content(HDC hdc, const RECT *area_to_draw);

    /* 
     *   Get the palette for painting.  This is required only if we're in a
     *   paletted video mode, and the window has a private palette, and it
     *   uses off-screen rendering.  By default, we don't have use a private
     *   palette, so we just return a null palette handle.  
     */
    virtual HPALETTE get_paint_palette() { return 0; }

    /* paint the window when it's minimized */
    virtual int do_paint_iconic();

    /* 
     *   Erase the background: returns TRUE if handled, FALSE if the default
     *   system handling should be used.  If the window is using off-screen
     *   rendering, we return TRUE by default, since the whole point of OSR
     *   is to avoid flicker by doing the background erase off-screen instead
     *   of directly on the display.  
     */
    virtual int do_erase_bkg(HDC) { return off_screen_render_; }

    /*
     *   Process mouse events.  These routines should return TRUE if the
     *   event was handled.  By default, these all simply return FALSE to
     *   indicate that the default Windows handling should apply.  
     */
    virtual int do_leftbtn_down(int /*keys*/, int /*x*/, int /*y*/,
                                int /*clicks*/)
        { return FALSE; }
    virtual int do_leftbtn_up(int keys, int x, int y);
    virtual int do_rightbtn_down(int /*keys*/, int /*x*/, int /*y*/,
                                 int /*clicks*/)
        { return FALSE; }
    virtual int do_rightbtn_up(int /*keys*/, int /*x*/, int /*y*/)
        { return FALSE; }
    virtual int do_mousemove(int keys, int x, int y);

    /* receive notification that we've lost capture */
    virtual int do_capture_changed(HWND new_capture_win);

    /*
     *   Process mouse events in the non-client area.  These routines
     *   return TRUE if the event was handled, false if not.  By default,
     *   these all simply return FALSE to indicate that the default
     *   Windows handling should apply.  
     */
    virtual int do_nc_leftbtn_down(int /*keys*/, int /*x*/, int /*y*/,
                                   int /*clicks*/, int /*hit_type*/)
        { return FALSE; }
    virtual int do_nc_leftbtn_up(int /*keys*/, int /*x*/, int /*y*/,
                                 int /*hit_type*/)
        { return FALSE; }
    virtual int do_nc_rightbtn_down(int /*keys*/, int /*x*/, int /*y*/,
                                    int /*clicks*/, int /*hit_type*/)
        { return FALSE; }
    virtual int do_nc_rightbtn_up(int /*keys*/, int /*x*/, int /*y*/,
                                  int /*hit_type*/)
        { return FALSE; }
    virtual int do_nc_mousemove(int /*keys*/, int /*x*/, int /*y*/,
                                int /*hit_type*/)
        { return FALSE; }

    /*
     *   Hit-test a mouse position in the non-client area.  Fill in
     *   (*hit_type) and return true if processed; return false to defer
     *   to the Windows default behavior.  
     */
    virtual int do_nc_hit_test(int /*x*/, int /*y*/, int * /*hit_type*/)
        { return FALSE; }


    /* 
     *   Process a user message - any message in the range WM_USER to 0x7fff
     *   (window-class-private messages internal to the application) or in
     *   the range 0xC000 to 0xFFFF (registered global messages, for
     *   inter-process communication) is passed to this routine.  We don't do
     *   anything by default.  
     */
    virtual int do_user_message(int /*msg*/, WPARAM, LPARAM)
        { return FALSE; }

    /*
     *   Handle a MIDI event.  By default, we'll coordinate with the basic
     *   MIDI file player class to handle the message; most subclasses
     *   shouldn't need to do anything with this. 
     */
    virtual int do_midi_event(UINT msg, WPARAM wpar, LPARAM lpar);

    /*
     *   Process a set-cursor event.  Returns true if the message was
     *   handled.  
     */
    virtual int do_setcursor(HWND, int /*hittest*/, int /*mousemsg*/)
        { return FALSE; }

    /*
     *   Handle WM_WINDOWPOSCHANGING and WM_WINDOWPOSCHANGED messages.
     *   Returns true if the message is handled, false if not.  
     */
    virtual int do_windowposchanging(WINDOWPOS *wp) { return FALSE; }
    virtual int do_windowposchanged(WINDOWPOS *wp) { return FALSE; }

    /* move the window */
    virtual void do_move(int x, int y);

    /* resize */
    virtual void do_resize(int mode, int x, int y);

    /* 
     *   synthesize a resize event - this can be used when some internal
     *   layout has changed and we want to recalculate positions using the
     *   normal resize code 
     */
    void synth_resize();

    /* enter/exit size/move operation */
    virtual void do_entersizemove() { }
    virtual void do_exitsizemove() { }

    /*
     *   Handle a WM_SHOWWINDOW event.  'show' is true if the window is
     *   being shown, false otherwise.  'status' is the function code:
     *   zero indicates that this is a simple ShowWindow() call; SW_xxx
     *   values (see the Win32 documentation for WM_SHOWWINDOW) indicate
     *   other causes.  Returns true if the message is handled, false if
     *   the default window procedure should be invoked.  
     */
    virtual int do_showwindow(int /*show*/, int /*status*/) { return FALSE; }

    /*
     *   Process a WM_COLORxxx message.  Returns a brush, if desired,
     *   which is used to paint the background of the control.  Returns
     *   null by default to paint with the default background color. 
     */
    virtual HBRUSH do_ctlcolor(UINT /*msg*/, HDC, HWND) { return 0; }

    /* process window creation message */
    virtual void do_create();

    /* 
     *   Process a close-window message.  Return true if closing should
     *   proceed, false if not. 
     */
    virtual int do_close();

    /* process a destroy window message */
    virtual void do_destroy();

    /*
     *   Process application activation/deactivation.  Returns true to
     *   suppress default handling by Windows, false to continue with system
     *   default processing.  We do nothing by default.  
     */
    virtual int do_activate_app(int /*flag*/, DWORD /*thread_id*/)
        { return FALSE; }

    /* 
     *   Process an activation/deactivation message.  Return true to
     *   suppress the default handling by Windows, false to continue with
     *   Windows default processing.  
     */
    virtual int do_activate(int flag, int minimized, HWND other_win);

    /* process non-client activation */
    virtual int do_ncactivate(int flag) { return FALSE; }

    /*
     *   Process an MDI activation/deactivation message; this message is
     *   received by MDI child windows.  By default, we'll call the normal
     *   do_activate routine so that processing in MDI vs. SDI mode can be
     *   handled by the same code; in situations where MDI and SDI
     *   activation need different behavior, this routine should be
     *   overridden.  Returns true if the activation was handled, false to
     *   continue with Windows default processing.  
     */
    virtual int do_mdiactivate(HWND old_active, HWND new_active)
    {
        if (old_active == handle_)
            return do_activate(WA_INACTIVE, FALSE, new_active);
        else if (new_active == handle_)
            return do_activate(WA_ACTIVE, FALSE, old_active);
        else
            return FALSE;
    }

    /*
     *   Process an MDI child activation message.  This message is
     *   received by MDI child windows when the user clicks on the
     *   window's title bar or moves or resizes it. 
     */
    virtual int do_childactivate() { return FALSE; }

    /*
     *   Process an MDI menu refresh message.  This message is passed to
     *   an MDI client window to refresh its "Window" menu.  Return true
     *   if handled, false if the message should be passed to the default
     *   window procedure. 
     */
    virtual int do_mdirefreshmenu() { return FALSE; }

    /* 
     *   Receive notification that an MDI child of this window has been
     *   created, destroyed, or activated.  This lets an MDI frame manage its
     *   list of MDI child windows, such as to maintain a tab-control list.
     *   'win' is the child window, and 'msg' is WM_CREATE, WM_DESTROY,
     *   WM_SHOW, WM_SIZE, or WM_MDIACTIVATE.  This is sent to the parent
     *   (MDI client) window.  By default we do nothing.  
     */
    virtual void mdi_child_event(
        CTadsWin *win, UINT msg, WPARAM wpar, LPARAM lpar) { }

    /* handle timer messages */
    virtual int do_timer(int timer_id);

    /* pass a message to the default window procedure */
    virtual LRESULT call_defwinproc(HWND hwnd, UINT msg,
                                    WPARAM wpar, LPARAM lpar)
    {
        /* let the system interface object handle it */
        return sysifc_->syswin_call_defwinproc(hwnd, msg, wpar, lpar);
    }

    /*
     *   Test whether we should always pass a given message to the default
     *   window procedure.  Certain window types -- in particular, MDI
     *   frame windows -- must always pass certain messages to default
     *   window procedure, whether or not they handle the message
     *   separately.  By default, this returns false, since we normally
     *   use the result of the virtual associated with the message to
     *   determine if we should pass the message to the default handler or
     *   not.  
     */
    int always_pass_message(int msg) const
        { return sysifc_->syswin_always_pass_message(msg); }

    /*
     *   Process keystrokes.  do_char() processes an ASCII character
     *   keystroke.  do_keydown() processes a raw virtual key event.
     *   do_syschar() processes a keystroke that had the ALT key down.
     *   do_hotkey() handles a registered hot-key message.  All of these
     *   should return true if they handled the event, false if not.  
     */
    virtual int do_char(TCHAR, long /*keydata*/) { return FALSE; }
    virtual int do_keydown(int /*virtual_key*/, long /*keydata*/)
        { return FALSE; }
    virtual int do_keyup(int /*virtual_key*/, long /*keydata*/)
        { return FALSE; }
    virtual int do_syskeydown(int /*virtual_key*/, long /*keydata*/)
        { return FALSE; }
    virtual int do_syschar(TCHAR /*ch*/, unsigned long /*keydata*/)
        { return FALSE; }
    virtual int do_hotkey(int /*id*/, unsigned int /*modifiers*/,
                          unsigned int /*vkey*/)
        { return FALSE; }

    /* 
     *   Handle a WM_MENUCHAR notification.  If the routine handles the
     *   notification, it must fill in *ret with the LRESULT to return to
     *   Windows, and return TRUE; otherwise, return FALSE to pass the
     *   notification to the default system window procedure.  
     */
    virtual int do_menuchar(TCHAR ch, unsigned int menu_type, HMENU menu,
                            LRESULT *ret)
        { return FALSE; }

    /*
     *   Handle a system ("window") menu keystroke.  This is invoked when we
     *   get a WM_SYSCOMMAND of type SC_KEYMENU, which indicates that the
     *   system menu was activated by an ALT key combination.  'ch' is the
     *   key that was combined with ALT to bring up the menu.  
     */
    virtual int do_syskeymenu(TCHAR ch) { return FALSE; }

    /* 
     *   Handle a system ("window") menu command.  This is invoked when we
     *   get a WM_SYSCOMMAND with anything but SC_KEYMENU as the command ID.
     *   'cmd' is the SC_xxx command ID (SC_CLOSE, SC_CONTEXTHELP, etc).
     *   
     *   If the command was activated with the mouse, x and y will give the
     *   screen coordinates of the mouse cursor.  If the command was
     *   activated via an accelerator key, y will be -1; if by a mnemonic
     *   ("shortcut") key, y is 0.  
     */
    virtual int do_syscommand(unsigned int cmd, int x, int y)
        { return FALSE; }

    /*
     *   Gain/lose focus
     */
    virtual void do_setfocus(HWND /*previous_focus*/)
    {
        /* notify my parent */
        notify_parent_focus(TRUE);
    }
    virtual void do_killfocus(HWND /*previous_focus*/)
    {
        /* notify my parent */
        notify_parent_focus(FALSE);
    }

    /*
     *   Notify the parent window of a focus change affecting this window.
     *   The do_setfocus() and do_killfocus() routines can use this method
     *   if parent notification is desired.  We notify the parent via a
     *   WM_NOTIFY message. 
     */
    void notify_parent_focus(int setting)
    {
        NMHDR nm;

        /* if there's no parent, there's no one to notify */
        if (GetParent(handle_) == 0)
            return;

        /* set up the NMHDR structure */
        nm.hwndFrom = handle_;
        nm.idFrom = 0;
        nm.code = (setting ? NM_SETFOCUS : NM_KILLFOCUS);

        /* send the message */
        SendMessage(GetParent(handle_), WM_NOTIFY, 0, (LPARAM)&nm);
    }

    /* 
     *   Process a WM_MOUSEACTIVATE message.  If this routine returns
     *   false, we'll call the default window procedure as usual.  If this
     *   returns true, we'll return the result value stored in '*result'. 
     */
    virtual int do_mouseactivate(HWND /*top_level_parent*/,
                                 int /*nc_hit_test*/,
                                 unsigned int /*mouse_message*/,
                                 LRESULT * /*result*/)
    {
        /* use the default window procedure's processing */
        return FALSE;
    }

    /* handle a system setting change notification */
    virtual int do_sys_setting_change(WPARAM /*system_param*/,
                                      const char * /*param_area*/)
    {
        return FALSE;
    }

    /* handle a system color change notification */
    virtual int do_sys_color_change()
    {
        /* not handled - invoke default window procedure */
        return FALSE;
    }

    /* receive notification that we've selected a menu item */
    virtual int menu_item_select(unsigned int /*item*/,
                                 unsigned int /*flags*/, HMENU /*menu*/)
        { return FALSE; }

    /* receive notification that we've closed a menu */
    virtual int menu_close(unsigned int /*item*/) { return FALSE; }

    /* initialize a popup menu that's about to be opened */
    virtual void init_menu_popup(HMENU menuhdl, unsigned int pos,
                                 int sysmenu);

    /*
     *   Handle owner-draw notifications.  These should return TRUE if they
     *   handle the event, FALSE if not.  
     */
    virtual int do_measure_item(int ctl_id, MEASUREITEMSTRUCT *mi)
        { return FALSE; }
    virtual int do_draw_item(int ctl_id, DRAWITEMSTRUCT *di)
        { return FALSE; }

    /*
     *   Register this window as an OLE drop target.  We won't do this by
     *   default, but a subclass can use this routine if it wants to be an
     *   OLE drop target.  If this routine is called, we'll automatically
     *   call drop_target_unregister before destroying the window. 
     */
    void drop_target_register();

    /* 
     *   Create the drop helper object.  This is done automatically by
     *   drop_target_register(), so there's normally no need to call this;
     *   however, if the subclass does its own drop target management, it can
     *   call this at construction to explicitly create the helper. 
     */
    void create_drop_target_helper();

    /*
     *   Unregister the window as an OLE drop target.  We'll automatically
     *   call this before destroying the window if the window was
     *   registered as a drop target, but a subclass can explicitly
     *   unregister with OLE at any time by calling this routine. 
     */
    void drop_target_unregister();

    /*
     *   Prepare to begin dragging out of this window.  This can be called
     *   in a mouse button down or mouse move routine to set up for
     *   dragging.  This doesn't actually initiate dragging, but records
     *   the current cursor position so that subsequent calls to
     *   drag_check() can determine if and when to start dragging.
     *   
     *   'already_have_capture' should be set to true if the window has
     *   already captured the mouse; if this is false, we'll capture the
     *   mouse ourselves.  
     */
    void drag_prepare(int already_have_capture);

    /*
     *   Check dragging.  After drag_prepare() has been called, this
     *   routine should be called whenever the mouse moves and the button
     *   is still down.  If dragging hasn't yet begun, but the mouse has
     *   now moved enough for dragging to begin, we'll begin dragging; if
     *   we're currently doing dragging, we'll continue it here.  Returns
     *   true if drag-and-drop occurred, false if not.  
     */
    int drag_check();

    /*
     *   End dragging.  If drag_prepare was called and dragging never
     *   occurred (i.e., drag_check never returned true), this must be
     *   called when the mouse is released or capture is forced away from
     *   this window.  It's harmless to call this if drag_check did return
     *   true.
     *   
     *   'already_released_capture' should be set to true if this routine
     *   was called as a result of notification of capture loss.  
     */
    void drag_end(int already_released_capture);


    /*
     *   Drag source support - get the IDataObject to be dragged.  We'll
     *   call this when we've decided we want to start a drag operation
     *   out of this window.  This must be overridden by windows that act
     *   as drag sources to provide a valid IDataObject implementation.
     *   
     *   This routine must add a reference to the data object on our
     *   behalf.  We will release our reference on the data object after
     *   the drag/drop operation is completed.  
     */
    virtual IDataObject *get_drag_dataobj() { return 0; }

    /*
     *   Get the valid drop effects.  We'll call this when we've decided
     *   we want to start a drag operation out of this window.  This can
     *   be overridden by windows that act as drag sources to customize
     *   the available operations; by default, we'll allow all of the drag
     *   effects. 
     */
    virtual ULONG get_drag_effects()
    {
        return (DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK
                | DROPEFFECT_SCROLL);
    }

    /*
     *   Receive notification that dragging is about to begin, and that it
     *   just ended.  drag_check() calls drag_pre when it's about to begin
     *   dragging, and drag_post when dragging is done.  We don't do
     *   anything by default, but subclasses can do any special processing
     *   to prepare for or clean up after dragging here.  
     */
    virtual void drag_pre() { }
    virtual void drag_post() { }

protected:
    /* system window interface object */
    CTadsSyswin *sysifc_;
    
    /* our handle */
    HWND handle_;

    /* our device context handle, valid during painting */
    HDC hdc_;

    /* 
     *   Our display device context handle.  When we're drawing directly
     *   onto the device, this will be the same as hdc_; when we're
     *   drawing into a memory DC, this will be the underlying display
     *   device context. 
     */
    HDC display_hdc_;

    /* parent window */
    CTadsWin *parent_;

    /*
     *   last click times and positions for the mouse buttons, for
     *   calculating double/triple/etc clicks 
     */
    BtnClick_t lbtn_click;
    BtnClick_t mbtn_click;
    BtnClick_t rbtn_click;

    /* basic cursor objects */
    HCURSOR arrow_cursor_;
    HCURSOR wait_cursor_;

    /* tracking popup */
    int tracking_popup_menu_ : 1;

    /* timer being used for drag-scroll tracking */
    int drag_scroll_timer_id_;

    /* 
     *   Timer ID list - this is a set of bits indicating whether timers
     *   have been allocated.  We allocate 32 bits for 32 timers ID's.
     */
    static unsigned long timers_alloced_;

    /* list of toolbars for which we're processing status events */
    HWND toolbars_[TADSWIN_TBMAX];
    int toolbar_cnt_;

    /* timer ID for the toolbar status processor */
    int tb_timer_id_;

    /* 
     *   external scrolling window - this is the window whose scrolling
     *   position we control through our child scrollbar controls 
     */
    CTadsWin *scroll_win_;

    /* 
     *   Accumulated scroll increment.  When we get WM_MOUSEWHEEL events
     *   smaller than WHEEL_DELTA, we accumulate the partial scrolls here
     *   until we have enough to scroll by one increment.  
     */
    int wheel_accum_;

    /* 
     *   starting cursor position of current drag operation; if
     *   drag_ready_ is true, we're watching for movement from this
     *   position, and will start a drag if the mouse moves far enough
     *   with the button down 
     */
    POINT drag_start_pos_;
    int drag_ready_ : 1;

    /* flag indicating that we captured the mouse for a drag operation */
    int drag_capture_ : 1;

    /* original keyboard state at start of drag operation */
    ULONG drag_start_key_;

    /* OLE IUnknown reference count */
    ULONG ole_refcnt_;

    /* flag indicating that we're registered with OLE as a drop target */
    int drop_target_regd_ : 1;

    /* 
     *   Flag: the window is maximized.  This flag provides a mechanism
     *   for keeping track of our maximized state independently of the
     *   normal Windows GetWindowPlacement() mechanism.  In some cases,
     *   GetWindowPlacement() won't give us useful information; for
     *   example, if we're an MDI child window, we need to find our
     *   maximized status through other means.
     */
    int maximized_ : 1;

    /* flag: if set, we'll use off-screen rendering by default */
    int off_screen_render_ : 1;

    /* drop target helper, if available */
    IDropTargetHelper *drop_target_helper_;

    /* high water mark for allocated timers */
    static int next_timer_id_;

    /* free timer list */
    static struct tadswin_timer_alo *free_timers_;

    /* in-use timer list */
    static struct tadswin_timer_alo *inuse_timers_;
};

/* ------------------------------------------------------------------------ */
/*
 *   Window with scrollbars 
 */

/* time (milliseconds) to wait between drag scrolling */
const long TADSWIN_DRAG_SCROLL_WAIT = 50;


class CTadsWinScroll: public CTadsWin
{
public:
    /* static class initialization/termination */
    static void class_init(class CTadsApp *app);
    static void class_terminate();

    /* construction/destruction */
    CTadsWinScroll(int has_vscroll, int has_hscroll);
    ~CTadsWinScroll();

    /* 
     *   Set sizebox style on or off.  This can be called at any time; we'll
     *   adjust our style accordingly.  
     */
    void set_has_sizebox(int f);

    /*
     *   Adjust coordinates from document coordinates to client window
     *   coordinates, by adjusting for the scrolling offset.  
     */
    long doc_to_screen_x(long x) const
        { return x - hscroll_ofs_ * get_hscroll_units(); }
    long doc_to_screen_y(long y) const
        { return y - vscroll_ofs_ * get_vscroll_units(); }
    CHtmlPoint doc_to_screen(CHtmlPoint pt) const
    {
        pt.x = doc_to_screen_x(pt.x); pt.y = doc_to_screen_y(pt.y);
        return pt;
    }
    CHtmlRect doc_to_screen(CHtmlRect r) const
    {
        r.offset(-hscroll_ofs_ * get_hscroll_units(),
                 -vscroll_ofs_ * get_vscroll_units());
        return r;
    }

    /*
     *   Adjust coordinates from client window coordinates to document
     *   coordinates 
     */
    long screen_to_doc_x(long x)
        { return x + hscroll_ofs_ * get_hscroll_units(); }
    long screen_to_doc_y(long y)
        { return y + vscroll_ofs_ * get_vscroll_units(); }
    CHtmlPoint screen_to_doc(CHtmlPoint pt)
        { pt.x = screen_to_doc_x(pt.x); pt.y = screen_to_doc_y(pt.y);
    return pt; }
    CHtmlRect screen_to_doc(CHtmlRect r)
        { r.offset(hscroll_ofs_ * get_hscroll_units(),
                   vscroll_ofs_ * get_vscroll_units());
          return r; }

    /* get the current scrollbar positions */
    long get_vscroll_pos() const { return vscroll_ofs_; }
    long get_hscroll_pos() const { return hscroll_ofs_; }

    /* handle a mouse-wheel scroll event */
    virtual int do_mousewheel(int keys, int dist, int x, int y);

    /*
     *   Do scrolling.  If use_pos is true, we'll ignore the scrollbar's
     *   internal control settings and use the pos value; otherwise, we'll
     *   use the scrollbar's settings.  When called directly (rather than
     *   in response to an event) to set the position, set use_pos to
     *   true; we normally set use_pos to false when handling an event,
     *   since we get better precision from the scrollbar internal control
     *   data than we can from the windows message.  
     */
    virtual void do_scroll(int vert, HWND sb_handle,
                           int scroll_code, long pos, int use_pos);

    /* get the default scrollbar handles */
    HWND get_vscroll_handle() const { return vscroll_; }
    HWND get_hscroll_handle() const { return hscroll_; }

    /*
     *   Set external scrollbars.  If the parent window (or another
     *   window) contains our scrollbar or scrollbars, it can tell us
     *   about the scrollar or scrollbars with this method.
     *   
     *   If ext_vscroll is null, the current scrollbar (internal or
     *   external) will not be affected.  Likewise ext_hscroll.
     *   
     *   The external window is responsible for reflecting WM_xSCROLL
     *   messages from the external scrollbars to our do_scroll() method.
     *   See CTadsWin::set_scroll_win().  The external window is also
     *   responsible for managing the location and size of the external
     *   scrollbars, since they belong to the external window and not to
     *   us.  We will, however, keep the scrollbar ranges adjusted, since
     *   we own the content that the scrollbars control.  
     */
    void set_ext_scrollbar(HWND ext_vscroll, HWND ext_hscroll);

    /* process MDI child activation */
    virtual int do_childactivate()
    {
        /* fix up scrollbars, in case we resized */
        adjust_scrollbar_positions();

        /* inherit default */
        return CTadsWin::do_childactivate();
    }

protected:
    /* process window creation message */
    void do_create();

    /* initialize the sizebox */
    void init_sizebox();

    /* resize the window */
    void do_resize(int mode, int x, int y);

    /* set control colors */
    HBRUSH do_ctlcolor(UINT msg, HDC hdc, HWND hwnd);

    /* handle timer messages */
    int do_timer(int timer_id);

    /* remember my position in the preferences */
    void set_pos_prefs();

    /*
     *   Get the scrollbar information.  This routine must be overridden
     *   to provide the proper ranges for the window.  Returns true if the
     *   scrollbar should be visible, false if not.  The default routine
     *   simply initializes the header of the structure.  
     */
    virtual int get_scroll_info(int /*vert*/, SCROLLINFO *info)
    {
        info->cbSize = sizeof(SCROLLINFO);
        info->fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
        return TRUE;
    }

    /*
     *   Adjust scrollbar ranges.  This is called after the window is
     *   resized, and should be called whenever the content is updated in
     *   such a way as to change the scrolling ranges.  The default
     *   routine has no effect; this must be overridden to calculate
     *   scrollbar ranges as appropriate for the window content.  
     */
    virtual void adjust_scrollbar_ranges() { }
    
    /*
     *   Set the positions of the scrollbars.  This is used after
     *   resizing the window to keep the scrollbars at the edges of the
     *   window, and also can be used when one of the scrollbars is being
     *   made visible or invisible, so that the other adjusts to fill the
     *   correct space. 
     */
    virtual void adjust_scrollbar_positions();

    /*
     *   Determine whether we track a scrollbar's thumb actively while
     *   the thumb is being dragged - if true, we scroll during thumb
     *   tracking, otherwise we wait until the thumb has been released to
     *   do the scrolling.  By default, we track directly, but when it's
     *   not practical to keep the window updated repeatedly during
     *   scrolling (which would be the case if it's slow to redraw the
     *   content), this should be overridden to return false.  
     */
    virtual int active_scroll_thumb_tracking(HWND)
        { return TRUE; }

    /*
     *   Get the area to be scrolled.  By default, we'll use the entire
     *   client area minus visible scrollbars.  If other non-scrolling
     *   frame features are present in the window's client area, this must
     *   be overridden to exclude those features.
     *   
     *   If 'vertical' is true, return the area for vertical scrolling;
     *   otherwise, return the area for horizontal scrolling.  These might
     *   be different in some cases; for example, if there's a header that
     *   scrolls horizontally but is fixed in place with respect to
     *   vertical scrolling, the header would be included in the
     *   horizontal scrolling area but not the vertical area.  
     */
    virtual void get_scroll_area(RECT *rc, int vertical) const;

    /*
     *   determine whether vertical and horizontal scrollbars are present
     *   and visible 
     */
    virtual int vscroll_is_visible() const { return FALSE; }
    virtual int hscroll_is_visible() const { return FALSE; }

    /*
     *   Get the number of pixels per scrolling unit.  When we scroll the
     *   window, we'll use this to determine how much we have to move the
     *   pixels on the screen for a given change in the scrollbar
     *   position.  The default is 16 pixels per unit, which is about the
     *   size of a character cell in a 12-point font.  
     */
    virtual long get_vscroll_units() const { return 16; }
    virtual long get_hscroll_units() const { return 16; }

    /*
     *   Notify the window that scrolling is about to occur.  The window
     *   may need to do some cleanup work, such as removing any xor
     *   drawing, that doesn't translate well across scrolling operations. 
     */
    virtual void notify_pre_scroll(HWND) { }

    /*
     *   Notify the window that scrolling has occurred.  The window
     *   should take note of the new position, but the scrollbar has
     *   already been updated and the window's pixels have been scrolled.
     *   The default implementation does nothing; this routine is provided
     *   so that the window subclass can fix up its internal positioning
     *   after scrolling has occurred.  
     */
    virtual void notify_scroll(HWND, long /*oldpos*/, long /*newpos*/) { }

    /*
     *   Initialize drag scrolling.  Sets a timer that will be called to
     *   simulate mouse move events when the mouse is being held outside
     *   the window.  
     */
    void start_drag_scroll();

    /*
     *   End drag scrolling.  This should be called when capture is
     *   released to remove the drag-scroll timer.  
     */
    void end_drag_scroll();

    /*
     *   Perform drag scrolling if appropriate.  We'll scroll by one unit in
     *   the appropriate direction if the mouse is outside the scrolling
     *   rectangle of the window, and enough time has elapsed since the last
     *   drag scrolling we did.  Returns true if we did any scrolling, false
     *   if not.
     *   
     *   If x_inset or y_inset are positive, they give the insets into the
     *   scrolling area for the neutral zone.  Normally these are zero, to
     *   indicate that we should scroll the window's contents only when the
     *   mouse is dragged outside the normal scrolling area.  However, it is
     *   sometimes desirable, such as during drag-and-drop operations, to
     *   scroll when the mouse strays into a border around the window frame,
     *   rather than requiring the mouse to leave the window frame; use the
     *   insets to establish the size of this interior border area, if
     *   desired.  
     */
    int maybe_drag_scroll(long x, long y, int x_inset, int y_inset);

    /* message filter hook for scrollbar events */
    static LRESULT CALLBACK sb_filter_proc(int code, WPARAM wpar,
                                           LPARAM lpar);

protected:
    /* scrollbars */
    HWND vscroll_;
    HWND hscroll_;

    /*
     *   Flags: scrollbars are external.  These are set to true when the
     *   scrollbars belong to an external window (such as our parent
     *   window), but still scroll content contained within our client
     *   area 
     */
    int ext_vscroll_ : 1;
    int ext_hscroll_ : 1;

    /* vertical and horizontal scrolling offsets */
    long vscroll_ofs_;
    long hscroll_ofs_;

    /* scrollbar visibility flags */
    int vscroll_vis_ : 1;
    int hscroll_vis_ : 1;

    /* sizebox control */
    HWND sizebox_;

    /* gray box at bottom right corner - used when both scrollbars present */
    HWND graybox_;

    /* flags indicating whether scrollbars are desired */
    int has_vscroll_ : 1;
    int has_hscroll_ : 1;

    /* 
     *   flag indicating whether a sizebox is desired in the corner
     *   between the vertical and horizontal scrollbars 
     */
    int has_sizebox_ : 1;

    /* flag indicating that we're doing drag scrolling */
    int in_drag_scroll_ : 1;

    /* last drag scroll time */
    DWORD drag_scroll_time_;

    /* our windows hook for message filter events */
    static HHOOK sb_filter_hook_;

    /* flag: scrollbar thumb tracking in progress */
    static int sb_thumb_track_;
};


/* ------------------------------------------------------------------------ */
/*
 *   Window creation hook.  This can be used to intercept the creation of
 *   system window objects, such as system dialog boxes, in order to subclass
 *   the window or change its attributes before the window is opened.  
 */
class CTadsWinCreateHook
{
public:
    CTadsWinCreateHook()
    {
        /* we haven't set up our hook yet */
        hhook_ = 0;
    }
    
    virtual ~CTadsWinCreateHook();

    /* 
     *   Set up the hook.  This must be called just before the system routine
     *   is called to create the window. 
     */
    void set_hook();

    /* 
     *   Remove the hook.  We handle this automatically when the object is
     *   destroyed or when the hook is invoked, so callers don't have to
     *   worry about this; however, this is provided in the public interface
     *   so that callers can explicitly remove the hook if desired. 
     */
    void unset_hook();

protected:
    /* 
     *   Process the window creation hook.  Subclasses should override this
     *   to carry out whatever special handling they want to apply when the
     *   window is created.  
     */
    virtual void do_create_hook(HWND hwnd, LPCREATESTRUCT lpcs) { }

    /* the hook callback */
    static LRESULT CALLBACK hookproc(int code, WPARAM wpar, LPARAM lpar);

    /* my hook handle */
    HHOOK hhook_;
};

/*
 *   A subclassing version of the creation hook.  This hooks the window
 *   creation and subclasses the window, so that the class can filter all of
 *   the window's subsequent messages. 
 */
class CTadsWinSubclassHook: public CTadsWinCreateHook
{
public:
    CTadsWinSubclassHook()
    {
        /* we haven't attached to our window yet */
        hwnd_ = 0;
        oldwinproc_ = 0;
    }
    
    virtual ~CTadsWinSubclassHook();

protected:
    /* on creation, we'll install our subclassed window procedure */
    virtual void do_create_hook(HWND hwnd, LPCREATESTRUCT lpcs);

    /* process a WM_CREATE */
    virtual void do_create(LPCREATESTRUCT lpcs) { }

    /* on destruction, we'll remove our subclassing */
    virtual void do_destroy();

    /* subclass window procedure */
    virtual LRESULT winproc(HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar);

    /* static entrypoint for window procedure */
    static LRESULT CALLBACK s_winproc(
        HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar)
    {
        /* get our 'self' object */
        CTadsWinSubclassHook *self =
            (CTadsWinSubclassHook *)GetProp(hwnd, scprop_);

        /* if it's null, there's nothing we can do */
        if (self == 0)
            return 0;

        /* call the virtual window procedure */
        return self->winproc(hwnd, msg, wpar, lpar);
    }

    /* our window handle */
    HWND hwnd_;

    /* the original window procedure */
    WNDPROC oldwinproc_;

    /* the property name we use for our subclass hook */
    static const char *scprop_;
};

#endif /* TADSWIN_H */
