/* -*- 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 .
*/
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;
class FmFilterRemovedHint : public FmFilterHint
{ public: explicit FmFilterRemovedHint(FmFilterData* pData)
:FmFilterHint(SfxHintId::FmFilterRemoved, pData){}
};
class FmFilterTextChangedHint : public FmFilterHint
{ public: explicit FmFilterTextChangedHint(FmFilterData* pData)
:FmFilterHint(SfxHintId::FmFilterTextChanged, pData){}
};
class FilterClearingHint : public SfxHint
{ public:
FilterClearingHint() : SfxHint(SfxHintId::FilterClearing) {}
};
class FmFilterCurrentChangedHint : public SfxHint
{ public:
FmFilterCurrentChangedHint() : SfxHint(SfxHintId::FmFilterCurrentChanged) {}
};
}
// class FmFilterAdapter, listener at the FilterControls class FmFilterAdapter : public ::cppu::WeakImplHelper< XFilterControllerListener >
{
FmFilterModel* m_pModel;
Reference< XIndexAccess > m_xControllers;
// 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 );
}
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) );
}
// Insert a new item for the form
FmFormItem* pFormItem = new FmFormItem( pParent, xController, aName );
Insert( pParent->GetChildren().end(), std::unique_ptr<FmFilterData>(pFormItem) );
// insert the existing filters for the form
OUString aTitle(SvxResId(RID_STR_FILTER_FILTER_FOR));
const Sequence< Sequence< OUString > > aExpressions = xFilterController->getPredicateExpressions(); for ( autoconst & 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) );
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 );
}
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) { returndynamic_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");
}
}
// 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 (auto pFilterItems = dynamic_cast<FmFilterItems*>( pData))
{
FmFormItem* pFormItem = static_cast<FmFormItem*>(pParent);
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 = pFilterItems->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 );
if (nAccept != DND_ACTION_NONE)
{ // to enable the autoscroll when we're close to the edges
weld::TreeView& rWidget = m_rTreeView.get_widget();
rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true);
}
// expand the filters for the current controller
std::unique_ptr<weld::TreeIter> xEntry = FindEntry(m_pModel->GetCurrentForm()); if (!xEntry || m_xTreeView->get_row_expanded(*xEntry)) return;
m_xTreeView->unselect_all();
m_xTreeView->expand_row(*xEntry);
xEntry = FindEntry(m_pModel->GetCurrentItems()); if (xEntry)
{ if (!m_xTreeView->get_row_expanded(*xEntry))
m_xTreeView->expand_row(*xEntry);
m_xTreeView->select(*xEntry);
SelectHdl(*m_xTreeView);
}
}
if (m_pModel->ValidateText(static_cast<FmFilterItem*>(pData), aText, aErrorMsg))
{ // 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*>(pData), aText);
m_xTreeView->set_text(rIter, aText);
} else
{ // display the error and return sal_False
SQLContext aError(SvxResId(RID_STR_SYNTAXERROR), {}, {}, 0, {}, aErrorMsg);
displayException(aError, VCLUnoHelper::GetInterface(m_xTopLevel));
returnfalse;
}
} returntrue;
}
IMPL_LINK( FmFilterNavigator, OnRemove, void*, p, void )
{
m_nAsyncRemoveEvent = nullptr; // now remove the entry
m_pModel->Remove(static_cast<FmFilterData*>(p));
}
if (!OFilterItemExchange::hasFormat(m_aDropTargetHelper.GetDataFlavorExVector())) return DND_ACTION_NONE;
// do we contain the formitem? if (!FindEntry(m_aControlExchange->getFormItem())) return DND_ACTION_NONE;
Point aDropPos = rEvt.maPosPixel;
std::unique_ptr<weld::TreeIter> xDropTarget(m_xTreeView->make_iterator()); // get_dest_row_at_pos with false cause we must drop exactly "on" a node to paste a condition into it if (!m_xTreeView->get_dest_row_at_pos(aDropPos, xDropTarget.get(), false))
xDropTarget.reset();
Point aDropPos = rEvt.maPosPixel;
std::unique_ptr<weld::TreeIter> xDropTarget(m_xTreeView->make_iterator()); // get_dest_row_at_pos with false cause we must drop exactly "on" a node to paste a condition into it if (!m_xTreeView->get_dest_row_at_pos(aDropPos, xDropTarget.get(), false))
xDropTarget.reset(); if (!xDropTarget) return DND_ACTION_NONE;
// search the container where to add the items
FmFilterItems* pTargetItems = getTargetItems(*m_xTreeView, *xDropTarget);
m_xTreeView->unselect_all();
std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pTargetItems); if (xEntry)
{
m_xTreeView->select(*xEntry);
m_xTreeView->set_cursor(*xEntry);
}
if (pFormItem)
{ // will the controller be exchanged? if (FmFilterItem* pItem = dynamic_cast<FmFilterItem*>(pData))
m_pModel->SetCurrentItems(static_cast<FmFilterItems*>(pItem->GetParent())); elseif (FmFilterItems* pItems = dynamic_cast<FmFilterItems*>(pData))
m_pModel->SetCurrentItems(pItems); else
m_pModel->SetCurrentController(pFormItem->GetController());
}
}
if (!xParentEntry) return;
m_xTreeView->expand_row(*xParentEntry);
}
void FmFilterNavigator::EndEditing()
{ if (m_xEditingCurrently)
{ // end editing
m_xTreeView->end_editing();
m_xEditingCurrently.reset();
}
}
void FmFilterNavigator::Remove(FmFilterData const * pItem)
{ // the entry for the data
std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pItem); if (!xEntry) return;
if (m_xEditingCurrently && m_xTreeView->iter_compare(*xEntry, *m_xEditingCurrently) == 0)
EndEditing();
m_xTreeView->remove(*xEntry);
}
FmFormItem* FmFilterNavigator::getSelectedFilterItems(::std::vector<FmFilterItem*>& _rItemList)
{ // be sure that the data is only used within only one form!
FmFormItem* pFirstItem = nullptr;
IMPL_LINK(FmFilterNavigator, PopupMenuHdl, const CommandEvent&, rEvt, bool)
{ bool bHandled = false; switch (rEvt.GetCommand())
{ case CommandEventId::ContextMenu:
{ // the place where it was clicked
Point aWhere;
std::unique_ptr<weld::TreeIter> xClicked(m_xTreeView->make_iterator()); if (rEvt.IsMouseEvent())
{
aWhere = rEvt.GetMousePosPixel(); if (!m_xTreeView->get_dest_row_at_pos(aWhere, xClicked.get(), false)) break;
if (!m_xTreeView->is_selected(*xClicked))
{
m_xTreeView->unselect_all();
m_xTreeView->select(*xClicked);
m_xTreeView->set_cursor(*xClicked);
}
} else
{ if (!m_xTreeView->get_cursor(xClicked.get())) break;
aWhere = m_xTreeView->get_row_area(*xClicked).Center();
}
// don't delete forms
FmFormItem* pForm = dynamic_cast<FmFormItem*>(pFilterEntry); if (!pForm)
aSelectList.push_back(pFilterEntry);
returnfalse;
});
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();
}
// every condition could be deleted except the first one if it's the only one bool bNoDelete = false; if (aSelectList.empty())
{
bNoDelete = true;
xContextMenu->remove(u"delete"_ustr);
}
if (bNoDelete && !bEdit)
{ // nothing is in the menu, don't bother returntrue;
}
if (!bEdit)
{
xContextMenu->remove(u"edit"_ustr);
xContextMenu->remove(u"isnull"_ustr);
xContextMenu->remove(u"isnotnull"_ustr);
}
OUString sIdent = xContextMenu->popup_at_rect(m_xTreeView.get(), tools::Rectangle(aWhere, ::Size(1, 1))); if (sIdent == "edit")
{
m_xTreeView->start_editing(*xClicked);
} elseif (sIdent == "isnull")
{
OUString aErrorMsg;
OUString aText = u"IS NULL"_ustr;
assert(pFilterItem && "if item is null this menu entry was removed and unavailable");
m_pModel->ValidateText(pFilterItem, aText, aErrorMsg);
m_pModel->SetTextForItem(pFilterItem, aText);
} elseif (sIdent == "isnotnull")
{
OUString aErrorMsg;
OUString aText = u"IS NOT NULL"_ustr;
assert(pFilterItem && "if item is null this menu entry was removed and unavailable");
m_pModel->ValidateText(pFilterItem, aText, aErrorMsg);
m_pModel->SetTextForItem(pFilterItem, aText);
} elseif (sIdent == "delete")
{
DeleteSelection();
}
bHandled = true;
} break; default: break;
}
return bHandled;
}
bool FmFilterNavigator::getNextEntry(weld::TreeIter& rEntry)
{ bool bEntry = m_xTreeView->iter_next(rEntry); // we need the next filter entry if (bEntry)
{ while (!m_xTreeView->iter_has_child(rEntry))
{
std::unique_ptr<weld::TreeIter> xNext = m_xTreeView->make_iterator(&rEntry); if (!m_xTreeView->iter_next(*xNext)) break;
m_xTreeView->copy_iterator(*xNext, rEntry);
}
} return bEntry;
}
bool FmFilterNavigator::getPrevEntry(weld::TreeIter& rEntry)
{ bool bEntry = m_xTreeView->iter_previous(rEntry); // check if the previous entry is a filter, if so get the next prev if (bEntry && m_xTreeView->iter_has_child(rEntry))
{
bEntry = m_xTreeView->iter_previous(rEntry); // if the entry is still no leaf return if (bEntry && m_xTreeView->iter_has_child(rEntry))
bEntry = false;
} return bEntry;
}
IMPL_LINK(FmFilterNavigator, KeyInputHdl, const ::KeyEvent&, rKEvt, bool)
{ 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;
case KEY_DELETE:
{ if ( rKeyCode.GetModifier() ) break;
std::unique_ptr<weld::TreeIter> xEntry = m_xTreeView->make_iterator(); if (m_xTreeView->get_iter_first(*xEntry) && !m_xTreeView->is_selected(*xEntry))
DeleteSelection();
returntrue;
}
}
returnfalse;
}
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<FmFilterData*> aEntryList;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.