/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#include <GroupsSorting.hxx>
#include <svtools/editbrowsebox.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/container/XContainerListener.hpp>
#include <com/sun/star/report/GroupOn.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
 
#include <strings.hrc>
#include <rptui_slotid.hrc>
#include <core_resource.hxx>
#include <helpids.h>
#include "GroupExchange.hxx"
#include <UITools.hxx>
#include <UndoActions.hxx>
#include <strings.hxx>
#include <ReportController.hxx>
#include <ColumnInfo.hxx>
 
#include <comphelper/propertyvalue.hxx>
#include <cppuhelper/implbase.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/svapp.hxx>
#include <comphelper/diagnose_ex.hxx>
 
#include <algorithm>
 
#define HANDLE_ID           0
#define FIELD_EXPRESSION    1
#define GROUPS_START_LEN    5
#define NO_GROUP            -1
 
namespace rptui
{
using namespace ::com::sun::star;
using namespace svt;
using namespace ::comphelper;
 
    static void lcl_addToList_throw( weld::ComboBox& _rListBox, ::std::vector<ColumnInfo>& o_aColumnList,const uno::Reference< container::XNameAccess>& i_xColumns )
    {
        const uno::Sequence< OUString > aEntries = i_xColumns->getElementNames();
        for ( const OUString& rEntry : aEntries )
        {
            uno::Reference< beans::XPropertySet> xColumn(i_xColumns->getByName(rEntry),uno::UNO_QUERY_THROW);
            OUString sLabel;
            if ( xColumn->getPropertySetInfo()->hasPropertyByName(PROPERTY_LABEL) )
                xColumn->getPropertyValue(PROPERTY_LABEL) >>= sLabel;
            o_aColumnList.emplace_back(rEntry,sLabel );
            if ( !sLabel.isEmpty() )
                _rListBox.append_text( sLabel );
            else
                _rListBox.append_text( rEntry );
        }
    }
 
/**
  * Separated out from OFieldExpressionControl to prevent collision of ref-counted base classes
  */
class OFieldExpressionControl;
 
namespace {
 
class OFieldExpressionControlContainerListener : public ::cppu::WeakImplHelper< container::XContainerListener >
{
    VclPtr<OFieldExpressionControl> mpParent;
public:
    explicit OFieldExpressionControlContainerListener(OFieldExpressionControl* pParent) : mpParent(pParent) {}
 
    // XEventListener
    virtual void SAL_CALL disposing(const css::lang::EventObject& Source) override;
    // XContainerListener
    virtual void SAL_CALL elementInserted(const css::container::ContainerEvent& rEvent) override;
    virtual void SAL_CALL elementReplaced(const css::container::ContainerEvent& rEvent) override;
    virtual void SAL_CALL elementRemoved(const css::container::ContainerEvent& rEvent) override;
};
 
}
 
class OFieldExpressionControl : public ::svt::EditBrowseBox
{
    ::osl::Mutex                    m_aMutex;
    ::std::vector<sal_Int32>        m_aGroupPositions;
    ::std::vector<ColumnInfo>       m_aColumnInfo;
    VclPtr< ::svt::ComboBoxControl>  m_pComboCell;
    sal_Int32                       m_nDataPos;
    sal_Int32                       m_nCurrentPos;
    ImplSVEvent *                   m_nDeleteEvent;
    OGroupsSortingDialog*           m_pParent;
    bool                            m_bIgnoreEvent;
    rtl::Reference<OFieldExpressionControlContainerListener> aContainerListener;
 
public:
    OFieldExpressionControl(OGroupsSortingDialog* pParentDialog, const css::uno::Reference<css::awt::XWindow> &rParent);
    virtual ~OFieldExpressionControl() override;
    virtual void dispose() override;
 
    // XContainerListener
    /// @throws css::uno::RuntimeException
    void elementInserted(const css::container::ContainerEvent& rEvent);
    /// @throws css::uno::RuntimeException
    void elementRemoved(const css::container::ContainerEvent& rEvent);
 
    virtual Size GetOptimalSize() const override;
 
    void        fillColumns(const uno::Reference< container::XNameAccess>& _xColumns);
    void        lateInit();
    bool    IsDeleteAllowed( ) const;
    void        DeleteRows();
 
    sal_Int32   getGroupPosition(sal_Int32 _nRow) const { return _nRow != BROWSER_ENDOFSELECTION ? m_aGroupPositions[_nRow] : sal_Int32(NO_GROUP); }
 
    /** returns the sequence with the selected groups
    */
    uno::Sequence<uno::Any> fillSelectedGroups();
 
    /** move groups given by _aGroups
    */
    void moveGroups(const uno::Sequence<uno::Any>& _aGroups,sal_Int32 _nRow,bool _bSelect = true);
 
    virtual bool CursorMoving(sal_Int32 nNewRow, sal_uInt16 nNewCol) override;
    using ::svt::EditBrowseBox::GetRowCount;
protected:
    virtual bool IsTabAllowed(bool bForward) const override;
 
    virtual void InitController( ::svt::CellControllerRef& rController, sal_Int32 nRow, sal_uInt16 nCol ) override;
    virtual ::svt::CellController* GetController( sal_Int32 nRow, sal_uInt16 nCol ) override;
    virtual void PaintCell( OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColId ) const override;
    virtual bool SeekRow( sal_Int32 nRow ) override;
    virtual bool SaveModified() override;
    virtual OUString GetCellText( sal_Int32 nRow, sal_uInt16 nColId ) const override;
    virtual RowStatus GetRowStatus(sal_Int32 nRow) const override;
 
    virtual void KeyInput(const KeyEvent& rEvt) override;
    virtual void Command( const CommandEvent& rEvt ) override;
 
    // D&D
    virtual void     StartDrag( sal_Int8 nAction, const Point& rPosPixel ) override;
    virtual sal_Int8 AcceptDrop( const BrowserAcceptDropEvent& rEvt ) override;
    virtual sal_Int8 ExecuteDrop( const BrowserExecuteDropEvent& rEvt ) override;
 
    using BrowseBox::AcceptDrop;
    using BrowseBox::ExecuteDrop;
 
private:
 
    DECL_LINK( CBChangeHdl, weld::ComboBox&, void);
 
public:
    DECL_LINK( DelayedDelete, void*, void );
 
};
 
 
void OFieldExpressionControlContainerListener::disposing(const css::lang::EventObject& )
{}
 
void OFieldExpressionControlContainerListener::elementInserted(const css::container::ContainerEvent& rEvent)
{ mpParent->elementInserted(rEvent); }
 
void OFieldExpressionControlContainerListener::elementReplaced(const css::container::ContainerEvent& )
{}
 
void OFieldExpressionControlContainerListener::elementRemoved(const css::container::ContainerEvent& rEvent)
{ mpParent->elementRemoved(rEvent); }
 
OFieldExpressionControl::OFieldExpressionControl(OGroupsSortingDialog* pParentDialog, const css::uno::Reference<css::awt::XWindow> &rParent)
    :EditBrowseBox( VCLUnoHelper::GetWindow(rParent), EditBrowseBoxFlags::NONE, WB_TABSTOP,
                    BrowserMode::COLUMNSELECTION | BrowserMode::MULTISELECTION | BrowserMode::AUTOSIZE_LASTCOL |
                              BrowserMode::KEEPHIGHLIGHT | BrowserMode::HLINES | BrowserMode::VLINES)
    ,m_aGroupPositions(GROUPS_START_LEN,-1)
    ,m_pComboCell(nullptr)
    ,m_nDataPos(-1)
    ,m_nCurrentPos(-1)
    ,m_nDeleteEvent(nullptr)
    ,m_pParent(pParentDialog)
    ,m_bIgnoreEvent(false)
    ,aContainerListener(new OFieldExpressionControlContainerListener(this))
{
    SetBorderStyle(WindowBorderStyle::MONO);
}
 
OFieldExpressionControl::~OFieldExpressionControl()
{
    disposeOnce();
}
 
void OFieldExpressionControl::dispose()
{
    uno::Reference< report::XGroups > xGroups = m_pParent->getGroups();
    xGroups->removeContainerListener(aContainerListener);
 
    // delete events from queue
    if( m_nDeleteEvent )
        Application::RemoveUserEvent( m_nDeleteEvent );
 
    m_pComboCell.disposeAndClear();
    m_pParent = nullptr;
    ::svt::EditBrowseBox::dispose();
}
 
uno::Sequence<uno::Any> OFieldExpressionControl::fillSelectedGroups()
{
    uno::Sequence<uno::Any> aList;
    ::std::vector<uno::Any> vClipboardList;
    vClipboardList.reserve(GetSelectRowCount());
 
    uno::Reference<report::XGroups> xGroups = m_pParent->getGroups();
    sal_Int32 nCount = xGroups->getCount();
    if ( nCount >= 1 )
    {
        for( tools::Long nIndex=FirstSelectedRow(); nIndex != SFX_ENDOFSELECTION; nIndex=NextSelectedRow() )
        {
            try
            {
                if ( m_aGroupPositions[nIndex] != NO_GROUP )
                {
                    uno::Reference< report::XGroup> xOrgGroup(xGroups->getByIndex(m_aGroupPositions[nIndex]),uno::UNO_QUERY);
                    /*uno::Reference< report::XGroup> xCopy = xGroups->createGroup();
                    ::comphelper::copyProperties(xOrgGroup.get(),xCopy.get());*/
                    vClipboardList.push_back( uno::Any(xOrgGroup) );
                }
            }
            catch(uno::Exception&)
            {
                OSL_FAIL("Can not access group!");
            }
        }
        if ( !vClipboardList.empty() )
            aList = uno::Sequence< uno::Any >(vClipboardList.data(), vClipboardList.size());
    }
    return aList;
}
 
void OFieldExpressionControl::StartDrag( sal_Int8 /*_nAction*/ , const Point& /*_rPosPixel*/ )
{
    if ( m_pParent && !m_pParent->isReadOnly( ) )
    {
        uno::Sequence<uno::Any> aClipboardList = fillSelectedGroups();
 
        if( aClipboardList.hasElements() )
        {
            rtl::Reference<OGroupExchange> pData = new OGroupExchange(aClipboardList);
            pData->StartDrag(this, DND_ACTION_MOVE );
        }
    }
}
 
sal_Int8 OFieldExpressionControl::AcceptDrop( const BrowserAcceptDropEvent& rEvt )
{
    sal_Int8 nAction = DND_ACTION_NONE;
    if ( IsEditing() )
    {
        weld::ComboBox& rComboBox = m_pComboCell->get_widget();
        sal_Int32 nPos = rComboBox.get_active();
        if (nPos != -1 || !rComboBox.get_active_text().isEmpty())
            SaveModified();
        DeactivateCell();
    }
    if ( IsDropFormatSupported( OGroupExchange::getReportGroupId() ) && m_pParent->getGroups()->getCount() > 1 && rEvt.GetWindow() == &GetDataWindow() )
    {
        nAction = DND_ACTION_MOVE;
    }
    return nAction;
}
 
sal_Int8 OFieldExpressionControl::ExecuteDrop( const BrowserExecuteDropEvent& rEvt )
{
    sal_Int8 nAction = DND_ACTION_NONE;
    if ( IsDropFormatSupported( OGroupExchange::getReportGroupId() ) )
    {
        sal_Int32   nRow = GetRowAtYPosPixel(rEvt.maPosPixel.Y(), false);
        SetNoSelection();
 
        TransferableDataHelper aDropped( rEvt.maDropEvent.Transferable );
        uno::Any aDrop = aDropped.GetAny(OGroupExchange::getReportGroupId(), OUString());
        uno::Sequence< uno::Any > aGroups;
        aDrop >>= aGroups;
        if ( aGroups.hasElements() )
        {
            moveGroups(aGroups,nRow);
            nAction = DND_ACTION_MOVE;
        }
    }
    return nAction;
}
 
void OFieldExpressionControl::moveGroups(const uno::Sequence<uno::Any>& _aGroups,sal_Int32 _nRow,bool _bSelect)
{
    if ( !_aGroups.hasElements() )
        return;
 
    m_bIgnoreEvent = true;
    {
        sal_Int32 nRow = _nRow;
        const OUString sUndoAction(RptResId(RID_STR_UNDO_MOVE_GROUP));
        const UndoContext aUndoContext( m_pParent->m_pController->getUndoManager(), sUndoAction );
 
        uno::Reference< report::XGroups> xGroups = m_pParent->getGroups();
        for(const uno::Any& rGroup : _aGroups)
        {
            uno::Reference< report::XGroup> xGroup(rGroup,uno::UNO_QUERY);
            if ( xGroup.is() )
            {
                uno::Sequence< beans::PropertyValue > aArgs{ comphelper::makePropertyValue(
                    PROPERTY_GROUP, xGroup) };
                // we use this way to create undo actions
                m_pParent->m_pController->executeChecked(SID_GROUP_REMOVE,aArgs);
                aArgs.realloc(2);
                auto pArgs = aArgs.getArray();
                if ( nRow > xGroups->getCount() )
                    nRow = xGroups->getCount();
                if ( _bSelect )
                    SelectRow(nRow);
                pArgs[1].Name = PROPERTY_POSITIONY;
                pArgs[1].Value <<= nRow;
                m_pParent->m_pController->executeChecked(SID_GROUP_APPEND,aArgs);
                ++nRow;
            }
        }
    }
    m_bIgnoreEvent = false;
    Invalidate();
}
 
void OFieldExpressionControl::fillColumns(const uno::Reference< container::XNameAccess>& _xColumns)
{
    weld::ComboBox& rComboBox = m_pComboCell->get_widget();
    rComboBox.clear();
    if ( _xColumns.is() )
        lcl_addToList_throw(rComboBox, m_aColumnInfo, _xColumns);
}
 
void OFieldExpressionControl::lateInit()
{
    uno::Reference< report::XGroups > xGroups = m_pParent->getGroups();
    sal_Int32 nGroupsCount = xGroups->getCount();
    m_aGroupPositions.resize(::std::max<sal_Int32>(nGroupsCount,sal_Int32(GROUPS_START_LEN)),NO_GROUP);
    ::std::vector<sal_Int32>::iterator aIter = m_aGroupPositions.begin();
    for (sal_Int32 i = 0; i < nGroupsCount; ++i,++aIter)
        *aIter = i;
 
    if ( ColCount() == 0 )
    {
        vcl::Font aFont( GetDataWindow().GetFont() );
        aFont.SetWeight( WEIGHT_NORMAL );
        GetDataWindow().SetFont( aFont );
 
        // Set font of the headline to light
        aFont = GetFont();
        aFont.SetWeight( WEIGHT_LIGHT );
        SetFont(aFont);
 
        InsertHandleColumn(static_cast<sal_uInt16>(GetTextWidth(OUString('0')) * 4)/*, sal_True */);
        InsertDataColumn( FIELD_EXPRESSION, RptResId(STR_RPT_EXPRESSION), 100);
 
        m_pComboCell = VclPtr<ComboBoxControl>::Create( &GetDataWindow() );
        weld::ComboBox& rComboBox = m_pComboCell->get_widget();
        rComboBox.connect_changed(LINK(this,OFieldExpressionControl,CBChangeHdl));
        m_pComboCell->SetHelpId(HID_RPT_FIELDEXPRESSION);
 
        m_pComboCell->SetFocusInHdl(LINK(m_pParent, OGroupsSortingDialog, OnControlFocusGot));
 
 
        // set browse mode
        BrowserMode nMode(BrowserMode::COLUMNSELECTION | BrowserMode::MULTISELECTION  | BrowserMode::KEEPHIGHLIGHT |
                          BrowserMode::HLINES | BrowserMode::VLINES       | BrowserMode::AUTOSIZE_LASTCOL | BrowserMode::AUTO_VSCROLL | BrowserMode::AUTO_HSCROLL);
        if( m_pParent->isReadOnly() )
            nMode |= BrowserMode::HIDECURSOR;
        SetMode(nMode);
        xGroups->addContainerListener(aContainerListener);
    }
    else
        // not the first call
        RowRemoved(0, GetRowCount());
 
    RowInserted(0, m_aGroupPositions.size());
}
 
IMPL_LINK_NOARG( OFieldExpressionControl, CBChangeHdl, weld::ComboBox&, void )
{
    SaveModified();
}
 
bool OFieldExpressionControl::IsTabAllowed(bool /*bForward*/) const
{
    return false;
}
 
bool OFieldExpressionControl::SaveModified()
{
    sal_Int32 nRow = GetCurRow();
    if ( nRow == BROWSER_ENDOFSELECTION )
        return true;
 
    try
    {
        bool bAppend = false;
        uno::Reference< report::XGroup> xGroup;
        if ( m_aGroupPositions[nRow] == NO_GROUP )
        {
            bAppend = true;
            OUString sUndoAction(RptResId(RID_STR_UNDO_APPEND_GROUP));
            m_pParent->m_pController->getUndoManager().EnterListAction( sUndoAction, OUString(), 0, ViewShellId(-1) );
            xGroup = m_pParent->getGroups()->createGroup();
            xGroup->setHeaderOn(true);
 
            // find position where to insert the new group
            sal_Int32 nGroupPos = 0;
            ::std::vector<sal_Int32>::iterator aIter = m_aGroupPositions.begin();
            ::std::vector<sal_Int32>::const_iterator aEnd  = m_aGroupPositions.begin() + nRow;
            for(;aIter != aEnd;++aIter)
                if ( *aIter != NO_GROUP )
                    nGroupPos = *aIter + 1;
            uno::Sequence< beans::PropertyValue > aArgs{
                comphelper::makePropertyValue(PROPERTY_GROUP, xGroup),
                comphelper::makePropertyValue(PROPERTY_POSITIONY, nGroupPos)
            };
            m_bIgnoreEvent = true;
            m_pParent->m_pController->executeChecked(SID_GROUP_APPEND,aArgs);
            m_bIgnoreEvent = false;
            OSL_ENSURE(*aIter == NO_GROUP ,"Illegal iterator!");
            *aIter++ = nGroupPos;
 
            aEnd  = m_aGroupPositions.end();
            for(;aIter != aEnd;++aIter)
                if ( *aIter != NO_GROUP )
                    ++*aIter;
        }
        else
            xGroup = m_pParent->getGroup(m_aGroupPositions[nRow]);
        if ( xGroup.is() )
        {
            weld::ComboBox& rComboBox = m_pComboCell->get_widget();
            sal_Int32 nPos = rComboBox.get_active();
            OUString sExpression;
            if (nPos == -1)
                sExpression = rComboBox.get_active_text();
            else
            {
                sExpression = m_aColumnInfo[nPos].sColumnName;
            }
            xGroup->setExpression( sExpression );
 
            ::rptui::adjustSectionName(xGroup,nPos);
 
            if ( bAppend )
                m_pParent->m_pController->getUndoManager().LeaveListAction();
        }
 
        if (Controller().is())
            Controller()->SaveValue();
        if ( GetRowCount() == m_pParent->getGroups()->getCount() )
        {
            RowInserted( GetRowCount()-1);
            m_aGroupPositions.push_back(NO_GROUP);
        }
 
        GoToRow(nRow);
        m_pParent->DisplayData(nRow);
    }
    catch(uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION( "reportdesign", "OFieldExpressionControl::SaveModified");
    }
 
    return true;
}
 
OUString OFieldExpressionControl::GetCellText( sal_Int32 nRow, sal_uInt16 /*nColId*/ ) const
{
    OUString sText;
    if ( nRow != BROWSER_ENDOFSELECTION && m_aGroupPositions[nRow] != NO_GROUP )
    {
        try
        {
            uno::Reference< report::XGroup> xGroup = m_pParent->getGroup(m_aGroupPositions[nRow]);
            OUString sExpression = xGroup->getExpression();
 
            auto aIter = std::find_if(m_aColumnInfo.begin(), m_aColumnInfo.end(),
                [&sExpression](const ColumnInfo& rColumnInfo) { return rColumnInfo.sColumnName == sExpression; });
            if (aIter != m_aColumnInfo.end() && !aIter->sLabel.isEmpty())
                sExpression = aIter->sLabel;
            sText = sExpression;
        }
        catch (const uno::Exception&)
        {
            TOOLS_WARN_EXCEPTION( "reportdesign", "Exception caught while getting expression value from the group");
        }
    }
    return sText;
}
 
void OFieldExpressionControl::InitController( CellControllerRef& /*rController*/, sal_Int32 nRow, sal_uInt16 nColumnId )
{
    weld::ComboBox& rComboBox = m_pComboCell->get_widget();
    rComboBox.set_entry_text(GetCellText(nRow, nColumnId));
}
 
bool OFieldExpressionControl::CursorMoving(sal_Int32 nNewRow, sal_uInt16 nNewCol)
{
 
    if (!EditBrowseBox::CursorMoving(nNewRow, nNewCol))
        return false;
    m_nDataPos = nNewRow;
    tools::Long nOldDataPos = GetCurRow();
    InvalidateStatusCell( m_nDataPos );
    InvalidateStatusCell( nOldDataPos );
 
    m_pParent->SaveData( nOldDataPos );
    m_pParent->DisplayData( m_nDataPos );
    return true;
}
 
CellController* OFieldExpressionControl::GetController( sal_Int32 /*nRow*/, sal_uInt16 /*nColumnId*/ )
{
    ComboBoxCellController* pCellController = new ComboBoxCellController( m_pComboCell );
    pCellController->GetComboBox().set_entry_editable(m_pParent->m_pController->isEditable());
    return pCellController;
}
 
bool OFieldExpressionControl::SeekRow( sal_Int32 _nRow )
{
    // the basis class needs the call, because that's how the class knows which line will be painted
    EditBrowseBox::SeekRow(_nRow);
    m_nCurrentPos = _nRow;
    return true;
}
 
void OFieldExpressionControl::PaintCell( OutputDevice& rDev, const tools::Rectangle& rRect, sal_uInt16 nColumnId ) const
{
    OUString aText  =GetCellText( m_nCurrentPos, nColumnId );
 
    Point aPos( rRect.TopLeft() );
    Size aTextSize( GetDataWindow().GetTextWidth( aText ), GetDataWindow().GetTextHeight() );
 
    if( aPos.X() < rRect.Left() || aPos.X() + aTextSize.Width() > rRect.Right() ||
        aPos.Y() < rRect.Top() || aPos.Y() + aTextSize.Height() > rRect.Bottom() )
        rDev.SetClipRegion(vcl::Region(rRect));
 
    rDev.DrawText( aPos, aText );
 
    if( rDev.IsClipRegion() )
        rDev.SetClipRegion();
}
 
EditBrowseBox::RowStatus OFieldExpressionControl::GetRowStatus(sal_Int32 nRow) const
{
    if (nRow >= 0 && nRow == m_nDataPos)
        return EditBrowseBox::CURRENT;
    if ( nRow != BROWSER_ENDOFSELECTION && nRow < static_cast<tools::Long>(m_aGroupPositions.size()) && m_aGroupPositions[nRow] != NO_GROUP )
    {
        try
        {
            uno::Reference< report::XGroup> xGroup = m_pParent->getGroup(m_aGroupPositions[nRow]);
            return (xGroup->getHeaderOn() || xGroup->getFooterOn())? EditBrowseBox::HEADERFOOTER : EditBrowseBox::CLEAN;
        }
        catch(uno::Exception&)
        {
            TOOLS_WARN_EXCEPTION( "reportdesign", "Exception caught while try to get a group!");
        }
    }
    return EditBrowseBox::CLEAN;
}
 
// XContainerListener
 
void OFieldExpressionControl::elementInserted(const container::ContainerEvent& evt)
{
    if ( m_bIgnoreEvent )
        return;
    SolarMutexGuard aSolarGuard;
    ::osl::MutexGuard aGuard( m_aMutex );
    sal_Int32 nGroupPos = 0;
    if ( !(evt.Accessor >>= nGroupPos) )
        return;
 
    if ( nGroupPos >= GetRowCount() )
    {
        sal_Int32 nAddedRows = nGroupPos - GetRowCount();
        RowInserted(nAddedRows);
        for (sal_Int32 i = 0; i < nAddedRows; ++i)
            m_aGroupPositions.push_back(NO_GROUP);
        m_aGroupPositions[nGroupPos] = nGroupPos;
    }
    else
    {
        ::std::vector<sal_Int32>::iterator aFind = m_aGroupPositions.begin()+ nGroupPos;
        if ( aFind == m_aGroupPositions.end() )
            aFind = ::std::find(m_aGroupPositions.begin(),m_aGroupPositions.end(),NO_GROUP);
 
        if ( aFind != m_aGroupPositions.end() )
        {
            if ( *aFind != NO_GROUP )
                aFind = m_aGroupPositions.insert(aFind,nGroupPos);
            else
                *aFind = nGroupPos;
 
            ::std::vector<sal_Int32>::const_iterator aEnd  = m_aGroupPositions.end();
            for(++aFind;aFind != aEnd;++aFind)
                if ( *aFind != NO_GROUP )
                    ++*aFind;
        }
    }
    Invalidate();
}
 
void OFieldExpressionControl::elementRemoved(const container::ContainerEvent& evt)
{
    SolarMutexGuard aSolarGuard;
    ::osl::MutexGuard aGuard( m_aMutex );
 
    if ( m_bIgnoreEvent )
        return;
 
    sal_Int32 nGroupPos = 0;
    if ( !(evt.Accessor >>= nGroupPos) )
        return;
 
    std::vector<sal_Int32>::iterator aEnd = m_aGroupPositions.end();
    std::vector<sal_Int32>::iterator aFind = std::find(m_aGroupPositions.begin(), aEnd, nGroupPos);
    if (aFind != aEnd)
    {
        *aFind = NO_GROUP;
        for(++aFind;aFind != aEnd;++aFind)
            if ( *aFind != NO_GROUP )
                --*aFind;
        Invalidate();
    }
}
 
bool OFieldExpressionControl::IsDeleteAllowed( ) const
{
    return !m_pParent->isReadOnly() && GetSelectRowCount() > 0;
}
 
void OFieldExpressionControl::KeyInput( const KeyEvent& rEvt )
{
    if (IsDeleteAllowed())
    {
        if (rEvt.GetKeyCode().GetCode() == KEY_DELETE &&    // Delete rows
            !rEvt.GetKeyCode().IsShift() &&
            !rEvt.GetKeyCode().IsMod1())
        {
            DeleteRows();
            return;
        }
    }
    EditBrowseBox::KeyInput(rEvt);
}
 
void OFieldExpressionControl::Command(const CommandEvent& rEvt)
{
    switch (rEvt.GetCommand())
    {
        case CommandEventId::ContextMenu:
        {
            if (!rEvt.IsMouseEvent())
            {
                EditBrowseBox::Command(rEvt);
                return;
            }
 
            sal_uInt16 nColId = GetColumnId(GetColumnAtXPosPixel(rEvt.GetMousePosPixel().X()));
 
            if ( nColId == HANDLE_ID )
            {
                bool bEnable = false;
                tools::Long nIndex = FirstSelectedRow();
                while( nIndex != SFX_ENDOFSELECTION && !bEnable )
                {
                    if ( m_aGroupPositions[nIndex] != NO_GROUP )
                        bEnable = true;
                    nIndex = NextSelectedRow();
                }
 
                ::tools::Rectangle aRect(rEvt.GetMousePosPixel(), Size(1, 1));
                weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect);
                std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, u"modules/dbreport/ui/groupsortmenu.ui"_ustr));
                std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu(u"menu"_ustr));
                xContextMenu->set_sensitive(u"delete"_ustr, IsDeleteAllowed() && bEnable);
                if (!xContextMenu->popup_at_rect(pPopupParent, aRect).isEmpty())
                {
                    if( m_nDeleteEvent )
                        Application::RemoveUserEvent( m_nDeleteEvent );
                    m_nDeleteEvent = Application::PostUserEvent( LINK(this, OFieldExpressionControl, DelayedDelete), nullptr, true );
                }
            }
            [[fallthrough]];
        }
        default:
            EditBrowseBox::Command(rEvt);
    }
 
}
 
void OFieldExpressionControl::DeleteRows()
{
 
    bool bIsEditing = IsEditing();
    if (bIsEditing)
    {
        DeactivateCell();
    }
    tools::Long nIndex = FirstSelectedRow();
    if (nIndex == SFX_ENDOFSELECTION)
    {
        nIndex = GetCurRow();
    }
    bool bFirstTime = true;
 
    tools::Long nOldDataPos = nIndex;
    m_bIgnoreEvent = true;
    while( nIndex >= 0 )
    {
        if ( m_aGroupPositions[nIndex] != NO_GROUP )
        {
            if ( bFirstTime )
            {
                bFirstTime = false;
                OUString sUndoAction(RptResId(RID_STR_UNDO_REMOVE_SELECTION));
                m_pParent->m_pController->getUndoManager().EnterListAction( sUndoAction, OUString(), 0, ViewShellId(-1) );
            }
 
            sal_Int32 nGroupPos = m_aGroupPositions[nIndex];
            uno::Reference< report::XGroup> xGroup = m_pParent->getGroup(nGroupPos);
            uno::Sequence< beans::PropertyValue > aArgs{ comphelper::makePropertyValue(
                PROPERTY_GROUP, xGroup) };
            // we use this way to create undo actions
            m_pParent->m_pController->executeChecked(SID_GROUP_REMOVE,aArgs);
 
            std::vector<sal_Int32>::iterator aEnd  = m_aGroupPositions.end();
            std::vector<sal_Int32>::iterator aFind = std::find(m_aGroupPositions.begin(), aEnd, nGroupPos);
            if (aFind != aEnd)
            {
                *aFind = NO_GROUP;
                for(++aFind;aFind != aEnd;++aFind)
                    if ( *aFind != NO_GROUP )
                        --*aFind;
            }
        }
        nIndex = NextSelectedRow();
    }
 
    if ( !bFirstTime )
        m_pParent->m_pController->getUndoManager().LeaveListAction();
 
    m_nDataPos = GetCurRow();
    InvalidateStatusCell( nOldDataPos );
    InvalidateStatusCell( m_nDataPos );
    ActivateCell();
    m_pParent->DisplayData( m_nDataPos );
    m_bIgnoreEvent = false;
    Invalidate();
}
 
IMPL_LINK_NOARG( OFieldExpressionControl, DelayedDelete, void*, void )
{
    m_nDeleteEvent = nullptr;
    DeleteRows();
}
 
Size OFieldExpressionControl::GetOptimalSize() const
{
    return LogicToPixel(Size(106, 75), MapMode(MapUnit::MapAppFont));
}
 
OGroupsSortingDialog::OGroupsSortingDialog(weld::Window* pParent, bool bReadOnly,
                                           OReportController* pController)
    : GenericDialogController(pParent, u"modules/dbreport/ui/floatingsort.ui"_ustr, u"FloatingSort"_ustr)
    , OPropertyChangeListener()
    , m_pController(pController)
    , m_xGroups(m_pController->getReportDefinition()->getGroups())
    , m_bReadOnly(bReadOnly)
    , m_xToolBox(m_xBuilder->weld_toolbar(u"toolbox"_ustr))
    , m_xProperties(m_xBuilder->weld_widget(u"properties"_ustr))
    , m_xOrderLst(m_xBuilder->weld_combo_box(u"sorting"_ustr))
    , m_xHeaderLst(m_xBuilder->weld_combo_box(u"header"_ustr))
    , m_xFooterLst(m_xBuilder->weld_combo_box(u"footer"_ustr))
    , m_xGroupOnLst(m_xBuilder->weld_combo_box(u"group"_ustr))
    , m_xGroupIntervalEd(m_xBuilder->weld_spin_button(u"interval"_ustr))
    , m_xKeepTogetherLst(m_xBuilder->weld_combo_box(u"keep"_ustr))
    , m_xHelpWindow(m_xBuilder->weld_label(u"helptext"_ustr))
    , m_xBox(m_xBuilder->weld_container(u"box"_ustr))
    , m_xTableCtrlParent(m_xBox->CreateChildFrame())
    , m_xFieldExpression(VclPtr<OFieldExpressionControl>::Create(this, m_xTableCtrlParent))
{
    m_xHelpWindow->set_size_request(-1, m_xHelpWindow->get_text_height() * 4);
    m_xFieldExpression->set_hexpand(true);
    m_xFieldExpression->set_vexpand(true);
 
    weld::Widget* pControlsLst[] = { m_xHeaderLst.get(), m_xFooterLst.get(), m_xGroupOnLst.get(),
                                     m_xKeepTogetherLst.get(), m_xOrderLst.get(), m_xGroupIntervalEd.get() };
    for (weld::Widget* i : pControlsLst)
    {
        i->connect_focus_in(LINK(this, OGroupsSortingDialog, OnWidgetFocusGot));
        i->show();
    }
 
    m_xGroupIntervalEd->connect_focus_out(LINK(this, OGroupsSortingDialog, OnWidgetFocusLost));
 
    for (size_t i = 0; i < SAL_N_ELEMENTS(pControlsLst) - 1; ++i)
        dynamic_cast<weld::ComboBox&>(*pControlsLst[i]).connect_changed(LINK(this,OGroupsSortingDialog,LBChangeHdl));
 
    m_pReportListener = new OPropertyChangeMultiplexer(this, m_pController->getReportDefinition());
    m_pReportListener->addProperty(PROPERTY_COMMAND);
    m_pReportListener->addProperty(PROPERTY_COMMANDTYPE);
 
    m_xFieldExpression->lateInit();
    fillColumns();
    Size aPrefSize = m_xFieldExpression->GetOptimalSize();
    m_xBox->set_size_request(aPrefSize.Width(), aPrefSize.Height());
    m_xFieldExpression->Show();
 
    m_xToolBox->connect_clicked(LINK(this, OGroupsSortingDialog, OnFormatAction));
 
    checkButtons(0);
}
 
OGroupsSortingDialog::~OGroupsSortingDialog()
{
    m_pReportListener->dispose();
    if ( m_pCurrentGroupListener.is() )
        m_pCurrentGroupListener->dispose();
    m_xFieldExpression.disposeAndClear();
    m_xTableCtrlParent->dispose();
    m_xTableCtrlParent.clear();
}
 
void OGroupsSortingDialog::UpdateData( )
{
    m_xFieldExpression->Invalidate();
    sal_Int32 nCurRow = m_xFieldExpression->GetCurRow();
    m_xFieldExpression->DeactivateCell();
    m_xFieldExpression->ActivateCell(nCurRow, m_xFieldExpression->GetCurColumnId());
    DisplayData(nCurRow);
}
 
void OGroupsSortingDialog::DisplayData( sal_Int32 _nRow )
{
    const sal_Int32 nGroupPos = m_xFieldExpression->getGroupPosition(_nRow);
    const bool bEmpty = nGroupPos == NO_GROUP;
    m_xProperties->set_sensitive(!bEmpty);
 
    checkButtons(_nRow);
 
    if ( m_pCurrentGroupListener.is() )
        m_pCurrentGroupListener->dispose();
    m_pCurrentGroupListener = nullptr;
    if (!bEmpty)
    {
        uno::Reference< report::XGroup> xGroup = getGroup(nGroupPos);
 
        m_pCurrentGroupListener = new OPropertyChangeMultiplexer(this, xGroup);
        m_pCurrentGroupListener->addProperty(PROPERTY_HEADERON);
        m_pCurrentGroupListener->addProperty(PROPERTY_FOOTERON);
 
        displayGroup(xGroup);
    }
}
 
void OGroupsSortingDialog::SaveData( sal_Int32 _nRow)
{
    sal_Int32 nGroupPos = m_xFieldExpression->getGroupPosition(_nRow);
    if ( nGroupPos == NO_GROUP )
        return;
 
    uno::Reference< report::XGroup> xGroup = getGroup(nGroupPos);
    if (m_xHeaderLst->get_value_changed_from_saved())
        xGroup->setHeaderOn( m_xHeaderLst->get_active() == 0 );
    if (m_xFooterLst->get_value_changed_from_saved())
        xGroup->setFooterOn( m_xFooterLst->get_active() == 0 );
    if (m_xKeepTogetherLst->get_value_changed_from_saved())
        xGroup->setKeepTogether( m_xKeepTogetherLst->get_active() );
    if (m_xGroupOnLst->get_value_changed_from_saved())
    {
        auto nGroupOn = m_xGroupOnLst->get_active_id().toInt32();
        xGroup->setGroupOn( nGroupOn );
    }
    if (m_xGroupIntervalEd->get_value_changed_from_saved())
    {
        xGroup->setGroupInterval(m_xGroupIntervalEd->get_value());
        m_xGroupIntervalEd->save_value();
    }
    if ( m_xOrderLst->get_value_changed_from_saved() )
        xGroup->setSortAscending( m_xOrderLst->get_active() == 0 );
 
    weld::ComboBox* pControls[] = { m_xHeaderLst.get(), m_xFooterLst.get(), m_xGroupOnLst.get(),
                                    m_xKeepTogetherLst.get(), m_xOrderLst.get() };
    for (weld::ComboBox* pControl : pControls)
        pControl->save_value();
}
 
sal_Int32 OGroupsSortingDialog::getColumnDataType(const OUString& _sColumnName)
{
    sal_Int32 nDataType = sdbc::DataType::VARCHAR;
    try
    {
        if ( !m_xColumns.is() )
            fillColumns();
        if ( m_xColumns.is() && m_xColumns->hasByName(_sColumnName) )
        {
            uno::Reference< beans::XPropertySet> xColumn(m_xColumns->getByName(_sColumnName),uno::UNO_QUERY);
            if ( xColumn.is() )
                xColumn->getPropertyValue(PROPERTY_TYPE) >>= nDataType;
        }
    }
    catch(uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION( "reportdesign", "Exception caught while getting the type of a column");
    }
 
    return nDataType;
}
 
IMPL_LINK_NOARG(OGroupsSortingDialog, OnControlFocusGot, LinkParamNone*, void )
{
    m_xHelpWindow->set_label(RptResId(STR_RPT_HELP_FIELD));
}
 
IMPL_LINK(OGroupsSortingDialog, OnWidgetFocusGot, weld::Widget&, rControl, void )
{
    const std::pair<weld::Widget*, TranslateId> pControls[] = {
        { m_xHeaderLst.get(), STR_RPT_HELP_HEADER },
        { m_xFooterLst.get(), STR_RPT_HELP_FOOTER },
        { m_xGroupOnLst.get(), STR_RPT_HELP_GROUPON },
        { m_xGroupIntervalEd.get(), STR_RPT_HELP_INTERVAL },
        { m_xKeepTogetherLst.get(), STR_RPT_HELP_KEEP },
        { m_xOrderLst.get(), STR_RPT_HELP_SORT }
    };
    for (size_t i = 0; i < SAL_N_ELEMENTS(pControls); ++i)
    {
        if (&rControl == pControls[i].first)
        {
            weld::ComboBox* pListBox = dynamic_cast<weld::ComboBox*>( &rControl );
            if ( pListBox )
                pListBox->save_value();
            weld::SpinButton* pNumericField = dynamic_cast<weld::SpinButton*>(&rControl);
            if ( pNumericField )
                pNumericField->save_value();
            //shows the text given by the id in the multiline edit
            m_xHelpWindow->set_label(RptResId(pControls[i].second));
            break;
        }
    }
}
 
IMPL_LINK_NOARG(OGroupsSortingDialog, OnWidgetFocusLost, weld::Widget&, void)
{
    if (m_xFieldExpression)
    {
        if (m_xGroupIntervalEd->get_value_changed_from_saved())
            SaveData(m_xFieldExpression->GetCurRow());
    }
}
 
IMPL_LINK(OGroupsSortingDialog, OnFormatAction, const OUString&, rCommand, void)
{
    if ( !m_xFieldExpression )
        return;
 
    tools::Long nIndex = m_xFieldExpression->GetCurrRow();
    sal_Int32 nGroupPos = m_xFieldExpression->getGroupPosition(nIndex);
    uno::Sequence<uno::Any> aClipboardList;
    if ( nIndex >= 0 && nGroupPos != NO_GROUP )
    {
        aClipboardList = { m_xGroups->getByIndex(nGroupPos) };
    }
    if (rCommand == "up")
    {
        --nIndex;
    }
    if (rCommand == "down")
    {
        ++nIndex;
    }
    if (rCommand == "delete")
    {
        Application::PostUserEvent(LINK(m_xFieldExpression, OFieldExpressionControl, DelayedDelete));
    }
    else
    {
        if ( nIndex >= 0 && aClipboardList.hasElements() )
        {
            m_xFieldExpression->SetNoSelection();
            m_xFieldExpression->moveGroups(aClipboardList,nIndex,false);
            m_xFieldExpression->DeactivateCell();
            m_xFieldExpression->GoToRow(nIndex);
            m_xFieldExpression->ActivateCell(nIndex, m_xFieldExpression->GetCurColumnId());
            DisplayData(nIndex);
        }
    }
}
 
IMPL_LINK( OGroupsSortingDialog, LBChangeHdl, weld::ComboBox&, rListBox, void )
{
    if ( !rListBox.get_value_changed_from_saved() )
        return;
 
    sal_Int32 nRow = m_xFieldExpression->GetCurRow();
    sal_Int32 nGroupPos = m_xFieldExpression->getGroupPosition(nRow);
    if (&rListBox != m_xHeaderLst.get() && &rListBox != m_xFooterLst.get())
    {
        if ( rListBox.get_value_changed_from_saved() )
            SaveData(nRow);
        if ( &rListBox == m_xGroupOnLst.get() )
            m_xGroupIntervalEd->set_sensitive(rListBox.get_active() != 0);
    }
    else if ( nGroupPos != NO_GROUP )
    {
        uno::Reference< report::XGroup> xGroup = getGroup(nGroupPos);
        const OUString aHeaderFooterOnName(( m_xHeaderLst.get() == &rListBox )
                                               ? std::u16string_view(PROPERTY_HEADERON)
                                               : std::u16string_view(PROPERTY_FOOTERON));
        uno::Sequence< beans::PropertyValue > aArgs{
            comphelper::makePropertyValue(aHeaderFooterOnName, rListBox.get_active() == 0),
            comphelper::makePropertyValue(PROPERTY_GROUP, xGroup)
        };
        m_pController->executeChecked(m_xHeaderLst.get() == &rListBox ? SID_GROUPHEADER : SID_GROUPFOOTER, aArgs);
        m_xFieldExpression->InvalidateHandleColumn();
    }
}
 
void OGroupsSortingDialog::_propertyChanged(const beans::PropertyChangeEvent& _rEvent)
{
    uno::Reference< report::XGroup > xGroup(_rEvent.Source,uno::UNO_QUERY);
    if ( xGroup.is() )
        displayGroup(xGroup);
    else
        fillColumns();
}
 
void OGroupsSortingDialog::fillColumns()
{
    m_xColumns = m_pController->getColumns();
    m_xFieldExpression->fillColumns(m_xColumns);
}
 
void OGroupsSortingDialog::displayGroup(const uno::Reference<report::XGroup>& _xGroup)
{
    m_xHeaderLst->set_active(_xGroup->getHeaderOn() ? 0 : 1 );
    m_xFooterLst->set_active(_xGroup->getFooterOn() ? 0 : 1 );
    sal_Int32 nDataType = getColumnDataType(_xGroup->getExpression());
 
    // first clear whole group on list
    while (m_xGroupOnLst->get_count() > 1 )
    {
        m_xGroupOnLst->remove(1);
    }
 
    switch(nDataType)
    {
        case sdbc::DataType::LONGVARCHAR:
        case sdbc::DataType::VARCHAR:
        case sdbc::DataType::CHAR:
            m_xGroupOnLst->append(OUString::number(report::GroupOn::PREFIX_CHARACTERS), RptResId(STR_RPT_PREFIXCHARS));
            break;
        case sdbc::DataType::DATE:
        case sdbc::DataType::TIME:
        case sdbc::DataType::TIMESTAMP:
            {
                const TranslateId aIds[] = { STR_RPT_YEAR, STR_RPT_QUARTER,STR_RPT_MONTH,STR_RPT_WEEK,STR_RPT_DAY,STR_RPT_HOUR,STR_RPT_MINUTE };
                for (size_t i = 0; i < SAL_N_ELEMENTS(aIds); ++i)
                {
                    m_xGroupOnLst->append(OUString::number(i+2), RptResId(aIds[i]));
                }
            }
            break;
        default:
            m_xGroupOnLst->append(OUString::number(report::GroupOn::INTERVAL), RptResId(STR_RPT_INTERVAL));
            break;
    }
    sal_Int32 nPos = 0;
    switch(_xGroup->getGroupOn())
    {
        case report::GroupOn::DEFAULT:
            nPos = 0;
            break;
        case report::GroupOn::INTERVAL:
        case report::GroupOn::PREFIX_CHARACTERS:
        case report::GroupOn::YEAR:
            nPos = 1;
            break;
        case report::GroupOn::QUARTAL:
            nPos = 2;
            break;
        case report::GroupOn::MONTH:
            nPos = 3;
            break;
        case report::GroupOn::WEEK:
            nPos = 4;
            break;
        case report::GroupOn::DAY:
            nPos = 5;
            break;
        case report::GroupOn::HOUR:
            nPos = 6;
            break;
        case report::GroupOn::MINUTE:
            nPos = 7;
            break;
        default:
            nPos = 0;
    }
    m_xGroupOnLst->set_active(nPos);
    m_xGroupIntervalEd->set_value(_xGroup->getGroupInterval());
    m_xGroupIntervalEd->save_value();
    m_xGroupIntervalEd->set_sensitive( nPos != 0 );
    m_xKeepTogetherLst->set_active(_xGroup->getKeepTogether());
    m_xOrderLst->set_active(_xGroup->getSortAscending() ? 0 : 1);
 
    weld::ComboBox* pControls[] = { m_xHeaderLst.get(), m_xFooterLst.get(), m_xGroupOnLst.get(),
                                    m_xKeepTogetherLst.get(), m_xOrderLst.get() };
    for (weld::ComboBox* pControl : pControls)
        pControl->save_value();
 
    bool bReadOnly = !m_pController->isEditable();
    for (weld::ComboBox* pControl : pControls)
        pControl->set_sensitive(!bReadOnly);
    m_xGroupIntervalEd->set_editable(!bReadOnly);
}
 
void OGroupsSortingDialog::checkButtons(sal_Int32 _nRow)
{
    sal_Int32 nGroupCount = m_xGroups->getCount();
    sal_Int32 nRowCount = m_xFieldExpression->GetRowCount();
    bool bEnabled = nGroupCount > 1;
 
    if (bEnabled && _nRow > 0 )
    {
        m_xToolBox->set_item_sensitive(u"up"_ustr, true);
    }
    else
    {
        m_xToolBox->set_item_sensitive(u"up"_ustr, false);
    }
    if (bEnabled && _nRow < (nRowCount - 1) )
    {
        m_xToolBox->set_item_sensitive(u"down"_ustr, true);
    }
    else
    {
        m_xToolBox->set_item_sensitive(u"down"_ustr, false);
    }
 
    sal_Int32 nGroupPos = m_xFieldExpression->getGroupPosition(_nRow);
    if ( nGroupPos != NO_GROUP )
    {
        bool bEnableDelete = nGroupCount > 0;
        m_xToolBox->set_item_sensitive(u"delete"_ustr, bEnableDelete);
    }
    else
    {
        m_xToolBox->set_item_sensitive(u"delete"_ustr, false);
    }
}
 
} // rptui
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V785 Constant expression in switch statement.