/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace com::sun::star::accessibility { class XAccessible; } const tools::Long THESIZE = 1000000; // Should be more than enough! const tools::Long INPUTLINE_INSET_MARGIN = 2; // Space between border and interior widgets of input line const tools::Long LEFT_OFFSET = 5; // Left offset of input line //TODO const long BUTTON_OFFSET = 2; // Space between input line and button to expand/collapse const tools::Long INPUTWIN_MULTILINES = 6; // Initial number of lines within multiline dropdown const tools::Long TOOLBOX_WINDOW_HEIGHT = 22; // Height of toolbox window in pixels - TODO: The same on all systems? const tools::Long POSITION_COMBOBOX_WIDTH = 18; // Width of position combobox in characters const int RESIZE_HOTSPOT_HEIGHT = 4; using com::sun::star::uno::Reference; using com::sun::star::uno::UNO_QUERY; using com::sun::star::frame::XLayoutManager; using com::sun::star::beans::XPropertySet; namespace { constexpr ToolBoxItemId SID_INPUT_FUNCTION (SC_VIEW_START + 47); constexpr ToolBoxItemId SID_INPUT_SUM (SC_VIEW_START + 48); constexpr ToolBoxItemId SID_INPUT_EQUAL (SC_VIEW_START + 49); constexpr ToolBoxItemId SID_INPUT_CANCEL (SC_VIEW_START + 50); constexpr ToolBoxItemId SID_INPUT_OK (SC_VIEW_START + 51); enum ScNameInputType { SC_NAME_INPUT_CELL, SC_NAME_INPUT_RANGE, SC_NAME_INPUT_NAMEDRANGE_LOCAL, SC_NAME_INPUT_NAMEDRANGE_GLOBAL, SC_NAME_INPUT_DATABASE, SC_NAME_INPUT_ROW, SC_NAME_INPUT_SHEET, SC_NAME_INPUT_DEFINE, SC_NAME_INPUT_BAD_NAME, SC_NAME_INPUT_BAD_SELECTION, SC_MANAGE_NAMES }; } SFX_IMPL_CHILDWINDOW_WITHID(ScInputWindowWrapper,FID_INPUTLINE_STATUS) ScInputWindowWrapper::ScInputWindowWrapper( vcl::Window* pParentP, sal_uInt16 nId, SfxBindings* pBindings, SfxChildWinInfo* /* pInfo */ ) : SfxChildWindow( pParentP, nId ) { VclPtr pWin = VclPtr::Create( pParentP, pBindings ); SetWindow( pWin ); pWin->Show(); pWin->SetSizePixel( pWin->CalcWindowSizePixel() ); SetAlignment(SfxChildAlignment::LOWESTTOP); pBindings->Invalidate( FID_TOGGLEINPUTLINE ); } /** * GetInfo is disposed of if there's a SFX_IMPL_TOOLBOX! */ SfxChildWinInfo ScInputWindowWrapper::GetInfo() const { SfxChildWinInfo aInfo = SfxChildWindow::GetInfo(); return aInfo; } static VclPtr lcl_chooseRuntimeImpl( vcl::Window* pParent, const SfxBindings* pBind ) { ScTabViewShell* pViewSh = nullptr; SfxDispatcher* pDisp = pBind->GetDispatcher(); if ( pDisp ) { SfxViewFrame* pViewFrm = pDisp->GetFrame(); if ( pViewFrm ) pViewSh = dynamic_cast( pViewFrm->GetViewShell() ); } return VclPtr::Create( pParent, pViewSh ); } ScInputWindow::ScInputWindow( vcl::Window* pParent, const SfxBindings* pBind ) : // With WB_CLIPCHILDREN otherwise we get flickering ToolBox ( pParent, WinBits(WB_CLIPCHILDREN | WB_BORDER | WB_NOSHADOW) ), aWndPos ( !comphelper::LibreOfficeKit::isActive() ? VclPtr::Create(this) : nullptr ), mxTextWindow ( lcl_chooseRuntimeImpl( this, pBind ) ), pInputHdl ( nullptr ), mpViewShell ( nullptr ), mnMaxY (0), mnStandardItemHeight(0), bIsOkCancelMode ( false ), bInResize ( false ) { // #i73615# don't rely on SfxViewShell::Current while constructing the input line // (also for GetInputHdl below) ScTabViewShell* pViewSh = nullptr; SfxDispatcher* pDisp = pBind->GetDispatcher(); if ( pDisp ) { SfxViewFrame* pViewFrm = pDisp->GetFrame(); if ( pViewFrm ) pViewSh = dynamic_cast( pViewFrm->GetViewShell() ); } OSL_ENSURE( pViewSh, "no view shell for input window" ); mpViewShell = pViewSh; // Position window, 3 buttons, input window if (!comphelper::LibreOfficeKit::isActive()) { InsertWindow (ToolBoxItemId(1), aWndPos.get(), ToolBoxItemBits::NONE, 0); InsertSeparator (1); InsertItem (SID_INPUT_FUNCTION, Image(StockImage::Yes, RID_BMP_INPUT_FUNCTION), ToolBoxItemBits::NONE, 2); } const bool bIsLOKMobilePhone = mpViewShell && mpViewShell->isLOKMobilePhone(); // sigma and equal buttons if (!bIsLOKMobilePhone) { InsertItem (SID_INPUT_SUM, Image(StockImage::Yes, RID_BMP_INPUT_SUM), ToolBoxItemBits::DROPDOWN, 3); InsertItem (SID_INPUT_EQUAL, Image(StockImage::Yes, RID_BMP_INPUT_EQUAL), ToolBoxItemBits::NONE, 4); InsertItem (SID_INPUT_CANCEL, Image(StockImage::Yes, RID_BMP_INPUT_CANCEL), ToolBoxItemBits::NONE, 5); InsertItem (SID_INPUT_OK, Image(StockImage::Yes, RID_BMP_INPUT_OK), ToolBoxItemBits::NONE, 6); } InsertWindow (ToolBoxItemId(7), mxTextWindow.get(), ToolBoxItemBits::NONE, 7); SetDropdownClickHdl( LINK( this, ScInputWindow, DropdownClickHdl )); if (!comphelper::LibreOfficeKit::isActive()) { aWndPos ->SetQuickHelpText(ScResId(SCSTR_QHELP_POSWND)); aWndPos ->SetHelpId (HID_INSWIN_POS); mxTextWindow->SetQuickHelpText(ScResId(SCSTR_QHELP_INPUTWND)); mxTextWindow->SetHelpId (HID_INSWIN_INPUT); // No SetHelpText: the helptexts come from the Help SetItemText (SID_INPUT_FUNCTION, ScResId(SCSTR_QHELP_BTNCALC)); SetHelpId (SID_INPUT_FUNCTION, HID_INSWIN_CALC); } // sigma and equal buttons if (!bIsLOKMobilePhone) { SetHelpId (SID_INPUT_SUM, HID_INSWIN_SUMME); SetHelpId (SID_INPUT_EQUAL, HID_INSWIN_FUNC); SetHelpId (SID_INPUT_CANCEL, HID_INSWIN_CANCEL); SetHelpId (SID_INPUT_OK, HID_INSWIN_OK); if (!comphelper::LibreOfficeKit::isActive()) { SetItemText ( SID_INPUT_SUM, ScResId( SCSTR_QHELP_BTNSUM ) ); SetItemText ( SID_INPUT_EQUAL, ScResId( SCSTR_QHELP_BTNEQUAL ) ); SetItemText ( SID_INPUT_CANCEL, ScResId( SCSTR_QHELP_BTNCANCEL ) ); SetItemText ( SID_INPUT_OK, ScResId( SCSTR_QHELP_BTNOK ) ); } EnableItem( SID_INPUT_CANCEL, false ); EnableItem( SID_INPUT_OK, false ); HideItem( SID_INPUT_CANCEL ); HideItem( SID_INPUT_OK ); mnStandardItemHeight = GetItemRect(SID_INPUT_SUM).GetHeight(); } SetHelpId( HID_SC_INPUTWIN ); // For the whole input row if (!comphelper::LibreOfficeKit::isActive()) aWndPos ->Show(); mxTextWindow->Show(); pInputHdl = SC_MOD()->GetInputHdl( pViewSh, false ); // use own handler even if ref-handler is set if (pInputHdl) pInputHdl->SetInputWindow( this ); if (pInputHdl && !pInputHdl->GetFormString().isEmpty()) { // Switch over while the Function AutoPilot is active // -> show content of the Function AutoPilot again // Also show selection (remember at the InputHdl) mxTextWindow->SetTextString(pInputHdl->GetFormString(), true); } else if (pInputHdl && pInputHdl->IsInputMode()) { // If the input row was hidden while editing (e.g. when editing a formula // and then switching to another document or the help), display the text // we just edited from the InputHandler mxTextWindow->SetTextString(pInputHdl->GetEditString(), true); // Display text if ( pInputHdl->IsTopMode() ) pInputHdl->SetMode( SC_INPUT_TABLE ); // Focus ends up at the bottom anyways } else if (pViewSh) { // Don't stop editing in LOK a remote user might be editing. const bool bStopEditing = !comphelper::LibreOfficeKit::isActive(); pViewSh->UpdateInputHandler(true, bStopEditing); // Absolutely necessary update } SetToolbarLayoutMode( ToolBoxLayoutMode::Locked ); SetAccessibleName(ScResId(STR_ACC_TOOLBAR_FORMULA)); } ScInputWindow::~ScInputWindow() { disposeOnce(); } void ScInputWindow::dispose() { bool bDown = !ScGlobal::oSysLocale; // after Clear? // if any view's input handler has a pointer to this input window, reset it // (may be several ones, #74522#) // member pInputHdl is not used here if ( !bDown ) { SfxViewShell* pSh = SfxViewShell::GetFirst( true, checkSfxViewShell ); while ( pSh ) { ScInputHandler* pHdl = static_cast(pSh)->GetInputHandler(); if ( pHdl && pHdl->GetInputWindow() == this ) { pHdl->SetInputWindow( nullptr ); pHdl->StopInputWinEngine( false ); // reset pTopView pointer } pSh = SfxViewShell::GetNext( *pSh, true, checkSfxViewShell ); } } if (comphelper::LibreOfficeKit::isActive()) { if (GetLOKNotifier()) ReleaseLOKNotifier(); } mxTextWindow.disposeAndClear(); aWndPos.disposeAndClear(); ToolBox::dispose(); } void ScInputWindow::SetInputHandler( ScInputHandler* pNew ) { // Is called in the Activate of the View ... if ( pNew != pInputHdl ) { // On Reload (last version) the pInputHdl is the InputHandler of the old, deleted // ViewShell: so don't touch it here! pInputHdl = pNew; if (pInputHdl) pInputHdl->SetInputWindow( this ); } } void ScInputWindow::Select() { ScModule* pScMod = SC_MOD(); ToolBox::Select(); ToolBoxItemId curItemId = GetCurItemId(); if (curItemId == SID_INPUT_FUNCTION) { //! new method at ScModule to query if function autopilot is open SfxViewFrame* pViewFrm = SfxViewFrame::Current(); if ( pViewFrm && ( comphelper::LibreOfficeKit::isActive() || !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) ) ) { pViewFrm->GetDispatcher()->Execute( SID_OPENDLG_FUNCTION, SfxCallMode::SYNCHRON | SfxCallMode::RECORD ); // The Toolbox will be disabled anyways, so we don't need to switch here, // regardless whether it succeeded or not! // SetOkCancelMode(); } } else if (curItemId == SID_INPUT_CANCEL) { pScMod->InputCancelHandler(); SetSumAssignMode(); } else if (curItemId == SID_INPUT_OK) { pScMod->InputEnterHandler(); SetSumAssignMode(); mxTextWindow->Invalidate(); // Or else the Selection remains } else if (curItemId == SID_INPUT_SUM) { bool bRangeFinder = false; bool bSubTotal = false; AutoSum(bRangeFinder, bSubTotal, ocSum); } else if (curItemId == SID_INPUT_EQUAL) { StartFormula(); } } void ScInputWindow::StartFormula() { ScModule* pScMod = SC_MOD(); mxTextWindow->StartEditEngine(); if ( pScMod->IsEditMode() ) // Isn't if e.g. protected { mxTextWindow->StartEditEngine(); sal_Int32 nStartPos = 1; sal_Int32 nEndPos = 1; ScTabViewShell* pViewSh = dynamic_cast( SfxViewShell::Current() ); if ( pViewSh ) { const OUString& rString = mxTextWindow->GetTextString(); const sal_Int32 nLen = rString.getLength(); ScDocument& rDoc = pViewSh->GetViewData().GetDocument(); CellType eCellType = rDoc.GetCellType( pViewSh->GetViewData().GetCurPos() ); switch ( eCellType ) { case CELLTYPE_VALUE: { nEndPos = nLen + 1; mxTextWindow->SetTextString("=" + rString, true); break; } case CELLTYPE_STRING: case CELLTYPE_EDIT: nStartPos = 0; nEndPos = nLen; break; case CELLTYPE_FORMULA: nEndPos = nLen; break; default: mxTextWindow->SetTextString(u"="_ustr, true); break; } } EditView* pView = mxTextWindow->GetEditView(); if (pView) { sal_Int32 nStartPara = 0, nEndPara = 0; if (comphelper::LibreOfficeKit::isActive()) { TextGrabFocus(); if (pViewSh && !pViewSh->isLOKDesktop()) { nStartPara = nEndPara = pView->getEditEngine().GetParagraphCount() ? (pView->getEditEngine().GetParagraphCount() - 1) : 0; nStartPos = nEndPos = pView->getEditEngine().GetTextLen(nStartPara); } } pView->SetSelection(ESelection(nStartPara, nStartPos, nEndPara, nEndPos)); pScMod->InputChanged(pView); SetOkCancelMode(); pView->SetEditEngineUpdateLayout(true); } } } void ScInputWindow::PixelInvalidate(const tools::Rectangle* pRectangle) { if (comphelper::LibreOfficeKit::isDialogPainting() || !comphelper::LibreOfficeKit::isActive()) return; if (pRectangle) { tools::Rectangle aRect(*pRectangle); aRect.Move(-GetOutOffXPixel(), -GetOutOffYPixel()); Window::PixelInvalidate(&aRect); } else { Window::PixelInvalidate(nullptr); } } void ScInputWindow::SetSizePixel( const Size& rNewSize ) { const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier(); if (pNotifier) { if (vcl::Window* pFrameWindowImpl = GetParent()) { if (vcl::Window* pWorkWindow = pFrameWindowImpl->GetParent()) { if (vcl::Window* pImplBorderWindow = pWorkWindow->GetParent()) { Size aSize = pImplBorderWindow->GetSizePixel(); aSize.setWidth(rNewSize.getWidth()); pImplBorderWindow->SetSizePixel(aSize); } } } } ToolBox::SetSizePixel(rNewSize); } void ScInputWindow::Resize() { ToolBox::Resize(); Size aStartSize = GetSizePixel(); Size aSize = aStartSize; auto nLines = mxTextWindow->GetNumLines(); //(-10) to allow margin between sidebar and formulabar tools::Long margin = (comphelper::LibreOfficeKit::isActive()) ? 10 : 0; Size aTextWindowSize(aSize.Width() - mxTextWindow->GetPosPixel().X() - LEFT_OFFSET - margin, mxTextWindow->GetPixelHeightForLines(nLines)); mxTextWindow->SetSizePixel(aTextWindowSize); int nTopOffset = 0; if (nLines > 1) { // Initially there is 1 line and the edit is vertically centered in the toolbar // Later, if expanded then the vertical position of the edit will remain at // that initial position, so when calculating the overall size of the expanded // toolbar we have to include that initial offset in order to not make // the edit overlap the RESIZE_HOTSPOT_HEIGHT area so that dragging to resize // is still possible. int nNormalHeight = mxTextWindow->GetPixelHeightForLines(1); int nInitialTopMargin = (mnStandardItemHeight - nNormalHeight) / 2; if (nInitialTopMargin > 0) nTopOffset = nInitialTopMargin; } // add empty space of RESIZE_HOTSPOT_HEIGHT so resize is possible when hovering there aSize.setHeight(CalcWindowSizePixel().Height() + RESIZE_HOTSPOT_HEIGHT + nTopOffset); if (aStartSize != aSize) SetSizePixel(aSize); Invalidate(); } void ScInputWindow::NotifyLOKClient() { if (comphelper::LibreOfficeKit::isActive() && !GetLOKNotifier() && mpViewShell) SetLOKNotifier(mpViewShell); } void ScInputWindow::SetFuncString( const OUString& rString, bool bDoEdit ) { //! new method at ScModule to query if function autopilot is open SfxViewFrame* pViewFrm = SfxViewFrame::Current(); EnableButtons( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) ); mxTextWindow->StartEditEngine(); ScModule* pScMod = SC_MOD(); if ( !pScMod->IsEditMode() ) return; if ( bDoEdit ) mxTextWindow->TextGrabFocus(); mxTextWindow->SetTextString(rString, true); EditView* pView = mxTextWindow->GetEditView(); if (!pView) return; sal_Int32 nLen = rString.getLength(); if ( nLen > 0 ) { nLen--; pView->SetSelection( ESelection( 0, nLen, 0, nLen ) ); } pScMod->InputChanged(pView); if ( bDoEdit ) SetOkCancelMode(); // Not the case if immediately followed by Enter/Cancel pView->SetEditEngineUpdateLayout(true); } void ScInputWindow::SetPosString( const OUString& rStr ) { if (!comphelper::LibreOfficeKit::isActive()) aWndPos->SetPos( rStr ); } void ScInputWindow::SetTextString( const OUString& rString, bool bKitUpdate ) { if (rString.getLength() <= 32767) mxTextWindow->SetTextString(rString, bKitUpdate); else mxTextWindow->SetTextString(rString.copy(0, 32767), bKitUpdate); } void ScInputWindow::SetOkCancelMode() { //! new method at ScModule to query if function autopilot is open SfxViewFrame* pViewFrm = SfxViewFrame::Current(); EnableButtons( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) ); if (bIsOkCancelMode) return; EnableItem ( SID_INPUT_SUM, false ); EnableItem ( SID_INPUT_EQUAL, false ); HideItem ( SID_INPUT_SUM ); HideItem ( SID_INPUT_EQUAL ); ShowItem ( SID_INPUT_CANCEL, true ); ShowItem ( SID_INPUT_OK, true ); EnableItem ( SID_INPUT_CANCEL, true ); EnableItem ( SID_INPUT_OK, true ); bIsOkCancelMode = true; } void ScInputWindow::SetSumAssignMode() { //! new method at ScModule to query if function autopilot is open SfxViewFrame* pViewFrm = SfxViewFrame::Current(); EnableButtons( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) ); if (!bIsOkCancelMode) return; EnableItem ( SID_INPUT_CANCEL, false ); EnableItem ( SID_INPUT_OK, false ); HideItem ( SID_INPUT_CANCEL ); HideItem ( SID_INPUT_OK ); ShowItem ( SID_INPUT_SUM, true ); ShowItem ( SID_INPUT_EQUAL, true ); EnableItem ( SID_INPUT_SUM, true ); EnableItem ( SID_INPUT_EQUAL, true ); bIsOkCancelMode = false; SetFormulaMode(false); // No editing -> no formula } void ScInputWindow::SetFormulaMode( bool bSet ) { if (!comphelper::LibreOfficeKit::isActive()) aWndPos->SetFormulaMode(bSet); mxTextWindow->SetFormulaMode(bSet); } bool ScInputWindow::IsInputActive() { return mxTextWindow->IsInputActive(); } EditView* ScInputWindow::GetEditView() { return mxTextWindow->GetEditView(); } vcl::Window* ScInputWindow::GetEditWindow() { return mxTextWindow; } Point ScInputWindow::GetCursorScreenPixelPos(bool bBelow) { return mxTextWindow->GetCursorScreenPixelPos(bBelow); } void ScInputWindow::MakeDialogEditView() { mxTextWindow->MakeDialogEditView(); } void ScInputWindow::StopEditEngine( bool bAll ) { mxTextWindow->StopEditEngine( bAll ); } void ScInputWindow::TextGrabFocus() { mxTextWindow->TextGrabFocus(); } void ScInputWindow::TextInvalidate() { mxTextWindow->Invalidate(); } void ScInputWindow::SwitchToTextWin() { // used for shift-ctrl-F2 mxTextWindow->StartEditEngine(); if ( SC_MOD()->IsEditMode() ) { mxTextWindow->TextGrabFocus(); EditView* pView = mxTextWindow->GetEditView(); if (pView) { sal_Int32 nPara = pView->getEditEngine().GetParagraphCount() ? ( pView->getEditEngine().GetParagraphCount() - 1 ) : 0; sal_Int32 nLen = pView->getEditEngine().GetTextLen( nPara ); ESelection aSel( nPara, nLen, nPara, nLen ); pView->SetSelection( aSel ); // set cursor to end of text } } } void ScInputWindow::PosGrabFocus() { if (!comphelper::LibreOfficeKit::isActive()) aWndPos->GrabFocus(); } void ScInputWindow::EnableButtons( bool bEnable ) { // when enabling buttons, always also enable the input window itself if ( bEnable && !IsEnabled() ) Enable(); EnableItem( SID_INPUT_FUNCTION, bEnable ); EnableItem( bIsOkCancelMode ? SID_INPUT_CANCEL : SID_INPUT_SUM, bEnable ); EnableItem( bIsOkCancelMode ? SID_INPUT_OK : SID_INPUT_EQUAL, bEnable ); // Invalidate(); } void ScInputWindow::NumLinesChanged() { mxTextWindow->NumLinesChanged(); } void ScInputWindow::StateChanged( StateChangedType nType ) { ToolBox::StateChanged( nType ); if ( nType == StateChangedType::InitShow ) Resize(); } void ScInputWindow::DataChanged( const DataChangedEvent& rDCEvt ) { if ( rDCEvt.GetType() == DataChangedEventType::SETTINGS && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) { // update item images SetItemImage(SID_INPUT_FUNCTION, Image(StockImage::Yes, RID_BMP_INPUT_FUNCTION)); if ( bIsOkCancelMode ) { SetItemImage(SID_INPUT_CANCEL, Image(StockImage::Yes, RID_BMP_INPUT_CANCEL)); SetItemImage(SID_INPUT_OK, Image(StockImage::Yes, RID_BMP_INPUT_OK)); } else { SetItemImage(SID_INPUT_SUM, Image(StockImage::Yes, RID_BMP_INPUT_SUM)); SetItemImage(SID_INPUT_EQUAL, Image(StockImage::Yes, RID_BMP_INPUT_EQUAL)); } } ToolBox::DataChanged( rDCEvt ); } bool ScInputWindow::IsPointerAtResizePos() { return GetOutputSizePixel().Height() - GetPointerPosPixel().Y() <= RESIZE_HOTSPOT_HEIGHT; } void ScInputWindow::MouseMove( const MouseEvent& rMEvt ) { Point aPosPixel = GetPointerPosPixel(); ScInputBarGroup* pGroupBar = mxTextWindow.get(); if (bInResize || IsPointerAtResizePos()) SetPointer(PointerStyle::WindowSSize); else SetPointer(PointerStyle::Arrow); if (bInResize) { // detect direction tools::Long nResizeThreshold = tools::Long(TOOLBOX_WINDOW_HEIGHT * 0.7); bool bResetPointerPos = false; // Detect attempt to expand toolbar too much if (aPosPixel.Y() >= mnMaxY) { bResetPointerPos = true; aPosPixel.setY( mnMaxY ); } // or expanding down else if (GetOutputSizePixel().Height() - aPosPixel.Y() < -nResizeThreshold) { pGroupBar->IncrementVerticalSize(); bResetPointerPos = true; } // or shrinking up else if ((GetOutputSizePixel().Height() - aPosPixel.Y()) > nResizeThreshold) { bResetPointerPos = true; pGroupBar->DecrementVerticalSize(); } if (bResetPointerPos) { aPosPixel.setY( GetOutputSizePixel().Height() ); SetPointerPosPixel(aPosPixel); } } ToolBox::MouseMove(rMEvt); } void ScInputWindow::MouseButtonDown( const MouseEvent& rMEvt ) { if (rMEvt.IsLeft()) { if (IsPointerAtResizePos()) { // Don't leave the mouse pointer leave *this* window CaptureMouse(); bInResize = true; // find the height of the gridwin, we don't want to be // able to expand the toolbar too far so we need to // calculate an upper limit // I'd prefer to leave at least a single column header and a // row but I don't know how to get that value in pixels. // Use TOOLBOX_WINDOW_HEIGHT for the moment if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) { mnMaxY = GetOutputSizePixel().Height() + (pViewSh->GetGridHeight(SC_SPLIT_TOP) + pViewSh->GetGridHeight(SC_SPLIT_BOTTOM)) - TOOLBOX_WINDOW_HEIGHT; } } } ToolBox::MouseButtonDown( rMEvt ); } void ScInputWindow::MouseButtonUp( const MouseEvent& rMEvt ) { ReleaseMouse(); if ( rMEvt.IsLeft() ) { bInResize = false; mnMaxY = 0; } ToolBox::MouseButtonUp( rMEvt ); } void ScInputWindow::AutoSum( bool& bRangeFinder, bool& bSubTotal, OpCode eCode ) { ScModule* pScMod = SC_MOD(); ScTabViewShell* pViewSh = dynamic_cast( SfxViewShell::Current() ); if ( !pViewSh ) return; const OUString aFormula = pViewSh->DoAutoSum(bRangeFinder, bSubTotal, eCode); if ( aFormula.isEmpty() ) return; SetFuncString( aFormula ); const sal_Int32 aOpen = aFormula.indexOf('('); const sal_Int32 aLen = aFormula.getLength(); if (!(bRangeFinder && pScMod->IsEditMode())) return; ScInputHandler* pHdl = pScMod->GetInputHdl( pViewSh ); if ( !pHdl ) return; pHdl->InitRangeFinder( aFormula ); //! SetSelection at the InputHandler? //! Set bSelIsRef? if ( aOpen != -1 && aLen > aOpen ) { ESelection aSel( 0, aOpen + (bSubTotal ? 3 : 1), 0, aLen-1 ); EditView* pTableView = pHdl->GetTableView(); if ( pTableView ) pTableView->SetSelection( aSel ); EditView* pTopView = pHdl->GetTopView(); if ( pTopView ) pTopView->SetSelection( aSel ); } } ScInputBarGroup::ScInputBarGroup(vcl::Window* pParent, ScTabViewShell* pViewSh) : InterimItemWindow(pParent, u"modules/scalc/ui/inputbar.ui"_ustr, u"InputBar"_ustr, true, reinterpret_cast(pViewSh)) , mxBackground(m_xBuilder->weld_container(u"background"_ustr)) , mxTextWndGroup(new ScTextWndGroup(*this, pViewSh)) , mxButtonUp(m_xBuilder->weld_button(u"up"_ustr)) , mxButtonDown(m_xBuilder->weld_button(u"down"_ustr)) { InitControlBase(m_xContainer.get()); SetPaintTransparent(false); SetBackgrounds(); mxButtonUp->connect_clicked(LINK(this, ScInputBarGroup, ClickHdl)); mxButtonDown->connect_clicked(LINK(this, ScInputBarGroup, ClickHdl)); if (!comphelper::LibreOfficeKit::isActive()) { mxButtonUp->set_tooltip_text(ScResId( SCSTR_QHELP_COLLAPSE_FORMULA)); mxButtonDown->set_tooltip_text(ScResId(SCSTR_QHELP_EXPAND_FORMULA)); } int nHeight = mxTextWndGroup->GetPixelHeightForLines(1); mxButtonUp->set_size_request(-1, nHeight); mxButtonDown->set_size_request(-1, nHeight); // disable the multiline toggle on the mobile phones const SfxViewShell* pViewShell = SfxViewShell::Current(); if (!comphelper::LibreOfficeKit::isActive() || !(pViewShell && pViewShell->isLOKMobilePhone())) mxButtonDown->show(); // tdf#154042 Use an initial height of one row so the Toolbar positions // this in the same place regardless of how many rows it eventually shows Size aSize(GetSizePixel().Width(), nHeight); SetSizePixel(aSize); } void ScInputBarGroup::SetBackgrounds() { const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); SetBackground(rStyleSettings.GetFaceColor()); // match to bg used in ScTextWnd::SetDrawingArea to the margin area is drawn with the // same desired bg mxBackground->set_background(rStyleSettings.GetWindowColor()); } void ScInputBarGroup::DataChanged(const DataChangedEvent& rDCEvt) { InterimItemWindow::DataChanged(rDCEvt); if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) { SetBackgrounds(); Invalidate(); } } Point ScInputBarGroup::GetCursorScreenPixelPos(bool bBelow) { return mxTextWndGroup->GetCursorScreenPixelPos(bBelow); } ScInputBarGroup::~ScInputBarGroup() { disposeOnce(); } void ScInputBarGroup::dispose() { mxTextWndGroup.reset(); mxButtonUp.reset(); mxButtonDown.reset(); mxBackground.reset(); InterimItemWindow::dispose(); } void ScInputBarGroup::InsertAccessibleTextData( ScAccessibleEditLineTextData& rTextData ) { mxTextWndGroup->InsertAccessibleTextData(rTextData); } void ScInputBarGroup::RemoveAccessibleTextData( ScAccessibleEditLineTextData& rTextData ) { mxTextWndGroup->RemoveAccessibleTextData(rTextData); } const OUString& ScInputBarGroup::GetTextString() const { return mxTextWndGroup->GetTextString(); } void ScInputBarGroup::SetTextString(const OUString& rString, bool bKitUpdate) { mxTextWndGroup->SetTextString(rString, bKitUpdate); } void ScInputBarGroup::Resize() { mxTextWndGroup->SetScrollPolicy(); InterimItemWindow::Resize(); } void ScInputBarGroup::StopEditEngine(bool bAll) { mxTextWndGroup->StopEditEngine(bAll); } void ScInputBarGroup::StartEditEngine() { mxTextWndGroup->StartEditEngine(); } void ScInputBarGroup::MakeDialogEditView() { mxTextWndGroup->MakeDialogEditView(); } EditView* ScInputBarGroup::GetEditView() const { return mxTextWndGroup->GetEditView(); } bool ScInputBarGroup::HasEditView() const { return mxTextWndGroup->HasEditView(); } bool ScInputBarGroup::IsInputActive() { return mxTextWndGroup->IsInputActive(); } void ScInputBarGroup::SetFormulaMode(bool bSet) { mxTextWndGroup->SetFormulaMode(bSet); } void ScInputBarGroup::IncrementVerticalSize() { mxTextWndGroup->SetNumLines(mxTextWndGroup->GetNumLines() + 1); TriggerToolboxLayout(); } void ScInputBarGroup::DecrementVerticalSize() { if (mxTextWndGroup->GetNumLines() > 1) { mxTextWndGroup->SetNumLines(mxTextWndGroup->GetNumLines() - 1); TriggerToolboxLayout(); } } void ScInputWindow::MenuHdl(std::u16string_view command) { if (command.empty()) return; bool bSubTotal = false; bool bRangeFinder = false; OpCode eCode = ocSum; if ( command == u"sum" ) { eCode = ocSum; } else if ( command == u"average" ) { eCode = ocAverage; } else if ( command == u"max" ) { eCode = ocMax; } else if ( command == u"min" ) { eCode = ocMin; } else if ( command == u"count" ) { eCode = ocCount; } else if ( command == u"counta" ) { eCode = ocCount2; } else if ( command == u"product" ) { eCode = ocProduct; } else if (command == u"stdev") { eCode = ocStDev; } else if (command == u"stdevp") { eCode = ocStDevP; } else if (command == u"var") { eCode = ocVar; } else if (command == u"varp") { eCode = ocVarP; } AutoSum( bRangeFinder, bSubTotal, eCode ); } IMPL_LINK_NOARG(ScInputWindow, DropdownClickHdl, ToolBox *, void) { ToolBoxItemId nCurID = GetCurItemId(); EndSelection(); if (nCurID == SID_INPUT_SUM) { tools::Rectangle aRect(GetItemRect(SID_INPUT_SUM)); weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect); std::unique_ptr xBuilder(Application::CreateBuilder(pPopupParent, u"modules/scalc/ui/autosum.ui"_ustr)); std::unique_ptr xPopMenu(xBuilder->weld_menu(u"menu"_ustr)); MenuHdl(xPopMenu->popup_at_rect(pPopupParent, aRect)); } } IMPL_LINK_NOARG(ScInputBarGroup, ClickHdl, weld::Button&, void) { if (mxTextWndGroup->GetNumLines() > 1) mxTextWndGroup->SetNumLines(1); else mxTextWndGroup->SetNumLines(mxTextWndGroup->GetLastNumExpandedLines()); NumLinesChanged(); } void ScInputBarGroup::NumLinesChanged() { if (mxTextWndGroup->GetNumLines() > 1) { mxButtonDown->hide(); mxButtonUp->show(); mxTextWndGroup->SetLastNumExpandedLines(mxTextWndGroup->GetNumLines()); } else { mxButtonUp->hide(); mxButtonDown->show(); } TriggerToolboxLayout(); // Restore focus to input line(s) if necessary ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); if ( pHdl && pHdl->IsTopMode() ) mxTextWndGroup->TextGrabFocus(); } void ScInputBarGroup::TriggerToolboxLayout() { // layout changes are expensive and un-necessary. if (comphelper::LibreOfficeKit::isActive()) return; vcl::Window *w=GetParent(); ScInputWindow &rParent = dynamic_cast(*w); SfxViewFrame* pViewFrm = SfxViewFrame::Current(); if ( !pViewFrm ) return; Reference< css::beans::XPropertySet > xPropSet( pViewFrm->GetFrame().GetFrameInterface(), UNO_QUERY ); Reference< css::frame::XLayoutManager > xLayoutManager; if ( xPropSet.is() ) { css::uno::Any aValue = xPropSet->getPropertyValue(u"LayoutManager"_ustr); aValue >>= xLayoutManager; } if ( !xLayoutManager.is() ) return; xLayoutManager->lock(); DataChangedEvent aFakeUpdate( DataChangedEventType::SETTINGS, nullptr, AllSettingsFlags::STYLE ); // this basically will trigger the repositioning of the // items in the toolbar from ImplFormat ( which is controlled by // mnWinHeight ) which in turn is updated in ImplCalcItem which is // controlled by mbCalc. Additionally the ImplFormat above is // controlled via mbFormat. It seems the easiest way to get these // booleans set is to send in the fake event below. rParent.DataChanged( aFakeUpdate); // highest item in toolbar will have been calculated via the // event above. Call resize on InputBar to pick up the height // change rParent.Resize(); // unlock relayouts the toolbars in the 4 quadrants xLayoutManager->unlock(); } void ScInputBarGroup::TextGrabFocus() { mxTextWndGroup->TextGrabFocus(); } constexpr tools::Long gnBorderWidth = (INPUTLINE_INSET_MARGIN + 1) * 2; constexpr tools::Long gnBorderHeight = INPUTLINE_INSET_MARGIN + 1; ScTextWndGroup::ScTextWndGroup(ScInputBarGroup& rParent, ScTabViewShell* pViewSh) : mxTextWnd(new ScTextWnd(*this, pViewSh)) , mxScrollWin(rParent.GetBuilder().weld_scrolled_window(u"scrolledwindow"_ustr, true)) , mxTextWndWin(new weld::CustomWeld(rParent.GetBuilder(), u"sc_input_window"_ustr, *mxTextWnd)) , mrParent(rParent) { mxScrollWin->connect_vadjustment_changed(LINK(this, ScTextWndGroup, Impl_ScrollHdl)); if (ScTabViewShell* pActiveViewShell = comphelper::LibreOfficeKit::isActive() ? dynamic_cast(SfxViewShell::Current()) : nullptr) { pActiveViewShell->LOKSendFormulabarUpdate(nullptr, u""_ustr, ESelection()); } } Point ScTextWndGroup::GetCursorScreenPixelPos(bool bBelow) { Point aPos; if (!HasEditView()) return aPos; EditView* pEditView = GetEditView(); vcl::Cursor* pCur = pEditView->GetCursor(); if (!pCur) return aPos; Point aLogicPos = pCur->GetPos(); if (bBelow) aLogicPos.AdjustY(pCur->GetHeight()); aPos = GetEditViewDevice().LogicToPixel(aLogicPos); bool bRTL = mrParent.IsRTLEnabled(); if (bRTL) aPos.setX(mxTextWnd->GetOutputSizePixel().Width() - aPos.X() + gnBorderWidth); else aPos.AdjustX(gnBorderWidth + 1); return mrParent.OutputToScreenPixel(aPos); } ScTextWndGroup::~ScTextWndGroup() { } void ScTextWndGroup::InsertAccessibleTextData(ScAccessibleEditLineTextData& rTextData) { mxTextWnd->InsertAccessibleTextData(rTextData); } EditView* ScTextWndGroup::GetEditView() const { return mxTextWnd->GetEditView(); } const OutputDevice& ScTextWndGroup::GetEditViewDevice() const { return mxTextWnd->GetEditViewDevice(); } tools::Long ScTextWndGroup::GetLastNumExpandedLines() const { return mxTextWnd->GetLastNumExpandedLines(); } void ScTextWndGroup::SetLastNumExpandedLines(tools::Long nLastExpandedLines) { mxTextWnd->SetLastNumExpandedLines(nLastExpandedLines); } tools::Long ScTextWndGroup::GetNumLines() const { return mxTextWnd->GetNumLines(); } int ScTextWndGroup::GetPixelHeightForLines(tools::Long nLines) { return mxTextWnd->GetPixelHeightForLines(nLines) + 2 * gnBorderHeight; } weld::ScrolledWindow& ScTextWndGroup::GetScrollWin() { return *mxScrollWin; } const OUString& ScTextWndGroup::GetTextString() const { return mxTextWnd->GetTextString(); } bool ScTextWndGroup::HasEditView() const { return mxTextWnd->HasEditView(); } bool ScTextWndGroup::IsInputActive() { return mxTextWnd->IsInputActive(); } void ScTextWndGroup::MakeDialogEditView() { mxTextWnd->MakeDialogEditView(); } void ScTextWndGroup::RemoveAccessibleTextData(ScAccessibleEditLineTextData& rTextData) { mxTextWnd->RemoveAccessibleTextData(rTextData); } void ScTextWndGroup::SetScrollPolicy() { if (mxTextWnd->GetNumLines() > 2) mxScrollWin->set_vpolicy(VclPolicyType::ALWAYS); else mxScrollWin->set_vpolicy(VclPolicyType::NEVER); } void ScTextWndGroup::SetNumLines(tools::Long nLines) { mxTextWnd->SetNumLines(nLines); } void ScTextWndGroup::SetFormulaMode(bool bSet) { mxTextWnd->SetFormulaMode(bSet); } void ScTextWndGroup::SetTextString(const OUString& rString, bool bKitUpdate) { mxTextWnd->SetTextString(rString, bKitUpdate); } void ScTextWndGroup::StartEditEngine() { mxTextWnd->StartEditEngine(); } void ScTextWndGroup::StopEditEngine(bool bAll) { mxTextWnd->StopEditEngine( bAll ); } void ScTextWndGroup::TextGrabFocus() { mxTextWnd->TextGrabFocus(); } IMPL_LINK_NOARG(ScTextWndGroup, Impl_ScrollHdl, weld::ScrolledWindow&, void) { mxTextWnd->DoScroll(); } void ScTextWnd::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) { const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); Color aBgColor = rStyleSettings.GetWindowColor(); rRenderContext.SetBackground(aBgColor); // tdf#137713 we rely on GetEditView creating it if it doesn't already exist so // GetEditView() must be called unconditionally if (EditView* pView = GetEditView()) { if (mbInvalidate) { pView->Invalidate(); mbInvalidate = false; } } if (comphelper::LibreOfficeKit::isActive() && m_xEditEngine) { // EditEngine/EditView works in twips logical coordinates, so set the device map-mode to twips before painting // and use twips version of the painting area 'rRect'. // Document zoom should not be included in this conversion. tools::Rectangle aLogicRect = OutputDevice::LogicToLogic(rRect, MapMode(MapUnit::MapPixel), MapMode(MapUnit::MapTwip)); MapMode aOriginalMode = rRenderContext.GetMapMode(); rRenderContext.SetMapMode(MapMode(MapUnit::MapTwip)); WeldEditView::Paint(rRenderContext, aLogicRect); rRenderContext.SetMapMode(aOriginalMode); } else WeldEditView::Paint(rRenderContext, rRect); } EditView* ScTextWnd::GetEditView() const { if ( !m_xEditView ) const_cast(*this).InitEditEngine(); return m_xEditView.get(); } bool ScTextWnd::HasEditView() const { return m_xEditView != nullptr; } const OutputDevice& ScTextWnd::GetEditViewDevice() const { return EditViewOutputDevice(); } int ScTextWnd::GetPixelHeightForLines(tools::Long nLines) { OutputDevice& rDevice = GetDrawingArea()->get_ref_device(); return rDevice.LogicToPixel(Size(0, nLines * rDevice.GetTextHeight())).Height() + 1; } tools::Long ScTextWnd::GetNumLines() const { ScViewData& rViewData = mpViewShell->GetViewData(); return rViewData.GetFormulaBarLines(); } void ScTextWnd::SetNumLines(tools::Long nLines) { ScViewData& rViewData = mpViewShell->GetViewData(); rViewData.SetFormulaBarLines(nLines); if ( nLines > 1 ) { // SetFormulaBarLines sanitizes the height, so get the sanitized value mnLastExpandedLines = rViewData.GetFormulaBarLines(); Resize(); } } void ScTextWnd::Resize() { if (m_xEditView) { Size aOutputSize = GetOutputSizePixel(); OutputDevice& rDevice = GetDrawingArea()->get_ref_device(); tools::Rectangle aOutputArea = rDevice.PixelToLogic( tools::Rectangle( Point(), aOutputSize )); m_xEditView->SetOutputArea( aOutputArea ); // Don't leave an empty area at the bottom if we can move the text down. tools::Long nMaxVisAreaTop = m_xEditEngine->GetTextHeight() - aOutputArea.GetHeight(); if (m_xEditView->GetVisArea().Top() > nMaxVisAreaTop) { m_xEditView->Scroll(0, m_xEditView->GetVisArea().Top() - nMaxVisAreaTop); } m_xEditEngine->SetPaperSize( rDevice.PixelToLogic( Size( aOutputSize.Width(), 10000 ) ) ); } // skip WeldEditView's Resize(); weld::CustomWidgetController::Resize(); SetScrollBarRange(); } int ScTextWnd::GetEditEngTxtHeight() const { return m_xEditView ? m_xEditView->getEditEngine().GetTextHeight() : 0; } void ScTextWnd::SetScrollBarRange() { if (!m_xEditView) return; OutputDevice& rDevice = GetDrawingArea()->get_ref_device(); Size aOutputSize = rDevice.GetOutputSize(); int nUpper = GetEditEngTxtHeight(); int nCurrentDocPos = m_xEditView->GetVisArea().Top(); int nStepIncrement = GetTextHeight(); int nPageIncrement = aOutputSize.Height(); int nPageSize = aOutputSize.Height(); /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has effectively... lower = gtk_adjustment_get_lower upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size and requires that upper > lower or the deceleration animation never ends */ nPageSize = std::min(nPageSize, nUpper); weld::ScrolledWindow& rVBar = mrGroupBar.GetScrollWin(); rVBar.vadjustment_configure(nCurrentDocPos, 0, nUpper, nStepIncrement, nPageIncrement, nPageSize); } void ScTextWnd::DoScroll() { if (m_xEditView) { weld::ScrolledWindow& rVBar = mrGroupBar.GetScrollWin(); auto currentDocPos = m_xEditView->GetVisArea().Top(); auto nDiff = currentDocPos - rVBar.vadjustment_get_value(); // we expect SetScrollBarRange callback to be triggered by Scroll // to set where we ended up m_xEditView->Scroll(0, nDiff); } } void ScTextWnd::StartEditEngine() { // Don't activate if we're a modal dialog ourselves (Doc-modal dialog) SfxObjectShell* pObjSh = SfxObjectShell::Current(); if ( pObjSh && pObjSh->IsInModalMode() ) return; if ( !m_xEditView || !m_xEditEngine ) { InitEditEngine(); } ScInputHandler* pHdl = mpViewShell->GetInputHandler(); if (pHdl) pHdl->SetMode(SC_INPUT_TOP, nullptr, static_cast(m_xEditEngine.get())); SfxViewFrame* pViewFrm = SfxViewFrame::Current(); if (pViewFrm) pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT ); } static void lcl_ExtendEditFontAttribs( SfxItemSet& rSet ) { const SfxPoolItem& rFontItem = rSet.Get( EE_CHAR_FONTINFO ); std::unique_ptr pNewItem(rFontItem.Clone()); pNewItem->SetWhich(EE_CHAR_FONTINFO_CJK); rSet.Put( *pNewItem ); pNewItem->SetWhich(EE_CHAR_FONTINFO_CTL); rSet.Put( *pNewItem ); const SfxPoolItem& rHeightItem = rSet.Get( EE_CHAR_FONTHEIGHT ); pNewItem.reset(rHeightItem.Clone()); pNewItem->SetWhich(EE_CHAR_FONTHEIGHT_CJK); rSet.Put( *pNewItem ); pNewItem->SetWhich(EE_CHAR_FONTHEIGHT_CTL); rSet.Put( *pNewItem ); const SfxPoolItem& rWeightItem = rSet.Get( EE_CHAR_WEIGHT ); pNewItem.reset(rWeightItem.Clone()); pNewItem->SetWhich(EE_CHAR_WEIGHT_CJK); rSet.Put( *pNewItem ); pNewItem->SetWhich(EE_CHAR_WEIGHT_CTL); rSet.Put( *pNewItem ); const SfxPoolItem& rItalicItem = rSet.Get( EE_CHAR_ITALIC ); pNewItem.reset(rItalicItem.Clone()); pNewItem->SetWhich(EE_CHAR_ITALIC_CJK); rSet.Put( *pNewItem ); pNewItem->SetWhich(EE_CHAR_ITALIC_CTL); rSet.Put( *pNewItem ); const SfxPoolItem& rLangItem = rSet.Get( EE_CHAR_LANGUAGE ); pNewItem.reset(rLangItem.Clone()); pNewItem->SetWhich(EE_CHAR_LANGUAGE_CJK); rSet.Put( *pNewItem ); pNewItem->SetWhich(EE_CHAR_LANGUAGE_CTL); rSet.Put( *pNewItem ); } static void lcl_ModifyRTLDefaults( SfxItemSet& rSet ) { rSet.Put( SvxAdjustItem( SvxAdjust::Right, EE_PARA_JUST ) ); // always using rtl writing direction would break formulas //rSet.Put( SvxFrameDirectionItem( SvxFrameDirection::Horizontal_RL_TB, EE_PARA_WRITINGDIR ) ); // PaperSize width is limited to USHRT_MAX in RTL mode (because of EditEngine's // sal_uInt16 values in EditLine), so the text may be wrapped and line spacing must be // increased to not see the beginning of the next line. SvxLineSpacingItem aItem( LINE_SPACE_DEFAULT_HEIGHT, EE_PARA_SBL ); aItem.SetPropLineSpace( 200 ); rSet.Put( aItem ); } static void lcl_ModifyRTLVisArea( EditView* pEditView ) { tools::Rectangle aVisArea = pEditView->GetVisArea(); Size aPaper = pEditView->getEditEngine().GetPaperSize(); tools::Long nDiff = aPaper.Width() - aVisArea.Right(); aVisArea.AdjustLeft(nDiff ); aVisArea.AdjustRight(nDiff ); pEditView->SetVisArea(aVisArea); } void ScTextWnd::InitEditEngine() { std::unique_ptr pNew; ScDocShell* pDocSh = nullptr; if ( mpViewShell ) { pDocSh = mpViewShell->GetViewData().GetDocShell(); ScDocument& rDoc = mpViewShell->GetViewData().GetDocument(); pNew = std::make_unique(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool()); } else pNew = std::make_unique(nullptr, EditEngine::CreatePool().get(), nullptr, true); pNew->SetExecuteURL( false ); m_xEditEngine = std::move(pNew); Size barSize = GetOutputSizePixel(); m_xEditEngine->SetUpdateLayout( false ); m_xEditEngine->SetPaperSize( GetDrawingArea()->get_ref_device().PixelToLogic(Size(barSize.Width(),10000)) ); m_xEditEngine->SetWordDelimiters( ScEditUtil::ModifyDelimiters( m_xEditEngine->GetWordDelimiters() ) ); m_xEditEngine->SetReplaceLeadingSingleQuotationMark( false ); UpdateAutoCorrFlag(); { auto pSet = std::make_unique( m_xEditEngine->GetEmptyItemSet() ); EditEngine::SetFontInfoInItemSet( *pSet, aTextFont ); lcl_ExtendEditFontAttribs( *pSet ); // turn off script spacing to match DrawText output pSet->Put( SvxScriptSpaceItem( false, EE_PARA_ASIANCJKSPACING ) ); if ( bIsRTL ) lcl_ModifyRTLDefaults( *pSet ); static_cast(m_xEditEngine.get())->SetDefaults( std::move(pSet) ); } // If the Cell contains URLFields, they need to be taken over into the entry row, // or else the position is not correct anymore bool bFilled = false; ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); if ( pHdl ) //! Test if it's the right InputHdl? bFilled = pHdl->GetTextAndFields(static_cast(*m_xEditEngine)); m_xEditEngine->SetUpdateLayout( true ); // aString is the truth ... if (bFilled && m_xEditEngine->GetText() == aString) Invalidate(); // Repaint for (filled) Field else static_cast(m_xEditEngine.get())->SetTextCurrentDefaults(aString); // At least the right text then m_xEditView = std::make_unique(m_xEditEngine.get(), nullptr); // we get cursor, selection etc. messages from the VCL/window layer // otherwise these are injected into the document causing confusion. m_xEditView->SuppressLOKMessages(true); m_xEditView->setEditViewCallbacks(this); m_xEditView->SetInsertMode(bIsInsertMode); const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); Color aBgColor = rStyleSettings.GetWindowColor(); m_xEditView->SetBackgroundColor(aBgColor); if (pAcc) { pAcc->InitAcc(nullptr, m_xEditView.get(), ScResId(STR_ACC_EDITLINE_NAME), ScResId(STR_ACC_EDITLINE_DESCR)); } if (comphelper::LibreOfficeKit::isActive()) m_xEditView->RegisterViewShell(mpViewShell); // Text from Clipboard is taken over as ASCII in a single row EVControlBits n = m_xEditView->GetControlWord(); m_xEditView->SetControlWord( n | EVControlBits::SINGLELINEPASTE ); m_xEditEngine->InsertView( m_xEditView.get(), EE_APPEND ); Resize(); if ( bIsRTL ) lcl_ModifyRTLVisArea( m_xEditView.get() ); m_xEditEngine->SetModifyHdl(LINK(this, ScTextWnd, ModifyHdl)); m_xEditEngine->SetStatusEventHdl(LINK(this, ScTextWnd, EditStatusHdl)); if (!maAccTextDatas.empty()) maAccTextDatas.back()->StartEdit(); // as long as EditEngine and DrawText sometimes differ for CTL text, // repaint now to have the EditEngine's version visible if (pDocSh) { ScDocument& rDoc = pDocSh->GetDocument(); // any document SvtScriptType nScript = rDoc.GetStringScriptType( aString ); if ( nScript & SvtScriptType::COMPLEX ) Invalidate(); } } ScTextWnd::ScTextWnd(ScTextWndGroup& rParent, ScTabViewShell* pViewSh) : bIsRTL(AllSettings::GetLayoutRTL()), bIsInsertMode(true), bFormulaMode (false), bInputMode (false), mpViewShell(pViewSh), mrGroupBar(rParent), mnLastExpandedLines(INPUTWIN_MULTILINES), mbInvalidate(false) { } ScTextWnd::~ScTextWnd() { while (!maAccTextDatas.empty()) { maAccTextDatas.back()->Dispose(); } } bool ScTextWnd::MouseMove( const MouseEvent& rMEvt ) { return m_xEditView && m_xEditView->MouseMove(rMEvt); } bool ScTextWnd::CanFocus() const { return SC_MOD()->IsEditMode(); } void ScTextWnd::UpdateFocus() { if (!HasFocus()) { StartEditEngine(); if (CanFocus()) TextGrabFocus(); } } bool ScTextWnd::MouseButtonDown( const MouseEvent& rMEvt ) { UpdateFocus(); bool bClickOnSelection = false; if (m_xEditView) { m_xEditView->SetEditEngineUpdateLayout( true ); bClickOnSelection = m_xEditView->IsSelectionAtPoint(rMEvt.GetPosPixel()); } if (!bClickOnSelection) { rtl::Reference xTransferable(new TransferDataContainer); GetDrawingArea()->enable_drag_source(xTransferable, DND_ACTION_NONE); } else { rtl::Reference xTransferable(m_xHelper); GetDrawingArea()->enable_drag_source(xTransferable, DND_ACTION_COPY); } return WeldEditView::MouseButtonDown(rMEvt); } bool ScTextWnd::MouseButtonUp( const MouseEvent& rMEvt ) { bool bRet = WeldEditView::MouseButtonUp(rMEvt); if (bRet) { if ( rMEvt.IsMiddle() && Application::GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) { // EditView may have pasted from selection SC_MOD()->InputChanged( m_xEditView.get() ); } else SC_MOD()->InputSelection( m_xEditView.get() ); } return bRet; } bool ScTextWnd::Command( const CommandEvent& rCEvt ) { bool bConsumed = false; bInputMode = true; CommandEventId nCommand = rCEvt.GetCommand(); if (m_xEditView) { ScModule* pScMod = SC_MOD(); ScTabViewShell* pStartViewSh = ScTabViewShell::GetActiveViewShell(); // don't modify the font defaults here - the right defaults are // already set in StartEditEngine when the EditEngine is created // Prevent that the EditView is lost when switching between Views pScMod->SetInEditCommand( true ); m_xEditView->Command( rCEvt ); pScMod->SetInEditCommand( false ); // CommandEventId::StartDrag does not mean by far that the content was actually changed, // so don't trigger an InputChanged. //! Detect if dragged with Move or forbid Drag&Move somehow if ( nCommand == CommandEventId::StartDrag ) { // Is dragged onto another View? ScTabViewShell* pEndViewSh = ScTabViewShell::GetActiveViewShell(); if ( pEndViewSh != pStartViewSh && pStartViewSh != nullptr ) { ScViewData& rViewData = pStartViewSh->GetViewData(); ScInputHandler* pHdl = pScMod->GetInputHdl( pStartViewSh ); if ( pHdl && rViewData.HasEditView( rViewData.GetActivePart() ) ) { pHdl->CancelHandler(); rViewData.GetView()->ShowCursor(); // Missing for KillEditView, due to being inactive } } } else if ( nCommand == CommandEventId::EndExtTextInput ) { if (bFormulaMode) { ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); if (pHdl) pHdl->InputCommand(rCEvt); } SC_MOD()->InputChanged( m_xEditView.get() ); } else if ( nCommand == CommandEventId::CursorPos ) { // don't call InputChanged for CommandEventId::CursorPos } else if ( nCommand == CommandEventId::InputLanguageChange ) { // #i55929# Font and font size state depends on input language if nothing is selected, // so the slots have to be invalidated when the input language is changed. SfxViewFrame* pViewFrm = SfxViewFrame::Current(); if (pViewFrm) { SfxBindings& rBindings = pViewFrm->GetBindings(); rBindings.Invalidate( SID_ATTR_CHAR_FONT ); rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); } } else if ( nCommand == CommandEventId::ContextMenu ) { bConsumed = true; SfxViewFrame* pViewFrm = SfxViewFrame::Current(); if (pViewFrm) { Point aPos = rCEvt.GetMousePosPixel(); if (!rCEvt.IsMouseEvent()) { Size aSize = GetOutputSizePixel(); aPos = Point(aSize.Width() / 2, aSize.Height() / 2); } if (IsMouseCaptured()) ReleaseMouse(); UpdateFocus(); pViewFrm->GetDispatcher()->ExecutePopup(u"formulabar"_ustr, &mrGroupBar.GetVclParent(), &aPos); } } else if ( nCommand == CommandEventId::Wheel ) { //don't call InputChanged for CommandEventId::Wheel } else if ( nCommand == CommandEventId::GestureSwipe ) { //don't call InputChanged for CommandEventId::GestureSwipe } else if ( nCommand == CommandEventId::GestureLongPress ) { //don't call InputChanged for CommandEventId::GestureLongPress } else if ( nCommand == CommandEventId::ModKeyChange ) { //pass alt press/release to parent impl } else SC_MOD()->InputChanged( m_xEditView.get() ); } if ( comphelper::LibreOfficeKit::isActive() && nCommand == CommandEventId::CursorPos ) { // LOK uses this to setup caret position because drawingarea is replaced // with text input field, it sends logical caret position (start, end) not pixels StartEditEngine(); TextGrabFocus(); if (!m_xEditView) return true; // information about paragraph is in additional data // information about position in a paragraph in a Mouse Pos // see vcl/jsdialog/executor.cxx "textselection" event const Point* pParaPoint = static_cast(rCEvt.GetEventData()); Point aSelectionStartEnd = rCEvt.GetMousePosPixel(); sal_Int32 nParaStart, nParaEnd, nPosStart, nPosEnd; nParaStart = pParaPoint ? pParaPoint->X() : 0; nParaEnd = pParaPoint ? pParaPoint->Y() : 0; nPosStart = m_xEditView->GetPosNoField(nParaStart, aSelectionStartEnd.X()); nPosEnd = m_xEditView->GetPosNoField(nParaEnd, aSelectionStartEnd.Y()); m_xEditView->SetSelection(ESelection(nParaStart, nPosStart, nParaEnd, nPosEnd)); SC_MOD()->InputSelection( m_xEditView.get() ); bConsumed = true; } bInputMode = false; return bConsumed; } bool ScTextWnd::StartDrag() { // tdf#145248 don't start a drag if actively selecting if (m_xEditView && !m_xEditEngine->IsInSelectionMode()) { OUString sSelection = m_xEditView->GetSelected(); m_xHelper->SetData(sSelection); return sSelection.isEmpty(); } return true; } bool ScTextWnd::KeyInput(const KeyEvent& rKEvt) { bool bUsed = true; bInputMode = true; if (!SC_MOD()->InputKeyEvent( rKEvt )) { bUsed = false; ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); if ( pViewSh ) bUsed = pViewSh->SfxKeyInput(rKEvt); // Only accelerators, no input } bInputMode = false; return bUsed; } void ScTextWnd::GetFocus() { ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); if ( pViewSh ) pViewSh->SetFormShellAtTop( false ); // focus in input line -> FormShell no longer on top WeldEditView::GetFocus(); } void ScTextWnd::SetFormulaMode( bool bSet ) { if ( bSet != bFormulaMode ) { bFormulaMode = bSet; UpdateAutoCorrFlag(); } } void ScTextWnd::UpdateAutoCorrFlag() { if (m_xEditEngine) { EEControlBits nControl = m_xEditEngine->GetControlWord(); EEControlBits nOld = nControl; if ( bFormulaMode ) nControl &= ~EEControlBits::AUTOCORRECT; // No AutoCorrect in Formulas else nControl |= EEControlBits::AUTOCORRECT; // Else do enable it if ( nControl != nOld ) m_xEditEngine->SetControlWord( nControl ); } } void ScTextWnd::EditViewScrollStateChange() { // editengine height has changed or editview scroll pos has changed SetScrollBarRange(); } IMPL_LINK_NOARG(ScTextWnd, ModifyHdl, LinkParamNone*, void) { if (m_xEditView && !bInputMode) { ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); // Use the InputHandler's InOwnChange flag to prevent calling InputChanged // while an InputHandler method is modifying the EditEngine content if ( pHdl && !pHdl->IsInOwnChange() ) pHdl->InputChanged( m_xEditView.get(), true ); // #i20282# InputChanged must know if called from modify handler } } IMPL_LINK_NOARG(ScTextWnd, EditStatusHdl, EditStatus&, void) { SetScrollBarRange(); DoScroll(); Invalidate(); } void ScTextWnd::StopEditEngine( bool bAll ) { if (!m_xEditEngine) return; if (m_xEditView) { if (!maAccTextDatas.empty()) maAccTextDatas.back()->EndEdit(); ScModule* pScMod = SC_MOD(); if (!bAll) pScMod->InputSelection( m_xEditView.get() ); aString = m_xEditEngine->GetText(); bIsInsertMode = m_xEditView->IsInsertMode(); bool bSelection = m_xEditView->HasSelection(); m_xEditEngine->SetStatusEventHdl(Link()); m_xEditEngine->SetModifyHdl(Link()); m_xEditView.reset(); m_xEditEngine.reset(); ScInputHandler* pHdl = mpViewShell->GetInputHandler(); if (pHdl && pHdl->IsEditMode() && !bAll) pHdl->SetMode(SC_INPUT_TABLE); SfxViewFrame* pViewFrm = SfxViewFrame::Current(); if (pViewFrm) pViewFrm->GetBindings().Invalidate( SID_ATTR_INSERT ); if (bSelection) Invalidate(); // So that the Selection is not left there } if (comphelper::LibreOfficeKit::isActive()) { // Clear std::vector aReferenceMarks; ScInputHandler::SendReferenceMarks( mpViewShell, aReferenceMarks ); } } static sal_Int32 findFirstNonMatchingChar(const OUString& rStr1, const OUString& rStr2) { // Search the string for unmatching chars const sal_Unicode* pStr1 = rStr1.getStr(); const sal_Unicode* pStr2 = rStr2.getStr(); sal_Int32 i = 0; while ( i < rStr1.getLength() ) { // Abort on the first unmatching char if ( *pStr1 != *pStr2 ) return i; ++pStr1; ++pStr2; ++i; } return i; } void ScTextWnd::SetTextString( const OUString& rNewString, bool bKitUpdate ) { // Ideally it would be best to create on demand the EditEngine/EditView here, but... for // the initialisation scenario where a cell is first clicked on we end up with the text in the // inputbar window scrolled to the bottom if we do that here ( because the tableview and topview // are synced I guess ). // should fix that I suppose :-/ need to look a bit further into that mbInvalidate = true; // ensure next Paint ( that uses editengine ) call will call Invalidate first if ( rNewString != aString ) { bInputMode = true; // Find position of the change, only paint the rest if (!m_xEditEngine) { bool bPaintAll = GetNumLines() > 1 || bIsRTL; if (!bPaintAll) { // test if CTL script type is involved SvtScriptType nOldScript = SvtScriptType::NONE; SvtScriptType nNewScript = SvtScriptType::NONE; SfxObjectShell* pObjSh = SfxObjectShell::Current(); if ( auto pDocShell = dynamic_cast( pObjSh) ) { // any document can be used (used only for its break iterator) ScDocument& rDoc = pDocShell->GetDocument(); nOldScript = rDoc.GetStringScriptType( aString ); nNewScript = rDoc.GetStringScriptType( rNewString ); } bPaintAll = ( nOldScript & SvtScriptType::COMPLEX ) || ( nNewScript & SvtScriptType::COMPLEX ); } if ( bPaintAll ) { // In multiline mode, or if CTL is involved, the whole text has to be redrawn Invalidate(); } else { tools::Long nTextSize = 0; sal_Int32 nDifPos; if (rNewString.getLength() > aString.getLength()) nDifPos = findFirstNonMatchingChar(rNewString, aString); else nDifPos = findFirstNonMatchingChar(aString, rNewString); tools::Long nSize1 = GetTextWidth(aString); tools::Long nSize2 = GetTextWidth(rNewString); if ( nSize1>0 && nSize2>0 ) nTextSize = std::max( nSize1, nSize2 ); else nTextSize = GetOutputSizePixel().Width(); // Overflow Point aLogicStart = GetDrawingArea()->get_ref_device().PixelToLogic(Point(0,0)); tools::Long nStartPos = aLogicStart.X(); tools::Long nInvPos = nStartPos; if (nDifPos) nInvPos += GetTextWidth(aString.copy(0,nDifPos)); Invalidate(tools::Rectangle(nInvPos, 0, nStartPos+nTextSize, GetOutputSizePixel().Height() - 1)); } } else { static_cast(m_xEditEngine.get())->SetTextCurrentDefaults(rNewString); } aString = rNewString; if (!maAccTextDatas.empty()) maAccTextDatas.back()->TextChanged(); bInputMode = false; } if (ScTabViewShell* pActiveViewShell = bKitUpdate && comphelper::LibreOfficeKit::isActive() ? dynamic_cast(SfxViewShell::Current()) : nullptr) { ESelection aSel = m_xEditView ? m_xEditView->GetSelection() : ESelection(); pActiveViewShell->LOKSendFormulabarUpdate(m_xEditView.get(), rNewString, aSel); } SetScrollBarRange(); DoScroll(); } const OUString& ScTextWnd::GetTextString() const { return aString; } bool ScTextWnd::IsInputActive() { return HasFocus(); } void ScTextWnd::MakeDialogEditView() { if ( m_xEditView ) return; std::unique_ptr pNew; ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); if ( pViewSh ) { ScDocument& rDoc = pViewSh->GetViewData().GetDocument(); pNew = std::make_unique(&rDoc, rDoc.GetEnginePool(), rDoc.GetEditPool()); } else pNew = std::make_unique(nullptr, EditEngine::CreatePool().get(), nullptr, true); pNew->SetExecuteURL( false ); m_xEditEngine = std::move(pNew); const bool bPrevUpdateLayout = m_xEditEngine->SetUpdateLayout( false ); m_xEditEngine->SetWordDelimiters( m_xEditEngine->GetWordDelimiters() + "=" ); m_xEditEngine->SetPaperSize( Size( bIsRTL ? USHRT_MAX : THESIZE, 300 ) ); auto pSet = std::make_unique( m_xEditEngine->GetEmptyItemSet() ); EditEngine::SetFontInfoInItemSet( *pSet, aTextFont ); lcl_ExtendEditFontAttribs( *pSet ); if ( bIsRTL ) lcl_ModifyRTLDefaults( *pSet ); static_cast(m_xEditEngine.get())->SetDefaults( std::move(pSet) ); m_xEditEngine->SetUpdateLayout( bPrevUpdateLayout ); m_xEditView = std::make_unique(m_xEditEngine.get(), nullptr); m_xEditView->setEditViewCallbacks(this); const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); Color aBgColor = rStyleSettings.GetWindowColor(); m_xEditView->SetBackgroundColor(aBgColor); if (pAcc) { pAcc->InitAcc(nullptr, m_xEditView.get(), ScResId(STR_ACC_EDITLINE_NAME), ScResId(STR_ACC_EDITLINE_DESCR)); } if (comphelper::LibreOfficeKit::isActive()) m_xEditView->RegisterViewShell(mpViewShell); m_xEditEngine->InsertView( m_xEditView.get(), EE_APPEND ); Resize(); if ( bIsRTL ) lcl_ModifyRTLVisArea( m_xEditView.get() ); if (!maAccTextDatas.empty()) maAccTextDatas.back()->StartEdit(); } void ScTextWnd::ImplInitSettings() { bIsRTL = AllSettings::GetLayoutRTL(); const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); Color aBgColor= rStyleSettings.GetWindowColor(); Color aTxtColor= rStyleSettings.GetWindowTextColor(); aTextFont.SetFillColor ( aBgColor ); aTextFont.SetColor (aTxtColor); Invalidate(); } void ScTextWnd::SetDrawingArea(weld::DrawingArea* pDrawingArea) { // bypass WeldEditView::SetDrawingArea weld::CustomWidgetController::SetDrawingArea(pDrawingArea); // set cursor pDrawingArea->set_cursor(PointerStyle::Text); // initialize dnd, deliberately just a simple string so // we don't transfer the happenstance formatting in // the input line m_xHelper.set(new svt::OStringTransferable(OUString())); rtl::Reference xHelper(m_xHelper); SetDragDataTransferable(xHelper, DND_ACTION_COPY); OutputDevice& rDevice = pDrawingArea->get_ref_device(); pDrawingArea->set_margin_start(gnBorderWidth); pDrawingArea->set_margin_end(gnBorderWidth); // leave 1 for the width of the scrolledwindow border pDrawingArea->set_margin_top(gnBorderHeight - 1); pDrawingArea->set_margin_bottom(gnBorderHeight - 1); // always use application font, so a font with cjk chars can be installed vcl::Font aAppFont = Application::GetSettings().GetStyleSettings().GetAppFont(); weld::SetPointFont(rDevice, aAppFont); aTextFont = rDevice.GetFont(); Size aFontSize = aTextFont.GetFontSize(); aTextFont.SetFontSize(rDevice.PixelToLogic(aFontSize, MapMode(MapUnit::MapTwip))); const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); Color aBgColor = rStyleSettings.GetWindowColor(); Color aTxtColor = rStyleSettings.GetWindowTextColor(); aTextFont.SetTransparent(true); aTextFont.SetFillColor(aBgColor); aTextFont.SetColor(aTxtColor); aTextFont.SetWeight(WEIGHT_NORMAL); Size aSize(1, GetPixelHeightForLines(1)); pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); rDevice.SetBackground(aBgColor); rDevice.SetLineColor(COL_BLACK); rDevice.SetMapMode(MapMode(MapUnit::MapTwip)); rDevice.SetFont(aTextFont); EnableRTL(false); // EditEngine can't be used with VCL EnableRTL } css::uno::Reference< css::accessibility::XAccessible > ScTextWnd::CreateAccessible() { pAcc = new ScAccessibleEditLineObject(this); return pAcc; } void ScTextWnd::InsertAccessibleTextData( ScAccessibleEditLineTextData& rTextData ) { OSL_ENSURE( ::std::find( maAccTextDatas.begin(), maAccTextDatas.end(), &rTextData ) == maAccTextDatas.end(), "ScTextWnd::InsertAccessibleTextData - passed object already registered" ); maAccTextDatas.push_back( &rTextData ); } void ScTextWnd::RemoveAccessibleTextData( ScAccessibleEditLineTextData& rTextData ) { AccTextDataVector::iterator aEnd = maAccTextDatas.end(); AccTextDataVector::iterator aIt = ::std::find( maAccTextDatas.begin(), aEnd, &rTextData ); OSL_ENSURE( aIt != aEnd, "ScTextWnd::RemoveAccessibleTextData - passed object not registered" ); if( aIt != aEnd ) maAccTextDatas.erase( aIt ); } void ScTextWnd::StyleUpdated() { ImplInitSettings(); CustomWidgetController::Invalidate(); } void ScTextWnd::TextGrabFocus() { GrabFocus(); } // Position window ScPosWnd::ScPosWnd(vcl::Window* pParent) : InterimItemWindow(pParent, u"modules/scalc/ui/posbox.ui"_ustr, u"PosBox"_ustr) , m_xWidget(m_xBuilder->weld_combo_box(u"pos_window"_ustr)) , m_nAsyncGetFocusId(nullptr) , nTipVisible(nullptr) , bFormulaMode(false) { InitControlBase(m_xWidget.get()); // Use calculation according to tdf#132338 to align combobox width to width of fontname combobox within formatting toolbar; // formatting toolbar is placed above formulabar when using multiple toolbars typically m_xWidget->set_entry_width_chars(1); Size aSize(LogicToPixel(Size(POSITION_COMBOBOX_WIDTH * 4, 0), MapMode(MapUnit::MapAppFont))); m_xWidget->set_size_request(aSize.Width(), -1); SetSizePixel(m_xContainer->get_preferred_size()); FillRangeNames(); StartListening( *SfxGetpApp() ); // For Navigator rangename updates m_xWidget->connect_key_press(LINK(this, ScPosWnd, KeyInputHdl)); m_xWidget->connect_entry_activate(LINK(this, ScPosWnd, ActivateHdl)); m_xWidget->connect_changed(LINK(this, ScPosWnd, ModifyHdl)); m_xWidget->connect_focus_in(LINK(this, ScPosWnd, FocusInHdl)); m_xWidget->connect_focus_out(LINK(this, ScPosWnd, FocusOutHdl)); } ScPosWnd::~ScPosWnd() { disposeOnce(); } void ScPosWnd::dispose() { EndListening( *SfxGetpApp() ); HideTip(); if (m_nAsyncGetFocusId) { Application::RemoveUserEvent(m_nAsyncGetFocusId); m_nAsyncGetFocusId = nullptr; } m_xWidget.reset(); InterimItemWindow::dispose(); } void ScPosWnd::SetFormulaMode( bool bSet ) { if ( bSet != bFormulaMode ) { bFormulaMode = bSet; if ( bSet ) FillFunctions(); else FillRangeNames(); HideTip(); } } void ScPosWnd::SetPos( const OUString& rPosStr ) { if ( aPosStr != rPosStr ) { aPosStr = rPosStr; m_xWidget->set_entry_text(aPosStr); } } // static OUString ScPosWnd::createLocalRangeName(std::u16string_view rName, std::u16string_view rTableName) { return OUString::Concat(rName) + " (" + rTableName + ")"; } void ScPosWnd::FillRangeNames() { m_xWidget->clear(); m_xWidget->freeze(); SfxObjectShell* pObjSh = SfxObjectShell::Current(); if ( auto pDocShell = dynamic_cast( pObjSh) ) { ScDocument& rDoc = pDocShell->GetDocument(); m_xWidget->append_text(ScResId(STR_MANAGE_NAMES)); m_xWidget->append_separator(u"separator"_ustr); ScRange aDummy; std::set aSet; ScRangeName* pRangeNames = rDoc.GetRangeName(); for (const auto& rEntry : *pRangeNames) { if (rEntry.second->IsValidReference(aDummy)) aSet.insert(rEntry.second->GetName()); } for (SCTAB i = 0; i < rDoc.GetTableCount(); ++i) { ScRangeName* pLocalRangeName = rDoc.GetRangeName(i); if (pLocalRangeName && !pLocalRangeName->empty()) { OUString aTableName; rDoc.GetName(i, aTableName); for (const auto& rEntry : *pLocalRangeName) { if (rEntry.second->IsValidReference(aDummy)) aSet.insert(createLocalRangeName(rEntry.second->GetName(), aTableName)); } } } for (const auto& rItem : aSet) { m_xWidget->append_text(rItem); } } m_xWidget->thaw(); m_xWidget->set_entry_text(aPosStr); } void ScPosWnd::FillFunctions() { m_xWidget->clear(); m_xWidget->freeze(); OUString aFirstName; const ScAppOptions& rOpt = SC_MOD()->GetAppOptions(); sal_uInt16 nMRUCount = rOpt.GetLRUFuncListCount(); const sal_uInt16* pMRUList = rOpt.GetLRUFuncList(); if (pMRUList) { const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList(); sal_uInt32 nListCount = pFuncList->GetCount(); for (sal_uInt16 i=0; iGetFunction( j ); if ( pDesc->nFIndex == nId && pDesc->mxFuncName ) { m_xWidget->append_text(*pDesc->mxFuncName); if (aFirstName.isEmpty()) aFirstName = *pDesc->mxFuncName; break; // Stop searching } } } } //! Re-add entry "Other..." for Function AutoPilot if it can work with text that // has been entered so far // m_xWidget->append_text(ScResId(STR_FUNCTIONLIST_MORE)); m_xWidget->thaw(); m_xWidget->set_entry_text(aFirstName); } void ScPosWnd::Notify( SfxBroadcaster&, const SfxHint& rHint ) { if ( bFormulaMode ) return; const SfxHintId nHintId = rHint.GetId(); // Does the list of range names need updating? if (nHintId == SfxHintId::ThisIsAnSfxEventHint) { SfxEventHintId nEventId = static_cast(rHint).GetEventId(); if ( nEventId == SfxEventHintId::ActivateDoc ) FillRangeNames(); } else { if (nHintId == SfxHintId::ScAreasChanged || nHintId == SfxHintId::ScNavigatorUpdateAll) FillRangeNames(); } } void ScPosWnd::HideTip() { if (nTipVisible) { Help::HidePopover(this, nTipVisible); nTipVisible = nullptr; } } static ScNameInputType lcl_GetInputType( const OUString& rText ) { ScNameInputType eRet = SC_NAME_INPUT_BAD_NAME; // the more general error ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); if ( pViewSh ) { ScViewData& rViewData = pViewSh->GetViewData(); ScDocument& rDoc = rViewData.GetDocument(); SCTAB nTab = rViewData.GetTabNo(); ScAddress::Details aDetails( rDoc.GetAddressConvention()); // test in same order as in SID_CURRENTCELL execute ScRange aRange; ScAddress aAddress; SCTAB nNameTab; sal_Int32 nNumeric; // From the context we know that when testing for a range name // sheet-local scope names have " (sheetname)" appended and global // names don't and can't contain ')', so we can force one or the other. const RutlNameScope eNameScope = ((!rText.isEmpty() && rText[rText.getLength()-1] == ')') ? RUTL_NAMES_LOCAL : RUTL_NAMES_GLOBAL); if (rText == ScResId(STR_MANAGE_NAMES)) eRet = SC_MANAGE_NAMES; else if ( aRange.Parse( rText, rDoc, aDetails ) & ScRefFlags::VALID ) eRet = SC_NAME_INPUT_RANGE; else if ( aAddress.Parse( rText, rDoc, aDetails ) & ScRefFlags::VALID ) eRet = SC_NAME_INPUT_CELL; else if ( ScRangeUtil::MakeRangeFromName( rText, rDoc, nTab, aRange, eNameScope, aDetails ) ) { eRet = ((eNameScope == RUTL_NAMES_LOCAL) ? SC_NAME_INPUT_NAMEDRANGE_LOCAL : SC_NAME_INPUT_NAMEDRANGE_GLOBAL); } else if ( ScRangeUtil::MakeRangeFromName( rText, rDoc, nTab, aRange, RUTL_DBASE, aDetails ) ) eRet = SC_NAME_INPUT_DATABASE; else if ( comphelper::string::isdigitAsciiString( rText ) && ( nNumeric = rText.toInt32() ) > 0 && nNumeric <= rDoc.MaxRow()+1 ) eRet = SC_NAME_INPUT_ROW; else if ( rDoc.GetTable( rText, nNameTab ) ) eRet = SC_NAME_INPUT_SHEET; else if (ScRangeData::IsNameValid(rText, rDoc) == ScRangeData::IsNameValidType::NAME_VALID) // nothing found, create new range? { if ( rViewData.GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) eRet = SC_NAME_INPUT_DEFINE; else eRet = SC_NAME_INPUT_BAD_SELECTION; } else eRet = SC_NAME_INPUT_BAD_NAME; } return eRet; } IMPL_LINK_NOARG(ScPosWnd, ModifyHdl, weld::ComboBox&, void) { HideTip(); if (m_xWidget->changed_by_direct_pick()) { DoEnter(); return; } if (bFormulaMode) return; // determine the action that would be taken for the current input ScNameInputType eType = lcl_GetInputType(m_xWidget->get_active_text()); // uses current view TranslateId pStrId; switch ( eType ) { case SC_NAME_INPUT_CELL: pStrId = STR_NAME_INPUT_CELL; break; case SC_NAME_INPUT_RANGE: case SC_NAME_INPUT_NAMEDRANGE_LOCAL: case SC_NAME_INPUT_NAMEDRANGE_GLOBAL: pStrId = STR_NAME_INPUT_RANGE; // named range or range reference break; case SC_NAME_INPUT_DATABASE: pStrId = STR_NAME_INPUT_DBRANGE; break; case SC_NAME_INPUT_ROW: pStrId = STR_NAME_INPUT_ROW; break; case SC_NAME_INPUT_SHEET: pStrId = STR_NAME_INPUT_SHEET; break; case SC_NAME_INPUT_DEFINE: pStrId = STR_NAME_INPUT_DEFINE; break; default: // other cases (error): no tip help break; } if (!pStrId) return; // show the help tip at the text cursor position Point aPos; vcl::Cursor* pCur = GetCursor(); if (pCur) aPos = LogicToPixel( pCur->GetPos() ); aPos = OutputToScreenPixel( aPos ); tools::Rectangle aRect( aPos, aPos ); OUString aText = ScResId(pStrId); QuickHelpFlags const nAlign = QuickHelpFlags::Left|QuickHelpFlags::Bottom; nTipVisible = Help::ShowPopover(this, aRect, aText, nAlign); } void ScPosWnd::DoEnter() { bool bOpenManageNamesDialog = false; OUString aText = m_xWidget->get_active_text(); if ( !aText.isEmpty() ) { if ( bFormulaMode ) { ScModule* pScMod = SC_MOD(); if ( aText == ScResId(STR_FUNCTIONLIST_MORE) ) { // Function AutoPilot //! Continue working with the text entered so far //! new method at ScModule to query if function autopilot is open SfxViewFrame* pViewFrm = SfxViewFrame::Current(); if ( pViewFrm && !pViewFrm->GetChildWindow( SID_OPENDLG_FUNCTION ) ) pViewFrm->GetDispatcher()->Execute( SID_OPENDLG_FUNCTION, SfxCallMode::SYNCHRON | SfxCallMode::RECORD ); } else { ScTabViewShell* pViewSh = dynamic_cast( SfxViewShell::Current() ); ScInputHandler* pHdl = pScMod->GetInputHdl( pViewSh ); if (pHdl) pHdl->InsertFunction( aText ); } } else { // depending on the input, select something or create a new named range ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); if ( pViewSh ) { ScViewData& rViewData = pViewSh->GetViewData(); ScDocShell* pDocShell = rViewData.GetDocShell(); ScDocument& rDoc = pDocShell->GetDocument(); ScNameInputType eType = lcl_GetInputType( aText ); if ( eType == SC_NAME_INPUT_BAD_NAME || eType == SC_NAME_INPUT_BAD_SELECTION ) { TranslateId pId = (eType == SC_NAME_INPUT_BAD_NAME) ? STR_NAME_ERROR_NAME : STR_NAME_ERROR_SELECTION; pViewSh->ErrorMessage(pId); } else if ( eType == SC_NAME_INPUT_DEFINE ) { ScRangeName* pNames = rDoc.GetRangeName(); ScRange aSelection; if ( pNames && !pNames->findByUpperName(ScGlobal::getCharClass().uppercase(aText)) && (rViewData.GetSimpleArea( aSelection ) == SC_MARK_SIMPLE) ) { ScRangeName aNewRanges( *pNames ); ScAddress aCursor( rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo() ); OUString aContent(aSelection.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention())); ScRangeData* pNew = new ScRangeData( rDoc, aText, aContent, aCursor ); if ( aNewRanges.insert(pNew) ) { pDocShell->GetDocFunc().ModifyRangeNames( aNewRanges ); pViewSh->UpdateInputHandler(true); } } } else if (eType == SC_MANAGE_NAMES) { // dialog is only set below after calling 'ReleaseFocus_Impl' to ensure it gets focus bOpenManageNamesDialog = true; } else { bool bForceGlobalName = false; // for all selection types, execute the SID_CURRENTCELL slot. if (eType == SC_NAME_INPUT_CELL || eType == SC_NAME_INPUT_RANGE) { // Note that SID_CURRENTCELL always expects address to // be in Calc A1 format. Convert the text. ScRange aRange(0,0, rViewData.GetTabNo()); aRange.ParseAny(aText, rDoc, rDoc.GetAddressConvention()); aText = aRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D, ::formula::FormulaGrammar::CONV_OOO); } else if (eType == SC_NAME_INPUT_NAMEDRANGE_GLOBAL) { bForceGlobalName = true; } SfxStringItem aPosItem( SID_CURRENTCELL, aText ); SfxBoolItem aUnmarkItem( FN_PARAM_1, true ); // remove existing selection // FN_PARAM_2 reserved for AlignToCursor SfxBoolItem aForceGlobalName( FN_PARAM_3, bForceGlobalName ); pViewSh->GetViewData().GetDispatcher().ExecuteList( SID_CURRENTCELL, SfxCallMode::SYNCHRON | SfxCallMode::RECORD, { &aPosItem, &aUnmarkItem, &aForceGlobalName }); } } } } else m_xWidget->set_entry_text(aPosStr); ReleaseFocus_Impl(); if (bOpenManageNamesDialog) { const sal_uInt16 nId = ScNameDlgWrapper::GetChildWindowId(); if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) { SfxViewFrame& rViewFrm = pViewSh->GetViewFrame(); SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); SC_MOD()->SetRefDialog( nId, pWnd == nullptr ); } } } IMPL_LINK_NOARG(ScPosWnd, ActivateHdl, weld::ComboBox&, bool) { DoEnter(); return true; } IMPL_LINK(ScPosWnd, KeyInputHdl, const KeyEvent&, rKEvt, bool) { bool bHandled = true; switch (rKEvt.GetKeyCode().GetCode()) { case KEY_RETURN: bHandled = ActivateHdl(*m_xWidget); break; case KEY_ESCAPE: if (nTipVisible) { // escape when the tip help is shown: only hide the tip HideTip(); } else { if (!bFormulaMode) m_xWidget->set_entry_text(aPosStr); ReleaseFocus_Impl(); } break; default: bHandled = false; break; } return bHandled || ChildKeyInput(rKEvt); } IMPL_LINK_NOARG(ScPosWnd, OnAsyncGetFocus, void*, void) { m_nAsyncGetFocusId = nullptr; m_xWidget->select_entry_region(0, -1); } IMPL_LINK_NOARG(ScPosWnd, FocusInHdl, weld::Widget&, void) { if (m_nAsyncGetFocusId) return; // do it async to defeat entry in combobox having its own ideas about the focus m_nAsyncGetFocusId = Application::PostUserEvent(LINK(this, ScPosWnd, OnAsyncGetFocus)); } IMPL_LINK_NOARG(ScPosWnd, FocusOutHdl, weld::Widget&, void) { if (m_nAsyncGetFocusId) { Application::RemoveUserEvent(m_nAsyncGetFocusId); m_nAsyncGetFocusId = nullptr; } HideTip(); } void ScPosWnd::ReleaseFocus_Impl() { HideTip(); SfxViewShell* pCurSh = SfxViewShell::Current(); ScInputHandler* pHdl = SC_MOD()->GetInputHdl( dynamic_cast( pCurSh ) ); if ( pHdl && pHdl->IsTopMode() ) { // Focus back in input row? ScInputWindow* pInputWin = pHdl->GetInputWindow(); if (pInputWin) { pInputWin->TextGrabFocus(); return; } } // Set focus to active View if ( pCurSh ) { vcl::Window* pShellWnd = pCurSh->GetWindow(); if ( pShellWnd ) pShellWnd->GrabFocus(); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */