tdf#105884 Qt5 implement TabControl theming

Drawing a QTabWidget is a really complex procedure. The main
problems I had were the adjustment of the frame, which I totally
missed in the Qt code for a long time.

Then there is the frame gap, which Qt draws by simply overlapping
the items a bit with the frame. And all the calculations need the
tabs together with the pane. None of it really fits very good into
the way VCL handles drawing the TabControl and since I needed a
way back from the plugin into VCL for the nOverlap value, there is
this hack using a static. I hope nOverlap never changes.

Change-Id: I8fe6eb12d39a2ac7f6fb89424586cac76e12545b
Reviewed-on: https://gerrit.libreoffice.org/74480
Tested-by: Jenkins
Reviewed-by: Jan-Marek Glogowski <glogow@fbihome.de>
diff --git a/include/vcl/salnativewidgets.hxx b/include/vcl/salnativewidgets.hxx
index 03d9a0e..2a67ceb 100644
--- a/include/vcl/salnativewidgets.hxx
+++ b/include/vcl/salnativewidgets.hxx
@@ -181,6 +181,9 @@
// hardcoded 2 pixel overlap between adjacent tabs
    TabsDrawRtl             = 3000,

// Qt doesn't have a separate header to draw
    TabPaneWithHeader       = 3001,

// For themes that do not want to have the focus
// rectangle part drawn by VCL but take care of the
// whole inner control part by themselves
@@ -340,6 +343,29 @@
        SliderValue & operator =(SliderValue &&) = delete; // due to ImplControlValue
};

class VCL_DLLPUBLIC TabPaneValue : public ImplControlValue
{
public:
    tools::Rectangle m_aTabHeaderRect;
    tools::Rectangle m_aSelectedTabRect;
    // increased tab size, so it'll overlab the frame rect when draing
    // static value, as there is currently no sane way to return additional data
    static int m_nOverlap;

    TabPaneValue(const tools::Rectangle &rTabHeaderRect, const tools::Rectangle &rSelectedTabRect)
        : ImplControlValue(ControlType::TabPane, 0)
        , m_aTabHeaderRect(rTabHeaderRect)
        , m_aSelectedTabRect(rSelectedTabRect)
    {
    }
    TabPaneValue* clone() const override;

    TabPaneValue(TabPaneValue const &) = default;
    TabPaneValue(TabPaneValue &&) = default;
    TabPaneValue & operator =(TabPaneValue const &) = delete;
    TabPaneValue & operator =(TabPaneValue &&) = delete;
};

/* TabitemValue:
 *
 *   Value container for tabitems.
diff --git a/vcl/qt5/Qt5Graphics_Controls.cxx b/vcl/qt5/Qt5Graphics_Controls.cxx
index 6cefef6..73a47e5 100644
--- a/vcl/qt5/Qt5Graphics_Controls.cxx
+++ b/vcl/qt5/Qt5Graphics_Controls.cxx
@@ -105,6 +105,10 @@
        case ControlType::Slider:
            return (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea);

        case ControlType::TabItem:
        case ControlType::TabPane:
            return ((part == ControlPart::Entire) || part == ControlPart::TabPaneWithHeader);

        default:
            break;
    }
@@ -112,7 +116,6 @@
    return false;
}

/// helper drawing methods
namespace
{
void draw(QStyle::ControlElement element, QStyleOption* option, QImage* image,
@@ -163,6 +166,27 @@
        painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
    QApplication::style()->drawPrimitive(element, &option, &painter);
}

void lcl_fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot)
{
    const TabitemValue& rValue = static_cast<const TabitemValue&>(value);
    if (rValue.isFirst())
        sot.position = rValue.isLast() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::Beginning;
    else if (rValue.isLast())
        sot.position = rValue.isFirst() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::End;
    else
        sot.position = QStyleOptionTab::Middle;
}

void lcl_fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option)
{
    option.state = QStyle::State_Enabled;
    option.rightCornerWidgetSize = QSize(0, 0);
    option.leftCornerWidgetSize = QSize(0, 0);
    option.lineWidth = QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
    option.midLineWidth = 0;
    option.shape = QTabBar::RoundedNorth;
}
}

bool Qt5Graphics_Controls::drawNativeControl(ControlType type, ControlPart part,
@@ -609,6 +633,34 @@
        draw(QStyle::CE_ProgressBar, &option, m_image.get(),
             vclStateValue2StateFlag(nControlState, value));
    }
    else if (type == ControlType::TabItem && part == ControlPart::Entire)
    {
        QStyleOptionTab sot;
        lcl_fillQStyleOptionTab(value, sot);
        draw(QStyle::CE_TabBarTabShape, &sot, m_image.get(),
             vclStateValue2StateFlag(nControlState, value));
    }
    else if (type == ControlType::TabPane && part == ControlPart::Entire)
    {
        const TabPaneValue& rValue = static_cast<const TabPaneValue&>(value);

        // get the overlap size for the tabs, so they will overlap the frame
        QStyleOptionTab tabOverlap;
        tabOverlap.shape = QTabBar::RoundedNorth;
        TabPaneValue::m_nOverlap
            = QApplication::style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap);

        QStyleOptionTabWidgetFrame option;
        lcl_fullQStyleOptionTabWidgetFrame(option);
        option.tabBarRect = toQRect(rValue.m_aTabHeaderRect);
        option.selectedTabRect
            = rValue.m_aSelectedTabRect.IsEmpty() ? QRect() : toQRect(rValue.m_aSelectedTabRect);
        option.tabBarSize = toQSize(rValue.m_aTabHeaderRect.GetSize());
        option.rect = m_image->rect();
        QRect aRect = QApplication::style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option);
        draw(QStyle::PE_FrameTabWidget, &option, m_image.get(),
             vclStateValue2StateFlag(nControlState, value), aRect);
    }
    else
    {
        returnVal = false;
@@ -818,7 +870,7 @@
                auto nStyle = static_cast<DrawFrameFlags>(val.getNumericVal() & 0xFFF0);
                if (nStyle & DrawFrameFlags::NoDraw)
                {
                    int nFrameWidth
                    const int nFrameWidth
                        = QApplication::style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
                    contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth);
                }
@@ -912,6 +964,31 @@
            }
            break;
        }
        case ControlType::TabItem:
        {
            QStyleOptionTab sot;
            lcl_fillQStyleOptionTab(val, sot);
            QSize aMinSize = QApplication::style()->sizeFromContents(QStyle::CT_TabBarTab, &sot,
                                                                     contentRect.size());
            contentRect.setSize(aMinSize);
            boundingRect = contentRect;
            retVal = true;
            break;
        }
        case ControlType::TabPane:
        {
            const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
            QStyleOptionTabWidgetFrame sotwf;
            lcl_fullQStyleOptionTabWidgetFrame(sotwf);
            QSize aMinSize = QApplication::style()->sizeFromContents(
                QStyle::CT_TabWidget, &sotwf,
                QSize(std::max(rValue.m_aTabHeaderRect.GetWidth(), controlRegion.GetWidth()),
                      rValue.m_aTabHeaderRect.GetHeight() + controlRegion.GetHeight()));
            contentRect.setSize(aMinSize);
            boundingRect = contentRect;
            retVal = true;
            break;
        }
        default:
            break;
    }
diff --git a/vcl/source/control/tabctrl.cxx b/vcl/source/control/tabctrl.cxx
index d23db1e..9b269e8 100644
--- a/vcl/source/control/tabctrl.cxx
+++ b/vcl/source/control/tabctrl.cxx
@@ -865,6 +865,7 @@
            tiValue.mnAlignment |= TabitemFlags::LastInGroup;

        tools::Rectangle aCtrlRegion( pItem->maRect );
        aCtrlRegion.AdjustBottom(TabPaneValue::m_nOverlap);
        bNativeOK = rRenderContext.DrawNativeControl(ControlType::TabItem, ControlPart::Entire,
                                                     aCtrlRegion, nState, tiValue, OUString() );
    }
@@ -1098,7 +1099,22 @@

    if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
    {
        const ImplControlValue aControlValue;
        const bool bPaneWithHeader = rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::TabPaneWithHeader);
        tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
        if (bPaneWithHeader)
        {
            aRect.SetTop(0);
            if (mpTabCtrlData->maItemList.size())
            {
                long nRight = 0;
                for (auto &item : mpTabCtrlData->maItemList)
                    if (item.m_bVisible)
                        nRight = item.maRect.Right();
                assert(nRight);
                aHeaderRect.SetRight(nRight);
            }
        }
        const TabPaneValue aTabPaneValue(aHeaderRect, pCurItem ? pCurItem->maRect : tools::Rectangle());

        ControlState nState = ControlState::ENABLED;
        if (!IsEnabled())
@@ -1108,15 +1124,12 @@

        if (lcl_canPaint(rRenderContext, rRect, aRect))
            rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
                                             aRect, nState, aControlValue, OUString());
                                             aRect, nState, aTabPaneValue, OUString());

        if (rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire))
        {
            tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
            if (lcl_canPaint(rRenderContext, rRect, aHeaderRect))
                rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
                                                 aHeaderRect, nState, aControlValue, OUString());
        }
        if (!bPaneWithHeader && rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire)
                && lcl_canPaint(rRenderContext, rRect, aHeaderRect))
            rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
                                             aHeaderRect, nState, aTabPaneValue, OUString());
    }
    else
    {
diff --git a/vcl/source/outdev/nativecontrols.cxx b/vcl/source/outdev/nativecontrols.cxx
index ef22958..0021834 100644
--- a/vcl/source/outdev/nativecontrols.cxx
+++ b/vcl/source/outdev/nativecontrols.cxx
@@ -95,6 +95,14 @@
    return new SliderValue( *this );
}

int TabPaneValue::m_nOverlap = 0;

TabPaneValue* TabPaneValue::clone() const
{
    assert(typeid(const TabPaneValue) == typeid(*this));
    return new TabPaneValue(*this);
}

TabitemValue::~TabitemValue()
{
}
@@ -229,6 +237,15 @@
            pNew->maGripRect = rDev.ImplLogicToDevicePixel( pTVal->maGripRect );
        }
        break;
    case ControlType::TabPane:
        {
            const TabPaneValue* pTIVal = static_cast<const TabPaneValue*>(&rVal);
            TabPaneValue* pNew = new TabPaneValue(*pTIVal);
            pNew->m_aTabHeaderRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aTabHeaderRect);
            pNew->m_aSelectedTabRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aSelectedTabRect);
            aResult.reset(pNew);
        }
        break;
    case ControlType::TabItem:
        {
            const TabitemValue* pTIVal = static_cast<const TabitemValue*>(&rVal);
@@ -263,7 +280,7 @@
        }
        break;
    default:
        OSL_FAIL( "unknown ImplControlValue type !" );
        std::abort();
        break;
    }
    return aResult;