/src/libreoffice/svx/source/form/navigatortree.cxx
Line | Count | Source |
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 <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> |
27 | | |
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> |
52 | | |
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> |
58 | | |
59 | | namespace svxform |
60 | | { |
61 | 0 | #define EXPLORER_SYNC_DELAY 200 |
62 | | // Time (in ms) until explorer synchronizes the view after select or deselect |
63 | | |
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; |
74 | | |
75 | | |
76 | | // helper |
77 | | |
78 | | |
79 | | typedef ::std::map< Reference< XInterface >, SdrObject* > MapModelToShape; |
80 | | |
81 | | |
82 | | static void collectShapeModelMapping( SdrPage const * _pPage, MapModelToShape& _rMapping ) |
83 | 0 | { |
84 | 0 | OSL_ENSURE( _pPage, "collectShapeModelMapping: invalid arg!" ); |
85 | |
|
86 | 0 | _rMapping.clear(); |
87 | |
|
88 | 0 | SdrObjListIter aIter( _pPage ); |
89 | 0 | while ( aIter.IsMore() ) |
90 | 0 | { |
91 | 0 | SdrObject* pSdrObject = aIter.Next(); |
92 | 0 | FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject ); |
93 | 0 | if ( !pFormObject ) |
94 | 0 | continue; |
95 | | |
96 | 0 | Reference< XInterface > xNormalizedModel( pFormObject->GetUnoControlModel(), UNO_QUERY ); |
97 | | // note that this is normalized (i.e. queried for XInterface explicitly) |
98 | |
|
99 | 0 | ::std::pair< MapModelToShape::iterator, bool > aPos = |
100 | 0 | _rMapping.emplace( xNormalizedModel, pSdrObject ); |
101 | 0 | 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 |
103 | 0 | } |
104 | 0 | } |
105 | | |
106 | | NavigatorTreeDropTarget::NavigatorTreeDropTarget(NavigatorTree& rTreeView) |
107 | 0 | : DropTargetHelper(rTreeView.get_widget().get_drop_target()) |
108 | 0 | , m_rTreeView(rTreeView) |
109 | 0 | { |
110 | 0 | } |
111 | | |
112 | | sal_Int8 NavigatorTreeDropTarget::AcceptDrop(const AcceptDropEvent& rEvt) |
113 | 0 | { |
114 | 0 | sal_Int8 nAccept = m_rTreeView.AcceptDrop(rEvt); |
115 | |
|
116 | 0 | if (nAccept != DND_ACTION_NONE) |
117 | 0 | { |
118 | | // to enable the autoscroll when we're close to the edges |
119 | 0 | weld::TreeView& rWidget = m_rTreeView.get_widget(); |
120 | 0 | rWidget.get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true); |
121 | 0 | } |
122 | |
|
123 | 0 | return nAccept; |
124 | 0 | } |
125 | | |
126 | | sal_Int8 NavigatorTreeDropTarget::ExecuteDrop(const ExecuteDropEvent& rEvt) |
127 | 0 | { |
128 | 0 | return m_rTreeView.ExecuteDrop(rEvt); |
129 | 0 | } |
130 | | |
131 | | NavigatorTree::NavigatorTree(std::unique_ptr<weld::TreeView> xTreeView) |
132 | 0 | :m_xTreeView(std::move(xTreeView)) |
133 | 0 | ,m_aDropTargetHelper(*this) |
134 | 0 | ,m_aSynchronizeTimer("svx NavigatorTree m_aSynchronizeTimer") |
135 | 0 | ,nEditEvent(nullptr) |
136 | 0 | ,m_sdiState(SDI_DIRTY) |
137 | 0 | ,m_nSelectLock(0) |
138 | 0 | ,m_nFormsSelected(0) |
139 | 0 | ,m_nControlsSelected(0) |
140 | 0 | ,m_nHiddenControls(0) |
141 | 0 | ,m_bDragDataDirty(false) |
142 | 0 | ,m_bPrevSelectionMixed(false) |
143 | 0 | ,m_bRootSelected(false) |
144 | 0 | ,m_bInitialUpdate(true) |
145 | 0 | ,m_bKeyboardCut( false ) |
146 | 0 | ,m_bEditing( false ) |
147 | 0 | { |
148 | 0 | m_xTreeView->set_help_id(HID_FORM_NAVIGATOR); |
149 | 0 | m_xTreeView->set_size_request(200, 200); |
150 | |
|
151 | 0 | m_xTreeView->set_selection_mode(SelectionMode::Multiple); |
152 | |
|
153 | 0 | m_pNavModel.reset(new NavigatorTreeModel()); |
154 | 0 | Clear(); |
155 | |
|
156 | 0 | StartListening( *m_pNavModel ); |
157 | |
|
158 | 0 | m_aSynchronizeTimer.SetInvokeHandler(LINK(this, NavigatorTree, OnSynchronizeTimer)); |
159 | 0 | m_xTreeView->connect_selection_changed(LINK(this, NavigatorTree, OnEntrySelDesel)); |
160 | 0 | m_xTreeView->connect_key_press(LINK(this, NavigatorTree, KeyInputHdl)); |
161 | 0 | m_xTreeView->connect_popup_menu(LINK(this, NavigatorTree, PopupMenuHdl)); |
162 | 0 | m_xTreeView->connect_editing(LINK(this, NavigatorTree, EditingEntryHdl), |
163 | 0 | LINK(this, NavigatorTree, EditedEntryHdl)); |
164 | 0 | m_xTreeView->connect_drag_begin(LINK(this, NavigatorTree, DragBeginHdl)); |
165 | 0 | } |
166 | | |
167 | | NavigatorTree::~NavigatorTree() |
168 | 0 | { |
169 | 0 | if( nEditEvent ) |
170 | 0 | Application::RemoveUserEvent( nEditEvent ); |
171 | |
|
172 | 0 | if (m_aSynchronizeTimer.IsActive()) |
173 | 0 | m_aSynchronizeTimer.Stop(); |
174 | |
|
175 | 0 | DBG_ASSERT(GetNavModel() != nullptr, "NavigatorTree::~NavigatorTree : unexpected : no ExplorerModel"); |
176 | 0 | EndListening( *m_pNavModel ); |
177 | 0 | Clear(); |
178 | 0 | m_pNavModel.reset(); |
179 | 0 | } |
180 | | |
181 | | void NavigatorTree::Clear() |
182 | 0 | { |
183 | 0 | m_pNavModel->Clear(); |
184 | 0 | } |
185 | | |
186 | | void NavigatorTree::UpdateContent( FmFormShell* pFormShell ) |
187 | 0 | { |
188 | 0 | if (m_bInitialUpdate) |
189 | 0 | { |
190 | 0 | GrabFocus(); |
191 | 0 | m_bInitialUpdate = false; |
192 | 0 | } |
193 | |
|
194 | 0 | FmFormShell* pOldShell = GetNavModel()->GetFormShell(); |
195 | 0 | FmFormPage* pOldPage = GetNavModel()->GetFormPage(); |
196 | 0 | FmFormPage* pNewPage = pFormShell ? pFormShell->GetCurPage() : nullptr; |
197 | |
|
198 | 0 | if ((pOldShell != pFormShell) || (pOldPage != pNewPage)) |
199 | 0 | { |
200 | | // new shell during editing |
201 | 0 | if (IsEditingActive()) |
202 | 0 | { |
203 | 0 | m_xTreeView->end_editing(); |
204 | 0 | m_bEditing = false; |
205 | 0 | } |
206 | |
|
207 | 0 | m_bDragDataDirty = true; // as a precaution, although I don't drag |
208 | 0 | } |
209 | 0 | GetNavModel()->UpdateContent( pFormShell ); |
210 | | |
211 | | // if there is a form, expand root |
212 | 0 | if (m_xRootEntry && !m_xTreeView->get_row_expanded(*m_xRootEntry)) |
213 | 0 | m_xTreeView->expand_row(*m_xRootEntry); |
214 | | // if there is EXACTLY ONE form, expand it too |
215 | 0 | if (m_xRootEntry) |
216 | 0 | { |
217 | 0 | std::unique_ptr<weld::TreeIter> xFirst(m_xTreeView->make_iterator(m_xRootEntry.get())); |
218 | 0 | bool bFirst = m_xTreeView->iter_children(*xFirst); |
219 | 0 | if (bFirst) |
220 | 0 | { |
221 | 0 | std::unique_ptr<weld::TreeIter> xSibling(m_xTreeView->make_iterator(xFirst.get())); |
222 | 0 | if (!m_xTreeView->iter_next_sibling(*xSibling)) |
223 | 0 | m_xTreeView->expand_row(*xFirst); |
224 | 0 | } |
225 | 0 | } |
226 | 0 | } |
227 | | |
228 | | bool NavigatorTree::implAllowExchange( sal_Int8 _nAction, bool* _pHasNonHidden ) |
229 | 0 | { |
230 | 0 | bool bCurEntry = m_xTreeView->get_cursor(nullptr); |
231 | 0 | if (!bCurEntry) |
232 | 0 | return false; |
233 | | |
234 | | // Information for AcceptDrop and Execute Drop |
235 | 0 | CollectSelectionData(SDI_ALL); |
236 | 0 | if (m_arrCurrentSelection.empty()) |
237 | | // nothing to do |
238 | 0 | return false; |
239 | | |
240 | | // check whether there are only hidden controls |
241 | | // I may add a format to pCtrlExch |
242 | 0 | bool bHasNonHidden = std::any_of(m_arrCurrentSelection.begin(), m_arrCurrentSelection.end(), |
243 | 0 | [this](const auto& rEntry) { |
244 | 0 | FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rEntry)); |
245 | 0 | return !IsHiddenControl( pCurrent ); |
246 | 0 | }); |
247 | |
|
248 | 0 | if ( bHasNonHidden && ( 0 == ( _nAction & DND_ACTION_MOVE ) ) ) |
249 | | // non-hidden controls need to be moved |
250 | 0 | return false; |
251 | | |
252 | 0 | if ( _pHasNonHidden ) |
253 | 0 | *_pHasNonHidden = bHasNonHidden; |
254 | |
|
255 | 0 | return true; |
256 | 0 | } |
257 | | |
258 | | bool NavigatorTree::implPrepareExchange( sal_Int8 _nAction ) |
259 | 0 | { |
260 | 0 | bool bHasNonHidden = false; |
261 | 0 | if ( !implAllowExchange( _nAction, &bHasNonHidden ) ) |
262 | 0 | return false; |
263 | | |
264 | 0 | m_aControlExchange.prepareDrag(); |
265 | 0 | m_aControlExchange->setFocusEntry(m_xTreeView->get_cursor(nullptr)); |
266 | |
|
267 | 0 | for (const auto& rpEntry : m_arrCurrentSelection) |
268 | 0 | m_aControlExchange->addSelectedEntry(m_xTreeView->make_iterator(rpEntry.get())); |
269 | |
|
270 | 0 | m_aControlExchange->setFormsRoot( GetNavModel()->GetFormPage()->GetForms() ); |
271 | 0 | m_aControlExchange->buildPathFormat(m_xTreeView.get(), m_xRootEntry.get()); |
272 | |
|
273 | 0 | if (!bHasNonHidden) |
274 | 0 | { |
275 | | // create a sequence |
276 | 0 | Sequence< Reference< XInterface > > seqIFaces(m_arrCurrentSelection.size()); |
277 | 0 | Reference< XInterface >* pArray = seqIFaces.getArray(); |
278 | 0 | for (const auto& rpEntry : m_arrCurrentSelection) |
279 | 0 | { |
280 | 0 | *pArray = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rpEntry))->GetElement(); |
281 | 0 | ++pArray; |
282 | 0 | } |
283 | | // and the new format |
284 | 0 | m_aControlExchange->addHiddenControlsFormat(seqIFaces); |
285 | 0 | } |
286 | |
|
287 | 0 | m_bDragDataDirty = false; |
288 | 0 | return true; |
289 | 0 | } |
290 | | |
291 | | IMPL_LINK(NavigatorTree, DragBeginHdl, bool&, rUnsetDragIcon, bool) |
292 | 0 | { |
293 | 0 | rUnsetDragIcon = false; |
294 | |
|
295 | 0 | bool bSuccess = implPrepareExchange(DND_ACTION_COPYMOVE); |
296 | 0 | if (bSuccess) |
297 | 0 | { |
298 | 0 | OControlExchange& rExchange = *m_aControlExchange; |
299 | 0 | rtl::Reference<TransferDataContainer> xHelper(&rExchange); |
300 | 0 | m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPYMOVE); |
301 | 0 | rExchange.setDragging(true); |
302 | 0 | } |
303 | 0 | return !bSuccess; |
304 | 0 | } |
305 | | |
306 | | IMPL_LINK(NavigatorTree, PopupMenuHdl, const CommandEvent&, rEvt, bool) |
307 | 0 | { |
308 | 0 | bool bHandled = false; |
309 | 0 | switch( rEvt.GetCommand() ) |
310 | 0 | { |
311 | 0 | case CommandEventId::ContextMenu: |
312 | 0 | { |
313 | | // Position of click |
314 | 0 | ::Point ptWhere; |
315 | 0 | if (rEvt.IsMouseEvent()) |
316 | 0 | { |
317 | 0 | ptWhere = rEvt.GetMousePosPixel(); |
318 | 0 | std::unique_ptr<weld::TreeIter> xClickedOn(m_xTreeView->make_iterator()); |
319 | 0 | if (!m_xTreeView->get_dest_row_at_pos(ptWhere, xClickedOn.get(), false)) |
320 | 0 | break; |
321 | 0 | if (!m_xTreeView->is_selected(*xClickedOn)) |
322 | 0 | { |
323 | 0 | m_xTreeView->unselect_all(); |
324 | 0 | m_xTreeView->select(*xClickedOn); |
325 | 0 | m_xTreeView->set_cursor(*xClickedOn); |
326 | 0 | } |
327 | 0 | } |
328 | 0 | else |
329 | 0 | { |
330 | 0 | if (m_arrCurrentSelection.empty()) // only happens with context menu via keyboard |
331 | 0 | break; |
332 | | |
333 | 0 | std::unique_ptr<weld::TreeIter> xCurrent(m_xTreeView->make_iterator()); |
334 | 0 | if (!m_xTreeView->get_cursor(xCurrent.get())) |
335 | 0 | break; |
336 | 0 | ptWhere = m_xTreeView->get_row_area(*xCurrent).Center(); |
337 | 0 | } |
338 | | |
339 | | // update my selection data |
340 | 0 | CollectSelectionData(SDI_ALL); |
341 | | |
342 | | // if there is at least one no-root-entry and the root selected, I deselect root |
343 | 0 | if ( (m_arrCurrentSelection.size() > 1) && m_bRootSelected ) |
344 | 0 | { |
345 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin(); |
346 | 0 | m_xTreeView->set_cursor(*rIter); |
347 | 0 | m_xTreeView->unselect(*m_xRootEntry); |
348 | 0 | } |
349 | 0 | bool bSingleSelection = (m_arrCurrentSelection.size() == 1); |
350 | | |
351 | |
|
352 | 0 | 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 |
355 | | |
356 | | |
357 | | // create menu |
358 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
359 | 0 | FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr; |
360 | 0 | if( pFormShell && pFormModel ) |
361 | 0 | { |
362 | 0 | std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(m_xTreeView.get(), u"svx/ui/formnavimenu.ui"_ustr)); |
363 | 0 | std::unique_ptr<weld::Menu> xContextMenu(xBuilder->weld_menu(u"menu"_ustr)); |
364 | 0 | std::unique_ptr<weld::Menu> xSubMenuNew(xBuilder->weld_menu(u"submenu"_ustr)); |
365 | | |
366 | | // menu 'New' only exists, if only the root or only one form is selected |
367 | 0 | bool bShowNew = bSingleSelection && (m_nFormsSelected || m_bRootSelected); |
368 | 0 | if (!bShowNew) |
369 | 0 | xContextMenu->remove(u"new"_ustr); |
370 | | |
371 | | // 'New'\'Form' under the same terms |
372 | 0 | bool bShowForm = bSingleSelection && (m_nFormsSelected || m_bRootSelected); |
373 | 0 | if (bShowForm) |
374 | 0 | xSubMenuNew->append(u"form"_ustr, SvxResId(RID_STR_FORM), RID_SVXBMP_FORM); |
375 | | |
376 | | // 'New'\'hidden...', if exactly one form is selected |
377 | 0 | bool bShowHidden = bSingleSelection && m_nFormsSelected; |
378 | 0 | if (bShowHidden) |
379 | 0 | xSubMenuNew->append(u"hidden"_ustr, SvxResId(RID_STR_HIDDEN), RID_SVXBMP_HIDDEN); |
380 | | |
381 | | // 'Delete': everything which is not root can be removed |
382 | 0 | if (m_bRootSelected) |
383 | 0 | xContextMenu->remove(u"delete"_ustr); |
384 | | |
385 | | // 'Cut', 'Copy' and 'Paste' |
386 | 0 | bool bShowCut = !m_bRootSelected && implAllowExchange(DND_ACTION_MOVE); |
387 | 0 | if (!bShowCut) |
388 | 0 | xContextMenu->remove(u"cut"_ustr); |
389 | 0 | bool bShowCopy = !m_bRootSelected && implAllowExchange(DND_ACTION_COPY); |
390 | 0 | if (!bShowCopy) |
391 | 0 | xContextMenu->remove(u"copy"_ustr); |
392 | 0 | if (!implAcceptPaste()) |
393 | 0 | xContextMenu->remove(u"paste"_ustr); |
394 | | |
395 | | // TabDialog, if exactly one form |
396 | 0 | bool bShowTabOrder = bSingleSelection && m_nFormsSelected; |
397 | 0 | if (!bShowTabOrder) |
398 | 0 | xContextMenu->remove(u"taborder"_ustr); |
399 | |
|
400 | 0 | bool bShowProps = true; |
401 | | // in XML forms, we don't allow for the properties of a form |
402 | | // #i36484# |
403 | 0 | if (pFormShell->GetImpl()->isEnhancedForm_Lock() && !m_nControlsSelected) |
404 | 0 | bShowProps = false; |
405 | | // if the property browser is already open, we don't allow for the properties, too |
406 | 0 | if (pFormShell->GetImpl()->IsPropBrwOpen_Lock()) |
407 | 0 | bShowProps = false; |
408 | | |
409 | | // and finally, if there's a mixed selection of forms and controls, disable the entry, too |
410 | 0 | if (bShowProps && !pFormShell->GetImpl()->IsPropBrwOpen_Lock()) |
411 | 0 | bShowProps = |
412 | 0 | (m_nControlsSelected && !m_nFormsSelected) || (!m_nControlsSelected && m_nFormsSelected); |
413 | |
|
414 | 0 | if (!bShowProps) |
415 | 0 | xContextMenu->remove(u"props"_ustr); |
416 | | |
417 | | // rename, if one element and no root |
418 | 0 | bool bShowRename = bSingleSelection && !m_bRootSelected; |
419 | 0 | if (!bShowRename) |
420 | 0 | xContextMenu->remove(u"rename"_ustr); |
421 | |
|
422 | 0 | if (!m_bRootSelected) |
423 | 0 | { |
424 | | // Readonly-entry is only for root |
425 | 0 | xContextMenu->remove(u"designmode"_ustr); |
426 | | // the same for automatic control focus |
427 | 0 | xContextMenu->remove(u"controlfocus"_ustr); |
428 | 0 | } |
429 | |
|
430 | 0 | 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 | 0 | if (!m_bRootSelected && !m_nFormsSelected && (m_nControlsSelected == 1)) |
434 | 0 | { |
435 | 0 | 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 |
442 | |
|
443 | 0 | pFormShell->GetImpl()->checkControlConversionSlotsForCurrentSelection_Lock(*xConversionMenu); |
444 | 0 | } |
445 | 0 | else |
446 | 0 | xContextMenu->remove(u"change"_ustr); |
447 | |
|
448 | 0 | if (m_bRootSelected) |
449 | 0 | { |
450 | | // set OpenReadOnly |
451 | 0 | xContextMenu->set_active(u"designmode"_ustr, pFormModel->GetOpenInDesignMode()); |
452 | 0 | xContextMenu->set_active(u"controlfocus"_ustr, pFormModel->GetAutoControlFocus()); |
453 | 0 | } |
454 | |
|
455 | 0 | OUString sIdent = xContextMenu->popup_at_rect(m_xTreeView.get(), tools::Rectangle(ptWhere, ::Size(1, 1))); |
456 | 0 | if (sIdent == "form") |
457 | 0 | { |
458 | 0 | OUString aStr(SvxResId(RID_STR_FORM)); |
459 | 0 | OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr); |
460 | |
|
461 | 0 | pFormModel->BegUndo(aUndoStr); |
462 | | // slot was only available, if there is only one selected entry, |
463 | | // which is a root or a form |
464 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin(); |
465 | 0 | NewForm(*rIter); |
466 | 0 | pFormModel->EndUndo(); |
467 | 0 | } |
468 | 0 | else if (sIdent == "hidden") |
469 | 0 | { |
470 | 0 | OUString aStr(SvxResId(RID_STR_CONTROL)); |
471 | 0 | OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr); |
472 | |
|
473 | 0 | pFormModel->BegUndo(aUndoStr); |
474 | | // slot was valid for (exactly) one selected form |
475 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin(); |
476 | 0 | NewControl(FM_COMPONENT_HIDDEN, *rIter, true); |
477 | 0 | pFormModel->EndUndo(); |
478 | 0 | } |
479 | 0 | else if (sIdent == "cut") |
480 | 0 | doCut(); |
481 | 0 | else if (sIdent == "copy") |
482 | 0 | doCopy(); |
483 | 0 | else if (sIdent == "paste") |
484 | 0 | doPaste(); |
485 | 0 | else if (sIdent == "delete") |
486 | 0 | DeleteSelection(); |
487 | 0 | else if (sIdent == "taborder") |
488 | 0 | { |
489 | | // this slot was effective for exactly one selected form |
490 | 0 | const std::unique_ptr<weld::TreeIter>& rSelectedForm = *m_arrCurrentSelection.begin(); |
491 | 0 | DBG_ASSERT( IsFormEntry(*rSelectedForm), "NavigatorTree::Command: This entry must be a FormEntry." ); |
492 | |
|
493 | 0 | FmFormData* pFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(*rSelectedForm)); |
494 | 0 | const Reference< XForm >& xForm( pFormData->GetFormIface()); |
495 | |
|
496 | 0 | Reference< XTabControllerModel > xTabController(xForm, UNO_QUERY); |
497 | 0 | if( !xTabController.is() ) |
498 | 0 | break; |
499 | 0 | GetNavModel()->GetFormShell()->GetImpl()->ExecuteTabOrderDialog_Lock(xTabController); |
500 | 0 | } |
501 | 0 | else if (sIdent == "props") |
502 | 0 | ShowSelectionProperties(true); |
503 | 0 | else if (sIdent == "rename") |
504 | 0 | { |
505 | | // only allowed for one no-root-entry |
506 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin(); |
507 | 0 | m_xTreeView->start_editing(*rIter); |
508 | 0 | m_bEditing = true; |
509 | 0 | } |
510 | 0 | else if (sIdent == "designmode") |
511 | 0 | { |
512 | 0 | pFormModel->SetOpenInDesignMode( !pFormModel->GetOpenInDesignMode() ); |
513 | 0 | pFormShell->GetViewShell()->GetViewFrame().GetBindings().Invalidate(SID_FM_OPEN_READONLY); |
514 | 0 | } |
515 | 0 | else if (sIdent == "controlfocus") |
516 | 0 | { |
517 | 0 | pFormModel->SetAutoControlFocus( !pFormModel->GetAutoControlFocus() ); |
518 | 0 | pFormShell->GetViewShell()->GetViewFrame().GetBindings().Invalidate(SID_FM_AUTOCONTROLFOCUS); |
519 | 0 | } |
520 | 0 | else if (FmXFormShell::isControlConversionSlot(sIdent)) |
521 | 0 | { |
522 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin(); |
523 | 0 | FmControlData* pCurrent = weld::fromId<FmControlData*>(m_xTreeView->get_id(*rIter)); |
524 | 0 | if (pFormShell->GetImpl()->executeControlConversionSlot_Lock(pCurrent->GetFormComponent(), sIdent)) |
525 | 0 | ShowSelectionProperties(); |
526 | 0 | } |
527 | 0 | } |
528 | 0 | bHandled = true; |
529 | 0 | } |
530 | 0 | break; |
531 | 0 | default: break; |
532 | 0 | } |
533 | | |
534 | 0 | return bHandled; |
535 | 0 | } |
536 | | |
537 | | std::unique_ptr<weld::TreeIter> NavigatorTree::FindEntry(FmEntryData* pEntryData) |
538 | 0 | { |
539 | 0 | std::unique_ptr<weld::TreeIter> xRet; |
540 | 0 | if(!pEntryData) |
541 | 0 | return xRet; |
542 | | |
543 | 0 | m_xTreeView->all_foreach([this, pEntryData, &xRet](weld::TreeIter& rEntry){ |
544 | 0 | FmEntryData* pCurEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rEntry)); |
545 | 0 | if (pCurEntryData && pCurEntryData->IsEqualWithoutChildren(pEntryData)) |
546 | 0 | { |
547 | 0 | xRet = m_xTreeView->make_iterator(&rEntry); |
548 | 0 | return true; |
549 | 0 | } |
550 | 0 | return false; |
551 | 0 | }); |
552 | |
|
553 | 0 | return xRet; |
554 | 0 | } |
555 | | |
556 | | void NavigatorTree::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) |
557 | 0 | { |
558 | 0 | if( rHint.GetId() == SfxHintId::FmNavRemoved ) |
559 | 0 | { |
560 | 0 | auto pRemovedHint = static_cast<const FmNavRemovedHint*>(&rHint); |
561 | 0 | FmEntryData* pEntryData = pRemovedHint->GetEntryData(); |
562 | 0 | Remove( pEntryData ); |
563 | 0 | } |
564 | 0 | else if( rHint.GetId() == SfxHintId::FmNavInserted ) |
565 | 0 | { |
566 | 0 | auto pInsertedHint = static_cast<const FmNavInsertedHint*>(&rHint); |
567 | 0 | FmEntryData* pEntryData = pInsertedHint->GetEntryData(); |
568 | 0 | sal_uInt32 nRelPos = pInsertedHint->GetRelPos(); |
569 | 0 | Insert( pEntryData, nRelPos ); |
570 | 0 | } |
571 | 0 | else if( rHint.GetId() == SfxHintId::FmNavModelReplaced ) |
572 | 0 | { |
573 | 0 | auto pReplacedHint = static_cast<const FmNavModelReplacedHint*>(&rHint); |
574 | 0 | FmEntryData* pData = pReplacedHint->GetEntryData(); |
575 | 0 | std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pData); |
576 | 0 | if (xEntry) |
577 | 0 | { |
578 | | // reset image |
579 | 0 | m_xTreeView->set_image(*xEntry, pData->GetNormalImage()); |
580 | 0 | } |
581 | 0 | } |
582 | 0 | else if( rHint.GetId() == SfxHintId::FmNavNameChanged ) |
583 | 0 | { |
584 | 0 | auto pNameChangedHint = static_cast<const FmNavNameChangedHint*>(&rHint); |
585 | 0 | std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pNameChangedHint->GetEntryData()); |
586 | 0 | if (xEntry) |
587 | 0 | m_xTreeView->set_text(*xEntry, pNameChangedHint->GetNewName()); |
588 | 0 | } |
589 | 0 | else if( rHint.GetId() == SfxHintId::FmNavCleared ) |
590 | 0 | { |
591 | 0 | m_aCutEntries.clear(); |
592 | 0 | if (m_aControlExchange.isDataExchangeActive()) |
593 | 0 | m_aControlExchange.clear(); |
594 | 0 | m_xTreeView->clear(); |
595 | | |
596 | | // default-entry "Forms" |
597 | 0 | OUString sText(SvxResId(RID_STR_FORMS)); |
598 | 0 | m_xRootEntry = m_xTreeView->make_iterator(); |
599 | 0 | m_xTreeView->insert(nullptr, -1, &sText, nullptr, nullptr, nullptr, |
600 | 0 | false, m_xRootEntry.get()); |
601 | 0 | m_xTreeView->set_image(*m_xRootEntry, RID_SVXBMP_FORMS); |
602 | 0 | m_xTreeView->set_sensitive(*m_xRootEntry, true); |
603 | 0 | } |
604 | 0 | else if (rHint.GetId() == SfxHintId::FmNavRequestSelect) |
605 | 0 | { |
606 | 0 | auto pSelectHint = static_cast<const FmNavRequestSelectHint*>(&rHint); |
607 | 0 | FmEntryDataArray& arredToSelect = const_cast<FmNavRequestSelectHint*>(pSelectHint)->GetItems(); |
608 | 0 | SynchronizeSelection(arredToSelect); |
609 | |
|
610 | 0 | 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 | 0 | m_bPrevSelectionMixed = true; |
614 | 0 | } |
615 | 0 | } |
616 | | |
617 | | std::unique_ptr<weld::TreeIter> NavigatorTree::Insert(const FmEntryData* pEntryData, int nRelPos) |
618 | 0 | { |
619 | | // insert current entry |
620 | 0 | std::unique_ptr<weld::TreeIter> xParentEntry = FindEntry( pEntryData->GetParent() ); |
621 | 0 | std::unique_ptr<weld::TreeIter> xNewEntry(m_xTreeView->make_iterator()); |
622 | 0 | OUString sId(weld::toId(pEntryData)); |
623 | |
|
624 | 0 | if(!xParentEntry) |
625 | 0 | { |
626 | 0 | m_xTreeView->insert(m_xRootEntry.get(), nRelPos, &pEntryData->GetText(), &sId, |
627 | 0 | nullptr, nullptr, false, xNewEntry.get()); |
628 | 0 | } |
629 | 0 | else |
630 | 0 | { |
631 | 0 | m_xTreeView->insert(xParentEntry.get(), nRelPos, &pEntryData->GetText(), &sId, |
632 | 0 | nullptr, nullptr, false, xNewEntry.get()); |
633 | 0 | } |
634 | |
|
635 | 0 | m_xTreeView->set_image(*xNewEntry, pEntryData->GetNormalImage()); |
636 | 0 | m_xTreeView->set_sensitive(*xNewEntry, true); |
637 | | |
638 | | // If root-entry, expand root |
639 | 0 | if (!xParentEntry) |
640 | 0 | m_xTreeView->expand_row(*m_xRootEntry); |
641 | | |
642 | | // insert children |
643 | 0 | FmEntryDataList* pChildList = pEntryData->GetChildList(); |
644 | 0 | size_t nChildCount = pChildList->size(); |
645 | 0 | for( size_t i = 0; i < nChildCount; i++ ) |
646 | 0 | { |
647 | 0 | FmEntryData* pChildData = pChildList->at( i ); |
648 | 0 | Insert(pChildData, -1); |
649 | 0 | } |
650 | |
|
651 | 0 | return xNewEntry; |
652 | 0 | } |
653 | | |
654 | | void NavigatorTree::Remove( FmEntryData* pEntryData ) |
655 | 0 | { |
656 | 0 | if( !pEntryData ) |
657 | 0 | return; |
658 | | |
659 | | // entry for the data |
660 | 0 | std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pEntryData); |
661 | 0 | if (!xEntry) |
662 | 0 | return; |
663 | | |
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 | 0 | LockSelectionHandling(); |
670 | | |
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 | 0 | m_xTreeView->unselect(*xEntry); |
674 | | |
675 | | // selection can be modified during deletion, |
676 | | // but because I disabled SelectionHandling, I have to do it later |
677 | 0 | auto nExpectedSelectionCount = m_xTreeView->count_selected_rows(); |
678 | |
|
679 | 0 | ModelHasRemoved(xEntry.get()); |
680 | 0 | m_xTreeView->remove(*xEntry); |
681 | |
|
682 | 0 | if (nExpectedSelectionCount != m_xTreeView->count_selected_rows()) |
683 | 0 | SynchronizeSelection(); |
684 | | |
685 | | // by default I treat the selection of course |
686 | 0 | UnlockSelectionHandling(); |
687 | 0 | } |
688 | | |
689 | | bool NavigatorTree::IsFormEntry(const weld::TreeIter& rEntry) |
690 | 0 | { |
691 | 0 | FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rEntry)); |
692 | 0 | return !pEntryData || dynamic_cast<const FmFormData*>( pEntryData) != nullptr; |
693 | 0 | } |
694 | | |
695 | | bool NavigatorTree::IsFormComponentEntry(const weld::TreeIter& rEntry) |
696 | 0 | { |
697 | 0 | FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rEntry)); |
698 | 0 | return dynamic_cast<const FmControlData*>( pEntryData) != nullptr; |
699 | 0 | } |
700 | | |
701 | | bool NavigatorTree::implAcceptPaste( ) |
702 | 0 | { |
703 | 0 | auto nSelectedEntries = m_xTreeView->count_selected_rows(); |
704 | 0 | if (nSelectedEntries != 1) |
705 | | // no selected entry, or at least two selected entries |
706 | 0 | return false; |
707 | | |
708 | | // get the clipboard |
709 | 0 | TransferableDataHelper aClipboardContent(TransferableDataHelper::CreateFromClipboard(m_xTreeView->get_clipboard())); |
710 | |
|
711 | 0 | sal_Int8 nAction = m_aControlExchange.isClipboardOwner() && doingKeyboardCut( ) ? DND_ACTION_MOVE : DND_ACTION_COPY; |
712 | 0 | std::unique_ptr<weld::TreeIter> xSelected(m_xTreeView->make_iterator()); |
713 | 0 | if (!m_xTreeView->get_selected(xSelected.get())) |
714 | 0 | xSelected.reset(); |
715 | 0 | return nAction == implAcceptDataTransfer(aClipboardContent.GetDataFlavorExVector(), nAction, xSelected.get(), false); |
716 | 0 | } |
717 | | |
718 | | sal_Int8 NavigatorTree::implAcceptDataTransfer( const DataFlavorExVector& _rFlavors, sal_Int8 _nAction, const weld::TreeIter* _pTargetEntry, bool _bDnD ) |
719 | 0 | { |
720 | | // no target -> no drop |
721 | 0 | if (!_pTargetEntry) |
722 | 0 | return DND_ACTION_NONE; |
723 | | |
724 | | // format check |
725 | 0 | bool bHasDefControlFormat = OControlExchange::hasFieldExchangeFormat( _rFlavors ); |
726 | 0 | bool bHasControlPathFormat = OControlExchange::hasControlPathFormat( _rFlavors ); |
727 | 0 | bool bHasHiddenControlsFormat = OControlExchange::hasHiddenControlModelsFormat( _rFlavors ); |
728 | 0 | if (!bHasDefControlFormat && !bHasControlPathFormat && !bHasHiddenControlsFormat) |
729 | 0 | return DND_ACTION_NONE; |
730 | | |
731 | 0 | bool bSelfSource = _bDnD ? m_aControlExchange.isDragSource() : m_aControlExchange.isClipboardOwner(); |
732 | |
|
733 | 0 | if ( bHasHiddenControlsFormat ) |
734 | 0 | { // bHasHiddenControlsFormat means that only hidden controls are part of the data |
735 | | |
736 | | // hidden controls can be copied to a form only |
737 | 0 | if (m_xTreeView->iter_compare(*_pTargetEntry, *m_xRootEntry) == 0 || !IsFormEntry(*_pTargetEntry)) |
738 | 0 | return DND_ACTION_NONE; |
739 | | |
740 | 0 | return bSelfSource ? ( DND_ACTION_COPYMOVE & _nAction ) : DND_ACTION_COPY; |
741 | 0 | } |
742 | | |
743 | 0 | if ( !bSelfSource ) |
744 | 0 | { |
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). |
748 | | |
749 | | // TODO: this smart behaviour later on ... at the moment, we disallow data transfer crossing navigator |
750 | | // boundaries. |
751 | |
|
752 | 0 | return DND_ACTION_NONE; |
753 | 0 | } |
754 | | |
755 | 0 | DBG_ASSERT( _bDnD ? m_aControlExchange.isDragSource() : m_aControlExchange.isClipboardOwner(), |
756 | 0 | "NavigatorTree::implAcceptDataTransfer: here only with source=dest!" ); |
757 | | // somebody changed the logic of this method ... |
758 | | |
759 | | // from here on, I can work with m_aControlExchange instead of _rData! |
760 | |
|
761 | 0 | bool bForeignCollection = m_aControlExchange->getFormsRoot().get() != GetNavModel()->GetFormPage()->GetForms().get(); |
762 | 0 | if ( bForeignCollection ) |
763 | 0 | { |
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 | 0 | DBG_ASSERT( !bHasHiddenControlsFormat, "NavigatorTree::implAcceptDataTransfer: still hidden controls format!" ); |
768 | | // somebody changed the logic of this method ... |
769 | |
|
770 | 0 | return DND_ACTION_COPY; |
771 | 0 | } |
772 | | |
773 | 0 | if (DND_ACTION_MOVE != _nAction) // 'normal' controls within a shell are moved only (never copied) |
774 | 0 | return DND_ACTION_NONE; |
775 | | |
776 | 0 | if ( m_bDragDataDirty || !bHasDefControlFormat ) |
777 | 0 | { |
778 | 0 | 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 | 0 | return DND_ACTION_NONE; |
782 | | |
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 | 0 | m_aControlExchange->buildListFromPath(m_xTreeView.get(), m_xRootEntry.get()); |
786 | 0 | m_bDragDataDirty = false; |
787 | 0 | } |
788 | | |
789 | | // List of dropped entries from DragServer |
790 | 0 | const ListBoxEntrySet& rDropped = m_aControlExchange->selected(); |
791 | 0 | DBG_ASSERT(!rDropped.empty(), "NavigatorTree::implAcceptDataTransfer: no entries !"); |
792 | |
|
793 | 0 | bool bDropTargetIsComponent = IsFormComponentEntry( *_pTargetEntry ); |
794 | | |
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) |
803 | | |
804 | | // collect the ancestors of the drop target (speeds up 3) |
805 | 0 | SvLBoxEntrySortedArray arrDropAncestors; |
806 | 0 | std::unique_ptr<weld::TreeIter> xLoop(m_xTreeView->make_iterator(_pTargetEntry)); |
807 | 0 | do |
808 | 0 | { |
809 | 0 | arrDropAncestors.emplace(m_xTreeView->make_iterator(xLoop.get())); |
810 | 0 | } |
811 | 0 | while (m_xTreeView->iter_parent(*xLoop)); |
812 | |
|
813 | 0 | for (const auto& rCurrent : rDropped) |
814 | 0 | { |
815 | | // test for 0) |
816 | 0 | if (m_xTreeView->iter_compare(*rCurrent, *m_xRootEntry) == 0) |
817 | 0 | return DND_ACTION_NONE; |
818 | | |
819 | 0 | std::unique_ptr<weld::TreeIter> xCurrentParent(m_xTreeView->make_iterator(rCurrent.get())); |
820 | 0 | m_xTreeView->iter_parent(*xCurrentParent); |
821 | | |
822 | | // test for 1) |
823 | 0 | if (m_xTreeView->iter_compare(*_pTargetEntry, *xCurrentParent) == 0) |
824 | 0 | return DND_ACTION_NONE; |
825 | | |
826 | | // test for 2) |
827 | 0 | if (m_xTreeView->iter_compare(*rCurrent, *_pTargetEntry) == 0) |
828 | 0 | return DND_ACTION_NONE; |
829 | | |
830 | | // test for 5) |
831 | 0 | if (bDropTargetIsComponent) |
832 | 0 | return DND_ACTION_NONE; |
833 | | |
834 | | // test for 3) |
835 | 0 | if (IsFormEntry(*rCurrent)) |
836 | 0 | { |
837 | 0 | auto aIter = std::find_if(arrDropAncestors.begin(), arrDropAncestors.end(), |
838 | 0 | [this, &rCurrent](const auto& rElem) { |
839 | 0 | return m_xTreeView->iter_compare(*rElem, *rCurrent) == 0; |
840 | 0 | }); |
841 | |
|
842 | 0 | if ( aIter != arrDropAncestors.end() ) |
843 | 0 | return DND_ACTION_NONE; |
844 | 0 | } |
845 | 0 | else if (IsFormComponentEntry(*rCurrent)) |
846 | 0 | { |
847 | | // test for 4) |
848 | 0 | if (m_xTreeView->iter_compare(*_pTargetEntry, *m_xRootEntry) == 0) |
849 | 0 | return DND_ACTION_NONE; |
850 | 0 | } |
851 | 0 | } |
852 | 0 | return DND_ACTION_MOVE; |
853 | 0 | } |
854 | | |
855 | | sal_Int8 NavigatorTree::AcceptDrop( const AcceptDropEvent& rEvt ) |
856 | 0 | { |
857 | 0 | ::Point aDropPos = rEvt.maPosPixel; |
858 | 0 | 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 | 0 | if (!m_xTreeView->get_dest_row_at_pos(aDropPos, xDropTarget.get(), false)) |
861 | 0 | xDropTarget.reset(); |
862 | 0 | return implAcceptDataTransfer(m_aDropTargetHelper.GetDataFlavorExVector(), rEvt.mnAction, xDropTarget.get(), true); |
863 | 0 | } |
864 | | |
865 | | sal_Int8 NavigatorTree::implExecuteDataTransfer( const OControlTransferData& _rData, sal_Int8 _nAction, const ::Point& _rDropPos, bool _bDnD ) |
866 | 0 | { |
867 | 0 | 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 | 0 | if (!m_xTreeView->get_dest_row_at_pos(_rDropPos, xDrop.get(), false)) |
870 | 0 | xDrop.reset(); |
871 | 0 | return implExecuteDataTransfer( _rData, _nAction, xDrop.get(), _bDnD ); |
872 | 0 | } |
873 | | |
874 | | sal_Int8 NavigatorTree::implExecuteDataTransfer( const OControlTransferData& _rData, sal_Int8 _nAction, const weld::TreeIter* _pTargetEntry, bool _bDnD ) |
875 | 0 | { |
876 | 0 | const DataFlavorExVector& rDataFlavors = _rData.GetDataFlavorExVector(); |
877 | |
|
878 | 0 | 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 | 0 | return DND_ACTION_NONE; |
881 | | |
882 | 0 | if (!_pTargetEntry) |
883 | | // no target -> no drop |
884 | 0 | return DND_ACTION_NONE; |
885 | | |
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 |
895 | | |
896 | 0 | if ( DND_ACTION_COPY == _nAction ) |
897 | 0 | { // 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 | 0 | DBG_ASSERT( _pTargetEntry && m_xTreeView->iter_compare(*_pTargetEntry, *m_xRootEntry) != 0 && IsFormEntry( *_pTargetEntry ), |
902 | 0 | "NavigatorTree::implExecuteDataTransfer: should not be here!" ); |
903 | | // implAcceptDataTransfer should have caught both cases |
904 | |
|
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 |
909 | | |
910 | | // because i want to select all targets (and only them) |
911 | 0 | m_xTreeView->unselect_all(); |
912 | |
|
913 | 0 | const Sequence< Reference< XInterface > >& aControls = _rData.hiddenControls(); |
914 | 0 | sal_Int32 nCount = aControls.getLength(); |
915 | 0 | const Reference< XInterface >* pControls = aControls.getConstArray(); |
916 | |
|
917 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
918 | 0 | FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr; |
919 | | |
920 | | // within undo |
921 | 0 | if (pFormModel) |
922 | 0 | { |
923 | 0 | OUString aStr(SvxResId(RID_STR_CONTROL)); |
924 | 0 | OUString aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_INSERT).replaceAll("#", aStr); |
925 | 0 | pFormModel->BegUndo(aUndoStr); |
926 | 0 | } |
927 | | |
928 | | // copy controls |
929 | 0 | for (sal_Int32 i=0; i<nCount; ++i) |
930 | 0 | { |
931 | | // create new control |
932 | 0 | FmControlData* pNewControlData = NewControl( FM_COMPONENT_HIDDEN, *_pTargetEntry, false); |
933 | 0 | Reference< XPropertySet > xNewPropSet( pNewControlData->GetPropertySet() ); |
934 | | |
935 | | // copy properties form old control to new one |
936 | 0 | 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 | 0 | Reference< XPropertySetInfo > xPropInfo( xCurrent->getPropertySetInfo()); |
945 | 0 | const Sequence< Property> seqAllCurrentProps = xPropInfo->getProperties(); |
946 | 0 | for (Property const & currentProp : seqAllCurrentProps) |
947 | 0 | { |
948 | 0 | if (((currentProp.Attributes & PropertyAttribute::READONLY) == 0) && (currentProp.Name != FM_PROP_NAME)) |
949 | 0 | { // (read-only attribs aren't set, ditto name, |
950 | | // NewControl defined it uniquely |
951 | 0 | xNewPropSet->setPropertyValue(currentProp.Name, xCurrent->getPropertyValue(currentProp.Name)); |
952 | 0 | } |
953 | 0 | } |
954 | |
|
955 | 0 | std::unique_ptr<weld::TreeIter> xToSelect = FindEntry(pNewControlData); |
956 | 0 | m_xTreeView->select(*xToSelect); |
957 | 0 | if (i == 0) |
958 | 0 | m_xTreeView->set_cursor(*xToSelect); |
959 | 0 | } |
960 | |
|
961 | 0 | if (pFormModel) |
962 | 0 | pFormModel->EndUndo(); |
963 | |
|
964 | 0 | return _nAction; |
965 | 0 | } |
966 | | |
967 | 0 | if ( !OControlExchange::hasFieldExchangeFormat( _rData.GetDataFlavorExVector() ) ) |
968 | 0 | { |
969 | | // can't do anything without the internal format here ... usually happens when doing DnD or CnP |
970 | | // over navigator boundaries |
971 | 0 | return DND_ACTION_NONE; |
972 | 0 | } |
973 | | |
974 | | // some data for the target |
975 | 0 | bool bDropTargetIsForm = IsFormEntry(*_pTargetEntry); |
976 | 0 | FmFormData* pTargetData = bDropTargetIsForm ? weld::fromId<FmFormData*>(m_xTreeView->get_id(*_pTargetEntry)) : nullptr; |
977 | |
|
978 | 0 | DBG_ASSERT( DND_ACTION_COPY != _nAction, "NavigatorTree::implExecuteDataTransfer: somebody changed the logics!" ); |
979 | | |
980 | | // list of dragged entries |
981 | 0 | const ListBoxEntrySet& rDropped = _rData.selected(); |
982 | 0 | DBG_ASSERT(!rDropped.empty(), "NavigatorTree::implExecuteDataTransfer: no entries!"); |
983 | | |
984 | | // make a copy because rDropped is updated on deleting an entry which we do in the processing loop |
985 | 0 | ListBoxEntrySet aDropped; |
986 | 0 | for (const auto& rEntry : rDropped) |
987 | 0 | aDropped.emplace(m_xTreeView->make_iterator(rEntry.get())); |
988 | | |
989 | | // shell and model |
990 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
991 | 0 | FmFormModel* pFormModel = pFormShell ? pFormShell->GetFormModel() : nullptr; |
992 | 0 | if (!pFormModel) |
993 | 0 | return DND_ACTION_NONE; |
994 | | |
995 | | // for Undo |
996 | 0 | const bool bUndo = pFormModel->IsUndoEnabled(); |
997 | |
|
998 | 0 | if( bUndo ) |
999 | 0 | { |
1000 | 0 | OUString strUndoDescription(SvxResId(RID_STR_UNDO_CONTAINER_REPLACE)); |
1001 | 0 | pFormModel->BegUndo(strUndoDescription); |
1002 | 0 | } |
1003 | | |
1004 | | // remove selection before adding an entry, so the mark doesn't flicker |
1005 | | // -> lock action of selection |
1006 | 0 | LockSelectionHandling(); |
1007 | | |
1008 | | // go through all dropped entries |
1009 | 0 | for ( ListBoxEntrySet::const_iterator dropped = aDropped.begin(); |
1010 | 0 | dropped != aDropped.end(); |
1011 | 0 | ++dropped |
1012 | 0 | ) |
1013 | 0 | { |
1014 | 0 | bool bFirstEntry = aDropped.begin() == dropped; |
1015 | | |
1016 | | // some data of the current element |
1017 | 0 | const auto& rCurrent = *dropped; |
1018 | 0 | DBG_ASSERT(rCurrent, "NavigatorTree::implExecuteDataTransfer: invalid entry"); |
1019 | 0 | DBG_ASSERT(m_xTreeView->get_iter_depth(*rCurrent) != 0, "NavigatorTree::implExecuteDataTransfer: invalid entry"); |
1020 | | // don't drag root |
1021 | |
|
1022 | 0 | FmEntryData* pCurrentUserData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rCurrent)); |
1023 | |
|
1024 | 0 | Reference< XChild > xCurrentChild = pCurrentUserData->GetChildIFace(); |
1025 | 0 | Reference< XIndexContainer > xContainer(xCurrentChild->getParent(), UNO_QUERY); |
1026 | |
|
1027 | 0 | FmFormData* pCurrentParentUserData = static_cast<FmFormData*>(pCurrentUserData->GetParent()); |
1028 | 0 | DBG_ASSERT(pCurrentParentUserData == nullptr || dynamic_cast<const FmFormData*>(pCurrentUserData->GetParent()) != nullptr, "NavigatorTree::implExecuteDataTransfer: invalid parent"); |
1029 | | |
1030 | | // remove from parent |
1031 | 0 | if (pCurrentParentUserData) |
1032 | 0 | pCurrentParentUserData->GetChildList()->removeNoDelete( pCurrentUserData ); |
1033 | 0 | else |
1034 | 0 | GetNavModel()->GetRootList()->removeNoDelete( pCurrentUserData ); |
1035 | | |
1036 | | // remove from container |
1037 | 0 | sal_Int32 nIndex = getElementPos(xContainer, xCurrentChild); |
1038 | 0 | GetNavModel()->m_pPropChangeList->Lock(); |
1039 | | // UndoAction for removal |
1040 | 0 | if ( bUndo && GetNavModel()->m_pPropChangeList->CanUndo()) |
1041 | 0 | { |
1042 | 0 | pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*pFormModel, FmUndoContainerAction::Removed, |
1043 | 0 | xContainer, xCurrentChild, nIndex)); |
1044 | 0 | } |
1045 | 0 | else if( !GetNavModel()->m_pPropChangeList->CanUndo() ) |
1046 | 0 | { |
1047 | 0 | FmUndoContainerAction::DisposeElement( xCurrentChild ); |
1048 | 0 | } |
1049 | | |
1050 | | // copy events |
1051 | 0 | Reference< XEventAttacherManager > xManager(xContainer, UNO_QUERY); |
1052 | 0 | Sequence< ScriptEventDescriptor > aEvts; |
1053 | |
|
1054 | 0 | if (xManager.is() && nIndex >= 0) |
1055 | 0 | aEvts = xManager->getScriptEvents(nIndex); |
1056 | 0 | xContainer->removeByIndex(nIndex); |
1057 | | |
1058 | | // remove selection |
1059 | 0 | m_xTreeView->unselect(*rCurrent); |
1060 | | // and delete it |
1061 | 0 | Remove(pCurrentUserData); |
1062 | | |
1063 | | // position in DropParents, where to insert dropped entries |
1064 | 0 | if (pTargetData) |
1065 | 0 | xContainer.set(pTargetData->GetElement(), UNO_QUERY); |
1066 | 0 | else |
1067 | 0 | xContainer = GetNavModel()->GetForms(); |
1068 | | |
1069 | | // always insert at the end |
1070 | 0 | nIndex = xContainer->getCount(); |
1071 | | |
1072 | | // UndoAction for insertion |
1073 | 0 | if ( bUndo && GetNavModel()->m_pPropChangeList->CanUndo()) |
1074 | 0 | pFormModel->AddUndo(std::make_unique<FmUndoContainerAction>(*pFormModel, FmUndoContainerAction::Inserted, |
1075 | 0 | xContainer, xCurrentChild, nIndex)); |
1076 | | |
1077 | | // insert in new container |
1078 | 0 | if (pTargetData) |
1079 | 0 | { |
1080 | | // insert in a form needs a FormComponent |
1081 | 0 | xContainer->insertByIndex( nIndex, |
1082 | 0 | Any( Reference< XFormComponent >( xCurrentChild, UNO_QUERY ) ) ); |
1083 | 0 | } |
1084 | 0 | else |
1085 | 0 | { |
1086 | 0 | xContainer->insertByIndex( nIndex, |
1087 | 0 | Any( Reference< XForm >( xCurrentChild, UNO_QUERY ) ) ); |
1088 | 0 | } |
1089 | |
|
1090 | 0 | if (aEvts.hasElements()) |
1091 | 0 | { |
1092 | 0 | xManager.set(xContainer, UNO_QUERY); |
1093 | 0 | if (xManager.is()) |
1094 | 0 | xManager->registerScriptEvents(nIndex, aEvts); |
1095 | 0 | } |
1096 | |
|
1097 | 0 | GetNavModel()->m_pPropChangeList->UnLock(); |
1098 | | |
1099 | | // give an entry the new parent |
1100 | 0 | pCurrentUserData->SetParent(pTargetData); |
1101 | | |
1102 | | // give parent the new child |
1103 | 0 | if (pTargetData) |
1104 | 0 | pTargetData->GetChildList()->insert( std::unique_ptr<FmEntryData>(pCurrentUserData), nIndex ); |
1105 | 0 | else |
1106 | 0 | GetNavModel()->GetRootList()->insert( std::unique_ptr<FmEntryData>(pCurrentUserData), nIndex ); |
1107 | | |
1108 | | // announce to myself and reselect |
1109 | 0 | std::unique_ptr<weld::TreeIter> xNew = Insert( pCurrentUserData, nIndex ); |
1110 | 0 | if (bFirstEntry && xNew) |
1111 | 0 | { |
1112 | 0 | if (m_xTreeView->iter_parent(*xNew)) |
1113 | 0 | m_xTreeView->expand_row(*xNew); |
1114 | 0 | } |
1115 | 0 | } |
1116 | |
|
1117 | 0 | UnlockSelectionHandling(); |
1118 | |
|
1119 | 0 | if( bUndo ) |
1120 | 0 | pFormModel->EndUndo(); |
1121 | | |
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 | 0 | SynchronizeSelection(); |
1126 | | |
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 | 0 | if( pFormShell && pFormShell->GetImpl() && pFormShell->GetFormView() ) |
1130 | 0 | pFormShell->GetImpl()->DetermineSelection_Lock( pFormShell->GetFormView()->GetMarkedObjectList() ); |
1131 | |
|
1132 | 0 | if ( m_aControlExchange.isClipboardOwner() && ( DND_ACTION_MOVE == _nAction ) ) |
1133 | 0 | m_aControlExchange->clear(); |
1134 | |
|
1135 | 0 | return _nAction; |
1136 | 0 | } |
1137 | | |
1138 | | sal_Int8 NavigatorTree::ExecuteDrop( const ExecuteDropEvent& rEvt ) |
1139 | 0 | { |
1140 | 0 | sal_Int8 nResult( DND_ACTION_NONE ); |
1141 | 0 | if ( m_aControlExchange.isDragSource() ) |
1142 | 0 | nResult = implExecuteDataTransfer( *m_aControlExchange, rEvt.mnAction, rEvt.maPosPixel, true ); |
1143 | 0 | else |
1144 | 0 | { |
1145 | 0 | OControlTransferData aDroppedData( rEvt.maDropEvent.Transferable ); |
1146 | 0 | nResult = implExecuteDataTransfer( aDroppedData, rEvt.mnAction, rEvt.maPosPixel, true ); |
1147 | 0 | } |
1148 | 0 | return nResult; |
1149 | 0 | } |
1150 | | |
1151 | | void NavigatorTree::doPaste() |
1152 | 0 | { |
1153 | 0 | std::unique_ptr<weld::TreeIter> xSelected(m_xTreeView->make_iterator()); |
1154 | 0 | if (!m_xTreeView->get_selected(xSelected.get())) |
1155 | 0 | xSelected.reset(); |
1156 | |
|
1157 | 0 | try |
1158 | 0 | { |
1159 | 0 | if ( m_aControlExchange.isClipboardOwner() ) |
1160 | 0 | { |
1161 | 0 | implExecuteDataTransfer( *m_aControlExchange, doingKeyboardCut( ) ? DND_ACTION_MOVE : DND_ACTION_COPY, xSelected.get(), false ); |
1162 | 0 | } |
1163 | 0 | else |
1164 | 0 | { |
1165 | | // the clipboard content |
1166 | 0 | Reference< XClipboard > xClipboard(m_xTreeView->get_clipboard()); |
1167 | 0 | Reference< XTransferable > xTransferable; |
1168 | 0 | if ( xClipboard.is() ) |
1169 | 0 | xTransferable = xClipboard->getContents(); |
1170 | |
|
1171 | 0 | OControlTransferData aClipboardContent( xTransferable ); |
1172 | 0 | implExecuteDataTransfer( aClipboardContent, DND_ACTION_COPY, xSelected.get(), false ); |
1173 | 0 | } |
1174 | 0 | } |
1175 | 0 | catch( const Exception& ) |
1176 | 0 | { |
1177 | 0 | TOOLS_WARN_EXCEPTION( "svx", "NavigatorTree::doPaste" ); |
1178 | 0 | } |
1179 | 0 | } |
1180 | | |
1181 | | void NavigatorTree::doCopy() |
1182 | 0 | { |
1183 | 0 | if ( implPrepareExchange( DND_ACTION_COPY ) ) |
1184 | 0 | { |
1185 | 0 | m_aControlExchange.setClipboardListener( LINK( this, NavigatorTree, OnClipboardAction ) ); |
1186 | 0 | m_aControlExchange.copyToClipboard(*m_xTreeView); |
1187 | 0 | } |
1188 | 0 | } |
1189 | | |
1190 | | void NavigatorTree::ModelHasRemoved(const weld::TreeIter* pTypedEntry) |
1191 | 0 | { |
1192 | 0 | if (doingKeyboardCut()) |
1193 | 0 | { |
1194 | 0 | auto aIter = std::find_if(m_aCutEntries.begin(), m_aCutEntries.end(), |
1195 | 0 | [this, pTypedEntry](const auto& rElem) { |
1196 | 0 | return m_xTreeView->iter_compare(*rElem, *pTypedEntry) == 0; |
1197 | 0 | }); |
1198 | 0 | if (aIter != m_aCutEntries.end()) |
1199 | 0 | m_aCutEntries.erase(aIter); |
1200 | 0 | } |
1201 | |
|
1202 | 0 | if (m_aControlExchange.isDataExchangeActive()) |
1203 | 0 | { |
1204 | 0 | if (0 == m_aControlExchange->onEntryRemoved(m_xTreeView.get(), pTypedEntry)) |
1205 | 0 | { |
1206 | | // last of the entries which we put into the clipboard has been deleted from the tree. |
1207 | | // Give up the clipboard ownership. |
1208 | 0 | m_aControlExchange.clear(); |
1209 | 0 | } |
1210 | 0 | } |
1211 | 0 | } |
1212 | | |
1213 | | void NavigatorTree::doCut() |
1214 | 0 | { |
1215 | 0 | if ( !implPrepareExchange( DND_ACTION_MOVE ) ) |
1216 | 0 | return; |
1217 | | |
1218 | 0 | m_aControlExchange.setClipboardListener( LINK( this, NavigatorTree, OnClipboardAction ) ); |
1219 | 0 | m_aControlExchange.copyToClipboard(*m_xTreeView); |
1220 | 0 | m_bKeyboardCut = true; |
1221 | | |
1222 | | // mark all the entries we just "cut" into the clipboard as "nearly moved" |
1223 | 0 | for (const auto& rEntry : m_arrCurrentSelection ) |
1224 | 0 | { |
1225 | 0 | if (!rEntry) |
1226 | 0 | continue; |
1227 | 0 | m_aCutEntries.emplace(m_xTreeView->make_iterator(rEntry.get())); |
1228 | 0 | m_xTreeView->set_sensitive(*rEntry, false); |
1229 | 0 | } |
1230 | 0 | } |
1231 | | |
1232 | | IMPL_LINK(NavigatorTree, KeyInputHdl, const ::KeyEvent&, rKEvt, bool) |
1233 | 0 | { |
1234 | 0 | const vcl::KeyCode& rCode = rKEvt.GetKeyCode(); |
1235 | | |
1236 | | // delete? |
1237 | 0 | if (rCode.GetCode() == KEY_DELETE && !rCode.GetModifier()) |
1238 | 0 | { |
1239 | 0 | DeleteSelection(); |
1240 | 0 | return true; |
1241 | 0 | } |
1242 | | |
1243 | | // copy'n'paste? |
1244 | 0 | switch ( rCode.GetFunction() ) |
1245 | 0 | { |
1246 | 0 | case KeyFuncType::CUT: |
1247 | 0 | doCut(); |
1248 | 0 | break; |
1249 | | |
1250 | 0 | case KeyFuncType::PASTE: |
1251 | 0 | if ( implAcceptPaste() ) |
1252 | 0 | doPaste(); |
1253 | 0 | break; |
1254 | | |
1255 | 0 | case KeyFuncType::COPY: |
1256 | 0 | doCopy(); |
1257 | 0 | break; |
1258 | | |
1259 | 0 | default: |
1260 | 0 | break; |
1261 | 0 | } |
1262 | | |
1263 | 0 | return false; |
1264 | 0 | } |
1265 | | |
1266 | | IMPL_LINK(NavigatorTree, EditingEntryHdl, const weld::TreeIter&, rIter, bool) |
1267 | 0 | { |
1268 | | // root, which isn't allowed to be renamed, has UserData=NULL |
1269 | 0 | m_bEditing = !m_xTreeView->get_id(rIter).isEmpty(); |
1270 | 0 | return m_bEditing; |
1271 | 0 | } |
1272 | | |
1273 | | void NavigatorTree::NewForm(const weld::TreeIter& rParentEntry) |
1274 | 0 | { |
1275 | | // get ParentFormData |
1276 | 0 | if (!IsFormEntry(rParentEntry)) |
1277 | 0 | return; |
1278 | | |
1279 | 0 | FmFormData* pParentFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(rParentEntry)); |
1280 | | |
1281 | | |
1282 | | // create new form |
1283 | 0 | const Reference<XComponentContext>& xContext = comphelper::getProcessComponentContext(); |
1284 | 0 | Reference< XForm > xNewForm(xContext->getServiceManager()->createInstanceWithContext(FM_SUN_COMPONENT_FORM, xContext), UNO_QUERY); |
1285 | 0 | if (!xNewForm.is()) |
1286 | 0 | return; |
1287 | | |
1288 | 0 | Reference< XPropertySet > xPropertySet(xNewForm, UNO_QUERY); |
1289 | 0 | if (!xPropertySet.is()) |
1290 | 0 | return; |
1291 | | |
1292 | 0 | FmFormData* pNewFormData = new FmFormData(xNewForm, pParentFormData); |
1293 | | |
1294 | | |
1295 | | // set name |
1296 | 0 | OUString aName = GenerateName(*pNewFormData); |
1297 | 0 | pNewFormData->SetText(aName); |
1298 | |
|
1299 | 0 | try |
1300 | 0 | { |
1301 | 0 | xPropertySet->setPropertyValue( FM_PROP_NAME, Any(aName) ); |
1302 | | // a form should always have the command type table as default |
1303 | 0 | xPropertySet->setPropertyValue( FM_PROP_COMMANDTYPE, Any(sal_Int32(CommandType::TABLE))); |
1304 | 0 | } |
1305 | 0 | catch ( const Exception& ) |
1306 | 0 | { |
1307 | 0 | OSL_FAIL("NavigatorTree::NewForm : could not set essential properties!"); |
1308 | 0 | } |
1309 | | |
1310 | | |
1311 | | // insert form |
1312 | 0 | GetNavModel()->Insert(pNewFormData, SAL_MAX_UINT32, true); |
1313 | | |
1314 | | |
1315 | | // set new form as active |
1316 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1317 | 0 | if( pFormShell ) |
1318 | 0 | { |
1319 | 0 | InterfaceBag aSelection; |
1320 | 0 | aSelection.insert( Reference<XInterface>( xNewForm, UNO_QUERY ) ); |
1321 | 0 | pFormShell->GetImpl()->setCurrentSelection_Lock(std::move(aSelection)); |
1322 | |
|
1323 | 0 | pFormShell->GetViewShell()->GetViewFrame().GetBindings().Invalidate(SID_FM_PROPERTIES, true, true); |
1324 | 0 | } |
1325 | 0 | GetNavModel()->SetModified(); |
1326 | | |
1327 | | // switch to EditMode |
1328 | 0 | std::unique_ptr<weld::TreeIter> xNewEntry = FindEntry(pNewFormData); |
1329 | 0 | m_xTreeView->start_editing(*xNewEntry); |
1330 | 0 | m_bEditing = true; |
1331 | 0 | } |
1332 | | |
1333 | | FmControlData* NavigatorTree::NewControl(const OUString& rServiceName, const weld::TreeIter& rParentEntry, bool bEditName) |
1334 | 0 | { |
1335 | | // get ParentForm |
1336 | 0 | if (!GetNavModel()->GetFormShell()) |
1337 | 0 | return nullptr; |
1338 | 0 | if (!IsFormEntry(rParentEntry)) |
1339 | 0 | return nullptr; |
1340 | | |
1341 | 0 | FmFormData* pParentFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(rParentEntry)); |
1342 | 0 | Reference<XForm> xParentForm(pParentFormData->GetFormIface()); |
1343 | | |
1344 | | // create new component |
1345 | 0 | const Reference<XComponentContext>& xContext = comphelper::getProcessComponentContext(); |
1346 | 0 | Reference<XFormComponent> xNewComponent( xContext->getServiceManager()->createInstanceWithContext(rServiceName, xContext), UNO_QUERY); |
1347 | 0 | if (!xNewComponent.is()) |
1348 | 0 | return nullptr; |
1349 | | |
1350 | 0 | FmControlData* pNewFormControlData = new FmControlData(xNewComponent, pParentFormData); |
1351 | | |
1352 | | // set name |
1353 | 0 | OUString sName = FmFormPageImpl::setUniqueName( xNewComponent, xParentForm ); |
1354 | |
|
1355 | 0 | pNewFormControlData->SetText( sName ); |
1356 | | |
1357 | | // insert FormComponent |
1358 | 0 | GetNavModel()->Insert(pNewFormControlData, SAL_MAX_UINT32, true); |
1359 | 0 | GetNavModel()->SetModified(); |
1360 | |
|
1361 | 0 | if (bEditName) |
1362 | 0 | { |
1363 | | // switch to EditMode |
1364 | 0 | std::unique_ptr<weld::TreeIter> xNewEntry = FindEntry( pNewFormControlData ); |
1365 | 0 | m_xTreeView->select(*xNewEntry); |
1366 | |
|
1367 | 0 | m_xTreeView->start_editing(*xNewEntry); |
1368 | 0 | m_bEditing = true; |
1369 | 0 | } |
1370 | |
|
1371 | 0 | return pNewFormControlData; |
1372 | 0 | } |
1373 | | |
1374 | | OUString NavigatorTree::GenerateName(const FmEntryData& rEntryData) |
1375 | 0 | { |
1376 | 0 | const sal_uInt16 nMaxCount = 99; |
1377 | 0 | OUString aNewName; |
1378 | | |
1379 | | // create base name |
1380 | 0 | OUString aBaseName; |
1381 | 0 | if( dynamic_cast<const FmFormData*>(&rEntryData) != nullptr ) |
1382 | 0 | aBaseName = SvxResId( RID_STR_STDFORMNAME ); |
1383 | 0 | else if( dynamic_cast<const FmControlData*>(&rEntryData) != nullptr ) |
1384 | 0 | aBaseName = SvxResId( RID_STR_CONTROL ); |
1385 | | |
1386 | | |
1387 | | // create new name |
1388 | 0 | FmFormData* pFormParentData = static_cast<FmFormData*>(rEntryData.GetParent()); |
1389 | |
|
1390 | 0 | for( sal_Int32 i=0; i<nMaxCount; i++ ) |
1391 | 0 | { |
1392 | 0 | aNewName = aBaseName; |
1393 | 0 | if( i>0 ) |
1394 | 0 | { |
1395 | 0 | aNewName += " " + OUString::number(i); |
1396 | 0 | } |
1397 | |
|
1398 | 0 | if( GetNavModel()->FindData(aNewName, pFormParentData,false) == nullptr ) |
1399 | 0 | break; |
1400 | 0 | } |
1401 | |
|
1402 | 0 | return aNewName; |
1403 | 0 | } |
1404 | | |
1405 | | IMPL_LINK(NavigatorTree, EditedEntryHdl, const IterString&, rIterString, bool) |
1406 | 0 | { |
1407 | 0 | m_bEditing = false; |
1408 | |
|
1409 | 0 | const weld::TreeIter& rIter = rIterString.first; |
1410 | |
|
1411 | 0 | FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rIter)); |
1412 | 0 | bool bRes = NavigatorTreeModel::Rename(pEntryData, rIterString.second); |
1413 | 0 | if (!bRes) |
1414 | 0 | { |
1415 | 0 | m_xEditEntry = m_xTreeView->make_iterator(&rIter); |
1416 | 0 | nEditEvent = Application::PostUserEvent(LINK(this, NavigatorTree, OnEdit)); |
1417 | 0 | } |
1418 | |
|
1419 | 0 | return bRes; |
1420 | 0 | } |
1421 | | |
1422 | | IMPL_LINK_NOARG(NavigatorTree, OnEdit, void*, void) |
1423 | 0 | { |
1424 | 0 | nEditEvent = nullptr; |
1425 | 0 | m_xTreeView->start_editing(*m_xEditEntry); |
1426 | 0 | m_bEditing = true; |
1427 | 0 | m_xEditEntry.reset(); |
1428 | 0 | } |
1429 | | |
1430 | | IMPL_LINK_NOARG(NavigatorTree, OnEntrySelDesel, weld::TreeView&, void) |
1431 | 0 | { |
1432 | 0 | m_sdiState = SDI_DIRTY; |
1433 | |
|
1434 | 0 | if (IsSelectionHandlingLocked()) |
1435 | 0 | return; |
1436 | | |
1437 | 0 | if (m_aSynchronizeTimer.IsActive()) |
1438 | 0 | m_aSynchronizeTimer.Stop(); |
1439 | |
|
1440 | 0 | m_aSynchronizeTimer.SetTimeout(EXPLORER_SYNC_DELAY); |
1441 | 0 | m_aSynchronizeTimer.Start(); |
1442 | 0 | } |
1443 | | |
1444 | | IMPL_LINK_NOARG(NavigatorTree, OnSynchronizeTimer, Timer *, void) |
1445 | 0 | { |
1446 | 0 | SynchronizeMarkList(); |
1447 | 0 | } |
1448 | | |
1449 | | IMPL_LINK_NOARG(NavigatorTree, OnClipboardAction, OLocalExchange&, void) |
1450 | 0 | { |
1451 | 0 | if ( m_aControlExchange.isClipboardOwner() ) |
1452 | 0 | return; |
1453 | | |
1454 | 0 | if ( !doingKeyboardCut() ) |
1455 | 0 | return; |
1456 | | |
1457 | 0 | for (const auto& rEntry : m_aCutEntries) |
1458 | 0 | { |
1459 | 0 | if (!rEntry) |
1460 | 0 | continue; |
1461 | 0 | m_xTreeView->set_sensitive(*rEntry, true); |
1462 | 0 | } |
1463 | 0 | ListBoxEntrySet().swap(m_aCutEntries); |
1464 | |
|
1465 | 0 | m_bKeyboardCut = false; |
1466 | 0 | } |
1467 | | |
1468 | | void NavigatorTree::ShowSelectionProperties(bool bForce) |
1469 | 0 | { |
1470 | | // at first i need the FormShell |
1471 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1472 | 0 | if (!pFormShell) |
1473 | | // no shell -> impossible to set curObject -> leave |
1474 | 0 | return; |
1475 | | |
1476 | 0 | CollectSelectionData(SDI_ALL); |
1477 | 0 | SAL_WARN_IF(static_cast<size_t>(m_nFormsSelected + m_nControlsSelected |
1478 | 0 | + (m_bRootSelected ? 1 : 0)) != m_arrCurrentSelection.size(), |
1479 | 0 | "svx.form", |
1480 | 0 | "NavigatorTree::ShowSelectionProperties : selection meta data invalid !"); |
1481 | | |
1482 | | |
1483 | 0 | InterfaceBag aSelection; |
1484 | 0 | bool bSetSelectionAsMarkList = false; |
1485 | |
|
1486 | 0 | if (m_bRootSelected) |
1487 | 0 | ; // no properties for the root, neither for single nor for multi selection |
1488 | 0 | else if ( m_nFormsSelected + m_nControlsSelected == 0 ) // none of the two should be less 0 |
1489 | 0 | ; // no selection -> no properties |
1490 | 0 | else if ( m_nFormsSelected * m_nControlsSelected != 0 ) |
1491 | 0 | ; // mixed selection -> no properties |
1492 | 0 | else |
1493 | 0 | { // either only forms, or only controls are selected |
1494 | 0 | if (m_arrCurrentSelection.size() == 1) |
1495 | 0 | { |
1496 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *m_arrCurrentSelection.begin(); |
1497 | 0 | if (m_nFormsSelected > 0) |
1498 | 0 | { // exactly one form is selected |
1499 | 0 | FmFormData* pFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(*rIter)); |
1500 | 0 | aSelection.insert( Reference< XInterface >( pFormData->GetFormIface(), UNO_QUERY ) ); |
1501 | 0 | } |
1502 | 0 | else |
1503 | 0 | { // exactly one control is selected (whatever hidden or normal) |
1504 | 0 | FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rIter)); |
1505 | |
|
1506 | 0 | aSelection.insert( Reference< XInterface >( pEntryData->GetElement(), UNO_QUERY ) ); |
1507 | 0 | } |
1508 | 0 | } |
1509 | 0 | else |
1510 | 0 | { // it's a MultiSelection, so we must build a MultiSet |
1511 | 0 | if (m_nFormsSelected > 0) |
1512 | 0 | { // ... only forms |
1513 | | // first of all collect PropertySet-Interfaces of the forms |
1514 | 0 | SvLBoxEntrySortedArray::const_iterator it = m_arrCurrentSelection.begin(); |
1515 | 0 | for ( sal_Int32 i = 0; i < m_nFormsSelected; ++i ) |
1516 | 0 | { |
1517 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *it; |
1518 | 0 | FmFormData* pFormData = weld::fromId<FmFormData*>(m_xTreeView->get_id(*rIter)); |
1519 | 0 | aSelection.insert( pFormData->GetPropertySet() ); |
1520 | 0 | ++it; |
1521 | 0 | } |
1522 | 0 | } |
1523 | 0 | else |
1524 | 0 | { // ... only controls |
1525 | 0 | if (m_nHiddenControls == m_nControlsSelected) |
1526 | 0 | { // a MultiSet for properties of hidden controls |
1527 | 0 | SvLBoxEntrySortedArray::const_iterator it = m_arrCurrentSelection.begin(); |
1528 | 0 | for ( sal_Int32 i = 0; i < m_nHiddenControls; ++i ) |
1529 | 0 | { |
1530 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *it; |
1531 | 0 | FmEntryData* pEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rIter)); |
1532 | 0 | aSelection.insert( pEntryData->GetPropertySet() ); |
1533 | 0 | ++it; |
1534 | 0 | } |
1535 | 0 | } |
1536 | 0 | else if (m_nHiddenControls == 0) |
1537 | 0 | { // only normal controls |
1538 | 0 | bSetSelectionAsMarkList = true; |
1539 | 0 | } |
1540 | 0 | } |
1541 | 0 | } |
1542 | |
|
1543 | 0 | } |
1544 | | |
1545 | | // and now my form and my SelObject |
1546 | 0 | if ( bSetSelectionAsMarkList ) |
1547 | 0 | pFormShell->GetImpl()->setCurrentSelectionFromMark_Lock(pFormShell->GetFormView()->GetMarkedObjectList()); |
1548 | 0 | else |
1549 | 0 | pFormShell->GetImpl()->setCurrentSelection_Lock(std::move(aSelection)); |
1550 | |
|
1551 | 0 | if (pFormShell->GetImpl()->IsPropBrwOpen_Lock() || bForce) |
1552 | 0 | { |
1553 | | // and now deliver all to the PropertyBrowser |
1554 | 0 | pFormShell->GetViewShell()->GetViewFrame().GetDispatcher()->Execute( SID_FM_SHOW_PROPERTY_BROWSER, SfxCallMode::ASYNCHRON ); |
1555 | 0 | } |
1556 | 0 | } |
1557 | | |
1558 | | |
1559 | | void NavigatorTree::DeleteSelection() |
1560 | 0 | { |
1561 | | // of course, i can't delete root |
1562 | 0 | bool bRootSelected = m_xTreeView->is_selected(*m_xRootEntry); |
1563 | 0 | auto nSelectedEntries = m_xTreeView->count_selected_rows(); |
1564 | 0 | if (bRootSelected && (nSelectedEntries > 1)) // root and other elements ? |
1565 | 0 | m_xTreeView->unselect(*m_xRootEntry); // yes -> remove root from selection |
1566 | |
|
1567 | 0 | if ((nSelectedEntries == 0) || bRootSelected) // still root ? |
1568 | 0 | return; // -> only selected element -> leave |
1569 | | |
1570 | 0 | DBG_ASSERT(!m_bPrevSelectionMixed, "NavigatorTree::DeleteSelection() : delete permitted if mark and selection are inconsistent"); |
1571 | | |
1572 | | // i need the FormModel later |
1573 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1574 | 0 | if (!pFormShell) |
1575 | 0 | return; |
1576 | 0 | FmFormModel* pFormModel = pFormShell->GetFormModel(); |
1577 | 0 | if (!pFormModel) |
1578 | 0 | return; |
1579 | | |
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 | 0 | CollectSelectionData( SDI_NORMALIZED ); |
1585 | | |
1586 | | // see below for why we need this mapping from models to shapes |
1587 | 0 | FmFormView* pFormView = pFormShell->GetFormView(); |
1588 | 0 | SdrPageView* pPageView = pFormView ? pFormView->GetSdrPageView() : nullptr; |
1589 | 0 | SdrPage* pPage = pPageView ? pPageView->GetPage() : nullptr; |
1590 | 0 | DBG_ASSERT( pPage, "NavigatorTree::DeleteSelection: invalid form page!" ); |
1591 | |
|
1592 | 0 | MapModelToShape aModelShapes; |
1593 | 0 | if ( pPage ) |
1594 | 0 | collectShapeModelMapping( pPage, aModelShapes ); |
1595 | | |
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 | 0 | pFormShell->GetImpl()->EnableTrackProperties_Lock(false); |
1604 | 0 | for (SvLBoxEntrySortedArray::reverse_iterator it = m_arrCurrentSelection.rbegin(); |
1605 | 0 | it != m_arrCurrentSelection.rend(); ) |
1606 | 0 | { |
1607 | 0 | const std::unique_ptr<weld::TreeIter>& rIter = *it; |
1608 | 0 | FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rIter)); |
1609 | | |
1610 | | // a form ? |
1611 | 0 | auto pFormData = dynamic_cast<FmFormData*>(pCurrent); |
1612 | | |
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 | 0 | if (pFormData) |
1617 | 0 | MarkViewObj(pFormData, true/*deep*/); |
1618 | | |
1619 | | // a hidden control ? |
1620 | 0 | bool bIsHidden = IsHiddenControl(pCurrent); |
1621 | | |
1622 | | // keep forms and hidden controls, the rest not |
1623 | 0 | if (!pFormData && !bIsHidden) |
1624 | 0 | { |
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 | 0 | if ( aModelShapes.find( pCurrent->GetElement() ) != aModelShapes.end() ) |
1629 | 0 | { |
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 | 0 | m_arrCurrentSelection.erase( --(it.base()) ); |
1635 | 0 | } |
1636 | 0 | else |
1637 | 0 | ++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. |
1640 | 0 | } |
1641 | 0 | else |
1642 | 0 | ++it; |
1643 | 0 | } |
1644 | 0 | pFormShell->GetImpl()->EnableTrackProperties_Lock(true); |
1645 | | |
1646 | | // let the view delete the marked controls |
1647 | 0 | pFormShell->GetFormView()->DeleteMarked(); |
1648 | | |
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# |
1653 | 0 | { |
1654 | | |
1655 | | // initialize UNDO |
1656 | 0 | OUString aUndoStr; |
1657 | 0 | if ( m_arrCurrentSelection.size() == 1 ) |
1658 | 0 | { |
1659 | 0 | aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_REMOVE); |
1660 | 0 | if (m_nFormsSelected) |
1661 | 0 | aUndoStr = aUndoStr.replaceFirst( "#", SvxResId( RID_STR_FORM ) ); |
1662 | 0 | else |
1663 | | // it must be a control (else the root would be selected, but it cannot be deleted) |
1664 | 0 | aUndoStr = aUndoStr.replaceFirst( "#", SvxResId( RID_STR_CONTROL ) ); |
1665 | 0 | } |
1666 | 0 | else |
1667 | 0 | { |
1668 | 0 | aUndoStr = SvxResId(RID_STR_UNDO_CONTAINER_REMOVE_MULTIPLE); |
1669 | 0 | aUndoStr = aUndoStr.replaceFirst( "#", OUString::number( m_arrCurrentSelection.size() ) ); |
1670 | 0 | } |
1671 | 0 | pFormModel->BegUndo(aUndoStr); |
1672 | 0 | } |
1673 | | |
1674 | | // remove remaining structure |
1675 | 0 | for (const auto& rpSelection : m_arrCurrentSelection) |
1676 | 0 | { |
1677 | 0 | FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(*rpSelection)); |
1678 | | |
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 | 0 | if ( pCurrent->GetChildList()->size() ) |
1683 | 0 | continue; |
1684 | | |
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 | 0 | if (auto pFormData = dynamic_cast<FmFormData*>( pCurrent)) |
1688 | 0 | { |
1689 | 0 | Reference< XForm > xCurrentForm( pFormData->GetFormIface() ); |
1690 | 0 | if (pFormShell->GetImpl()->getCurrentForm_Lock() == xCurrentForm) // shell knows form to be deleted ? |
1691 | 0 | pFormShell->GetImpl()->forgetCurrentForm_Lock(); // -> take away ... |
1692 | 0 | } |
1693 | 0 | GetNavModel()->Remove(pCurrent, true); |
1694 | 0 | } |
1695 | 0 | pFormModel->EndUndo(); |
1696 | 0 | } |
1697 | | |
1698 | | |
1699 | | void NavigatorTree::CollectSelectionData(SELDATA_ITEMS sdiHow) |
1700 | 0 | { |
1701 | 0 | DBG_ASSERT(sdiHow != SDI_DIRTY, "NavigatorTree::CollectSelectionData : ever thought about your parameter ? DIRTY ?"); |
1702 | 0 | if (sdiHow == m_sdiState) |
1703 | 0 | return; |
1704 | | |
1705 | 0 | m_arrCurrentSelection.clear(); |
1706 | 0 | m_nFormsSelected = m_nControlsSelected = m_nHiddenControls = 0; |
1707 | 0 | m_bRootSelected = false; |
1708 | |
|
1709 | 0 | m_xTreeView->selected_foreach([this, sdiHow](weld::TreeIter& rSelectionLoop){ |
1710 | | // count different elements |
1711 | 0 | if (m_xTreeView->iter_compare(rSelectionLoop, *m_xRootEntry) == 0) |
1712 | 0 | m_bRootSelected = true; |
1713 | 0 | else |
1714 | 0 | { |
1715 | 0 | if (IsFormEntry(rSelectionLoop)) |
1716 | 0 | ++m_nFormsSelected; |
1717 | 0 | else |
1718 | 0 | { |
1719 | 0 | ++m_nControlsSelected; |
1720 | 0 | if (IsHiddenControl(weld::fromId<FmEntryData*>(m_xTreeView->get_id(rSelectionLoop)))) |
1721 | 0 | ++m_nHiddenControls; |
1722 | 0 | } |
1723 | 0 | } |
1724 | |
|
1725 | 0 | if (sdiHow == SDI_NORMALIZED) |
1726 | 0 | { |
1727 | | // don't take something with a selected ancestor |
1728 | 0 | if (m_xTreeView->iter_compare(rSelectionLoop, *m_xRootEntry) == 0) |
1729 | 0 | m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop)); |
1730 | 0 | else |
1731 | 0 | { |
1732 | 0 | std::unique_ptr<weld::TreeIter> xParentLoop(m_xTreeView->make_iterator(&rSelectionLoop)); |
1733 | 0 | bool bParentLoop = m_xTreeView->iter_parent(*xParentLoop); |
1734 | 0 | while (bParentLoop) |
1735 | 0 | { |
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 | 0 | if (m_xTreeView->is_selected(*xParentLoop)) |
1741 | 0 | break; |
1742 | 0 | else |
1743 | 0 | { |
1744 | 0 | if (m_xTreeView->iter_compare(*xParentLoop, *m_xRootEntry) == 0) |
1745 | 0 | { |
1746 | | // until root (exclusive), there was no selected parent -> entry belongs to normalized list |
1747 | 0 | m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop)); |
1748 | 0 | break; |
1749 | 0 | } |
1750 | 0 | else |
1751 | 0 | bParentLoop = m_xTreeView->iter_parent(*xParentLoop); |
1752 | 0 | } |
1753 | 0 | } |
1754 | 0 | } |
1755 | 0 | } |
1756 | 0 | else if (sdiHow == SDI_NORMALIZED_FORMARK) |
1757 | 0 | { |
1758 | 0 | std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(&rSelectionLoop)); |
1759 | 0 | bool bParent = m_xTreeView->iter_parent(*xParent); |
1760 | 0 | if (!bParent || !m_xTreeView->is_selected(*xParent) || IsFormEntry(rSelectionLoop)) |
1761 | 0 | m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop)); |
1762 | 0 | } |
1763 | 0 | else |
1764 | 0 | m_arrCurrentSelection.emplace(m_xTreeView->make_iterator(&rSelectionLoop)); |
1765 | |
|
1766 | 0 | return false; |
1767 | 0 | }); |
1768 | |
|
1769 | 0 | m_sdiState = sdiHow; |
1770 | 0 | } |
1771 | | |
1772 | | void NavigatorTree::SynchronizeSelection(FmEntryDataArray& arredToSelect) |
1773 | 0 | { |
1774 | 0 | LockSelectionHandling(); |
1775 | 0 | if (arredToSelect.empty()) |
1776 | 0 | { |
1777 | 0 | m_xTreeView->unselect_all(); |
1778 | 0 | } |
1779 | 0 | else |
1780 | 0 | { |
1781 | | // compare current selection with requested SelectList |
1782 | 0 | m_xTreeView->selected_foreach([this, &arredToSelect](weld::TreeIter& rSelection) { |
1783 | 0 | FmEntryData* pCurrent = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rSelection)); |
1784 | 0 | if (pCurrent != nullptr) |
1785 | 0 | { |
1786 | 0 | FmEntryDataArray::iterator it = arredToSelect.find(pCurrent); |
1787 | 0 | if ( it != arredToSelect.end() ) |
1788 | 0 | { // entry already selected, but also in SelectList |
1789 | | // remove it from there |
1790 | 0 | arredToSelect.erase(it); |
1791 | 0 | } else |
1792 | 0 | { // entry selected, but not in SelectList -> remove selection |
1793 | 0 | 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 | 0 | m_xTreeView->scroll_to_row(rSelection); |
1797 | 0 | } |
1798 | 0 | } |
1799 | 0 | else |
1800 | 0 | m_xTreeView->unselect(rSelection); |
1801 | |
|
1802 | 0 | return false; |
1803 | 0 | }); |
1804 | | |
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 | 0 | m_xTreeView->all_foreach([this, &arredToSelect](weld::TreeIter& rLoop){ |
1814 | 0 | FmEntryData* pCurEntryData = weld::fromId<FmEntryData*>(m_xTreeView->get_id(rLoop)); |
1815 | 0 | FmEntryDataArray::iterator it = arredToSelect.find(pCurEntryData); |
1816 | 0 | if (it != arredToSelect.end()) |
1817 | 0 | { |
1818 | 0 | m_xTreeView->select(rLoop); |
1819 | 0 | m_xTreeView->scroll_to_row(rLoop); |
1820 | 0 | } |
1821 | |
|
1822 | 0 | return false; |
1823 | 0 | }); |
1824 | 0 | } |
1825 | 0 | UnlockSelectionHandling(); |
1826 | 0 | } |
1827 | | |
1828 | | |
1829 | | void NavigatorTree::SynchronizeSelection() |
1830 | 0 | { |
1831 | | // shell and view |
1832 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1833 | 0 | if(!pFormShell) return; |
1834 | | |
1835 | 0 | FmFormView* pFormView = pFormShell->GetFormView(); |
1836 | 0 | if (!pFormView) return; |
1837 | | |
1838 | 0 | GetNavModel()->BroadcastMarkedObjects(pFormView->GetMarkedObjectList()); |
1839 | 0 | } |
1840 | | |
1841 | | |
1842 | | void NavigatorTree::SynchronizeMarkList() |
1843 | 0 | { |
1844 | | // i'll need this shell |
1845 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1846 | 0 | if (!pFormShell) return; |
1847 | | |
1848 | 0 | CollectSelectionData(SDI_NORMALIZED_FORMARK); |
1849 | | |
1850 | | // the view shouldn't notify now if MarkList changed |
1851 | 0 | pFormShell->GetImpl()->EnableTrackProperties_Lock(false); |
1852 | |
|
1853 | 0 | UnmarkAllViewObj(); |
1854 | |
|
1855 | 0 | for (auto& rSelectionLoop : m_arrCurrentSelection) |
1856 | 0 | { |
1857 | | // When form selection, mark all controls of form |
1858 | 0 | if (IsFormEntry(*rSelectionLoop) && m_xTreeView->iter_compare(*rSelectionLoop, *m_xRootEntry) != 0) |
1859 | 0 | MarkViewObj(weld::fromId<FmFormData*>(m_xTreeView->get_id(*rSelectionLoop)), false/*deep*/); |
1860 | | |
1861 | | // When control selection, mark Control-SdrObjects |
1862 | 0 | else if (IsFormComponentEntry(*rSelectionLoop)) |
1863 | 0 | { |
1864 | 0 | FmControlData* pControlData = weld::fromId<FmControlData*>(m_xTreeView->get_id(*rSelectionLoop)); |
1865 | 0 | if (pControlData) |
1866 | 0 | { |
1867 | | |
1868 | | // When HiddenControl no object can be selected |
1869 | 0 | Reference< XFormComponent > xFormComponent( pControlData->GetFormComponent()); |
1870 | 0 | if (!xFormComponent.is()) |
1871 | 0 | continue; |
1872 | 0 | Reference< XPropertySet > xSet(xFormComponent, UNO_QUERY); |
1873 | 0 | if (!xSet.is()) |
1874 | 0 | continue; |
1875 | | |
1876 | 0 | sal_uInt16 nClassId = ::comphelper::getINT16(xSet->getPropertyValue(FM_PROP_CLASSID)); |
1877 | 0 | if (nClassId != FormComponentType::HIDDENCONTROL) |
1878 | 0 | MarkViewObj(pControlData); |
1879 | 0 | } |
1880 | 0 | } |
1881 | 0 | } |
1882 | | |
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 | 0 | ShowSelectionProperties(); |
1887 | | |
1888 | | // reset flag at view |
1889 | 0 | pFormShell->GetImpl()->EnableTrackProperties_Lock(true); |
1890 | | |
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 | 0 | if ((m_arrCurrentSelection.size() != 1) || (m_nFormsSelected != 1)) |
1895 | 0 | return; |
1896 | | |
1897 | 0 | std::unique_ptr<weld::TreeIter> xSelected(m_xTreeView->make_iterator()); |
1898 | 0 | if (!m_xTreeView->get_selected(xSelected.get())) |
1899 | 0 | xSelected.reset(); |
1900 | 0 | FmFormData* pSingleSelectionData = xSelected ? dynamic_cast<FmFormData*>(weld::fromId<FmEntryData*>(m_xTreeView->get_id(*xSelected))) |
1901 | 0 | : nullptr; |
1902 | 0 | DBG_ASSERT( pSingleSelectionData, "NavigatorTree::SynchronizeMarkList: invalid selected form!" ); |
1903 | 0 | if ( pSingleSelectionData ) |
1904 | 0 | { |
1905 | 0 | InterfaceBag aSelection; |
1906 | 0 | aSelection.insert( Reference< XInterface >( pSingleSelectionData->GetFormIface(), UNO_QUERY ) ); |
1907 | 0 | pFormShell->GetImpl()->setCurrentSelection_Lock(std::move(aSelection)); |
1908 | 0 | } |
1909 | 0 | } |
1910 | | |
1911 | | bool NavigatorTree::IsHiddenControl(FmEntryData const * pEntryData) |
1912 | 0 | { |
1913 | 0 | if (pEntryData == nullptr) return false; |
1914 | | |
1915 | 0 | const Reference< XPropertySet >& xProperties( pEntryData->GetPropertySet() ); |
1916 | 0 | if (::comphelper::hasProperty(FM_PROP_CLASSID, xProperties)) |
1917 | 0 | { |
1918 | 0 | Any aClassID = xProperties->getPropertyValue( FM_PROP_CLASSID ); |
1919 | 0 | return (::comphelper::getINT16(aClassID) == FormComponentType::HIDDENCONTROL); |
1920 | 0 | } |
1921 | 0 | return false; |
1922 | 0 | } |
1923 | | |
1924 | | void NavigatorTree::UnmarkAllViewObj() |
1925 | 0 | { |
1926 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1927 | 0 | if( !pFormShell ) |
1928 | 0 | return; |
1929 | 0 | FmFormView* pFormView = pFormShell->GetFormView(); |
1930 | 0 | pFormView->UnMarkAll(); |
1931 | 0 | } |
1932 | | |
1933 | | void NavigatorTree::MarkViewObj(FmFormData const * pFormData, bool bDeep ) |
1934 | 0 | { |
1935 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1936 | 0 | if( !pFormShell ) |
1937 | 0 | return; |
1938 | | |
1939 | | // first collect all sdrobjects |
1940 | 0 | ::std::set< Reference< XFormComponent > > aObjects; |
1941 | 0 | CollectObjects(pFormData,bDeep,aObjects); |
1942 | | |
1943 | | |
1944 | | // find and select appropriate SdrObj in page |
1945 | 0 | FmFormView* pFormView = pFormShell->GetFormView(); |
1946 | 0 | SdrPageView* pPageView = pFormView->GetSdrPageView(); |
1947 | 0 | SdrPage* pPage = pPageView->GetPage(); |
1948 | | //FmFormPage* pFormPage = dynamic_cast< FmFormPage* >( pPage ); |
1949 | |
|
1950 | 0 | SdrObjListIter aIter( pPage ); |
1951 | 0 | while ( aIter.IsMore() ) |
1952 | 0 | { |
1953 | 0 | SdrObject* pSdrObject = aIter.Next(); |
1954 | 0 | FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject ); |
1955 | 0 | if ( !pFormObject ) |
1956 | 0 | continue; |
1957 | | |
1958 | 0 | Reference< XFormComponent > xControlModel( pFormObject->GetUnoControlModel(),UNO_QUERY ); |
1959 | 0 | if ( xControlModel.is() && aObjects.find(xControlModel) != aObjects.end() && !pFormView->IsObjMarked( pSdrObject ) ) |
1960 | 0 | { |
1961 | | // unfortunately, the writer doesn't like marking an already-marked object, again, so reset the mark first |
1962 | 0 | pFormView->MarkObj( pSdrObject, pPageView ); |
1963 | 0 | } |
1964 | 0 | } // while ( aIter.IsMore() ) |
1965 | | // make the mark visible |
1966 | 0 | ::tools::Rectangle aMarkRect( pFormView->GetAllMarkedRect()); |
1967 | 0 | for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) |
1968 | 0 | { |
1969 | 0 | SdrPaintWindow* pPaintWindow = pFormView->GetPaintWindow( i ); |
1970 | 0 | OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); |
1971 | 0 | if ( ( OUTDEV_WINDOW == rOutDev.GetOutDevType() ) && !aMarkRect.IsEmpty() ) |
1972 | 0 | { |
1973 | 0 | pFormView->MakeVisible( aMarkRect, *rOutDev.GetOwnerWindow() ); |
1974 | 0 | } |
1975 | 0 | } // for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) |
1976 | 0 | } |
1977 | | |
1978 | | void NavigatorTree::CollectObjects(FmFormData const * pFormData, bool bDeep, ::std::set< Reference< XFormComponent > >& _rObjects) |
1979 | 0 | { |
1980 | 0 | FmEntryDataList* pChildList = pFormData->GetChildList(); |
1981 | 0 | for( size_t i = 0; i < pChildList->size(); ++i ) |
1982 | 0 | { |
1983 | 0 | FmEntryData* pEntryData = pChildList->at( i ); |
1984 | 0 | if( auto pControlData = dynamic_cast<FmControlData*>( pEntryData) ) |
1985 | 0 | { |
1986 | 0 | _rObjects.insert(pControlData->GetFormComponent()); |
1987 | 0 | } // if( dynamic_cast<const FmControlData*>( pEntryData) != nullptr ) |
1988 | 0 | else if (bDeep) |
1989 | 0 | if (auto pEntryFormData = dynamic_cast<FmFormData*>( pEntryData)) |
1990 | 0 | CollectObjects(pEntryFormData, bDeep, _rObjects); |
1991 | 0 | } // for( sal_uInt32 i=0; i<pChildList->Count(); i++ ) |
1992 | 0 | } |
1993 | | |
1994 | | void NavigatorTree::MarkViewObj( FmControlData const * pControlData) |
1995 | 0 | { |
1996 | 0 | if( !pControlData ) |
1997 | 0 | return; |
1998 | 0 | FmFormShell* pFormShell = GetNavModel()->GetFormShell(); |
1999 | 0 | if( !pFormShell ) |
2000 | 0 | return; |
2001 | | |
2002 | | |
2003 | | // find and select appropriate SdrObj |
2004 | 0 | FmFormView* pFormView = pFormShell->GetFormView(); |
2005 | 0 | const Reference< XFormComponent >& xFormComponent( pControlData->GetFormComponent()); |
2006 | 0 | SdrPageView* pPageView = pFormView->GetSdrPageView(); |
2007 | 0 | SdrPage* pPage = pPageView->GetPage(); |
2008 | |
|
2009 | 0 | bool bPaint = false; |
2010 | 0 | SdrObjListIter aIter( pPage ); |
2011 | 0 | while ( aIter.IsMore() ) |
2012 | 0 | { |
2013 | 0 | SdrObject* pSdrObject = aIter.Next(); |
2014 | 0 | FmFormObj* pFormObject = FmFormObj::GetFormObject( pSdrObject ); |
2015 | 0 | if ( !pFormObject ) |
2016 | 0 | continue; |
2017 | | |
2018 | 0 | Reference< XInterface > xControlModel( pFormObject->GetUnoControlModel() ); |
2019 | 0 | if ( xControlModel != xFormComponent ) |
2020 | 0 | continue; |
2021 | | |
2022 | | // mark the object |
2023 | 0 | if ( !pFormView->IsObjMarked( pSdrObject ) ) |
2024 | | // unfortunately, the writer doesn't like marking an already-marked object, again, so reset the mark first |
2025 | 0 | pFormView->MarkObj( pSdrObject, pPageView ); |
2026 | |
|
2027 | 0 | bPaint = true; |
2028 | |
|
2029 | 0 | } // while ( aIter.IsMore() ) |
2030 | 0 | if ( !bPaint ) |
2031 | 0 | return; |
2032 | | |
2033 | | // make the mark visible |
2034 | 0 | ::tools::Rectangle aMarkRect( pFormView->GetAllMarkedRect()); |
2035 | 0 | for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) |
2036 | 0 | { |
2037 | 0 | SdrPaintWindow* pPaintWindow = pFormView->GetPaintWindow( i ); |
2038 | 0 | OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); |
2039 | 0 | if ( OUTDEV_WINDOW == rOutDev.GetOutDevType() ) |
2040 | 0 | { |
2041 | 0 | pFormView->MakeVisible( aMarkRect, *rOutDev.GetOwnerWindow() ); |
2042 | 0 | } |
2043 | 0 | } // for ( sal_uInt32 i = 0; i < pFormView->PaintWindowCount(); ++i ) |
2044 | 0 | } |
2045 | | |
2046 | | |
2047 | | } |
2048 | | |
2049 | | |
2050 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |