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
