xref: /core/sw/source/uibase/wrtsh/wrtsh2.cxx (revision aa818a0b)
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 <svl/macitem.hxx>
21 #include <sfx2/frame.hxx>
22 #include <svl/eitem.hxx>
23 #include <svl/listener.hxx>
24 #include <svl/stritem.hxx>
25 #include <sfx2/docfile.hxx>
26 #include <sfx2/dispatch.hxx>
27 #include <sfx2/linkmgr.hxx>
28 #include <sfx2/viewfrm.hxx>
29 #include <sot/exchange.hxx>
30 #include <osl/diagnose.h>
31 #include <fmtinfmt.hxx>
32 #include <wrtsh.hxx>
33 #include <docsh.hxx>
34 #include <fldbas.hxx>
35 #include <expfld.hxx>
36 #include <docufld.hxx>
37 #include <reffld.hxx>
38 #include <swundo.hxx>
39 #include <doc.hxx>
40 #include <frmfmt.hxx>
41 #include <fmtfld.hxx>
42 #include <view.hxx>
43 #include <swevent.hxx>
44 #include <section.hxx>
45 #include <navicont.hxx>
46 #include <txtinet.hxx>
47 #include <cmdid.h>
48 #include <swabstdlg.hxx>
49 #include <SwRewriter.hxx>
50 #include <authfld.hxx>
51 
52 #include <com/sun/star/document/XDocumentProperties.hpp>
53 #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
54 
55 #include <memory>
56 
57 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
58 #include <comphelper/lok.hxx>
59 #include <sfx2/event.hxx>
60 #include <sal/log.hxx>
61 
62 void SwWrtShell::Insert(SwField const& rField, SwPaM* pAnnotationRange)
63 {
64     ResetCursorStack();
65     if(!CanInsert())
66         return;
67     StartAllAction();
68 
69     SwRewriter aRewriter;
70     aRewriter.AddRule(UndoArg1, rField.GetDescription());
71 
72     StartUndo(SwUndoId::INSERT, &aRewriter);
73 
74     bool bDeleted = false;
75     std::unique_ptr<SwPaM> pAnnotationTextRange;
76     if (pAnnotationRange)
77     {
78         pAnnotationTextRange.reset(new SwPaM(*pAnnotationRange->Start(), *pAnnotationRange->End()));
79     }
80 
81     if ( HasSelection() )
82     {
83         if ( rField.GetTyp()->Which() == SwFieldIds::Postit )
84         {
85             // for annotation fields:
86             // - keep the current selection in order to create a corresponding annotation mark
87             // - collapse cursor to its end
88             if ( IsTableMode() )
89             {
90                 GetTableCrs()->Normalize( false );
91                 const SwPosition rStartPos( *(GetTableCrs()->GetMark()->nNode.GetNode().GetContentNode()), 0 );
92                 KillPams();
93                 if ( !IsEndOfPara() )
94                 {
95                     EndPara();
96                 }
97                 const SwPosition rEndPos( *GetCurrentShellCursor().GetPoint() );
98                 pAnnotationTextRange.reset(new SwPaM( rStartPos, rEndPos ));
99             }
100             else
101             {
102                 NormalizePam( false );
103                 const SwPaM& rCurrPaM = GetCurrentShellCursor();
104                 pAnnotationTextRange.reset(new SwPaM( *rCurrPaM.GetPoint(), *rCurrPaM.GetMark() ));
105                 ClearMark();
106             }
107         }
108         else
109         {
110             bDeleted = DelRight();
111         }
112     }
113 
114     SwEditShell::Insert2(rField, bDeleted);
115 
116     if ( pAnnotationTextRange )
117     {
118         if ( GetDoc() != nullptr )
119         {
120             const SwPaM& rCurrPaM = GetCurrentShellCursor();
121             if (*rCurrPaM.Start() == *pAnnotationTextRange->Start()
122                 && *rCurrPaM.End() == *pAnnotationTextRange->End())
123             {
124                 // Annotation range was passed in externally, and inserting the postit field shifted
125                 // its start/end positions right by one. Restore the original position for the range
126                 // start. This allows commenting on the placeholder character of the field.
127                 SwIndex& rRangeStart = pAnnotationTextRange->Start()->nContent;
128                 if (rRangeStart.GetIndex() > 0)
129                     --rRangeStart;
130             }
131             IDocumentMarkAccess* pMarksAccess = GetDoc()->getIDocumentMarkAccess();
132             pMarksAccess->makeAnnotationMark( *pAnnotationTextRange, OUString() );
133         }
134         pAnnotationTextRange.reset();
135     }
136 
137     EndUndo();
138     EndAllAction();
139 }
140 
141 // Start the field update
142 
143 void SwWrtShell::UpdateInputFields( SwInputFieldList* pLst )
144 {
145     // Go through the list of fields and updating
146     std::unique_ptr<SwInputFieldList> pTmp;
147     if (!pLst)
148     {
149         pTmp.reset(new SwInputFieldList( this ));
150         pLst = pTmp.get();
151     }
152 
153     const size_t nCnt = pLst->Count();
154     if(!nCnt)
155         return;
156 
157     pLst->PushCursor();
158 
159     bool bCancel = false;
160 
161     size_t nIndex = 0;
162     FieldDialogPressedButton ePressedButton = FieldDialogPressedButton::NONE;
163 
164     SwField* pField = GetCurField();
165     if (pField)
166     {
167         for (size_t i = 0; i < nCnt; i++)
168         {
169             if (pField == pLst->GetField(i))
170             {
171                 nIndex = i;
172                 break;
173             }
174         }
175     }
176 
177     while (!bCancel)
178     {
179         bool bPrev = nIndex > 0;
180         bool bNext = nIndex < nCnt - 1;
181         pLst->GotoFieldPos(nIndex);
182         pField = pLst->GetField(nIndex);
183         if (pField->GetTyp()->Which() == SwFieldIds::Dropdown)
184         {
185             bCancel = StartDropDownFieldDlg(pField, bPrev, bNext, GetView().GetFrameWeld(), &ePressedButton);
186         }
187         else
188             bCancel = StartInputFieldDlg(pField, bPrev, bNext, GetView().GetFrameWeld(), &ePressedButton);
189 
190         if (!bCancel)
191         {
192             // Otherwise update error at multi-selection:
193             pLst->GetField(nIndex)->GetTyp()->UpdateFields();
194 
195             if (ePressedButton == FieldDialogPressedButton::Previous && nIndex > 0)
196                 nIndex--;
197             else if (ePressedButton == FieldDialogPressedButton::Next && nIndex < nCnt - 1)
198                 nIndex++;
199             else
200                 bCancel = true;
201         }
202     }
203 
204     pLst->PopCursor();
205 }
206 
207 namespace {
208 
209 // Listener class: will close InputField dialog if input field(s)
210 // is(are) deleted (for instance, by an extension) after the dialog shows up.
211 // Otherwise, the for loop in SwWrtShell::UpdateInputFields will crash when doing:
212 //         'pTmp->GetField( i )->GetTyp()->UpdateFields();'
213 // on a deleted field.
214 class FieldDeletionListener : public SvtListener
215 {
216     public:
217         FieldDeletionListener(AbstractFieldInputDlg* pInputFieldDlg, SwField* pField)
218             : mpInputFieldDlg(pInputFieldDlg)
219             , mpFormatField(nullptr)
220         {
221             SwInputField *const pInputField(dynamic_cast<SwInputField*>(pField));
222             SwSetExpField *const pSetExpField(dynamic_cast<SwSetExpField*>(pField));
223 
224             if (pInputField && pInputField->GetFormatField())
225             {
226                 mpFormatField = pInputField->GetFormatField();
227             }
228             else if (pSetExpField && pSetExpField->GetFormatField())
229             {
230                 mpFormatField = pSetExpField->GetFormatField();
231             }
232 
233             // Register for possible field deletion while dialog is open
234             if (mpFormatField)
235                 StartListening(mpFormatField->GetNotifier());
236         }
237 
238         virtual ~FieldDeletionListener() override
239         {
240             // Dialog closed, remove modification listener
241             EndListeningAll();
242         }
243 
244         virtual void Notify(const SfxHint& rHint) override
245         {
246             // Input field has been deleted: better to close the dialog
247             if(rHint.GetId() == SfxHintId::Dying)
248             {
249                 mpFormatField = nullptr;
250                 mpInputFieldDlg->EndDialog(RET_CANCEL);
251             }
252         }
253     private:
254         VclPtr<AbstractFieldInputDlg> mpInputFieldDlg;
255         SwFormatField* mpFormatField;
256 };
257 
258 }
259 
260 // Start input dialog for a specific field
261 bool SwWrtShell::StartInputFieldDlg(SwField* pField, bool bPrevButton, bool bNextButton,
262                                     weld::Widget* pParentWin, SwWrtShell::FieldDialogPressedButton* pPressedButton)
263 {
264 
265     SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
266     ScopedVclPtr<AbstractFieldInputDlg> pDlg(pFact->CreateFieldInputDlg(pParentWin, *this, pField, bPrevButton, bNextButton));
267 
268     bool bRet;
269 
270     {
271         FieldDeletionListener aModify(pDlg.get(), pField);
272         bRet = RET_CANCEL == pDlg->Execute();
273     }
274 
275     if (pPressedButton)
276     {
277         if (pDlg->PrevButtonPressed())
278             *pPressedButton = FieldDialogPressedButton::Previous;
279         else if (pDlg->NextButtonPressed())
280             *pPressedButton = FieldDialogPressedButton::Next;
281     }
282 
283     pDlg.disposeAndClear();
284     GetWin()->PaintImmediately();
285     return bRet;
286 }
287 
288 bool SwWrtShell::StartDropDownFieldDlg(SwField* pField, bool bPrevButton, bool bNextButton,
289                                        weld::Widget* pParentWin, SwWrtShell::FieldDialogPressedButton* pPressedButton)
290 {
291     SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
292     ScopedVclPtr<AbstractDropDownFieldDialog> pDlg(pFact->CreateDropDownFieldDialog(pParentWin, *this, pField, bPrevButton, bNextButton));
293     const short nRet = pDlg->Execute();
294 
295     if (pPressedButton)
296     {
297         if (pDlg->PrevButtonPressed())
298             *pPressedButton = FieldDialogPressedButton::Previous;
299         else if (pDlg->NextButtonPressed())
300             *pPressedButton = FieldDialogPressedButton::Next;
301     }
302 
303     pDlg.disposeAndClear();
304     bool bRet = RET_CANCEL == nRet;
305     GetWin()->PaintImmediately();
306     if(RET_YES == nRet)
307     {
308         GetView().GetViewFrame()->GetDispatcher()->Execute(FN_EDIT_FIELD, SfxCallMode::SYNCHRON);
309     }
310     return bRet;
311 }
312 
313 // Insert directory - remove selection
314 
315 void SwWrtShell::InsertTableOf(const SwTOXBase& rTOX, const SfxItemSet* pSet)
316 {
317     if(!CanInsert())
318         return;
319 
320     if(HasSelection())
321         DelRight();
322 
323     SwEditShell::InsertTableOf(rTOX, pSet);
324 }
325 
326 // Update directory - remove selection
327 
328 void SwWrtShell::UpdateTableOf(const SwTOXBase& rTOX, const SfxItemSet* pSet)
329 {
330     if(CanInsert())
331     {
332         SwEditShell::UpdateTableOf(rTOX, pSet);
333     }
334 }
335 
336 // handler for click on the field given as parameter.
337 // the cursor is positioned on the field.
338 
339 void SwWrtShell::ClickToField(const SwField& rField, bool bExecHyperlinks)
340 {
341     // cross reference field must not be selected because it moves the cursor
342     if (SwFieldIds::GetRef != rField.GetTyp()->Which())
343     {
344         StartAllAction();
345         Right( CRSR_SKIP_CHARS, true, 1, false ); // Select the field.
346         NormalizePam();
347         EndAllAction();
348     }
349 
350     m_bIsInClickToEdit = true;
351     switch( rField.GetTyp()->Which() )
352     {
353     case SwFieldIds::JumpEdit:
354         {
355             sal_uInt16 nSlotId = 0;
356             switch( rField.GetFormat() )
357             {
358             case JE_FMT_TABLE:
359                 nSlotId = FN_INSERT_TABLE;
360                 break;
361 
362             case JE_FMT_FRAME:
363                 nSlotId = FN_INSERT_FRAME;
364                 break;
365 
366             case JE_FMT_GRAPHIC:    nSlotId = SID_INSERT_GRAPHIC;       break;
367             case JE_FMT_OLE:        nSlotId = SID_INSERT_OBJECT;        break;
368 
369             }
370 
371             if( nSlotId )
372             {
373                 StartUndo( SwUndoId::START );
374                 //#97295# immediately select the right shell
375                 GetView().StopShellTimer();
376                 GetView().GetViewFrame()->GetDispatcher()->Execute( nSlotId,
377                             SfxCallMode::SYNCHRON|SfxCallMode::RECORD );
378                 EndUndo( SwUndoId::END );
379             }
380         }
381         break;
382 
383     case SwFieldIds::Macro:
384         {
385             const SwMacroField *pField = static_cast<const SwMacroField*>(&rField);
386             const OUString sText( rField.GetPar2() );
387             OUString sRet( sText );
388             ExecMacro( pField->GetSvxMacro(), &sRet );
389 
390             // return value changed?
391             if( sRet != sText )
392             {
393                 StartAllAction();
394                 const_cast<SwField&>(rField).SetPar2( sRet );
395                 rField.GetTyp()->UpdateFields();
396                 EndAllAction();
397             }
398         }
399         break;
400 
401     case SwFieldIds::TableOfAuthorities:
402         {
403             if (!bExecHyperlinks)
404             {
405                 break;
406             }
407 
408             auto pField = static_cast<const SwAuthorityField*>(&rField);
409             if (!pField->HasURL())
410             {
411                 break;
412             }
413 
414             const OUString& rURL = pField->GetAbsoluteURL();
415             ::LoadURL(*this, rURL, LoadUrlFlags::NewView, /*rTargetFrameName=*/OUString());
416         }
417         break;
418 
419     case SwFieldIds::GetRef:
420         StartAllAction();
421         SwCursorShell::GotoRefMark( static_cast<const SwGetRefField&>(rField).GetSetRefName(),
422                                     static_cast<const SwGetRefField&>(rField).GetSubType(),
423                                     static_cast<const SwGetRefField&>(rField).GetSeqNo() );
424         EndAllAction();
425         break;
426 
427     case SwFieldIds::Input:
428         {
429             const SwInputField* pInputField = dynamic_cast<const SwInputField*>(&rField);
430             if ( pInputField == nullptr )
431             {
432                 StartInputFieldDlg(const_cast<SwField*>(&rField), false, false, GetView().GetFrameWeld());
433             }
434         }
435         break;
436 
437     case SwFieldIds::SetExp:
438         if( static_cast<const SwSetExpField&>(rField).GetInputFlag() )
439             StartInputFieldDlg(const_cast<SwField*>(&rField), false, false, GetView().GetFrameWeld());
440         break;
441     case SwFieldIds::Dropdown :
442         StartDropDownFieldDlg(const_cast<SwField*>(&rField), false, false, GetView().GetFrameWeld());
443     break;
444     default:
445         SAL_WARN_IF(rField.IsClickable(), "sw", "unhandled clickable field!");
446     }
447 
448     m_bIsInClickToEdit = false;
449 }
450 
451 void SwWrtShell::ClickToINetAttr( const SwFormatINetFormat& rItem, LoadUrlFlags nFilter )
452 {
453     if( rItem.GetValue().isEmpty() )
454         return ;
455 
456     m_bIsInClickToEdit = true;
457 
458     // At first run the possibly set ObjectSelect Macro
459     const SvxMacro* pMac = rItem.GetMacro( SvMacroItemId::OnClick );
460     if( pMac )
461     {
462         SwCallMouseEvent aCallEvent;
463         aCallEvent.Set( &rItem );
464         GetDoc()->CallEvent( SvMacroItemId::OnClick, aCallEvent );
465     }
466 
467     // So that the implementation of templates is displayed immediately
468     ::LoadURL( *this, rItem.GetValue(), nFilter, rItem.GetTargetFrame() );
469     const SwTextINetFormat* pTextAttr = rItem.GetTextINetFormat();
470     if( pTextAttr )
471     {
472         const_cast<SwTextINetFormat*>(pTextAttr)->SetVisited( true );
473         const_cast<SwTextINetFormat*>(pTextAttr)->SetVisitedValid( true );
474     }
475 
476     m_bIsInClickToEdit = false;
477 }
478 
479 bool SwWrtShell::ClickToINetGrf( const Point& rDocPt, LoadUrlFlags nFilter )
480 {
481     bool bRet = false;
482     OUString sURL;
483     OUString sTargetFrameName;
484     const SwFrameFormat* pFnd = IsURLGrfAtPos( rDocPt, &sURL, &sTargetFrameName );
485     if( pFnd && !sURL.isEmpty() )
486     {
487         bRet = true;
488         // At first run the possibly set ObjectSelect Macro
489         SwCallMouseEvent aCallEvent;
490         aCallEvent.Set(EVENT_OBJECT_URLITEM, pFnd);
491         GetDoc()->CallEvent(SvMacroItemId::OnClick, aCallEvent);
492 
493         ::LoadURL(*this, sURL, nFilter, sTargetFrameName);
494     }
495     return bRet;
496 }
497 
498 void LoadURL( SwViewShell& rVSh, const OUString& rURL, LoadUrlFlags nFilter,
499               const OUString& rTargetFrameName )
500 {
501     OSL_ENSURE( !rURL.isEmpty(), "what should be loaded here?" );
502     if( rURL.isEmpty() )
503         return ;
504 
505     // The shell could be 0 also!!!!!
506     if ( dynamic_cast<const SwCursorShell*>( &rVSh) ==  nullptr )
507         return;
508 
509     // We are doing tiledRendering, let the client handles the URL loading,
510     // unless we are jumping to a TOC mark.
511     if (comphelper::LibreOfficeKit::isActive() && !rURL.startsWith("#"))
512     {
513         rVSh.GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, rURL.toUtf8().getStr());
514         return;
515     }
516 
517     //A CursorShell is always a WrtShell
518     SwWrtShell &rSh = static_cast<SwWrtShell&>(rVSh);
519 
520     SwDocShell* pDShell = rSh.GetView().GetDocShell();
521     OSL_ENSURE( pDShell, "No DocShell?!");
522     OUString sTargetFrame(rTargetFrameName);
523     if (sTargetFrame.isEmpty() && pDShell)
524     {
525         using namespace ::com::sun::star;
526         uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
527             pDShell->GetModel(), uno::UNO_QUERY_THROW);
528         uno::Reference<document::XDocumentProperties> xDocProps
529             = xDPS->getDocumentProperties();
530         sTargetFrame = xDocProps->getDefaultTarget();
531     }
532 
533     OUString sReferer;
534     if( pDShell && pDShell->GetMedium() )
535         sReferer = pDShell->GetMedium()->GetName();
536     SfxViewFrame* pViewFrame = rSh.GetView().GetViewFrame();
537     SfxFrameItem aView( SID_DOCFRAME, pViewFrame );
538     SfxStringItem aName( SID_FILE_NAME, rURL );
539     SfxStringItem aTargetFrameName( SID_TARGETNAME, sTargetFrame );
540     SfxStringItem aReferer( SID_REFERER, sReferer );
541 
542     SfxBoolItem aNewView( SID_OPEN_NEW_VIEW, false );
543     //#39076# Silent can be removed accordingly to SFX.
544     SfxBoolItem aBrowse( SID_BROWSE, true );
545 
546     if ((nFilter & LoadUrlFlags::NewView) && !comphelper::LibreOfficeKit::isActive())
547         aTargetFrameName.SetValue( "_blank" );
548 
549     const SfxPoolItem* aArr[] = {
550                 &aName,
551                 &aNewView, /*&aSilent,*/
552                 &aReferer,
553                 &aView, &aTargetFrameName,
554                 &aBrowse,
555                 nullptr
556     };
557 
558     pViewFrame->GetDispatcher()->GetBindings()->Execute( SID_OPENDOC, aArr,
559             SfxCallMode::ASYNCHRON|SfxCallMode::RECORD );
560 }
561 
562 void SwWrtShell::NavigatorPaste( const NaviContentBookmark& rBkmk,
563                                     const sal_uInt16 nAction )
564 {
565     if( EXCHG_IN_ACTION_COPY == nAction )
566     {
567         // Insert
568         OUString sURL = rBkmk.GetURL();
569         // Is this is a jump within the current Doc?
570         const SwDocShell* pDocShell = GetView().GetDocShell();
571         if(pDocShell->HasName())
572         {
573             const OUString rName = pDocShell->GetMedium()->GetURLObject().GetURLNoMark();
574 
575             if (sURL.startsWith(rName))
576             {
577                 if (sURL.getLength()>rName.getLength())
578                 {
579                     sURL = sURL.copy(rName.getLength());
580                 }
581                 else
582                 {
583                     sURL.clear();
584                 }
585             }
586         }
587         SwFormatINetFormat aFormat( sURL, OUString() );
588         InsertURL( aFormat, rBkmk.GetDescription() );
589     }
590     else
591     {
592         SwSectionData aSection( SectionType::FileLink, GetUniqueSectionName() );
593         OUString aLinkFile = rBkmk.GetURL().getToken(0, '#')
594             + OUStringChar(sfx2::cTokenSeparator)
595             + OUStringChar(sfx2::cTokenSeparator)
596             + rBkmk.GetURL().getToken(1, '#');
597         aSection.SetLinkFileName( aLinkFile );
598         aSection.SetProtectFlag( true );
599         const SwSection* pIns = InsertSection( aSection );
600         if( EXCHG_IN_ACTION_MOVE == nAction && pIns )
601         {
602             aSection = SwSectionData(*pIns);
603             aSection.SetLinkFileName( OUString() );
604             aSection.SetType( SectionType::Content );
605             aSection.SetProtectFlag( false );
606 
607             // the update of content from linked section at time delete
608             // the undostack. Then the change of the section don't create
609             // any undoobject. -  BUG 69145
610             bool bDoesUndo = DoesUndo();
611             SwUndoId nLastUndoId(SwUndoId::EMPTY);
612             if (GetLastUndoInfo(nullptr, & nLastUndoId))
613             {
614                 if (SwUndoId::INSSECTION != nLastUndoId)
615                 {
616                     DoUndo(false);
617                 }
618             }
619             UpdateSection( GetSectionFormatPos( *pIns->GetFormat() ), aSection );
620             DoUndo( bDoesUndo );
621         }
622     }
623 }
624 
625 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
626