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