summaryrefslogtreecommitdiffstats
path: root/dbaccess/source/ui/control/dbtreelistbox.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'dbaccess/source/ui/control/dbtreelistbox.cxx')
-rw-r--r--dbaccess/source/ui/control/dbtreelistbox.cxx584
1 files changed, 584 insertions, 0 deletions
diff --git a/dbaccess/source/ui/control/dbtreelistbox.cxx b/dbaccess/source/ui/control/dbtreelistbox.cxx
new file mode 100644
index 000000000..549a27c39
--- /dev/null
+++ b/dbaccess/source/ui/control/dbtreelistbox.cxx
@@ -0,0 +1,584 @@
+/* -*- 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 <dbtreelistbox.hxx>
+#include <listviewitems.hxx>
+#include <callbacks.hxx>
+
+#include <com/sun/star/ui/XContextMenuInterceptor.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/frame/XController.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer2.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <vcl/help.hxx>
+#include <dbaccess/IController.hxx>
+#include <framework/actiontriggerhelper.hxx>
+#include <toolkit/awt/vclxmenu.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <vcl/treelistentry.hxx>
+#include <vcl/event.hxx>
+
+#include <memory>
+
+namespace dbaui
+{
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::ui;
+using namespace ::com::sun::star::view;
+
+#define SPACEBETWEENENTRIES 4
+DBTreeListBox::DBTreeListBox( vcl::Window* pParent, WinBits nWinStyle )
+ :SvTreeListBox(pParent,nWinStyle)
+ ,m_pDragedEntry(nullptr)
+ ,m_pActionListener(nullptr)
+ ,m_pContextMenuProvider(nullptr)
+ ,m_pResetEvent(nullptr)
+{
+ init();
+}
+
+void DBTreeListBox::init()
+{
+ SetSpaceBetweenEntries(SPACEBETWEENENTRIES);
+
+ m_aTimer.SetTimeout(900);
+ m_aTimer.SetInvokeHandler(LINK(this, DBTreeListBox, OnTimeOut));
+
+ m_aScrollHelper.setUpScrollMethod( LINK(this, DBTreeListBox, ScrollUpHdl) );
+ m_aScrollHelper.setDownScrollMethod( LINK(this, DBTreeListBox, ScrollDownHdl) );
+
+ SetNodeDefaultImages( );
+
+ EnableContextMenuHandling();
+
+ SetQuickSearch( true );
+}
+
+DBTreeListBox::~DBTreeListBox()
+{
+ assert(!m_xMenuController.is());
+ disposeOnce();
+}
+
+void DBTreeListBox::dispose()
+{
+ if (m_pResetEvent)
+ {
+ RemoveUserEvent(m_pResetEvent);
+ m_pResetEvent = nullptr;
+ }
+ implStopSelectionTimer();
+ SvTreeListBox::dispose();
+}
+
+SvTreeListEntry* DBTreeListBox::GetEntryPosByName( const OUString& aName, SvTreeListEntry* pStart, const IEntryFilter* _pFilter ) const
+{
+ SvTreeList* myModel = GetModel();
+ std::pair<SvTreeListEntries::const_iterator,SvTreeListEntries::const_iterator> aIters =
+ myModel->GetChildIterators(pStart);
+
+ SvTreeListEntry* pEntry = nullptr;
+ SvTreeListEntries::const_iterator it = aIters.first, itEnd = aIters.second;
+ for (; it != itEnd; ++it)
+ {
+ pEntry = (*it).get();
+ const SvLBoxString* pItem = static_cast<const SvLBoxString*>(
+ pEntry->GetFirstItem(SvLBoxItemType::String));
+
+ if (pItem && pItem->GetText() == aName)
+ {
+ if (!_pFilter || _pFilter->includeEntry(pEntry->GetUserData()))
+ // found
+ break;
+ }
+ pEntry = nullptr;
+ }
+
+ return pEntry;
+}
+
+void DBTreeListBox::RequestingChildren( SvTreeListEntry* pParent )
+{
+ if (m_aPreExpandHandler.IsSet() && !m_aPreExpandHandler.Call(pParent))
+ {
+ // an error occurred. The method calling us will reset the entry flags, so it can't be expanded again.
+ // But we want that the user may do a second try (i.e. because he mistypes a password in this try), so
+ // we have to reset these flags controlling the expand ability
+ m_pResetEvent = PostUserEvent(LINK(this, DBTreeListBox, OnResetEntryHdl), pParent, true);
+ }
+}
+
+void DBTreeListBox::InitEntry(SvTreeListEntry* _pEntry, const OUString& aStr, const Image& _rCollEntryBmp, const Image& _rExpEntryBmp)
+{
+ SvTreeListBox::InitEntry( _pEntry, aStr, _rCollEntryBmp,_rExpEntryBmp);
+ SvLBoxItem* pTextItem(_pEntry->GetFirstItem(SvLBoxItemType::String));
+ _pEntry->ReplaceItem(std::make_unique<OBoldListboxString>(aStr), _pEntry->GetPos(pTextItem));
+}
+
+void DBTreeListBox::implStopSelectionTimer()
+{
+ if ( m_aTimer.IsActive() )
+ m_aTimer.Stop();
+}
+
+void DBTreeListBox::implStartSelectionTimer()
+{
+ implStopSelectionTimer();
+ m_aTimer.Start();
+}
+
+void DBTreeListBox::DeselectHdl()
+{
+ m_aSelectedEntries.erase( GetHdlEntry() );
+ SvTreeListBox::DeselectHdl();
+ implStartSelectionTimer();
+}
+
+void DBTreeListBox::SelectHdl()
+{
+ m_aSelectedEntries.insert( GetHdlEntry() );
+ SvTreeListBox::SelectHdl();
+ implStartSelectionTimer();
+}
+
+void DBTreeListBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ bool bHitEmptySpace = (nullptr == GetEntry(rMEvt.GetPosPixel(), true));
+ if (bHitEmptySpace && (rMEvt.GetClicks() == 2) && rMEvt.IsMod1())
+ Control::MouseButtonDown(rMEvt);
+ else
+ SvTreeListBox::MouseButtonDown(rMEvt);
+}
+
+void DBTreeListBox::EnableExpandHandler(SvTreeListEntry* pEntry)
+{
+ // set the flag which allows if the entry can be expanded
+ pEntry->SetFlags( (pEntry->GetFlags() & ~SvTLEntryFlags(SvTLEntryFlags::NO_NODEBMP | SvTLEntryFlags::HAD_CHILDREN)) | SvTLEntryFlags::CHILDREN_ON_DEMAND );
+ // redraw the entry
+ GetModel()->InvalidateEntry(pEntry);
+}
+
+IMPL_LINK(DBTreeListBox, OnResetEntryHdl, void*, p, void)
+{
+ m_pResetEvent = nullptr;
+ EnableExpandHandler(static_cast<SvTreeListEntry*>(p));
+}
+
+void DBTreeListBox::ModelHasEntryInvalidated( SvTreeListEntry* _pEntry )
+{
+ SvTreeListBox::ModelHasEntryInvalidated( _pEntry );
+
+ if (m_aSelectedEntries.find(_pEntry) != m_aSelectedEntries.end())
+ {
+ SvLBoxItem* pTextItem = _pEntry->GetFirstItem(SvLBoxItemType::String);
+ if ( pTextItem && !static_cast< OBoldListboxString* >( pTextItem )->isEmphasized() )
+ {
+ implStopSelectionTimer();
+ m_aSelectedEntries.erase(_pEntry);
+ // ehm - why?
+ }
+ }
+}
+
+void DBTreeListBox::ModelHasRemoved( SvTreeListEntry* _pEntry )
+{
+ SvTreeListBox::ModelHasRemoved(_pEntry);
+ if (m_aSelectedEntries.find(_pEntry) != m_aSelectedEntries.end())
+ {
+ implStopSelectionTimer();
+ m_aSelectedEntries.erase(_pEntry);
+ }
+}
+
+sal_Int8 DBTreeListBox::AcceptDrop( const AcceptDropEvent& _rEvt )
+{
+ sal_Int8 nDropOption = DND_ACTION_NONE;
+ if ( m_pActionListener )
+ {
+ SvTreeListEntry* pDroppedEntry = GetEntry(_rEvt.maPosPixel);
+ // check if drag is on child entry, which is not allowed
+ SvTreeListEntry* pParent = nullptr;
+ if ( _rEvt.mnAction & DND_ACTION_MOVE )
+ {
+ if ( !m_pDragedEntry ) // no entry to move
+ {
+ nDropOption = m_pActionListener->queryDrop( _rEvt, GetDataFlavorExVector() );
+ m_aMousePos = _rEvt.maPosPixel;
+ m_aScrollHelper.scroll(m_aMousePos,GetOutputSizePixel());
+ return nDropOption;
+ }
+
+ pParent = pDroppedEntry ? GetParent(pDroppedEntry) : nullptr;
+ while ( pParent && pParent != m_pDragedEntry )
+ pParent = GetParent(pParent);
+ }
+
+ if ( !pParent )
+ {
+ nDropOption = m_pActionListener->queryDrop( _rEvt, GetDataFlavorExVector() );
+ // check if move is allowed
+ if ( nDropOption & DND_ACTION_MOVE )
+ {
+ if ( m_pDragedEntry == pDroppedEntry || GetEntryPosByName(GetEntryText(m_pDragedEntry),pDroppedEntry) )
+ nDropOption = nDropOption & ~DND_ACTION_MOVE;//DND_ACTION_NONE;
+ }
+ m_aMousePos = _rEvt.maPosPixel;
+ m_aScrollHelper.scroll(m_aMousePos,GetOutputSizePixel());
+ }
+ }
+
+ return nDropOption;
+}
+
+sal_Int8 DBTreeListBox::ExecuteDrop( const ExecuteDropEvent& _rEvt )
+{
+ if ( m_pActionListener )
+ return m_pActionListener->executeDrop( _rEvt );
+
+ return DND_ACTION_NONE;
+}
+
+void DBTreeListBox::StartDrag( sal_Int8 /*_nAction*/, const Point& _rPosPixel )
+{
+ if ( m_pActionListener )
+ {
+ m_pDragedEntry = GetEntry(_rPosPixel);
+ if ( m_pDragedEntry && m_pActionListener->requestDrag( _rPosPixel ) )
+ {
+ // if the (asynchronous) drag started, stop the selection timer
+ implStopSelectionTimer();
+ // and stop selecting entries by simply moving the mouse
+ EndSelection();
+ }
+ }
+}
+
+void DBTreeListBox::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( !m_pActionListener )
+ {
+ SvTreeListBox::RequestHelp( rHEvt );
+ return;
+ }
+
+ if( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ Point aPos( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ));
+ SvTreeListEntry* pEntry = GetEntry( aPos );
+ if( pEntry )
+ {
+ OUString sQuickHelpText;
+ if ( m_pActionListener->requestQuickHelp( pEntry, sQuickHelpText ) )
+ {
+ Size aSize( GetOutputSizePixel().Width(), GetEntryHeight() );
+ tools::Rectangle aScreenRect( OutputToScreenPixel( GetEntryPosition( pEntry ) ), aSize );
+
+ Help::ShowQuickHelp( this, aScreenRect,
+ sQuickHelpText, QuickHelpFlags::Left | QuickHelpFlags::VCenter );
+ return;
+ }
+ }
+ }
+
+ SvTreeListBox::RequestHelp( rHEvt );
+}
+
+void DBTreeListBox::KeyInput( const KeyEvent& rKEvt )
+{
+ KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ bool bHandled = false;
+
+ if(eFunc != KeyFuncType::DONTKNOW)
+ {
+ switch(eFunc)
+ {
+ case KeyFuncType::COPY:
+ bHandled = ( m_aCopyHandler.IsSet() && !m_aSelectedEntries.empty() );
+ if ( bHandled )
+ m_aCopyHandler.Call( nullptr );
+ break;
+ case KeyFuncType::PASTE:
+ bHandled = ( m_aPasteHandler.IsSet() && !m_aSelectedEntries.empty() );
+ if ( bHandled )
+ m_aPasteHandler.Call( nullptr );
+ break;
+ case KeyFuncType::DELETE:
+ bHandled = ( m_aDeleteHandler.IsSet() && !m_aSelectedEntries.empty() );
+ if ( bHandled )
+ m_aDeleteHandler.Call( nullptr );
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ( KEY_RETURN == nCode )
+ {
+ bHandled = false;
+ m_aEnterKeyHdl.Call(this);
+ // this is a HACK. If the data source browser is opened in the "beamer", while the main frame
+ //
+ // contains a writer document, then pressing enter in the DSB would be rerouted to the writer
+ //
+ // document if we would not do this hack here.
+ // The problem is that the Writer uses RETURN as _accelerator_ (which is quite weird itself),
+ //
+ // so the SFX framework is _obligated_ to pass it to the Writer if nobody else handled it. There
+ //
+ // is no chance to distinguish between
+ // "accelerators which are to be executed if the main document has the focus"
+ // and
+ // "accelerators which are always to be executed"
+ //
+ // Thus we cannot prevent the handling of this key in the writer without declaring the key event
+ // as "handled" herein.
+ //
+ // The bad thing about this approach is that it does not scale. Every other accelerator which
+ // is used by the document will raise a similar bug once somebody discovers it.
+ // If this is the case, we should discuss a real solution with the framework (SFX) and the
+ // applications.
+ }
+
+ if ( !bHandled )
+ SvTreeListBox::KeyInput(rKEvt);
+}
+
+bool DBTreeListBox::EditingEntry( SvTreeListEntry* /*pEntry*/, Selection& /*_aSelection*/)
+{
+ return false;
+}
+
+bool DBTreeListBox::EditedEntry( SvTreeListEntry* pEntry, const OUString& rNewText )
+{
+ DBTreeEditedEntry aEntry;
+ aEntry.aNewText = rNewText;
+ SetEntryText(pEntry,aEntry.aNewText);
+
+ return false; // we never want that the base change our text
+}
+
+bool DBTreeListBox::DoubleClickHdl()
+{
+ // continue default processing if the DoubleClickHandler didn't handle it
+ return !aDoubleClickHdl.Call( this );
+}
+
+static void scrollWindow(DBTreeListBox* _pListBox, const Point& _rPos,bool _bUp)
+{
+ SvTreeListEntry* pEntry = _pListBox->GetEntry( _rPos );
+ if( pEntry && pEntry != _pListBox->Last() )
+ {
+ _pListBox->ScrollOutputArea( _bUp ? -1 : 1 );
+ }
+}
+
+IMPL_LINK_NOARG( DBTreeListBox, ScrollUpHdl, LinkParamNone*, void )
+{
+ scrollWindow(this,m_aMousePos,true);
+}
+
+IMPL_LINK_NOARG( DBTreeListBox, ScrollDownHdl, LinkParamNone*, void )
+{
+ scrollWindow(this,m_aMousePos,false);
+}
+
+namespace
+{
+ // SelectionSupplier
+ typedef ::cppu::WeakImplHelper< XSelectionSupplier
+ > SelectionSupplier_Base;
+ class SelectionSupplier : public SelectionSupplier_Base
+ {
+ public:
+ explicit SelectionSupplier( const Any& _rSelection )
+ :m_aSelection( _rSelection )
+ {
+ }
+
+ virtual sal_Bool SAL_CALL select( const Any& xSelection ) override;
+ virtual Any SAL_CALL getSelection( ) override;
+ virtual void SAL_CALL addSelectionChangeListener( const Reference< XSelectionChangeListener >& xListener ) override;
+ virtual void SAL_CALL removeSelectionChangeListener( const Reference< XSelectionChangeListener >& xListener ) override;
+
+ protected:
+ virtual ~SelectionSupplier() override
+ {
+ }
+
+ private:
+ Any m_aSelection;
+ };
+
+ sal_Bool SAL_CALL SelectionSupplier::select( const Any& /*_Selection*/ )
+ {
+ throw IllegalArgumentException();
+ // API bug: this should be a NoSupportException
+ }
+
+ Any SAL_CALL SelectionSupplier::getSelection( )
+ {
+ return m_aSelection;
+ }
+
+ void SAL_CALL SelectionSupplier::addSelectionChangeListener( const Reference< XSelectionChangeListener >& /*_Listener*/ )
+ {
+ OSL_FAIL( "SelectionSupplier::removeSelectionChangeListener: no support!" );
+ // API bug: this should be a NoSupportException
+ }
+
+ void SAL_CALL SelectionSupplier::removeSelectionChangeListener( const Reference< XSelectionChangeListener >& /*_Listener*/ )
+ {
+ OSL_FAIL( "SelectionSupplier::removeSelectionChangeListener: no support!" );
+ // API bug: this should be a NoSupportException
+ }
+}
+
+VclPtr<PopupMenu> DBTreeListBox::CreateContextMenu()
+{
+ if ( !m_pContextMenuProvider )
+ return nullptr;
+
+ OUString aResourceName( m_pContextMenuProvider->getContextMenuResourceName( *this ) );
+ if ( aResourceName.isEmpty() )
+ return nullptr;
+
+ css::uno::Sequence< css::uno::Any > aArgs( 3 );
+ aArgs[0] <<= comphelper::makePropertyValue( "Value", aResourceName );
+ aArgs[1] <<= comphelper::makePropertyValue( "Frame", m_pContextMenuProvider->getCommandController().getXController()->getFrame() );
+ aArgs[2] <<= comphelper::makePropertyValue( "IsContextMenu", true );
+
+ css::uno::Reference< css::uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
+ m_xMenuController.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.comp.framework.ResourceMenuController", aArgs, xContext ), css::uno::UNO_QUERY );
+
+ if ( !m_xMenuController.is() )
+ return nullptr;
+
+ rtl::Reference xPopupMenu( new VCLXPopupMenu );
+ m_xMenuController->setPopupMenu( xPopupMenu.get() );
+ VclPtr<PopupMenu> pContextMenu( static_cast< PopupMenu* >( xPopupMenu->GetMenu() ) );
+ pContextMenu->AddEventListener( LINK( this, DBTreeListBox, MenuEventListener ) );
+
+ // allow context menu interception
+ ::comphelper::OInterfaceContainerHelper2* pInterceptors = m_pContextMenuProvider->getContextMenuInterceptors();
+ if ( !pInterceptors || !pInterceptors->getLength() )
+ return pContextMenu;
+
+ OUString aMenuIdentifier( "private:resource/popupmenu/" + aResourceName );
+
+ ContextMenuExecuteEvent aEvent;
+ aEvent.SourceWindow = VCLUnoHelper::GetInterface( this );
+ aEvent.ExecutePosition.X = -1;
+ aEvent.ExecutePosition.Y = -1;
+ aEvent.ActionTriggerContainer = ::framework::ActionTriggerHelper::CreateActionTriggerContainerFromMenu(
+ pContextMenu.get(), &aMenuIdentifier );
+ aEvent.Selection = new SelectionSupplier( m_pContextMenuProvider->getCurrentSelection( *this ) );
+
+ ::comphelper::OInterfaceIteratorHelper2 aIter( *pInterceptors );
+ bool bModifiedMenu = false;
+ bool bAskInterceptors = true;
+ while ( aIter.hasMoreElements() && bAskInterceptors )
+ {
+ Reference< XContextMenuInterceptor > xInterceptor( aIter.next(), UNO_QUERY );
+ if ( !xInterceptor.is() )
+ continue;
+
+ try
+ {
+ ContextMenuInterceptorAction eAction = xInterceptor->notifyContextMenuExecute( aEvent );
+ switch ( eAction )
+ {
+ case ContextMenuInterceptorAction_CANCELLED:
+ return nullptr;
+
+ case ContextMenuInterceptorAction_EXECUTE_MODIFIED:
+ bModifiedMenu = true;
+ bAskInterceptors = false;
+ break;
+
+ case ContextMenuInterceptorAction_CONTINUE_MODIFIED:
+ bModifiedMenu = true;
+ bAskInterceptors = true;
+ break;
+
+ default:
+ OSL_FAIL( "DBTreeListBox::CreateContextMenu: unexpected return value of the interceptor call!" );
+ [[fallthrough]];
+ case ContextMenuInterceptorAction_IGNORED:
+ break;
+ }
+ }
+ catch( const DisposedException& e )
+ {
+ if ( e.Context == xInterceptor )
+ aIter.remove();
+ }
+ }
+
+ if ( bModifiedMenu )
+ {
+ pContextMenu->Clear();
+ ::framework::ActionTriggerHelper::CreateMenuFromActionTriggerContainer(
+ pContextMenu, aEvent.ActionTriggerContainer );
+ aEvent.ActionTriggerContainer.clear();
+ }
+
+ return pContextMenu;
+}
+
+void DBTreeListBox::ExecuteContextMenuAction( sal_uInt16 )
+{
+}
+
+IMPL_LINK( DBTreeListBox, MenuEventListener, VclMenuEvent&, rMenuEvent, void )
+{
+ if ( rMenuEvent.GetId() == VclEventId::ObjectDying )
+ {
+ css::uno::Reference< css::lang::XComponent > xComponent( m_xMenuController, css::uno::UNO_QUERY );
+ if ( xComponent.is() )
+ xComponent->dispose();
+ m_xMenuController.clear();
+ }
+}
+
+IMPL_LINK_NOARG(DBTreeListBox, OnTimeOut, Timer*, void)
+{
+ implStopSelectionTimer();
+
+ m_aSelChangeHdl.Call( nullptr );
+}
+
+void DBTreeListBox::StateChanged( StateChangedType nStateChange )
+{
+ if ( nStateChange == StateChangedType::Visible )
+ implStopSelectionTimer();
+}
+
+} // namespace dbaui
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */