1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <dbtreelistbox.hxx>
21 #include <dbexchange.hxx>
22 #include <callbacks.hxx>
23 
24 #include <com/sun/star/ui/XContextMenuInterceptor.hpp>
25 #include <com/sun/star/uno/XComponentContext.hpp>
26 #include <com/sun/star/frame/XController.hpp>
27 #include <com/sun/star/frame/XPopupMenuController.hpp>
28 #include <com/sun/star/lang/IllegalArgumentException.hpp>
29 #include <cppuhelper/implbase.hxx>
30 #include <comphelper/interfacecontainer2.hxx>
31 #include <comphelper/processfactory.hxx>
32 #include <comphelper/propertyvalue.hxx>
33 #include <dbaccess/IController.hxx>
34 #include <framework/actiontriggerhelper.hxx>
35 #include <toolkit/awt/vclxmenu.hxx>
36 #include <toolkit/helper/vclunohelper.hxx>
37 #include <svx/dbaobjectex.hxx>
38 #include <vcl/commandevent.hxx>
39 #include <vcl/event.hxx>
40 #include <vcl/menu.hxx>
41 #include <vcl/svapp.hxx>
42 
43 #include <memory>
44 
45 namespace dbaui
46 {
47 
48 using namespace ::com::sun::star;
49 using namespace ::com::sun::star::uno;
50 using namespace ::com::sun::star::beans;
51 using namespace ::com::sun::star::lang;
52 using namespace ::com::sun::star::datatransfer;
53 using namespace ::com::sun::star::ui;
54 using namespace ::com::sun::star::view;
55 
InterimDBTreeListBox(vcl::Window * pParent)56 InterimDBTreeListBox::InterimDBTreeListBox(vcl::Window* pParent)
57     : InterimItemWindow(pParent, "dbaccess/ui/dbtreelist.ui", "DBTreeList")
58     , TreeListBox(m_xBuilder->weld_tree_view("treeview"), true)
59     , m_xStatusBar(m_xBuilder->weld_label("statusbar"))
60 {
61     InitControlBase(&GetWidget());
62 }
63 
~InterimDBTreeListBox()64 InterimDBTreeListBox::~InterimDBTreeListBox()
65 {
66     disposeOnce();
67 }
68 
dispose()69 void InterimDBTreeListBox::dispose()
70 {
71     implStopSelectionTimer();
72     m_xStatusBar.reset();
73     m_xTreeView.reset();
74     InterimItemWindow::dispose();
75 }
76 
DoChildKeyInput(const KeyEvent & rKEvt)77 bool InterimDBTreeListBox::DoChildKeyInput(const KeyEvent& rKEvt)
78 {
79     return ChildKeyInput(rKEvt);
80 }
81 
TreeListBoxDropTarget(TreeListBox & rTreeView)82 TreeListBoxDropTarget::TreeListBoxDropTarget(TreeListBox& rTreeView)
83     : DropTargetHelper(rTreeView.GetWidget().get_drop_target())
84     , m_rTreeView(rTreeView)
85 {
86 }
87 
AcceptDrop(const AcceptDropEvent & rEvt)88 sal_Int8 TreeListBoxDropTarget::AcceptDrop(const AcceptDropEvent& rEvt)
89 {
90     sal_Int8 nAccept = m_rTreeView.AcceptDrop(rEvt);
91 
92     if (nAccept != DND_ACTION_NONE)
93     {
94         // to enable the autoscroll when we're close to the edges
95         weld::TreeView& rWidget = m_rTreeView.GetWidget();
96         rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true);
97     }
98 
99     return nAccept;
100 }
101 
ExecuteDrop(const ExecuteDropEvent & rEvt)102 sal_Int8 TreeListBoxDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt)
103 {
104     return m_rTreeView.ExecuteDrop(rEvt);
105 }
106 
TreeListBox(std::unique_ptr<weld::TreeView> xTreeView,bool bSQLType)107 TreeListBox::TreeListBox(std::unique_ptr<weld::TreeView> xTreeView, bool bSQLType)
108     : m_xTreeView(std::move(xTreeView))
109     , m_aDropTargetHelper(*this)
110     , m_pActionListener(nullptr)
111     , m_pContextMenuProvider(nullptr)
112 {
113     m_xTreeView->connect_key_press(LINK(this, TreeListBox, KeyInputHdl));
114     m_xTreeView->connect_changed(LINK(this, TreeListBox, SelectHdl));
115     m_xTreeView->connect_query_tooltip(LINK(this, TreeListBox, QueryTooltipHdl));
116     m_xTreeView->connect_popup_menu(LINK(this, TreeListBox, CommandHdl));
117 
118     if (bSQLType)
119         m_xHelper.set(new ODataClipboard);
120     else
121         m_xHelper.set(new svx::OComponentTransferable);
122     m_xTreeView->enable_drag_source(m_xHelper, DND_ACTION_COPY);
123     m_xTreeView->connect_drag_begin(LINK(this, TreeListBox, DragBeginHdl));
124 
125     m_aTimer.SetTimeout(900);
126     m_aTimer.SetInvokeHandler(LINK(this, TreeListBox, OnTimeOut));
127 }
128 
DoChildKeyInput(const KeyEvent &)129 bool TreeListBox::DoChildKeyInput(const KeyEvent& /*rKEvt*/)
130 {
131     // nothing by default
132     return false;
133 }
134 
IMPL_LINK(TreeListBox,KeyInputHdl,const KeyEvent &,rKEvt,bool)135 IMPL_LINK(TreeListBox, KeyInputHdl, const KeyEvent&, rKEvt, bool)
136 {
137     KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
138     bool bHandled = false;
139 
140     switch (eFunc)
141     {
142         case KeyFuncType::COPY:
143             bHandled = m_aCopyHandler.IsSet() && !m_xTreeView->get_selected(nullptr);
144             if (bHandled)
145                 m_aCopyHandler.Call(nullptr);
146             break;
147         case KeyFuncType::PASTE:
148             bHandled = m_aPasteHandler.IsSet() && !m_xTreeView->get_selected(nullptr);
149             if (bHandled)
150                 m_aPasteHandler.Call(nullptr);
151             break;
152         case KeyFuncType::DELETE:
153             bHandled = m_aDeleteHandler.IsSet() && !m_xTreeView->get_selected(nullptr);
154             if (bHandled)
155                 m_aDeleteHandler.Call(nullptr);
156             break;
157         default:
158             break;
159     }
160 
161     return bHandled || DoChildKeyInput(rKEvt);
162 }
163 
implStopSelectionTimer()164 void TreeListBox::implStopSelectionTimer()
165 {
166     if ( m_aTimer.IsActive() )
167         m_aTimer.Stop();
168 }
169 
implStartSelectionTimer()170 void TreeListBox::implStartSelectionTimer()
171 {
172     implStopSelectionTimer();
173     m_aTimer.Start();
174 }
175 
IMPL_LINK_NOARG(TreeListBox,SelectHdl,weld::TreeView &,void)176 IMPL_LINK_NOARG(TreeListBox, SelectHdl, weld::TreeView&, void)
177 {
178     implStartSelectionTimer();
179 }
180 
~TreeListBox()181 TreeListBox::~TreeListBox()
182 {
183 }
184 
GetEntryPosByName(std::u16string_view aName,const weld::TreeIter * pStart,const IEntryFilter * _pFilter) const185 std::unique_ptr<weld::TreeIter> TreeListBox::GetEntryPosByName(std::u16string_view aName, const weld::TreeIter* pStart, const IEntryFilter* _pFilter) const
186 {
187     auto xEntry(m_xTreeView->make_iterator(pStart));
188     if (!pStart && !m_xTreeView->get_iter_first(*xEntry))
189         return nullptr;
190 
191     do
192     {
193         if (m_xTreeView->get_text(*xEntry) == aName)
194         {
195             if (!_pFilter || _pFilter->includeEntry(reinterpret_cast<void*>(m_xTreeView->get_id(*xEntry).toUInt64())))
196             {
197                 // found
198                 return xEntry;
199             }
200         }
201     } while (m_xTreeView->iter_next(*xEntry));
202 
203     return nullptr;
204 }
205 
IMPL_LINK(TreeListBox,DragBeginHdl,bool &,rUnsetDragIcon,bool)206 IMPL_LINK(TreeListBox, DragBeginHdl, bool&, rUnsetDragIcon, bool)
207 {
208     rUnsetDragIcon = false;
209 
210     if (m_pActionListener)
211     {
212         m_xDragedEntry = m_xTreeView->make_iterator();
213         if (!m_xTreeView->get_selected(m_xDragedEntry.get()))
214             m_xDragedEntry.reset();
215         if (m_xDragedEntry && m_pActionListener->requestDrag(*m_xDragedEntry))
216         {
217             // if the (asynchronous) drag started, stop the selection timer
218             implStopSelectionTimer();
219             return false;
220         }
221     }
222 
223     return true;
224 }
225 
AcceptDrop(const AcceptDropEvent & rEvt)226 sal_Int8 TreeListBox::AcceptDrop(const AcceptDropEvent& rEvt)
227 {
228     sal_Int8 nDropOption = DND_ACTION_NONE;
229     if ( m_pActionListener )
230     {
231         ::Point aDropPos = rEvt.maPosPixel;
232         std::unique_ptr<weld::TreeIter> xDropTarget(m_xTreeView->make_iterator());
233         if (!m_xTreeView->get_dest_row_at_pos(aDropPos, xDropTarget.get(), true))
234             xDropTarget.reset();
235 
236         // check if drag is on child entry, which is not allowed
237         std::unique_ptr<weld::TreeIter> xParent;
238         if (rEvt.mnAction & DND_ACTION_MOVE)
239         {
240             if (!m_xDragedEntry) // no entry to move
241                 return m_pActionListener->queryDrop(rEvt, m_aDropTargetHelper.GetDataFlavorExVector());
242 
243             if (xDropTarget)
244             {
245                 xParent = m_xTreeView->make_iterator(xDropTarget.get());
246                 if (!m_xTreeView->iter_parent(*xParent))
247                     xParent.reset();
248             }
249             while (xParent && m_xTreeView->iter_compare(*xParent, *m_xDragedEntry) != 0)
250             {
251                 if (!m_xTreeView->iter_parent(*xParent))
252                     xParent.reset();
253             }
254         }
255 
256         if (!xParent)
257         {
258             nDropOption = m_pActionListener->queryDrop(rEvt, m_aDropTargetHelper.GetDataFlavorExVector());
259             // check if move is allowed
260             if ( nDropOption & DND_ACTION_MOVE )
261             {
262                 if (!m_xDragedEntry || !xDropTarget ||
263                     m_xTreeView->iter_compare(*m_xDragedEntry, *xDropTarget) == 0 ||
264                     GetEntryPosByName(m_xTreeView->get_text(*m_xDragedEntry), xDropTarget.get()))
265                 {
266                     nDropOption = nDropOption & ~DND_ACTION_MOVE;//DND_ACTION_NONE;
267                 }
268             }
269         }
270     }
271 
272     return nDropOption;
273 }
274 
ExecuteDrop(const ExecuteDropEvent & rEvt)275 sal_Int8 TreeListBox::ExecuteDrop(const ExecuteDropEvent& rEvt)
276 {
277     if (m_pActionListener)
278         m_pActionListener->executeDrop(rEvt);
279     m_xTreeView->unset_drag_dest_row();
280     return DND_ACTION_NONE;
281 }
282 
IMPL_LINK(TreeListBox,QueryTooltipHdl,const weld::TreeIter &,rIter,OUString)283 IMPL_LINK(TreeListBox, QueryTooltipHdl, const weld::TreeIter&, rIter, OUString)
284 {
285     OUString sQuickHelpText;
286     if (m_pActionListener &&
287         m_pActionListener->requestQuickHelp(reinterpret_cast<void*>(m_xTreeView->get_id(rIter).toUInt64()), sQuickHelpText))
288     {
289         return sQuickHelpText;
290     }
291     return m_xTreeView->get_tooltip_text();
292 }
293 
294 namespace
295 {
296     // SelectionSupplier
297     typedef ::cppu::WeakImplHelper<   XSelectionSupplier
298                                   >   SelectionSupplier_Base;
299     class SelectionSupplier : public SelectionSupplier_Base
300     {
301     public:
SelectionSupplier(const Any & _rSelection)302         explicit SelectionSupplier( const Any& _rSelection )
303             :m_aSelection( _rSelection )
304         {
305         }
306 
307         virtual sal_Bool SAL_CALL select( const Any& xSelection ) override;
308         virtual Any SAL_CALL getSelection(  ) override;
309         virtual void SAL_CALL addSelectionChangeListener( const Reference< XSelectionChangeListener >& xListener ) override;
310         virtual void SAL_CALL removeSelectionChangeListener( const Reference< XSelectionChangeListener >& xListener ) override;
311 
312     protected:
~SelectionSupplier()313         virtual ~SelectionSupplier() override
314         {
315         }
316 
317     private:
318         Any m_aSelection;
319     };
320 
select(const Any &)321     sal_Bool SAL_CALL SelectionSupplier::select( const Any& /*_Selection*/ )
322     {
323         throw IllegalArgumentException();
324         // API bug: this should be a NoSupportException
325     }
326 
getSelection()327     Any SAL_CALL SelectionSupplier::getSelection(  )
328     {
329         return m_aSelection;
330     }
331 
addSelectionChangeListener(const Reference<XSelectionChangeListener> &)332     void SAL_CALL SelectionSupplier::addSelectionChangeListener( const Reference< XSelectionChangeListener >& /*_Listener*/ )
333     {
334         OSL_FAIL( "SelectionSupplier::removeSelectionChangeListener: no support!" );
335         // API bug: this should be a NoSupportException
336     }
337 
removeSelectionChangeListener(const Reference<XSelectionChangeListener> &)338     void SAL_CALL SelectionSupplier::removeSelectionChangeListener( const Reference< XSelectionChangeListener >& /*_Listener*/ )
339     {
340         OSL_FAIL( "SelectionSupplier::removeSelectionChangeListener: no support!" );
341         // API bug: this should be a NoSupportException
342     }
343 }
344 
IMPL_LINK(TreeListBox,CommandHdl,const CommandEvent &,rCEvt,bool)345 IMPL_LINK(TreeListBox, CommandHdl, const CommandEvent&, rCEvt, bool)
346 {
347     if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
348         return false;
349 
350     ::Point aPos = rCEvt.GetMousePosPixel();
351 
352     std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator());
353     if (m_xTreeView->get_dest_row_at_pos(aPos, xIter.get(), false) && !m_xTreeView->is_selected(*xIter))
354     {
355         m_xTreeView->unselect_all();
356         m_xTreeView->set_cursor(*xIter);
357         m_xTreeView->select(*xIter);
358         SelectHdl(*m_xTreeView);
359     }
360 
361     if (!m_pContextMenuProvider)
362         return false;
363 
364     OUString aResourceName(m_pContextMenuProvider->getContextMenuResourceName());
365     if (aResourceName.isEmpty())
366         return false;
367 
368     css::uno::Sequence< css::uno::Any > aArgs( 3 );
369     aArgs[0] <<= comphelper::makePropertyValue( "Value", aResourceName );
370     aArgs[1] <<= comphelper::makePropertyValue( "Frame", m_pContextMenuProvider->getCommandController().getXController()->getFrame() );
371     aArgs[2] <<= comphelper::makePropertyValue( "IsContextMenu", true );
372 
373     css::uno::Reference< css::uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
374     css::uno::Reference<css::frame::XPopupMenuController> xMenuController
375         (xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
376             "com.sun.star.comp.framework.ResourceMenuController", aArgs, xContext), css::uno::UNO_QUERY);
377 
378     if (!xMenuController.is())
379         return false;
380 
381     VclPtr<vcl::Window> xMenuParent = m_pContextMenuProvider->getMenuParent();
382 
383     rtl::Reference xPopupMenu( new VCLXPopupMenu );
384     xMenuController->setPopupMenu( xPopupMenu );
385     VclPtr<PopupMenu> pContextMenu( static_cast< PopupMenu* >( xPopupMenu->GetMenu() ) );
386 
387     // allow context menu interception
388     ::comphelper::OInterfaceContainerHelper2* pInterceptors = m_pContextMenuProvider->getContextMenuInterceptors();
389     if (pInterceptors && pInterceptors->getLength())
390     {
391         OUString aMenuIdentifier( "private:resource/popupmenu/" + aResourceName );
392 
393         ContextMenuExecuteEvent aEvent;
394         aEvent.SourceWindow = VCLUnoHelper::GetInterface(xMenuParent);
395         aEvent.ExecutePosition.X = -1;
396         aEvent.ExecutePosition.Y = -1;
397         aEvent.ActionTriggerContainer = ::framework::ActionTriggerHelper::CreateActionTriggerContainerFromMenu(
398             pContextMenu.get(), &aMenuIdentifier );
399         aEvent.Selection = new SelectionSupplier(m_pContextMenuProvider->getCurrentSelection(*m_xTreeView));
400 
401         ::comphelper::OInterfaceIteratorHelper2 aIter( *pInterceptors );
402         bool bModifiedMenu = false;
403         bool bAskInterceptors = true;
404         while ( aIter.hasMoreElements() && bAskInterceptors )
405         {
406             Reference< XContextMenuInterceptor > xInterceptor( aIter.next(), UNO_QUERY );
407             if ( !xInterceptor.is() )
408                 continue;
409 
410             try
411             {
412                 ContextMenuInterceptorAction eAction = xInterceptor->notifyContextMenuExecute( aEvent );
413                 switch ( eAction )
414                 {
415                     case ContextMenuInterceptorAction_CANCELLED:
416                         return false;
417 
418                     case ContextMenuInterceptorAction_EXECUTE_MODIFIED:
419                         bModifiedMenu = true;
420                         bAskInterceptors = false;
421                         break;
422 
423                     case ContextMenuInterceptorAction_CONTINUE_MODIFIED:
424                         bModifiedMenu = true;
425                         bAskInterceptors = true;
426                         break;
427 
428                     default:
429                         OSL_FAIL( "DBTreeListBox::CreateContextMenu: unexpected return value of the interceptor call!" );
430                         [[fallthrough]];
431                     case ContextMenuInterceptorAction_IGNORED:
432                         break;
433                 }
434             }
435             catch( const DisposedException& e )
436             {
437                 if ( e.Context == xInterceptor )
438                     aIter.remove();
439             }
440         }
441 
442         if ( bModifiedMenu )
443         {
444             pContextMenu->Clear();
445             ::framework::ActionTriggerHelper::CreateMenuFromActionTriggerContainer(
446                 pContextMenu, aEvent.ActionTriggerContainer );
447             aEvent.ActionTriggerContainer.clear();
448         }
449     }
450 
451     // adjust pos relative to m_xTreeView to relative to xMenuParent
452     m_pContextMenuProvider->adjustMenuPosition(*m_xTreeView, aPos);
453 
454     // do action for selected entry in popup menu
455     pContextMenu->Execute(xMenuParent, aPos);
456     pContextMenu.disposeAndClear();
457 
458     css::uno::Reference<css::lang::XComponent> xComponent(xMenuController, css::uno::UNO_QUERY);
459     if (xComponent.is())
460         xComponent->dispose();
461     xMenuController.clear();
462 
463     return true;
464 }
465 
IMPL_LINK_NOARG(TreeListBox,OnTimeOut,Timer *,void)466 IMPL_LINK_NOARG(TreeListBox, OnTimeOut, Timer*, void)
467 {
468     implStopSelectionTimer();
469 
470     m_aSelChangeHdl.Call( nullptr );
471 }
472 
GetRootLevelParent(const weld::TreeIter * pEntry) const473 std::unique_ptr<weld::TreeIter> TreeListBox::GetRootLevelParent(const weld::TreeIter* pEntry) const
474 {
475     if (!pEntry)
476         return nullptr;
477     std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(pEntry));
478     while (m_xTreeView->get_iter_depth(*xEntry))
479         m_xTreeView->iter_parent(*xEntry);
480     return xEntry;
481 }
482 
DBTreeViewBase(weld::Container * pContainer)483 DBTreeViewBase::DBTreeViewBase(weld::Container* pContainer)
484     : m_xBuilder(Application::CreateBuilder(pContainer, "dbaccess/ui/dbtreelist.ui"))
485     , m_xContainer(m_xBuilder->weld_container("DBTreeList"))
486 {
487 }
488 
~DBTreeViewBase()489 DBTreeViewBase::~DBTreeViewBase()
490 {
491 }
492 
DBTreeView(weld::Container * pContainer,bool bSQLType)493 DBTreeView::DBTreeView(weld::Container* pContainer, bool bSQLType)
494     : DBTreeViewBase(pContainer)
495 {
496     m_xTreeListBox.reset(new TreeListBox(m_xBuilder->weld_tree_view("treeview"), bSQLType));
497 }
498 
499 }   // namespace dbaui
500 
501 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
502