/* 
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"

#include "mforms/mforms.h"
#include "wf_view.h"
#include "wf_scrollpanel.h"

/**
 * Implementation of a specialized mforms panel, which has scrollbars.
 * On Windows every container has scrollbars, so we don't need a separate implementation here.
 * Instead we reuse PanelImpl.
 */

using namespace MySQL;
using namespace MySQL::Forms;

bool ScrollFillLayout::Layout(Object^ sender, LayoutEventArgs^ arguments)
{
  // This layout is actually very simple. Simply resize the first (and only) child control so that
  // it fills the entire client area of the container.
  // However, and that is different to just dock it with DockStyle::Fill, don't make it smaller
  // than it wants to be, i.e. determined through its minimum size and layout size.

  ScrollFillPanel^ container= (ScrollFillPanel^) sender;
  if (container->Controls->Count > 0)
  {
    ViewImpl::adjust_auto_resize_from_docking(container);
    Size boxSize= container->Size;

    Control^ content= container->Controls[0];
    Size childSize;

    childSize= content->GetPreferredSize(container->ClientSize);
    if (ViewImpl::use_min_width_for_layout(content))
      childSize.Width= content->MinimumSize.Width;
    if (ViewImpl::use_min_height_for_layout(content))
      childSize.Height= content->MinimumSize.Height;

    // Stretch client to fill the entire height, if it isn't larger already.
    if (childSize.Height < container->ClientSize.Height)
      childSize.Height= container->ClientSize.Height;
    container->UpdateVerticalScrollbar(childSize.Height);

    if (childSize.Width < container->ClientSize.Width)
      childSize.Width= container->ClientSize.Width;
    container->UpdateHorizontalScrollbar(childSize.Width);

    ViewImpl::remove_auto_resize(content, AutoResizeMode::ResizeBoth);
    content->Size= childSize;

    return false;
  }
  return false;
}

//----------------- ScrollFillPanel ----------------------------------------------------------------

private ref class WheelMessageFilter : Windows::Forms::IMessageFilter
{
private:
  ScrollFillPanel^ panel;

public:
  WheelMessageFilter(ScrollFillPanel^ panel)
  {
    this->panel = panel;
  }

  virtual bool PreFilterMessage(Message% m)
  {
    if (panel != nullptr && panel->Visible && m.Msg == WM_MOUSEWHEEL)
    {
      // Only handle messages if the mouse without our bounds.
      POINT cursorPoint;
      GetCursorPos(&cursorPoint);
      Point point = panel->PointToClient(Point(cursorPoint.x, cursorPoint.y));
      if (panel->ClientRectangle.Contains(point))
      {
        panel->WndProc(m);
        return true;
      }
    }

    return false;
  }
};

//--------------------------------------------------------------------------------------------------

ScrollFillPanel::ScrollFillPanel()
{
  autoHideScrollbars= true;
  hideHorizontalScrollbar= false;
  hideVerticalScrollbar= false;
  horizontalOffset= 0;
  verticalOffset= 0;
  updateCount= 0;

  // In addition to the normal scroll wheel handling we add a message filter for scroll wheel
  // messages to react also to them if the mouse is within our client area but we are not focused.
  wheelMessageFilter = gcnew WheelMessageFilter(this);
  Application::AddMessageFilter(wheelMessageFilter);
}

//--------------------------------------------------------------------------------------------------

ScrollFillPanel::~ScrollFillPanel()
{
  Application::RemoveMessageFilter(wheelMessageFilter);
}

//--------------------------------------------------------------------------------------------------

void ScrollFillPanel::HandleHorizontalScrolling(Message% m)
{
  switch (m.WParam.ToInt32() & 0xFFFF) // Low word only.
  {
  case SB_RIGHT:
    {
      System::Drawing::Size maxSize(0, 0);
      if (Controls->Count > 0)
      {
        Control^ content= Controls[0];
        maxSize= content->Size;
      }
      SetOffset(-maxSize.Width, verticalOffset);
      break;
    }
  case SB_ENDSCROLL:
    break;
  case SB_LINELEFT:
    SetOffset(horizontalOffset + 8, verticalOffset);
    break;
  case SB_LINERIGHT:
    SetOffset(horizontalOffset - 8, verticalOffset);
    break;
  case SB_PAGELEFT:
    SetOffset(horizontalOffset + ClientSize.Width, verticalOffset);
    break;
  case SB_PAGERIGHT:
    SetOffset(horizontalOffset - ClientSize.Height, verticalOffset);
    break;
  case SB_THUMBPOSITION:
  case SB_THUMBTRACK:
    SCROLLINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cbSize= sizeof(si);
    si.fMask= SIF_TRACKPOS;
    GetScrollInfo((HWND) Handle.ToPointer(), SB_HORZ, &si);
    SetOffset(-si.nTrackPos, verticalOffset);
    break;
  case SB_TOP:
    SetOffset(0, verticalOffset);
    break;
  }
  m.Result= IntPtr::Zero;
}

//--------------------------------------------------------------------------------------------------

void ScrollFillPanel::HandleVerticalScrolling(Message% m)
{
  switch (m.WParam.ToInt32() & 0xFFFF) // Low word only.
  {
  case SB_BOTTOM:
    {
      System::Drawing::Size maxSize(0, 0);
      if (Controls->Count > 0)
      {
        Control^ content= Controls[0];
        maxSize= content->Size;
      }
      SetOffset(horizontalOffset, -maxSize.Height);
      break;
    }
  case SB_ENDSCROLL:
    break;
  case SB_LINEUP:
    SetOffset(horizontalOffset, verticalOffset + 8);
    break;
  case SB_LINEDOWN:
    SetOffset(horizontalOffset, verticalOffset - 8);
    break;
  case SB_PAGEUP:
    SetOffset(horizontalOffset, verticalOffset + ClientSize.Height);
    break;
  case SB_PAGEDOWN:
    SetOffset(horizontalOffset, verticalOffset - ClientSize.Height);
    break;
  case SB_THUMBPOSITION:
  case SB_THUMBTRACK:
    SCROLLINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cbSize= sizeof(si);
    si.fMask= SIF_TRACKPOS;
    GetScrollInfo((HWND) Handle.ToPointer(), SB_VERT, &si);
    SetOffset(horizontalOffset, -si.nTrackPos);
    break;
  case SB_TOP:
    SetOffset(horizontalOffset, 0);
    break;
  }
  m.Result= IntPtr::Zero;
}

//--------------------------------------------------------------------------------------------------

void ScrollFillPanel::HandleWheelScrolling(Message% m)
{
  System::Drawing::Size maxSize(0, 0);
  if (Controls->Count > 0)
  {
    Control^ content= Controls[0];
    maxSize= content->Size;
  }

  int scrollAmount;
  double wheelFactor= (m.WParam.ToInt32() >> 16) / (double) WHEEL_DELTA;

  DWORD scrollLines;
  SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0);
  if (maxSize.Height > ClientSize.Height)
  {
    if (scrollLines == WHEEL_PAGESCROLL)
      scrollAmount= (int) (wheelFactor * ClientSize.Height);
    else
      scrollAmount= (int) (wheelFactor * scrollLines * 16); // TODO: find a better way to define a line.
    SetOffset(horizontalOffset, verticalOffset + scrollAmount);
  }
  else
    if (maxSize.Width > ClientSize.Width)
    {
      scrollAmount= (int) (wheelFactor * scrollLines);
      SetOffset(horizontalOffset + scrollAmount, verticalOffset);
    }

  m.Result= IntPtr::Zero;
}

//--------------------------------------------------------------------------------------------------

/**
 * Scrolls the control's content to the given position.
 */
void ScrollFillPanel::SetOffset(int x, int y)
{
  System::Drawing::Size maxSize(0, 0);
  if (Controls->Count > 0)
  {
    Control^ content= Controls[0];
    maxSize= content->Size;
  }

  // Sanity checks.
  if (x < (ClientSize.Width - maxSize.Width))
    x= ClientSize.Width - maxSize.Width;
  if (x > 0)
    x= 0;
  int deltaX= x - horizontalOffset;
  if (y < (ClientSize.Height - maxSize.Height))
    y= ClientSize.Height - maxSize.Height;
  if (y > 0)
    y= 0;
  int deltaY= y - verticalOffset;

  horizontalOffset= x;
  verticalOffset= y;

  if (deltaX != 0 || deltaY != 0)
    ScrollWindow((HWND)Handle.ToPointer(), deltaX, deltaY, NULL, NULL);

  if (updateCount == 0)
  {
    UpdateHorizontalScrollbar(-1);
    UpdateVerticalScrollbar(-1);
  }
}

//--------------------------------------------------------------------------------------------------

System::Drawing::Size ScrollFillPanel::GetPreferredSize(System::Drawing::Size proposedSize)
{
  return proposedSize;
}

//--------------------------------------------------------------------------------------------------

void ScrollFillPanel::WndProc(Message% m)
{
  switch (m.Msg)
  {
  case WM_HSCROLL:
    HandleHorizontalScrolling(m);
    return;
  case WM_VSCROLL:
    HandleVerticalScrolling(m);
    return;
  case WM_MOUSEWHEEL:
    HandleWheelScrolling(m);
    return;
  case WM_SIZE:
    if (updateCount == 0)
    {
      updateCount++;
      UpdateVerticalScrollbar(-1);
      UpdateHorizontalScrollbar(-1);
      updateCount--;
    }
    break;
  }
  Control::WndProc(m);
}

//--------------------------------------------------------------------------------------------------

void ScrollFillPanel::UpdateHorizontalScrollbar(int newWidth)
{
  SCROLLINFO si;
  ZeroMemory(&si, sizeof(si));
  si.cbSize= sizeof(si);
  si.fMask= SIF_ALL;

  updateCount++;

  System::Drawing::Size contentSize(newWidth, 0);
  if (newWidth < 0 && Controls->Count > 0)
    contentSize= Controls[0]->Size;

  bool needScrollbar= contentSize.Width > ClientSize.Width;
  if (!needScrollbar)
    contentSize.Width= ClientSize.Width;
  if (!hideHorizontalScrollbar)
  {
    if (needScrollbar)
    {
      si.nMax= contentSize.Width;
      si.nPage= ClientSize.Width + 1;
      si.nPos= -horizontalOffset;
    }
    else
      // No scrollbar needed. Hide it if auto hiding is enabled, otherwise disable it.
      if (!autoHideScrollbars)
        si.fMask |= SIF_DISABLENOSCROLL;
  }
  SetScrollInfo((HWND)Handle.ToPointer(), SB_HORZ, &si, true);

  SetOffset(horizontalOffset, verticalOffset);

  updateCount--;
}

//--------------------------------------------------------------------------------------------------

void ScrollFillPanel::UpdateVerticalScrollbar(int newHeight)
{
  SCROLLINFO si;
  ZeroMemory(&si, sizeof(si));
  si.cbSize= sizeof(si);
  si.fMask= SIF_ALL;
  int newVerticalOffset = verticalOffset;

  updateCount++;

  System::Drawing::Size contentSize(0, newHeight);
  if (newHeight < 0 && Controls->Count > 0)
    contentSize= Controls[0]->Size;

  bool needScrollbar= contentSize.Height > ClientSize.Height; 
  if (!needScrollbar)
  {
    contentSize.Height= ClientSize.Height;
    newVerticalOffset = 0;
  }

  if (!hideVerticalScrollbar)
  {
    if (needScrollbar)
    {
      si.nMax= contentSize.Height;
      si.nPage= ClientSize.Height + 1;
      si.nPos= -verticalOffset;
    }
    else
      // No scrollbar needed. Hide it if auto hiding is enabled, otherwise disable it.
      if (!autoHideScrollbars)
        si.fMask |= SIF_DISABLENOSCROLL;
  }
  SetScrollInfo((HWND)Handle.ToPointer(), SB_VERT, &si, true);

  SetOffset(horizontalOffset, newVerticalOffset);

  updateCount--;
}

//--------------------------------------------------------------------------------------------------

/**
 * Scrolls the box so that the given control is in the visual portion of the box.
 * Note: For this to work we don't consider the (only) child of this box (as it is always in full size)
 *       but its children, which is the real content of the box (nested, due to the way mforms works).
 */
void ScrollFillPanel::ScrollControlIntoView(Control^ control)
{
  Control^ parent = (Controls->Count > 0) ? Controls[0] : nullptr;
  if (parent != nullptr && parent->Contains(control))
  {
    Point position = parent->PointToClient(control->PointToScreen(control->Location));
    int left = position.X + horizontalOffset; // Relative position to the view port.
    int top = position.Y + verticalOffset; // Ditto.

    int newHorizontalOffset = horizontalOffset;
    int newVerticalOffset = verticalOffset;

    if (left < 0)
      newHorizontalOffset -= left;
    if (top < 0)
      newVerticalOffset -= top;

    int right = left + control->Bounds.Width;
    int bottom = top + control->Bounds.Height;
    if (right > Width)
      newHorizontalOffset += Width - right;
    if (bottom > Height)
      newVerticalOffset += Height - bottom;

    SetOffset(newHorizontalOffset, newVerticalOffset);
  }
}

//----------------- ScrollPanelImpl ----------------------------------------------------------------

bool ScrollPanelImpl::create(mforms::ScrollPanel* self, mforms::ScrollPanelFlags flags)
{
  ScrollPanelImpl^ panel= gcnew ScrollPanelImpl(self);
  if (panel != nullptr)
  {
    if (flags & mforms::ScrollPanelBordered)
    {
      // Have to fake a bordered scrollbox by embedding a panel in a groupbox.
      Control^ control= ViewImpl::create<GroupBox>(self, panel);
      control->AutoSize= false;
      control->Padding= Padding(5);
      panel->_container= gcnew ScrollFillPanel();
      control->Controls->Add(panel->_container);
      panel->_container->Dock= DockStyle::Fill;
    }
    else
      panel->_container= ViewImpl::create<ScrollFillPanel>(self, panel);
    panel->_container->AutoSize= false;
    panel->_container->Margin= Padding(0);
    panel->_container->Padding= Padding(0);
    return true;
  }
  return false;
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::add(mforms::ScrollPanel *self, mforms::View *view)
{
  ScrollPanelImpl^ panel= (ScrollPanelImpl^)ObjectImpl::FromUnmanaged(self);
  if (panel != nullptr)
  {
    panel->add(view);
    self->set_layout_dirty(true);
  }
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::scroll_to_view(mforms::ScrollPanel *self, mforms::View *view)
{
  ScrollPanelImpl^ panel= (ScrollPanelImpl^)ObjectImpl::FromUnmanaged(self);
  if (panel != nullptr)
  {
    panel->scroll_to_view(view);
  }
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::remove(mforms::ScrollPanel *self)
{
  ScrollPanelImpl^ panel= (ScrollPanelImpl^)ObjectImpl::FromUnmanaged(self);
  if (panel != nullptr)
  {
    panel->remove();
    self->set_layout_dirty(true);
  }
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::set_autohide_scrollers(mforms::ScrollPanel *self, bool flag)
{
  ScrollPanelImpl^ panel= (ScrollPanelImpl^) ObjectImpl::FromUnmanaged(self);
  if (panel != nullptr)
    panel->_container->AutoHideScrollbars= flag;
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::set_visible_scrollers(mforms::ScrollPanel *self, bool vertical, bool horizontal)
{
  ScrollPanelImpl^ panel= (ScrollPanelImpl^) ObjectImpl::FromUnmanaged(self);
  if (panel != nullptr)
  {
    panel->_container->HideHorizontalScrollbar= !horizontal;
    panel->_container->HideVerticalScrollbar= !vertical;
    self->set_layout_dirty(true);
  }
}

//--------------------------------------------------------------------------------------------------

ScrollPanelImpl::ScrollPanelImpl(mforms::ScrollPanel *self)
  : ViewImpl(self)
{
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::add(mforms::View *view)
{
  ViewImpl^ child= (ViewImpl^)ObjectImpl::FromUnmanaged(view);
  Control^ ctl= child->get_control<Control>();

  _container->Controls->Add(ctl);
  ctl->Location= Point(0, 0);
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::remove()
{
  _container->Controls->Clear();
}

//--------------------------------------------------------------------------------------------------

void ScrollPanelImpl::scroll_to_view(mforms::View *view)
{
  ViewImpl^ child= (ViewImpl^)ObjectImpl::FromUnmanaged(view);
  Control^ ctl= child->get_control<Control>();
  _container->ScrollControlIntoView(ctl);
}

//--------------------------------------------------------------------------------------------------
