1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <osl/diagnose.h>
24 #include <comphelper/string.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <officecfg/Office/Common.hxx>
27 #include <tools/gen.hxx>
28 #include <sfx2/objface.hxx>
29 #include <sfx2/viewfrm.hxx>
30 #include <sfx2/dispatch.hxx>
31 #include <svx/ruler.hxx>
32 #include <svl/stritem.hxx>
33 #include <vcl/event.hxx>
34 #include <vcl/weldutils.hxx>
36 #include <swtypes.hxx>
38 #include <swmodule.hxx>
41 #include <inputwin.hxx>
45 #include <cellatr.hxx>
48 #include <strings.hrc>
49 #include <bitmaps.hlst>
51 // Only for the UpdateRange: Delete the box in which the stacked cursor is positioned.
56 #include <IDocumentContentOperations.hxx>
58 constexpr ToolBoxItemId
ED_POS(2);
59 constexpr ToolBoxItemId
ED_FORMULA(3);
60 constexpr ToolBoxItemId
FN_FORMULA_CALC(FN_FORMAT
+ 156); /* select formula */
61 constexpr ToolBoxItemId
FN_FORMULA_CANCEL(FN_FORMAT
+ 157); /* don't apply formula */
62 constexpr ToolBoxItemId
FN_FORMULA_APPLY(FN_FORMAT
+ 158); /* apply formula */
64 SFX_IMPL_POS_CHILDWINDOW_WITHID( SwInputChild
, FN_EDIT_FORMULA
, SFX_OBJECTBAR_OBJECT
)
66 IMPL_LINK(PosEdit
, KeyInputHdl
, const KeyEvent
&, rKEvt
, bool)
68 return ChildKeyInput(rKEvt
);
71 SwInputWindow::SwInputWindow(vcl::Window
* pParent
, SfxDispatcher
const * pDispatcher
)
72 : ToolBox(pParent
, WB_3DLOOK
|WB_BORDER
)
73 , mxPos(VclPtr
<PosEdit
>::Create(this))
74 , mxEdit(VclPtr
<InputEdit
>::Create(this))
75 , m_pWrtShell(nullptr)
82 m_bIsTable
= m_bDelSel
= false;
84 InsertItem(FN_FORMULA_CALC
, Image(StockImage::Yes
, RID_BMP_FORMULA_CALC
),
85 SwResId(STR_FORMULA_CALC
));
86 InsertItem(FN_FORMULA_CANCEL
, Image(StockImage::Yes
, RID_BMP_FORMULA_CANCEL
),
87 SwResId(STR_FORMULA_CANCEL
));
88 InsertItem(FN_FORMULA_APPLY
, Image(StockImage::Yes
, RID_BMP_FORMULA_APPLY
),
89 SwResId(STR_FORMULA_APPLY
));
91 SetHelpId(FN_FORMULA_CALC
, HID_TBX_FORMULA_CALC
);
92 SetHelpId(FN_FORMULA_CANCEL
, HID_TBX_FORMULA_CANCEL
);
93 SetHelpId(FN_FORMULA_APPLY
, HID_TBX_FORMULA_APPLY
);
95 SwView
*pDispatcherView
= dynamic_cast<SwView
*>(pDispatcher
? pDispatcher
->GetFrame()->GetViewShell() : nullptr);
96 SwView
* pActiveView
= ::GetActiveView();
97 if (pDispatcherView
== pActiveView
)
98 m_pView
= pActiveView
;
99 m_pWrtShell
= m_pView
? m_pView
->GetWrtShellPtr() : nullptr;
101 InsertWindow(ED_POS
, mxPos
.get(), ToolBoxItemBits::NONE
, 0);
102 SetItemText(ED_POS
, SwResId(STR_ACCESS_FORMULA_TYPE
));
103 mxPos
->set_accessible_name(SwResId(STR_ACCESS_FORMULA_TYPE
));
104 SetAccessibleName(SwResId(STR_ACCESS_FORMULA_TOOLBAR
));
105 InsertSeparator ( 1 );
107 InsertWindow(ED_FORMULA
, mxEdit
.get());
108 SetItemText(ED_FORMULA
, SwResId(STR_ACCESS_FORMULA_TEXT
));
109 mxEdit
->set_accessible_name(SwResId(STR_ACCESS_FORMULA_TEXT
));
110 SetHelpId(ED_FORMULA
, HID_EDIT_FORMULA
);
112 SetItemBits( FN_FORMULA_CALC
, GetItemBits( FN_FORMULA_CALC
) | ToolBoxItemBits::DROPDOWNONLY
);
113 SetDropdownClickHdl( LINK( this, SwInputWindow
, DropdownClickHdl
));
115 Size aSizeTbx
= CalcWindowSizePixel();
116 Size aEditSize
= mxEdit
->GetSizePixel();
117 tools::Rectangle
aItemRect( GetItemRect(FN_FORMULA_CALC
) );
118 tools::Long nMaxHeight
= std::max(aEditSize
.Height(), aItemRect
.GetHeight());
119 if( nMaxHeight
+2 > aSizeTbx
.Height() )
120 aSizeTbx
.setHeight( nMaxHeight
+2 );
121 Size aSize
= GetSizePixel();
122 aSize
.setHeight( aSizeTbx
.Height() );
123 SetSizePixel( aSize
);
125 // align edit and item vcentered
126 Size aPosSize
= mxPos
->GetSizePixel();
127 aPosSize
.setHeight( nMaxHeight
);
128 aEditSize
.setHeight( nMaxHeight
);
129 Point aPosPos
= mxPos
->GetPosPixel();
130 Point aEditPos
= mxEdit
->GetPosPixel();
131 aPosPos
.setY( (aSize
.Height() - nMaxHeight
)/2 + 1 );
132 aEditPos
.setY( (aSize
.Height() - nMaxHeight
)/2 + 1 );
133 mxPos
->SetPosSizePixel( aPosPos
, aPosSize
);
134 mxEdit
->SetPosSizePixel( aEditPos
, aEditSize
);
137 SwInputWindow::~SwInputWindow()
142 void SwInputWindow::dispose()
147 m_pView
->GetHRuler().SetActive();
148 m_pView
->GetVRuler().SetActive();
152 m_pWrtShell
->EndSelTableCells();
154 CleanupUglyHackWithUndo();
156 mxPos
.disposeAndClear();
157 mxEdit
.disposeAndClear();
161 void SwInputWindow::CleanupUglyHackWithUndo()
169 m_pWrtShell
->DoUndo(m_bDoesUndo
);
175 m_bResetUndo
= false; // #i117122# once is enough :)
178 void SwInputWindow::Resize()
182 tools::Long nWidth
= GetSizePixel().Width();
183 tools::Long nLeft
= mxEdit
->GetPosPixel().X();
184 Size aEditSize
= mxEdit
->GetSizePixel();
186 aEditSize
.setWidth( std::max( static_cast<tools::Long
>(nWidth
- nLeft
- 5), tools::Long(0) ) );
187 mxEdit
->SetSizePixel( aEditSize
);
190 void SwInputWindow::ShowWin()
194 if (m_pView
&& m_pWrtShell
)
196 m_pView
->GetHRuler().SetActive( false );
197 m_pView
->GetVRuler().SetActive( false );
200 m_bIsTable
= m_pWrtShell
->IsCursorInTable();
203 m_pWrtShell
->SelTableCells( LINK( this, SwInputWindow
,
204 SelTableCellsNotify
) );
207 const OUString
& rPos
= m_pWrtShell
->GetBoxNms();
210 while( (nPos
= rPos
.indexOf( ':',nPos
+ 1 ) ) != -1 )
211 nSrch
= static_cast<short>(nPos
);
212 mxPos
->set_text( rPos
.copy( ++nSrch
) );
213 m_aCurrentTableName
= m_pWrtShell
->GetTableFormat()->GetName();
216 mxPos
->set_text(SwResId(STR_TBL_FORMULA
));
218 // Edit current field
219 OSL_ENSURE(m_pMgr
== nullptr, "FieldManager not deleted");
220 m_pMgr
.reset(new SwFieldMgr
);
222 // Form should always begin with "=" , so set here
224 if( m_pMgr
->GetCurField() && SwFieldTypesEnum::Formel
== m_pMgr
->GetCurTypeId() )
226 sEdit
+= m_pMgr
->GetCurFieldPar2();
228 else if( m_bFirst
&& m_bIsTable
)
232 officecfg::Office::Common::Undo::Steps::get() <= 0,
233 "sw", "/org.openoffice.Office.Common/Undo/Steps <= 0");
235 m_bDoesUndo
= m_pWrtShell
->DoesUndo();
238 m_pWrtShell
->DoUndo();
241 if( !m_pWrtShell
->SwCursorShell::HasSelection() )
243 m_pWrtShell
->MoveSection( GoCurrSection
, fnSectionStart
);
244 m_pWrtShell
->SetMark();
245 m_pWrtShell
->MoveSection( GoCurrSection
, fnSectionEnd
);
247 if( m_pWrtShell
->SwCursorShell::HasSelection() )
249 m_pWrtShell
->StartUndo( SwUndoId::DELETE
);
250 m_pWrtShell
->Delete(false);
251 if( SwUndoId::EMPTY
!= m_pWrtShell
->EndUndo( SwUndoId::DELETE
))
256 m_pWrtShell
->DoUndo(false);
258 SfxItemSetFixed
<RES_BOXATR_FORMULA
, RES_BOXATR_FORMULA
> aSet( m_pWrtShell
->GetAttrPool() );
259 if( m_pWrtShell
->GetTableBoxFormulaAttrs( aSet
))
261 SwTableBoxFormula
& rFormula
262 = const_cast<SwTableBoxFormula
&>(aSet
.Get(RES_BOXATR_FORMULA
));
263 // rFormula could be ANY of the table's formulas.
264 // GetFormula returns the "current" formula - which is basically undefined,
265 // so do something that encourages the current position's formula to become current.
266 if (m_pWrtShell
->GetCursor())
268 const SwNode
* pNd
= m_pWrtShell
->GetCursor()->GetPointNode().FindTableNode();
271 const SwTable
& rTable
= static_cast<const SwTableNode
*>(pNd
)->GetTable();
272 // get cell's external formula (for UI) by waving the magic wand.
273 rFormula
.PtrToBoxNm(&rTable
);
277 sEdit
+= rFormula
.GetFormula();
283 // Set WrtShell flags correctly
284 m_pWrtShell
->SttSelect();
285 m_pWrtShell
->EndSelect();
290 mxEdit
->connect_changed( LINK( this, SwInputWindow
, ModifyHdl
));
292 mxEdit
->set_text( sEdit
);
293 m_sOldFormula
= sEdit
;
295 // For input cut the UserInterface
297 m_pView
->GetEditWin().LockKeyInput(true);
298 m_pView
->GetViewFrame().GetDispatcher()->Lock(true);
304 // grab focus after ToolBox is shown so focus isn't potentially lost elsewhere
307 int nPos
= mxEdit
->get_text().getLength();
308 mxEdit
->select_region(nPos
, nPos
);
313 void SwInputWindow::MenuHdl(std::u16string_view command
)
315 if (!command
.empty())
316 mxEdit
->replace_selection(OUString::Concat(command
) + " ");
319 IMPL_LINK_NOARG(SwInputWindow
, DropdownClickHdl
, ToolBox
*, void)
321 ToolBoxItemId nCurID
= GetCurItemId();
322 EndSelection(); // reset back CurItemId !
323 if (nCurID
== FN_FORMULA_CALC
)
325 std::unique_ptr
<weld::Builder
> xBuilder(Application::CreateBuilder(nullptr, "modules/swriter/ui/inputwinmenu.ui"));
326 std::unique_ptr
<weld::Menu
> xPopMenu(xBuilder
->weld_menu("menu"));
327 tools::Rectangle
aRect(GetItemRect(FN_FORMULA_CALC
));
328 weld::Window
* pParent
= weld::GetPopupParent(*this, aRect
);
329 MenuHdl(xPopMenu
->popup_at_rect(pParent
, aRect
));
333 void SwInputWindow::Click( )
335 ToolBoxItemId nCurID
= GetCurItemId();
336 EndSelection(); // reset back CurItemId !
337 if ( nCurID
== FN_FORMULA_CANCEL
)
341 else if (nCurID
== FN_FORMULA_APPLY
)
347 void SwInputWindow::ApplyFormula()
349 // in case it was created while loading the document, the active view
350 // wasn't initialised at that time, so ShowWin() didn't initialise anything
351 // either - nothing to do
352 if (!m_pView
|| !m_pWrtShell
)
354 // presumably there must be an active view now since the event arrived
355 if (SwView
* pView
= GetActiveView())
357 // this just makes the input window go away, so that the next time it works
358 pView
->GetViewFrame().GetDispatcher()->Execute(FN_EDIT_FORMULA
, SfxCallMode::ASYNCHRON
);
363 m_pView
->GetViewFrame().GetDispatcher()->Lock(false);
364 m_pView
->GetEditWin().LockKeyInput(false);
365 CleanupUglyHackWithUndo();
366 m_pWrtShell
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
368 // Form should always begin with "=", so remove it here again
369 OUString
sEdit(comphelper::string::strip(mxEdit
->get_text(), ' '));
370 if( !sEdit
.isEmpty() && '=' == sEdit
[0] )
371 sEdit
= sEdit
.copy( 1 );
372 SfxStringItem
aParam(FN_EDIT_FORMULA
, sEdit
);
374 m_pWrtShell
->EndSelTableCells();
375 m_pView
->GetEditWin().GrabFocus();
376 const SfxPoolItem
* aArgs
[2];
379 m_pView
->GetViewFrame().GetBindings().Execute( FN_EDIT_FORMULA
, aArgs
, SfxCallMode::ASYNCHRON
);
382 void SwInputWindow::CancelFormula()
384 // in case it was created while loading the document, the active view
385 // wasn't initialised at that time, so ShowWin() didn't initialise anything
386 // either - nothing to do
387 if (!m_pView
|| !m_pWrtShell
)
389 // presumably there must be an active view now since the event arrived
390 if (SwView
* pView
= GetActiveView())
392 // this just makes the input window go away, so that the next time it works
393 pView
->GetViewFrame().GetDispatcher()->Execute(FN_EDIT_FORMULA
, SfxCallMode::ASYNCHRON
);
398 m_pView
->GetViewFrame().GetDispatcher()->Lock( false );
399 m_pView
->GetEditWin().LockKeyInput(false);
400 CleanupUglyHackWithUndo();
401 m_pWrtShell
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
404 m_pWrtShell
->EnterStdMode();
406 m_pWrtShell
->EndSelTableCells();
408 m_pView
->GetEditWin().GrabFocus();
410 m_pView
->GetViewFrame().GetDispatcher()->Execute( FN_EDIT_FORMULA
, SfxCallMode::ASYNCHRON
);
413 const sal_Unicode CH_LRE
= 0x202a;
414 const sal_Unicode CH_PDF
= 0x202c;
416 IMPL_LINK( SwInputWindow
, SelTableCellsNotify
, SwWrtShell
&, rCaller
, void )
418 if(m_pWrtShell
&& m_bIsTable
)
420 SwFrameFormat
* pTableFormat
= rCaller
.GetTableFormat();
421 OUString
sBoxNms( rCaller
.GetBoxNms() );
423 if( pTableFormat
&& m_aCurrentTableName
!= pTableFormat
->GetName() )
424 sTableNm
= pTableFormat
->GetName();
426 mxEdit
->UpdateRange( sBoxNms
, sTableNm
);
428 OUString sNew
= OUStringChar(CH_LRE
) + mxEdit
->get_text()
429 + OUStringChar(CH_PDF
);
431 if( sNew
!= m_sOldFormula
)
433 // The WrtShell is in the table selection,
434 // then cancel the table selection otherwise, the cursor is
435 // positioned "in the forest" and the live update does not work!
436 m_pWrtShell
->StartAllAction();
438 SwPaM
aPam( *m_pWrtShell
->GetStackCursor()->GetPoint() );
439 aPam
.Move( fnMoveBackward
, GoInSection
);
441 aPam
.Move( fnMoveForward
, GoInSection
);
443 IDocumentContentOperations
& rIDCO
= m_pWrtShell
->getIDocumentContentOperations();
444 rIDCO
.DeleteRange( aPam
);
445 rIDCO
.InsertString( aPam
, sNew
);
446 m_pWrtShell
->EndAllAction();
447 m_sOldFormula
= sNew
;
454 void SwInputWindow::SetFormula( const OUString
& rFormula
)
457 if( !rFormula
.isEmpty() )
459 if( '=' == rFormula
[0] )
464 mxEdit
->set_text( sEdit
);
465 mxEdit
->select_region(sEdit
.getLength(), sEdit
.getLength());
469 IMPL_LINK_NOARG(SwInputWindow
, ModifyHdl
, weld::Entry
&, void)
471 if (m_pWrtShell
&& m_bIsTable
&& m_bResetUndo
)
473 m_pWrtShell
->StartAllAction();
475 OUString sNew
= OUStringChar(CH_LRE
) + mxEdit
->get_text()
476 + OUStringChar(CH_PDF
);
477 m_pWrtShell
->SwEditShell::Insert2( sNew
);
478 m_pWrtShell
->EndAllAction();
479 m_sOldFormula
= sNew
;
483 void SwInputWindow::DelBoxContent()
485 if( m_pWrtShell
&& m_bIsTable
)
487 m_pWrtShell
->StartAllAction();
488 m_pWrtShell
->ClearMark();
489 m_pWrtShell
->Pop(SwCursorShell::PopMode::DeleteCurrent
);
491 m_pWrtShell
->MoveSection( GoCurrSection
, fnSectionStart
);
492 m_pWrtShell
->SetMark();
493 m_pWrtShell
->MoveSection( GoCurrSection
, fnSectionEnd
);
494 m_pWrtShell
->SwEditShell::Delete(false);
495 m_pWrtShell
->EndAllAction();
499 IMPL_LINK(InputEdit
, KeyInputHdl
, const KeyEvent
&, rEvent
, bool)
501 bool bHandled
= false;
502 const vcl::KeyCode aCode
= rEvent
.GetKeyCode();
503 if (aCode
== KEY_RETURN
|| aCode
== KEY_F2
)
505 bHandled
= ActivateHdl(*m_xWidget
);
507 else if(aCode
== KEY_ESCAPE
)
509 static_cast<SwInputWindow
*>(GetParent())->CancelFormula();
512 return bHandled
|| ChildKeyInput(rEvent
);
515 IMPL_LINK_NOARG(InputEdit
, ActivateHdl
, weld::Entry
&, bool)
517 static_cast<SwInputWindow
*>(GetParent())->ApplyFormula();
521 void InputEdit::UpdateRange(std::u16string_view rBoxes
,
522 const OUString
& rName
)
529 const sal_Unicode cOpen
= '<', cClose
= '>',
531 OUString aPrefix
= rName
;
534 OUString aBoxes
= aPrefix
+ rBoxes
;
536 int nSelStartPos
, nSelEndPos
;
537 m_xWidget
->get_selection_bounds(nSelStartPos
, nSelEndPos
);
539 Selection
aSelection(nSelStartPos
, nSelEndPos
);
540 sal_uInt16 nSel
= o3tl::narrowing
<sal_uInt16
>(aSelection
.Len());
541 // OS: The following expression ensures that in the overwrite mode,
542 // the selected closing parenthesis will be not deleted.
543 if( nSel
&& ( nSel
> 1 ||
544 m_xWidget
->get_text()[ o3tl::narrowing
<sal_uInt16
>(aSelection
.Min()) ] != cClose
))
545 m_xWidget
->cut_clipboard();
547 aSelection
.Max() = aSelection
.Min();
548 OUString
aActText(m_xWidget
->get_text());
549 const sal_uInt16 nLen
= aActText
.getLength();
552 OUString aStr
= OUStringChar(cOpen
) + aBoxes
+ OUStringChar(cClose
);
553 m_xWidget
->set_text(aStr
);
554 sal_Int32 nPos
= aStr
.indexOf( cClose
);
555 OSL_ENSURE(nPos
!= -1, "delimiter not found");
557 m_xWidget
->select_region(nPos
, nPos
);
563 sal_uInt16 nPos
, nEndPos
= 0, nStartPos
= o3tl::narrowing
<sal_uInt16
>(aSelection
.Min());
567 if( cOpen
== (cCh
= aActText
[ nStartPos
] ) ||
568 cOpenBracket
== cCh
)
570 bFound
= cCh
== cOpen
;
573 } while( nStartPos
-- > 0 );
579 while( nEndPos
< nLen
)
581 if( cClose
== aActText
[ nEndPos
] )
588 // Only if the current position lies in the range or right behind.
589 if( bFound
&& ( nStartPos
>= o3tl::make_unsigned(aSelection
.Max()) ||
590 o3tl::narrowing
<sal_uInt16
>(aSelection
.Max()) > nEndPos
+ 1 ))
595 nPos
= ++nStartPos
+ 1; // We want behind
596 aActText
= aActText
.replaceAt( nStartPos
, nEndPos
- nStartPos
, aBoxes
);
597 nPos
= nPos
+ aBoxes
.getLength();
601 OUString aTmp
= OUStringChar(cOpen
) + aBoxes
+ OUStringChar(cClose
);
602 nPos
= o3tl::narrowing
<sal_uInt16
>(aSelection
.Min());
603 aActText
= aActText
.replaceAt( nPos
, 0, aTmp
);
604 nPos
= nPos
+ aTmp
.getLength();
606 if( m_xWidget
->get_text() != aActText
)
608 m_xWidget
->set_text(aActText
);
609 m_xWidget
->select_region(nPos
, nPos
);
616 SwInputChild::SwInputChild(vcl::Window
* _pParent
,
618 SfxBindings
const * pBindings
,
620 SfxChildWindow( _pParent
, nId
)
622 m_pDispatch
= pBindings
->GetDispatcher();
623 SetWindow(VclPtr
<SwInputWindow
>::Create(_pParent
, m_pDispatch
));
624 static_cast<SwInputWindow
*>(GetWindow())->ShowWin();
625 SetAlignment(SfxChildAlignment::LOWESTTOP
);
628 SwInputChild::~SwInputChild()
631 m_pDispatch
->Lock(false);
634 SfxChildWinInfo
SwInputChild::GetInfo() const
636 SfxChildWinInfo aInfo
= SfxChildWindow::GetInfo();
640 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */