/* 
 * Copyright (c) 2008, 2014, 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 "base/log.h"
#include "base/string_utilities.h"
#include "base/drawing.h"

#include "wf_base.h"
#include "wf_view.h"
#include "wf_app.h"
#include "wf_code_editor.h"
#include "wf_drawbox.h"

using namespace System::Drawing;
using namespace System::IO;
using namespace System::Windows::Forms;
using namespace System::Runtime::InteropServices;

using namespace MySQL;
using namespace MySQL::Controls;
using namespace MySQL::Forms;
using namespace MySQL::Utilities;
using namespace MySQL::Utilities::SysUtils;

using namespace HtmlRenderer;
using namespace Aga::Controls::Tree;

typedef System::Runtime::InteropServices::ComTypes::IDataObject SystemIDataObject;

DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_WRAPPER)

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

/**
 * Converts Windows specific mouse button identifiers mforms identifiers.
 */
static mforms::MouseButton convert_mouse_button(MouseButtons button)
{
  switch (button)
  {
    case MouseButtons::Left:
      return mforms::MouseButtonLeft;
    case MouseButtons::Right:
      return mforms::MouseButtonRight;
    case MouseButtons::Middle:
      return mforms::MouseButtonOther;
    default:
      return mforms::MouseButtonNone;
  }
}

//----------------- ViewEventTarget ----------------------------------------------------------------

// A helper class to have a target for managed events.
ref class MySQL::Forms::ViewEventTarget
{
private:
  mforms::View *view;
  mforms::DropDelegate *target;

  Control ^control;
  MySQL::Utilities::IDropTargetHelper ^helper;

public:
  ViewEventTarget(mforms::View *theView, Control ^theControl)
  {
    view = theView;
    target = NULL;
    control = theControl;
    helper = nullptr;
  }

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

  void SetDropTarget(mforms::DropDelegate *theTarget)
  {
    target = theTarget;
  }

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

  void Resize(System::Object ^sender, EventArgs ^e)
  {
    view->resize();
  }

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

  void Enter(System::Object ^sender, EventArgs ^e)
  {
    view->focus_changed();
  }

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

  /**
   * Converts drag event key states flags to mforms constants.
   * This is not the same as converting KeyData (like in wf_textbox.cpp).
   */
  mforms::ModifierKey GetModifiers(int keyState)
  {
    mforms::ModifierKey modifiers = mforms::ModifierNoModifier;
    if ((keyState & 8) == 8)
      modifiers = modifiers | mforms::ModifierControl;
    if ((keyState & 32) == 32)
      modifiers = modifiers | mforms::ModifierAlt;
    if ((keyState & 4) == 4)
      modifiers = modifiers | mforms::ModifierShift;

    return modifiers;
  }

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

  void HandleMouseDown(System::Object ^sender, MouseEventArgs ^e)
  {
    Control ^control = dynamic_cast<Control^>(sender);
    if (control == nullptr)
      return;

    control->Focus();
    view->mouse_down(convert_mouse_button(e->Button), e->X, e->Y);
  }

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

  void HandleMouseUp(System::Object ^sender, MouseEventArgs ^e)
  {
    view->mouse_up(convert_mouse_button(e->Button), e->X, e->Y);
  }

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

  void HandleMouseClick(System::Object ^sender, MouseEventArgs ^e)
  {
    view->mouse_click(convert_mouse_button(e->Button), e->X, e->Y);
  }

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

  void HandleMouseDoubleClick(System::Object ^sender, MouseEventArgs ^e)
  {
    view->mouse_double_click(convert_mouse_button(e->Button), e->X, e->Y);
  }

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

  void HandleMouseMove(System::Object ^sender, MouseEventArgs ^e)
  {
    view->mouse_move(convert_mouse_button(e->Button), e->X, e->Y);
  }

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

  void HandleMouseLeave(System::Object ^sender, EventArgs ^e)
  {
    view->mouse_leave();
  }

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

  void HandleMouseEnter(System::Object ^sender, EventArgs ^e)
  {
    view->mouse_enter();
  }

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

  std::vector<std::string> get_available_formats(Windows::Forms::IDataObject ^data)
  {
    std::vector<std::string> result;
    array<String ^> ^formats = data->GetFormats();

    // Translate known formats to the backend type, copy others as is and let the receiver decide.
    for (int i = 0; i < formats->Length; ++i)
    {
      if (formats[i] == DataFormats::FileDrop)
        result.push_back(mforms::DragFormatFileName);
      else
        if (formats[i] == DataFormats::UnicodeText) // This includes other text that can be converted to Unicode text.
          result.push_back(mforms::DragFormatText);
        else
          result.push_back(NativeToCppString(formats[i]));
    }

    return result;
  }

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

  void HandleDragEnter(System::Object ^sender, DragEventArgs ^e)
  {
    Control^ control= dynamic_cast<Control^>(sender);
    if (control != nullptr)
    {
      if (e->Data->GetDataPresent("DragImageBits"))
      {
        // If a drag image was registered we can use the drag drop helper.
        helper = (MySQL::Utilities::IDropTargetHelper ^)gcnew MySQL::Utilities::DragDropHelper();
        Win32::POINT point(e->X, e->Y);
        helper->DragEnter(IntPtr::Zero, (SystemIDataObject^)e->Data, point, (int)e->Effect);
      }
    }
  }

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

  void HandleDragOver(System::Object ^sender, DragEventArgs ^e)
  {
    e->Effect = DragDropEffects::None;
    Control^ control = dynamic_cast<Control^>(sender);
    if (control != nullptr)
    {
      if (target != NULL)
      {
        Drawing::Point point = control->PointToClient(Point(e->X, e->Y));

        mforms::View *origin = ViewWrapper::source_view_from_data(e->Data);

        mforms::DragOperation operation = mforms::DragOperationNone;
        if ((e->AllowedEffect & DragDropEffects::Copy) == DragDropEffects::Copy)
          operation = operation | mforms::DragOperationCopy;
        if ((e->AllowedEffect & DragDropEffects::Move) == DragDropEffects::Move)
          operation = operation | mforms::DragOperationMove;

        operation = target->drag_over(origin, base::Point(point.X, point.Y), operation,
          get_available_formats(e->Data));

        if ((operation & mforms::DragOperationCopy) != 0)
          e->Effect = DragDropEffects::Copy;
        if ((operation & mforms::DragOperationMove) != 0)
          e->Effect = e->Effect | DragDropEffects::Move;
      }
      else
        e->Effect = DragDropEffects::None;

      if (helper != nullptr)
      {
        Win32::POINT point;
        point.x = e->X;
        point.y = e->Y;
        helper->DragOver(point, (int)e->Effect);
      }
    }
  }

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

  void HandleDragDrop(System::Object ^sender, DragEventArgs ^e)
  {
    e->Effect = DragDropEffects::None;
    Control^ control = dynamic_cast<Control^>(sender);
    if (control != nullptr)
    {
      if (target != NULL)
      {
        mforms::View *origin = NULL;
        if (e->Data->GetDataPresent(DRAG_SOURCE_FORMAT_NAME))
        {
          IntPtr ^ref = (IntPtr ^)e->Data->GetData(DRAG_SOURCE_FORMAT_NAME);
          if (ref != nullptr)
            origin = (mforms::View *)ref->ToPointer();
        }

        Drawing::Point point = control->PointToClient(Point(e->X, e->Y));
        std::vector<std::string> formats = get_available_formats(e->Data);

        mforms::DragOperation operation;
        mforms::DragOperation allowedOperations = mforms::DragOperationNone;
        if ((e->AllowedEffect & DragDropEffects::Copy) == DragDropEffects::Copy)
          allowedOperations = allowedOperations | mforms::DragOperationCopy;
        if ((e->AllowedEffect & DragDropEffects::Move) == DragDropEffects::Move)
          allowedOperations = allowedOperations | mforms::DragOperationMove;

        for (size_t i = 0; i < formats.size(); ++i)
        {
          if (formats[i] == mforms::DragFormatFileName)
          {
            array<String^> ^names = dynamic_cast<array<String^> ^>(e->Data->GetData(DataFormats::FileDrop));
            if (names != nullptr)
            {
              std::vector<std::string> file_names;
              for each (String ^name in names)
                file_names.push_back(NativeToCppStringRaw(name));
              operation = target->files_dropped(origin, base::Point(point.X, point.Y), allowedOperations,
                file_names);
              break; // TODO: the breaks in the loop allow only for a single drop format, even though the view.h description says differently.
            }
          }
          else
            if (formats[i] == mforms::DragFormatText)
            {
              String ^text = (String^)e->Data->GetData(DataFormats::UnicodeText);
              operation = target->text_dropped(origin, base::Point(point.X, point.Y), allowedOperations,
                NativeToCppStringRaw(text));
              break;
            }
            else
            {
              // Forward unknown data only if we at least know it is from us.
              DataWrapper ^wrapper = dynamic_cast<DataWrapper^>(e->Data->GetData(e->Data->GetFormats()[(int)i], false));
              if (wrapper != nullptr)
              {
                operation = target->data_dropped(origin, base::Point(point.X, point.Y), allowedOperations,
                  wrapper->GetData(), formats[i]);
                break;
              }
            }
        }

        if ((operation & mforms::DragOperationCopy) != 0)
          e->Effect = DragDropEffects::Copy;
        if ((operation & mforms::DragOperationMove) != 0)
          e->Effect = e->Effect | DragDropEffects::Move;
      }

      if (helper != nullptr)
      {
        Win32::POINT point;
        point.x = e->X;
        point.y = e->Y;
        helper->Drop((SystemIDataObject^)e->Data, point, (int)e->Effect);
      }
    }
  }

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

  void HandleDragLeave(System::Object ^sender, EventArgs ^e)
  {
    if (helper != nullptr)
    {
      MySQL::Utilities::IDropTargetHelper ^helper = (MySQL::Utilities::IDropTargetHelper ^)gcnew MySQL::Utilities::DragDropHelper();
      helper->DragLeave();

      delete helper;
      helper = nullptr;
    }
  }

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

  void Relayout()
  {
    // Not really an event handler but a target for a threaded invocation.
    control->PerformLayout(control, "Bounds");
  }
};

//----------------- ViewWrapper --------------------------------------------------------------------

ViewWrapper::ViewWrapper(mforms::View *view)
  : ObjectWrapper(view)
{
  tooltip = nullptr;
  backgroundImage = nullptr;
  eventTarget = nullptr;
  layoutSuspended = false;
  _resize_mode = AutoResizeMode::ResizeBoth;
  backgroundImageAlignment = mforms::NoAlign;
}

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

void ViewWrapper::destroy(mforms::View *backend)
{
  // Not needed anymore.
}

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

/**
 * Determines, depending on the type of the given control, if the layout process should use the control's
 * minimum or its preferred size. 
 * This is necessary because there is a discrepancy between what is considered the minimum size for layouting
 * and the same size for Windows controls. The latter is usually just 0. Instead the preferred size comes close
 * to what we need in the layouting process. Unfortunately, some controls just return their current size
 * (even if they are empty) or the size they need to fit their full text (text box).
 */
bool ViewWrapper::use_min_width_for_layout(Control ^control)
{
  TextBox ^text = dynamic_cast<TextBox ^>(control);
  bool needsMinWidth = (text != nullptr) && (text->Multiline);
  if (!needsMinWidth)
  {
    needsMinWidth =
         is<TabControl>(control)
      || (Panel::typeid == control->GetType()) // Only the standard panel, not our derivation or we will cause an endless loop.
      || is<ComboBox>(control)
      || is<TreeViewAdv>(control)
      || is<ListBox>(control)
      || is<SplitContainer>(control)
      || is<Windows::Forms::WebBrowser>(control)
      || is<HtmlLabel>(control)
      || is<HtmlPanel>(control)
      || is<ProgressBar>(control)
      || is<ScintillaControl>(control)
      || is<DataGridView>(control)
    ;
  }

  return needsMinWidth;
}

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

bool ViewWrapper::use_min_height_for_layout(Control ^control)
{
  TextBox ^text = dynamic_cast<TextBox^>(control);
  bool needsMinHeight = (text != nullptr) && (text->Multiline);
  if (!needsMinHeight)
  {
    needsMinHeight =
         is<TabControl>(control)
      || (Panel::typeid == control->GetType())
      || is<ListBox>(control)
      || is<TreeViewAdv>(control)
      || is<SplitContainer>(control)
      || is<Windows::Forms::WebBrowser>(control)
      || is<ScintillaControl>(control)
      || is<DataGridView>(control)
    ;
  }

  return needsMinHeight;
}

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

/**
 * Removes the given mode from the auto resize settings of the given control.
 */
void ViewWrapper::remove_auto_resize(Control ^control, AutoResizeMode mode)
{
  ViewWrapper *wrapper = GetWrapper<ViewWrapper>(control);
  if (wrapper == NULL)
    return;

  switch (mode)
  {
    case AutoResizeMode::ResizeBoth:
      wrapper->_resize_mode = AutoResizeMode::ResizeNone;
      break;
    case AutoResizeMode::ResizeHorizontal:
      if (wrapper->_resize_mode == AutoResizeMode::ResizeBoth)
        wrapper->_resize_mode = AutoResizeMode::ResizeVertical;
      else
        wrapper->_resize_mode = AutoResizeMode::ResizeNone;
      break;
    case AutoResizeMode::ResizeVertical:
      if (wrapper->_resize_mode == AutoResizeMode::ResizeBoth)
        wrapper->_resize_mode = AutoResizeMode::ResizeHorizontal;
      else
        wrapper->_resize_mode = AutoResizeMode::ResizeNone;
      break;
  }
}

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

AutoResizeMode ViewWrapper::get_auto_resize(Control ^control)
{
  ViewWrapper *wrapper = GetWrapper<ViewWrapper>(control);
  if (wrapper == NULL) // Can only happen for a control that wasn't created by mforms.
    return AutoResizeMode::ResizeNone;
  return wrapper->_resize_mode;
}

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

/**
 * Determines if the given control can be resized in horizontal direction.
 */
bool ViewWrapper::can_auto_resize_horizontally(Control ^control)
{
  ViewWrapper *wrapper = GetWrapper<ViewWrapper>(control);
  if (wrapper == NULL)
    return false;
  AutoResizeMode mode = wrapper->_resize_mode;

  return (mode == AutoResizeMode::ResizeHorizontal) || (mode == AutoResizeMode::ResizeBoth);
}

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

/**
 * Determines if the given control can be resized in vertical direction.
 */
bool ViewWrapper::can_auto_resize_vertically(Control^ control)
{
  ViewWrapper *wrapper = GetWrapper<ViewWrapper>(control);
  if (wrapper == NULL)
    return false;
  AutoResizeMode mode = wrapper->_resize_mode;

  return (mode == AutoResizeMode::ResizeVertical) || (mode == AutoResizeMode::ResizeBoth);
}

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

/**
 * Enables resizing of the control in both directions.
 */
void ViewWrapper::set_full_auto_resize(Control ^control)
{
  ViewWrapper *wrapper = GetWrapper<ViewWrapper>(control);
  if (wrapper == NULL)
    return;
  wrapper->_resize_mode = AutoResizeMode::ResizeBoth;
}

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

/**
 * Sets resize mode whatever the caller specified.
 */
void ViewWrapper::set_auto_resize(Control ^control, AutoResizeMode mode)
{
  ViewWrapper *wrapper = GetWrapper<ViewWrapper>(control);
  if (wrapper == NULL)
    return;
  wrapper->_resize_mode = mode;
}

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

bool ViewWrapper::is_layout_dirty(Control ^control)
{
  mforms::View *view = ViewWrapper::GetBackend<mforms::View>(control);
  if (view == NULL)
    return false;
  return view->is_layout_dirty();
}

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

void ViewWrapper::set_layout_dirty(Control ^control, bool value)
{
  mforms::View *view = ViewWrapper::GetBackend<mforms::View>(control);
  if (view != NULL)
    view->set_layout_dirty(value);
}

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

void ViewWrapper::show(mforms::View *backend, bool show)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  Control ^control = wrapper->GetControl();

  // Create the control's window handle if not yet done. This is necessary because otherwise
  // when setting a control to hidden and its window handle is not created, it will be moved to the end
  // of it's parent Controls list, messing up our layout.
  if (!control->IsHandleCreated)
    control->Handle;

  // Use reflection to get the true visible keyState. The Visible property returns the value for the
  // control itself as well as all its parents.
  bool visible = (bool)Control::typeid
    ->GetMethod("GetState", System::Reflection::BindingFlags::Instance | System::Reflection::BindingFlags::NonPublic)
    ->Invoke(control, gcnew array<Object^> { 2 });
  if (show != visible && backend->get_parent() != NULL)
  {
    if (wrapper->layoutSuspended)
      backend->get_parent()->set_layout_dirty(true);
    else
      control->PerformLayout(control, "Visible");
  }

  control->Visible = show;
}

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

int ViewWrapper::get_width(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->Width;
}

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

int ViewWrapper::get_height(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->Height;
}

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

int ViewWrapper::get_preferred_width(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->PreferredSize.Width;
}

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

int ViewWrapper::get_preferred_height(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->PreferredSize.Height;
}

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

int ViewWrapper::get_x(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->Location.X;
}

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

int ViewWrapper::get_y(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->Location.Y;
}

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

void ViewWrapper::set_size(mforms::View *backend, int w, int h)
{
  Control ^control = GetManagedObject<Control>(backend);
  Size newSize = control->Size;
  Size newMinSize = control->MinimumSize;
  if (w >= 0)
  {
    newSize.Width = w;
    newMinSize.Width = w;
  }
  if (h >= 0)
  {
    newSize.Height = h;
    newMinSize.Height = h;
  }

  // Setting the min size here is a trick to tell layouters that we want a fixed size
  // (not the preferred size).
  control->MinimumSize = newMinSize;
  control->Size = newSize;
}

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

void ViewWrapper::set_padding(mforms::View *backend, int left, int top, int right, int bottom)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  wrapper->set_padding(left, top, right, bottom);
}

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

void ViewWrapper::set_position(mforms::View *backend, int x, int y)
{
  Control ^control = GetManagedObject<Control>(backend);
  control->Location = Point(x, y);
  if (backend->get_parent() != NULL)
    backend->get_parent()->set_layout_dirty(true);
}

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

std::pair<int, int> ViewWrapper::client_to_screen(mforms::View *backend, int x, int y)
{
  Control ^control = GetManagedObject<Control>(backend);
  System::Drawing::Point location = System::Drawing::Point(x, y);
  location = control->PointToScreen(location);
  return std::make_pair(location.X, location.Y);
}

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

std::pair<int, int> ViewWrapper::screen_to_client(mforms::View *backend, int x, int y)
{
  Control ^control = GetManagedObject<Control>(backend);
  System::Drawing::Point location = System::Drawing::Point(x, y);
  location = control->PointToClient(location);
  return std::make_pair(location.X, location.Y);
}

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

void ViewWrapper::relayout(mforms::View *backend)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  Control ^control = wrapper->GetControl();
  if (control->InvokeRequired)
    control->BeginInvoke(gcnew MethodInvoker(wrapper->eventTarget, &ViewEventTarget::Relayout));
  else
    control->PerformLayout(control, "Bounds");
}

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

void ViewWrapper::set_enabled(mforms::View *backend, bool flag)
{
  Control ^control = GetManagedObject<Control>(backend);
  control->Enabled = flag;
}

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

bool ViewWrapper::is_enabled(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->Enabled;
}

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

mforms::View* ViewWrapper::find_subview(mforms::View *backend, std::string &name)
{
  return NULL;
}

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

void ViewWrapper::set_name(mforms::View *backend, const std::string& text)
{
  Control ^control = GetManagedObject<Control>(backend);
  control->Name = CppStringToNative(text);
  control->AccessibleName = control->Name;
}

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

void ViewWrapper::set_needs_repaint(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  control->Invalidate();
}

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

void ViewWrapper::suspend_layout(mforms::View *backend, bool flag)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  wrapper->layoutSuspended = flag;
  
  Control ^control = wrapper->GetControl();
  if (flag)
    control->SuspendLayout();
  else
  {
    control->ResumeLayout();
    if (backend->is_layout_dirty())
      control->PerformLayout(control, "Bounds");
  }
}

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

void ViewWrapper::set_front_color(mforms::View *backend, const std::string &color)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  wrapper->set_front_color(CppStringToNativeRaw(color));
}

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

std::string ViewWrapper::get_front_color(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  Color ^color = control->ForeColor;
  if (color == nullptr)
    return "#000000";
  return base::strfmt("#%02x%02x%02x", color->R, color->G, color->B);
}

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

void ViewWrapper::set_back_color(mforms::View *backend, const std::string &color)
{
  FlatTabControl ^tabcontrol = GetManagedObject<FlatTabControl>(backend);
  if (tabcontrol != nullptr)
  {
    if (color.empty())
      tabcontrol->BackgroundColor = Color::Transparent;
    else
      tabcontrol->BackgroundColor = System::Drawing::ColorTranslator::FromHtml(CppStringToNativeRaw(color));
    return;
  }

  Control ^control = GetManagedObject<Control>(backend);
  if (color.empty())
  {
    // Use an empty string as indicator to reset the background color.
    control->ResetBackColor();
    if (is<TreeViewAdv>(control))
      control->BackColor = SystemColors::Window;
    else
      if (is<ButtonBase>(control))
        ((ButtonBase ^)control)->UseVisualStyleBackColor = true;
      else
        if (is<TabPage>(control))
          ((TabPage ^)control)->UseVisualStyleBackColor = true;
  }
  else
    control->BackColor = ColorTranslator::FromHtml(CppStringToNativeRaw(color));
}

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

std::string ViewWrapper::get_back_color(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  Color ^color = control->BackColor;
  if (color == nullptr)
    return "#000000";
  return base::strfmt("#%02x%02x%02x", color->R, color->G, color->B);
}

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

void ViewWrapper::set_back_image(mforms::View *backend, const std::string &path, mforms::Alignment align)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  String ^native_path = AppWrapper::get_image_path(CppStringToNative(path));
  if (File::Exists(native_path))
  {
    wrapper->backgroundImage = Image::FromFile(native_path, true);
    wrapper->backgroundImageAlignment = align;
  }
}


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

void ViewWrapper::focus(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  Windows::Forms::Form ^form = control->FindForm();
  if (form != nullptr)
    form->ActiveControl = control;
  else
    if (control->CanSelect)
      control->Select();
}

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

bool ViewWrapper::has_focus(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->Focused;
}

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

void ViewWrapper::set_tooltip(mforms::View *backend, const std::string& text)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  Control ^control = wrapper->GetControl();
  if (static_cast<ToolTip^>(wrapper->tooltip) == nullptr)
  {
    wrapper->tooltip = gcnew ToolTip();
    wrapper->tooltip->AutoPopDelay = 10000; // 10 sec. show time
    wrapper->tooltip->InitialDelay = 500;   // 500 ms before showing the tooltip if none was visible before.
    wrapper->tooltip->ReshowDelay = 250;    // 250 ms before showing the tooltip if another one was active.
    wrapper->tooltip->ShowAlways = true;    // Show tooltip even if the control is not active.
  }
  wrapper->tooltip->SetToolTip(control, CppStringToNative(text));
}

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

void ViewWrapper::set_font(mforms::View *backend, const std::string& fontDescription)
{
  Control ^control = GetManagedObject<Control>(backend);

  std::string font;
  float size;
  bool bold;
  bool italic;
  base::parse_font_description(fontDescription, font, size, bold, italic);

  FontStyle style = FontStyle::Regular;
  if (bold)
    style = (FontStyle) (style | FontStyle::Bold);
  if (italic)
    style = (FontStyle) (style | FontStyle::Italic);

  try
  {
    // Font size in points.
    control->Font = ControlUtilities::GetFont(CppStringToNativeRaw(font), size, style);
  }
  catch (System::ArgumentException^ e)
  {
    // Argument exception pops up when the system cannot find the Regular font style (corrupt font).
    log_error("ViewWrapper::set_font failed. %s\n", e->Message);
  }
}

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

bool ViewWrapper::is_shown(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  return control->Visible;
}

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

bool ViewWrapper::is_fully_visible(mforms::View *backend)
{
  Control ^control = GetManagedObject<Control>(backend);
  while (control->Visible) 
  {
    if (control->Parent == nullptr)
      return true;
    control = control->Parent;
  };
  
  return false;
}

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

void ViewWrapper::register_drop_formats(mforms::View *backend, mforms::DropDelegate *target,
  const std::vector<std::string> &formats)
{
  Control ^control = GetManagedObject<Control>(backend);
  control->AllowDrop = !formats.empty(); // On Windows we don't need to preregister formats like we do on the other platforms.

  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  ViewEventTarget ^nativeTarget = wrapper->eventTarget;
  if (nativeTarget == nullptr)
  {
    // Not set up for general drag/drop. But for file drops we can register via a separate path
    // which is necessary anyway for controls that do low level drag/drop (like Scintilla),
    // disabling so managed drag/drop.
    // Wrappers that can handle file drops have to do processing on their own (via WM_DROPFILES).
    if (formats.size() == 1 && formats[0] == mforms::DragFormatFileName)
      wrapper->register_file_drop(target);
    else
      throw gcnew System::Exception("Attempt to set a drop target on a view that is not enabled for drag and drop");
  }
  else
    nativeTarget->SetDropTarget(target);
}

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

mforms::DragOperation ViewWrapper::drag_text(mforms::View *backend, mforms::DragDetails details,
  const std::string &text)
{
  Control ^control = GetManagedObject<Control>(backend);
  Windows::Forms::DataObject ^dataObject = gcnew Windows::Forms::DataObject(gcnew MySQL::Utilities::DataObject());
  WBIDataObjectExtensions::SetDataEx(dataObject, DataFormats::UnicodeText, CppStringToNative(text));

  // Store the backend pointer in the data object, so we can distinguish between internal and
  // external drag sources.
  WBIDataObjectExtensions::SetDataEx(dataObject, DRAG_SOURCE_FORMAT_NAME, gcnew IntPtr(backend));

  DragDropEffects effects = DragDropEffects::None;
  if ((details.allowedOperations & mforms::DragOperationCopy) != 0)
    effects = effects | DragDropEffects::Copy;
  if ((details.allowedOperations & mforms::DragOperationMove) != 0)
    effects = effects | DragDropEffects::Move;

  effects = control->DoDragDrop(dataObject, effects);
  delete dataObject;

  if ((effects & DragDropEffects::Copy) == DragDropEffects::Copy)
    return mforms::DragOperationCopy;
  if ((effects & DragDropEffects::Move) == DragDropEffects::Move)
    return mforms::DragOperationMove;

  return mforms::DragOperationNone;
}

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

mforms::DragOperation ViewWrapper::drag_data(mforms::View *backend, mforms::DragDetails details, void *data,
  const std::string &format)
{
  Control ^control = GetManagedObject<Control>(backend);
  DataWrapper ^wrapper = gcnew DataWrapper(data);
  System::Windows::Forms::DataObject ^dataObject = gcnew System::Windows::Forms::DataObject(gcnew MySQL::Utilities::DataObject());
  WBIDataObjectExtensions::SetDataEx(dataObject, CppStringToNativeRaw(format), wrapper);

  WBIDataObjectExtensions::SetDataEx(dataObject, DRAG_SOURCE_FORMAT_NAME, gcnew IntPtr(backend));

  DragDropEffects effects = DragDropEffects::None;
  if ((details.allowedOperations & mforms::DragOperationCopy) != 0)
    effects = effects | DragDropEffects::Copy;
  if ((details.allowedOperations & mforms::DragOperationMove) != 0)
    effects = effects | DragDropEffects::Move;

  if (details.image != NULL)
    SetDragImage(dataObject, details);
  bool flag = dataObject->GetDataPresent(CppStringToNativeRaw(format));

  effects = control->DoDragDrop(dataObject, effects);
  delete dataObject;
  if ((effects & DragDropEffects::Copy) == DragDropEffects::Copy)
    return mforms::DragOperationCopy;
  if ((effects & DragDropEffects::Move) == DragDropEffects::Move)
    return mforms::DragOperationMove;

  return mforms::DragOperationNone;
}

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

mforms::DropPosition ViewWrapper::get_drop_position(mforms::View *backend)
{
  ViewWrapper *wrapper = backend->get_data<ViewWrapper>();
  return wrapper->get_drop_position();
}

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

/**
*	Tries to get a reference to the source backend view in a drag operation, if there's one.
*/
mforms::View* ViewWrapper::source_view_from_data(Windows::Forms::IDataObject ^data)
{
  if (data->GetDataPresent(DRAG_SOURCE_FORMAT_NAME))
  {
    IntPtr ^ref = (IntPtr ^)data->GetData(DRAG_SOURCE_FORMAT_NAME);
    if (ref != nullptr)
      return (mforms::View *)ref->ToPointer();
  }
  return NULL;
}

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

/**
 * Creates and sets a drag image from the given details for the data object.
 */
void ViewWrapper::SetDragImage(Windows::Forms::DataObject ^data, mforms::DragDetails details)
{
  BITMAPINFO bmpInfo = {0};
  bmpInfo.bmiHeader.biSize = sizeof(bmpInfo);
  bmpInfo.bmiHeader.biWidth = cairo_image_surface_get_width(details.image);
  bmpInfo.bmiHeader.biHeight = cairo_image_surface_get_height(details.image);
  bmpInfo.bmiHeader.biPlanes = 1;
  bmpInfo.bmiHeader.biBitCount = 32;
  bmpInfo.bmiHeader.biCompression = BI_RGB;

  HDC dc = GetDC(0);
  HDC workingDC = CreateCompatibleDC(dc);
  ReleaseDC(0, dc);

  LPVOID bits;
  HBITMAP hbmp = CreateDIBSection(workingDC, &bmpInfo, DIB_RGB_COLORS, &bits, 0, 0);

  cairo_surface_t *bitmapSurface = cairo_image_surface_create_for_data((unsigned char*)bits,
    CAIRO_FORMAT_ARGB32,
    bmpInfo.bmiHeader.biWidth,
    bmpInfo.bmiHeader.biHeight,
    bmpInfo.bmiHeader.biWidth * 4);

  if (bitmapSurface)
  {
    cairo_t *cr = cairo_create(bitmapSurface);
    cairo_surface_destroy(bitmapSurface);

    // Windows DIB sections as create here are flipped. Correct this with a mirror matrix.
    cairo_matrix_t matrix;
    cairo_matrix_init(&matrix, 1.0, 0.0, 0.0, -1.0, 0.0, bmpInfo.bmiHeader.biHeight);
    cairo_set_matrix(cr, &matrix);

    // Draw the provided source image into the temporary cairo context which will in turn
    // act on the DIB section.
    cairo_set_source_surface(cr, details.image, 0, 0);
    cairo_paint(cr);
    cairo_destroy(cr);
    
    Win32::ShDragImage shdi;
    shdi.sizeDragImage.cx = cairo_image_surface_get_width(details.image);
    shdi.sizeDragImage.cy = cairo_image_surface_get_height(details.image);
    shdi.ptOffset.x = (long)details.hotspot.x;
    shdi.ptOffset.y = (long)details.hotspot.y;
    shdi.hbmpDragImage = IntPtr(hbmp);
    shdi.crColorKey = 0xFFFFFFFF;
  
    MySQL::Utilities::IDragSourceHelper ^helper = (MySQL::Utilities::IDragSourceHelper ^)gcnew DragDropHelper();
    helper->InitializeFromBitmap(shdi, data);
  }
}

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

/**
 * Resizes the given control according to its dock keyState. Sizes of the control which were not changed
 * are also reset in the given Size structure to allow for proper child layouting.
 */
void ViewWrapper::resize_with_docking(Control ^control, System::Drawing::Size &size)
{
  // If the control is docked somewhere resizing must be restricted not to destroy the docked
  // appearance.
  // Do not resize if the control has the fill dock style. In that case it must stay as it is.
  if (control->Dock == DockStyle::Fill)
    return;

  if (control->Dock == DockStyle::Left || control->Dock == DockStyle::Right)
    size.Height = control->Size.Height;
  else
    if (control->Dock == DockStyle::Top || control->Dock == DockStyle::Bottom)
      size.Width = control->Size.Width;

  control->Size = size;
  set_layout_dirty(control, true);
}

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

/**
 * Removes auto resizing flags if a control is limited in resizing by docking.
 */
void ViewWrapper::adjust_auto_resize_from_docking(Control ^control)
{
  if (control->Dock == DockStyle::Fill)
    set_auto_resize(control, AutoResizeMode::ResizeNone);
  else
    if (control->Dock == DockStyle::Left || control->Dock == DockStyle::Right)
      remove_auto_resize(control, AutoResizeMode::ResizeHorizontal);
    else
      if (control->Dock == DockStyle::Top || control->Dock == DockStyle::Bottom)
        remove_auto_resize(control, AutoResizeMode::ResizeVertical);
}

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

/**
 * Quick check if it makes sense to start layouting the given control.
 */
bool ViewWrapper::can_layout(Control ^control, String ^reason)
{
  // Limit layouting to certain property changes.
  if (reason != nullptr)
  {
    if (reason != "Padding" &&
      reason != "Bounds" &&
      reason != "Visible" &&
      reason != "Parent"
      )
      return false;
  }

  /* Opting out based on a not created handle is a good optimization. Unfortunately, sometimes it
     prevents some forms (e.g. wizards) from layouting properly. So we have to disable this for now.
  if (!control->IsHandleCreated)
    return false;
  */

  if (control->Tag != nullptr)
  {
    mforms::View *backend = GetBackend<mforms::View>(control);
    if (backend->is_destroying())
      return false;
  }

  return true;
}

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

void ViewWrapper::flush_events(mforms::View *)
{
  Application::DoEvents();
}

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

/**
 * Draws the background image with the earlier set layout, if there's one.
 */
void ViewWrapper::DrawBackground(PaintEventArgs ^args)
{
  if (static_cast<Drawing::Image ^>(backgroundImage) != nullptr)
  {
    int left = 0;
    int top = 0;
    Control ^control = GetControl();

    // Horizontal alignment.
    switch (backgroundImageAlignment)
    {
    case mforms::BottomCenter:
    case mforms::MiddleCenter:
    case mforms::TopCenter:
      left = (control->Width - backgroundImage->Width) / 2;
      break;

    case mforms::BottomRight:
    case mforms::MiddleRight:
    case mforms::TopRight:
      left = control->Width - backgroundImage->Width;
      break;
    }

    // Vertical alignment.
    switch (backgroundImageAlignment)
    {
    case mforms::MiddleLeft:
    case mforms::MiddleCenter:
    case mforms::MiddleRight:
      top = (control->Height - backgroundImage->Height) / 2;
      break;

    case mforms::BottomLeft:
    case mforms::BottomCenter:
    case mforms::BottomRight:
      top = control->Height - backgroundImage->Height;
      break;
    }

    args->Graphics->DrawImage(backgroundImage, Point(left, top));
  }
}

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

void ViewWrapper::set_resize_mode(AutoResizeMode mode)
{
  _resize_mode = mode;
}

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

void ViewWrapper::Initialize()
{
  Control ^control = GetControl();

  // Can be null, e.g. for non-control objects like dialogs.
  if (control != nullptr)
  {
    control->AutoSize = false;
    control->Font = gcnew Font(DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, FontStyle::Regular, GraphicsUnit::Pixel);

    bool hookEvents =
         is<TabControl>(control)
      || (Panel::typeid == control->GetType())
      || is<TreeViewAdv>(control)
      || is<CanvasControl>(control)
      // Don't add ScintillaControl here. It has its own drag/drop handling.
    ;

    if (hookEvents)
    {
      mforms::View *view = GetBackend<mforms::View>(control);
      eventTarget = gcnew ViewEventTarget(view, control);
      control->Resize += gcnew EventHandler(eventTarget, &ViewEventTarget::Resize);
      control->Enter += gcnew EventHandler(eventTarget, &ViewEventTarget::Enter);

      control->MouseDown += gcnew MouseEventHandler(eventTarget, &ViewEventTarget::HandleMouseDown);
      control->MouseUp += gcnew MouseEventHandler(eventTarget, &ViewEventTarget::HandleMouseUp);
      control->MouseMove += gcnew MouseEventHandler(eventTarget, &ViewEventTarget::HandleMouseMove);
      control->MouseClick += gcnew MouseEventHandler(eventTarget, &ViewEventTarget::HandleMouseClick);
      control->MouseDoubleClick += gcnew MouseEventHandler(eventTarget, &ViewEventTarget::HandleMouseDoubleClick);
      control->MouseEnter += gcnew EventHandler(eventTarget, &ViewEventTarget::HandleMouseEnter);
      control->MouseLeave += gcnew EventHandler(eventTarget, &ViewEventTarget::HandleMouseLeave);

      control->DragEnter += gcnew DragEventHandler(eventTarget, &ViewEventTarget::HandleDragEnter);
      control->DragDrop += gcnew DragEventHandler(eventTarget, &ViewEventTarget::HandleDragDrop);
      control->DragOver += gcnew DragEventHandler(eventTarget, &ViewEventTarget::HandleDragOver);
      control->DragLeave += gcnew EventHandler(eventTarget, &ViewEventTarget::HandleDragLeave);
    }
  }
};

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

void ViewWrapper::set_front_color(String ^color)
{
  Control ^control = GetManagedObject<Control>();
  control->ForeColor = System::Drawing::ColorTranslator::FromHtml(color);
}

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

void ViewWrapper::set_padding(int left, int top, int right, int bottom)
{
  Control ^control = GetManagedObject<Control>();
  control->Padding = Windows::Forms::Padding(left, top, right, bottom);
}

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

void ViewWrapper::init()
{
  mforms::ControlFactory *f = mforms::ControlFactory::get_instance();

  f->_view_impl.destroy = &destroy;
  f->_view_impl.get_x = &get_x;
  f->_view_impl.get_y = &get_y;
  f->_view_impl.get_width = &get_width;
  f->_view_impl.get_height = &get_height;
  f->_view_impl.get_preferred_width = &get_preferred_width;
  f->_view_impl.get_preferred_height = &get_preferred_height;
  f->_view_impl.set_position = &set_position;
  f->_view_impl.set_size = &set_size;
  f->_view_impl.set_padding = &set_padding;
  f->_view_impl.screen_to_client = &screen_to_client;
  f->_view_impl.client_to_screen = &client_to_screen;

  f->_view_impl.show = &show;
  f->_view_impl.set_enabled = &set_enabled;
  f->_view_impl.is_enabled = &is_enabled;
  f->_view_impl.set_name = &set_name;
  f->_view_impl.relayout = &relayout;
  f->_view_impl.set_needs_repaint = &set_needs_repaint;
  f->_view_impl.set_tooltip = &set_tooltip;
  f->_view_impl.set_font = &set_font;
  f->_view_impl.is_shown = &is_shown;
  f->_view_impl.is_fully_visible = &is_fully_visible;
  f->_view_impl.suspend_layout = &suspend_layout;
  f->_view_impl.set_front_color = &set_front_color;
  f->_view_impl.get_front_color = &get_front_color;
  f->_view_impl.set_back_color = &set_back_color;
  f->_view_impl.get_back_color = &get_back_color;
  f->_view_impl.set_back_image = &set_back_image;
  f->_view_impl.flush_events = &flush_events;
  f->_view_impl.focus = &focus;
  f->_view_impl.has_focus = &has_focus;

  f->_view_impl.register_drop_formats = &register_drop_formats;
  f->_view_impl.drag_text = &drag_text;
  f->_view_impl.drag_data = &drag_data;
  f->_view_impl.get_drop_position = &get_drop_position;
}

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