//----------------------------------------------------------------------------
// ObjectWindows
// (C) Copyright 1992, 1994 by Borland International, All Rights Reserved
//
//   Implementation of class TGadgetWindow.
//----------------------------------------------------------------------------
#include <owl/owlpch.h>
#include <owl/gadget.h>
#include <owl/gadgetwi.h>

DEFINE_RESPONSE_TABLE1(TGadgetWindow, TWindow)
  EV_WM_CTLCOLOR,
  EV_WM_LBUTTONDOWN,
  EV_WM_LBUTTONUP,
  EV_WM_LBUTTONDBLCLK,
  EV_WM_MOUSEMOVE,
  EV_WM_SIZE,
  EV_WM_SYSCOLORCHANGE,
END_RESPONSE_TABLE;

static inline int HeightInPixels(int pointSize)
{
  TScreenDC  dc;
  return MulDiv(-pointSize, dc.GetDeviceCaps(LOGPIXELSY), 72);
}

TGadgetWindowFont::TGadgetWindowFont(int pointSize, bool bold, bool italic)
:
  TFont("MS Sans Serif", HeightInPixels(pointSize), 0, 0, 0,
        bold ? FW_BOLD : FW_NORMAL,
        uint8(VARIABLE_PITCH | FF_SWISS), uint8(italic))
{
}

TGadgetWindow::TGadgetWindow(TWindow*       parent,
                             TTileDirection direction,
                             TFont*         font,
                             TModule*       module)
:
  TWindow(parent, 0, module)
{
  PRECONDITION(font);

  Attr.Style |= WS_BORDER;
  Capture = 0;
  AtMouse = 0;
  Font = font;
  Direction = direction;
  if (Direction == Horizontal) {
    ShrinkWrapWidth = false;
    ShrinkWrapHeight = true;
  }
  else {
    ShrinkWrapWidth = true;
    ShrinkWrapHeight = false;
  }
  HintMode = PressHints;

  WideAsPossible = 0;  // number of gadgets with "WideAsPossible" set
  DirtyLayout = true;
  Gadgets = 0;
  NumGadgets = 0;

  //
  // compute the font height
  //
  TScreenDC   dc;
  TEXTMETRIC  metrics;

  dc.SelectObject(*Font);
  dc.GetTextMetrics(metrics);
  FontHeight = metrics.tmHeight + metrics.tmExternalLeading;

  //
  // choose a default height based on the height of the font plus room
  // for a top and bottom border
  //
  Attr.H = FontHeight;

  if (Attr.Style & WS_BORDER)
    Attr.H += 2 * GetSystemMetrics(SM_CYBORDER);

  SetBkgndColor(GetSysColor(COLOR_BTNFACE));
  BkgndBrush = new TBrush(GetSysColor(COLOR_BTNFACE));
}

TGadgetWindow::~TGadgetWindow()
{
  TGadget* gadget = Gadgets;

  while (gadget) {
    TGadget*  tmp = gadget;

    gadget = gadget->Next;
    delete tmp;
  }

  delete Font;
  delete BkgndBrush;
}

void
TGadgetWindow::EvSysColorChange()
{
  for (TGadget* g = Gadgets; g; g = g->NextGadget())
    g->SysColorChange();
  SetBkgndColor(GetSysColor(COLOR_BTNFACE));
  delete BkgndBrush;
  BkgndBrush = new TBrush(GetSysColor(COLOR_BTNFACE));
}

bool
TGadgetWindow::IdleAction(long idleCount)
{
  if (idleCount == 0) {
    // See if we missed a mouse move & still need to send a MouseLeave to a
    // gadget
    //
    if (AtMouse) {
      TPoint crsPoint;
      GetCursorPos(crsPoint);
      if (WindowFromPoint(crsPoint) != HWindow)
        HandleMessage(WM_MOUSEMOVE, 0, MAKELPARAM(-1,-1));  // nowhere
    }

    // Let the gadgets do command enabling if they need to
    //
    for (TGadget* g = Gadgets; g; g = g->NextGadget())
      g->CommandEnable();
  }
  return TWindow::IdleAction(idleCount);
}

TGadget*
TGadgetWindow::GadgetWithId(int id) const
{
  for (TGadget* g = Gadgets; g; g = g->NextGadget())
    if (g->GetId() == id)
      return g;

  return 0;
}

void
TGadgetWindow::SetShrinkWrap(bool shrinkWrapWidth, bool shrinkWrapHeight)
{
  ShrinkWrapWidth = shrinkWrapWidth;
  ShrinkWrapHeight = shrinkWrapHeight;
}

bool
TGadgetWindow::GadgetSetCapture(TGadget& gadget)
{
  if (Capture) {
    return false;
  }
  else {
    Capture = &gadget;
    SetCapture();
    return true;
  }
}

void
TGadgetWindow::GadgetReleaseCapture(TGadget& gadget)
{
  if (&gadget == Capture) {
    Capture = 0;
    ReleaseCapture();
  }
}

//
// Simulate menu select & idle messages to our parent so that it can display
// hint text the same way it does for menu commands
//
void
TGadgetWindow::SetHintCommand(int id)
{
  if (Parent) {
    if (id > 0)
      Parent->HandleMessage(WM_MENUSELECT, id, 0);

    else
      // Send a menuselect w/ flags==0xFFFF and hMenu==0 to indicate end
      //
#if defined(BI_PLAT_WIN32)
      Parent->HandleMessage(WM_MENUSELECT, 0xFFFF0000, 0); // flags+item, hmenu
#else
      Parent->HandleMessage(WM_MENUSELECT, 0, 0x0000FFFFL);// item, hmenu+flags
#endif

    Parent->HandleMessage(WM_ENTERIDLE, MSGF_MENU);
  }
}

void
TGadgetWindow::SetMargins(TMargins& margins)
{
  Margins = margins;
  LayoutSession();
}

int
TGadgetWindow::LayoutUnitsToPixels(int units)
{
  const long  UnitsPerEM = 8;

  return int((long(units) * FontHeight + UnitsPerEM / 2) / UnitsPerEM);
}

void
TGadgetWindow::GetMargins(TMargins& margins,
                          int& left, int& right, int& top, int& bottom)
{
  switch (margins.Units) {
    case TMargins::Pixels:
      left = margins.Left;
      top = margins.Top;
      right = margins.Right;
      bottom = margins.Bottom;
      break;

    case TMargins::LayoutUnits:
      left = LayoutUnitsToPixels(margins.Left);
      top = LayoutUnitsToPixels(margins.Top);
      right = LayoutUnitsToPixels(margins.Right);
      bottom = LayoutUnitsToPixels(margins.Bottom);
      break;

    case TMargins::BorderUnits:
      int  cxBorder = GetSystemMetrics(SM_CXBORDER);
      int  cyBorder = GetSystemMetrics(SM_CYBORDER);

      left = margins.Left * cxBorder;
      top = margins.Top * cyBorder;
      right = margins.Right * cxBorder;
      bottom = margins.Bottom * cyBorder;
      break;
  }
}

void
TGadgetWindow::LayoutSession()
{
  if (HWindow)
    InvalidateRect(TileGadgets());
}

void
TGadgetWindow::GetDesiredSize(TSize& size)
{
  int       left, right, top, bottom;
  int       maxWidth = 0, maxHeight = 0;
  TGadget*  lastGadget = 0;

  GetMargins(Margins, left, right, top, bottom);

  if (ShrinkWrapWidth || ShrinkWrapHeight) {
    for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next) {
      TSize  size;

      gadget->GetDesiredSize(size);

      if (size.cx > maxWidth)
        maxWidth = size.cx;

      if (size.cy > maxHeight)
        maxHeight = size.cy;
    }

    //
    // in some cases we need to actually tile the gadgets in order to tell how
    // much room is needed
    //
    if (Direction == Horizontal && ShrinkWrapWidth ||
        Direction == Vertical && ShrinkWrapHeight) {
      TileGadgets();

      for (lastGadget = Gadgets; lastGadget && lastGadget->Next; lastGadget = lastGadget->Next)
        ;
    }
  }

  if (!ShrinkWrapWidth)
    size.cx = Attr.W;

  else if (Direction == Horizontal) {
    size.cx = right;

    if (lastGadget)
      size.cx += lastGadget->GetBounds().right;
  }
  else {
    size.cx = left + right + maxWidth;

    if (Attr.Style & WS_BORDER)
      size.cx += 2 * GetSystemMetrics(SM_CXBORDER);
  }

  if (!ShrinkWrapHeight)
    size.cy = Attr.H;

  else if (Direction == Vertical) {
    size.cx = bottom;

    if (lastGadget)
      size.cx += lastGadget->GetBounds().bottom;
  }
  else {
    size.cy = top + bottom + maxHeight;

    if (Attr.Style & WS_BORDER)
      size.cy += 2 * GetSystemMetrics(SM_CYBORDER);
  }
}

bool
TGadgetWindow::Create()
{
  TSize  size;

  GetDesiredSize(size);

  if (ShrinkWrapWidth)
    Attr.W = size.cx;

  if (ShrinkWrapHeight)
    Attr.H = size.cy;

  bool r =  TWindow::Create();

  GetDesiredSize(size);
  if ((ShrinkWrapWidth && Attr.W != size.cx) || (ShrinkWrapHeight &&  Attr.H != size.cy)) {
    if (ShrinkWrapWidth)
      Attr.W = size.cx;

    if (ShrinkWrapHeight)
      Attr.H = size.cy;

    LayoutSession();
  }

  return r;
}

//
// Set direction, & default shrink wrap flags. If already created, then also
// adjust shrink wrap dimension & re-layout
//
void
TGadgetWindow::SetDirection(TTileDirection direction)
{
  if (Direction != direction) {
    Direction = direction;
    if (Direction == Horizontal) {
      ShrinkWrapWidth = false;
      ShrinkWrapHeight = true;
    }
    else {
      ShrinkWrapWidth = true;
      ShrinkWrapHeight = false;
    }
    //
    // Swap margin's X & Y axis
    //
    int t = Margins.Left; Margins.Left = Margins.Top; Margins.Top = t;
    t = Margins.Right; Margins.Right = Margins.Bottom; Margins.Bottom = t;

    if (HWindow) {
      TSize  size;
      GetDesiredSize(size);

      if (ShrinkWrapWidth)
        Attr.W = size.cx;

      if (ShrinkWrapHeight)
        Attr.H = size.cy;

      LayoutSession();
    }
  }
}

void
TGadgetWindow::GetInnerRect(TRect& rect)
{
  int left, right, top, bottom;
  GetMargins(Margins, left, right, top, bottom);

  rect.left = left;
  rect.right = Attr.W - right;
  rect.top = top;
  rect.bottom = Attr.H - bottom;

  if (Attr.Style & WS_BORDER) {
    rect.right -= 2 * GetSystemMetrics(SM_CYBORDER);
    rect.bottom -= 2 * GetSystemMetrics(SM_CXBORDER);
  }
}

void
TGadgetWindow::PositionGadget(TGadget*, TGadget*, TPoint&)
{
}

TRect
TGadgetWindow::TileHorizontally()
{
  TRect  innerRect;
  TRect  invalidRect;
  int    width = 0;
  int    x;

  GetInnerRect(innerRect);
  invalidRect.SetEmpty();

  //
  // first pass tally the width of all gadgets that don't have "WideAsPossible"
  // set
  //
  // NOTE: we must also take into account any adjustments to the gadget spacing
  //
  for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next) {
    if (!gadget->WideAsPossible) {
      TSize   desiredSize;

      gadget->GetDesiredSize(desiredSize);
      width += desiredSize.cx;
    }

    if (gadget->Next) {
      TPoint  spacing(0, 0);

      PositionGadget(gadget, gadget->Next, spacing);
      width += spacing.x;
    }
  }

  //
  // now tile all the gadgets
  //
  x = innerRect.left;
  for (gadget = Gadgets; gadget; gadget = gadget->Next) {
    TRect  bounds = gadget->Bounds;
    TRect  originalBounds(bounds);
    TSize  desiredSize;

    bounds.left = x;
    gadget->GetDesiredSize(desiredSize);
    bounds.top = innerRect.top + (innerRect.Height() - desiredSize.cy) / 2;
    bounds.bottom = bounds.top + desiredSize.cy;

    if (gadget->WideAsPossible)
      bounds.right = bounds.left + (innerRect.Width() - width) / WideAsPossible;

    else
      bounds.right = bounds.left + desiredSize.cx;

    if (bounds != originalBounds) {
      gadget->SetBounds(bounds);
      invalidRect |= bounds;

      if (originalBounds.TopLeft() != TPoint(0, 0))
        invalidRect |= originalBounds;
    }
    x += bounds.Width();

    if (gadget->Next) {
      TPoint  origin(x, 0);

      PositionGadget(gadget, gadget->Next, origin);
      x = origin.x;
    }
  }
  return invalidRect;
}

TRect
TGadgetWindow::TileVertically()
{
  TRect  innerRect;
  TRect  invalidRect;
  int    y;

  GetInnerRect(innerRect);
  invalidRect.SetEmpty();

  //
  // now tile all the gadgets
  //
  y = innerRect.top;
  for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next) {
    TRect  bounds = gadget->Bounds;
    TRect  originalBounds(bounds);
    TSize  desiredSize;

    gadget->GetDesiredSize(desiredSize);
    bounds.top = y;
    bounds.bottom = bounds.top + desiredSize.cy;
    bounds.left = innerRect.left + (innerRect.Width() - desiredSize.cx) / 2;
    bounds.right = bounds.left + desiredSize.cx;

    if (bounds != originalBounds) {
      gadget->SetBounds(bounds);
      invalidRect |= bounds;

      if (originalBounds.TopLeft () != TPoint (0, 0))
        invalidRect |= originalBounds;
    }

    y += bounds.Height();

    if (gadget->Next) {
      TPoint  origin(0, y);

      PositionGadget(gadget, gadget->Next, origin);
      y = origin.y;
    }
  }
  return invalidRect;
}

TRect
TGadgetWindow::TileGadgets()
{
  return (Direction == Horizontal) ? TileHorizontally() : TileVertically();
}

void
TGadgetWindow::GadgetChangedSize(TGadget&)
{
  LayoutSession();
}

void
TGadgetWindow::Insert(TGadget& gadget, TPlacement placement, TGadget* sibling)
{
  PRECONDITION(!gadget.Window);

  TGadget**  g = &Gadgets;

  if (sibling || placement == After) {
    while (*g && *g != sibling)
      g = &(*g)->Next;

    CHECK(*g == sibling);
  }

  if (placement == After && *g)
    g = &(*g)->Next;

  gadget.Next = *g;
  *g = &gadget;

  gadget.Window = this;
  NumGadgets++;
  gadget.Inserted();

  if (gadget.WideAsPossible)
    WideAsPossible++;
}

//
// remove a gadget from the gadget window's list
//
TGadget*
TGadgetWindow::Remove(TGadget& gadget)
{
  if (gadget.Window != this || !Gadgets)
    return 0;

  //
  // handle head-of-list case
  //
  if (&gadget == Gadgets) {
    Gadgets = Gadgets->Next;
  }
  //
  // scan for gadget, looking one link ahead
  //
  else {
    TGadget* g = Gadgets;

    while (g->Next && g->Next != &gadget)
      g = g->Next;

    if (!g->Next)   // not found
      return 0;

    g->Next = g->Next->Next;
  }

  NumGadgets--;

  //
  // re-adjust gadget now that it doesn't live in a window
  //
  if (gadget.WideAsPossible)
    WideAsPossible--;
  if (&gadget == AtMouse)
    AtMouse = 0;
  GadgetReleaseCapture(gadget);
  gadget.Removed();
  gadget.Window = 0;
  gadget.Next = 0;
  gadget.GetBounds() -= gadget.GetBounds().TopLeft();

  return &gadget;
}

void
TGadgetWindow::EvSize(uint sizeType, TSize& size)
{
  TWindow::EvSize(sizeType, size);

  if (DirtyLayout || WideAsPossible > 0) {
    DirtyLayout = false;
    TileGadgets();
    Invalidate();
  }
}

//
// Iterate thru the gadget list and paint each one.
//
void
TGadgetWindow::PaintGadgets(TDC& dc, bool, TRect& rect)
{
  TPoint viewport = dc.GetViewportOrg();
  for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next) {
    if (gadget->Bounds.Touches(rect)) {
      dc.SetViewportOrg((TPoint&)gadget->Bounds);

      if (gadget->Clip) {
        dc.SaveDC();
        dc.IntersectClipRect(gadget->Bounds);
      }

      gadget->Paint(dc);

      if (gadget->Clip)
        dc.RestoreDC();
    }
  }
  dc.SetViewportOrg(viewport);
}

void
TGadgetWindow::Paint(TDC& dc, bool erase, TRect& rect)
{
  dc.SelectObject(*Font);
  PaintGadgets(dc, erase, rect);
}

//
// Return gadget that a given window-relative point is in, 0 if none
//
TGadget*
TGadgetWindow::GadgetFromPoint(TPoint& point) const
{
  for (TGadget* gadget = Gadgets; gadget; gadget = gadget->Next)
    if (gadget->PtIn(point - *(TSize*)&gadget->Bounds.TopLeft()))
      break;

  return gadget;
}

HBRUSH
TGadgetWindow::EvCtlColor(HDC hDC, HWND /*hWndChild*/, uint /*ctlType*/)
{
  ::SetBkColor(hDC, BkgndColor);
  return HBRUSH(*BkgndBrush);
}

//
// Determine if the mouse down is in an enabled gadget, & if so pass it to
// the gadget.
//
void
TGadgetWindow::EvLButtonDown(uint modKeys, TPoint& point)
{
  TGadget* gadget = Capture ? Capture : GadgetFromPoint(point);

  if (gadget && (gadget->GetEnabled() || Capture))
    gadget->LButtonDown(modKeys, point - *(TSize*)&gadget->Bounds.TopLeft());

  TWindow::EvLButtonDown(modKeys, point);
}

//
// Forward mouse ups to the gadget that has captures the mouse, if any, or
// to the gadget at the mouse pos
//
void
TGadgetWindow::EvLButtonUp(uint modKeys, TPoint& point)
{
  TGadget* gadget = Capture ? Capture : GadgetFromPoint(point);

  if (gadget && (gadget->GetEnabled() || Capture))
    gadget->LButtonUp(modKeys, TPoint(point.x - gadget->Bounds.left,
                                      point.y - gadget->Bounds.top));
  TWindow::EvLButtonUp(modKeys, point);
}

//
// Pass double clicks thru as if they were just a second click, finish the
// first click, & begin the second. Dn + Dbl + Up -> Dn + Up+Dn + Up
//
void
TGadgetWindow::EvLButtonDblClk(uint modKeys, TPoint& point)
{
  EvLButtonUp(modKeys, point);
  EvLButtonDown(modKeys, point);
}

//
// Forward mouse moves to the gadget that has captures the mouse, if any.
// Otherwise checks for mouse entering & leaving gadgets
//
// Could enhance this by delaying mouse enter messages until mouse has been
// in the same area for a while, or looking ahead in queue for mouse msgs.
//
void
TGadgetWindow::EvMouseMove(uint modKeys, TPoint& point)
{
  if (Capture) {
    Capture->MouseMove(modKeys, TPoint(point.x - Capture->Bounds.left,
                                       point.y - Capture->Bounds.top));
  }
  else {
    TGadget* gadget = GadgetFromPoint(point);
    if (gadget != AtMouse) {
      if (AtMouse)
        AtMouse->MouseLeave(modKeys, TPoint(point.x - AtMouse->Bounds.left,
                                            point.y - AtMouse->Bounds.top));
      AtMouse = gadget;
      if (AtMouse)
        AtMouse->MouseEnter(modKeys, TPoint(point.x - AtMouse->Bounds.left,
                                            point.y - AtMouse->Bounds.top));
    }
  }
  TWindow::EvMouseMove(modKeys, point);
}
