//+------------------------------------------------------------------+
//|                                         MaximizableAppDialog.mqh |
//|                                    Copyright (c) 2019, Marketeer |
//|                          https://www.mql5.com/en/users/marketeer |
//+------------------------------------------------------------------+

#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>

#resource "res\\Expand2.bmp"
#resource "res\\size6.bmp"
#resource "res\\size10.bmp"

class MaximizableAppDialog: public CAppDialog
{
  protected:
    CBmpButton m_button_truemax;
    CBmpButton m_button_size;
    bool m_maximized;
    CRect m_max_rect;
    CSize m_size_limit;
    bool m_sizing;

    // window maximization
    virtual bool CreateButtonMinMax(void) override;
    virtual void OnClickButtonMinMax(void) override;
    virtual void OnClickButtonTrueMax(void);
    virtual void OnClickButtonSizeFixMe(void);
    virtual void Expand(void);
    virtual void Restore(void);

    virtual void Minimize(void) override;

    // window resizing
    bool CreateButtonSize(void);
    bool OnDialogSizeStart(void);
    virtual bool OnDialogDragStart(void) override;
    virtual bool OnDialogDragProcess(void) override;
    virtual bool OnDialogDragEnd(void) override;

    virtual void SelfAdjustment(const bool minimized = false) = 0;

  public:
    MaximizableAppDialog(): m_maximized(false), m_sizing(false) {}
    virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) override;
    virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override;

    virtual bool OnChartChange(const long &lparam, const double &dparam, const string &sparam);

    void ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
    
    void SetSizeLimit(const CSize &limit) { m_size_limit = limit; }
    CSize GetSizeLimit() { return m_size_limit; }
};

void MaximizableAppDialog::ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
  if(id == CHARTEVENT_CHART_CHANGE)
  {
    if(OnChartChange(lparam, dparam, sparam)) return;
  }
  CAppDialog::ChartEvent(id, lparam, dparam, sparam);
}

EVENT_MAP_BEGIN(MaximizableAppDialog)
  ON_EVENT(ON_CLICK, m_button_truemax, OnClickButtonTrueMax)
  ON_EVENT(ON_CLICK, m_button_size, OnClickButtonSizeFixMe)
  ON_EVENT(ON_DRAG_START, m_button_size, OnDialogSizeStart)
  ON_EVENT_PTR(ON_DRAG_PROCESS, m_drag_object, OnDialogDragProcess)
  ON_EVENT_PTR(ON_DRAG_END, m_drag_object, OnDialogDragEnd)
EVENT_MAP_END(CAppDialog)

bool MaximizableAppDialog::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2)
{
  // 1 * CONTROLS_BORDER_WIDTH - stays here, because the standard control library minimizes window
  // when it's height is 1 pixel smaller than the entire chart height
  m_max_rect.SetBound(0,
                      0,
                      (int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) - 0 * CONTROLS_BORDER_WIDTH,
                      (int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS) - 1 * CONTROLS_BORDER_WIDTH);
  if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)) return false;
  if(!CreateButtonSize()) return false;
  m_size_limit.cx = x2 - x1;
  m_size_limit.cy = y2 - y1;
  if(m_size_limit.cx >= m_max_rect.Width() || m_size_limit.cy >= m_max_rect.Height())
  {
    m_size_limit.cx = m_min_rect.Width() * 3;
    m_size_limit.cy = m_min_rect.Height() * 7;
  }

  return true;
}

bool MaximizableAppDialog::CreateButtonMinMax(void) override
{
  if(!CAppDialog::CreateButtonMinMax()) return false;

  // add maximization button
  int off = (m_panel_flag) ? 0 : 2 * CONTROLS_BORDER_WIDTH;

  int x1 = Width() - off - 3 * (CONTROLS_BUTTON_SIZE + CONTROLS_DIALOG_BUTTON_OFF);
  int y1 = off + CONTROLS_DIALOG_BUTTON_OFF;
  int x2 = x1 + CONTROLS_BUTTON_SIZE;
  int y2 = y1 + CONTROLS_BUTTON_SIZE;

  if(!m_button_truemax.Create(m_chart_id, m_name + "TrueMax", m_subwin, x1, y1, x2, y2)) return false;
  if(!m_button_truemax.BmpNames("::res\\Expand2.bmp", "::res\\Restore.bmp")) return false;
  if(!CWndContainer::Add(m_button_truemax)) return false;
  
  m_button_truemax.Locking(true);
  m_button_truemax.Alignment(WND_ALIGN_RIGHT, 0, 0, off + 2 * CONTROLS_BUTTON_SIZE + 2 * CONTROLS_DIALOG_BUTTON_OFF, 0);

  CaptionAlignment(WND_ALIGN_WIDTH, off, 0, off + 3 * (CONTROLS_BUTTON_SIZE + CONTROLS_DIALOG_BUTTON_OFF), 0);

  return true;
}

bool MaximizableAppDialog::CreateButtonSize(void)
{
  int off = (m_panel_flag) ? 0 : 2 * CONTROLS_BORDER_WIDTH;

  int x1 = Width() - CONTROLS_BUTTON_SIZE + 1;
  int y1 = Height() - CONTROLS_BUTTON_SIZE + 1;
  int x2 = x1 + CONTROLS_BUTTON_SIZE - 1;
  int y2 = y1 + CONTROLS_BUTTON_SIZE - 1;

  if(!m_button_size.Create(m_chart_id, m_name + "Size", m_subwin, x1, y1, x2, y2)) return false;
  if(!m_button_size.BmpNames("::res\\size6.bmp", "::res\\size10.bmp")) return false;
  if(!CWndContainer::Add(m_button_size)) return false;
  m_button_size.Alignment(WND_ALIGN_RIGHT|WND_ALIGN_BOTTOM, 0, 0, 0, 0);
  m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);

  return true;
}

void MaximizableAppDialog::OnClickButtonTrueMax(void)
{
  if(m_button_truemax.Pressed())
    Expand();
  else
    Restore();

  SubwinOff();
}

// This is a hack. It's required because in minimized state sizing button somehow "overlaps"
// the close button and intercepts clicks on it (which prevents exit from minimized app).
// This happens despite the fact that the sizing button is hidden, disabled and assigned
// with minimal Z-order (checked out, then removed).
// Looks like a bug in the standard control library, specifically:
// In CWnd::OnMouseEvent there must be a line:
//
//   if(!IS_ENABLED || !IS_VISIBLE) return false;
//
// but it's not there, so invisible, disabled and even background objects are processed
// in the same manner as all other objects. Specifically in CWndContainer::OnMouseEvent
// there is a reverse loop through all objects (it does _not_ respect Z-order anyhow).

void MaximizableAppDialog::OnClickButtonSizeFixMe(void)
{
  if(m_minimized)
  {
    Destroy();
  }
}

void MaximizableAppDialog::Expand(void)
{
  m_maximized = true;
  m_minimized = false;
  m_button_minmax.Pressed(false);
  Rebound(m_max_rect);
  m_button_size.Hide();
  m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE);
  m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
  if(!m_panel_flag)
  {
    m_caption.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
  }
  
  ClientAreaVisible(true);
  SelfAdjustment();
}

//+------------------------------------------------------------------+
//| Restore dialog window                                            |
//+------------------------------------------------------------------+
void MaximizableAppDialog::Restore(void)
{
  m_maximized = false;
  m_minimized = false;
  m_button_minmax.Pressed(false);
  m_button_size.Show();
  m_button_size.StateFlagsSet(WND_STATE_FLAG_ENABLE);
  m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
  CAppDialog::Maximize();
  if(!m_panel_flag)
  {
    m_caption.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
  }
  SelfAdjustment();
}

void MaximizableAppDialog::Minimize()
{
  CAppDialog::Minimize();
  m_button_size.Hide();
  m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE);
  m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
}

bool MaximizableAppDialog::OnChartChange(const long &lparam, const double &dparam, const string &sparam)
{
  m_max_rect.SetBound(0, 0,
                      (int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) - 0 * CONTROLS_BORDER_WIDTH,
                      (int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS) - 1 * CONTROLS_BORDER_WIDTH);
  if(m_maximized)
  {
    if(m_rect.Width() != m_max_rect.Width() || m_rect.Height() != m_max_rect.Height())
    {
      Rebound(m_max_rect);
      SelfAdjustment();
      m_chart.Redraw();
    }
    return true;
  }
  return false;
}

void MaximizableAppDialog::OnClickButtonMinMax(void)
{
  CAppDialog::OnClickButtonMinMax();
  m_button_truemax.Pressed(false);
  m_maximized = false;
  if(m_minimized)
  {
    m_button_size.Hide();
    m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE);
    m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
  }
  else
  {
    m_button_size.Show();
    m_button_size.StateFlagsSet(WND_STATE_FLAG_ENABLE);
    m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
  }
  if(!m_panel_flag)
  {
    m_caption.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
  }
  SelfAdjustment(m_minimized);
}

bool MaximizableAppDialog::OnDialogSizeStart(void)
{
  if(m_drag_object == NULL)
  {
    m_drag_object = new CDragWnd;
    if(m_drag_object == NULL) return false;
  }
  int x1 = m_button_size.Left() - CONTROLS_DRAG_SPACING;
  int y1 = m_button_size.Top() - CONTROLS_DRAG_SPACING;
  int x2 = m_button_size.Right() + CONTROLS_DRAG_SPACING;
  int y2 = m_button_size.Bottom() + CONTROLS_DRAG_SPACING;

  m_drag_object.Create(m_chart_id, "", m_subwin, x1, y1, x2, y2);
  m_drag_object.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);

  CChart chart;
  chart.Attach(m_chart_id);
  m_drag_object.Limits(-CONTROLS_DRAG_SPACING, -CONTROLS_DRAG_SPACING,
                       chart.WidthInPixels() + CONTROLS_DRAG_SPACING,
                       chart.HeightInPixels(m_subwin) + CONTROLS_DRAG_SPACING);
  chart.Detach();

  m_drag_object.MouseX(m_button_size.MouseX());
  m_drag_object.MouseY(m_button_size.MouseY());
  m_drag_object.MouseFlags(m_button_size.MouseFlags());
  
  m_sizing = true;

  return true;
}

bool MaximizableAppDialog::OnDialogDragStart(void)
{
  if(m_maximized) return false;
  
  return CAppDialog::OnDialogDragStart();
}
//+------------------------------------------------------------------+
//| Continue dragging the dialog box                                 |
//+------------------------------------------------------------------+
bool MaximizableAppDialog::OnDialogDragProcess(void)
{
  if(!m_sizing) return CDialog::OnDialogDragProcess();

  if(m_drag_object == NULL) return false;

  int x = m_drag_object.Right() - Right() - CONTROLS_DRAG_SPACING;
  int y = m_drag_object.Bottom() - Bottom() - CONTROLS_DRAG_SPACING;

  // resize dialog
  CRect r = Rect();
  r.right += x;
  r.bottom += y;
  
  if(r.Width() < m_size_limit.cx) r.right = r.left + m_size_limit.cx;
  if(r.Height() < m_size_limit.cy) r.bottom = r.top + m_size_limit.cy;
  
  Rebound(r);
  
  SelfAdjustment();

  return true;
}
//+------------------------------------------------------------------+
//| End dragging the dialog box                                      |
//+------------------------------------------------------------------+
bool MaximizableAppDialog::OnDialogDragEnd(void)
{
  if(!m_sizing) return CDialog::OnDialogDragEnd();

  if(m_drag_object != NULL)
  {
    m_button_size.MouseFlags(m_drag_object.MouseFlags());
    delete m_drag_object;
    m_drag_object = NULL;
  }

  m_norm_rect.SetBound(m_rect);
  m_sizing = false;
  
  SelfAdjustment();
  
  return true;
}
