﻿// TortoiseMerge - a Diff/Patch program

// Copyright (C) 2006, 2011, 2016, 2020-2021 - TortoiseSVN

// 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; either version 2
// of the License, or (at your option) any later version.

// 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,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "stdafx.h"
#include "XSplitter.h"
#include "Theme.h"

#ifdef _DEBUG
// ReSharper disable once CppInconsistentNaming
#    define new DEBUG_NEW
#    undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CXSplitter::CXSplitter()
    : m_bBarLocked(FALSE)
    , m_nHiddenCol(-1)
    , m_nHiddenRow(-1)
    , m_pColOldSize(nullptr)
    , m_pRowOldSize(nullptr)
    , m_nOldCols(0)
    , m_nOldRows(0)
{
}

CXSplitter::~CXSplitter()
{
    delete[] m_pRowOldSize;
    delete[] m_pColOldSize;
}

BEGIN_MESSAGE_MAP(CXSplitter, CSplitterWnd)
    ON_WM_LBUTTONDBLCLK()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_WM_MOUSEMOVE()
    ON_WM_SETCURSOR()
END_MESSAGE_MAP()

void CXSplitter::OnLButtonDown(UINT nFlags, CPoint point)
{
    if (!m_bBarLocked)
        CSplitterWnd::OnLButtonDown(nFlags, point);
}

void CXSplitter::OnMouseMove(UINT nFlags, CPoint point)
{
    if (!m_bBarLocked)
        CSplitterWnd::OnMouseMove(nFlags, point);
    else
        CWnd::OnMouseMove(nFlags, point);
}

BOOL CXSplitter::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    if (!m_bBarLocked)
        return CWnd::OnSetCursor(pWnd, nHitTest, message);

    return CSplitterWnd::OnSetCursor(pWnd, nHitTest, message);
}

void CXSplitter::HideRow(int nRowHide)
{
    ASSERT_VALID(this);
    ASSERT(m_nRows > 1);
    ASSERT(nRowHide < m_nRows);
    ASSERT(m_nHiddenRow == -1);
    m_nHiddenRow = nRowHide;

    int nActiveRow, nActiveCol;

    // if the nRow has an active window -- change it
    if (GetActivePane(&nActiveRow, &nActiveCol) != nullptr)
    {
        if (nActiveRow == nRowHide)
        {
            if (++nActiveRow >= m_nRows)
                nActiveRow = 0;
            SetActivePane(nActiveRow, nActiveCol);
        }
    }

    // hide all nRow panes.
    for (int nCol = 0; nCol < m_nCols; ++nCol)
    {
        CWnd* pPaneHide = GetPane(nRowHide, nCol);
        ASSERT(pPaneHide != nullptr);

        pPaneHide->ShowWindow(SW_HIDE);
        pPaneHide->SetDlgCtrlID(AFX_IDW_PANE_FIRST + nCol * 16 + m_nRows);

        for (int nRow = nRowHide + 1; nRow < m_nRows; ++nRow)
        {
            CWnd* pPane = GetPane(nRow, nCol);
            ASSERT(pPane != nullptr);

            pPane->SetDlgCtrlID(IdFromRowCol(nRow - 1, nCol));
        }
    }

    m_nRows--;
    m_pRowInfo[m_nRows].nCurSize = m_pRowInfo[nRowHide].nCurSize;
    RecalcLayout();
}

void CXSplitter::ShowRow()
{
    ASSERT_VALID(this);
    ASSERT(m_nRows < m_nMaxRows);
    ASSERT(m_nHiddenRow != -1);

    int nShowRow = m_nHiddenRow;
    m_nHiddenRow = -1;

    int cyNew = m_pRowInfo[m_nRows].nCurSize;
    m_nRows++; // add a nRow

    ASSERT(m_nRows == m_nMaxRows);

    int nRow;

    // Show the hidden nRow
    for (int nCol = 0; nCol < m_nCols; ++nCol)
    {
        CWnd* pPaneShow = GetDlgItem(AFX_IDW_PANE_FIRST + nCol * 16 + m_nRows);
        ASSERT(pPaneShow != nullptr);
        pPaneShow->ShowWindow(SW_SHOWNA);

        for (nRow = m_nRows - 2; nRow >= nShowRow; --nRow)
        {
            CWnd* pPane = GetPane(nRow, nCol);
            ASSERT(pPane != nullptr);
            pPane->SetDlgCtrlID(IdFromRowCol(nRow + 1, nCol));
        }

        pPaneShow->SetDlgCtrlID(IdFromRowCol(nShowRow, nCol));
    }

    // new panes have been created -- recalculate layout
    for (nRow = nShowRow + 1; nRow < m_nRows; nRow++)
        m_pRowInfo[nRow].nIdealSize = m_pRowInfo[nRow - 1].nCurSize;

    m_pRowInfo[nShowRow].nIdealSize = cyNew;
    RecalcLayout();
}

void CXSplitter::HideColumn(int nColHide)
{
    ASSERT_VALID(this);
    ASSERT(m_nCols > 1);
    ASSERT(nColHide < m_nCols);
    ASSERT(m_nHiddenCol == -1);
    m_nHiddenCol = nColHide;

    // if the column has an active window -- change it
    int nActiveRow, nActiveCol;
    if (GetActivePane(&nActiveRow, &nActiveCol) != nullptr)
    {
        if (nActiveCol == nColHide)
        {
            if (++nActiveCol >= m_nCols)
                nActiveCol = 0;
            SetActivePane(nActiveRow, nActiveCol);
        }
    }

    // hide all column panes
    for (int nRow = 0; nRow < m_nRows; nRow++)
    {
        CWnd* pPaneHide = GetPane(nRow, nColHide);
        ASSERT(pPaneHide != nullptr);

        pPaneHide->ShowWindow(SW_HIDE);
        pPaneHide->SetDlgCtrlID(AFX_IDW_PANE_FIRST + nRow * 16 + m_nCols);

        for (int nCol = nColHide + 1; nCol < m_nCols; nCol++)
        {
            CWnd* pPane = GetPane(nRow, nCol);
            ASSERT(pPane != nullptr);

            pPane->SetDlgCtrlID(IdFromRowCol(nRow, nCol - 1));
        }
    }

    m_nCols--;
    m_pColInfo[m_nCols].nCurSize = m_pColInfo[nColHide].nCurSize;
    RecalcLayout();
}

void CXSplitter::ShowColumn()
{
    ASSERT_VALID(this);
    ASSERT(m_nCols < m_nMaxCols);
    ASSERT(m_nHiddenCol != -1);

    int nShowCol = m_nHiddenCol;
    m_nHiddenCol = -1;

    int cxNew = m_pColInfo[m_nCols].nCurSize;
    m_nCols++; // add a column

    ASSERT(m_nCols == m_nMaxCols);

    int nCol;

    // Show the hidden column
    for (int nRow = 0; nRow < m_nRows; ++nRow)
    {
        CWnd* pPaneShow = GetDlgItem(AFX_IDW_PANE_FIRST + nRow * 16 + m_nCols);
        ASSERT(pPaneShow != nullptr);
        pPaneShow->ShowWindow(SW_SHOWNA);

        for (nCol = m_nCols - 2; nCol >= nShowCol; --nCol)
        {
            CWnd* pPane = GetPane(nRow, nCol);
            ASSERT(pPane != nullptr);
            pPane->SetDlgCtrlID(IdFromRowCol(nRow, nCol + 1));
        }

        pPaneShow->SetDlgCtrlID(IdFromRowCol(nRow, nShowCol));
    }

    // new panes have been created -- recalculate layout
    for (nCol = nShowCol + 1; nCol < m_nCols; nCol++)
        m_pColInfo[nCol].nIdealSize = m_pColInfo[nCol - 1].nCurSize;

    m_pColInfo[nShowCol].nIdealSize = cxNew;
    RecalcLayout();
}

void CXSplitter::CenterSplitter()
{
    // get the size of all views
    int width  = 0;
    int height = 0;
    for (int nRow = 0; nRow < m_nRows; ++nRow)
    {
        height += m_pRowInfo[nRow].nCurSize;
    }
    for (int nCol = 0; nCol < m_nCols; ++nCol)
    {
        width += m_pColInfo[nCol].nCurSize;
    }

    // now set the sizes of the views
    for (int nRow = 0; nRow < m_nRows; ++nRow)
    {
        m_pRowInfo[nRow].nIdealSize = height / m_nRows;
    }
    for (int nCol = 0; nCol < m_nCols; ++nCol)
    {
        m_pColInfo[nCol].nIdealSize = width / m_nCols;
    }
    RecalcLayout();
    CopyRowAndColInfo();
}

void CXSplitter::OnLButtonDblClk(UINT /*nFlags*/, CPoint /*point*/)
{
    CenterSplitter();
}

void CXSplitter::OnLButtonUp(UINT nFlags, CPoint point)
{
    CSplitterWnd::OnLButtonUp(nFlags, point);
    CopyRowAndColInfo();
}

void CXSplitter::CopyRowAndColInfo()
{
    delete[] m_pColOldSize;
    m_pColOldSize = nullptr;
    delete[] m_pRowOldSize;
    m_pRowOldSize = nullptr;

    m_nOldCols = m_nCols;
    m_nOldRows = m_nRows;
    if (m_nCols)
    {
        m_pColOldSize = new int[m_nCols];
        for (int i = 0; i < m_nCols; ++i)
            m_pColOldSize[i] = m_pColInfo[i].nCurSize;
    }
    if (m_nRows)
    {
        m_pRowOldSize = new int[m_nRows];
        for (int i = 0; i < m_nRows; ++i)
            m_pRowOldSize[i] = m_pRowInfo[i].nCurSize;
    }
}

void CXSplitter::OnDrawSplitter(CDC* pDC, ESplitType nType, const CRect& rectArg)
{
    if (!CTheme::Instance().IsDarkTheme())
        return __super::OnDrawSplitter(pDC, nType, rectArg);
    // if pDC == NULL, then just invalidate
    if (pDC == nullptr)
    {
        RedrawWindow(rectArg, nullptr, RDW_INVALIDATE | RDW_NOCHILDREN);
        return;
    }
    ASSERT_VALID(pDC);

    // otherwise, actually draw
    CRect rect = rectArg;
    switch (nType)
    {
        case splitBorder:
            pDC->Draw3dRect(rect, CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNSHADOW)), CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNHILIGHT)));
            rect.InflateRect(-AFX_CX_BORDER, -AFX_CY_BORDER);
            pDC->Draw3dRect(rect, CTheme::Instance().GetThemeColor(GetSysColor(COLOR_WINDOWFRAME)), CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNFACE)));
            return;

        case splitIntersection:
            break;

        case splitBox:
            pDC->Draw3dRect(rect, CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNFACE)), CTheme::Instance().GetThemeColor(GetSysColor(COLOR_WINDOWFRAME)));
            rect.InflateRect(-AFX_CX_BORDER, -AFX_CY_BORDER);
            pDC->Draw3dRect(rect, CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNHIGHLIGHT)), CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNSHADOW)));
            rect.InflateRect(-AFX_CX_BORDER, -AFX_CY_BORDER);
            break;

        case splitBar:
            break;

        default:
            ASSERT(FALSE); // unknown splitter type
    }

    // fill the middle
    COLORREF clr = CTheme::Instance().GetThemeColor(GetSysColor(COLOR_BTNFACE));
    pDC->FillSolidRect(rect, clr);
}
