//----------------------------------------------------------------------------
// ObjectWindows
// (C) Copyright 1991, 1994 by Borland International, All Rights Reserved
//
//   Implementation of TWindow.  This defines the basic behavior of all
//   Windows.
//----------------------------------------------------------------------------
#include <owl/owlpch.h>
#include <owl/window.h>
#include <owl/applicat.h>
#include <owl/appdict.h>
#include <owl/scroller.h>
#include <owl/gdiobjec.h>
#include <owl/menu.h>
#include <owl/framewin.h>
#include <stdlib.h>

DIAG_DECLARE_GROUP(OwlMsg);  // diagnostic group for message tracing
DIAG_DEFINE_GROUP_INIT(OWL_INI, OwlWin, 1, 0);  // diagnostic group for windows

//
// define to build registered Window's classname using the module name
//
//#define _OWL_BUILDUP_CLASSNAME

//
// define to use rtti to create unique window ids for the message cache
//
#if defined(__BORLANDC__) && !defined(BI_NO_RTTI)
# define OWL_RTTI_MSGCACHE
# define TYPE_UNIQUE_UINT32(t)    reinterpret_cast<uint32>(typeid(t).tpp)
#endif

//
// Externs defined in owl.cpp
//
extern LRESULT CALLBACK __export InitWndProc(HWND, uint, WPARAM, LPARAM);

extern WNDPROC       CreateInstanceThunk(TWindow*);
extern void          FreeInstanceThunk(WNDPROC);
extern void _OWLFUNC SetCreationWindow(TWindow*);
extern uint _OWLDATA GetWindowPtrMsgId;

//$---------------------------------------------------------------------------

DEFINE_RESPONSE_TABLE(TWindow)
  EV_WM_SETCURSOR,
  EV_WM_SIZE,
  EV_WM_MOVE,
  EV_WM_COMPAREITEM,
  EV_WM_DELETEITEM,
  EV_WM_DRAWITEM,
  EV_WM_MEASUREITEM,
  EV_WM_VSCROLL,
  EV_WM_HSCROLL,
  EV_WM_CHILDINVALID,
  EV_WM_ERASEBKGND,
  EV_WM_PAINT,
  EV_WM_LBUTTONDOWN,
  EV_WM_KILLFOCUS,
#if defined(BI_PLAT_WIN32)
  EV_MESSAGE(WM_CTLCOLORMSGBOX, EvWin32CtlColor),
  EV_MESSAGE(WM_CTLCOLOREDIT, EvWin32CtlColor),
  EV_MESSAGE(WM_CTLCOLORLISTBOX, EvWin32CtlColor),
  EV_MESSAGE(WM_CTLCOLORBTN, EvWin32CtlColor),
  EV_MESSAGE(WM_CTLCOLORDLG, EvWin32CtlColor),
  EV_MESSAGE(WM_CTLCOLORSCROLLBAR, EvWin32CtlColor),
  EV_MESSAGE(WM_CTLCOLORSTATIC, EvWin32CtlColor),
#endif
  EV_WM_CREATE,
  EV_WM_CLOSE,
  EV_WM_DESTROY,
  EV_WM_NCDESTROY,
  EV_COMMAND(CM_EXIT, CmExit),
  EV_WM_QUERYENDSESSION,
  EV_WM_SYSCOLORCHANGE,
  EV_WM_INITMENUPOPUP,
END_RESPONSE_TABLE;


//$---------------------------------------------------------------------------
//
//
TCommandEnabler::TCommandEnabler(uint id, HWND hWndReceiver)
  : Id(id), HWndReceiver(hWndReceiver)
{
  Handled = false;
}

void
TCommandEnabler::Enable(bool)
{
  Handled = true;
}

//$---------------------------------------------------------------------------

//
// constructor for a TWindow.  if a parent window is passed, adds the TWindow
// to its parent's list of children
//
TWindow::TWindow(TWindow*        parent,
                 const char far* title,
                 TModule*        module)
{
  Thunk = 0;  // remember that we still need to initialize
  Init(parent, title, module);
}

//$---------------------------------------------------------------------------
//
// constructor for a TWindow which is being used as an alias for a
// non-OWL window.  This ctor is generally not used by derived classes
//
TWindow::TWindow(HWND hWnd, TModule* module)
{
  // Perform preliminary initialization
  //
  HWindow = hWnd;
  Title = 0;
  Thunk = 0;  // remember that we still need to initialize

  // If the receiver's parent is an OWL window then pass the window to Init()
  // so the receiver can be added to the parent's list of children
  //
  HWND      hParent = GetParent();
  TWindow*  parent = hParent ? GetWindowPtr(hParent) : 0;
  Init(parent, module);

  // Thunk the window
  //
  SubclassWindowFunction();

  // Copy over the title, styles, the coordinates & the id
  //
  GetWindowTextTitle();
  GetHWndState();

  // Keep track that this is an alias object & that it is already created
  //
  SetFlag(wfAlias | wfFullyCreated);

  // Unique id may have been set inadvertantly to TWindow by the above
  // GetWindowTextTitle, et. al. Reset it just in case
  //
  SetUniqueId();
}

//$---------------------------------------------------------------------------
//
// Protected constructor for use by immediate virtually derived classes.
// Immediate derivitives must call Init() before constructions are done.
//
TWindow::TWindow()
{
  Thunk = 0;  // remember that we still need to initialize
}

//
// Normal initialization of a default constructed TWindow. Is ignored
// if called more than once.
//
void
TWindow::Init(TWindow*        parent,
              const char far* title,
              TModule*        module)
{
  if (!Thunk) {
    Init(parent, module);
    Title = strnewdup(title);
    DefaultProc = 0;
    HWindow = 0;
    EnableAutoCreate();

    // initialize creation attributes
    //
    Attr.Style = WS_CHILD | WS_VISIBLE;
    Attr.X = Attr.Y = Attr.W = Attr.H = 0;
    Attr.ExStyle = 0;
    Attr.Id = 0;
  }
}

//
// Private initializer function that does the bulk of the work
//
void
TWindow::Init(TWindow* parent, TModule* module)
{
  ZOrder = 0;
  ChildList = 0;
  Flags = wfDeleteOnClose;
  TransferBuffer = 0;
  Parent = parent;
  Attr.Param = 0;
  Attr.Menu = 0;
  Attr.AccelTable = 0;
  hAccel = 0;
  Thunk = ::CreateInstanceThunk(this);
  Scroller = 0;

  CursorModule = 0;
  CursorResId = 0;
  HCursor = 0;
  BkgndColor = NoColor;

  if (Parent)
    Parent->AddChild(this);
  else
    SiblingList = 0;

  // Use passed module if one, else get parent's. If no parent, use app
  //
  if (Parent) {
    Application = Parent->GetApplication();
    Module = module ? module : Parent->GetModule();
  }
  else {
    Module = module ? module : 0;
    Application = TYPESAFE_DOWNCAST(Module,TApplication);
    if (!Application) {
      Application = ::GetApplicationObject();
      if (!Module)
        Module = Application;
    }
    CHECK(Application);
  }
  SetUniqueId();

  TRACEX(OwlWin, 1, "TWindow constructed @" << (void*)this);
}

//
// installs the thunk as the window function and saves the old window function
// in "DefaultProc"
//
void
TWindow::SubclassWindowFunction()
{
  PRECONDITION(HWindow != 0);
#if defined(BI_PLAT_WIN32)
  uint32 processId;
  ::GetWindowThreadProcessId(HWindow, &processId);
  if (processId == ::GetCurrentProcessId()) {
#else
  if (::GetWindowTask(HWindow) == ::GetCurrentTask()) {
#endif
    DefaultProc = (WNDPROC)SetWindowLong(GWL_WNDPROC, uint32(GetThunk()));
    CHECK(DefaultProc != GetThunk());  // can only be called once!
  }
}

//
// Destructor for a TWindow: disposes of each window in its ChildList
// and removes itself from a non-0 parent's list of children
//
// Destroys a still-associated MS-Windows interface element and frees
// the instance thunk used for association of an MS-Windows element
// to the TWindow
//
// Disposes of its Scroller if the TScroller object was constructed
//
static void shutDown(TWindow* win, void*) {
  win->Destroy();
  delete win;
}

TWindow::~TWindow()
{
  // Flush the cache so that messages dont get dispatched to a
  // already-destructed derived class
  //
  void CacheFlush(uint32 id);
  CacheFlush(UniqueId);

  // ShutDown children first, so the Owl objects get a chance to clean up
  //
  ForEach(shutDown);

  // If the HWindow is still around, then destroy it or unlink from it as
  // appropriate.
  //
  if (HWindow) {
    // for aliases:
    //  - we are destructing the C++ object but not destroying the MS window
    //  - restore the original window function
    //
    if (IsFlagSet(wfAlias)) {
      // May want to check WNDPROC against Thunk to see if its still us
      // and not restore if it's not us...
      //
      WARNX(OwlWin, GetWindowLong(GWL_WNDPROC) != (uint32)GetThunk(), 0,
            "Restoring old WndProc after foreign subclass of:" << *this);
      SetWindowLong(GWL_WNDPROC, uint32(DefaultProc));
    }
    // for non-aliases:
    //  - destroy the windows element
    // this is not a normal condition and is a safety net only
    //
    else {
      WARNX(OwlWin, HWindow, 0, "Destroying from TWindow::~TWindow: " << *this);
      Destroy();
    }
  }

  if (Parent)
    Parent->RemoveChild(this);

  if (IsFlagSet(wfMainWindow))
    GetApplication()->MainWindow = 0;
  if (Application)
    Application->Uncondemn(this);

  // Delete menu id string, scroller, title, cursor & thunk
  //
  if (Attr.Menu.IsString())
    delete [] (char far*)Attr.Menu;  // assume strnewdup was used

  // delete scroller if there is one, by casting up to base and relying on
  // virtual dtor to avoid pulling in TScroller object code.
  //
  if (Scroller) {
    delete (TStreamableBase*)Scroller;
    Scroller = 0;
  }

  if (HIWORD(Title))
    delete [] Title;

  if (HCursor && CursorModule)
    SetCursor(0, 0);

  ::FreeInstanceThunk(Thunk);
  TRACEX(OwlWin, 1, "TWindow destructed @" << (void*)this);
}

#if defined(BI_MULTI_THREAD)
//
// overrides TEventHandler::Dispatch() to handle multi-thread synchronization
//
LRESULT TWindow::Dispatch(TEventInfo& info, WPARAM wp, LPARAM lp)
{
  TApplication::TAppLock Lock(*GetApplication());
  return TEventHandler::Dispatch(info, wp, lp);
}

#endif

//
//
//
bool
TWindow::PreProcessMsg(MSG& msg)
{
  return hAccel ? ::TranslateAccelerator(HWindow, hAccel, &msg) : false;
}

//
//
//
bool
TWindow::IdleAction(long /*idleCount*/)
{
  return false;  // we don't need any more time for now
}

//
// Respond to this message by broadcasting it to all children
//
void
TWindow::EvSysColorChange()
{
  ChildBroadcastMessage(WM_SYSCOLORCHANGE);
}

//
// Removes the passed pointer to a child window from the linked list of
// sibling windows which the TWindow's ChildList points to
//
void
TWindow::RemoveChild(TWindow* child)
{
  if (child && ChildList) {
    TWindow*  lastChild = ChildList;
    TWindow*  nextChild = lastChild;

    while (nextChild->SiblingList != lastChild &&
           nextChild->SiblingList != child) {
      nextChild = nextChild->SiblingList;
    }

    if (nextChild->SiblingList == child) {
      if (nextChild->SiblingList == nextChild)
        ChildList = 0;

      else {
        if (nextChild->SiblingList == ChildList)
          ChildList = nextChild;

        nextChild->SiblingList = nextChild->SiblingList->SiblingList;
      }
    }
  }
}

//
// Reparents this window to a new owl parent window. Also sets the windows
// parent (owner)
//
void
TWindow::SetParent(TWindow* newParent)
{
  if (Parent != newParent) {
    if (Parent)
      Parent->RemoveChild(this);

    SiblingList = 0;

    Parent = newParent;

    if (Parent)
      Parent->AddChild(this);
  }
  // tell Windows about the change too, if appropriate
  //
  if (HWindow && Parent && GetParent() != Parent->HWindow) {
    if (newParent) {
      if (newParent->HWindow)
        ::SetParent(HWindow, newParent->HWindow);
    }
    else
      ::SetParent(HWindow, 0);
  }
}

//
// Default behavior for updating document title is to pass it to parent frame
//
bool
TWindow::SetDocTitle(const char far* docname, int index)
{
  return Parent ? Parent->SetDocTitle(docname, index) : false;
}

//
// Wildcard check used for child id notifications
//
static bool wildcardCheck(TGenericTableEntry __RTFAR& entry,
                          TEventHandler::TEventInfo& info) {
  return entry.Id == info.Id && (entry.Msg == UINT_MAX || entry.Msg == info.Msg);
}

//
// handles default processing of events
//
// this includes continued processing of menu/accelerators and child Id
// notifications
//
LRESULT
TWindow::DefaultProcessing()
{
  TCurrentEvent& currentEvent = GetCurrentEvent();

  if (currentEvent.Message == WM_COMMAND_ENABLE) {
    if (currentEvent.Win != this) {
      TWindow*          receiver = Parent ? Parent : currentEvent.Win;
      TCommandEnabler&  commandEnabler = *(TCommandEnabler*)currentEvent.LParam;
      TEventInfo        eventInfo(WM_COMMAND_ENABLE, commandEnabler.Id);

      if (receiver->Find(eventInfo))
        return receiver->Dispatch(eventInfo, 0, currentEvent.LParam);
    }

    return 0;
  }

  if (currentEvent.Message != WM_COMMAND)
    return DefWindowProc(currentEvent.Message, currentEvent.WParam, currentEvent.LParam);

  //
  // currentEvent.Message == WM_COMMAND
  //
#if defined(BI_PLAT_WIN16)
  uint    notifyCode = HIWORD(currentEvent.LParam);
#else
  uint    notifyCode = HIWORD(currentEvent.WParam);
#endif
  uint    id = LOWORD(currentEvent.WParam);
  HWND    hWndCtl = (HWND)(uint)currentEvent.LParam;

  if (currentEvent.Win == this) {
    //
    // menu command originally destined for the receiver. defer to the app
    //
    if (hWndCtl == 0) {
      TEventInfo     eventInfo(0, id);
      TApplication*  app = GetApplication();

      if (app->Find(eventInfo)) {
        app->Dispatch(eventInfo, eventInfo.Id);
        return 0;
      }
      WARNX(OwlMsg, notifyCode<=1, 0, "Unprocessed WM_COMMAND(id=" << id << ")");
    }
    //
    // originally destined for the receiver and the receiver has called us so
    // default processing can occur
    //
    // unpack the original parameters and call DefWindowProc()
    //
    return DefWindowProc(currentEvent.Message, currentEvent.WParam, currentEvent.LParam);
  }
  else {
    TWindow*        receiver;
    uint            wParam;
    TEqualOperator  equal = 0;

    if (hWndCtl == 0) {
      //
      // menu/accelerator/push button
      //
      // either give the message to the receiver's parent or the original
      // receiver of the message
      //
      receiver = Parent ? Parent : currentEvent.Win;

      //
      // "notifyCode" is either 0 or 1 depending on whether this is from an
      // accelerator; however, we want to explicitly look for a 0...
      //
      notifyCode = 0;

      wParam = id;  // pass along "id" in case the receiver wants it
    }
    else {
      //
      // child id notification sent to the child and the child has called us
      //
      // offer the parent an opportunity to handle the notification
      //
      // NOTE: use equal function that will do a wildcard search
      //
      equal = wildcardCheck;
      receiver = currentEvent.Win;

      //
      // the child window identifier shouldn't be 0, but if it is then it will
      // look like we are searching for a WM_ message with value "notifyCode"
      //
      if (receiver->IsFlagSet(wfAlias) || id == 0)
        return receiver->DefWindowProc(currentEvent.Message, currentEvent.WParam, currentEvent.LParam);

      wParam = notifyCode;  // pass notification code in case receiver wants it
    }

    TEventInfo  eventInfo(notifyCode, id);

    if (receiver->Find(eventInfo, equal))
      return receiver->Dispatch(eventInfo, wParam);

    else
      return receiver->DefaultProcessing();
  }
}

//
// processing for WM_COMMAND
//
LRESULT
TWindow::EvCommand(uint id, HWND hWndCtl, uint notifyCode)
{
  TWindow*        receiver = this;
  uint            wParam;
  TEqualOperator  equal = 0;

  if (hWndCtl == 0) {
    //
    // menu/accelerator
    //
    // "notifyCode" is either 0 or 1 depending on whether this is from an
    // accelerator; however, we want to explicitly look for a 0...
    //
    notifyCode = 0;

    //
    // pass the "id" along in case the user wants it...
    //
    wParam = id;
  }
  else {
    //
    // child id notification
    //
    TWindow*  child = GetWindowPtr(hWndCtl);

    //
    // pass the "notifyCode" along in case the user wants it...
    //
    wParam = notifyCode;

    if (child) {
      //
      // give the child first crack at the event
      //
      receiver = child;
      id = UINT_MAX;  // special designator for child Id notifications that are
                      // handled at the child
    }
    else {
      //
      // offer the parent an opportunity to handle the notification
      //
      // NOTE: use equal function that will do a wildcard search
      //
      equal = wildcardCheck;

      //
      // the child window identifier shouldn't be 0, but if it is then it will
      // look like we are searching for a WM_ message with value "notifyCode"
      //
      if (id == 0)
        return DefaultProcessing();
    }
  }

  TEventInfo  eventInfo(notifyCode, id);

  if (receiver->Find(eventInfo, equal))
    return receiver->Dispatch(eventInfo, wParam);

  else
    return receiver->DefaultProcessing();
}

//
//
//
void
TWindow::EvCommandEnable(TCommandEnabler& commandEnabler)
{
  TEventInfo  eventInfo(WM_COMMAND_ENABLE, commandEnabler.Id);

  if (Find(eventInfo))
    Dispatch(eventInfo, 0, (LPARAM)&commandEnabler);
}

//
// Don't process for windows out of our window tree (esp. other apps)
//
void
TWindow::RouteCommandEnable(HWND hInitCmdTarget, TCommandEnabler& commandEnabler)
{
  // Extra processing for commands & commandEnablers: send the command down the
  // command chain if we are the original receiver
  //
  if (commandEnabler.IsReceiver(*this)) {
    HWND  hCmdTarget = hInitCmdTarget;
    while (hCmdTarget && hCmdTarget != *this) {
      TWindow*  cmdTarget = GetWindowPtr(hCmdTarget);

      if (cmdTarget) {
        cmdTarget->EvCommandEnable(commandEnabler);

        if (commandEnabler.GetHandled())
          return;
      }
      hCmdTarget = ::GetParent(hCmdTarget);
    }
  }

  // Always call base handler
  //
  TWindow::EvCommandEnable(commandEnabler);

  // No one explicitly enabled/disabled the command via the enabler, so run up
  // the command chain checking for any one who is going to handle the command
  // itself; if not then disable it...
  // Don't do this for command senders that don't actually generate commands,
  // like popup menu items.
  //
  if (commandEnabler.IsReceiver(*this) && !commandEnabler.GetHandled()
      && commandEnabler.SendsCommand()) {
    bool        enable = false;
    TEventInfo  eventInfo(0, commandEnabler.Id);

    HWND  hCmdTarget = hInitCmdTarget;
    while (true) {
      TWindow*  cmdTarget = GetWindowPtr(hCmdTarget);

      if (cmdTarget && cmdTarget->Find(eventInfo)) {
        enable = true;  // command will be handled, leave sender alone
        break;
      }
      if (!hCmdTarget || hCmdTarget == *this)
        break;

      hCmdTarget = ::GetParent(hCmdTarget);
    }

    if (!enable) {
      // check if the app wants to handle it
      //
      TEventInfo    enableInfo(WM_COMMAND_ENABLE, commandEnabler.Id);
      TApplication* app = GetApplication();
      if (app->Find(enableInfo)) {
        app->Dispatch(enableInfo, 0, (LPARAM)&commandEnabler);
        if (commandEnabler.GetHandled())
          return;
      }
      enable = app->Find(eventInfo);
    }

    commandEnabler.Enable(enable);
  }
}

//
// specifies default processing for an incoming message
//
// calls original window proc that we subclassed, using ::CallWindowProc to
// make sure that registers get setup correctly.
//
LRESULT
TWindow::DefWindowProc(uint message, WPARAM wParam, LPARAM lParam)
{
  CHECK(DefaultProc);
  LRESULT result = ::CallWindowProc(DefaultProc, HWindow, message, wParam, lParam);
  GetApplication()->ResumeThrow();
  return result;
}


static const int     msgCacheSize = 31;
struct TCacheEntry {
  uint32                       UniqueId;
  TGenericTableEntry  __RTFAR* Entry;
  uint                         Msg;
  int                          Delta;  // adjustment to "this" pointer

  void Set(uint32 uniqueId, uint msg, TGenericTableEntry __RTFAR* entry, int delta = 0) {
    UniqueId = uniqueId;
    Entry = entry;
    Msg = msg;
    Delta = delta;
  }
  bool  Hit(uint msg, uint32 uniqueId) {return msg == Msg && uniqueId == UniqueId;}

  static uint  Key(uint32 id, uint msg) {return (uint(id) ^ msg) % msgCacheSize;}
};

uint32 TWindow::LastUniqueId = 0;

static TCacheEntry  msgCache[msgCacheSize];
static bool         cacheEnabled = true;

void CacheFlush(uint32 id) {
  for (int i = 0; i < msgCacheSize; i++)
    if (msgCache[i].UniqueId == id)
      msgCache[i].Msg = 0;
}

//
// If rtti is available, then use it get an id for this window that is unique
// on a per-class basis.
//
// Without rtti, use a per-instance unique id. Less efficient, but safe without
// rtti.
//
void
TWindow::SetUniqueId()
{
#if defined(OWL_RTTI_MSGCACHE)
  UniqueId = 0;
#else
  if (++LastUniqueId == 0) {
    //
    // numbers wrapped around. disable the cache to be safe...
    //
    LastUniqueId = 1;
    cacheEnabled = false;
  }
  UniqueId = LastUniqueId;
#endif
}

//
// if the message is WM_COMMAND calls EvCommand(); otherwise looks for a handler
// in the message response table
//
LRESULT
TWindow::WindowProc(uint msg, WPARAM wParam, LPARAM lParam)
{
  // Handle WM_COMMAND_ENABLE command enable msgs by directly calling the
  // virtual EvCommandEnable
  //
  if (msg == WM_COMMAND_ENABLE) {
    EvCommandEnable(*(TCommandEnabler*)lParam);
    return 0;
  }
  // Handle WM_COMMAND command msgs by directly calling the
  // virtual EvCommand
  //
  else if (msg == WM_COMMAND) {
#if defined(BI_PLAT_WIN16)
    return EvCommand(LOWORD(wParam), (HWND)(uint)lParam, HIWORD(lParam));
#else
    return EvCommand(LOWORD(wParam), (HWND)(uint)lParam, HIWORD(wParam));
#endif
  }

  // Handle all other messages by looking up and dispatching using the
  // response tables
  //
  else {
#if defined(OWL_RTTI_MSGCACHE)
    if (!UniqueId)
      UniqueId = TYPE_UNIQUE_UINT32(*this);
#endif
    uint        key = TCacheEntry::Key(UniqueId, msg);
    TEventInfo  eventInfo(msg);

    // Check the cache. A cache hit may be an RT handler, or an RT miss.
    //
    if (cacheEnabled && msgCache[key].Hit(msg, UniqueId)) {
      eventInfo.Entry = msgCache[key].Entry;
      if (eventInfo.Entry) {
        TRACEX(OwlMsg, 1, MsgName(msg) << "* => " << *this);
        eventInfo.Object = (GENERIC*)(((char*)this) + msgCache[key].Delta);
        return Dispatch(eventInfo, wParam, lParam);

      } // else fall out & do default below
    }
    // Perform the lookup on this window.
    //
    else if (Find(eventInfo)) {
      TRACEX(OwlMsg, 1, MsgName(msg) << " => " << *this);
      msgCache[key].Set(UniqueId, msg, eventInfo.Entry, int(eventInfo.Object) - int(this));
      return Dispatch(eventInfo, wParam, lParam);
    }
    else  // not found
      msgCache[key].Set(UniqueId, msg, 0);  // Cache no-handler entries too

    // Behavior for messages that have no handler. If this is the main window,
    // then give the app a chance to handle the message. If not the main
    // window, or if the app had no handler, just call DefWindowProc() to
    // pass the message back to whomever we subclassed
    //
    if (IsFlagSet(wfMainWindow)) {
      TEventInfo cmdEventInfo(msg, wParam);
      if (GetApplication()->Find(cmdEventInfo))
        return GetApplication()->Dispatch(cmdEventInfo, wParam, lParam);
      if (GetApplication()->Find(eventInfo))
        return GetApplication()->Dispatch(eventInfo, wParam, lParam);
    }
    return DefWindowProc(msg, wParam, lParam);
  }
}

//
// Save current event and call this window's WindowProc. Handles exceptions,
// error checking, & GetWindowPtr messages.
//
LRESULT
TWindow::HandleMessage(uint msg, WPARAM wParam, LPARAM lParam)
{
  // save event away in "CurrentEvent"
  //
  TCurrentEvent& currentEvent = GetCurrentEvent();
  TCurrentEvent  saveEvent = currentEvent;  // for nested calls
  currentEvent.Win = this;
  currentEvent.Message = msg;
  currentEvent.WParam = wParam;
  currentEvent.LParam = lParam;

  // call window object's WindowProc virtual function
  //
  LRESULT  result;
  result = WindowProc(msg, wParam, lParam);
  currentEvent = saveEvent;  // restore previous event to current event
  return result;
}

//
// Window function called for normal, initialized, OWL windows via a
// per-window thunk. 'this' ptr is setup in register(s) by the thunk before
// StdWndProc is called.
//
#if defined(BI_PLAT_WIN16)
LRESULT
FAR PASCAL __export
StdWndProc(HWND, uint msg, WPARAM wParam, LPARAM lParam)
{
  return ((TWindow*)MK_FP(_ES,_BX))->ReceiveMessage(msg, wParam, lParam);
}
#else
LRESULT
FAR PASCAL _stdcall
StdWndProc(HWND, uint msg, WPARAM wParam, LPARAM lParam)
{
  return ((TWindow*)_EAX)->ReceiveMessage(msg, wParam, lParam);
}
#endif

//
// First member function that receives messages from windows thru the
// thunk & then StdWndProc. Deals with exception suspension for non-NT
// environments.
//
LRESULT
TWindow::ReceiveMessage(uint msg, WPARAM wParam, LPARAM lParam)
{
  // if it's a "GetWindowPtr" message, then return pointer to this window
  //
  if (msg == ::GetWindowPtrMsgId && (!lParam || lParam == LPARAM(Application)))
    return LRESULT(this);

#if defined(BI_PLAT_WIN32)
  static bool exceptionOK =
                !(::GetVersion()&0x80000000) || (::GetVersion()&0xFF) >= 4;
  if (exceptionOK)
    return HandleMessage(msg, wParam, lParam);
#endif
  TRY {
    return HandleMessage(msg, wParam, lParam);
  }
  CATCH( (TXOwl& x) {
    GetApplication()->SuspendThrow(x);
  })
  CATCH( (xalloc& x) {
    GetApplication()->SuspendThrow(x);
  })
  CATCH( (xmsg& x) {
    GetApplication()->SuspendThrow(x);
  })
  CATCH( (Bad_cast& x) {
    GetApplication()->SuspendThrow(TApplication::xsBadCast);
  })
  CATCH( (Bad_typeid& x) {
    GetApplication()->SuspendThrow(TApplication::xsBadTypeid);
  })
  CATCH( (...) {
    GetApplication()->SuspendThrow(TApplication::xsUnknown);
  })
  ENDCATCH

  // When exceptions are disabled CATCH is defined as empty, so the only code
  // is the TRY block, making this return unreachable
  //
# pragma warn -rch
  return msg == WM_CREATE ? -1 : 0;  // default value returned when exception caught
# pragma warn .rch
}

//
// Determine the object pointer by sending a GetWindowPtrMsgId message to the
// window.  When the first StdWndProc() in the subclass chain receives this
// message it returns a pointer to the object (via the thunking mechanism).
// If app is non-zero, then the process makes sure that the corresponding
// TWindow is returned.
//
TWindow* _OWLFUNC GetWindowPtr(HWND hWnd, const TApplication* app)
{
  if (!hWnd /* && ::IsWindow(hWnd) */)  // could also check handle validity
    return 0;

#if defined(BI_DATA_NEAR)
  LPARAM param2 = app ? LPARAM(app) : 0;
#else
  LPARAM param2 = LPARAM(app);
#endif

  // Avoid SendMessage to cutdown the overhead & message spy tool flooding
  //
  // Under Win16, need to fallback to SendMessage when the given hWnd is owned
  // by another task. Using CallWindowProc on it would result in a bad SS
  // setup when it received the message.
  //
  // Under Win32 don't even try if it is not our process. Some Win32's will
  // return a wndProc that crashes.
  //
#if defined(BI_PLAT_WIN16)
  if (::GetWindowTask(hWnd) != ::GetCurrentTask())
    return (TWindow*)::SendMessage(hWnd, GetWindowPtrMsgId, 0, param2);
#else
  uint32 processId;
  ::GetWindowThreadProcessId(hWnd, &processId);
  if (processId != ::GetCurrentProcessId())
    return 0;
#endif

  WNDPROC wndProc = (WNDPROC)::GetWindowLong(hWnd, GWL_WNDPROC);
  if (!wndProc)
    return 0;
  return (TWindow*)::CallWindowProc(wndProc, hWnd, GetWindowPtrMsgId, 0, param2);
}

//
// response method for an incoming WM_CREATE message
//
// Call virtual SetupWindow to give derived classes a chance to set things up
// now that we are created & have an HWindow
//
int
TWindow::EvCreate(CREATESTRUCT far&)
{
  SetupWindow();
  SetFlag(wfFullyCreated);
  return (int)DefaultProcessing();
}

//
// regular windows never hold focus child handles--just say no.
//
bool
TWindow::HoldFocusHWnd(HWND /*hWndLose*/, HWND /*hWndGain*/)
{
  return false;
}

//
// handle WM_KILLFOCUS so that we can have a parent window hold onto our
// HWindow and possibly restore focus later.
//
void
TWindow::EvKillFocus(HWND getFocus)
{
  // Follow the parent chain back until a window volunteers to hold our handle
  //
  if (IsFlagSet(wfFullyCreated)) {
    TWindow* holdWin = Parent;
    while (holdWin && !holdWin->HoldFocusHWnd(HWindow, getFocus))
      holdWin = holdWin->Parent;
  }

  DefaultProcessing();
}

//
// response method for an incoming WM_SIZE message
//
// saves the normal size of the window in Attr
//
// also calls the SetPageSize() and SetBarRange() methods of the TWindow's
// scroller, if it has been constructed
//
void
TWindow::EvSize(uint sizeType, TSize&)
{
  if (Scroller && sizeType != SIZE_MINIMIZED) {
    Scroller->SetPageSize();
    Scroller->SetSBarRange();
  }

  if (sizeType == SIZE_RESTORED) {
    TRect  wndRect;

    GetWindowRect(wndRect);
    Attr.W = wndRect.Width();
    Attr.H = wndRect.Height();
  }

  // Added owl functionality: notify parent of a resize in case it wants to
  // adust accordingly
  //
  if (Parent && !(GetWindowLong(GWL_EXSTYLE)&WS_EX_NOPARENTNOTIFY))
    Parent->SendMessage(WM_PARENTNOTIFY, WM_SIZE, (uint)HWindow);

  DefaultProcessing();
}

//
// save the normal position of the window
//
// if IsIconic() or IsZoomed() ignore the position since it does not reflect the
// normal state
//
void
TWindow::EvMove(TPoint&)
{
  if (!IsIconic() && !IsZoomed()) {
    TRect wndRect;

    GetWindowRect(wndRect);

    if ((GetWindowLong(GWL_STYLE) & WS_CHILD) == WS_CHILD && Parent &&
         Parent->HWindow)
      Parent->ScreenToClient(wndRect.TopLeft());

    Attr.X = wndRect.left;
    Attr.Y = wndRect.top;
  }

  DefaultProcessing();
}

//
// handles WM_COMPAREITEM message (for owner draw controls) by forwarding
// message to control itself
//
LRESULT
TWindow::EvCompareItem(uint /*ctrlId*/, COMPAREITEMSTRUCT far& compareInfo)
{
  TWindow* win = GetWindowPtr(compareInfo.hwndItem);
  if (win)
    return win->ForwardMessage();
  return DefaultProcessing();
}

//
// handles WM_DELETEITEM message (for owner draw controls) by forwarding
// message to control itself
//
void
TWindow::EvDeleteItem(uint /*ctrlId*/, DELETEITEMSTRUCT far& deleteInfo)
{
  TWindow* win = GetWindowPtr(deleteInfo.hwndItem);
  if (deleteInfo.hwndItem != HWindow && win)
    win->ForwardMessage();
  else
    DefaultProcessing();
}

//
// handles WM_DRAWITEM message (for owner draw controls & menus) by forwarding
// message to control itself
//
void
TWindow::EvDrawItem(uint /*ctrlId*/, DRAWITEMSTRUCT far& drawInfo)
{
  if (drawInfo.CtlType == ODT_MENU) {
    // dispatch to menu...
    // TMenu* menu = ...
    // menu->DrawItem(drawInfo);
  }
  else {
    TWindow* win = GetWindowPtr(drawInfo.hwndItem);
    if (drawInfo.hwndItem != HWindow && win) {
      win->ForwardMessage();
      return;
    }
  }
  DefaultProcessing();
}

//
// handles WM_MEASUREITEM message (for owner draw controls & menus) by
// forwarding message to control itself
//
void
TWindow::EvMeasureItem(uint ctrlId, MEASUREITEMSTRUCT far& measureInfo)
{
  if (measureInfo.CtlType == ODT_MENU) {
    // dispatch to menu...
    // TMenu* menu = ...
    // menu->MeasureItem(measureInfo);
  }
  else {
    HWND hwndCtl = GetDlgItem(measureInfo.CtlID);  // hWnd not in struct, get
    TWindow* win = GetWindowPtr(hwndCtl);
    //
    // If the GetWindowPtr failed, & CreationWindow is non-zero, then this
    // WM_MEASUREITEM is probably for the ctrl which is not yet thunked.
    // route the message directly to creation window.
    //
    extern TWindow* _OWLDATA CreationWindow;
    if (!win) {             // sometimes this is send before Owl thunks ctl
      if (CreationWindow)
        win = CreationWindow;
      else
        win = ChildWithId(ctrlId);
    }
    if (win) {
      win->HandleMessage(WM_MEASUREITEM, ctrlId, LPARAM(&measureInfo));
      return;
    }
  }
  DefaultProcessing();
}

//
//
#if defined(BI_PLAT_WIN32)
LRESULT
TWindow::EvWin32CtlColor(WPARAM wParam, LPARAM lParam)
{
  int  ctlType = GetCurrentEvent().Message - WM_CTLCOLORMSGBOX;

  CHECK(ctlType >= CTLCOLOR_MSGBOX && ctlType <= CTLCOLOR_STATIC);

  TEventInfo  eventInfo(WM_CTLCOLOR);

  if (!Find(eventInfo))
    return DefWindowProc(GetCurrentEvent().Message, wParam, lParam);

  else {
    typedef HBRUSH(GENERIC::*CTL_COLOR_PMF)(HDC, HWND, uint);

    CTL_COLOR_PMF&  pmf = (CTL_COLOR_PMF&)(eventInfo.Entry->Pmf);

    return (LRESULT)(eventInfo.Object->*pmf)((HDC)wParam, (HWND)lParam, ctlType);
  }
}
#endif

//
// dispatches scroll messages as if they were Command messages.
//
void
TWindow::DispatchScroll(uint scrollCode, uint /*thumbPos*/, HWND hWndCtrl)
{
  if (hWndCtrl) {
    TWindow* win = GetWindowPtr(hWndCtrl);
    if (win)
      win->ForwardMessage();

    //
    // Adjust "CurrentEvent" to allow DefaultProcessing to work
    //
    uint16 id = (uint16)::GetDlgCtrlID(hWndCtrl);
    TCurrentEvent& currentEvent = GetCurrentEvent();
    currentEvent.Message = WM_COMMAND;
#if defined(BI_PLAT_WIN16)
      currentEvent.WParam = id;
      currentEvent.LParam = MAKELPARAM(hWndCtrl, scrollCode);
#else
      currentEvent.WParam = MAKEWPARAM(id, scrollCode);
      currentEvent.LParam = LPARAM(hWndCtrl);
#endif

    EvCommand(id, hWndCtrl, scrollCode);
    return;
  }
  DefaultProcessing();
}

//
// response method for an incoming WM_HSCROLL message
//
// if the message is from a scrollbar control, calls DispatchScroll()
// otherwise passes the message to the TWindow's scroller if it has been
// constructed, else calls DefaultProcessing()
//
// assumes, because of a Windows bug, that if the window has the scrollbar
// style, it will not have scrollbar controls
//
void
TWindow::EvHScroll(uint scrollCode, uint thumbPos, HWND hWndCtl)
{
  if (!(GetWindowLong(GWL_STYLE) & WS_HSCROLL))
    DispatchScroll(scrollCode, thumbPos, hWndCtl);  // from scrollbar control

  else if (Scroller)
    Scroller->HScroll(scrollCode, thumbPos);

  else
    DefaultProcessing();
}

//
// response method for an incoming WM_VSCROLL message
//
// if the message is from a scrollbar control, calls DispatchScroll()
// otherwise passes the message to the TWindow's scroller if it has been
// constructed, else calls DefaultProcessing()
//
// assumes, because of a Windows bug, that if the window has the scrollbar
// style, it will not have scrollbar controls
//
void
TWindow::EvVScroll(uint scrollCode, uint thumbPos, HWND hWndCtl)
{
  if (!(GetWindowLong(GWL_STYLE) & WS_VSCROLL))
    DispatchScroll(scrollCode, thumbPos, hWndCtl);

  else if (Scroller)
    Scroller->VScroll(scrollCode, thumbPos);

  else
    DefaultProcessing();
}

//
// response method for an incoming WM_ERASEBKGND message
//
bool
TWindow::EvEraseBkgnd(HDC hDC)
{
  // color set, we'll handle erasing (or doing nothing) here
  //
  if (BkgndColor != NoColor) {

    // if a color is set, blt out a rectangle of it, else don't erase & let
    // paint handle background
    //
    if (BkgndColor != NoErase) {
      SetBkColor(hDC, BkgndColor);
      ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &GetClientRect(), 0, 0, 0);
    }
    return true;
  }

  //
  // no color set, use default class brush
  //
  return (bool)DefaultProcessing();
}

//
// response method for an incoming WM_PAINT message
//
// calls Paint(), performing Windows-required paint setup and cleanup before
// and after. if the TWindow has a TScroller, also calls its BeginView() and
// EndView() methods before and after call to Paint()
//
void
TWindow::EvPaint()
{
  if (IsFlagSet(wfAlias))
    DefaultProcessing();  // use application-defined wndproc

  else {
    TPaintDC dc(*this);
    TRect&   rect = *(TRect*)&dc.Ps.rcPaint;

    if (Scroller)
      Scroller->BeginView(dc, rect);

    Paint(dc, dc.Ps.fErase, rect);

    if (Scroller)
      Scroller->EndView();
  }
}

//
// redraws the contents of the TWindow after a WM_PAINT message
// is received
//
// placeholder for descendant object types to redefine
//
void
TWindow::Paint(TDC&, bool /*erase*/, TRect&)
{
}

//
// response method for an incoming WM_SETCURSOR message
//
// if a cursor has been set for this window, & the mouse is over the
// client area, set the cursor
//
bool
TWindow::EvSetCursor(HWND hWndCursor, uint hitTest, uint /*mouseMsg*/)
{
  if (hWndCursor == HWindow && hitTest == HTCLIENT && HCursor) {
    ::SetCursor(HCursor);
    return true;
  }
  return (bool)DefaultProcessing();
}

//
// response method for an incoming WM_LBUTTONDOWN message
//
// if the TWindow's Scroller has been constructed and if auto-scrolling
// has been requested, captures mouse input, loops until a WM_LBUTTONUP
// message comes in calling the Scroller's AutoScroll method, and then
// releases capture on mouse input
// will also break if a WM_MOUSEMOVE comes in without the left button down
// indicating a lost WM_LBUTTONUP
//
void
TWindow::EvLButtonDown(uint /*modKeys*/, TPoint& /*pt*/)
{
  if (Scroller && Scroller->IsAutoMode()) {
    MSG  loopMsg;

    loopMsg.message = 0;
    SetCapture();

    while (loopMsg.message != WM_LBUTTONUP &&
          (loopMsg.message != WM_MOUSEMOVE || (loopMsg.wParam&MK_LBUTTON))) {
      if (::PeekMessage(&loopMsg, 0, 0, 0, PM_REMOVE)) {
        ::TranslateMessage(&loopMsg);
        ::DispatchMessage(&loopMsg);
      }

      Scroller->AutoScroll();
    }

    ReleaseCapture();
    GetApplication()->ResumeThrow();
  }

  DefaultProcessing();
}

//
void
DoEnableAutoCreate(TWindow* win, void* /*retVal*/)
{
  if (win->HWindow)
    win->EnableAutoCreate();
}

//
// Destroys an MS-Windows element associated with the TWindow.
// First, sets the wfAutoCreate flag to ON for each of the windows in the
// TWindow's ChildList to allow later re-creation.
//
void
TWindow::Destroy(int)
{
  if (HWindow) {
    ForEach(DoEnableAutoCreate, 0);

    if (::DestroyWindow(HWindow))
      HWindow = 0;
    GetApplication()->ResumeThrow();
    WARNX(OwlWin, HWindow, 0, "::DestroyWindow(" << (uint)HWindow << ") failed");
  }
}

//
// specifies registration attributes for the MS-Windows window class
// of the TWindow, allowing instances of TWindow to be registered
//
// sets the fields of the passed WNDCLASS parameter to the default
// attributes appropriate for a TWindow
//
void
TWindow::GetWindowClass(WNDCLASS& wndClass)
{
  wndClass.cbClsExtra = 0;
  wndClass.cbWndExtra = 0;
  wndClass.hInstance = *GetModule();
  wndClass.hIcon = 0;
  wndClass.hCursor = ::LoadCursor(0, IDC_ARROW);
  wndClass.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
  wndClass.lpszMenuName = 0;
  wndClass.lpszClassName = GetClassName();
  wndClass.style = CS_DBLCLKS;
  wndClass.lpfnWndProc = ::InitWndProc;
}

//
// Returns a classname for a generic owl window. Builds the name based on the
// name part of the current module's filename with "Window" appended.
//
char far*
TWindow::GetClassName()
{
#if defined(_OWL_BUILDUP_CLASSNAME)
  static char classSuffix[] = "Window";
  static char className[_MAX_FNAME+sizeof(classSuffix)];

  if (!*className) {
    char filePath[_MAX_PATH];
#if (BI_PLAT_WIN16)
    int  len = GetModule()->GetModuleFileName(filePath, sizeof(filePath));
#else
    int  len = ::GetModuleFileName(0, filePath, sizeof(filePath));
#endif
    CHECK(len > 0);
    _splitpath(filePath, 0, 0, className, 0);
    strcat(className, classSuffix);
  }
  return className;
#else
  return "OwlWindow";  // assume application private class
#endif
}

//
void
TWindow::LoadAcceleratorTable()
{
  if (Attr.AccelTable) {
    hAccel = GetModule()->LoadAccelerators(Attr.AccelTable);
    WARNX(OwlWin, !hAccel, 0,
          "Unable to load accelerators " << Attr.AccelTable
          << " from " << *GetModule());
  }
}

//
// Perform MS Windows window creation
//
void
TWindow::PerformCreate(int menuOrId)
{
  HWindow = ::CreateWindowEx(Attr.ExStyle,
                           GetClassName(),
                           Title,
                           Attr.Style,
                           Attr.X, Attr.Y, Attr.W, Attr.H,
                           Parent ? Parent->HWindow : 0,
                           (HMENU)menuOrId,
                           *GetModule(),
                           Attr.Param);
}

//
// associates an MS-Windows interface element with the TWindow object,
// after creating the interface element if not already created
//
// when creating an element, uses the creation attributes previously set in
// the Attr data field(simply associates the TWindow with an already
// created interface element if the "FromResource" flag is set)
//
// Since this member function now throw an exception on error, it always
// returns true.
//
bool
TWindow::Create()
{
  if (HWindow)
    return true;

  int  menuOrId = 0;

  DisableAutoCreate();

  if (IsFlagSet(wfFromResource))
    HWindow = Parent ? Parent->GetDlgItem(Attr.Id) : 0;  // windows already created it

  else if (Register()) {
    ::SetCreationWindow(this);

    LoadAcceleratorTable();

    if (Attr.Menu) {
      menuOrId = (int)GetModule()->LoadMenu(Attr.Menu);
      WARNX(OwlWin, !menuOrId, 0, "Unable to load menu: " << Attr.Menu <<
            " from " << *GetModule());
    }
    else
      menuOrId = Attr.Id;

    PerformCreate(menuOrId);
    GetApplication()->ResumeThrow();
  }
  else
    THROW( TXWindow(this, IDS_CLASSREGISTERFAIL) );

  if (!HWindow) {
    if (Attr.Menu)  // && !IsFlagSet(wfFromResource) ?
      DestroyMenu(HMENU(menuOrId));

    THROW( TWindow::TXWindow(this, IDS_WINDOWCREATEFAIL) );
  }

  // predefined (non-owl) class--grab the state info that the class setup,
  // install the thunk & zero the creation window since InitWndProc never got
  // called, and set a flag
  //
  if (!GetWindowPtr(HWindow)) {
    GetWindowTextTitle();
    GetHWndState();

    SubclassWindowFunction();
    ::SetCreationWindow(0);
    SetFlag(wfPredefinedClass|wfFullyCreated);

    SetupWindow();
  }

  return true;
}

//
// performs setup following creation of an associated MS-Windows window.
// Iterates through the TWindow's ChildList, attempting to create
// an associated MS-Windows interface element for each child window
// object in the list (a child's Create method is not called if its
// wfAutoCreate flag is not set)
//
// calls TransferData to transfer data for its children for whom data transfer
// is enabled
//
// if the receiver has a TScroller object, calls the scroller's SetBarRange()
// method
//
// can be redefined in derived classes to perform additional special
// initialization that requires an HWND
//
void
TWindow::SetupWindow()
{
  if (Scroller)
    Scroller->SetSBarRange();

  if (!CreateChildren())
    THROW( TXWindow(0, IDS_CHILDCREATEFAIL) ); // not us, an unspecified child

  TransferData(tdSetData);
}

//
// always called just before HWindow goes away to give derived classes a
// chance to cleanup HWND related resources.
//
void
TWindow::CleanupWindow()
{
}

//
// transfer window 'data' to/from the passed data buffer.  Used to initialize
// windows and get data in or out of them.
//
// the direction passed specifies whether data is to be read from or
// written to the passed buffer, or whether the data element size is simply
// to be returned
//
// the return value is the size (in bytes) of the transfer data.  this method
// recursively calls transfer on all its children that have wfTransfer set.
//

struct TTransferIterInfo {
  void*               Data;
  TTransferDirection  Direction;
};

static void transferDatchild(TWindow* child, TTransferIterInfo* info) {
  if (child->IsFlagSet(wfTransfer))
    (char*)info->Data += child->Transfer(info->Data, info->Direction);
}

uint
TWindow::Transfer(void* buffer, TTransferDirection direction)
{
  if (buffer) {
    TTransferIterInfo info = { buffer, direction };
    ForEach((TActionFunc)transferDatchild, &info);
    return (char near*)info.Data - (char near*)buffer;
  }
  return 0;
}

//$---------------------------------------------------------------------------
//
// transfers data between the TWindow's data buffer and the child
// windows in its ChildList (data is not transfered between any child
// windows whose wfTransfer flag is not set)
//
void
TWindow::TransferData(TTransferDirection direction)
{
  if (TransferBuffer)
    Transfer(TransferBuffer, direction);
}

//
// registers the TWindow's MS-Windows, if not already registered
//
bool
TWindow::Register()
{
  // Only check for globally registered classes if not in an NT WoW box,
  // since WoW plays nasty games with class registration.
  //
#if defined(BI_PLAT_WIN32)
  static bool checkGlobal = ToBool(::GetVersion()&0x80000000);  // not NT
#else
  static bool checkGlobal = !ToBool(::GetWinFlags()&0x4000);  // not WoW box
#endif

  WNDCLASS  windowClass;
  bool gc;
  if (checkGlobal)
    gc = ToBool(::GetClassInfo(0, GetClassName(), &windowClass));
  else
    gc = false;

  if (!gc && !GetModule()->GetClassInfo(GetClassName(), &windowClass)) {
    GetWindowClass(windowClass);
    return ::RegisterClass(&windowClass);
  }

  return true;
}

//
// returns a bool indicating whether or not it is Ok to close the TWindow
//
// iterates through the TWindow's ChildList, calling the CanClose()
// method of each
//
// returns false if any of the child windows returns false
//

static bool cannotClose(TWindow* win, void*) {
  return win->HWindow && !win->CanClose();
}

bool
TWindow::CanClose()
{
  return !FirstThat(cannotClose);
}

//
// destroys the associated MS-Windows interface element
// after determining that it is Ok to do so
//
// if the TWindow is the main window of the application, calls the CanClose()
// method of the application, else calls this->CanClose(), before calling
// Destroy()
//
void
TWindow::CloseWindow(int retVal)
{
  bool  willClose;

  if (IsFlagSet(wfMainWindow))
    willClose = GetApplication()->CanClose();

  else
    willClose = CanClose();

  if (willClose)
    Destroy(retVal);
}

//
// the default response to a WM_CLOSE message is to call CloseWindow()
// and then have the window deleted if the HWindow was really destroyed.
//
void
TWindow::EvClose()
{
  if (IsFlagSet(wfAlias))
    DefaultProcessing();

  else {
    CloseWindow();
    if (!HWindow && IsFlagSet(wfDeleteOnClose))
      GetApplication()->Condemn(this);  // Assumes delete
  }
}

//
// responds to an incoming WM_DESTROY message
//
// Calls CleanupWindow() to let derived classes cleanup
// Clears the wfFullyCreated flag since this window is no longer fully created
//
// if the TWindow is the application's main window posts a 'quit' message to
// end the application, unless already in ~TApplication() (MainWindow == 0)
//
void
TWindow::EvDestroy()
{
  ClearFlag(wfFullyCreated);
  CleanupWindow();

  if (!IsFlagSet(wfAlias)) {
    if (IsFlagSet(wfMainWindow) && GetApplication()->IsRunning())
      ::PostQuitMessage(GetApplication()->Status);
  }

  DefaultProcessing();
}

//
// responds to an incoming WM_NCDESTROY message, the last message
// sent to an MS-Windows interface element
//
// sets the HWindow data member of the TWindow to zero to indicate that an
// interface element is no longer associated with the object
//
void
TWindow::EvNCDestroy()
{
  DefaultProcessing();
  HWindow = 0;
}

//
// respond to Windows attempt to close down
//
bool
TWindow::EvQueryEndSession()
{
  if (IsFlagSet(wfAlias))
    return (bool)DefaultProcessing();

  else if (IsFlagSet(wfMainWindow))
    return GetApplication()->CanClose();

  else
    return CanClose();
}

//
// if the window receives an Exit menu choice, it will attempt
// to close down the window
//
void
TWindow::CmExit()
{
  if (IsFlagSet(wfMainWindow))
    CloseWindow();

  else
    DefaultProcessing();
}

//
// Handle message posted to us by a control needing assistance in dealing with
// invalid inputs
//
void
TWindow::EvChildInvalid(HWND hWnd)
{
  ::SendMessage(hWnd, WM_CHILDINVALID, 0, 0);
}

//----------------------------------------------------------------------------
// Non-virtuals
//----------------------------------------------------------------------------

//$---------------------------------------------------------------------------
unsigned
TWindow::NumChildren()
{
  return IndexOf(ChildList) + 1;
}

//$---------------------------------------------------------------------------
//
//
void
TWindow::AssignZOrder()
{
  TWindow*  wnd;
  HWND      curWindow = HWindow;

  if (curWindow) {
    curWindow = ::GetWindow(curWindow, GW_CHILD);

    if (curWindow) {
      int  i = 1;

      for (curWindow = ::GetWindow(curWindow, GW_HWNDLAST);
           curWindow;
           curWindow = ::GetWindow(curWindow, GW_HWNDPREV))
      {
        wnd = GetWindowPtr(curWindow);

        if (wnd)
          wnd->ZOrder = (uint16)i++;
      }
    }
  }
}

//$---------------------------------------------------------------------------
//
// The private field ZOrder is used to ensure the Z-order is
// consistent through read and write of the object.
//
// When the object is written, parent->AssignZOrder will fill in this value
//
// ZOrder ranges from 1 to N where N is the number of objects and the top one.
// A ZOrder value of 0 means that the Z-ordering has not be recoreded.
//
bool
TWindow::OrderIsI(TWindow* win, void* position)
{
  return win->ZOrder == *(int*)position;
}

//$---------------------------------------------------------------------------
//
// returns true if the child was supposed to be created but couldn't be
//
static bool
cantCreate(TWindow* win, void*)
{
  if (win->HWindow)
    return false;

  bool autoCreate = win->IsFlagSet(wfAutoCreate);

  WARNX(OwlWin, !autoCreate, 0,
        "Child window(Id=" << win->GetId() << ") not autocreated");
  if (!autoCreate)
    return false;

  //
  // this call will only fail if a user-defined Create() returns false. Owl's
  // Creates always throw exceptions.
  //
  if (!win->Create())
    return true;

  if (win->IsIconic()) {
    int    textLen = ::GetWindowTextLength(win->HWindow);
    char*  text = new char[textLen + 1];

    ::GetWindowText(win->HWindow, text, textLen + 1);
    ::SetWindowText(win->HWindow, text);
    delete [] text;
  }
  return false;
}

//
// create the children of the object.  returns true if all the windows
// were successfully created
//
bool
TWindow::CreateChildren()
{
  if (FirstThat(cantCreate)) // create children first to restore created order
    return false;
  //
  // restore Z-ordering for children that have Z-ordering recorded
  //
  HWND above = HWND_TOP;
  for (int top = NumChildren(); top; top--) {
    TWindow* wnd = FirstThat(&TWindow::OrderIsI, &top);
    if (wnd) {
      wnd->SetWindowPos(above, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
      above = wnd->HWindow;
    }
  }
  return true;
}

//$---------------------------------------------------------------------------
//
// adds the passed pointer to a child window to the linked list
// of sibling windows which ChildList points to
//
void
TWindow::AddChild(TWindow* child)
{
  if (child)
    if (ChildList) {
      child->SiblingList = ChildList->SiblingList;
      ChildList->SiblingList = child;
      ChildList = child;
    }
    else {
      ChildList = child;
      child->SiblingList = child;
    }
}

//$---------------------------------------------------------------------------
//
// returns a pointer to the TWindow's previous sibling (the
// window previous to the TWindow in its parent's child window
// list)
//
// returns the sibling which points to the TWindow
//
// if the TWindow was the first child added to the list, returns
// a pointer to the last child added
//
TWindow*
TWindow::Previous()
{
  if (!SiblingList)
    return 0;

  else {
    TWindow*  CurrentIndex = this;

    while (CurrentIndex->Next() != this)
      CurrentIndex = CurrentIndex->Next();

    return CurrentIndex;
  }
}

//$---------------------------------------------------------------------------
//
// returns a pointer to the first TWindow in the ChildList that meets some
// specified criteria
//
// if no child in the list meets the criteria, 0 is returned
//
// the Test parameter which defines the criteria, is a pointer to a
// function that takes a TWindow pointer & a pointer to void
//
// the TWindow* will be used as the pointer to the child window and
// the void* as a pointer to the Test function's additional parameters
//
// the Test function must return a Boolean value indicating whether the
// child passed meets the criteria
//
TWindow*
TWindow::FirstThat(TCondFunc test, void* paramList)
{
  if (ChildList) {
    TWindow*  nextChild = ChildList->Next();
    TWindow*  curChild;

    do {
      curChild = nextChild;
      nextChild = nextChild->Next();

      //
      // Test curChild for okay
      //
      if (test(curChild, paramList))
        return curChild;
    } while (curChild != ChildList && ChildList);
  }
  return 0;
}

//$---------------------------------------------------------------------------
//
// iterates over each child window in the TWindow's ChildList,
// calling the procedure whose pointer is passed as the Action to be
// performed for each child
//
// a pointer to a child is passed as the first parameter to the iteration
// procedure
//
void
TWindow::ForEach(TActionFunc action, void* paramList)
{
  if (ChildList) {
    TWindow*  curChild;
    TWindow*  nextChild = ChildList->Next();  // allows ForEach to delete children

    do {
      curChild = nextChild;
      nextChild = nextChild->Next();
      action(curChild, paramList);
    } while (curChild != ChildList && ChildList);
  }
}

//$---------------------------------------------------------------------------
//
// returns a pointer to the first TWindow in the ChildList that
// meets some specified criteria
//
// if no child in the list meets the criteria, 0 is returned
//
// the Test parameter which defines the criteria, is a pointer to a member
// function (this is how it's different from FirstThat above) that takes a
// pointer to a TWindow & a pointer to void
//
// the TWindow pointer will be used as the pointer to the child window and the
// void pointer as a pointer to the Test function's additional parameters
//
// the Test function must return a Boolean value indicating whether the child
// passed meets the criteria
//
TWindow*
TWindow::FirstThat(TCondMemFunc test, void* paramList)
{
  if (ChildList) {
    TWindow*  nextChild = ChildList->Next();
    TWindow*  curChild;

    do {
      curChild = nextChild;
      nextChild = nextChild->Next();

      if ((this->*test)(curChild, paramList))
        return curChild;
    } while (curChild != ChildList && ChildList);
  }
  return 0;
}

//$---------------------------------------------------------------------------
//
// iterates over each child window in the TWindow's ChildList,
// calling the member function (unlike ForEach above which takes pointer
// to non-member function) whose pointer is passed as the Action to
// be performed for each child
//
// a pointer to a child is passed as the first parameter to the iteration
// procedure
//
void
TWindow::ForEach(TActionMemFunc action, void* paramList)
{
  if (ChildList) {
    TWindow*  nextChild = ChildList->Next();
    TWindow*  curChild;

    do {
      curChild = nextChild;
      nextChild = nextChild->Next();
      (this->*action)(curChild, paramList);
    } while (curChild != ChildList && ChildList);
  }
}

//$---------------------------------------------------------------------------
//
// returns the position at which the passed child window appears
// in the TWindow's ChildList
//
// if the child does not appear in the list, -1 is returned
//
// zero'th position is ChildList->Next
//
static int position;
static bool isItThisChild1(TWindow* win, void* child) {
  ++position;
  return win == (TWindow*)child;
}

int
TWindow::IndexOf(TWindow* child)
{
  position = -1;
  return FirstThat(isItThisChild1, child) ? position : -1;
}

//$---------------------------------------------------------------------------
//
// returns the child at the passed position in the TWindow's
// ChildList
//
// the ChildList is circularly-referent so that passing a position
// larger than the number of children will cause the traversal of the
// list to wrap
//
// zero'th position is ChildList->Next
//
TWindow*
TWindow::At(int position)
{
  if (position == -1)
    return 0;

  else {
    TWindow*  currentChild = ChildList;

    if (currentChild) {
      currentChild = ChildList->Next();

      while (position > 0) {
        currentChild = currentChild->Next();
        position--;
      }
    }
    return currentChild;
  }
}

//$---------------------------------------------------------------------------
//
// returns a pointer to the window in the ChildList with the passed Id
//
// if no child in the list has the passed Id, 0 is returned
//
static bool isItThisChild2(TWindow* win, void* id) {
  return win->GetId() == *(int*)id;
}

TWindow*
TWindow::ChildWithId(int id) const
{
  return ((TWindow*)this)->FirstThat(isItThisChild2, &id);
}

//$---------------------------------------------------------------------------
//
LRESULT
TWindow::SendMessage(uint msg, WPARAM wParam, LPARAM lParam) {
  LRESULT result = ::SendMessage(HWindow, msg, wParam, lParam);
  GetApplication()->ResumeThrow();
  return result;
}

//$---------------------------------------------------------------------------
//
LRESULT
TWindow::ForwardMessage(HWND hWnd, bool send)
{
  if (!hWnd)
    return 0;
  TCurrentEvent& currentEvent = GetCurrentEvent();
  if (send) {
    LRESULT result = ::SendMessage(hWnd, currentEvent.Message,
                                   currentEvent.WParam,
                                   currentEvent.LParam);
    GetApplication()->ResumeThrow();
    return result;
  }
  else
    return ::PostMessage(hWnd, currentEvent.Message,
                         currentEvent.WParam,
                         currentEvent.LParam);
}

//$---------------------------------------------------------------------------
//
// Forward a message to an Owl window. If send, then bypass windows directly
// and call the owl window's window proc.
//
LRESULT
TWindow::ForwardMessage(bool send)
{
  TCurrentEvent& currentEvent = GetCurrentEvent();
  if (send)
    return HandleMessage(currentEvent.Message, currentEvent.WParam,
                         currentEvent.LParam);
  return ForwardMessage(HWindow, send);
}

//$---------------------------------------------------------------------------
//
void
TWindow::ChildBroadcastMessage(uint msg, WPARAM wParam, LPARAM lParam)
{
  for (HWND hWndChild = GetWindow(GW_CHILD); hWndChild; ) {
    HWND hWndNext = ::GetWindow(hWndChild, GW_HWNDNEXT);
    ::SendMessage(hWndChild, msg, wParam, lParam);
    GetApplication()->ResumeThrow();
    hWndChild = hWndNext;
  }
}

//$---------------------------------------------------------------------------
//
// destroys the associated MS-Windows interface element and deletes the C++
// object unconditionally (without calling CanClose())
//
// This function is static to avoid side effects of deleting 'this'.
//
void
TWindow::ShutDownWindow(TWindow* win, int retVal)
{
  win->Destroy(retVal);
  delete win;
}

//$---------------------------------------------------------------------------
//
// displays the TWindow, after checking that it has a valid handle
//
void
TWindow::Show(int cmdShow)
{
  //
  // if the window is being minimzed send a WM_SYSCOMMAND; this way the
  // frame window focus saving works properly
  //
  if (HWindow)
    if (cmdShow == SW_MINIMIZE)
      HandleMessage(WM_SYSCOMMAND, SC_MINIMIZE);

    else
      ::ShowWindow(HWindow, cmdShow);
}

//$---------------------------------------------------------------------------
//
// sets the Title and caption of the TWindow
//
void
TWindow::SetCaption(const char far* title)
{
  if (Title != title) {
    if (HIWORD(Title))
      delete [] Title;

    Title = strnewdup(title);
  }

  if (HWindow)
    ::SetWindowText(HWindow, Title);
}

//$---------------------------------------------------------------------------
//
// gets the Title member var from the current window caption or text
//
void
TWindow::GetWindowTextTitle()
{
  if (LOWORD(Title) == 0xFFFF)  // ignore "don't-change" titles
    return;

  if (HIWORD(Title))
    delete [] Title;

  int titleLength = GetWindowTextLength();
  if (titleLength < 0)
    Title = strnewdup((const char far*)"");

  else {
    Title = new far char[titleLength + 1];
    Title[0] = 0;
    Title[titleLength] = 0;
    GetWindowText(Title, titleLength + 1);
  }
}

//$---------------------------------------------------------------------------
//
// copy over the styles, the coordinates & the id from the existing HWnd into
// the owl TWindow members.
// Note: the title is not copied here
//
void
TWindow::GetHWndState()
{
  //
  // retrieve Attr.Style and Attr.ExStyle
  //
  // NOTE: some windows controls (e.g. EDIT) change the style bits
  // (e.g. WS_BORDER) from their original values.  if we always reset
  // Attr.Style and Attr.ExStyle by extracting their values from
  // Windows, we will lose some of the style bits we supplied
  // in the CreateWindowEx call.  in the case of the ResourceId
  // constructors, of course, we must retrieve these values.
  //
  if (IsFlagSet(wfFromResource)) {
    Attr.Style = GetWindowLong(GWL_STYLE);
    Attr.ExStyle = GetWindowLong(GWL_EXSTYLE);
  }

  //
  // retrieve Attr.X, Attr.Y, Attr.W and Attr.H
  //
  TRect  wndRect;

  GetWindowRect(wndRect);
  Attr.H = wndRect.Height();
  Attr.W = wndRect.Width();

  HWND      hParent = GetParent();
  if ((Attr.Style & WS_CHILD) && hParent)
    ::ScreenToClient(hParent, &wndRect.TopLeft());

  Attr.X = wndRect.left;
  Attr.Y = wndRect.top;

#if defined(BI_PLAT_WIN16)
  Attr.Id = GetWindowWord(GWW_ID);
#else
  Attr.Id = GetWindowLong(GWL_ID);
#endif
}

//$---------------------------------------------------------------------------
//
// Set the cursor for this window given a TModule and a resId
// Updates the current cursor if it is over this window.
//
bool
TWindow::SetCursor(TModule* module, TResId resId)
{
  if (module == CursorModule && resId == CursorResId)
    return false;

  HCURSOR hOldCursor = (HCursor && CursorModule) ? HCursor : 0;

  CursorModule = module;
  CursorResId = resId;
  if (CursorResId)
    if (CursorModule)
      HCursor = CursorModule->LoadCursor(CursorResId);
    else
      HCursor = ::LoadCursor(0, CursorResId);
  else
    HCursor = 0;

  //
  // If the cursor is in our client window then set it now
  //
  if (HWindow) {
    TPoint p;
    GetCursorPos(p);
    ScreenToClient(p);
    if (GetClientRect().Contains(p))
      ::SetCursor(HCursor);
  }

  //
  // Destroy old cursor if there was one & it was not loaded from USER
  //
  if (hOldCursor)
    ::DestroyCursor(hOldCursor);
  return true;
}

//
// Handle WM_INITMENUPOPUP while embeded to generate command enable messages
// for our server menu items. Very similar to TFrameWindow::EvInitMenuPopup;
// could rearrange code to share better.
//
void
TWindow::EvInitMenuPopup(HMENU hPopupMenu, uint /*index*/, bool sysMenu)
{
  if (!sysMenu && hPopupMenu) {
    const int count = ::GetMenuItemCount(hPopupMenu);

    for (int pos = 0; pos < count; pos++) {
      uint  id;

      if (hPopupMenu == GetMenu()) // top level menu
        id = ::GetMenuItemID(hPopupMenu, pos);

      else {
        // For second level and below menus, return the implied id for popup
        // sub-menus. The implied id for a sub-menu is the id of the first item
        // in the popup sub-menu less 1. If there are more than one level of
        // popup menus, it will recursively look into those sub-menus as well.
        //
        TMenu popupMenu(hPopupMenu);
        id = popupMenu.GetMenuItemID(pos);
      }

      // ignore separators
      //
      if (id == 0 || id == uint(-1))
        continue;

      // Skip the rest if it is the mdi child list, or system commands
      //
      if (id == IDW_FIRSTMDICHILD || id > 0xF000)
        break;

      EvCommandEnable(TMenuItemEnabler(hPopupMenu, id, HWindow, pos));
    }
  }
}

//$---------------------------------------------------------------------------
//
// not inline to avoid requiring gdiobjec.h by window.h
//
bool
TWindow::GetUpdateRgn(TRegion& region, bool erase) const
{
  return ::GetUpdateRgn(HWindow, region, erase);
}

//$---------------------------------------------------------------------------
//
//
int
TWindow::MessageBox(const char far* text,
                    const char far* caption,
                    uint            type)
{
  if (GetApplication()->BWCCEnabled()) {
    static bool FAR PASCAL(*bwccMessageBox)(HWND, LPCSTR, LPCSTR, uint);
    if (!bwccMessageBox)
      (FARPROC)bwccMessageBox =
        GetApplication()->GetBWCCModule()->GetProcAddress("BWCCMessageBox");
    if (bwccMessageBox)
      return (*bwccMessageBox)(HWindow, text, caption, type);
    return 0;
  }
  else {
    GetApplication()->EnableCtl3dAutosubclass(true);
    int retValue = ::MessageBox(HWindow, text, caption, type);
    GetApplication()->EnableCtl3dAutosubclass(false);
    return retValue;
  }
}

#if defined(__TRACE) || defined(__WARN)
  ostream& operator <<(ostream& os, const TWindow& w)
  {
    os << '(';
#if !defined(BI_NO_RTTI)
      os << typeid(w).name() << ',';
#endif
    os << "0x" << hex << (unsigned)w.HWindow << ',';
    if (!w.Parent)
      os << '\'' << TResId(w.Title) << '\'';
    else
      os << "id=" << w.GetId();
    os << ')';
    return os;
  }
#endif

//$---------------------------------------------------------------------------
//
// static member function to construct base TXOwl object
//
string
TWindow::TXWindow::Msg(TWindow* win, uint resId)
{
  bool   found;  // did we locate the string
  string rscMsg = ResourceIdToString(&found, resId);

  if (found) {
    char buf[255];

    //
    // supply title of window if we have a valid window
    //
    wsprintf(buf, rscMsg.c_str(), win && HIWORD(win->Title) ? win->Title : "");
    return string(buf);
  }
  else
    return rscMsg;
}

//$---------------------------------------------------------------------------
//
// InvalidWindow exception constructor
//
TWindow::TXWindow::TXWindow(TWindow* win, uint resId)
  : TXOwl(Msg(win, resId), resId)
{
  Window = win;
}

TWindow::TXWindow::TXWindow(const TXWindow& src) : TXOwl(src),Window(src.Window)
{
}

int
TWindow::TXWindow::Unhandled(TModule* app, unsigned promptResId)
{
  delete Window;
  Window = 0;
  return TXOwl::Unhandled(app, promptResId);
}

TXOwl*
TWindow::TXWindow::Clone()
{
  return new TXWindow(*this);
}
