xref: /core/sw/source/uibase/ribbar/inputwin.cxx (revision d8037ae1)
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 <sal/config.h>
21 #include <sal/log.hxx>
22 
23 #include <comphelper/string.hxx>
24 #include <officecfg/Office/Common.hxx>
25 #include <tools/gen.hxx>
26 #include <sfx2/objface.hxx>
27 #include <sfx2/viewfrm.hxx>
28 #include <sfx2/dispatch.hxx>
29 #include <svx/ruler.hxx>
30 #include <svl/zforlist.hxx>
31 #include <svl/stritem.hxx>
32 #include <vcl/settings.hxx>
33 
34 #include <swtypes.hxx>
35 #include <cmdid.h>
36 #include <swmodule.hxx>
37 #include <wrtsh.hxx>
38 #include <view.hxx>
39 #include <calc.hxx>
40 #include <inputwin.hxx>
41 #include <fldbas.hxx>
42 #include <fldmgr.hxx>
43 #include <frmfmt.hxx>
44 #include <cellatr.hxx>
45 #include <edtwin.hxx>
46 #include <helpids.h>
47 #include <strings.hrc>
48 #include <bitmaps.hlst>
49 
50 // Only for the UpdateRange: Delete the box in which the stacked cursor is positioned.
51 #include <pam.hxx>
52 
53 #include <swundo.hxx>
54 #include <dbui.hrc>
55 
56 #include <IDocumentContentOperations.hxx>
57 
58 #define ED_POS              2
59 #define ED_FORMULA          3
60 
61 SFX_IMPL_POS_CHILDWINDOW_WITHID( SwInputChild, FN_EDIT_FORMULA, SFX_OBJECTBAR_OBJECT )
62 
63 SwInputWindow::SwInputWindow(vcl::Window* pParent, SfxDispatcher const * pDispatcher)
64     : ToolBox(pParent, WB_3DLOOK|WB_BORDER)
65     , aPos(VclPtr<Edit>::Create(this, WB_3DLOOK|WB_CENTER|WB_BORDER|WB_READONLY))
66     , aEdit(VclPtr<InputEdit>::Create(this, WB_3DLOOK|WB_TABSTOP|WB_BORDER|WB_NOHIDESELECTION))
67     , pWrtShell(nullptr)
68     , pView(nullptr)
69     , m_bDoesUndo(true)
70     , m_bResetUndo(false)
71     , m_bCallUndo(false)
72 {
73     bFirst = true;
74     bIsTable = bDelSel = false;
75 
76     aEdit->SetSizePixel(aEdit->CalcMinimumSize());
77     aPos->SetSizePixel(aPos->LogicToPixel(Size(45, 11), MapMode(MapUnit::MapAppFont)));
78 
79     InsertItem(FN_FORMULA_CALC, Image(StockImage::Yes, RID_BMP_FORMULA_CALC),
80                SwResId(STR_FORMULA_CALC));
81     InsertItem(FN_FORMULA_CANCEL, Image(StockImage::Yes, RID_BMP_FORMULA_CANCEL),
82                SwResId(STR_FORMULA_CANCEL));
83     InsertItem(FN_FORMULA_APPLY, Image(StockImage::Yes, RID_BMP_FORMULA_APPLY),
84                SwResId(STR_FORMULA_APPLY));
85 
86     SetHelpId(FN_FORMULA_CALC, HID_TBX_FORMULA_CALC);
87     SetHelpId(FN_FORMULA_CANCEL, HID_TBX_FORMULA_CANCEL);
88     SetHelpId(FN_FORMULA_APPLY, HID_TBX_FORMULA_APPLY);
89 
90     SwView *pDispatcherView = dynamic_cast<SwView*>(pDispatcher ? pDispatcher->GetFrame()->GetViewShell() : nullptr);
91     SwView* pActiveView = ::GetActiveView();
92     if (pDispatcherView == pActiveView)
93         pView = pActiveView;
94     pWrtShell = pView ? pView->GetWrtShellPtr() : nullptr;
95 
96     InsertWindow(ED_POS, aPos.get(), ToolBoxItemBits::NONE, 0);
97     SetItemText(ED_POS, SwResId(STR_ACCESS_FORMULA_TYPE));
98     aPos->SetAccessibleName(SwResId(STR_ACCESS_FORMULA_TYPE));
99     SetAccessibleName(SwResId(STR_ACCESS_FORMULA_TOOLBAR));
100     InsertSeparator ( 1 );
101     InsertSeparator ();
102     InsertWindow(ED_FORMULA, aEdit.get());
103     SetItemText(ED_FORMULA, SwResId(STR_ACCESS_FORMULA_TEXT));
104     aEdit->SetAccessibleName(SwResId(STR_ACCESS_FORMULA_TEXT));
105     SetHelpId(ED_FORMULA, HID_EDIT_FORMULA);
106 
107     SetItemBits( FN_FORMULA_CALC, GetItemBits( FN_FORMULA_CALC ) | ToolBoxItemBits::DROPDOWNONLY );
108     SetDropdownClickHdl( LINK( this, SwInputWindow, DropdownClickHdl ));
109 
110     Size    aSizeTbx = CalcWindowSizePixel();
111     Size    aEditSize = aEdit->GetSizePixel();
112     tools::Rectangle aItemRect( GetItemRect(FN_FORMULA_CALC) );
113     long nMaxHeight = std::max(aEditSize.Height(), aItemRect.GetHeight());
114     if( nMaxHeight+2 > aSizeTbx.Height() )
115         aSizeTbx.setHeight( nMaxHeight+2 );
116     Size aSize = GetSizePixel();
117     aSize.setHeight( aSizeTbx.Height() );
118     SetSizePixel( aSize );
119 
120     // align edit and item vcentered
121     Size    aPosSize = aPos->GetSizePixel();
122     aPosSize.setHeight( nMaxHeight );
123     aEditSize.setHeight( nMaxHeight );
124     Point aPosPos  = aPos->GetPosPixel();
125     Point aEditPos = aEdit->GetPosPixel();
126     aPosPos.setY( (aSize.Height() - nMaxHeight)/2 + 1 );
127     aEditPos.setY( (aSize.Height() - nMaxHeight)/2 + 1 );
128     aPos->SetPosSizePixel( aPosPos, aPosSize );
129     aEdit->SetPosSizePixel( aEditPos, aEditSize );
130 }
131 
132 SwInputWindow::~SwInputWindow()
133 {
134     disposeOnce();
135 }
136 
137 void SwInputWindow::dispose()
138 {
139     // wake rulers
140     if(pView)
141     {
142         pView->GetHRuler().SetActive();
143         pView->GetVRuler().SetActive();
144     }
145     pMgr.reset();
146     if(pWrtShell)
147         pWrtShell->EndSelTableCells();
148 
149     CleanupUglyHackWithUndo();
150 
151     aPos.disposeAndClear();
152     aEdit.disposeAndClear();
153     ToolBox::dispose();
154 }
155 
156 void SwInputWindow::CleanupUglyHackWithUndo()
157 {
158     if (m_bResetUndo)
159     {
160         if (pWrtShell)
161         {
162             DelBoxContent();
163             pWrtShell->DoUndo(m_bDoesUndo);
164             if (m_bCallUndo)
165             {
166                 pWrtShell->Undo();
167             }
168         }
169         m_bResetUndo = false; // #i117122# once is enough :)
170     }
171 }
172 
173 void SwInputWindow::Resize()
174 {
175     ToolBox::Resize();
176 
177     long    nWidth      = GetSizePixel().Width();
178     long    nLeft       = aEdit->GetPosPixel().X();
179     Size    aEditSize   = aEdit->GetSizePixel();
180 
181     aEditSize.setWidth( std::max( static_cast<long>(nWidth - nLeft - 5), long(0) ) );
182     aEdit->SetSizePixel( aEditSize );
183     aEdit->Invalidate();
184 }
185 
186 void SwInputWindow::ShowWin()
187 {
188     bIsTable = false;
189     // stop rulers
190     if(pView)
191     {
192         pView->GetHRuler().SetActive( false );
193         pView->GetVRuler().SetActive( false );
194 
195         OSL_ENSURE(pWrtShell, "no WrtShell!");
196         // Cursor in table
197         bIsTable = pWrtShell->IsCursorInTable();
198 
199         if( bFirst )
200             pWrtShell->SelTableCells( LINK( this, SwInputWindow,
201                                                 SelTableCellsNotify) );
202         if( bIsTable )
203         {
204             const OUString& rPos = pWrtShell->GetBoxNms();
205             sal_Int32 nPos = 0;
206             short nSrch = -1;
207             while( (nPos = rPos.indexOf( ':',nPos + 1 ) ) != -1 )
208                 nSrch = static_cast<short>(nPos);
209             aPos->SetText( rPos.copy( ++nSrch ) );
210             aCurrentTableName = pWrtShell->GetTableFormat()->GetName();
211         }
212         else
213             aPos->SetText(SwResId(STR_TBL_FORMULA));
214 
215         // Edit current field
216         OSL_ENSURE(pMgr == nullptr, "FieldManager not deleted");
217         pMgr.reset(new SwFieldMgr);
218 
219         // Form should always begin with "=" , so set here
220         OUString sEdit('=');
221         if( pMgr->GetCurField() && TYP_FORMELFLD == pMgr->GetCurTypeId() )
222         {
223             sEdit += pMgr->GetCurFieldPar2();
224         }
225         else if( bFirst && bIsTable )
226         {
227             m_bResetUndo = true;
228             SAL_WARN_IF(
229                 officecfg::Office::Common::Undo::Steps::get() <= 0,
230                 "sw", "/org.openoffice.Office.Common/Undo/Steps <= 0");
231 
232             m_bDoesUndo = pWrtShell->DoesUndo();
233             if( !m_bDoesUndo )
234             {
235                 pWrtShell->DoUndo();
236             }
237 
238             if( !pWrtShell->SwCursorShell::HasSelection() )
239             {
240                 pWrtShell->MoveSection( GoCurrSection, fnSectionStart );
241                 pWrtShell->SetMark();
242                 pWrtShell->MoveSection( GoCurrSection, fnSectionEnd );
243             }
244             if( pWrtShell->SwCursorShell::HasSelection() )
245             {
246                 pWrtShell->StartUndo( SwUndoId::DELETE );
247                 pWrtShell->Delete();
248                 if( SwUndoId::EMPTY != pWrtShell->EndUndo( SwUndoId::DELETE ))
249                 {
250                     m_bCallUndo = true;
251                 }
252             }
253             pWrtShell->DoUndo(false);
254 
255             SfxItemSet aSet( pWrtShell->GetAttrPool(), svl::Items<RES_BOXATR_FORMULA, RES_BOXATR_FORMULA>{} );
256             if( pWrtShell->GetTableBoxFormulaAttrs( aSet ))
257                 sEdit += aSet.Get( RES_BOXATR_FORMULA ).GetFormula();
258         }
259 
260         if( bFirst )
261         {
262             // Set WrtShell flags correctly
263             pWrtShell->SttSelect();
264             pWrtShell->EndSelect();
265         }
266 
267         bFirst = false;
268 
269         aEdit->SetModifyHdl( LINK( this, SwInputWindow, ModifyHdl ));
270 
271         aEdit->SetText( sEdit );
272         aEdit->SetSelection( Selection( sEdit.getLength(), sEdit.getLength() ) );
273         sOldFormula = sEdit;
274 
275         aEdit->Invalidate();
276         aEdit->Update();
277         aEdit->GrabFocus();
278         // For input cut the UserInterface
279 
280         pView->GetEditWin().LockKeyInput(true);
281         pView->GetViewFrame()->GetDispatcher()->Lock(true);
282         pWrtShell->Push();
283     }
284     ToolBox::Show();
285 }
286 
287 IMPL_LINK( SwInputWindow, MenuHdl, Menu *, pMenu, bool )
288 {
289     OString aCommand = pMenu->GetCurItemIdent();
290     if (!aCommand.isEmpty())
291     {
292         aCommand += " ";
293         aEdit->ReplaceSelected(OStringToOUString(aCommand, RTL_TEXTENCODING_ASCII_US));
294     }
295     return false;
296 }
297 
298 IMPL_LINK_NOARG(SwInputWindow, DropdownClickHdl, ToolBox *, void)
299 {
300     sal_uInt16 nCurID = GetCurItemId();
301     EndSelection(); // reset back CurItemId !
302     if (nCurID == FN_FORMULA_CALC)
303     {
304         VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "modules/swriter/ui/inputwinmenu.ui", "");
305         VclPtr<PopupMenu> aPopMenu(aBuilder.get_menu("menu"));
306         aPopMenu->SetSelectHdl(LINK(this, SwInputWindow, MenuHdl));
307         aPopMenu->Execute(this, GetItemRect(FN_FORMULA_CALC), PopupMenuFlags::NoMouseUpClose);
308     }
309 }
310 
311 void SwInputWindow::Click( )
312 {
313     sal_uInt16 nCurID = GetCurItemId();
314     EndSelection(); // reset back CurItemId !
315     switch ( nCurID )
316     {
317         case FN_FORMULA_CANCEL:
318         {
319             CancelFormula();
320         }
321         break;
322         case FN_FORMULA_APPLY:
323         {
324             ApplyFormula();
325         }
326         break;
327    }
328 }
329 
330 void  SwInputWindow::ApplyFormula()
331 {
332     pView->GetViewFrame()->GetDispatcher()->Lock(false);
333     pView->GetEditWin().LockKeyInput(false);
334     CleanupUglyHackWithUndo();
335     pWrtShell->Pop(SwCursorShell::PopMode::DeleteCurrent);
336 
337     // Form should always begin with "=", so remove it here again
338     OUString sEdit(comphelper::string::strip(aEdit->GetText(), ' '));
339     if( !sEdit.isEmpty() && '=' == sEdit[0] )
340         sEdit = sEdit.copy( 1 );
341     SfxStringItem aParam(FN_EDIT_FORMULA, sEdit);
342 
343     pWrtShell->EndSelTableCells();
344     pView->GetEditWin().GrabFocus();
345     const SfxPoolItem* aArgs[2];
346     aArgs[0] = &aParam;
347     aArgs[1] = nullptr;
348     pView->GetViewFrame()->GetBindings().Execute( FN_EDIT_FORMULA, aArgs, SfxCallMode::ASYNCHRON );
349 }
350 
351 void  SwInputWindow::CancelFormula()
352 {
353     if(pView)
354     {
355         pView->GetViewFrame()->GetDispatcher()->Lock( false );
356         pView->GetEditWin().LockKeyInput(false);
357         CleanupUglyHackWithUndo();
358         pWrtShell->Pop(SwCursorShell::PopMode::DeleteCurrent);
359 
360         if( bDelSel )
361             pWrtShell->EnterStdMode();
362 
363         pWrtShell->EndSelTableCells();
364 
365         pView->GetEditWin().GrabFocus();
366 
367         pView->GetViewFrame()->GetDispatcher()->Execute( FN_EDIT_FORMULA, SfxCallMode::ASYNCHRON);
368     }
369 }
370 
371 const sal_Unicode CH_LRE = 0x202a;
372 const sal_Unicode CH_PDF = 0x202c;
373 
374 IMPL_LINK( SwInputWindow, SelTableCellsNotify, SwWrtShell&, rCaller, void )
375 {
376     if(bIsTable)
377     {
378         SwFrameFormat* pTableFormat = rCaller.GetTableFormat();
379         OUString sBoxNms( rCaller.GetBoxNms() );
380         OUString sTableNm;
381         if( pTableFormat && aCurrentTableName != pTableFormat->GetName() )
382             sTableNm = pTableFormat->GetName();
383 
384         aEdit->UpdateRange( sBoxNms, sTableNm );
385 
386         OUString sNew = OUStringLiteral1(CH_LRE) + aEdit->GetText()
387             + OUStringLiteral1(CH_PDF);
388 
389         if( sNew != sOldFormula )
390         {
391             // The WrtShell is in the table selection,
392             // then cancel the table selection otherwise, the cursor is
393             // positioned "in the forest" and the live update does not work!
394             pWrtShell->StartAllAction();
395 
396             SwPaM aPam( *pWrtShell->GetStackCursor()->GetPoint() );
397             aPam.Move( fnMoveBackward, GoInSection );
398             aPam.SetMark();
399             aPam.Move( fnMoveForward, GoInSection );
400 
401             IDocumentContentOperations& rIDCO = pWrtShell->getIDocumentContentOperations();
402             rIDCO.DeleteRange( aPam );
403             rIDCO.InsertString( aPam, sNew );
404             pWrtShell->EndAllAction();
405             sOldFormula = sNew;
406         }
407     }
408     else
409         aEdit->GrabFocus();
410 }
411 
412 void SwInputWindow::SetFormula( const OUString& rFormula )
413 {
414     OUString sEdit('=');
415     if( !rFormula.isEmpty() )
416     {
417         if( '=' == rFormula[0] )
418             sEdit = rFormula;
419         else
420             sEdit += rFormula;
421     }
422     aEdit->SetText( sEdit );
423     aEdit->SetSelection( Selection( sEdit.getLength(), sEdit.getLength() ) );
424     aEdit->Invalidate();
425     bDelSel = true;
426 }
427 
428 IMPL_LINK_NOARG(SwInputWindow, ModifyHdl, Edit&, void)
429 {
430     if (bIsTable && m_bResetUndo)
431     {
432         pWrtShell->StartAllAction();
433         DelBoxContent();
434         OUString sNew = OUStringLiteral1(CH_LRE) + aEdit->GetText()
435             + OUStringLiteral1(CH_PDF);
436         pWrtShell->SwEditShell::Insert2( sNew );
437         pWrtShell->EndAllAction();
438         sOldFormula = sNew;
439     }
440 }
441 
442 void SwInputWindow::DelBoxContent()
443 {
444     if( bIsTable )
445     {
446         pWrtShell->StartAllAction();
447         pWrtShell->ClearMark();
448         pWrtShell->Pop(SwCursorShell::PopMode::DeleteCurrent);
449         pWrtShell->Push();
450         pWrtShell->MoveSection( GoCurrSection, fnSectionStart );
451         pWrtShell->SetMark();
452         pWrtShell->MoveSection( GoCurrSection, fnSectionEnd );
453         pWrtShell->SwEditShell::Delete();
454         pWrtShell->EndAllAction();
455     }
456 }
457 
458 void InputEdit::KeyInput(const KeyEvent& rEvent)
459 {
460     const vcl::KeyCode aCode = rEvent.GetKeyCode();
461     if(aCode == KEY_RETURN || aCode == KEY_F2 )
462         static_cast<SwInputWindow*>(GetParent())->ApplyFormula();
463     else if(aCode == KEY_ESCAPE )
464         static_cast<SwInputWindow*>(GetParent())->CancelFormula();
465     else
466         Edit::KeyInput(rEvent);
467 }
468 
469 void InputEdit::UpdateRange(const OUString& rBoxes,
470                                     const OUString& rName )
471 {
472     if( rBoxes.isEmpty() )
473     {
474         GrabFocus();
475         return;
476     }
477     const sal_Unicode   cOpen = '<', cClose = '>',
478                 cOpenBracket = '(';
479     OUString aPrefix = rName;
480     if(!rName.isEmpty())
481         aPrefix += ".";
482     OUString aBoxes = aPrefix;
483     aBoxes += rBoxes;
484     Selection aSelection(GetSelection());
485     sal_uInt16 nSel = static_cast<sal_uInt16>(aSelection.Len());
486     // OS: The following expression ensures that in the overwrite mode,
487     // the selected closing parenthesis will be not deleted.
488     if( nSel && ( nSel > 1 ||
489                   GetText()[ static_cast<sal_uInt16>(aSelection.Min()) ] != cClose ))
490         Cut();
491     else
492         aSelection.Max() = aSelection.Min();
493     OUString aActText(GetText());
494     const sal_uInt16 nLen = aActText.getLength();
495     if( !nLen )
496     {
497         OUString aStr = OUStringBuffer().
498             append(cOpen).append(aBoxes).append(cClose).
499             makeStringAndClear();
500         SetText(aStr);
501         sal_Int32 nPos = aStr.indexOf( cClose );
502         OSL_ENSURE(nPos != -1, "delimiter not found");
503         ++nPos;
504         SetSelection( Selection( nPos, nPos ));
505     }
506     else
507     {
508         bool bFound = false;
509         sal_Unicode cCh;
510         sal_uInt16 nPos, nEndPos = 0, nStartPos = static_cast<sal_uInt16>(aSelection.Min());
511         if( nStartPos-- )
512         {
513             do {
514                 if( cOpen  == (cCh = aActText[ nStartPos ] ) ||
515                     cOpenBracket == cCh )
516                 {
517                     bFound = cCh == cOpen;
518                     break;
519                 }
520             } while( nStartPos-- > 0 );
521         }
522         if( bFound )
523         {
524             bFound = false;
525             nEndPos = nStartPos;
526             while( nEndPos < nLen )
527             {
528                 if( cClose == (cCh = aActText[ nEndPos ]))
529                 {
530                     bFound = true;
531                     break;
532                 }
533                 ++nEndPos;
534             }
535             // Only if the current position lies in the range or right behind.
536             if( bFound && !( nStartPos < static_cast<sal_uInt16>(aSelection.Max()) &&
537                              static_cast<sal_uInt16>(aSelection.Max()) <= nEndPos + 1 ))
538                 bFound = false;
539         }
540         if( bFound )
541         {
542             nPos = ++nStartPos + 1; // We want behind
543             aActText = aActText.replaceAt( nStartPos, nEndPos - nStartPos, aBoxes );
544             nPos = nPos + aBoxes.getLength();
545         }
546         else
547         {
548             OUString aTmp = OUStringBuffer().
549                 append(cOpen).append(aBoxes).append(cClose).
550                 makeStringAndClear();
551             nPos = static_cast<sal_uInt16>(aSelection.Min());
552             aActText = aActText.replaceAt( nPos, 0, aTmp );
553             nPos = nPos + aTmp.getLength();
554         }
555         if( GetText() != aActText )
556         {
557             SetText( aActText );
558             SetSelection( Selection( nPos, nPos ) );
559         }
560     }
561     GrabFocus();
562 
563 }
564 
565 SwInputChild::SwInputChild(vcl::Window* _pParent,
566                                 sal_uInt16 nId,
567                                 SfxBindings const * pBindings,
568                                 SfxChildWinInfo* ) :
569                                 SfxChildWindow( _pParent, nId )
570 {
571     pDispatch = pBindings->GetDispatcher();
572     SetWindow(VclPtr<SwInputWindow>::Create(_pParent, pDispatch));
573     static_cast<SwInputWindow*>(GetWindow())->ShowWin();
574     SetAlignment(SfxChildAlignment::LOWESTTOP);
575 }
576 
577 SwInputChild::~SwInputChild()
578 {
579     if(pDispatch)
580         pDispatch->Lock(false);
581 }
582 
583 SfxChildWinInfo SwInputChild::GetInfo() const
584 {
585     SfxChildWinInfo aInfo = SfxChildWindow::GetInfo();
586     return aInfo;
587 }
588 
589 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
590