cid#1636690 Dereference after null check
[LibreOffice.git] / svx / source / form / navigatortree.cxx
blob17571e16838c21d6580b00e38285de87082c9880
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <memory>
21 #include <svx/dialmgr.hxx>
22 #include <svx/fmshell.hxx>
23 #include <svx/fmmodel.hxx>
24 #include <svx/fmpage.hxx>
25 #include <svx/svdpagv.hxx>
26 #include <svx/svditer.hxx>
28 #include <helpids.h>
29 #include <fmexpl.hxx>
30 #include <fmshimp.hxx>
31 #include <fmservs.hxx>
32 #include <fmundo.hxx>
33 #include <fmpgeimp.hxx>
34 #include <fmobj.hxx>
35 #include <fmprop.hxx>
36 #include <sal/log.hxx>
37 #include <vcl/svapp.hxx>
38 #include <sfx2/viewsh.hxx>
39 #include <sfx2/dispatch.hxx>
40 #include <sfx2/viewfrm.hxx>
41 #include <comphelper/processfactory.hxx>
42 #include <comphelper/property.hxx>
43 #include <comphelper/types.hxx>
44 #include <com/sun/star/form/FormComponentType.hpp>
45 #include <com/sun/star/sdb/CommandType.hpp>
46 #include <com/sun/star/beans/PropertyAttribute.hpp>
47 #include <com/sun/star/script/XEventAttacherManager.hpp>
48 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
49 #include <com/sun/star/datatransfer/XTransferable.hpp>
50 #include <com/sun/star/uno/XComponentContext.hpp>
51 #include <svx/sdrpaintwindow.hxx>
53 #include <svx/strings.hrc>
54 #include <comphelper/diagnose_ex.hxx>
55 #include <svx/svxids.hrc>
56 #include <bitmaps.hlst>
57 #include <vcl/commandevent.hxx>
59 namespace svxform
61 #define EXPLORER_SYNC_DELAY 200
62 // Time (in ms) until explorer synchronizes the view after select or deselect
64 using namespace ::com::sun::star::uno;
65 using namespace ::com::sun::star::lang;
66 using namespace ::com::sun::star::beans;
67 using namespace ::com::sun::star::form;
68 using namespace ::com::sun::star::awt;
69 using namespace ::com::sun::star::container;
70 using namespace ::com::sun::star::script;
71 using namespace ::com::sun::star::datatransfer;
72 using namespace ::com::sun::star::datatransfer::clipboard;
73 using namespace ::com::sun::star::sdb;
76 // helper
79 typedef ::std::map< Reference< XInterface >, SdrObject* > MapModelToShape;
82 static void collectShapeModelMapping( SdrPage const * _pPage, MapModelToShape& _rMapping )
84 OSL_ENSURE( _pPage, "collectShapeModelMapping: invalid arg!" );
86 _rMapping.clear();
88 SdrObjListIter aIter( _pPage );
89 while ( aIter.IsMore() )
91 SdrObject* pSdrObject = aIter.Next();
92 FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject );
93 if ( !pFormObject )
94 continue;
96 Reference< XInterface > xNormalizedModel( pFormObject->GetUnoControlModel(), UNO_QUERY );
97 // note that this is normalized (i.e. queried for XInterface explicitly)
99 ::std::pair< MapModelToShape::iterator, bool > aPos =
100 _rMapping.emplace( xNormalizedModel, pSdrObject );
101 DBG_ASSERT( aPos.second, "collectShapeModelMapping: model was already existent!" );
102 // if this asserts, this would mean we have 2 shapes pointing to the same model
106 NavigatorTreeDropTarget::NavigatorTreeDropTarget(NavigatorTree& rTreeView)
107 : DropTargetHelper(rTreeView.get_widget().get_drop_target())
108 , m_rTreeView(rTreeView)
112 sal_Int8 NavigatorTreeDropTarget::AcceptDrop(const AcceptDropEvent& rEvt)
114 sal_Int8 nAccept = m_rTreeView.AcceptDrop(rEvt);
116 if (nAccept != DND_ACTION_NONE)
118 // to enable the autoscroll when we're close to the edges
119 weld::TreeView& rWidget = m_rTreeView.get_widget();
120 rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true);
123 return nAccept;
126 sal_Int8 NavigatorTreeDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt)
128 return m_rTreeView.ExecuteDrop(rEvt);
131 NavigatorTree::NavigatorTree(std::unique_ptr<weld::TreeView> xTreeView)
132 :m_xTreeView(std::move(xTreeView))
133 ,m_aDropTargetHelper(*this)
134 ,m_aSynchronizeTimer("svx NavigatorTree m_aSynchronizeTimer")
135 ,nEditEvent(nullptr)
136 ,m_sdiState(SDI_DIRTY)
137 ,m_nSelectLock(0)
138 ,m_nFormsSelected(0)
139 ,m_nControlsSelected(0)
140 ,m_nHiddenControls(0)
141 ,m_bDragDataDirty(false)
142 ,m_bPrevSelectionMixed(false)
143 ,m_bRootSelected(false)
144 ,m_bInitialUpdate(true)
145 ,m_bKeyboardCut( false )
146 ,m_bEditing( false )
148 m_xTreeView->set_help_id(HID_FORM_NAVIGATOR);
149 m_xTreeView->set_size_request(200, 200);
151 m_xTreeView->set_selection_mode(SelectionMode::Multiple);
153 m_pNavModel.reset(new NavigatorTreeModel());
154 Clear();
156 StartListening( *m_pNavModel );
158 m_aSynchronizeTimer.SetInvokeHandler(LINK(this, NavigatorTree, OnSynchronizeTimer));
159 m_xTreeView->connect_selection_changed(LINK(this, NavigatorTree, OnEntrySelDesel));
160 m_xTreeView->connect_key_press(LINK(this, NavigatorTree, KeyInputHdl));
161 m_xTreeView->connect_popup_menu(LINK(this, NavigatorTree, PopupMenuHdl));
162 m_xTreeView->connect_editing(LINK(this, NavigatorTree, EditingEntryHdl),
163 LINK(this, NavigatorTree, EditedEntryHdl));
164 m_xTreeView->connect_drag_begin(LINK(this, NavigatorTree, DragBeginHdl));
167 NavigatorTree::~NavigatorTree()
169 if( nEditEvent )
170 Application::RemoveUserEvent( nEditEvent );
172 if (m_aSynchronizeTimer.IsActive())
173 m_aSynchronizeTimer.Stop();
175 DBG_ASSERT(GetNavModel() != nullptr, "NavigatorTree::~NavigatorTree : unexpected : no ExplorerModel");
176 EndListening( *m_pNavModel );
177 Clear();
178 m_pNavModel.reset();
181 void NavigatorTree::Clear()
183 m_pNavModel->Clear();
186 void NavigatorTree::UpdateContent( FmFormShell* pFormShell )
188 if (m_bInitialUpdate)
190 GrabFocus();
191 m_bInitialUpdate = false;
194 FmFormShell* pOldShell = GetNavModel()->GetFormShell();
195 FmFormPage* pOldPage = GetNavModel()->GetFormPage();
196 FmFormPage* pNewPage = pFormShell ? pFormShell->GetCurPage() : nullptr;
198 if ((pOldShell != pFormShell) || (pOldPage != pNewPage))
200 // new shell during editing
201 if (IsEditingActive())
203 m_xTreeView->end_editing();
204 m_bEditing = false;
207 m_bDragDataDirty = true; // as a precaution, although I don't drag
209 GetNavModel()->UpdateContent( pFormShell );
211 // if there is a form, expand root
212 if (m_xRootEntry && !m_xTreeView->get_row_expanded(*m_xRootEntry))
213 m_xTreeView->expand_row(*m_xRootEntry);
214 // if there is EXACTLY ONE form, expand it too
215 if (m_xRootEntry)
217 std::unique_ptr<weld::TreeIter> xFirst(m_xTreeView->make_iterator(m_xRootEntry.get()));
218 bool bFirst = m_xTreeView->iter_children(*xFirst);
219 if (bFirst)
221 std::unique_ptr<weld::TreeIter> xSibling(m_xTreeView->make_iterator(xFirst.get()));
222 if (!m_xTreeView->iter_next_sibling(*xSibling))
223 m_xTreeView->expand_row(*xFirst);
228 bool NavigatorTree::implAllowExchange( sal_Int8 _nAction, bool* _pHasNonHidden )
230 bool bCurEntry = m_xTreeView->get_cursor(nullptr);
231 if (!bCurEntry)
232 return false;
234 // Information for AcceptDrop and Execute Drop
235 CollectSelectionData(SDI_ALL);
236 if (m_arrCurrentSelection.empty())
237 // nothing to do
238 return false;
240 // check whether there are only hidden controls
241 // I may add a format to pCtrlExch
242 bool bHasNonHidden = std::any_of(m_arrCurrentSelection.begin(), m_arrCurrentSelection.end(),
243 [this](const auto& rEntry) {
244 FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rEntry));
245 return !IsHiddenControl( pCurrent );
248 if ( bHasNonHidden && ( 0 == ( _nAction & DND_ACTION_MOVE ) ) )
249 // non-hidden controls need to be moved
250 return false;
252 if ( _pHasNonHidden )
253 *_pHasNonHidden = bHasNonHidden;
255 return true;
258 bool NavigatorTree::implPrepareExchange( sal_Int8 _nAction )
260 bool bHasNonHidden = false;
261 if ( !implAllowExchange( _nAction, &bHasNonHidden ) )
262 return false;
264 m_aControlExchange.prepareDrag();
265 m_aControlExchange->setFocusEntry(m_xTreeView->get_cursor(nullptr));
267 for (const auto& rpEntry : m_arrCurrentSelection)
268 m_aControlExchange->addSelectedEntry(m_xTreeView->make_iterator(rpEntry.get()));
270 m_aControlExchange->setFormsRoot( GetNavModel()->GetFormPage()->GetForms() );
271 m_aControlExchange->buildPathFormat(m_xTreeView.get(), m_xRootEntry.get());
273 if (!bHasNonHidden)
275 // create a sequence
276 Sequence< Reference< XInterface > > seqIFaces(m_arrCurrentSelection.size());
277 Reference< XInterface >* pArray = seqIFaces.getArray();
278 for (const auto& rpEntry : m_arrCurrentSelection)
280 *pArray = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rpEntry))->GetElement();
281 ++pArray;
283 // and the new format
284 m_aControlExchange->addHiddenControlsFormat(seqIFaces);
287 m_bDragDataDirty = false;
288 return true;
291 IMPL_LINK(NavigatorTree, DragBeginHdl, bool&, rUnsetDragIcon, bool)
293 rUnsetDragIcon = false;
295 bool bSuccess = implPrepareExchange(DND_ACTION_COPYMOVE);
296 if (bSuccess)
298 OControlExchange& rExchange = *m_aControlExchange;
299 rtl::Reference<TransferDataContainer> xHelper(&rExchange);
300 m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPYMOVE);
301 rExchange.setDragging(true);
303 return !bSuccess;
306 IMPL_LINK(NavigatorTree, PopupMenuHdl, const CommandEvent&, rEvt, bool)
308 bool bHandled = false;
309 switch( rEvt.GetCommand() )
311 case CommandEventId::ContextMenu:
313 // Position of click
314 ::Point ptWhere;
315 if (rEvt.IsMouseEvent())
317 ptWhere = rEvt.GetMousePosPixel();
318 std::unique_ptr<weld::TreeIter> xClickedOn(m_xTreeView->make_iterator());
319 if (!m_xTreeView->get_dest_row_at_pos(ptWhere, xClickedOn.get(), false))
320 break;
321 if (!m_xTreeView->is_selected(*xClickedOn))
323 m_xTreeView->unselect_all();
324 m_xTreeView->select(*xClickedOn);
325 m_xTreeView->set_cursor(*xClickedOn);
328 else
330 if (m_arrCurrentSelection.empty()) // only happens with context menu via keyboard
331 break;
333 std::unique_ptr<weld::TreeIter> xCurrent(m_xTreeView->make_iterator());
334 if (!m_xTreeView->get_cursor(xCurrent.get()))
335 break;
336 ptWhere = m_xTreeView->get_row_area(*xCurrent).Center();
339 // update my selection data
340 CollectSelectionData(SDI_ALL);
342 // if there is at least one no-root-entry and the root selected, I deselect root
343 if ( (m_arrCurrentSelection.size() > 1) && m_bRootSelected )
345 const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin();
346 m_xTreeView->set_cursor(*rIter);
347 m_xTreeView->unselect(*m_xRootEntry);
349 bool bSingleSelection = (m_arrCurrentSelection.size() == 1);
352 DBG_ASSERT( (!m_arrCurrentSelection.empty()) || m_bRootSelected, "no entries selected" );
353 // shouldn't happen, because I would have selected one during call to IsSelected,
354 // if there was none before
357 // create menu
358 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
359 FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr;
360 if( pFormShell && pFormModel )
362 std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(m_xTreeView.get(), u"svx/ui/formnavimenu.ui"_ustr));
363 std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu(u"menu"_ustr));
364 std::unique_ptr<weld::Menu> xSubMenuNew(xBuilder->weld_menu(u"submenu"_ustr));
366 // menu 'New' only exists, if only the root or only one form is selected
367 bool bShowNew = bSingleSelection && (m_nFormsSelected || m_bRootSelected);
368 if (!bShowNew)
369 xContextMenu->remove(u"new"_ustr);
371 // 'New'\'Form' under the same terms
372 bool bShowForm = bSingleSelection && (m_nFormsSelected || m_bRootSelected);
373 if (bShowForm)
374 xSubMenuNew->append(u"form"_ustr, SvxResId(RID_STR_FORM), RID_SVXBMP_FORM);
376 // 'New'\'hidden...', if exactly one form is selected
377 bool bShowHidden = bSingleSelection && m_nFormsSelected;
378 if (bShowHidden)
379 xSubMenuNew->append(u"hidden"_ustr, SvxResId(RID_STR_HIDDEN), RID_SVXBMP_HIDDEN);
381 // 'Delete': everything which is not root can be removed
382 if (m_bRootSelected)
383 xContextMenu->remove(u"delete"_ustr);
385 // 'Cut', 'Copy' and 'Paste'
386 bool bShowCut = !m_bRootSelected && implAllowExchange(DND_ACTION_MOVE);
387 if (!bShowCut)
388 xContextMenu->remove(u"cut"_ustr);
389 bool bShowCopy = !m_bRootSelected && implAllowExchange(DND_ACTION_COPY);
390 if (!bShowCopy)
391 xContextMenu->remove(u"copy"_ustr);
392 if (!implAcceptPaste())
393 xContextMenu->remove(u"paste"_ustr);
395 // TabDialog, if exactly one form
396 bool bShowTabOrder = bSingleSelection && m_nFormsSelected;
397 if (!bShowTabOrder)
398 xContextMenu->remove(u"taborder"_ustr);
400 bool bShowProps = true;
401 // in XML forms, we don't allow for the properties of a form
402 // #i36484#
403 if (pFormShell->GetImpl()->isEnhancedForm_Lock() && !m_nControlsSelected)
404 bShowProps = false;
405 // if the property browser is already open, we don't allow for the properties, too
406 if (pFormShell->GetImpl()->IsPropBrwOpen_Lock())
407 bShowProps = false;
409 // and finally, if there's a mixed selection of forms and controls, disable the entry, too
410 if (bShowProps && !pFormShell->GetImpl()->IsPropBrwOpen_Lock())
411 bShowProps =
412 (m_nControlsSelected && !m_nFormsSelected) || (!m_nControlsSelected && m_nFormsSelected);
414 if (!bShowProps)
415 xContextMenu->remove(u"props"_ustr);
417 // rename, if one element and no root
418 bool bShowRename = bSingleSelection && !m_bRootSelected;
419 if (!bShowRename)
420 xContextMenu->remove(u"rename"_ustr);
422 if (!m_bRootSelected)
424 // Readonly-entry is only for root
425 xContextMenu->remove(u"designmode"_ustr);
426 // the same for automatic control focus
427 xContextMenu->remove(u"controlfocus"_ustr);
430 std::unique_ptr<weld::Menu> xConversionMenu(xBuilder->weld_menu(u"changemenu"_ustr));
431 // ConvertTo-Slots are enabled, if one control is selected
432 // the corresponding slot is disabled
433 if (!m_bRootSelected && !m_nFormsSelected && (m_nControlsSelected == 1))
435 FmXFormShell::GetConversionMenu_Lock(*xConversionMenu);
436 #if OSL_DEBUG_LEVEL > 0
437 const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin();
438 FmControlData* pCurrent = weld::fromId<FmControlData*>(m_xTreeView->get_id(*rIter));
439 OSL_ENSURE( pFormShell->GetImpl()->isSolelySelected_Lock( pCurrent->GetFormComponent() ),
440 "NavigatorTree::Command: inconsistency between the navigator selection, and the selection as the shell knows it!" );
441 #endif
443 pFormShell->GetImpl()->checkControlConversionSlotsForCurrentSelection_Lock(*xConversionMenu);
445 else
446 xContextMenu->remove(u"change"_ustr);
448 if (m_bRootSelected)
450 // set OpenReadOnly
451 xContextMenu->set_active(u"designmode"_ustr, pFormModel->GetOpenInDesignMode());
452 xContextMenu->set_active(u"controlfocus"_ustr, pFormModel->GetAutoControlFocus());
455 OUString sIdent = xContextMenu->popup_at_rect(m_xTreeView.get(), tools::Rectangle(ptWhere, ::Size(1, 1)));
456 if (sIdent == "form")
458 OUString aStr(SvxResId(RID_STR_FORM));
459 OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr);
461 pFormModel->BegUndo(aUndoStr);
462 // slot was only available, if there is only one selected entry,
463 // which is a root or a form
464 const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin();
465 NewForm(*rIter);
466 pFormModel->EndUndo();
468 else if (sIdent == "hidden")
470 OUString aStr(SvxResId(RID_STR_CONTROL));
471 OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr);
473 pFormModel->BegUndo(aUndoStr);
474 // slot was valid for (exactly) one selected form
475 const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin();
476 NewControl(FM_COMPONENT_HIDDEN, *rIter, true);
477 pFormModel->EndUndo();
479 else if (sIdent == "cut")
480 doCut();
481 else if (sIdent == "copy")
482 doCopy();
483 else if (sIdent == "paste")
484 doPaste();
485 else if (sIdent == "delete")
486 DeleteSelection();
487 else if (sIdent == "taborder")
489 // this slot was effective for exactly one selected form
490 const std::unique_ptr<weld::TreeIter>& rSelectedForm = *m_arrCurrentSelection.begin();
491 DBG_ASSERT( IsFormEntry(*rSelectedForm), "NavigatorTree::Command: This entry must be a FormEntry." );
493 FmFormData* pFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(*rSelectedForm));
494 const Reference< XForm >& xForm( pFormData->GetFormIface());
496 Reference< XTabControllerModel > xTabController(xForm, UNO_QUERY);
497 if( !xTabController.is() )
498 break;
499 GetNavModel()->GetFormShell()->GetImpl()->ExecuteTabOrderDialog_Lock(xTabController);
501 else if (sIdent == "props")
502 ShowSelectionProperties(true);
503 else if (sIdent == "rename")
505 // only allowed for one no-root-entry
506 const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin();
507 m_xTreeView->start_editing(*rIter);
508 m_bEditing = true;
510 else if (sIdent == "designmode")
512 pFormModel->SetOpenInDesignMode( !pFormModel->GetOpenInDesignMode() );
513 pFormShell->GetViewShell()->GetViewFrame().GetBindings().Invalidate(SID_FM_OPEN_READONLY);
515 else if (sIdent == "controlfocus")
517 pFormModel->SetAutoControlFocus( !pFormModel->GetAutoControlFocus() );
518 pFormShell->GetViewShell()->GetViewFrame().GetBindings().Invalidate(SID_FM_AUTOCONTROLFOCUS);
520 else if (FmXFormShell::isControlConversionSlot(sIdent))
522 const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin();
523 FmControlData* pCurrent = weld::fromId<FmControlData*>(m_xTreeView->get_id(*rIter));
524 if (pFormShell->GetImpl()->executeControlConversionSlot_Lock(pCurrent->GetFormComponent(), sIdent))
525 ShowSelectionProperties();
528 bHandled = true;
530 break;
531 default: break;
534 return bHandled;
537 std::unique_ptr<weld::TreeIter> NavigatorTree::FindEntry(FmEntryData* pEntryData)
539 std::unique_ptr<weld::TreeIter> xRet;
540 if(!pEntryData)
541 return xRet;
543 m_xTreeView->all_foreach([this, pEntryData, &xRet](weld::TreeIter& rEntry){
544 FmEntryData* pCurEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rEntry));
545 if (pCurEntryData && pCurEntryData->IsEqualWithoutChildren(pEntryData))
547 xRet = m_xTreeView->make_iterator(&rEntry);
548 return true;
550 return false;
553 return xRet;
556 void NavigatorTree::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
558 if( rHint.GetId() == SfxHintId::FmNavRemoved )
560 auto pRemovedHint = static_cast<const FmNavRemovedHint*>(&rHint);
561 FmEntryData* pEntryData = pRemovedHint->GetEntryData();
562 Remove( pEntryData );
564 else if( rHint.GetId() == SfxHintId::FmNavInserted )
566 auto pInsertedHint = static_cast<const FmNavInsertedHint*>(&rHint);
567 FmEntryData* pEntryData = pInsertedHint->GetEntryData();
568 sal_uInt32 nRelPos = pInsertedHint->GetRelPos();
569 Insert( pEntryData, nRelPos );
571 else if( rHint.GetId() == SfxHintId::FmNavModelReplaced )
573 auto pReplacedHint = static_cast<const FmNavModelReplacedHint*>(&rHint);
574 FmEntryData* pData = pReplacedHint->GetEntryData();
575 std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pData);
576 if (xEntry)
578 // reset image
579 m_xTreeView->set_image(*xEntry, pData->GetNormalImage());
582 else if( rHint.GetId() == SfxHintId::FmNavNameChanged )
584 auto pNameChangedHint = static_cast<const FmNavNameChangedHint*>(&rHint);
585 std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pNameChangedHint->GetEntryData());
586 if (xEntry)
587 m_xTreeView->set_text(*xEntry, pNameChangedHint->GetNewName());
589 else if( rHint.GetId() == SfxHintId::FmNavCleared )
591 m_aCutEntries.clear();
592 if (m_aControlExchange.isDataExchangeActive())
593 m_aControlExchange.clear();
594 m_xTreeView->clear();
596 // default-entry "Forms"
597 OUString sText(SvxResId(RID_STR_FORMS));
598 m_xRootEntry = m_xTreeView->make_iterator();
599 m_xTreeView->insert(nullptr, -1, &sText, nullptr, nullptr, nullptr,
600 false, m_xRootEntry.get());
601 m_xTreeView->set_image(*m_xRootEntry, RID_SVXBMP_FORMS);
602 m_xTreeView->set_sensitive(*m_xRootEntry, true);
604 else if (rHint.GetId() == SfxHintId::FmNavRequestSelect)
606 auto pSelectHint = static_cast<const FmNavRequestSelectHint*>(&rHint);
607 FmEntryDataArray& arredToSelect = const_cast<FmNavRequestSelectHint*>(pSelectHint)->GetItems();
608 SynchronizeSelection(arredToSelect);
610 if (pSelectHint->IsMixedSelection())
611 // in this case I deselect all, although the view had a mixed selection
612 // during next selection, I must adapt the navigator to the view
613 m_bPrevSelectionMixed = true;
617 std::unique_ptr<weld::TreeIter> NavigatorTree::Insert(const FmEntryData* pEntryData, int nRelPos)
619 // insert current entry
620 std::unique_ptr<weld::TreeIter> xParentEntry = FindEntry( pEntryData->GetParent() );
621 std::unique_ptr<weld::TreeIter> xNewEntry(m_xTreeView->make_iterator());
622 OUString sId(weld::toId(pEntryData));
624 if(!xParentEntry)
626 m_xTreeView->insert(m_xRootEntry.get(), nRelPos, &pEntryData->GetText(), &sId,
627 nullptr, nullptr, false, xNewEntry.get());
629 else
631 m_xTreeView->insert(xParentEntry.get(), nRelPos, &pEntryData->GetText(), &sId,
632 nullptr, nullptr, false, xNewEntry.get());
635 m_xTreeView->set_image(*xNewEntry, pEntryData->GetNormalImage());
636 m_xTreeView->set_sensitive(*xNewEntry, true);
638 // If root-entry, expand root
639 if (!xParentEntry)
640 m_xTreeView->expand_row(*m_xRootEntry);
642 // insert children
643 FmEntryDataList* pChildList = pEntryData->GetChildList();
644 size_t nChildCount = pChildList->size();
645 for( size_t i = 0; i < nChildCount; i++ )
647 FmEntryData* pChildData = pChildList->at( i );
648 Insert(pChildData, -1);
651 return xNewEntry;
654 void NavigatorTree::Remove( FmEntryData* pEntryData )
656 if( !pEntryData )
657 return;
659 // entry for the data
660 std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pEntryData);
661 if (!xEntry)
662 return;
664 // delete entry from TreeListBox
665 // I'm not allowed, to treat the selection, which I trigger:
666 // select changes the MarkList of the view, if somebody else does this at the same time
667 // and removes a selection, we get a problem
668 // e.g. Group controls with open navigator
669 LockSelectionHandling();
671 // little problem: I remember the selected data, but if somebody deletes one of these entries,
672 // I get inconsistent... this would be bad
673 m_xTreeView->unselect(*xEntry);
675 // selection can be modified during deletion,
676 // but because I disabled SelectionHandling, I have to do it later
677 auto nExpectedSelectionCount = m_xTreeView->count_selected_rows();
679 ModelHasRemoved(xEntry.get());
680 m_xTreeView->remove(*xEntry);
682 if (nExpectedSelectionCount != m_xTreeView->count_selected_rows())
683 SynchronizeSelection();
685 // by default I treat the selection of course
686 UnlockSelectionHandling();
689 bool NavigatorTree::IsFormEntry(const weld::TreeIter& rEntry)
691 FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rEntry));
692 return !pEntryData || dynamic_cast<const FmFormData*>( pEntryData) != nullptr;
695 bool NavigatorTree::IsFormComponentEntry(const weld::TreeIter& rEntry)
697 FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rEntry));
698 return dynamic_cast<const FmControlData*>( pEntryData) != nullptr;
701 bool NavigatorTree::implAcceptPaste( )
703 auto nSelectedEntries = m_xTreeView->count_selected_rows();
704 if (nSelectedEntries != 1)
705 // no selected entry, or at least two selected entries
706 return false;
708 // get the clipboard
709 TransferableDataHelper aClipboardContent(TransferableDataHelper::CreateFromClipboard(m_xTreeView->get_clipboard()));
711 sal_Int8 nAction = m_aControlExchange.isClipboardOwner() && doingKeyboardCut( ) ? DND_ACTION_MOVE : DND_ACTION_COPY;
712 std::unique_ptr<weld::TreeIter> xSelected(m_xTreeView->make_iterator());
713 if (!m_xTreeView->get_selected(xSelected.get()))
714 xSelected.reset();
715 return nAction == implAcceptDataTransfer(aClipboardContent.GetDataFlavorExVector(), nAction, xSelected.get(), false);
718 sal_Int8 NavigatorTree::implAcceptDataTransfer( const DataFlavorExVector& _rFlavors, sal_Int8 _nAction, const weld::TreeIter* _pTargetEntry, bool _bDnD )
720 // no target -> no drop
721 if (!_pTargetEntry)
722 return DND_ACTION_NONE;
724 // format check
725 bool bHasDefControlFormat = OControlExchange::hasFieldExchangeFormat( _rFlavors );
726 bool bHasControlPathFormat = OControlExchange::hasControlPathFormat( _rFlavors );
727 bool bHasHiddenControlsFormat = OControlExchange::hasHiddenControlModelsFormat( _rFlavors );
728 if (!bHasDefControlFormat && !bHasControlPathFormat && !bHasHiddenControlsFormat)
729 return DND_ACTION_NONE;
731 bool bSelfSource = _bDnD ? m_aControlExchange.isDragSource() : m_aControlExchange.isClipboardOwner();
733 if ( bHasHiddenControlsFormat )
734 { // bHasHiddenControlsFormat means that only hidden controls are part of the data
736 // hidden controls can be copied to a form only
737 if (m_xTreeView->iter_compare(*_pTargetEntry, *m_xRootEntry) == 0 || !IsFormEntry(*_pTargetEntry))
738 return DND_ACTION_NONE;
740 return bSelfSource ? ( DND_ACTION_COPYMOVE & _nAction ) : DND_ACTION_COPY;
743 if ( !bSelfSource )
745 // DnD or CnP crossing navigator boundaries
746 // The main problem here is that the current API does not allow us to sneak into the content which
747 // is to be inserted. So we have to allow it for the moment, but maybe reject later on (in the real drop).
749 // TODO: this smart behaviour later on ... at the moment, we disallow data transfer crossing navigator
750 // boundaries.
752 return DND_ACTION_NONE;
755 DBG_ASSERT( _bDnD ? m_aControlExchange.isDragSource() : m_aControlExchange.isClipboardOwner(),
756 "NavigatorTree::implAcceptDataTransfer: here only with source=dest!" );
757 // somebody changed the logic of this method ...
759 // from here on, I can work with m_aControlExchange instead of _rData!
761 bool bForeignCollection = m_aControlExchange->getFormsRoot().get() != GetNavModel()->GetFormPage()->GetForms().get();
762 if ( bForeignCollection )
764 // crossing shell/page boundaries, we can exchange hidden controls only
765 // But if we survived the checks above, we do not have hidden controls.
766 // -> no data transfer
767 DBG_ASSERT( !bHasHiddenControlsFormat, "NavigatorTree::implAcceptDataTransfer: still hidden controls format!" );
768 // somebody changed the logic of this method ...
770 return DND_ACTION_COPY;
773 if (DND_ACTION_MOVE != _nAction) // 'normal' controls within a shell are moved only (never copied)
774 return DND_ACTION_NONE;
776 if ( m_bDragDataDirty || !bHasDefControlFormat )
778 if (!bHasControlPathFormat)
779 // I am in the shell/page, which has the controls, but I have no format,
780 // which survived the shell change (SVX_FM_CONTROLS_AS_PATH)
781 return DND_ACTION_NONE;
783 // I must recreate the list of the ExchangeObjects, because the shell was changed during dragging
784 // (there are SvLBoxEntries in it, and we lost them during change)
785 m_aControlExchange->buildListFromPath(m_xTreeView.get(), m_xRootEntry.get());
786 m_bDragDataDirty = false;
789 // List of dropped entries from DragServer
790 const ListBoxEntrySet& rDropped = m_aControlExchange->selected();
791 DBG_ASSERT(!rDropped.empty(), "NavigatorTree::implAcceptDataTransfer: no entries !");
793 bool bDropTargetIsComponent = IsFormComponentEntry( *_pTargetEntry );
795 // conditions to disallow the drop
796 // 0) the root entry is part of the list (can't DnD the root!)
797 // 1) one of the dragged entries is to be dropped onto its own parent
798 // 2) - " - is to be dropped onto itself
799 // 3) - " - is a Form and to be dropped onto one of its descendants
800 // 4) one of the entries is a control and to be dropped onto the root
801 // 5) a control or form will be dropped onto a control which is _not_ a sibling (dropping onto a sibling
802 // means moving the control)
804 // collect the ancestors of the drop target (speeds up 3)
805 SvLBoxEntrySortedArray arrDropAncestors;
806 std::unique_ptr<weld::TreeIter> xLoop(m_xTreeView->make_iterator(_pTargetEntry));
809 arrDropAncestors.emplace(m_xTreeView->make_iterator(xLoop.get()));
811 while (m_xTreeView->iter_parent(*xLoop));
813 for (const auto& rCurrent : rDropped)
815 // test for 0)
816 if (m_xTreeView->iter_compare(*rCurrent, *m_xRootEntry) == 0)
817 return DND_ACTION_NONE;
819 std::unique_ptr<weld::TreeIter> xCurrentParent(m_xTreeView->make_iterator(rCurrent.get()));
820 m_xTreeView->iter_parent(*xCurrentParent);
822 // test for 1)
823 if (m_xTreeView->iter_compare(*_pTargetEntry, *xCurrentParent) == 0)
824 return DND_ACTION_NONE;
826 // test for 2)
827 if (m_xTreeView->iter_compare(*rCurrent, *_pTargetEntry) == 0)
828 return DND_ACTION_NONE;
830 // test for 5)
831 if (bDropTargetIsComponent)
832 return DND_ACTION_NONE;
834 // test for 3)
835 if (IsFormEntry(*rCurrent))
837 auto aIter = std::find_if(arrDropAncestors.begin(), arrDropAncestors.end(),
838 [this, &rCurrent](const auto& rElem) {
839 return m_xTreeView->iter_compare(*rElem, *rCurrent) == 0;
842 if ( aIter != arrDropAncestors.end() )
843 return DND_ACTION_NONE;
845 else if (IsFormComponentEntry(*rCurrent))
847 // test for 4)
848 if (m_xTreeView->iter_compare(*_pTargetEntry, *m_xRootEntry) == 0)
849 return DND_ACTION_NONE;
852 return DND_ACTION_MOVE;
855 sal_Int8 NavigatorTree::AcceptDrop( const AcceptDropEvent& rEvt )
857 ::Point aDropPos = rEvt.maPosPixel;
858 std::unique_ptr<weld::TreeIter> xDropTarget(m_xTreeView->make_iterator());
859 // get_dest_row_at_pos with false cause we must drop exactly "on" a form to paste a control into it
860 if (!m_xTreeView->get_dest_row_at_pos(aDropPos, xDropTarget.get(), false))
861 xDropTarget.reset();
862 return implAcceptDataTransfer(m_aDropTargetHelper.GetDataFlavorExVector(), rEvt.mnAction, xDropTarget.get(), true);
865 sal_Int8 NavigatorTree::implExecuteDataTransfer( const OControlTransferData& _rData, sal_Int8 _nAction, const ::Point& _rDropPos, bool _bDnD )
867 std::unique_ptr<weld::TreeIter> xDrop(m_xTreeView->make_iterator());
868 // get_dest_row_at_pos with false cause we must drop exactly "on" a form to paste a control into it
869 if (!m_xTreeView->get_dest_row_at_pos(_rDropPos, xDrop.get(), false))
870 xDrop.reset();
871 return implExecuteDataTransfer( _rData, _nAction, xDrop.get(), _bDnD );
874 sal_Int8 NavigatorTree::implExecuteDataTransfer( const OControlTransferData& _rData, sal_Int8 _nAction, const weld::TreeIter* _pTargetEntry, bool _bDnD )
876 const DataFlavorExVector& rDataFlavors = _rData.GetDataFlavorExVector();
878 if ( DND_ACTION_NONE == implAcceptDataTransfer( rDataFlavors, _nAction, _pTargetEntry, _bDnD ) )
879 // under some platforms, it may happen that ExecuteDrop is called though AcceptDrop returned DND_ACTION_NONE
880 return DND_ACTION_NONE;
882 if (!_pTargetEntry)
883 // no target -> no drop
884 return DND_ACTION_NONE;
886 // format checks
887 #ifdef DBG_UTIL
888 bool bHasHiddenControlsFormat = OControlExchange::hasHiddenControlModelsFormat( rDataFlavors );
889 bool bForeignCollection = _rData.getFormsRoot().get() != GetNavModel()->GetFormPage()->GetForms().get();
890 DBG_ASSERT(!bForeignCollection || bHasHiddenControlsFormat, "NavigatorTree::implExecuteDataTransfer: invalid format (AcceptDrop shouldn't have let this pass) !");
891 DBG_ASSERT(bForeignCollection || !m_bDragDataDirty, "NavigatorTree::implExecuteDataTransfer: invalid state (shell changed since last exchange resync) !");
892 // this should be done in AcceptDrop: the list of controls is created in _rData
893 // and m_bDragDataDirty is reset
894 #endif
896 if ( DND_ACTION_COPY == _nAction )
897 { // bHasHiddenControlsFormat means that only hidden controls are part of the data
898 #ifdef DBG_UTIL
899 DBG_ASSERT( bHasHiddenControlsFormat, "NavigatorTree::implExecuteDataTransfer: copy allowed for hidden controls only!" );
900 #endif
901 DBG_ASSERT( _pTargetEntry && m_xTreeView->iter_compare(*_pTargetEntry, *m_xRootEntry) != 0 && IsFormEntry( *_pTargetEntry ),
902 "NavigatorTree::implExecuteDataTransfer: should not be here!" );
903 // implAcceptDataTransfer should have caught both cases
905 #ifdef DBG_UTIL
906 DBG_ASSERT(bHasHiddenControlsFormat, "NavigatorTree::implExecuteDataTransfer: only copying of hidden controls is supported !");
907 // should be caught by AcceptDrop
908 #endif
910 // because i want to select all targets (and only them)
911 m_xTreeView->unselect_all();
913 const Sequence< Reference< XInterface > >& aControls = _rData.hiddenControls();
914 sal_Int32 nCount = aControls.getLength();
915 const Reference< XInterface >* pControls = aControls.getConstArray();
917 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
918 FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr;
920 // within undo
921 if (pFormModel)
923 OUString aStr(SvxResId(RID_STR_CONTROL));
924 OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr);
925 pFormModel->BegUndo(aUndoStr);
928 // copy controls
929 for (sal_Int32 i=0; i<nCount; ++i)
931 // create new control
932 FmControlData* pNewControlData = NewControl( FM_COMPONENT_HIDDEN, *_pTargetEntry, false);
933 Reference< XPropertySet > xNewPropSet( pNewControlData->GetPropertySet() );
935 // copy properties form old control to new one
936 Reference< XPropertySet > xCurrent(pControls[i], UNO_QUERY);
937 #if (OSL_DEBUG_LEVEL > 0)
938 // check whether it is a hidden control
939 sal_Int16 nClassId = ::comphelper::getINT16(xCurrent->getPropertyValue(FM_PROP_CLASSID));
940 OSL_ENSURE(nClassId == FormComponentType::HIDDENCONTROL, "NavigatorTree::implExecuteDataTransfer: invalid control in drop list !");
941 // if SVX_FM_HIDDEN_CONTROLS-format exists, the sequence
942 // should only contain hidden controls
943 #endif // (OSL_DEBUG_LEVEL > 0)
944 Reference< XPropertySetInfo > xPropInfo( xCurrent->getPropertySetInfo());
945 const Sequence< Property> seqAllCurrentProps = xPropInfo->getProperties();
946 for (Property const & currentProp : seqAllCurrentProps)
948 if (((currentProp.Attributes & PropertyAttribute::READONLY) == 0) && (currentProp.Name != FM_PROP_NAME))
949 { // (read-only attribs aren't set, ditto name,
950 // NewControl defined it uniquely
951 xNewPropSet->setPropertyValue(currentProp.Name, xCurrent->getPropertyValue(currentProp.Name));
955 std::unique_ptr<weld::TreeIter> xToSelect = FindEntry(pNewControlData);
956 m_xTreeView->select(*xToSelect);
957 if (i == 0)
958 m_xTreeView->set_cursor(*xToSelect);
961 if (pFormModel)
962 pFormModel->EndUndo();
964 return _nAction;
967 if ( !OControlExchange::hasFieldExchangeFormat( _rData.GetDataFlavorExVector() ) )
969 // can't do anything without the internal format here ... usually happens when doing DnD or CnP
970 // over navigator boundaries
971 return DND_ACTION_NONE;
974 // some data for the target
975 bool bDropTargetIsForm = IsFormEntry(*_pTargetEntry);
976 FmFormData* pTargetData = bDropTargetIsForm ? weld::fromId<FmFormData*>(m_xTreeView->get_id(*_pTargetEntry)) : nullptr;
978 DBG_ASSERT( DND_ACTION_COPY != _nAction, "NavigatorTree::implExecuteDataTransfer: somebody changed the logics!" );
980 // list of dragged entries
981 const ListBoxEntrySet& rDropped = _rData.selected();
982 DBG_ASSERT(!rDropped.empty(), "NavigatorTree::implExecuteDataTransfer: no entries!");
984 // make a copy because rDropped is updated on deleting an entry which we do in the processing loop
985 ListBoxEntrySet aDropped;
986 for (const auto& rEntry : rDropped)
987 aDropped.emplace(m_xTreeView->make_iterator(rEntry.get()));
989 // shell and model
990 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
991 FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr;
992 if (!pFormModel)
993 return DND_ACTION_NONE;
995 // for Undo
996 const bool bUndo = pFormModel->IsUndoEnabled();
998 if( bUndo )
1000 OUString strUndoDescription(SvxResId(RID_STR_UNDO_CONTAINER_REPLACE));
1001 pFormModel->BegUndo(strUndoDescription);
1004 // remove selection before adding an entry, so the mark doesn't flicker
1005 // -> lock action of selection
1006 LockSelectionHandling();
1008 // go through all dropped entries
1009 for ( ListBoxEntrySet::const_iterator dropped = aDropped.begin();
1010 dropped != aDropped.end();
1011 ++dropped
1014 bool bFirstEntry = aDropped.begin() == dropped;
1016 // some data of the current element
1017 const auto& rCurrent = *dropped;
1018 DBG_ASSERT(rCurrent, "NavigatorTree::implExecuteDataTransfer: invalid entry");
1019 DBG_ASSERT(m_xTreeView->get_iter_depth(*rCurrent) != 0, "NavigatorTree::implExecuteDataTransfer: invalid entry");
1020 // don't drag root
1022 FmEntryData* pCurrentUserData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rCurrent));
1024 Reference< XChild > xCurrentChild = pCurrentUserData->GetChildIFace();
1025 Reference< XIndexContainer > xContainer(xCurrentChild->getParent(), UNO_QUERY);
1027 FmFormData* pCurrentParentUserData = static_cast<FmFormData*>(pCurrentUserData->GetParent());
1028 DBG_ASSERT(pCurrentParentUserData == nullptr || dynamic_cast<const FmFormData*>(pCurrentUserData->GetParent()) != nullptr, "NavigatorTree::implExecuteDataTransfer: invalid parent");
1030 // remove from parent
1031 if (pCurrentParentUserData)
1032 pCurrentParentUserData->GetChildList()->removeNoDelete( pCurrentUserData );
1033 else
1034 GetNavModel()->GetRootList()->removeNoDelete( pCurrentUserData );
1036 // remove from container
1037 sal_Int32 nIndex = getElementPos(xContainer, xCurrentChild);
1038 GetNavModel()->m_pPropChangeList->Lock();
1039 // UndoAction for removal
1040 if ( bUndo && GetNavModel()->m_pPropChangeList->CanUndo())
1042 pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*pFormModel, FmUndoContainerAction::Removed,
1043 xContainer, xCurrentChild, nIndex));
1045 else if( !GetNavModel()->m_pPropChangeList->CanUndo() )
1047 FmUndoContainerAction::DisposeElement( xCurrentChild );
1050 // copy events
1051 Reference< XEventAttacherManager > xManager(xContainer, UNO_QUERY);
1052 Sequence< ScriptEventDescriptor > aEvts;
1054 if (xManager.is() && nIndex >= 0)
1055 aEvts = xManager->getScriptEvents(nIndex);
1056 xContainer->removeByIndex(nIndex);
1058 // remove selection
1059 m_xTreeView->unselect(*rCurrent);
1060 // and delete it
1061 Remove(pCurrentUserData);
1063 // position in DropParents, where to insert dropped entries
1064 if (pTargetData)
1065 xContainer.set(pTargetData->GetElement(), UNO_QUERY);
1066 else
1067 xContainer = GetNavModel()->GetForms();
1069 // always insert at the end
1070 nIndex = xContainer->getCount();
1072 // UndoAction for insertion
1073 if ( bUndo && GetNavModel()->m_pPropChangeList->CanUndo())
1074 pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*pFormModel, FmUndoContainerAction::Inserted,
1075 xContainer, xCurrentChild, nIndex));
1077 // insert in new container
1078 if (pTargetData)
1080 // insert in a form needs a FormComponent
1081 xContainer->insertByIndex( nIndex,
1082 Any( Reference< XFormComponent >( xCurrentChild, UNO_QUERY ) ) );
1084 else
1086 xContainer->insertByIndex( nIndex,
1087 Any( Reference< XForm >( xCurrentChild, UNO_QUERY ) ) );
1090 if (aEvts.hasElements())
1092 xManager.set(xContainer, UNO_QUERY);
1093 if (xManager.is())
1094 xManager->registerScriptEvents(nIndex, aEvts);
1097 GetNavModel()->m_pPropChangeList->UnLock();
1099 // give an entry the new parent
1100 pCurrentUserData->SetParent(pTargetData);
1102 // give parent the new child
1103 if (pTargetData)
1104 pTargetData->GetChildList()->insert( std::unique_ptr<FmEntryData>(pCurrentUserData), nIndex );
1105 else
1106 GetNavModel()->GetRootList()->insert( std::unique_ptr<FmEntryData>(pCurrentUserData), nIndex );
1108 // announce to myself and reselect
1109 std::unique_ptr<weld::TreeIter> xNew = Insert( pCurrentUserData, nIndex );
1110 if (bFirstEntry && xNew)
1112 if (m_xTreeView->iter_parent(*xNew))
1113 m_xTreeView->expand_row(*xNew);
1117 UnlockSelectionHandling();
1119 if( bUndo )
1120 pFormModel->EndUndo();
1122 // During the move, the markings of the underlying view did not change (because the view is not affected by the logical
1123 // hierarchy of the form/control models. But my selection changed - which means I have to adjust it according to the
1124 // view marks, again.
1125 SynchronizeSelection();
1127 // in addition, with the move of controls such things as "the current form" may have changed - force the shell
1128 // to update itself accordingly
1129 if( pFormShell && pFormShell->GetImpl() && pFormShell->GetFormView() )
1130 pFormShell->GetImpl()->DetermineSelection_Lock( pFormShell->GetFormView()->GetMarkedObjectList() );
1132 if ( m_aControlExchange.isClipboardOwner() && ( DND_ACTION_MOVE == _nAction ) )
1133 m_aControlExchange->clear();
1135 return _nAction;
1138 sal_Int8 NavigatorTree::ExecuteDrop( const ExecuteDropEvent& rEvt )
1140 sal_Int8 nResult( DND_ACTION_NONE );
1141 if ( m_aControlExchange.isDragSource() )
1142 nResult = implExecuteDataTransfer( *m_aControlExchange, rEvt.mnAction, rEvt.maPosPixel, true );
1143 else
1145 OControlTransferData aDroppedData( rEvt.maDropEvent.Transferable );
1146 nResult = implExecuteDataTransfer( aDroppedData, rEvt.mnAction, rEvt.maPosPixel, true );
1148 return nResult;
1151 void NavigatorTree::doPaste()
1153 std::unique_ptr<weld::TreeIter> xSelected(m_xTreeView->make_iterator());
1154 if (!m_xTreeView->get_selected(xSelected.get()))
1155 xSelected.reset();
1159 if ( m_aControlExchange.isClipboardOwner() )
1161 implExecuteDataTransfer( *m_aControlExchange, doingKeyboardCut( ) ? DND_ACTION_MOVE : DND_ACTION_COPY, xSelected.get(), false );
1163 else
1165 // the clipboard content
1166 Reference< XClipboard > xClipboard(m_xTreeView->get_clipboard());
1167 Reference< XTransferable > xTransferable;
1168 if ( xClipboard.is() )
1169 xTransferable = xClipboard->getContents();
1171 OControlTransferData aClipboardContent( xTransferable );
1172 implExecuteDataTransfer( aClipboardContent, DND_ACTION_COPY, xSelected.get(), false );
1175 catch( const Exception& )
1177 TOOLS_WARN_EXCEPTION( "svx", "NavigatorTree::doPaste" );
1181 void NavigatorTree::doCopy()
1183 if ( implPrepareExchange( DND_ACTION_COPY ) )
1185 m_aControlExchange.setClipboardListener( LINK( this, NavigatorTree, OnClipboardAction ) );
1186 m_aControlExchange.copyToClipboard(*m_xTreeView);
1190 void NavigatorTree::ModelHasRemoved(const weld::TreeIter* pTypedEntry)
1192 if (doingKeyboardCut())
1194 auto aIter = std::find_if(m_aCutEntries.begin(), m_aCutEntries.end(),
1195 [this, pTypedEntry](const auto& rElem) {
1196 return m_xTreeView->iter_compare(*rElem, *pTypedEntry) == 0;
1198 if (aIter != m_aCutEntries.end())
1199 m_aCutEntries.erase(aIter);
1202 if (m_aControlExchange.isDataExchangeActive())
1204 if (0 == m_aControlExchange->onEntryRemoved(m_xTreeView.get(), pTypedEntry))
1206 // last of the entries which we put into the clipboard has been deleted from the tree.
1207 // Give up the clipboard ownership.
1208 m_aControlExchange.clear();
1213 void NavigatorTree::doCut()
1215 if ( !implPrepareExchange( DND_ACTION_MOVE ) )
1216 return;
1218 m_aControlExchange.setClipboardListener( LINK( this, NavigatorTree, OnClipboardAction ) );
1219 m_aControlExchange.copyToClipboard(*m_xTreeView);
1220 m_bKeyboardCut = true;
1222 // mark all the entries we just "cut" into the clipboard as "nearly moved"
1223 for (const auto& rEntry : m_arrCurrentSelection )
1225 if (!rEntry)
1226 continue;
1227 m_aCutEntries.emplace(m_xTreeView->make_iterator(rEntry.get()));
1228 m_xTreeView->set_sensitive(*rEntry, false);
1232 IMPL_LINK(NavigatorTree, KeyInputHdl, const ::KeyEvent&, rKEvt, bool)
1234 const vcl::KeyCode& rCode = rKEvt.GetKeyCode();
1236 // delete?
1237 if (rCode.GetCode() == KEY_DELETE && !rCode.GetModifier())
1239 DeleteSelection();
1240 return true;
1243 // copy'n'paste?
1244 switch ( rCode.GetFunction() )
1246 case KeyFuncType::CUT:
1247 doCut();
1248 break;
1250 case KeyFuncType::PASTE:
1251 if ( implAcceptPaste() )
1252 doPaste();
1253 break;
1255 case KeyFuncType::COPY:
1256 doCopy();
1257 break;
1259 default:
1260 break;
1263 return false;
1266 IMPL_LINK(NavigatorTree, EditingEntryHdl, const weld::TreeIter&, rIter, bool)
1268 // root, which isn't allowed to be renamed, has UserData=NULL
1269 m_bEditing = !m_xTreeView->get_id(rIter).isEmpty();
1270 return m_bEditing;
1273 void NavigatorTree::NewForm(const weld::TreeIter& rParentEntry)
1275 // get ParentFormData
1276 if (!IsFormEntry(rParentEntry))
1277 return;
1279 FmFormData* pParentFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(rParentEntry));
1282 // create new form
1283 const Reference<XComponentContext>& xContext = comphelper::getProcessComponentContext();
1284 Reference< XForm > xNewForm(xContext->getServiceManager()->createInstanceWithContext(FM_SUN_COMPONENT_FORM, xContext), UNO_QUERY);
1285 if (!xNewForm.is())
1286 return;
1288 Reference< XPropertySet > xPropertySet(xNewForm, UNO_QUERY);
1289 if (!xPropertySet.is())
1290 return;
1292 FmFormData* pNewFormData = new FmFormData(xNewForm, pParentFormData);
1295 // set name
1296 OUString aName = GenerateName(*pNewFormData);
1297 pNewFormData->SetText(aName);
1301 xPropertySet->setPropertyValue( FM_PROP_NAME, Any(aName) );
1302 // a form should always have the command type table as default
1303 xPropertySet->setPropertyValue( FM_PROP_COMMANDTYPE, Any(sal_Int32(CommandType::TABLE)));
1305 catch ( const Exception& )
1307 OSL_FAIL("NavigatorTree::NewForm : could not set essential properties!");
1311 // insert form
1312 GetNavModel()->Insert(pNewFormData, SAL_MAX_UINT32, true);
1315 // set new form as active
1316 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1317 if( pFormShell )
1319 InterfaceBag aSelection;
1320 aSelection.insert( Reference<XInterface>( xNewForm, UNO_QUERY ) );
1321 pFormShell->GetImpl()->setCurrentSelection_Lock(std::move(aSelection));
1323 pFormShell->GetViewShell()->GetViewFrame().GetBindings().Invalidate(SID_FM_PROPERTIES, true, true);
1325 GetNavModel()->SetModified();
1327 // switch to EditMode
1328 std::unique_ptr<weld::TreeIter> xNewEntry = FindEntry(pNewFormData);
1329 m_xTreeView->start_editing(*xNewEntry);
1330 m_bEditing = true;
1333 FmControlData* NavigatorTree::NewControl(const OUString& rServiceName, const weld::TreeIter& rParentEntry, bool bEditName)
1335 // get ParentForm
1336 if (!GetNavModel()->GetFormShell())
1337 return nullptr;
1338 if (!IsFormEntry(rParentEntry))
1339 return nullptr;
1341 FmFormData* pParentFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(rParentEntry));
1342 Reference<XForm> xParentForm(pParentFormData->GetFormIface());
1344 // create new component
1345 const Reference<XComponentContext>& xContext = comphelper::getProcessComponentContext();
1346 Reference<XFormComponent> xNewComponent( xContext->getServiceManager()->createInstanceWithContext(rServiceName, xContext), UNO_QUERY);
1347 if (!xNewComponent.is())
1348 return nullptr;
1350 FmControlData* pNewFormControlData = new FmControlData(xNewComponent, pParentFormData);
1352 // set name
1353 OUString sName = FmFormPageImpl::setUniqueName( xNewComponent, xParentForm );
1355 pNewFormControlData->SetText( sName );
1357 // insert FormComponent
1358 GetNavModel()->Insert(pNewFormControlData, SAL_MAX_UINT32, true);
1359 GetNavModel()->SetModified();
1361 if (bEditName)
1363 // switch to EditMode
1364 std::unique_ptr<weld::TreeIter> xNewEntry = FindEntry( pNewFormControlData );
1365 m_xTreeView->select(*xNewEntry);
1367 m_xTreeView->start_editing(*xNewEntry);
1368 m_bEditing = true;
1371 return pNewFormControlData;
1374 OUString NavigatorTree::GenerateName(const FmEntryData& rEntryData)
1376 const sal_uInt16 nMaxCount = 99;
1377 OUString aNewName;
1379 // create base name
1380 OUString aBaseName;
1381 if( dynamic_cast<const FmFormData*>(&rEntryData) != nullptr )
1382 aBaseName = SvxResId( RID_STR_STDFORMNAME );
1383 else if( dynamic_cast<const FmControlData*>(&rEntryData) != nullptr )
1384 aBaseName = SvxResId( RID_STR_CONTROL );
1387 // create new name
1388 FmFormData* pFormParentData = static_cast<FmFormData*>(rEntryData.GetParent());
1390 for( sal_Int32 i=0; i<nMaxCount; i++ )
1392 aNewName = aBaseName;
1393 if( i>0 )
1395 aNewName += " " + OUString::number(i);
1398 if( GetNavModel()->FindData(aNewName, pFormParentData,false) == nullptr )
1399 break;
1402 return aNewName;
1405 IMPL_LINK(NavigatorTree, EditedEntryHdl, const IterString&, rIterString, bool)
1407 m_bEditing = false;
1409 const weld::TreeIter& rIter = rIterString.first;
1411 FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rIter));
1412 bool bRes = NavigatorTreeModel::Rename(pEntryData, rIterString.second);
1413 if (!bRes)
1415 m_xEditEntry = m_xTreeView->make_iterator(&rIter);
1416 nEditEvent = Application::PostUserEvent(LINK(this, NavigatorTree, OnEdit));
1419 return bRes;
1422 IMPL_LINK_NOARG(NavigatorTree, OnEdit, void*, void)
1424 nEditEvent = nullptr;
1425 m_xTreeView->start_editing(*m_xEditEntry);
1426 m_bEditing = true;
1427 m_xEditEntry.reset();
1430 IMPL_LINK_NOARG(NavigatorTree, OnEntrySelDesel, weld::TreeView&, void)
1432 m_sdiState = SDI_DIRTY;
1434 if (IsSelectionHandlingLocked())
1435 return;
1437 if (m_aSynchronizeTimer.IsActive())
1438 m_aSynchronizeTimer.Stop();
1440 m_aSynchronizeTimer.SetTimeout(EXPLORER_SYNC_DELAY);
1441 m_aSynchronizeTimer.Start();
1444 IMPL_LINK_NOARG(NavigatorTree, OnSynchronizeTimer, Timer *, void)
1446 SynchronizeMarkList();
1449 IMPL_LINK_NOARG(NavigatorTree, OnClipboardAction, OLocalExchange&, void)
1451 if ( m_aControlExchange.isClipboardOwner() )
1452 return;
1454 if ( !doingKeyboardCut() )
1455 return;
1457 for (const auto& rEntry : m_aCutEntries)
1459 if (!rEntry)
1460 continue;
1461 m_xTreeView->set_sensitive(*rEntry, true);
1463 ListBoxEntrySet().swap(m_aCutEntries);
1465 m_bKeyboardCut = false;
1468 void NavigatorTree::ShowSelectionProperties(bool bForce)
1470 // at first i need the FormShell
1471 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1472 if (!pFormShell)
1473 // no shell -> impossible to set curObject -> leave
1474 return;
1476 CollectSelectionData(SDI_ALL);
1477 SAL_WARN_IF(static_cast<size_t>(m_nFormsSelected + m_nControlsSelected
1478 + (m_bRootSelected ? 1 : 0)) != m_arrCurrentSelection.size(),
1479 "svx.form",
1480 "NavigatorTree::ShowSelectionProperties : selection meta data invalid !");
1483 InterfaceBag aSelection;
1484 bool bSetSelectionAsMarkList = false;
1486 if (m_bRootSelected)
1487 ; // no properties for the root, neither for single nor for multi selection
1488 else if ( m_nFormsSelected + m_nControlsSelected == 0 ) // none of the two should be less 0
1489 ; // no selection -> no properties
1490 else if ( m_nFormsSelected * m_nControlsSelected != 0 )
1491 ; // mixed selection -> no properties
1492 else
1493 { // either only forms, or only controls are selected
1494 if (m_arrCurrentSelection.size() == 1)
1496 const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin();
1497 if (m_nFormsSelected > 0)
1498 { // exactly one form is selected
1499 FmFormData* pFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(*rIter));
1500 aSelection.insert( Reference< XInterface >( pFormData->GetFormIface(), UNO_QUERY ) );
1502 else
1503 { // exactly one control is selected (whatever hidden or normal)
1504 FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rIter));
1506 aSelection.insert( Reference< XInterface >( pEntryData->GetElement(), UNO_QUERY ) );
1509 else
1510 { // it's a MultiSelection, so we must build a MultiSet
1511 if (m_nFormsSelected > 0)
1512 { // ... only forms
1513 // first of all collect PropertySet-Interfaces of the forms
1514 SvLBoxEntrySortedArray::const_iterator it = m_arrCurrentSelection.begin();
1515 for ( sal_Int32 i = 0; i < m_nFormsSelected; ++i )
1517 const std::unique_ptr<weld::TreeIter>& rIter = *it;
1518 FmFormData* pFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(*rIter));
1519 aSelection.insert( pFormData->GetPropertySet() );
1520 ++it;
1523 else
1524 { // ... only controls
1525 if (m_nHiddenControls == m_nControlsSelected)
1526 { // a MultiSet for properties of hidden controls
1527 SvLBoxEntrySortedArray::const_iterator it = m_arrCurrentSelection.begin();
1528 for ( sal_Int32 i = 0; i < m_nHiddenControls; ++i )
1530 const std::unique_ptr<weld::TreeIter>& rIter = *it;
1531 FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rIter));
1532 aSelection.insert( pEntryData->GetPropertySet() );
1533 ++it;
1536 else if (m_nHiddenControls == 0)
1537 { // only normal controls
1538 bSetSelectionAsMarkList = true;
1545 // and now my form and my SelObject
1546 if ( bSetSelectionAsMarkList )
1547 pFormShell->GetImpl()->setCurrentSelectionFromMark_Lock(pFormShell->GetFormView()->GetMarkedObjectList());
1548 else
1549 pFormShell->GetImpl()->setCurrentSelection_Lock(std::move(aSelection));
1551 if (pFormShell->GetImpl()->IsPropBrwOpen_Lock() || bForce)
1553 // and now deliver all to the PropertyBrowser
1554 pFormShell->GetViewShell()->GetViewFrame().GetDispatcher()->Execute( SID_FM_SHOW_PROPERTY_BROWSER, SfxCallMode::ASYNCHRON );
1559 void NavigatorTree::DeleteSelection()
1561 // of course, i can't delete root
1562 bool bRootSelected = m_xTreeView->is_selected(*m_xRootEntry);
1563 auto nSelectedEntries = m_xTreeView->count_selected_rows();
1564 if (bRootSelected && (nSelectedEntries > 1)) // root and other elements ?
1565 m_xTreeView->unselect(*m_xRootEntry); // yes -> remove root from selection
1567 if ((nSelectedEntries == 0) || bRootSelected) // still root ?
1568 return; // -> only selected element -> leave
1570 DBG_ASSERT(!m_bPrevSelectionMixed, "NavigatorTree::DeleteSelection() : delete permitted if mark and selection are inconsistent");
1572 // i need the FormModel later
1573 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1574 if (!pFormShell)
1575 return;
1576 FmFormModel* pFormModel = pFormShell->GetFormModel();
1577 if (!pFormModel)
1578 return;
1580 // now I have to safeguard the DeleteList: if you delete a form and a dependent element
1581 // - in this order - than the SvLBoxEntryPtr of the dependent element is already invalid,
1582 // when it should be deleted... you have to prohibit this GPF, that of course would happen,
1583 // so I take the 'normalized' list
1584 CollectSelectionData( SDI_NORMALIZED );
1586 // see below for why we need this mapping from models to shapes
1587 FmFormView* pFormView = pFormShell->GetFormView();
1588 SdrPageView* pPageView = pFormView ? pFormView->GetSdrPageView() : nullptr;
1589 SdrPage* pPage = pPageView ? pPageView->GetPage() : nullptr;
1590 DBG_ASSERT( pPage, "NavigatorTree::DeleteSelection: invalid form page!" );
1592 MapModelToShape aModelShapes;
1593 if ( pPage )
1594 collectShapeModelMapping( pPage, aModelShapes );
1596 // problem: we have to use ExplorerModel::Remove, since only this one properly deletes Form objects.
1597 // But, the controls themself must be deleted via DeleteMarked (else, the Writer has some problems
1598 // somewhere). In case I'd first delete the structure, then the controls, the UNDO would not work
1599 // (since UNDO then would mean to first restore the controls, then the structure, means their parent
1600 // form). The other way round, the EntryDatas would be invalid, if I'd first delete the controls and
1601 // then go on to the structure. This means I have to delete the forms *after* the normal controls, so
1602 // that during UNDO, they're restored in the proper order.
1603 pFormShell->GetImpl()->EnableTrackProperties_Lock(false);
1604 for (SvLBoxEntrySortedArray::reverse_iterator it = m_arrCurrentSelection.rbegin();
1605 it != m_arrCurrentSelection.rend(); )
1607 const std::unique_ptr<weld::TreeIter>& rIter = *it;
1608 FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rIter));
1610 // a form ?
1611 auto pFormData = dynamic_cast<FmFormData*>(pCurrent);
1613 // because deletion is done by the view, and i build on its MarkList,
1614 // but normally only direct controls, no indirect ones, are marked in a marked form,
1615 // I have to do it later
1616 if (pFormData)
1617 MarkViewObj(pFormData, true/*deep*/);
1619 // a hidden control ?
1620 bool bIsHidden = IsHiddenControl(pCurrent);
1622 // keep forms and hidden controls, the rest not
1623 if (!pFormData && !bIsHidden)
1625 // well, no form and no hidden control -> we can remove it from m_arrCurrentSelection, as it will
1626 // be deleted automatically. This is because for every model (except forms and hidden control models)
1627 // there exist a shape, which is marked _if_and_only_if_ the model is selected in our tree.
1628 if ( aModelShapes.find( pCurrent->GetElement() ) != aModelShapes.end() )
1630 // if there's a shape for the current entry, then either it is marked or it is in a
1631 // hidden layer (#i28502#), or something like this.
1632 // In the first case, it will be deleted below, in the second case, we currently don't
1633 // delete it, as there's no real (working!) API for this, neither in UNO nor in non-UNO.
1634 m_arrCurrentSelection.erase( --(it.base()) );
1636 else
1637 ++it;
1638 // In case there is no shape for the current entry, we keep the entry in m_arrCurrentSelection,
1639 // since then we can definitely remove it.
1641 else
1642 ++it;
1644 pFormShell->GetImpl()->EnableTrackProperties_Lock(true);
1646 // let the view delete the marked controls
1647 pFormShell->GetFormView()->DeleteMarked();
1649 // start UNDO at this point. Unfortunately, this results in 2 UNDO actions, since DeleteMarked is
1650 // creating an own one. However, if we'd move it before DeleteMarked, Writer does not really like
1651 // this ... :(
1652 // #i31038#
1655 // initialize UNDO
1656 OUString aUndoStr;
1657 if ( m_arrCurrentSelection.size() == 1 )
1659 aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_REMOVE);
1660 if (m_nFormsSelected)
1661 aUndoStr = aUndoStr.replaceFirst( "#", SvxResId( RID_STR_FORM ) );
1662 else
1663 // it must be a control (else the root would be selected, but it cannot be deleted)
1664 aUndoStr = aUndoStr.replaceFirst( "#", SvxResId( RID_STR_CONTROL ) );
1666 else
1668 aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_REMOVE_MULTIPLE);
1669 aUndoStr = aUndoStr.replaceFirst( "#", OUString::number( m_arrCurrentSelection.size() ) );
1671 pFormModel->BegUndo(aUndoStr);
1674 // remove remaining structure
1675 for (const auto& rpSelection : m_arrCurrentSelection)
1677 FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rpSelection));
1679 // if the entry still has children, we skipped deletion of one of those children.
1680 // This may for instance be because the shape is in a hidden layer, where we're unable
1681 // to remove it
1682 if ( pCurrent->GetChildList()->size() )
1683 continue;
1685 // one remaining subtle problem, before deleting it : if it's a form and the shell
1686 // knows it as CurrentObject, I have to tell it something else
1687 if (auto pFormData = dynamic_cast<FmFormData*>( pCurrent))
1689 Reference< XForm > xCurrentForm( pFormData->GetFormIface() );
1690 if (pFormShell->GetImpl()->getCurrentForm_Lock() == xCurrentForm) // shell knows form to be deleted ?
1691 pFormShell->GetImpl()->forgetCurrentForm_Lock(); // -> take away ...
1693 GetNavModel()->Remove(pCurrent, true);
1695 pFormModel->EndUndo();
1699 void NavigatorTree::CollectSelectionData(SELDATA_ITEMS sdiHow)
1701 DBG_ASSERT(sdiHow != SDI_DIRTY, "NavigatorTree::CollectSelectionData : ever thought about your parameter ? DIRTY ?");
1702 if (sdiHow == m_sdiState)
1703 return;
1705 m_arrCurrentSelection.clear();
1706 m_nFormsSelected = m_nControlsSelected = m_nHiddenControls = 0;
1707 m_bRootSelected = false;
1709 m_xTreeView->selected_foreach([this, sdiHow](weld::TreeIter& rSelectionLoop){
1710 // count different elements
1711 if (m_xTreeView->iter_compare(rSelectionLoop, *m_xRootEntry) == 0)
1712 m_bRootSelected = true;
1713 else
1715 if (IsFormEntry(rSelectionLoop))
1716 ++m_nFormsSelected;
1717 else
1719 ++m_nControlsSelected;
1720 if (IsHiddenControl(weld::fromId<FmEntryData*>(m_xTreeView->get_id(rSelectionLoop))))
1721 ++m_nHiddenControls;
1725 if (sdiHow == SDI_NORMALIZED)
1727 // don't take something with a selected ancestor
1728 if (m_xTreeView->iter_compare(rSelectionLoop, *m_xRootEntry) == 0)
1729 m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop));
1730 else
1732 std::unique_ptr<weld::TreeIter> xParentLoop(m_xTreeView->make_iterator(&rSelectionLoop));
1733 bool bParentLoop = m_xTreeView->iter_parent(*xParentLoop);
1734 while (bParentLoop)
1736 // actually i would have to test, if parent is part of m_arr_CurrentSelection ...
1737 // but if it's selected, then it's in m_arrCurrentSelection
1738 // or one of its ancestors, which was selected earlier.
1739 // In both cases IsSelected is enough
1740 if (m_xTreeView->is_selected(*xParentLoop))
1741 break;
1742 else
1744 if (m_xTreeView->iter_compare(*xParentLoop, *m_xRootEntry) == 0)
1746 // until root (exclusive), there was no selected parent -> entry belongs to normalized list
1747 m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop));
1748 break;
1750 else
1751 bParentLoop = m_xTreeView->iter_parent(*xParentLoop);
1756 else if (sdiHow == SDI_NORMALIZED_FORMARK)
1758 std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(&rSelectionLoop));
1759 bool bParent = m_xTreeView->iter_parent(*xParent);
1760 if (!bParent || !m_xTreeView->is_selected(*xParent) || IsFormEntry(rSelectionLoop))
1761 m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop));
1763 else
1764 m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop));
1766 return false;
1769 m_sdiState = sdiHow;
1772 void NavigatorTree::SynchronizeSelection(FmEntryDataArray& arredToSelect)
1774 LockSelectionHandling();
1775 if (arredToSelect.empty())
1777 m_xTreeView->unselect_all();
1779 else
1781 // compare current selection with requested SelectList
1782 m_xTreeView->selected_foreach([this, &arredToSelect](weld::TreeIter& rSelection) {
1783 FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rSelection));
1784 if (pCurrent != nullptr)
1786 FmEntryDataArray::iterator it = arredToSelect.find(pCurrent);
1787 if ( it != arredToSelect.end() )
1788 { // entry already selected, but also in SelectList
1789 // remove it from there
1790 arredToSelect.erase(it);
1791 } else
1792 { // entry selected, but not in SelectList -> remove selection
1793 m_xTreeView->unselect(rSelection);
1794 // make it visible (maybe it's the only modification i do in this handler
1795 // so you should see it
1796 m_xTreeView->scroll_to_row(rSelection);
1799 else
1800 m_xTreeView->unselect(rSelection);
1802 return false;
1805 // now SelectList contains only entries, which have to be selected
1806 // two possibilities : 1) run through SelectList, get SvTreeListEntry for every entry and select it (is more intuitive)
1807 // 2) run through my SvLBoxEntries and select those, i can find in the SelectList
1808 // 1) needs =(k*n) (k=length of SelectList, n=number of entries),
1809 // plus the fact, that FindEntry uses extensive IsEqualWithoutChilden instead of comparing pointer to UserData
1810 // 2) needs =(n*log k), duplicates some code from FindEntry
1811 // This may be a frequently used code ( at every change in mark of the view!),
1812 // so i use latter one
1813 m_xTreeView->all_foreach([this, &arredToSelect](weld::TreeIter& rLoop){
1814 FmEntryData* pCurEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rLoop));
1815 FmEntryDataArray::iterator it = arredToSelect.find(pCurEntryData);
1816 if (it != arredToSelect.end())
1818 m_xTreeView->select(rLoop);
1819 m_xTreeView->scroll_to_row(rLoop);
1822 return false;
1825 UnlockSelectionHandling();
1829 void NavigatorTree::SynchronizeSelection()
1831 // shell and view
1832 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1833 if(!pFormShell) return;
1835 FmFormView* pFormView = pFormShell->GetFormView();
1836 if (!pFormView) return;
1838 GetNavModel()->BroadcastMarkedObjects(pFormView->GetMarkedObjectList());
1842 void NavigatorTree::SynchronizeMarkList()
1844 // i'll need this shell
1845 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1846 if (!pFormShell) return;
1848 CollectSelectionData(SDI_NORMALIZED_FORMARK);
1850 // the view shouldn't notify now if MarkList changed
1851 pFormShell->GetImpl()->EnableTrackProperties_Lock(false);
1853 UnmarkAllViewObj();
1855 for (auto& rSelectionLoop : m_arrCurrentSelection)
1857 // When form selection, mark all controls of form
1858 if (IsFormEntry(*rSelectionLoop) && m_xTreeView->iter_compare(*rSelectionLoop, *m_xRootEntry) != 0)
1859 MarkViewObj(weld::fromId<FmFormData*>(m_xTreeView->get_id(*rSelectionLoop)), false/*deep*/);
1861 // When control selection, mark Control-SdrObjects
1862 else if (IsFormComponentEntry(*rSelectionLoop))
1864 FmControlData* pControlData = weld::fromId<FmControlData*>(m_xTreeView->get_id(*rSelectionLoop));
1865 if (pControlData)
1868 // When HiddenControl no object can be selected
1869 Reference< XFormComponent > xFormComponent( pControlData->GetFormComponent());
1870 if (!xFormComponent.is())
1871 continue;
1872 Reference< XPropertySet > xSet(xFormComponent, UNO_QUERY);
1873 if (!xSet.is())
1874 continue;
1876 sal_uInt16 nClassId = ::comphelper::getINT16(xSet->getPropertyValue(FM_PROP_CLASSID));
1877 if (nClassId != FormComponentType::HIDDENCONTROL)
1878 MarkViewObj(pControlData);
1883 // if PropertyBrowser is open, I have to adopt it according to my selection
1884 // (Not as MarkList of view : if a form is selected, all belonging controls are selected in the view
1885 // but of course i want to see the form-properties
1886 ShowSelectionProperties();
1888 // reset flag at view
1889 pFormShell->GetImpl()->EnableTrackProperties_Lock(true);
1891 // if exactly one form is selected now, shell should notice it as CurrentForm
1892 // (if selection handling isn't locked, view cares about it in MarkListHasChanged
1893 // but mechanism doesn't work, if form is empty for example
1894 if ((m_arrCurrentSelection.size() != 1) || (m_nFormsSelected != 1))
1895 return;
1897 std::unique_ptr<weld::TreeIter> xSelected(m_xTreeView->make_iterator());
1898 if (!m_xTreeView->get_selected(xSelected.get()))
1899 xSelected.reset();
1900 FmFormData* pSingleSelectionData = xSelected ? dynamic_cast<FmFormData*>(weld::fromId<FmEntryData*>(m_xTreeView->get_id(*xSelected)))
1901 : nullptr;
1902 DBG_ASSERT( pSingleSelectionData, "NavigatorTree::SynchronizeMarkList: invalid selected form!" );
1903 if ( pSingleSelectionData )
1905 InterfaceBag aSelection;
1906 aSelection.insert( Reference< XInterface >( pSingleSelectionData->GetFormIface(), UNO_QUERY ) );
1907 pFormShell->GetImpl()->setCurrentSelection_Lock(std::move(aSelection));
1911 bool NavigatorTree::IsHiddenControl(FmEntryData const * pEntryData)
1913 if (pEntryData == nullptr) return false;
1915 const Reference< XPropertySet >& xProperties( pEntryData->GetPropertySet() );
1916 if (::comphelper::hasProperty(FM_PROP_CLASSID, xProperties))
1918 Any aClassID = xProperties->getPropertyValue( FM_PROP_CLASSID );
1919 return (::comphelper::getINT16(aClassID) == FormComponentType::HIDDENCONTROL);
1921 return false;
1924 void NavigatorTree::UnmarkAllViewObj()
1926 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1927 if( !pFormShell )
1928 return;
1929 FmFormView* pFormView = pFormShell->GetFormView();
1930 pFormView->UnMarkAll();
1933 void NavigatorTree::MarkViewObj(FmFormData const * pFormData, bool bDeep )
1935 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1936 if( !pFormShell )
1937 return;
1939 // first collect all sdrobjects
1940 ::std::set< Reference< XFormComponent > > aObjects;
1941 CollectObjects(pFormData,bDeep,aObjects);
1944 // find and select appropriate SdrObj in page
1945 FmFormView* pFormView = pFormShell->GetFormView();
1946 SdrPageView* pPageView = pFormView->GetSdrPageView();
1947 SdrPage* pPage = pPageView->GetPage();
1948 //FmFormPage* pFormPage = dynamic_cast< FmFormPage* >( pPage );
1950 SdrObjListIter aIter( pPage );
1951 while ( aIter.IsMore() )
1953 SdrObject* pSdrObject = aIter.Next();
1954 FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject );
1955 if ( !pFormObject )
1956 continue;
1958 Reference< XFormComponent > xControlModel( pFormObject->GetUnoControlModel(),UNO_QUERY );
1959 if ( xControlModel.is() && aObjects.find(xControlModel) != aObjects.end() && !pFormView->IsObjMarked( pSdrObject ) )
1961 // unfortunately, the writer doesn't like marking an already-marked object, again, so reset the mark first
1962 pFormView->MarkObj( pSdrObject, pPageView );
1964 } // while ( aIter.IsMore() )
1965 // make the mark visible
1966 ::tools::Rectangle aMarkRect( pFormView->GetAllMarkedRect());
1967 for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i )
1969 SdrPaintWindow* pPaintWindow = pFormView->GetPaintWindow( i );
1970 OutputDevice& rOutDev = pPaintWindow->GetOutputDevice();
1971 if ( ( OUTDEV_WINDOW == rOutDev.GetOutDevType() ) && !aMarkRect.IsEmpty() )
1973 pFormView->MakeVisible( aMarkRect, *rOutDev.GetOwnerWindow() );
1975 } // for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i )
1978 void NavigatorTree::CollectObjects(FmFormData const * pFormData, bool bDeep, ::std::set< Reference< XFormComponent > >& _rObjects)
1980 FmEntryDataList* pChildList = pFormData->GetChildList();
1981 for( size_t i = 0; i < pChildList->size(); ++i )
1983 FmEntryData* pEntryData = pChildList->at( i );
1984 if( auto pControlData = dynamic_cast<FmControlData*>( pEntryData) )
1986 _rObjects.insert(pControlData->GetFormComponent());
1987 } // if( dynamic_cast<const FmControlData*>( pEntryData) != nullptr )
1988 else if (bDeep)
1989 if (auto pEntryFormData = dynamic_cast<FmFormData*>( pEntryData))
1990 CollectObjects(pEntryFormData, bDeep, _rObjects);
1991 } // for( sal_uInt32 i=0; i<pChildList->Count(); i++ )
1994 void NavigatorTree::MarkViewObj( FmControlData const * pControlData)
1996 if( !pControlData )
1997 return;
1998 FmFormShell* pFormShell = GetNavModel()->GetFormShell();
1999 if( !pFormShell )
2000 return;
2003 // find and select appropriate SdrObj
2004 FmFormView* pFormView = pFormShell->GetFormView();
2005 const Reference< XFormComponent >& xFormComponent( pControlData->GetFormComponent());
2006 SdrPageView* pPageView = pFormView->GetSdrPageView();
2007 SdrPage* pPage = pPageView->GetPage();
2009 bool bPaint = false;
2010 SdrObjListIter aIter( pPage );
2011 while ( aIter.IsMore() )
2013 SdrObject* pSdrObject = aIter.Next();
2014 FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject );
2015 if ( !pFormObject )
2016 continue;
2018 Reference< XInterface > xControlModel( pFormObject->GetUnoControlModel() );
2019 if ( xControlModel != xFormComponent )
2020 continue;
2022 // mark the object
2023 if ( !pFormView->IsObjMarked( pSdrObject ) )
2024 // unfortunately, the writer doesn't like marking an already-marked object, again, so reset the mark first
2025 pFormView->MarkObj( pSdrObject, pPageView );
2027 bPaint = true;
2029 } // while ( aIter.IsMore() )
2030 if ( !bPaint )
2031 return;
2033 // make the mark visible
2034 ::tools::Rectangle aMarkRect( pFormView->GetAllMarkedRect());
2035 for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i )
2037 SdrPaintWindow* pPaintWindow = pFormView->GetPaintWindow( i );
2038 OutputDevice& rOutDev = pPaintWindow->GetOutputDevice();
2039 if ( OUTDEV_WINDOW == rOutDev.GetOutDevType() )
2041 pFormView->MakeVisible( aMarkRect, *rOutDev.GetOwnerWindow() );
2043 } // for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i )
2050 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */