summaryrefslogtreecommitdiffstats
path: root/svx/source/form/filtnav.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'svx/source/form/filtnav.cxx')
-rw-r--r--svx/source/form/filtnav.cxx1926
1 files changed, 1926 insertions, 0 deletions
diff --git a/svx/source/form/filtnav.cxx b/svx/source/form/filtnav.cxx
new file mode 100644
index 000000000..2b6dee83e
--- /dev/null
+++ b/svx/source/form/filtnav.cxx
@@ -0,0 +1,1926 @@
+/* -*- 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 <memory>
+#include <filtnav.hxx>
+#include <fmexch.hxx>
+#include <helpids.h>
+#include <fmprop.hxx>
+#include <svx/strings.hrc>
+
+#include <com/sun/star/awt/XControl.hpp>
+#include <com/sun/star/form/runtime/XFormController.hpp>
+#include <com/sun/star/util/NumberFormatter.hpp>
+#include <com/sun/star/sdb/SQLContext.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <connectivity/dbtools.hxx>
+#include <connectivity/sqlnode.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <fmshimp.hxx>
+#include <o3tl/safeint.hxx>
+#include <sfx2/objitem.hxx>
+#include <sfx2/request.hxx>
+#include <svx/dialmgr.hxx>
+#include <svx/fmshell.hxx>
+#include <svx/fmtools.hxx>
+#include <svx/svxids.hrc>
+#include <vcl/settings.hxx>
+#include <tools/diagnose_ex.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svlbitm.hxx>
+#include <vcl/treelistentry.hxx>
+#include <vcl/viewdataentry.hxx>
+#include <vcl/svapp.hxx>
+
+#include <bitmaps.hlst>
+
+#include <functional>
+
+#define DROP_ACTION_TIMER_INITIAL_TICKS 10
+ // it takes this long for the scrolling to begin
+#define DROP_ACTION_TIMER_SCROLL_TICKS 3
+ // a line is scrolled in these intervals
+#define DROP_ACTION_TIMER_TICK_BASE 10
+ // this is the basis for multiplying both figures (in ms)
+
+using namespace ::svxform;
+using namespace ::connectivity;
+using namespace ::dbtools;
+
+
+namespace svxform
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::container::XIndexAccess;
+ using ::com::sun::star::uno::UNO_QUERY;
+ using ::com::sun::star::beans::XPropertySet;
+ using ::com::sun::star::form::runtime::XFormController;
+ using ::com::sun::star::form::runtime::XFilterController;
+ using ::com::sun::star::form::runtime::XFilterControllerListener;
+ using ::com::sun::star::form::runtime::FilterEvent;
+ using ::com::sun::star::lang::EventObject;
+ using ::com::sun::star::form::XForm;
+ using ::com::sun::star::container::XChild;
+ using ::com::sun::star::awt::XControl;
+ using ::com::sun::star::sdbc::XConnection;
+ using ::com::sun::star::util::XNumberFormatsSupplier;
+ using ::com::sun::star::util::XNumberFormatter;
+ using ::com::sun::star::util::NumberFormatter;
+ using ::com::sun::star::sdbc::XRowSet;
+ using ::com::sun::star::lang::Locale;
+ using ::com::sun::star::sdb::SQLContext;
+ using ::com::sun::star::uno::XInterface;
+ using ::com::sun::star::uno::UNO_QUERY_THROW;
+ using ::com::sun::star::uno::UNO_SET_THROW;
+ using ::com::sun::star::uno::Exception;
+ using ::com::sun::star::uno::Sequence;
+
+
+OFilterItemExchange::OFilterItemExchange()
+ : m_pFormItem(nullptr)
+{
+}
+
+void OFilterItemExchange::AddSupportedFormats()
+{
+ AddFormat(getFormatId());
+}
+
+
+SotClipboardFormatId OFilterItemExchange::getFormatId()
+{
+ static SotClipboardFormatId s_nFormat =
+ SotExchange::RegisterFormatName("application/x-openoffice;windows_formatname=\"form.FilterControlExchange\"");
+ DBG_ASSERT(static_cast<SotClipboardFormatId>(-1) != s_nFormat, "OFilterExchangeHelper::getFormatId: bad exchange id!");
+ return s_nFormat;
+}
+
+
+OLocalExchange* OFilterExchangeHelper::createExchange() const
+{
+ return new OFilterItemExchange;
+}
+
+
+Image FmFilterData::GetImage() const
+{
+ return Image();
+}
+
+FmParentData::~FmParentData()
+{
+}
+
+Image FmFormItem::GetImage() const
+{
+ return Image(StockImage::Yes, RID_SVXBMP_FORM);
+}
+
+FmFilterItem* FmFilterItems::Find( const ::sal_Int32 _nFilterComponentIndex ) const
+{
+ for ( auto & pData : m_aChildren )
+ {
+ FmFilterItem& rCondition = dynamic_cast<FmFilterItem&>(*pData);
+ if ( _nFilterComponentIndex == rCondition.GetComponentIndex() )
+ return &rCondition;
+ }
+ return nullptr;
+}
+
+Image FmFilterItems::GetImage() const
+{
+ return Image(StockImage::Yes, RID_SVXBMP_FILTER);
+}
+
+FmFilterItem::FmFilterItem( FmFilterItems* pParent,
+ const OUString& aFieldName,
+ const OUString& aText,
+ const sal_Int32 _nComponentIndex )
+ :FmFilterData(pParent, aText)
+ ,m_aFieldName(aFieldName)
+ ,m_nComponentIndex( _nComponentIndex )
+{
+}
+
+Image FmFilterItem::GetImage() const
+{
+ return Image(StockImage::Yes, RID_SVXBMP_FIELD);
+}
+
+// Hints for communication between model and view
+
+namespace {
+
+class FmFilterHint : public SfxHint
+{
+ FmFilterData* m_pData;
+
+public:
+ explicit FmFilterHint(FmFilterData* pData):m_pData(pData){}
+ FmFilterData* GetData() const { return m_pData; }
+};
+
+class FmFilterInsertedHint : public FmFilterHint
+{
+ size_t m_nPos; // Position relative to the parent of the data
+
+public:
+ FmFilterInsertedHint(FmFilterData* pData, size_t nRelPos)
+ :FmFilterHint(pData)
+ ,m_nPos(nRelPos){}
+
+ size_t GetPos() const { return m_nPos; }
+};
+
+class FmFilterRemovedHint : public FmFilterHint
+{
+public:
+ explicit FmFilterRemovedHint(FmFilterData* pData)
+ :FmFilterHint(pData){}
+};
+
+
+class FmFilterTextChangedHint : public FmFilterHint
+{
+public:
+ explicit FmFilterTextChangedHint(FmFilterData* pData)
+ :FmFilterHint(pData){}
+};
+
+class FilterClearingHint : public SfxHint
+{
+public:
+ FilterClearingHint(){}
+};
+
+class FmFilterCurrentChangedHint : public SfxHint
+{
+public:
+ FmFilterCurrentChangedHint(){}
+};
+
+}
+
+// class FmFilterAdapter, listener at the FilterControls
+class FmFilterAdapter : public ::cppu::WeakImplHelper< XFilterControllerListener >
+{
+ FmFilterModel* m_pModel;
+ Reference< XIndexAccess > m_xControllers;
+
+public:
+ FmFilterAdapter(FmFilterModel* pModel, const Reference< XIndexAccess >& xControllers);
+
+// XEventListener
+ virtual void SAL_CALL disposing(const EventObject& Source) override;
+
+// XFilterControllerListener
+ virtual void SAL_CALL predicateExpressionChanged( const FilterEvent& Event ) override;
+ virtual void SAL_CALL disjunctiveTermRemoved( const FilterEvent& Event ) override;
+ virtual void SAL_CALL disjunctiveTermAdded( const FilterEvent& Event ) override;
+
+// helpers
+ /// @throws RuntimeException
+ void dispose();
+
+ void AddOrRemoveListener( const Reference< XIndexAccess >& _rxControllers, const bool _bAdd );
+
+ static void setText(sal_Int32 nPos,
+ const FmFilterItem* pFilterItem,
+ const OUString& rText);
+};
+
+
+FmFilterAdapter::FmFilterAdapter(FmFilterModel* pModel, const Reference< XIndexAccess >& xControllers)
+ :m_pModel( pModel )
+ ,m_xControllers( xControllers )
+{
+ AddOrRemoveListener( m_xControllers, true );
+}
+
+
+void FmFilterAdapter::dispose()
+{
+ AddOrRemoveListener( m_xControllers, false );
+}
+
+
+void FmFilterAdapter::AddOrRemoveListener( const Reference< XIndexAccess >& _rxControllers, const bool _bAdd )
+{
+ for (sal_Int32 i = 0, nLen = _rxControllers->getCount(); i < nLen; ++i)
+ {
+ Reference< XIndexAccess > xElement( _rxControllers->getByIndex(i), UNO_QUERY );
+
+ // step down
+ AddOrRemoveListener( xElement, _bAdd );
+
+ // handle this particular controller
+ Reference< XFilterController > xController( xElement, UNO_QUERY );
+ OSL_ENSURE( xController.is(), "FmFilterAdapter::InsertElements: no XFilterController, cannot sync data!" );
+ if ( xController.is() )
+ {
+ if ( _bAdd )
+ xController->addFilterControllerListener( this );
+ else
+ xController->removeFilterControllerListener( this );
+ }
+ }
+}
+
+
+void FmFilterAdapter::setText(sal_Int32 nRowPos,
+ const FmFilterItem* pFilterItem,
+ const OUString& rText)
+{
+ FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( pFilterItem->GetParent()->GetParent() );
+ assert(pFormItem);
+ try
+ {
+ Reference< XFilterController > xController( pFormItem->GetController(), UNO_QUERY_THROW );
+ xController->setPredicateExpression( pFilterItem->GetComponentIndex(), nRowPos, rText );
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+}
+
+
+// XEventListener
+
+void SAL_CALL FmFilterAdapter::disposing(const EventObject& /*e*/)
+{
+}
+
+
+namespace
+{
+ OUString lcl_getLabelName_nothrow( const Reference< XControl >& _rxControl )
+ {
+ OUString sLabelName;
+ try
+ {
+ Reference< XPropertySet > xModel( _rxControl->getModel(), UNO_QUERY_THROW );
+ sLabelName = getLabelName( xModel );
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+ return sLabelName;
+ }
+
+ Reference< XPropertySet > lcl_getBoundField_nothrow( const Reference< XControl >& _rxControl )
+ {
+ Reference< XPropertySet > xField;
+ try
+ {
+ Reference< XPropertySet > xModelProps( _rxControl->getModel(), UNO_QUERY_THROW );
+ xField.set( xModelProps->getPropertyValue( FM_PROP_BOUNDFIELD ), UNO_QUERY_THROW );
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+ return xField;
+ }
+}
+
+// XFilterControllerListener
+
+void FmFilterAdapter::predicateExpressionChanged( const FilterEvent& Event )
+{
+ SolarMutexGuard aGuard;
+
+ if ( !m_pModel )
+ return;
+
+ // the controller which sent the event
+ Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW );
+ Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW );
+ Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW );
+
+ FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm );
+ OSL_ENSURE( pFormItem, "FmFilterAdapter::predicateExpressionChanged: don't know this form!" );
+ if ( !pFormItem )
+ return;
+
+ const sal_Int32 nActiveTerm( xFilterController->getActiveTerm() );
+
+ FmFilterData* pData = pFormItem->GetChildren()[nActiveTerm].get();
+ FmFilterItems& rFilter = dynamic_cast<FmFilterItems&>(*pData);
+ FmFilterItem* pFilterItem = rFilter.Find( Event.FilterComponent );
+ if ( pFilterItem )
+ {
+ if ( !Event.PredicateExpression.isEmpty())
+ {
+ pFilterItem->SetText( Event.PredicateExpression );
+ // notify the UI
+ FmFilterTextChangedHint aChangeHint(pFilterItem);
+ m_pModel->Broadcast( aChangeHint );
+ }
+ else
+ {
+ // no text anymore so remove the condition
+ m_pModel->Remove(pFilterItem);
+ }
+ }
+ else
+ {
+ // searching the component by field name
+ OUString aFieldName( lcl_getLabelName_nothrow( xFilterController->getFilterComponent( Event.FilterComponent ) ) );
+
+ std::unique_ptr<FmFilterItem> pNewFilterItem(new FmFilterItem(&rFilter, aFieldName, Event.PredicateExpression, Event.FilterComponent));
+ m_pModel->Insert(rFilter.GetChildren().end(), std::move(pNewFilterItem));
+ }
+
+ // ensure there's one empty term in the filter, just in case the active term was previously empty
+ m_pModel->EnsureEmptyFilterRows( *pFormItem );
+}
+
+
+void SAL_CALL FmFilterAdapter::disjunctiveTermRemoved( const FilterEvent& Event )
+{
+ SolarMutexGuard aGuard;
+
+ Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW );
+ Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW );
+ Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW );
+
+ FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm );
+ OSL_ENSURE( pFormItem, "FmFilterAdapter::disjunctiveTermRemoved: don't know this form!" );
+ if ( !pFormItem )
+ return;
+
+ auto& rTermItems = pFormItem->GetChildren();
+ const bool bValidIndex = ( Event.DisjunctiveTerm >= 0 ) && ( o3tl::make_unsigned(Event.DisjunctiveTerm) < rTermItems.size() );
+ OSL_ENSURE( bValidIndex, "FmFilterAdapter::disjunctiveTermRemoved: invalid term index!" );
+ if ( !bValidIndex )
+ return;
+
+ // if the first term was removed, then the to-be first term needs its text updated
+ if ( Event.DisjunctiveTerm == 0 )
+ {
+ rTermItems[1]->SetText( SvxResId(RID_STR_FILTER_FILTER_FOR));
+ FmFilterTextChangedHint aChangeHint( rTermItems[1].get() );
+ m_pModel->Broadcast( aChangeHint );
+ }
+
+ // finally remove the entry from the model
+ m_pModel->Remove( rTermItems.begin() + Event.DisjunctiveTerm );
+
+ // ensure there's one empty term in the filter, just in case the currently removed one was the last empty one
+ m_pModel->EnsureEmptyFilterRows( *pFormItem );
+}
+
+
+void SAL_CALL FmFilterAdapter::disjunctiveTermAdded( const FilterEvent& Event )
+{
+ SolarMutexGuard aGuard;
+
+ Reference< XFormController > xController( Event.Source, UNO_QUERY_THROW );
+ Reference< XFilterController > xFilterController( Event.Source, UNO_QUERY_THROW );
+ Reference< XForm > xForm( xController->getModel(), UNO_QUERY_THROW );
+
+ FmFormItem* pFormItem = m_pModel->Find( m_pModel->m_aChildren, xForm );
+ OSL_ENSURE( pFormItem, "FmFilterAdapter::disjunctiveTermAdded: don't know this form!" );
+ if ( !pFormItem )
+ return;
+
+ const sal_Int32 nInsertPos = Event.DisjunctiveTerm;
+ bool bValidIndex = ( nInsertPos >= 0 ) && ( o3tl::make_unsigned(nInsertPos) <= pFormItem->GetChildren().size() );
+ if ( !bValidIndex )
+ {
+ OSL_FAIL( "FmFilterAdapter::disjunctiveTermAdded: invalid index!" );
+ return;
+ }
+
+ auto insertPos = pFormItem->GetChildren().begin() + nInsertPos;
+
+ // "Filter for" for first position, "Or" for the other positions
+ std::unique_ptr<FmFilterItems> pFilterItems(new FmFilterItems(pFormItem, (nInsertPos?SvxResId(RID_STR_FILTER_FILTER_OR):SvxResId(RID_STR_FILTER_FILTER_FOR))));
+ m_pModel->Insert( insertPos, std::move(pFilterItems) );
+}
+
+
+FmFilterModel::FmFilterModel()
+ :FmParentData(nullptr, OUString())
+ ,OSQLParserClient(comphelper::getProcessComponentContext())
+ ,m_pCurrentItems(nullptr)
+{
+}
+
+
+FmFilterModel::~FmFilterModel()
+{
+ Clear();
+}
+
+
+void FmFilterModel::Clear()
+{
+ // notify
+ FilterClearingHint aClearedHint;
+ Broadcast( aClearedHint );
+
+ // lose endings
+ if (m_pAdapter.is())
+ {
+ m_pAdapter->dispose();
+ m_pAdapter.clear();
+ }
+
+ m_pCurrentItems = nullptr;
+ m_xController = nullptr;
+ m_xControllers = nullptr;
+
+ m_aChildren.clear();
+}
+
+
+void FmFilterModel::Update(const Reference< XIndexAccess > & xControllers, const Reference< XFormController > & xCurrent)
+{
+ if ( xCurrent == m_xController )
+ return;
+
+ if (!xControllers.is())
+ {
+ Clear();
+ return;
+ }
+
+ // there is only a new current controller
+ if ( m_xControllers != xControllers )
+ {
+ Clear();
+
+ m_xControllers = xControllers;
+ Update(m_xControllers, this);
+
+ DBG_ASSERT(xCurrent.is(), "FmFilterModel::Update(...) no current controller");
+
+ // Listening for TextChanges
+ m_pAdapter = new FmFilterAdapter(this, xControllers);
+
+ SetCurrentController(xCurrent);
+ EnsureEmptyFilterRows( *this );
+ }
+ else
+ SetCurrentController(xCurrent);
+}
+
+
+void FmFilterModel::Update(const Reference< XIndexAccess > & xControllers, FmParentData* pParent)
+{
+ try
+ {
+ sal_Int32 nCount = xControllers->getCount();
+ for ( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ Reference< XFormController > xController( xControllers->getByIndex(i), UNO_QUERY_THROW );
+
+ Reference< XPropertySet > xFormProperties( xController->getModel(), UNO_QUERY_THROW );
+ OUString aName;
+ OSL_VERIFY( xFormProperties->getPropertyValue( FM_PROP_NAME ) >>= aName );
+
+ // Insert a new item for the form
+ FmFormItem* pFormItem = new FmFormItem( pParent, xController, aName );
+ Insert( pParent->GetChildren().end(), std::unique_ptr<FmFilterData>(pFormItem) );
+
+ Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW );
+
+ // insert the existing filters for the form
+ OUString aTitle(SvxResId(RID_STR_FILTER_FILTER_FOR));
+
+ const Sequence< Sequence< OUString > > aExpressions = xFilterController->getPredicateExpressions();
+ for ( auto const & conjunctionTerm : aExpressions )
+ {
+ // we always display one row, even if there's no term to be displayed
+ FmFilterItems* pFilterItems = new FmFilterItems( pFormItem, aTitle );
+ Insert( pFormItem->GetChildren().end(), std::unique_ptr<FmFilterData>(pFilterItems) );
+
+ const Sequence< OUString >& rDisjunction( conjunctionTerm );
+ sal_Int32 nComponentIndex = -1;
+ for ( const OUString& rDisjunctiveTerm : rDisjunction )
+ {
+ ++nComponentIndex;
+
+ if ( rDisjunctiveTerm.isEmpty() )
+ // no condition for this particular component in this particular conjunction term
+ continue;
+
+ // determine the display name of the control
+ const Reference< XControl > xFilterControl( xFilterController->getFilterComponent( nComponentIndex ) );
+ const OUString sDisplayName( lcl_getLabelName_nothrow( xFilterControl ) );
+
+ // insert a new entry
+ std::unique_ptr<FmFilterItem> pANDCondition(new FmFilterItem( pFilterItems, sDisplayName, rDisjunctiveTerm, nComponentIndex ));
+ Insert( pFilterItems->GetChildren().end(), std::move(pANDCondition) );
+ }
+
+ // title for the next conditions
+ aTitle = SvxResId( RID_STR_FILTER_FILTER_OR );
+ }
+
+ // now add dependent controllers
+ Update( xController, pFormItem );
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+}
+
+
+FmFormItem* FmFilterModel::Find(const ::std::vector<std::unique_ptr<FmFilterData>>& rItems, const Reference< XFormController > & xController) const
+{
+ for (const auto& rItem : rItems)
+ {
+ FmFormItem* pForm = dynamic_cast<FmFormItem*>( rItem.get() );
+ if (pForm)
+ {
+ if ( xController == pForm->GetController() )
+ return pForm;
+ else
+ {
+ pForm = Find(pForm->GetChildren(), xController);
+ if (pForm)
+ return pForm;
+ }
+ }
+ }
+ return nullptr;
+}
+
+
+FmFormItem* FmFilterModel::Find(const ::std::vector<std::unique_ptr<FmFilterData>>& rItems, const Reference< XForm >& xForm) const
+{
+ for (const auto& rItem : rItems)
+ {
+ FmFormItem* pForm = dynamic_cast<FmFormItem*>( rItem.get() );
+ if (pForm)
+ {
+ if (xForm == pForm->GetController()->getModel())
+ return pForm;
+ else
+ {
+ pForm = Find(pForm->GetChildren(), xForm);
+ if (pForm)
+ return pForm;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void FmFilterModel::SetCurrentController(const Reference< XFormController > & xCurrent)
+{
+ if ( xCurrent == m_xController )
+ return;
+
+ m_xController = xCurrent;
+
+ FmFormItem* pItem = Find( m_aChildren, xCurrent );
+ if ( !pItem )
+ return;
+
+ try
+ {
+ Reference< XFilterController > xFilterController( m_xController, UNO_QUERY_THROW );
+ const sal_Int32 nActiveTerm( xFilterController->getActiveTerm() );
+ if (nActiveTerm != -1 && pItem->GetChildren().size() > o3tl::make_unsigned(nActiveTerm))
+ {
+ SetCurrentItems( static_cast< FmFilterItems* >( pItem->GetChildren()[ nActiveTerm ].get() ) );
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+}
+
+void FmFilterModel::AppendFilterItems( FmFormItem& _rFormItem )
+{
+ // insert the condition behind the last filter items
+ auto iter = std::find_if(_rFormItem.GetChildren().rbegin(), _rFormItem.GetChildren().rend(),
+ [](const std::unique_ptr<FmFilterData>& rChild) { return dynamic_cast<const FmFilterItems*>(rChild.get()) != nullptr; });
+
+ sal_Int32 nInsertPos = iter.base() - _rFormItem.GetChildren().begin();
+ // delegate this to the FilterController, it will notify us, which will let us update our model
+ try
+ {
+ Reference< XFilterController > xFilterController( _rFormItem.GetFilterController(), UNO_SET_THROW );
+ if ( nInsertPos >= xFilterController->getDisjunctiveTerms() )
+ xFilterController->appendEmptyDisjunctiveTerm();
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+}
+
+void FmFilterModel::Insert(const ::std::vector<std::unique_ptr<FmFilterData>>::iterator& rPos, std::unique_ptr<FmFilterData> pData)
+{
+ auto pTemp = pData.get();
+ size_t nPos;
+ ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pData->GetParent()->GetChildren();
+ if (rPos == rItems.end())
+ {
+ nPos = rItems.size();
+ rItems.push_back(std::move(pData));
+ }
+ else
+ {
+ nPos = rPos - rItems.begin();
+ rItems.insert(rPos, std::move(pData));
+ }
+
+ // notify the UI
+ FmFilterInsertedHint aInsertedHint(pTemp, nPos);
+ Broadcast( aInsertedHint );
+}
+
+void FmFilterModel::Remove(FmFilterData* pData)
+{
+ FmParentData* pParent = pData->GetParent();
+ ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pParent->GetChildren();
+
+ // erase the item from the model
+ auto i = ::std::find_if(rItems.begin(), rItems.end(),
+ [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pData; } );
+ DBG_ASSERT(i != rItems.end(), "FmFilterModel::Remove(): unknown Item");
+ // position within the parent
+ sal_Int32 nPos = i - rItems.begin();
+ if (dynamic_cast<const FmFilterItems*>( pData) != nullptr)
+ {
+ FmFormItem* pFormItem = static_cast<FmFormItem*>(pParent);
+
+ try
+ {
+ Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW );
+
+ bool bEmptyLastTerm = ( ( nPos == 0 ) && xFilterController->getDisjunctiveTerms() == 1 );
+ if ( bEmptyLastTerm )
+ {
+ // remove all children (by setting an empty predicate expression)
+ ::std::vector< std::unique_ptr<FmFilterData> >& rChildren = static_cast<FmFilterItems*>(pData)->GetChildren();
+ while ( !rChildren.empty() )
+ {
+ auto removePos = rChildren.end() - 1;
+ if (FmFilterItem* pFilterItem = dynamic_cast<FmFilterItem*>( removePos->get() ))
+ {
+ FmFilterAdapter::setText( nPos, pFilterItem, OUString() );
+ }
+ Remove( removePos );
+ }
+ }
+ else
+ {
+ xFilterController->removeDisjunctiveTerm( nPos );
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+ }
+ else // FormItems can not be deleted
+ {
+ FmFilterItem& rFilterItem = dynamic_cast<FmFilterItem&>(*pData);
+
+ // if it's the last condition remove the parent
+ if (rItems.size() == 1)
+ Remove(rFilterItem.GetParent());
+ else
+ {
+ // find the position of the father within his father
+ ::std::vector<std::unique_ptr<FmFilterData>>& rParentParentItems = pData->GetParent()->GetParent()->GetChildren();
+ auto j = ::std::find_if(rParentParentItems.begin(), rParentParentItems.end(),
+ [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == rFilterItem.GetParent(); });
+ DBG_ASSERT(j != rParentParentItems.end(), "FmFilterModel::Remove(): unknown Item");
+ sal_Int32 nParentPos = j - rParentParentItems.begin();
+
+ // EmptyText removes the filter
+ FmFilterAdapter::setText(nParentPos, &rFilterItem, OUString());
+ Remove( i );
+ }
+ }
+}
+
+void FmFilterModel::Remove( const ::std::vector<std::unique_ptr<FmFilterData>>::iterator& rPos )
+{
+ // remove from parent's child list
+ std::unique_ptr<FmFilterData> pData = std::move(*rPos);
+ pData->GetParent()->GetChildren().erase( rPos );
+
+ // notify the view, this will remove the actual SvTreeListEntry
+ FmFilterRemovedHint aRemoveHint( pData.get() );
+ Broadcast( aRemoveHint );
+}
+
+
+bool FmFilterModel::ValidateText(FmFilterItem const * pItem, OUString& rText, OUString& rErrorMsg) const
+{
+ FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( pItem->GetParent()->GetParent() );
+ assert(pFormItem);
+ try
+ {
+ Reference< XFormController > xFormController( pFormItem->GetController() );
+ // obtain the connection of the form belonging to the controller
+ Reference< XRowSet > xRowSet( xFormController->getModel(), UNO_QUERY_THROW );
+ Reference< XConnection > xConnection( getConnection( xRowSet ) );
+
+ // obtain a number formatter for this connection
+ // TODO: shouldn't this be cached?
+ Reference< XNumberFormatsSupplier > xFormatSupplier = getNumberFormats( xConnection, true );
+ Reference< XNumberFormatter > xFormatter( NumberFormatter::create( comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
+ xFormatter->attachNumberFormatsSupplier( xFormatSupplier );
+
+ // get the field (database column) which the item is responsible for
+ Reference< XFilterController > xFilterController( xFormController, UNO_QUERY_THROW );
+ Reference< XPropertySet > xField( lcl_getBoundField_nothrow( xFilterController->getFilterComponent( pItem->GetComponentIndex() ) ), UNO_SET_THROW );
+
+ // parse the given text as filter predicate
+ OUString aErr, aTxt( rText );
+ std::unique_ptr< OSQLParseNode > pParseNode = predicateTree( aErr, aTxt, xFormatter, xField );
+ rErrorMsg = aErr;
+ rText = aTxt;
+ if ( pParseNode != nullptr )
+ {
+ OUString aPreparedText;
+ Locale aAppLocale = Application::GetSettings().GetUILanguageTag().getLocale();
+ pParseNode->parseNodeToPredicateStr(
+ aPreparedText, xConnection, xFormatter, xField, OUString(), aAppLocale, OUString("."), getParseContext() );
+ rText = aPreparedText;
+ return true;
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+
+ return false;
+}
+
+
+void FmFilterModel::Append(FmFilterItems* pItems, std::unique_ptr<FmFilterItem> pFilterItem)
+{
+ Insert(pItems->GetChildren().end(), std::move(pFilterItem));
+}
+
+
+void FmFilterModel::SetTextForItem(FmFilterItem* pItem, const OUString& rText)
+{
+ ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pItem->GetParent()->GetParent()->GetChildren();
+ auto i = ::std::find_if(rItems.begin(), rItems.end(),
+ [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pItem->GetParent(); });
+ sal_Int32 nParentPos = i - rItems.begin();
+
+ FmFilterAdapter::setText(nParentPos, pItem, rText);
+
+ if (rText.isEmpty())
+ Remove(pItem);
+ else
+ {
+ // Change the text
+ pItem->SetText(rText);
+ FmFilterTextChangedHint aChangeHint(pItem);
+ Broadcast( aChangeHint );
+ }
+}
+
+
+void FmFilterModel::SetCurrentItems(FmFilterItems* pCurrent)
+{
+ if (m_pCurrentItems == pCurrent)
+ return;
+
+ // search for the condition
+ if (pCurrent)
+ {
+ FmFormItem* pFormItem = static_cast<FmFormItem*>(pCurrent->GetParent());
+ ::std::vector<std::unique_ptr<FmFilterData>>& rItems = pFormItem->GetChildren();
+ auto i = ::std::find_if(rItems.begin(), rItems.end(),
+ [&](const std::unique_ptr<FmFilterData>& p) { return p.get() == pCurrent; });
+
+ if (i != rItems.end())
+ {
+ // determine the filter position
+ sal_Int32 nPos = i - rItems.begin();
+ try
+ {
+ Reference< XFilterController > xFilterController( pFormItem->GetFilterController(), UNO_SET_THROW );
+ xFilterController->setActiveTerm( nPos );
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("svx");
+ }
+
+ if ( m_xController != pFormItem->GetController() )
+ // calls SetCurrentItems again
+ SetCurrentController( pFormItem->GetController() );
+ else
+ m_pCurrentItems = pCurrent;
+ }
+ else
+ m_pCurrentItems = nullptr;
+ }
+ else
+ m_pCurrentItems = nullptr;
+
+
+ // notify the UI
+ FmFilterCurrentChangedHint aHint;
+ Broadcast( aHint );
+}
+
+
+void FmFilterModel::EnsureEmptyFilterRows( FmParentData& _rItem )
+{
+ // checks whether for each form there's one free level for input
+ ::std::vector< std::unique_ptr<FmFilterData> >& rChildren = _rItem.GetChildren();
+ bool bAppendLevel = dynamic_cast<const FmFormItem*>(&_rItem) != nullptr;
+
+ for ( const auto& rpChild : rChildren )
+ {
+ FmFilterItems* pItems = dynamic_cast<FmFilterItems*>( rpChild.get() );
+ if ( pItems && pItems->GetChildren().empty() )
+ {
+ bAppendLevel = false;
+ break;
+ }
+
+ FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( rpChild.get() );
+ if (pFormItem)
+ {
+ EnsureEmptyFilterRows( *pFormItem );
+ continue;
+ }
+ }
+
+ if ( bAppendLevel )
+ {
+ FmFormItem* pFormItem = dynamic_cast<FmFormItem*>( &_rItem );
+ OSL_ENSURE( pFormItem, "FmFilterModel::EnsureEmptyFilterRows: no FmFormItem, but a FmFilterItems child?" );
+ if ( pFormItem )
+ AppendFilterItems( *pFormItem );
+ }
+}
+
+namespace {
+
+class FmFilterItemsString : public SvLBoxString
+{
+public:
+ explicit FmFilterItemsString(const OUString& rStr)
+ : SvLBoxString(rStr)
+ {
+ }
+
+ virtual void Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* pView, const SvTreeListEntry& rEntry) override;
+ virtual void InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData = nullptr) override;
+};
+
+}
+
+const int nxDBmp = 12;
+
+void FmFilterItemsString::Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* /*pView*/, const SvTreeListEntry& rEntry)
+{
+ FmFilterItems* pRow = static_cast<FmFilterItems*>(rEntry.GetUserData());
+ FmFormItem* pForm = static_cast<FmFormItem*>(pRow->GetParent());
+
+ // current filter is significant painted
+ const bool bIsCurrentFilter = pForm->GetChildren()[ pForm->GetFilterController()->getActiveTerm() ].get() == pRow;
+ if (bIsCurrentFilter)
+ {
+ rRenderContext.Push(PushFlags::LINECOLOR);
+ rRenderContext.SetLineColor(rRenderContext.GetTextColor());
+
+ Size aSize(GetWidth(&rDev, &rEntry), GetHeight(&rDev, &rEntry));
+ tools::Rectangle aRect(rPos, aSize);
+ Point aFirst(rPos.X(), aRect.Bottom() - 6);
+ Point aSecond(aFirst .X() + 2, aFirst.Y() + 3);
+
+ rRenderContext.DrawLine(aFirst, aSecond);
+
+ aFirst = aSecond;
+ aFirst.AdjustX(1 );
+ aSecond.AdjustX(6 );
+ aSecond.AdjustY( -5 );
+
+ rRenderContext.DrawLine(aFirst, aSecond);
+ rRenderContext.Pop();
+ }
+
+ rRenderContext.DrawText(Point(rPos.X() + nxDBmp, rPos.Y()), GetText());
+}
+
+
+void FmFilterItemsString::InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+
+ Size aSize(pView->GetTextWidth(GetText()), pView->GetTextHeight());
+ aSize.AdjustWidth(nxDBmp );
+ pViewData->mnWidth = aSize.Width();
+ pViewData->mnHeight = aSize.Height();
+}
+
+namespace {
+
+class FmFilterString : public SvLBoxString
+{
+ OUString m_aName;
+
+public:
+ FmFilterString( const OUString& rStr, const OUString& aName)
+ : SvLBoxString(rStr)
+ , m_aName(aName)
+ {
+ m_aName += ": ";
+ }
+
+ virtual void Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* pView, const SvTreeListEntry& rEntry) override;
+ virtual void InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData = nullptr) override;
+};
+
+}
+
+const int nxD = 4;
+
+void FmFilterString::InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData)
+{
+ if( !pViewData )
+ pViewData = pView->GetViewDataItem( pEntry, this );
+
+ vcl::Font aOldFont( pView->GetFont());
+ vcl::Font aFont( aOldFont );
+ aFont.SetWeight(WEIGHT_BOLD);
+ pView->Control::SetFont( aFont );
+
+ Size aSize(pView->GetTextWidth(m_aName), pView->GetTextHeight());
+ pView->Control::SetFont( aOldFont );
+ aSize.AdjustWidth(pView->GetTextWidth(GetText()) + nxD );
+ pViewData->mnWidth = aSize.Width();
+ pViewData->mnHeight = aSize.Height();
+}
+
+void FmFilterString::Paint(const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext,
+ const SvViewDataEntry* /*pView*/, const SvTreeListEntry& /*rEntry*/)
+{
+ rRenderContext.Push(PushFlags::FONT);
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetWeight(WEIGHT_BOLD);
+ rRenderContext.SetFont(aFont);
+
+ Point aPos(rPos);
+ rRenderContext.DrawText(aPos, m_aName);
+
+ // position for the second text
+ aPos.AdjustX(rDev.GetTextWidth(m_aName) + nxD );
+ rRenderContext.Pop();
+ rDev.DrawText(aPos, GetText());
+}
+
+FmFilterNavigator::FmFilterNavigator( vcl::Window* pParent )
+ :SvTreeListBox( pParent, WB_HASBUTTONS|WB_HASLINES|WB_BORDER|WB_HASBUTTONSATROOT )
+ ,m_pEditingCurrently( nullptr )
+ ,m_aControlExchange( this )
+ ,m_aTimerCounter( 0 )
+ ,m_aDropActionType( DA_SCROLLUP )
+{
+ SetHelpId( HID_FILTER_NAVIGATOR );
+
+ SetNodeBitmaps(
+ Image(StockImage::Yes, RID_SVXBMP_COLLAPSEDNODE),
+ Image(StockImage::Yes, RID_SVXBMP_EXPANDEDNODE)
+ );
+
+ m_pModel.reset( new FmFilterModel() );
+ StartListening( *m_pModel );
+
+ EnableInplaceEditing( true );
+ SetSelectionMode(SelectionMode::Multiple);
+
+ SetDragDropMode(DragDropMode::ALL);
+
+ m_aDropActionTimer.SetInvokeHandler(LINK(this, FmFilterNavigator, OnDropActionTimer));
+}
+
+
+FmFilterNavigator::~FmFilterNavigator()
+{
+ disposeOnce();
+}
+
+void FmFilterNavigator::dispose()
+{
+ EndListening( *m_pModel );
+ m_pModel.reset();
+ SvTreeListBox::dispose();
+}
+
+
+void FmFilterNavigator::UpdateContent(const Reference< XIndexAccess > & xControllers, const Reference< XFormController > & xCurrent)
+{
+ if (xCurrent == m_pModel->GetCurrentController())
+ return;
+
+ m_pModel->Update(xControllers, xCurrent);
+
+ // expand the filters for the current controller
+ SvTreeListEntry* pEntry = FindEntry(m_pModel->GetCurrentForm());
+ if (pEntry && !IsExpanded(pEntry))
+ {
+ SelectAll(false);
+
+ if (!IsExpanded(pEntry))
+ Expand(pEntry);
+
+ pEntry = FindEntry(m_pModel->GetCurrentItems());
+ if (pEntry)
+ {
+ if (!IsExpanded(pEntry))
+ Expand(pEntry);
+ Select(pEntry);
+ }
+ }
+}
+
+
+bool FmFilterNavigator::EditingEntry( SvTreeListEntry* pEntry, Selection& rSelection )
+{
+ m_pEditingCurrently = pEntry;
+ if (!SvTreeListBox::EditingEntry( pEntry, rSelection ))
+ return false;
+
+ return pEntry && dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr;
+}
+
+
+bool FmFilterNavigator::EditedEntry( SvTreeListEntry* pEntry, const OUString& rNewText )
+{
+ DBG_ASSERT(pEntry == m_pEditingCurrently, "FmFilterNavigator::EditedEntry: suspicious entry!");
+ m_pEditingCurrently = nullptr;
+
+ if (EditingCanceled())
+ return true;
+
+ DBG_ASSERT(dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr,
+ "FmFilterNavigator::EditedEntry() wrong entry");
+
+ OUString aText(comphelper::string::strip(rNewText, ' '));
+ if (aText.isEmpty())
+ {
+ // deleting the entry asynchron
+ PostUserEvent(LINK(this, FmFilterNavigator, OnRemove), pEntry, true);
+ }
+ else
+ {
+ OUString aErrorMsg;
+
+ if (m_pModel->ValidateText(static_cast<FmFilterItem*>(pEntry->GetUserData()), aText, aErrorMsg))
+ {
+ GrabFocus();
+ // this will set the text at the FmFilterItem, as well as update any filter controls
+ // which are connected to this particular entry
+ m_pModel->SetTextForItem( static_cast< FmFilterItem* >( pEntry->GetUserData() ), aText );
+
+ SetCursor( pEntry, true );
+ SetEntryText( pEntry, aText );
+ }
+ else
+ {
+ // display the error and return sal_False
+ SQLContext aError;
+ aError.Message = SvxResId(RID_STR_SYNTAXERROR);
+ aError.Details = aErrorMsg;
+ displayException(aError, this);
+
+ return false;
+ }
+ }
+ return true;
+}
+
+
+IMPL_LINK( FmFilterNavigator, OnRemove, void*, p, void )
+{
+ SvTreeListEntry* pEntry = static_cast<SvTreeListEntry*>(p);
+ // now remove the entry
+ m_pModel->Remove(static_cast<FmFilterData*>(pEntry->GetUserData()));
+}
+
+
+IMPL_LINK_NOARG(FmFilterNavigator, OnDropActionTimer, Timer *, void)
+{
+ if (--m_aTimerCounter > 0)
+ return;
+
+ switch (m_aDropActionType)
+ {
+ case DA_SCROLLUP :
+ ScrollOutputArea(1);
+ m_aTimerCounter = DROP_ACTION_TIMER_SCROLL_TICKS;
+ break;
+ case DA_SCROLLDOWN :
+ ScrollOutputArea(-1);
+ m_aTimerCounter = DROP_ACTION_TIMER_SCROLL_TICKS;
+ break;
+ case DA_EXPANDNODE:
+ {
+ SvTreeListEntry* pToExpand = GetEntry(m_aTimerTriggered);
+ if (pToExpand && (GetChildCount(pToExpand) > 0) && !IsExpanded(pToExpand))
+ Expand(pToExpand);
+ m_aDropActionTimer.Stop();
+ }
+ break;
+ }
+}
+
+
+sal_Int8 FmFilterNavigator::AcceptDrop( const AcceptDropEvent& rEvt )
+{
+ Point aDropPos = rEvt.maPosPixel;
+
+ // possible DropActions scroll and expand
+ if (rEvt.mbLeaving)
+ {
+ if (m_aDropActionTimer.IsActive())
+ m_aDropActionTimer.Stop();
+ }
+ else
+ {
+ bool bNeedTrigger = false;
+ // first entry ?
+ if ((aDropPos.Y() >= 0) && (aDropPos.Y() < GetEntryHeight()))
+ {
+ m_aDropActionType = DA_SCROLLUP;
+ bNeedTrigger = true;
+ }
+ else
+ {
+ if ((aDropPos.Y() < GetSizePixel().Height()) && (aDropPos.Y() >= GetSizePixel().Height() - GetEntryHeight()))
+ {
+ m_aDropActionType = DA_SCROLLDOWN;
+ bNeedTrigger = true;
+ }
+ else
+ { // is it an entry with children, and not yet expanded?
+ SvTreeListEntry* pDroppedOn = GetEntry(aDropPos);
+ if (pDroppedOn && (GetChildCount(pDroppedOn) > 0) && !IsExpanded(pDroppedOn))
+ {
+ // -> expand
+ m_aDropActionType = DA_EXPANDNODE;
+ bNeedTrigger = true;
+ }
+ }
+ }
+ if (bNeedTrigger && (m_aTimerTriggered != aDropPos))
+ {
+ m_aTimerCounter = DROP_ACTION_TIMER_INITIAL_TICKS;
+ // remember DropPos because there are QueryDrops even though the mouse was not moved
+ m_aTimerTriggered = aDropPos;
+ if (!m_aDropActionTimer.IsActive())
+ {
+ m_aDropActionTimer.SetTimeout(DROP_ACTION_TIMER_TICK_BASE);
+ m_aDropActionTimer.Start();
+ }
+ }
+ else if (!bNeedTrigger)
+ m_aDropActionTimer.Stop();
+ }
+
+ if (!m_aControlExchange.isDragSource())
+ return DND_ACTION_NONE;
+
+ if (!OFilterItemExchange::hasFormat(GetDataFlavorExVector()))
+ return DND_ACTION_NONE;
+
+ // do we contain the formitem?
+ if (!FindEntry(m_aControlExchange->getFormItem()))
+ return DND_ACTION_NONE;
+
+ SvTreeListEntry* pDropTarget = GetEntry(aDropPos);
+ if (!pDropTarget)
+ return DND_ACTION_NONE;
+
+ FmFilterData* pData = static_cast<FmFilterData*>(pDropTarget->GetUserData());
+ FmFormItem* pForm = nullptr;
+ if (dynamic_cast<const FmFilterItem*>(pData) != nullptr)
+ {
+ pForm = dynamic_cast<FmFormItem*>( pData->GetParent()->GetParent() );
+ if (pForm != m_aControlExchange->getFormItem())
+ return DND_ACTION_NONE;
+ }
+ else if (dynamic_cast<const FmFilterItems*>( pData) != nullptr)
+ {
+ pForm = dynamic_cast<FmFormItem*>( pData->GetParent() );
+ if (pForm != m_aControlExchange->getFormItem())
+ return DND_ACTION_NONE;
+ }
+ else
+ return DND_ACTION_NONE;
+
+ return rEvt.mnAction;
+}
+
+namespace
+{
+ FmFilterItems* getTargetItems(SvTreeListEntry const * _pTarget)
+ {
+ FmFilterData* pData = static_cast<FmFilterData*>(_pTarget->GetUserData());
+ FmFilterItems* pTargetItems = dynamic_cast<FmFilterItems*>(pData);
+ if (!pTargetItems)
+ pTargetItems = dynamic_cast<FmFilterItems*>(pData->GetParent());
+ return pTargetItems;
+ }
+}
+
+sal_Int8 FmFilterNavigator::ExecuteDrop( const ExecuteDropEvent& rEvt )
+{
+ // you can't scroll after dropping...
+ if (m_aDropActionTimer.IsActive())
+ m_aDropActionTimer.Stop();
+
+ if (!m_aControlExchange.isDragSource())
+ return DND_ACTION_NONE;
+
+ Point aDropPos = rEvt.maPosPixel;
+ SvTreeListEntry* pDropTarget = GetEntry( aDropPos );
+ if (!pDropTarget)
+ return DND_ACTION_NONE;
+
+ // search the container where to add the items
+ FmFilterItems* pTargetItems = getTargetItems(pDropTarget);
+ SelectAll(false);
+ SvTreeListEntry* pEntry = FindEntry(pTargetItems);
+ Select(pEntry);
+ SetCurEntry(pEntry);
+
+ insertFilterItem(m_aControlExchange->getDraggedEntries(),pTargetItems,DND_ACTION_COPY == rEvt.mnAction);
+
+ return DND_ACTION_COPY;
+}
+
+
+void FmFilterNavigator::InitEntry(SvTreeListEntry* pEntry,
+ const OUString& rStr,
+ const Image& rImg1,
+ const Image& rImg2)
+{
+ SvTreeListBox::InitEntry( pEntry, rStr, rImg1, rImg2 );
+ std::unique_ptr<SvLBoxString> pString;
+
+ if (dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ pString.reset(new FmFilterString(rStr,
+ static_cast<FmFilterItem*>(pEntry->GetUserData())->GetFieldName()));
+ else if (dynamic_cast<const FmFilterItems*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ pString.reset(new FmFilterItemsString(rStr));
+
+ if (pString)
+ pEntry->ReplaceItem(std::move(pString), 1 );
+}
+
+
+bool FmFilterNavigator::Select( SvTreeListEntry* pEntry, bool bSelect )
+{
+ if (bSelect == IsSelected(pEntry)) // This happens sometimes. I think the basic class errs too much on the side of caution. ;)
+ return true;
+
+ if (SvTreeListBox::Select(pEntry, bSelect))
+ {
+ if (bSelect)
+ {
+ FmFormItem* pFormItem = nullptr;
+ if ( dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ pFormItem = static_cast<FmFormItem*>(static_cast<FmFilterItem*>(pEntry->GetUserData())->GetParent()->GetParent());
+ else if (dynamic_cast<const FmFilterItems*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ pFormItem = static_cast<FmFormItem*>(static_cast<FmFilterItem*>(pEntry->GetUserData())->GetParent()->GetParent());
+ else if (dynamic_cast<const FmFormItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ pFormItem = static_cast<FmFormItem*>(pEntry->GetUserData());
+
+ if (pFormItem)
+ {
+ // will the controller be exchanged?
+ if (dynamic_cast<const FmFilterItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ m_pModel->SetCurrentItems(static_cast<FmFilterItems*>(static_cast<FmFilterItem*>(pEntry->GetUserData())->GetParent()));
+ else if (dynamic_cast<const FmFilterItems*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ m_pModel->SetCurrentItems(static_cast<FmFilterItems*>(pEntry->GetUserData()));
+ else if (dynamic_cast<const FmFormItem*>(static_cast<FmFilterData*>(pEntry->GetUserData())) != nullptr)
+ m_pModel->SetCurrentController(static_cast<FmFormItem*>(pEntry->GetUserData())->GetController());
+ }
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+void FmFilterNavigator::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
+{
+ if (const FmFilterInsertedHint* pInsertHint = dynamic_cast<const FmFilterInsertedHint*>(&rHint))
+ {
+ Insert(pInsertHint->GetData(), pInsertHint->GetPos());
+ }
+ else if( dynamic_cast<const FilterClearingHint*>(&rHint) )
+ {
+ SvTreeListBox::Clear();
+ }
+ else if (const FmFilterRemovedHint* pRemoveHint = dynamic_cast<const FmFilterRemovedHint*>(&rHint))
+ {
+ Remove(pRemoveHint->GetData());
+ }
+ else if (const FmFilterTextChangedHint *pChangeHint = dynamic_cast<const FmFilterTextChangedHint*>(&rHint))
+ {
+ SvTreeListEntry* pEntry = FindEntry(pChangeHint->GetData());
+ if (pEntry)
+ SetEntryText( pEntry, pChangeHint->GetData()->GetText());
+ }
+ else if( dynamic_cast<const FmFilterCurrentChangedHint*>(&rHint) )
+ {
+ // invalidate the entries
+ for (SvTreeListEntry* pEntry = First(); pEntry != nullptr;
+ pEntry = Next(pEntry))
+ GetModel()->InvalidateEntry( pEntry );
+ }
+}
+
+SvTreeListEntry* FmFilterNavigator::FindEntry(const FmFilterData* pItem) const
+{
+ SvTreeListEntry* pEntry = nullptr;
+ if (pItem)
+ {
+ for (pEntry = First(); pEntry != nullptr; pEntry = Next( pEntry ))
+ {
+ FmFilterData* pEntryItem = static_cast<FmFilterData*>(pEntry->GetUserData());
+ if (pEntryItem == pItem)
+ break;
+ }
+ }
+ return pEntry;
+}
+
+
+void FmFilterNavigator::Insert(FmFilterData* pItem, sal_uLong nPos)
+{
+ const FmParentData* pParent = pItem->GetParent() ? pItem->GetParent() : m_pModel.get();
+
+ // insert the item
+ SvTreeListEntry* pParentEntry = FindEntry( pParent );
+ InsertEntry( pItem->GetText(), pItem->GetImage(), pItem->GetImage(), pParentEntry, false, nPos, pItem );
+ if ( pParentEntry )
+ Expand( pParentEntry );
+}
+
+
+void FmFilterNavigator::Remove(FmFilterData const * pItem)
+{
+ // the entry for the data
+ SvTreeListEntry* pEntry = FindEntry(pItem);
+
+ if (pEntry == m_pEditingCurrently)
+ // cancel editing
+ EndEditing(true);
+
+ if (pEntry)
+ GetModel()->Remove( pEntry );
+}
+
+FmFormItem* FmFilterNavigator::getSelectedFilterItems(::std::vector<FmFilterItem*>& _rItemList)
+{
+ // be sure that the data is only used within only one form!
+ FmFormItem* pFirstItem = nullptr;
+
+ bool bHandled = true;
+ bool bFoundSomething = false;
+ for (SvTreeListEntry* pEntry = FirstSelected();
+ bHandled && pEntry != nullptr;
+ pEntry = NextSelected(pEntry))
+ {
+ FmFilterItem* pFilter = dynamic_cast<FmFilterItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) );
+ if (pFilter)
+ {
+ FmFormItem* pForm = dynamic_cast<FmFormItem*>( pFilter->GetParent()->GetParent() );
+ if (!pForm)
+ bHandled = false;
+ else if (!pFirstItem)
+ pFirstItem = pForm;
+ else if (pFirstItem != pForm)
+ bHandled = false;
+
+ if (bHandled)
+ {
+ _rItemList.push_back(pFilter);
+ bFoundSomething = true;
+ }
+ }
+ }
+ if ( !bHandled || !bFoundSomething )
+ pFirstItem = nullptr;
+ return pFirstItem;
+}
+
+void FmFilterNavigator::insertFilterItem(const ::std::vector<FmFilterItem*>& _rFilterList,FmFilterItems* _pTargetItems,bool _bCopy)
+{
+ for (FmFilterItem* pLookupItem : _rFilterList)
+ {
+ if ( pLookupItem->GetParent() == _pTargetItems )
+ continue;
+
+ FmFilterItem* pFilterItem = _pTargetItems->Find( pLookupItem->GetComponentIndex() );
+ OUString aText = pLookupItem->GetText();
+ if ( !pFilterItem )
+ {
+ pFilterItem = new FmFilterItem( _pTargetItems, pLookupItem->GetFieldName(), aText, pLookupItem->GetComponentIndex() );
+ m_pModel->Append( _pTargetItems, std::unique_ptr<FmFilterItem>(pFilterItem) );
+ }
+
+ if ( !_bCopy )
+ m_pModel->Remove( pLookupItem );
+
+ // now set the text for the new dragged item
+ m_pModel->SetTextForItem( pFilterItem, aText );
+ }
+
+ m_pModel->EnsureEmptyFilterRows( *_pTargetItems->GetParent() );
+}
+
+
+void FmFilterNavigator::StartDrag( sal_Int8 /*_nAction*/, const Point& /*_rPosPixel*/ )
+{
+ EndSelection();
+
+ // be sure that the data is only used within an only one form!
+ m_aControlExchange.prepareDrag();
+
+ ::std::vector<FmFilterItem*> aItemList;
+ if ( FmFormItem* pFirstItem = getSelectedFilterItems(aItemList) )
+ {
+ m_aControlExchange->setDraggedEntries(aItemList);
+ m_aControlExchange->setFormItem(pFirstItem);
+ m_aControlExchange.startDrag( DND_ACTION_COPYMOVE );
+ }
+}
+
+
+void FmFilterNavigator::Command( const CommandEvent& rEvt )
+{
+ bool bHandled = false;
+ switch (rEvt.GetCommand())
+ {
+ case CommandEventId::ContextMenu:
+ {
+ // the place where it was clicked
+ Point aWhere;
+ SvTreeListEntry* pClicked = nullptr;
+ if (rEvt.IsMouseEvent())
+ {
+ aWhere = rEvt.GetMousePosPixel();
+ pClicked = GetEntry(aWhere);
+ if (pClicked == nullptr)
+ break;
+
+ if (!IsSelected(pClicked))
+ {
+ SelectAll(false);
+ Select(pClicked);
+ SetCurEntry(pClicked);
+ }
+ }
+ else
+ {
+ pClicked = GetCurEntry();
+ if (!pClicked)
+ break;
+ aWhere = GetEntryPosition( pClicked );
+ }
+
+ ::std::vector<FmFilterData*> aSelectList;
+ for (SvTreeListEntry* pEntry = FirstSelected();
+ pEntry != nullptr;
+ pEntry = NextSelected(pEntry))
+ {
+ // don't delete forms
+ FmFormItem* pForm = dynamic_cast<FmFormItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) );
+ if (!pForm)
+ aSelectList.push_back(static_cast<FmFilterData*>(pEntry->GetUserData()));
+ }
+ if (aSelectList.size() == 1)
+ {
+ // don't delete the only empty row of a form
+ FmFilterItems* pFilterItems = dynamic_cast<FmFilterItems*>( aSelectList[0] );
+ if (pFilterItems && pFilterItems->GetChildren().empty()
+ && pFilterItems->GetParent()->GetChildren().size() == 1)
+ aSelectList.clear();
+ }
+
+ VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "svx/ui/filtermenu.ui", "");
+ VclPtr<PopupMenu> aContextMenu(aBuilder.get_menu("menu"));
+
+ // every condition could be deleted except the first one if it's the only one
+ aContextMenu->EnableItem(aContextMenu->GetItemId("delete"), !aSelectList.empty());
+
+
+ bool bEdit = dynamic_cast<FmFilterItem*>( static_cast<FmFilterData*>(pClicked->GetUserData()) ) != nullptr &&
+ IsSelected(pClicked) && GetSelectionCount() == 1;
+
+ aContextMenu->EnableItem(aContextMenu->GetItemId("edit"), bEdit);
+ aContextMenu->EnableItem(aContextMenu->GetItemId("isnull"), bEdit);
+ aContextMenu->EnableItem(aContextMenu->GetItemId("isnotnull"), bEdit);
+
+ aContextMenu->RemoveDisabledEntries(true, true);
+ aContextMenu->Execute(this, aWhere);
+ OString sIdent = aContextMenu->GetCurItemIdent();
+ if (sIdent == "edit")
+ EditEntry( pClicked );
+ else if (sIdent == "isnull")
+ {
+ OUString aErrorMsg;
+ OUString aText = "IS NULL";
+ m_pModel->ValidateText(static_cast<FmFilterItem*>(pClicked->GetUserData()),
+ aText, aErrorMsg);
+ m_pModel->SetTextForItem(static_cast<FmFilterItem*>(pClicked->GetUserData()), aText);
+ }
+ else if (sIdent == "isnotnull")
+ {
+ OUString aErrorMsg;
+ OUString aText = "IS NOT NULL";
+
+ m_pModel->ValidateText(static_cast<FmFilterItem*>(pClicked->GetUserData()),
+ aText, aErrorMsg);
+ m_pModel->SetTextForItem(static_cast<FmFilterItem*>(pClicked->GetUserData()), aText);
+ }
+ else if (sIdent == "delete")
+ {
+ DeleteSelection();
+ }
+ bHandled = true;
+ }
+ break;
+ default: break;
+ }
+
+ if (!bHandled)
+ SvTreeListBox::Command( rEvt );
+}
+
+SvTreeListEntry* FmFilterNavigator::getNextEntry(SvTreeListEntry* _pStartWith)
+{
+ SvTreeListEntry* pEntry = _pStartWith ? _pStartWith : LastSelected();
+ pEntry = Next(pEntry);
+ // we need the next filter entry
+ while( pEntry && GetChildCount( pEntry ) == 0 && pEntry != Last() )
+ pEntry = Next(pEntry);
+ return pEntry;
+}
+
+SvTreeListEntry* FmFilterNavigator::getPrevEntry(SvTreeListEntry* _pStartWith)
+{
+ SvTreeListEntry* pEntry = _pStartWith ? _pStartWith : FirstSelected();
+ pEntry = Prev(pEntry);
+ // check if the previous entry is a filter, if so get the next prev
+ if ( pEntry && GetChildCount( pEntry ) != 0 )
+ {
+ pEntry = Prev(pEntry);
+ // if the entry is still no leaf return
+ if ( pEntry && GetChildCount( pEntry ) != 0 )
+ pEntry = nullptr;
+ }
+ return pEntry;
+}
+
+void FmFilterNavigator::KeyInput(const KeyEvent& rKEvt)
+{
+ const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
+
+ switch ( rKeyCode.GetCode() )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ {
+ if ( !rKeyCode.IsMod1() || !rKeyCode.IsMod2() || rKeyCode.IsShift() )
+ break;
+
+ ::std::vector<FmFilterItem*> aItemList;
+ if ( !getSelectedFilterItems( aItemList ) )
+ break;
+
+ ::std::function<SvTreeListEntry*(FmFilterNavigator *, SvTreeListEntry*)> getter = ::std::mem_fn(&FmFilterNavigator::getNextEntry);
+ if ( rKeyCode.GetCode() == KEY_UP )
+ getter = ::std::mem_fn(&FmFilterNavigator::getPrevEntry);
+
+ SvTreeListEntry* pTarget = getter( this, nullptr );
+ if ( !pTarget )
+ break;
+
+ FmFilterItems* pTargetItems = getTargetItems( pTarget );
+ if ( !pTargetItems )
+ break;
+
+ ::std::vector<FmFilterItem*>::const_iterator aEnd = aItemList.end();
+ bool bNextTargetItem = true;
+ while ( bNextTargetItem )
+ {
+ ::std::vector<FmFilterItem*>::const_iterator i = aItemList.begin();
+ for (; i != aEnd; ++i)
+ {
+ if ( (*i)->GetParent() == pTargetItems )
+ {
+ pTarget = getter(this,pTarget);
+ if ( !pTarget )
+ return;
+ pTargetItems = getTargetItems( pTarget );
+ break;
+ }
+ else
+ {
+ FmFilterItem* pFilterItem = pTargetItems->Find( (*i)->GetComponentIndex() );
+ // we found the text component so jump above
+ if ( pFilterItem )
+ {
+ pTarget = getter( this, pTarget );
+ if ( !pTarget )
+ return;
+
+ pTargetItems = getTargetItems( pTarget );
+ break;
+ }
+ }
+ }
+ bNextTargetItem = i != aEnd && pTargetItems;
+ }
+
+ if ( pTargetItems )
+ {
+ insertFilterItem( aItemList, pTargetItems, false );
+ return;
+ }
+ }
+ break;
+
+ case KEY_DELETE:
+ {
+ if ( rKeyCode.GetModifier() )
+ break;
+
+ if ( !IsSelected( First() ) || GetEntryCount() > 1 )
+ DeleteSelection();
+ return;
+ }
+ }
+
+ SvTreeListBox::KeyInput(rKEvt);
+}
+
+
+void FmFilterNavigator::DeleteSelection()
+{
+ // to avoid the deletion of an entry twice (e.g. deletion of a parent and afterward
+ // the deletion of its child, I have to shrink the selection list
+ ::std::vector<SvTreeListEntry*> aEntryList;
+ for (SvTreeListEntry* pEntry = FirstSelected();
+ pEntry != nullptr;
+ pEntry = NextSelected(pEntry))
+ {
+ FmFilterItem* pFilterItem = dynamic_cast<FmFilterItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) );
+ if (pFilterItem && IsSelected(GetParent(pEntry)))
+ continue;
+
+ FmFormItem* pForm = dynamic_cast<FmFormItem*>( static_cast<FmFilterData*>(pEntry->GetUserData()) );
+ if (!pForm)
+ aEntryList.push_back(pEntry);
+ }
+
+ // Remove the selection
+ SelectAll(false);
+
+ for (::std::vector<SvTreeListEntry*>::reverse_iterator i = aEntryList.rbegin();
+ // link problems with operator ==
+ i.base() != aEntryList.rend().base(); ++i)
+ {
+ m_pModel->Remove(static_cast<FmFilterData*>((*i)->GetUserData()));
+ }
+}
+
+FmFilterNavigatorWin::FmFilterNavigatorWin( SfxBindings* _pBindings, SfxChildWindow* _pMgr,
+ vcl::Window* _pParent )
+ :SfxDockingWindow( _pBindings, _pMgr, _pParent, WinBits(WB_STDMODELESS|WB_SIZEABLE|WB_ROLLABLE|WB_3DLOOK|WB_DOCKABLE) )
+ ,SfxControllerItem( SID_FM_FILTER_NAVIGATOR_CONTROL, *_pBindings )
+{
+ SetHelpId( HID_FILTER_NAVIGATOR_WIN );
+
+ m_pNavigator = VclPtr<FmFilterNavigator>::Create( this );
+ m_pNavigator->Show();
+ SetText( SvxResId(RID_STR_FILTER_NAVIGATOR) );
+ SfxDockingWindow::SetFloatingSize( Size(200,200) );
+}
+
+
+FmFilterNavigatorWin::~FmFilterNavigatorWin()
+{
+ disposeOnce();
+}
+
+void FmFilterNavigatorWin::dispose()
+{
+ m_pNavigator.disposeAndClear();
+ ::SfxControllerItem::dispose();
+ SfxDockingWindow::dispose();
+}
+
+
+void FmFilterNavigatorWin::UpdateContent(FmFormShell const * pFormShell)
+{
+ if (!m_pNavigator)
+ return;
+
+ if (!pFormShell)
+ m_pNavigator->UpdateContent( nullptr, nullptr );
+ else
+ {
+ Reference<XFormController> const xController(pFormShell->GetImpl()->getActiveInternalController_Lock());
+ Reference< XIndexAccess > xContainer;
+ if (xController.is())
+ {
+ Reference< XChild > xChild = xController;
+ for (Reference< XInterface > xParent(xChild->getParent());
+ xParent.is();
+ xParent = xChild.is() ? xChild->getParent() : Reference< XInterface > ())
+ {
+ xContainer.set(xParent, UNO_QUERY);
+ xChild.set(xParent, UNO_QUERY);
+ }
+ }
+ m_pNavigator->UpdateContent(xContainer, xController);
+ }
+}
+
+
+void FmFilterNavigatorWin::StateChanged( sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState )
+{
+ if( !pState || SID_FM_FILTER_NAVIGATOR_CONTROL != nSID )
+ return;
+
+ if( eState >= SfxItemState::DEFAULT )
+ {
+ FmFormShell* pShell = dynamic_cast<FmFormShell*>( static_cast<const SfxObjectItem*>(pState)->GetShell() );
+ UpdateContent( pShell );
+ }
+ else
+ UpdateContent( nullptr );
+}
+
+
+bool FmFilterNavigatorWin::Close()
+{
+ if ( m_pNavigator && m_pNavigator->IsEditingActive() )
+ m_pNavigator->EndEditing();
+
+ if ( m_pNavigator && m_pNavigator->IsEditingActive() )
+ // the EndEditing was vetoed (perhaps of an syntax error or such)
+ return false;
+
+ UpdateContent( nullptr );
+ return SfxDockingWindow::Close();
+}
+
+
+void FmFilterNavigatorWin::FillInfo( SfxChildWinInfo& rInfo ) const
+{
+ SfxDockingWindow::FillInfo( rInfo );
+ rInfo.bVisible = false;
+}
+
+
+Size FmFilterNavigatorWin::CalcDockingSize( SfxChildAlignment eAlign )
+{
+ if ( ( eAlign == SfxChildAlignment::TOP ) || ( eAlign == SfxChildAlignment::BOTTOM ) )
+ return Size();
+
+ return SfxDockingWindow::CalcDockingSize( eAlign );
+}
+
+
+SfxChildAlignment FmFilterNavigatorWin::CheckAlignment( SfxChildAlignment eActAlign, SfxChildAlignment eAlign )
+{
+ switch (eAlign)
+ {
+ case SfxChildAlignment::LEFT:
+ case SfxChildAlignment::RIGHT:
+ case SfxChildAlignment::NOALIGNMENT:
+ return eAlign;
+ default:
+ break;
+ }
+
+ return eActAlign;
+}
+
+
+void FmFilterNavigatorWin::Resize()
+{
+ SfxDockingWindow::Resize();
+
+ Size aLogOutputSize = PixelToLogic(GetOutputSizePixel(), MapMode(MapUnit::MapAppFont));
+ Size aLogExplSize = aLogOutputSize;
+ aLogExplSize.AdjustWidth( -6 );
+ aLogExplSize.AdjustHeight( -6 );
+
+ Point aExplPos = LogicToPixel(Point(3,3), MapMode(MapUnit::MapAppFont));
+ Size aExplSize = LogicToPixel(aLogExplSize, MapMode(MapUnit::MapAppFont));
+
+ m_pNavigator->SetPosSizePixel( aExplPos, aExplSize );
+}
+
+void FmFilterNavigatorWin::GetFocus()
+{
+ // oj #97405#
+ if ( m_pNavigator )
+ m_pNavigator->GrabFocus();
+}
+
+SFX_IMPL_DOCKINGWINDOW( FmFilterNavigatorWinMgr, SID_FM_FILTER_NAVIGATOR )
+
+
+FmFilterNavigatorWinMgr::FmFilterNavigatorWinMgr( vcl::Window *_pParent, sal_uInt16 _nId,
+ SfxBindings *_pBindings, SfxChildWinInfo* _pInfo )
+ :SfxChildWindow( _pParent, _nId )
+{
+ SetWindow( VclPtr<FmFilterNavigatorWin>::Create( _pBindings, this, _pParent ) );
+ static_cast<SfxDockingWindow*>(GetWindow())->Initialize( _pInfo );
+}
+
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */