/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "accessibility.hxx" #include #include #include // space around the edit window, in pixels // fdo#69111: Increased border on the top so that the window is // easier to tear off. #define CMD_BOX_PADDING 3 #define CMD_BOX_PADDING_TOP 11 #define ShellClass_SmViewShell #include using namespace css; using namespace css::accessibility; using namespace css::uno; static OUString GetScrollUIName(const SmViewShell& rShell) { const SmDocShell* pShell = rShell.GetDoc(); if (pShell && pShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED) { // This one has no border on the scrolledwindow, to maximize the space // available when in embedded mode and minimize the difference from // the ole preview to give a more seamless embedded editing experience. return "modules/smath/ui/embedwindow.ui"; } return "modules/smath/ui/mathwindow.ui"; } SmGraphicWindow::SmGraphicWindow(SmViewShell& rShell) : InterimItemWindow(&rShell.GetViewFrame().GetWindow(), GetScrollUIName(rShell), u"MathWindow"_ustr) , nLinePixH(GetSettings().GetStyleSettings().GetScrollBarSize()) , nColumnPixW(nLinePixH) , nZoom(100) // continue to use user-scrolling to make this work equivalent to how it 'always' worked , mxScrolledWindow(m_xBuilder->weld_scrolled_window(u"scrolledwindow"_ustr, true)) , mxGraphic(new SmGraphicWidget(rShell, *this)) , mxGraphicWin(new weld::CustomWeld(*m_xBuilder, u"mathview"_ustr, *mxGraphic)) { InitControlBase(mxGraphic->GetDrawingArea()); mxScrolledWindow->connect_hadjustment_changed(LINK(this, SmGraphicWindow, ScrollHdl)); mxScrolledWindow->connect_vadjustment_changed(LINK(this, SmGraphicWindow, ScrollHdl)); // docking windows are usually hidden (often already done in the // resource) and will be shown by the sfx framework. Hide(); } void SmGraphicWindow::dispose() { InitControlBase(nullptr); mxGraphicWin.reset(); mxGraphic.reset(); mxScrolledWindow.reset(); InterimItemWindow::dispose(); } SmGraphicWindow::~SmGraphicWindow() { disposeOnce(); } void SmGraphicWindow::Resize() { InterimItemWindow::Resize(); // get the new output-size in pixel Size aOutPixSz = GetOutputSizePixel(); // determine the size of the output-area and if we need scrollbars const auto nScrSize = mxScrolledWindow->get_scroll_thickness(); bool bVVisible = false; // by default no vertical-ScrollBar bool bHVisible = false; // by default no horizontal-ScrollBar bool bChanged; // determines if a visibility was changed do { bChanged = false; // does we need a vertical ScrollBar if ( aOutPixSz.Width() < aTotPixSz.Width() && !bHVisible ) { bHVisible = true; aOutPixSz.AdjustHeight( -nScrSize ); bChanged = true; } // does we need a horizontal ScrollBar if ( aOutPixSz.Height() < aTotPixSz.Height() && !bVVisible ) { bVVisible = true; aOutPixSz.AdjustWidth( -nScrSize ); bChanged = true; } } while ( bChanged ); // until no visibility has changed // store the old offset and map-mode MapMode aMap(GetGraphicMapMode()); Point aOldPixOffset(aPixOffset); // justify (right/bottom borders should never exceed the virtual window) Size aPixDelta; if ( aPixOffset.X() < 0 && aPixOffset.X() + aTotPixSz.Width() < aOutPixSz.Width() ) aPixDelta.setWidth( aOutPixSz.Width() - ( aPixOffset.X() + aTotPixSz.Width() ) ); if ( aPixOffset.Y() < 0 && aPixOffset.Y() + aTotPixSz.Height() < aOutPixSz.Height() ) aPixDelta.setHeight( aOutPixSz.Height() - ( aPixOffset.Y() + aTotPixSz.Height() ) ); if ( aPixDelta.Width() || aPixDelta.Height() ) { aPixOffset.AdjustX(aPixDelta.Width() ); aPixOffset.AdjustY(aPixDelta.Height() ); } // for axis without scrollbar restore the origin if ( !bVVisible || !bHVisible ) { aPixOffset = Point( bHVisible ? aPixOffset.X() : (aOutPixSz.Width()-aTotPixSz.Width()) / 2, bVVisible ? aPixOffset.Y() : (aOutPixSz.Height()-aTotPixSz.Height()) / 2 ); } if (bHVisible && mxScrolledWindow->get_hpolicy() == VclPolicyType::NEVER) aPixOffset.setX( 0 ); if (bVVisible && mxScrolledWindow->get_vpolicy() == VclPolicyType::NEVER) aPixOffset.setY( 0 ); // select the shifted map-mode if (aPixOffset != aOldPixOffset) SetGraphicMapMode(aMap); // show or hide scrollbars mxScrolledWindow->set_vpolicy(bVVisible ? VclPolicyType::ALWAYS : VclPolicyType::NEVER); mxScrolledWindow->set_hpolicy(bHVisible ? VclPolicyType::ALWAYS : VclPolicyType::NEVER); // resize scrollbars and set their ranges if ( bHVisible ) { mxScrolledWindow->hadjustment_configure(-aPixOffset.X(), 0, aTotPixSz.Width(), nColumnPixW, aOutPixSz.Width(), aOutPixSz.Width()); } if ( bVVisible ) { mxScrolledWindow->vadjustment_configure(-aPixOffset.Y(), 0, aTotPixSz.Height(), nLinePixH, aOutPixSz.Height(), aOutPixSz.Height()); } } IMPL_LINK_NOARG(SmGraphicWindow, ScrollHdl, weld::ScrolledWindow&, void) { MapMode aMap(GetGraphicMapMode()); Point aNewPixOffset(aPixOffset); // scrolling horizontally? if (mxScrolledWindow->get_hpolicy() == VclPolicyType::ALWAYS) aNewPixOffset.setX(-mxScrolledWindow->hadjustment_get_value()); // scrolling vertically? if (mxScrolledWindow->get_vpolicy() == VclPolicyType::ALWAYS) aNewPixOffset.setY(-mxScrolledWindow->vadjustment_get_value()); // scrolling? if (aPixOffset == aNewPixOffset) return; // recompute the logical scroll units aPixOffset = aNewPixOffset; SetGraphicMapMode(aMap); } void SmGraphicWindow::SetGraphicMapMode(const MapMode& rNewMapMode) { OutputDevice& rDevice = mxGraphic->GetOutputDevice(); MapMode aMap( rNewMapMode ); aMap.SetOrigin( aMap.GetOrigin() + rDevice.PixelToLogic( aPixOffset, aMap ) ); rDevice.SetMapMode( aMap ); mxGraphic->Invalidate(); } MapMode SmGraphicWindow::GetGraphicMapMode() const { OutputDevice& rDevice = mxGraphic->GetOutputDevice(); MapMode aMap(rDevice.GetMapMode()); aMap.SetOrigin( aMap.GetOrigin() - rDevice.PixelToLogic( aPixOffset ) ); return aMap; } void SmGraphicWindow::SetTotalSize( const Size& rNewSize ) { aTotPixSz = mxGraphic->GetOutputDevice().LogicToPixel(rNewSize); Resize(); } Size SmGraphicWindow::GetTotalSize() const { return mxGraphic->GetOutputDevice().PixelToLogic(aTotPixSz); } void SmGraphicWindow::ShowContextMenu(const CommandEvent& rCEvt) { GetParent()->ToTop(); Point aPos(5, 5); if (rCEvt.IsMouseEvent()) aPos = rCEvt.GetMousePosPixel(); // added for replaceability of context menus SfxDispatcher::ExecutePopup( this, &aPos ); } SmGraphicWidget::SmGraphicWidget(SmViewShell& rShell, SmGraphicWindow& rGraphicWindow) : mrGraphicWindow(rGraphicWindow) , bIsCursorVisible(false) , bIsLineVisible(false) , aCaretBlinkTimer("SmGraphicWidget aCaretBlinkTimer") , mrViewShell(rShell) { } void SmGraphicWidget::SetDrawingArea(weld::DrawingArea* pDrawingArea) { weld::CustomWidgetController::SetDrawingArea(pDrawingArea); OutputDevice& rDevice = GetOutputDevice(); rDevice.EnableRTL(GetDoc()->GetFormat().IsRightToLeft()); rDevice.SetBackground( SmModule::get()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor); if (comphelper::LibreOfficeKit::isActive()) { // Disable map mode, so that it's possible to send mouse event coordinates // directly in twips. rDevice.EnableMapMode(false); } else { const Fraction aFraction(1, 1); rDevice.SetMapMode(MapMode(SmMapUnit(), Point(), aFraction, aFraction)); } SetTotalSize(); SetHelpId(HID_SMA_WIN_DOCUMENT); ShowLine(false); CaretBlinkInit(); } SmGraphicWidget::~SmGraphicWidget() { if (mxAccessible.is()) mxAccessible->ClearWin(); // make Accessible nonfunctional mxAccessible.clear(); CaretBlinkStop(); } SmDocShell* SmGraphicWidget::GetDoc() { return GetView().GetDoc(); } SmCursor& SmGraphicWidget::GetCursor() { assert(GetDoc()); return GetDoc()->GetCursor(); } bool SmGraphicWidget::MouseButtonDown(const MouseEvent& rMEvt) { GrabFocus(); // set formula-cursor and selection of edit window according to the // position clicked at SAL_WARN_IF( rMEvt.GetClicks() == 0, "starmath", "0 clicks" ); if ( !rMEvt.IsLeft() ) return true; OutputDevice& rDevice = GetOutputDevice(); // get click position relative to formula Point aPos(rDevice.PixelToLogic(rMEvt.GetPosPixel()) - GetFormulaDrawPos()); const SmNode *pTree = GetDoc()->GetFormulaTree(); if (!pTree) return true; SmEditWindow* pEdit = GetView().GetEditWindow(); if (SmViewShell::IsInlineEditEnabled()) { GetCursor().MoveTo(&rDevice, aPos, !rMEvt.IsShift()); GetView().InvalidateSlots(); // 'on grab' window events are missing in lok, do it explicitly if (comphelper::LibreOfficeKit::isActive()) SetIsCursorVisible(true); return true; } const SmNode *pNode = nullptr; // if it was clicked inside the formula then get the appropriate node if (pTree->OrientedDist(aPos) <= 0) pNode = pTree->FindRectClosestTo(aPos); if (!pNode) return true; if (!pEdit) return true; // set selection to the beginning of the token pEdit->SetSelection(pNode->GetSelection()); SetCursor(pNode); // allow for immediate editing and //! implicitly synchronize the cursor position mark in this window pEdit->GrabFocus(); return true; } bool SmGraphicWidget::MouseMove(const MouseEvent &rMEvt) { if (rMEvt.IsLeft() && SmViewShell::IsInlineEditEnabled()) { OutputDevice& rDevice = GetOutputDevice(); Point aPos(rDevice.PixelToLogic(rMEvt.GetPosPixel()) - GetFormulaDrawPos()); GetCursor().MoveTo(&rDevice, aPos, false); CaretBlinkStop(); SetIsCursorVisible(true); CaretBlinkStart(); RepaintViewShellDoc(); } return true; } void SmGraphicWidget::GetFocus() { if (!SmViewShell::IsInlineEditEnabled()) return; if (SmEditWindow* pEdit = GetView().GetEditWindow()) pEdit->Flush(); SetIsCursorVisible(true); ShowLine(true); CaretBlinkStart(); RepaintViewShellDoc(); } void SmGraphicWidget::LoseFocus() { if (mxAccessible.is()) { uno::Any aOldValue, aNewValue; aOldValue <<= AccessibleStateType::FOCUSED; // aNewValue remains empty mxAccessible->LaunchEvent( AccessibleEventId::STATE_CHANGED, aOldValue, aNewValue ); } if (!SmViewShell::IsInlineEditEnabled()) return; SetIsCursorVisible(false); ShowLine(false); CaretBlinkStop(); RepaintViewShellDoc(); } void SmGraphicWidget::RepaintViewShellDoc() { if (SmDocShell* pDoc = GetDoc()) pDoc->Repaint(); } IMPL_LINK_NOARG(SmGraphicWidget, CaretBlinkTimerHdl, Timer *, void) { if (IsCursorVisible()) SetIsCursorVisible(false); else SetIsCursorVisible(true); RepaintViewShellDoc(); } void SmGraphicWidget::CaretBlinkInit() { if (comphelper::LibreOfficeKit::isActive()) return; // No blinking in lok case aCaretBlinkTimer.SetInvokeHandler(LINK(this, SmGraphicWidget, CaretBlinkTimerHdl)); aCaretBlinkTimer.SetTimeout(Application::GetSettings().GetStyleSettings().GetCursorBlinkTime()); } void SmGraphicWidget::CaretBlinkStart() { if (!SmViewShell::IsInlineEditEnabled() || comphelper::LibreOfficeKit::isActive()) return; if (aCaretBlinkTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME) aCaretBlinkTimer.Start(); } void SmGraphicWidget::CaretBlinkStop() { if (!SmViewShell::IsInlineEditEnabled() || comphelper::LibreOfficeKit::isActive()) return; aCaretBlinkTimer.Stop(); } // shows or hides the formula-cursor depending on 'bShow' is true or not void SmGraphicWidget::ShowCursor(bool bShow) { if (SmViewShell::IsInlineEditEnabled()) return; bool bInvert = bShow != IsCursorVisible(); if (bInvert) InvertFocusRect(GetOutputDevice(), aCursorRect); SetIsCursorVisible(bShow); } void SmGraphicWidget::ShowLine(bool bShow) { if (!SmViewShell::IsInlineEditEnabled()) return; bIsLineVisible = bShow; } void SmGraphicWidget::SetIsCursorVisible(bool bVis) { bIsCursorVisible = bVis; if (comphelper::LibreOfficeKit::isActive()) { mrViewShell.SendCaretToLOK(); mrViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, OString::boolean(bVis)); } } void SmGraphicWidget::SetCursor(const SmNode *pNode) { if (SmViewShell::IsInlineEditEnabled()) return; const SmNode *pTree = GetDoc()->GetFormulaTree(); // get appropriate rectangle Point aOffset (pNode->GetTopLeft() - pTree->GetTopLeft()), aTLPos (GetFormulaDrawPos() + aOffset); aTLPos.AdjustX( -(pNode->GetItalicLeftSpace()) ); Size aSize (pNode->GetItalicSize()); SetCursor(tools::Rectangle(aTLPos, aSize)); } void SmGraphicWidget::SetCursor(const tools::Rectangle &rRect) // sets cursor to new position (rectangle) 'rRect'. // The old cursor will be removed, and the new one will be shown if // that is activated in the ConfigItem { if (SmViewShell::IsInlineEditEnabled()) return; if (IsCursorVisible()) ShowCursor(false); // clean up remainings of old cursor aCursorRect = rRect; if (SmModule::get()->GetConfig()->IsShowFormulaCursor()) ShowCursor(true); // draw new cursor } const SmNode * SmGraphicWidget::SetCursorPos(sal_uInt16 nRow, sal_uInt16 nCol) // looks for a VISIBLE node in the formula tree with its token at // (or around) the position 'nRow', 'nCol' in the edit window // (row and column numbering starts with 1 there!). // If there is such a node the formula-cursor is set to cover that nodes // rectangle. If not the formula-cursor will be hidden. // In any case the search result is being returned. { if (SmViewShell::IsInlineEditEnabled()) return nullptr; // find visible node with token at nRow, nCol const SmNode *pTree = GetDoc()->GetFormulaTree(), *pNode = nullptr; if (pTree) pNode = pTree->FindTokenAt(nRow, nCol); if (pNode) SetCursor(pNode); else ShowCursor(false); return pNode; } void SmGraphicWidget::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) { assert(GetDoc()); SmDocShell& rDoc = *GetDoc(); Point aPoint; rDoc.DrawFormula(rRenderContext, aPoint, true); //! modifies aPoint to be the topleft //! corner of the formula aFormulaDrawPos = aPoint; if (SmViewShell::IsInlineEditEnabled()) { //Draw cursor if any... if (rDoc.HasCursor() && IsLineVisible()) rDoc.GetCursor().Draw(rRenderContext, aPoint, IsCursorVisible()); } else { SetIsCursorVisible(false); // (old) cursor must be drawn again if (const SmEditWindow* pEdit = GetView().GetEditWindow()) { // get new position for formula-cursor (for possible altered formula) sal_Int32 nRow; sal_uInt16 nCol; SmGetLeftSelectionPart(pEdit->GetSelection(), nRow, nCol); const SmNode *pFound = SetCursorPos(static_cast(nRow), nCol); if (pFound && SmModule::get()->GetConfig()->IsShowFormulaCursor()) ShowCursor(true); } } } void SmGraphicWidget::SetTotalSize() { assert(GetDoc()); OutputDevice& rDevice = GetOutputDevice(); const Size aTmp(rDevice.PixelToLogic(rDevice.LogicToPixel(GetDoc()->GetSize()))); if (aTmp != mrGraphicWindow.GetTotalSize()) mrGraphicWindow.SetTotalSize(aTmp); } namespace { SmBracketType BracketTypeOf(sal_uInt32 c) { switch (c) { case '(': case ')': return SmBracketType::Round; case '[': case ']': return SmBracketType::Square; case '{': case '}': return SmBracketType::Curly; } assert(false); // Unreachable return SmBracketType::Round; } bool CharInput(sal_uInt32 c, SmCursor& rCursor, OutputDevice& rDevice) { switch (c) { case 0: return false; case ' ': rCursor.InsertElement(BlankElement); break; case '!': rCursor.InsertElement(FactorialElement); break; case '%': rCursor.InsertElement(PercentElement); break; case '*': rCursor.InsertElement(CDotElement); break; case '+': rCursor.InsertElement(PlusElement); break; case '-': rCursor.InsertElement(MinusElement); break; case '<': rCursor.InsertElement(LessThanElement); break; case '=': rCursor.InsertElement(EqualElement); break; case '>': rCursor.InsertElement(GreaterThanElement); break; case '^': rCursor.InsertSubSup(RSUP); break; case '_': rCursor.InsertSubSup(RSUB); break; case '/': rCursor.InsertFraction(); break; case '(': case '[': case '{': rCursor.InsertBrackets(BracketTypeOf(c)); break; case ')': case ']': case '}': if (rCursor.IsAtTailOfBracket(BracketTypeOf(c))) { rCursor.Move(&rDevice, MoveRight); break; } [[fallthrough]]; default: rCursor.InsertText(OUString(&c, 1)); break; } return true; } } bool SmGraphicWidget::KeyInput(const KeyEvent& rKEvt) { if (rKEvt.GetKeyCode().GetCode() == KEY_F1) { GetView().StartMainHelp(); return true; } if (rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE) { // Terminate possible InPlace mode return GetView().Escape(); } if (!SmViewShell::IsInlineEditEnabled()) return GetView().KeyInput(rKEvt); bool bConsumed = true; SmCursor& rCursor = GetCursor(); switch (rKEvt.GetKeyCode().GetFunction()) { case KeyFuncType::COPY: rCursor.Copy(&mrGraphicWindow); break; case KeyFuncType::CUT: rCursor.Cut(&mrGraphicWindow); break; case KeyFuncType::PASTE: rCursor.Paste(&mrGraphicWindow); break; case KeyFuncType::UNDO: GetDoc()->Execute(o3tl::temporary(SfxRequest(*GetView().GetFrame(), SID_UNDO))); break; case KeyFuncType::REDO: GetDoc()->Execute(o3tl::temporary(SfxRequest(*GetView().GetFrame(), SID_REDO))); break; default: switch (rKEvt.GetKeyCode().GetCode()) { case KEY_LEFT: rCursor.Move(&GetOutputDevice(), MoveLeft, !rKEvt.GetKeyCode().IsShift()); break; case KEY_RIGHT: rCursor.Move(&GetOutputDevice(), MoveRight, !rKEvt.GetKeyCode().IsShift()); break; case KEY_UP: rCursor.Move(&GetOutputDevice(), MoveUp, !rKEvt.GetKeyCode().IsShift()); break; case KEY_DOWN: rCursor.Move(&GetOutputDevice(), MoveDown, !rKEvt.GetKeyCode().IsShift()); break; case KEY_RETURN: if (!rKEvt.GetKeyCode().IsShift()) rCursor.InsertRow(); break; case KEY_DELETE: if (!rCursor.HasSelection()) { rCursor.Move(&GetOutputDevice(), MoveRight, false); if (rCursor.HasComplexSelection()) break; } rCursor.Delete(); break; case KEY_BACKSPACE: rCursor.DeletePrev(&GetOutputDevice()); break; default: if (!CharInput(rKEvt.GetCharCode(), rCursor, GetOutputDevice())) bConsumed = GetView().KeyInput(rKEvt); } } GetView().InvalidateSlots(); CaretBlinkStop(); CaretBlinkStart(); SetIsCursorVisible(true); RepaintViewShellDoc(); return bConsumed; } bool SmGraphicWidget::Command(const CommandEvent& rCEvt) { bool bCallBase = true; if (!GetView().GetViewFrame().GetFrame().IsInPlace()) { switch ( rCEvt.GetCommand() ) { case CommandEventId::ContextMenu: // purely for "ExecutePopup" taking a vcl::Window and // we assume SmGraphicWindow 0,0 is at SmEditWindow 0,0 mrGraphicWindow.ShowContextMenu(rCEvt); bCallBase = false; break; case CommandEventId::Wheel: { const CommandWheelData* pWData = rCEvt.GetWheelData(); if ( pWData && CommandWheelMode::ZOOM == pWData->GetMode() ) { sal_uInt16 nTmpZoom = mrGraphicWindow.GetZoom(); if( 0 > pWData->GetDelta() ) nTmpZoom -= 10; else nTmpZoom += 10; mrGraphicWindow.SetZoom(nTmpZoom); bCallBase = false; } break; } case CommandEventId::GestureZoom: { const CommandGestureZoomData* pData = rCEvt.GetGestureZoomData(); if (pData) { if (pData->meEventType == GestureEventZoomType::Begin) { mfLastZoomScale = pData->mfScaleDelta; } else if (pData->meEventType == GestureEventZoomType::Update) { double deltaBetweenEvents = (pData->mfScaleDelta - mfLastZoomScale) / mfLastZoomScale; mfLastZoomScale = pData->mfScaleDelta; // Accumulate fractional zoom to avoid small zoom changes from being ignored mfAccumulatedZoom += deltaBetweenEvents; int nZoomChangePercent = mfAccumulatedZoom * 100; mfAccumulatedZoom -= nZoomChangePercent / 100.0; sal_uInt16 nZoom = mrGraphicWindow.GetZoom(); nZoom += nZoomChangePercent; mrGraphicWindow.SetZoom(nZoom); } bCallBase = false; } break; } default: break; } } switch (rCEvt.GetCommand()) { case CommandEventId::ExtTextInput: if (SmViewShell::IsInlineEditEnabled()) { const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); assert(pData); const OUString& rText = pData->GetText(); SmCursor& rCursor = GetCursor(); OutputDevice& rDevice = GetOutputDevice(); for (sal_Int32 i = 0; i < rText.getLength();) CharInput(rText.iterateCodePoints(&i), rCursor, rDevice); bCallBase = false; } break; default: break; } return !bCallBase; } void SmGraphicWindow::SetZoom(sal_uInt16 Factor) { if (comphelper::LibreOfficeKit::isActive()) return; nZoom = std::clamp(Factor, MINZOOM, MAXZOOM); Fraction aFraction(nZoom, 100); SetGraphicMapMode(MapMode(SmMapUnit(), Point(), aFraction, aFraction)); mxGraphic->SetTotalSize(); SmViewShell& rViewSh = mxGraphic->GetView(); rViewSh.GetViewFrame().GetBindings().Invalidate(SID_ATTR_ZOOM); rViewSh.GetViewFrame().GetBindings().Invalidate(SID_ATTR_ZOOMSLIDER); } void SmGraphicWindow::ZoomToFitInWindow() { // set defined mapmode before calling 'LogicToPixel' below SetGraphicMapMode(MapMode(SmMapUnit())); assert(mxGraphic->GetDoc()); Size aSize(mxGraphic->GetOutputDevice().LogicToPixel(mxGraphic->GetDoc()->GetSize())); Size aWindowSize(GetSizePixel()); if (!aSize.IsEmpty()) { tools::Long nVal = std::min ((85 * aWindowSize.Width()) / aSize.Width(), (85 * aWindowSize.Height()) / aSize.Height()); SetZoom ( sal::static_int_cast< sal_uInt16 >(nVal) ); } } uno::Reference< XAccessible > SmGraphicWidget::CreateAccessible() { if (!mxAccessible.is()) { mxAccessible = new SmGraphicAccessible( this ); } return mxAccessible; } /**************************************************************************/ SmGraphicController::SmGraphicController(SmGraphicWidget &rSmGraphic, sal_uInt16 nId_, SfxBindings &rBindings) : SfxControllerItem(nId_, rBindings), rGraphic(rSmGraphic) { } void SmGraphicController::StateChangedAtToolBoxControl(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) { rGraphic.SetTotalSize(); rGraphic.Invalidate(); SfxControllerItem::StateChangedAtToolBoxControl (nSID, eState, pState); } /**************************************************************************/ SmEditController::SmEditController(SmEditWindow &rSmEdit, sal_uInt16 nId_, SfxBindings &rBindings) : SfxControllerItem(nId_, rBindings), rEdit(rSmEdit) { } void SmEditController::StateChangedAtToolBoxControl(sal_uInt16 nSID, SfxItemState eState, const SfxPoolItem* pState) { const SfxStringItem *pItem = dynamic_cast( pState); if ((pItem != nullptr) && (rEdit.GetText() != pItem->GetValue())) rEdit.SetText(pItem->GetValue()); SfxControllerItem::StateChangedAtToolBoxControl (nSID, eState, pState); } /**************************************************************************/ SmCmdBoxWindow::SmCmdBoxWindow(SfxBindings *pBindings_, SfxChildWindow *pChildWindow, vcl::Window *pParent) : SfxDockingWindow(pBindings_, pChildWindow, pParent, u"EditWindow"_ustr, u"modules/smath/ui/editwindow.ui"_ustr) , m_xEdit(new SmEditWindow(*this, *m_xBuilder)) , aController(*m_xEdit, SID_TEXT, *pBindings_) , bExiting(false) , aInitialFocusTimer("SmCmdBoxWindow aInitialFocusTimer") { set_id(u"math_edit"_ustr); SetHelpId( HID_SMA_COMMAND_WIN ); SetSizePixel(LogicToPixel(Size(292 , 94), MapMode(MapUnit::MapAppFont))); SetText(SmResId(STR_CMDBOXWINDOW)); Hide(); // Don't try to grab focus in inline edit mode if (!SmViewShell::IsInlineEditEnabled()) { aInitialFocusTimer.SetInvokeHandler(LINK(this, SmCmdBoxWindow, InitialFocusTimerHdl)); aInitialFocusTimer.SetTimeout(100); } } Point SmCmdBoxWindow::WidgetToWindowPos(const weld::Widget& rWidget, const Point& rPos) { Point aRet(rPos); int x(0), y(0), width(0), height(0); rWidget.get_extents_relative_to(*m_xContainer, x, y, width, height); aRet.Move(x, y); aRet.Move(m_xBox->GetPosPixel().X(), m_xBox->GetPosPixel().Y()); return aRet; } void SmCmdBoxWindow::ShowContextMenu(const Point& rPos) { ToTop(); SmViewShell *pViewSh = GetView(); if (pViewSh) pViewSh->GetViewFrame().GetDispatcher()->ExecutePopup(u"edit"_ustr, this, &rPos); } void SmCmdBoxWindow::Command(const CommandEvent& rCEvt) { if (rCEvt.GetCommand() == CommandEventId::ContextMenu) { ShowContextMenu(rCEvt.GetMousePosPixel()); return; } SfxDockingWindow::Command(rCEvt); } SmCmdBoxWindow::~SmCmdBoxWindow () { disposeOnce(); } void SmCmdBoxWindow::dispose() { aInitialFocusTimer.Stop(); bExiting = true; aController.dispose(); m_xEdit.reset(); SfxDockingWindow::dispose(); } SmViewShell * SmCmdBoxWindow::GetView() { SfxDispatcher *pDispatcher = GetBindings().GetDispatcher(); SfxViewShell *pView = pDispatcher ? pDispatcher->GetFrame()->GetViewShell() : nullptr; return dynamic_cast( pView); } Size SmCmdBoxWindow::CalcDockingSize(SfxChildAlignment eAlign) { switch (eAlign) { case SfxChildAlignment::LEFT: case SfxChildAlignment::RIGHT: return Size(); default: break; } return SfxDockingWindow::CalcDockingSize(eAlign); } SfxChildAlignment SmCmdBoxWindow::CheckAlignment(SfxChildAlignment eActual, SfxChildAlignment eWish) { switch (eWish) { case SfxChildAlignment::TOP: case SfxChildAlignment::BOTTOM: case SfxChildAlignment::NOALIGNMENT: return eWish; default: break; } return eActual; } void SmCmdBoxWindow::StateChanged( StateChangedType nStateChange ) { if (StateChangedType::InitShow == nStateChange) { Resize(); // avoid SmEditWindow not being painted correctly // set initial position of window in floating mode if (IsFloatingMode()) AdjustPosition(); //! don't change pos in docking-mode ! aInitialFocusTimer.Start(); } SfxDockingWindow::StateChanged( nStateChange ); } IMPL_LINK_NOARG( SmCmdBoxWindow, InitialFocusTimerHdl, Timer *, void ) { // We want to have the focus in the edit window once Math has been opened // to allow for immediate typing. // Problem: There is no proper way to do this // Thus: this timer based solution has been implemented (see GrabFocus below) // Follow-up problem (#i114910): grabbing the focus may bust the help system since // it relies on getting the current frame which conflicts with grabbing the focus. // Thus aside from the 'GrabFocus' call everything else is to get the // help reliably working despite using 'GrabFocus'. try { uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( comphelper::getProcessComponentContext() ); m_xEdit->GrabFocus(); SmViewShell* pView = GetView(); assert(pView); bool bInPlace = pView->GetViewFrame().GetFrame().IsInPlace(); uno::Reference< frame::XFrame > xFrame( GetBindings().GetDispatcher()->GetFrame()->GetFrame().GetFrameInterface()); if ( bInPlace ) { uno::Reference xModel(pView->GetDoc()->GetModel(), uno::UNO_QUERY_THROW); uno::Reference< frame::XModel > xParent( xModel->getParent(), uno::UNO_QUERY_THROW ); uno::Reference< frame::XController > xParentCtrler( xParent->getCurrentController() ); uno::Reference< frame::XFramesSupplier > xParentFrame( xParentCtrler->getFrame(), uno::UNO_QUERY_THROW ); xParentFrame->setActiveFrame( xFrame ); } else { xDesktop->setActiveFrame( xFrame ); } } catch (uno::Exception &) { SAL_WARN( "starmath", "failed to properly set initial focus to edit window" ); } } void SmCmdBoxWindow::AdjustPosition() { const tools::Rectangle aRect( Point(), GetParent()->GetOutputSizePixel() ); Point aTopLeft( Point( aRect.Left(), aRect.Bottom() - GetSizePixel().Height() ) ); Point aPos( GetParent()->OutputToScreenPixel( aTopLeft ) ); if (aPos.X() < 0) aPos.setX( 0 ); if (aPos.Y() < 0) aPos.setY( 0 ); SetPosPixel( aPos ); } void SmCmdBoxWindow::ToggleFloatingMode() { SfxDockingWindow::ToggleFloatingMode(); if (GetFloatingWindow()) GetFloatingWindow()->SetMinOutputSizePixel(Size (200, 50)); } void SmCmdBoxWindow::GetFocus() { if (!bExiting) m_xEdit->GrabFocus(); } SFX_IMPL_DOCKINGWINDOW_WITHID(SmCmdBoxWrapper, SID_CMDBOXWINDOW); SmCmdBoxWrapper::SmCmdBoxWrapper(vcl::Window *pParentWindow, sal_uInt16 nId, SfxBindings *pBindings, SfxChildWinInfo *pInfo) : SfxChildWindow(pParentWindow, nId) { VclPtrInstance pDialog(pBindings, this, pParentWindow); SetWindow(pDialog); // make window docked to the bottom initially (after first start) SetAlignment(SfxChildAlignment::BOTTOM); pDialog->setDeferredProperties(); pDialog->set_border_width(CMD_BOX_PADDING); pDialog->set_margin_top(CMD_BOX_PADDING_TOP); pDialog->Initialize(pInfo); } SFX_IMPL_SUPERCLASS_INTERFACE(SmViewShell, SfxViewShell) void SmViewShell::InitInterface_Impl() { GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_TOOLS, SfxVisibilityFlags::Standard | SfxVisibilityFlags::FullScreen | SfxVisibilityFlags::Server, ToolbarId::Math_Toolbox); //Dummy-Objectbar, to avoid quiver while activating GetStaticInterface()->RegisterChildWindow(SmCmdBoxWrapper::GetChildWindowId()); GetStaticInterface()->RegisterChildWindow(SfxInfoBarContainerChild::GetChildWindowId()); GetStaticInterface()->RegisterChildWindow(::sfx2::sidebar::SidebarChildWindow::GetChildWindowId()); } SFX_IMPL_NAMED_VIEWFACTORY(SmViewShell, "Default") { SFX_VIEW_REGISTRATION(SmDocShell); } void SmViewShell::InnerResizePixel(const Point &rOfs, const Size &rSize, bool) { Size aObjSize = GetObjectShell()->GetVisArea().GetSize(); if ( !aObjSize.IsEmpty() ) { Size aProvidedSize = GetWindow()->PixelToLogic(rSize, MapMode(SmMapUnit())); Fraction aZoomX(aProvidedSize.Width(), aObjSize.Width()); Fraction aZoomY(aProvidedSize.Height(), aObjSize.Height()); MapMode aMap(mxGraphicWindow->GetGraphicMapMode()); aMap.SetScaleX(aZoomX); aMap.SetScaleY(aZoomY); mxGraphicWindow->SetGraphicMapMode(aMap); } SetBorderPixel( SvBorder() ); mxGraphicWindow->SetPosSizePixel(rOfs, rSize); GetGraphicWidget().SetTotalSize(); } void SmViewShell::OuterResizePixel(const Point &rOfs, const Size &rSize) { mxGraphicWindow->SetPosSizePixel(rOfs, rSize); if (GetDoc()->IsPreview()) mxGraphicWindow->ZoomToFitInWindow(); } void SmViewShell::QueryObjAreaPixel( tools::Rectangle& rRect ) const { rRect.SetSize(mxGraphicWindow->GetSizePixel()); } void SmViewShell::SetZoomFactor( const Fraction &rX, const Fraction &rY ) { const Fraction &rFrac = std::min(rX, rY); mxGraphicWindow->SetZoom(sal::static_int_cast(tools::Long(rFrac * Fraction( 100, 1 )))); //To avoid rounding errors base class regulates crooked values too //if necessary SfxViewShell::SetZoomFactor( rX, rY ); } SfxPrinter* SmViewShell::GetPrinter(bool bCreate) { SmDocShell* pDoc = GetDoc(); if (pDoc->HasPrinter() || bCreate) return pDoc->GetPrinter(); return nullptr; } sal_uInt16 SmViewShell::SetPrinter(SfxPrinter *pNewPrinter, SfxPrinterChangeFlags nDiffFlags ) { SfxPrinter *pOld = GetDoc()->GetPrinter(); if ( pOld && pOld->IsPrinting() ) return SFX_PRINTERROR_BUSY; if ((nDiffFlags & SfxPrinterChangeFlags::PRINTER) == SfxPrinterChangeFlags::PRINTER) GetDoc()->SetPrinter( pNewPrinter ); if ((nDiffFlags & SfxPrinterChangeFlags::OPTIONS) == SfxPrinterChangeFlags::OPTIONS) { SmModule::get()->GetConfig()->ItemSetToConfig(pNewPrinter->GetOptions()); } return 0; } bool SmViewShell::HasPrintOptionsPage() const { return true; } std::unique_ptr SmViewShell::CreatePrintOptionsPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet &rOptions) { return SmPrintOptionsTabPage::Create(pPage, pController, rOptions); } SmEditWindow *SmViewShell::GetEditWindow() { SmCmdBoxWrapper* pWrapper = static_cast( GetViewFrame().GetChildWindow(SmCmdBoxWrapper::GetChildWindowId())); if (pWrapper != nullptr) { SmEditWindow& rEditWin = pWrapper->GetEditWindow(); return &rEditWin; } return nullptr; } void SmViewShell::SetStatusText(const OUString& rText) { maStatusText = rText; GetViewFrame().GetBindings().Invalidate(SID_TEXTSTATUS); } void SmViewShell::ShowError(const SmErrorDesc* pErrorDesc) { assert(GetDoc()); if (pErrorDesc || nullptr != (pErrorDesc = GetDoc()->GetParser()->GetError()) ) { SetStatusText( pErrorDesc->m_aText ); if (SmEditWindow* pEdit = GetEditWindow()) pEdit->MarkError( Point( pErrorDesc->m_pNode->GetColumn(), pErrorDesc->m_pNode->GetRow())); } } void SmViewShell::NextError() { assert(GetDoc()); const SmErrorDesc *pErrorDesc = GetDoc()->GetParser()->NextError(); if (pErrorDesc) ShowError( pErrorDesc ); } void SmViewShell::PrevError() { assert(GetDoc()); const SmErrorDesc *pErrorDesc = GetDoc()->GetParser()->PrevError(); if (pErrorDesc) ShowError( pErrorDesc ); } void SmViewShell::Insert( SfxMedium& rMedium ) { SmDocShell *pDoc = GetDoc(); bool bRet = false; uno::Reference xStorage = rMedium.GetStorage(); if (xStorage.is() && xStorage->getElementNames().hasElements()) { if (xStorage->hasByName(u"content.xml"_ustr)) { // is this a fabulous math package ? rtl::Reference xModel(dynamic_cast(pDoc->GetModel().get())); SmXMLImportWrapper aEquation(xModel); //!! modifies the result of pDoc->GetText() !! bRet = ERRCODE_NONE == aEquation.Import(rMedium); } } if (!bRet) return; OUString aText = pDoc->GetText(); if (SmEditWindow *pEditWin = GetEditWindow()) pEditWin->InsertText( aText ); else { SAL_WARN( "starmath", "EditWindow missing" ); } pDoc->Parse(); pDoc->SetModified(); SfxBindings &rBnd = GetViewFrame().GetBindings(); rBnd.Invalidate(SID_GRAPHIC_SM); rBnd.Invalidate(SID_TEXT); } void SmViewShell::InsertFrom(SfxMedium &rMedium) { bool bSuccess = false; SmDocShell* pDoc = GetDoc(); SvStream* pStream = rMedium.GetInStream(); if (pStream) { const OUString& rFltName = rMedium.GetFilter()->GetFilterName(); if ( rFltName == MATHML_XML ) { rtl::Reference xModel(dynamic_cast(pDoc->GetModel().get())); SmXMLImportWrapper aEquation(xModel); //!! modifies the result of pDoc->GetText() !! bSuccess = ERRCODE_NONE == aEquation.Import(rMedium); } } if (!bSuccess) return; OUString aText = pDoc->GetText(); if (SmEditWindow *pEditWin = GetEditWindow()) pEditWin->InsertText(aText); else SAL_WARN( "starmath", "EditWindow missing" ); pDoc->Parse(); pDoc->SetModified(); SfxBindings& rBnd = GetViewFrame().GetBindings(); rBnd.Invalidate(SID_GRAPHIC_SM); rBnd.Invalidate(SID_TEXT); } void SmViewShell::Execute(SfxRequest& rReq) { SmEditWindow *pWin = GetEditWindow(); switch (rReq.GetSlot()) { case SID_FORMULACURSOR: { auto* config = SmModule::get()->GetConfig(); const SfxItemSet *pArgs = rReq.GetArgs(); const SfxPoolItem *pItem; bool bVal; if ( pArgs && SfxItemState::SET == pArgs->GetItemState( SID_FORMULACURSOR, false, &pItem)) bVal = static_cast(pItem)->GetValue(); else bVal = !config->IsShowFormulaCursor(); config->SetShowFormulaCursor(bVal); if (!IsInlineEditEnabled()) GetGraphicWidget().ShowCursor(bVal); break; } case SID_DRAW: if (pWin) { GetDoc()->SetText( pWin->GetText() ); SetStatusText(OUString()); ShowError( nullptr ); GetDoc()->Repaint(); } break; case SID_ZOOM_OPTIMAL: mxGraphicWindow->ZoomToFitInWindow(); break; case SID_ZOOMIN: mxGraphicWindow->SetZoom(mxGraphicWindow->GetZoom() + 25); break; case SID_ZOOMOUT: SAL_WARN_IF( mxGraphicWindow->GetZoom() < 25, "starmath", "incorrect sal_uInt16 argument" ); mxGraphicWindow->SetZoom(mxGraphicWindow->GetZoom() - 25); break; case SID_COPYOBJECT: { //TODO/LATER: does not work because of UNO Tunneling - will be fixed later Reference< datatransfer::XTransferable > xTrans( GetDoc()->GetModel(), uno::UNO_QUERY ); if( xTrans.is() ) { auto pTrans = dynamic_cast(xTrans.get()); if (pTrans) { if (pWin) pTrans->CopyToClipboard(pWin->GetClipboard()); } } } break; case SID_PASTEOBJECT: { uno::Reference < io::XInputStream > xStrm; if (pWin) { TransferableDataHelper aData(TransferableDataHelper::CreateFromClipboard(pWin->GetClipboard())); SotClipboardFormatId nId; if( aData.GetTransferable().is() && ( aData.HasFormat( nId = SotClipboardFormatId::EMBEDDED_OBJ ) || (aData.HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) && aData.HasFormat( nId = SotClipboardFormatId::EMBED_SOURCE )))) xStrm = aData.GetInputStream(nId, OUString()); } if (xStrm.is()) { try { uno::Reference < embed::XStorage > xStorage = ::comphelper::OStorageHelper::GetStorageFromInputStream( xStrm, ::comphelper::getProcessComponentContext() ); SfxMedium aMedium( xStorage, OUString() ); Insert( aMedium ); GetDoc()->UpdateText(); } catch (uno::Exception &) { SAL_WARN( "starmath", "SmViewShell::Execute (SID_PASTEOBJECT): failed to get storage from input stream" ); } } } break; case SID_CUT: if (IsInlineEditEnabled()) { GetDoc()->GetCursor().Cut(&GetGraphicWindow()); GetGraphicWidget().GrabFocus(); } else if (pWin) pWin->Cut(); break; case SID_COPY: if (IsInlineEditEnabled()) { GetDoc()->GetCursor().Copy(&GetGraphicWindow()); GetGraphicWidget().GrabFocus(); } else if (pWin) { if (pWin->IsAllSelected()) { GetViewFrame().GetDispatcher()->ExecuteList( SID_COPYOBJECT, SfxCallMode::RECORD, { new SfxVoidItem(SID_COPYOBJECT) }); } else pWin->Copy(); } break; case SID_PASTE: { if (IsInlineEditEnabled()) { GetDoc()->GetCursor().Paste(&GetGraphicWindow()); GetGraphicWidget().GrabFocus(); break; } if( pWin ) { TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromClipboard( pWin->GetClipboard())); if( aDataHelper.GetTransferable().is() && aDataHelper.HasFormat( SotClipboardFormatId::STRING )) pWin->Paste(); else { GetViewFrame().GetDispatcher()->ExecuteList( SID_PASTEOBJECT, SfxCallMode::RECORD, { new SfxVoidItem(SID_PASTEOBJECT) }); } } } break; case SID_DELETE: if (IsInlineEditEnabled()) { if (!GetDoc()->GetCursor().HasSelection()) { GetDoc()->GetCursor().Move(&GetGraphicWindow().GetGraphicWidget().GetOutputDevice(), MoveRight, false); if (!GetDoc()->GetCursor().HasComplexSelection()) GetDoc()->GetCursor().Delete(); } else GetDoc()->GetCursor().Delete(); GetGraphicWidget().GrabFocus(); } else if (pWin) pWin->Delete(); break; case SID_SELECT: if (pWin) pWin->SelectAll(); break; case SID_INSERTCOMMANDTEXT: { const SfxStringItem& rItem = rReq.GetArgs()->Get(SID_INSERTCOMMANDTEXT); if (IsInlineEditEnabled()) { GetDoc()->GetCursor().InsertCommandText(rItem.GetValue()); GetGraphicWidget().GrabFocus(); } else if (pWin) { pWin->InsertText(rItem.GetValue()); } break; } case SID_INSERTSPECIAL: { const SfxStringItem& rItem = rReq.GetArgs()->Get(SID_INSERTSPECIAL); if (IsInlineEditEnabled()) GetDoc()->GetCursor().InsertSpecial(rItem.GetValue()); else if (pWin) pWin->InsertText(rItem.GetValue()); break; } case SID_IMPORT_FORMULA: { mpRequest.reset(new SfxRequest( rReq )); mpDocInserter.reset(new ::sfx2::DocumentInserter(pWin ? pWin->GetFrameWeld() : nullptr, GetDoc()->GetFactory().GetFactoryName())); mpDocInserter->StartExecuteModal( LINK( this, SmViewShell, DialogClosedHdl ) ); break; } case SID_IMPORT_MATHML_CLIPBOARD: { if (pWin) { TransferableDataHelper aDataHelper(TransferableDataHelper::CreateFromClipboard(pWin->GetClipboard())); uno::Reference < io::XInputStream > xStrm; if ( aDataHelper.GetTransferable().is() ) { SotClipboardFormatId nId = SotClipboardFormatId::MATHML; if (aDataHelper.HasFormat(nId)) { xStrm = aDataHelper.GetInputStream(nId, u""_ustr); if (xStrm.is()) { SfxMedium aClipboardMedium; aClipboardMedium.GetItemSet(); //generate initial itemset, not sure if necessary std::shared_ptr pMathFilter = SfxFilter::GetFilterByName(MATHML_XML); aClipboardMedium.SetFilter(pMathFilter); aClipboardMedium.setStreamToLoadFrom(xStrm, true /*bIsReadOnly*/); InsertFrom(aClipboardMedium); GetDoc()->UpdateText(); } } else { nId = SotClipboardFormatId::STRING; if (aDataHelper.HasFormat(nId)) { // In case of FORMAT_STRING no stream exists, need to generate one OUString aString; if (aDataHelper.GetString( nId, aString)) { // tdf#117091 force xml declaration to exist if (!aString.startsWith("\n" + aString; SfxMedium aClipboardMedium; aClipboardMedium.GetItemSet(); //generates initial itemset, not sure if necessary std::shared_ptr pMathFilter = SfxFilter::GetFilterByName(MATHML_XML); aClipboardMedium.SetFilter(pMathFilter); SvMemoryStream aStrm( const_cast(aString.getStr()), aString.getLength() * sizeof(sal_Unicode), StreamMode::READ); uno::Reference xStrm2( new ::utl::OInputStreamWrapper(aStrm) ); aClipboardMedium.setStreamToLoadFrom(xStrm2, true /*bIsReadOnly*/); InsertFrom(aClipboardMedium); GetDoc()->UpdateText(); } } } } } break; } case SID_NEXTERR: NextError(); if (pWin) pWin->GrabFocus(); break; case SID_PREVERR: PrevError(); if (pWin) pWin->GrabFocus(); break; case SID_NEXTMARK: if (pWin) { pWin->SelNextMark(); pWin->GrabFocus(); } break; case SID_PREVMARK: if (pWin) { pWin->SelPrevMark(); pWin->GrabFocus(); } break; case SID_TEXTSTATUS: { if (rReq.GetArgs() != nullptr) { const SfxStringItem& rItem = rReq.GetArgs()->Get(SID_TEXTSTATUS); SetStatusText(rItem.GetValue()); } break; } case SID_GETEDITTEXT: if (pWin && !pWin->GetText().isEmpty()) GetDoc()->SetText( pWin->GetText() ); break; case SID_ATTR_ZOOM: { if ( !GetViewFrame().GetFrame().IsInPlace() ) { const SfxItemSet *pSet = rReq.GetArgs(); if ( pSet ) { ZoomByItemSet(pSet); } else { SfxItemSetFixed aSet( SmDocShell::GetPool() ); aSet.Put( SvxZoomItem( SvxZoomType::PERCENT, mxGraphicWindow->GetZoom())); SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); ScopedVclPtr xDlg(pFact->CreateSvxZoomDialog(GetViewFrame().GetWindow().GetFrameWeld(), aSet)); xDlg->SetLimits( MINZOOM, MAXZOOM ); if (xDlg->Execute() != RET_CANCEL) ZoomByItemSet(xDlg->GetOutputItemSet()); } } } break; case SID_ATTR_ZOOMSLIDER: { const SfxItemSet *pArgs = rReq.GetArgs(); const SfxPoolItem* pItem; if ( pArgs && SfxItemState::SET == pArgs->GetItemState(SID_ATTR_ZOOMSLIDER, true, &pItem ) ) { const sal_uInt16 nCurrentZoom = static_cast(pItem)->GetValue(); mxGraphicWindow->SetZoom(nCurrentZoom); } } break; case SID_ELEMENTSDOCKINGWINDOW: { // First make sure that the sidebar is visible GetViewFrame().ShowChildWindow(SID_SIDEBAR); sfx2::sidebar::Sidebar::TogglePanel(u"MathElementsPanel", GetViewFrame().GetFrame().GetFrameInterface()); GetViewFrame().GetBindings().Invalidate( SID_ELEMENTSDOCKINGWINDOW ); rReq.Ignore (); } break; case SID_CMDBOXWINDOW: { GetViewFrame().ToggleChildWindow(SID_CMDBOXWINDOW); GetViewFrame().GetBindings().Invalidate(SID_CMDBOXWINDOW); } break; case SID_UNICODE_NOTATION_TOGGLE: { EditEngine* pEditEngine = nullptr; if( pWin ) pEditEngine = pWin->GetEditEngine(); EditView* pEditView = nullptr; if( pEditEngine ) pEditView = pEditEngine->GetView(); if( pEditView ) { const OUString sInput = pEditView->GetSurroundingText(); ESelection aSel( pWin->GetSelection() ); if (aSel.start.nIndex > aSel.end.nIndex) aSel.end.nIndex = aSel.start.nIndex; //calculate a valid end-position by reading logical characters sal_Int32 nUtf16Pos=0; while ((nUtf16Pos < sInput.getLength()) && (nUtf16Pos < aSel.end.nIndex)) { sInput.iterateCodePoints(&nUtf16Pos); if (nUtf16Pos > aSel.end.nIndex) aSel.end.nIndex = nUtf16Pos; } ToggleUnicodeCodepoint aToggle; while( nUtf16Pos && aToggle.AllowMoreInput( sInput[nUtf16Pos-1]) ) --nUtf16Pos; const OUString sReplacement = aToggle.ReplacementString(); if( !sReplacement.isEmpty() ) { pEditView->SetSelection( aSel ); pEditEngine->UndoActionStart(EDITUNDO_REPLACEALL); aSel.start.nIndex = aSel.end.nIndex - aToggle.StringToReplace().getLength(); pWin->SetSelection( aSel ); pEditView->InsertText( sReplacement, true ); pEditEngine->UndoActionEnd(); pWin->Flush(); } } } break; case SID_SYMBOLS_CATALOGUE: { SmModule* pp = SmModule::get(); // get device used to retrieve the FontList SmDocShell *pDoc = GetDoc(); OutputDevice *pDev = pDoc->GetPrinter(); if (!pDev || pDev->GetFontFaceCollectionCount() == 0) pDev = &pp->GetDefaultVirtualDev(); SAL_WARN_IF( !pDev, "starmath", "device for font list missing" ); SmSymbolDialog aDialog(pWin ? pWin->GetFrameWeld() : nullptr, pDev, pp->GetSymbolManager(), *this); aDialog.run(); } break; case SID_CHARMAP: { const SfxItemSet* pArgs = rReq.GetArgs(); const SfxStringItem* pItem = nullptr; if (pArgs && SfxItemState::SET == pArgs->GetItemState(SID_CHARMAP, true, &pItem)) { if (IsInlineEditEnabled()) GetDoc()->GetCursor().InsertText(pItem->GetValue()); else if (pWin) pWin->InsertText(pItem->GetValue()); break; } SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); SfxAllItemSet aSet(GetViewFrame().GetObjectShell()->GetPool()); aSet.Put(SfxBoolItem(FN_PARAM_1, false)); aSet.Put(SfxStringItem(SID_FONT_NAME, GetDoc()->GetFormat().GetFont(FNT_VARIABLE).GetFamilyName())); VclPtr pDialog( pFact->CreateCharMapDialog(pWin ? pWin->GetFrameWeld() : nullptr, aSet, GetViewFrame().GetFrame().GetFrameInterface())); pDialog->StartExecuteAsync( [pDialog] (sal_Int32 /*nResult*/)->void { pDialog->disposeOnce(); } ); } break; case SID_ATTR_PARA_LEFT_TO_RIGHT: case SID_ATTR_PARA_RIGHT_TO_LEFT: { bool bRTL = rReq.GetSlot() == SID_ATTR_PARA_RIGHT_TO_LEFT; GetDoc()->SetRightToLeft(bRTL); GetGraphicWindow().GetGraphicWidget().GetOutputDevice().EnableRTL(bRTL); GetViewFrame().GetBindings().Invalidate(bRTL ? SID_ATTR_PARA_LEFT_TO_RIGHT : SID_ATTR_PARA_RIGHT_TO_LEFT); } break; case SID_SAVE_FORMULA: { OUString aName = "My Formula 1"; OUString aDesc(SmResId(STR_USER_DEFINED_FORMULA)); SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); ScopedVclPtr pDlg( pFact->CreateSvxNameDialog(GetFrameWeld(), aName, aDesc)); if (pDlg->Execute() == RET_OK) { aName = pDlg->GetName(); if (SmModule::get()->GetConfig()->HasUserDefinedFormula(aName)) { std::unique_ptr xQuery(Application::CreateMessageDialog( GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, SmResId(STR_USER_DEFINED_FORMULA_EXISTS).replaceAll("%1", aName))); if (xQuery->run() == RET_NO) break; } if (SmEditWindow* pEditWin = GetEditWindow()) SmModule::get()->GetConfig()->SaveUserDefinedFormula(aName, pEditWin->GetText()); // Show the Elements sidebar with the "User-defined" entry selected GetViewFrame().ShowChildWindow(SID_SIDEBAR); sfx2::sidebar::Sidebar::ShowPanel(u"MathElementsPanel", GetViewFrame().GetFrame().GetFrameInterface()); GetViewFrame().GetBindings().Invalidate( SID_ELEMENTSDOCKINGWINDOW ); Broadcast(SfxHint(SfxHintId::SmNewUserFormula)); rReq.Ignore (); } pDlg.disposeAndClear(); } break; } rReq.Done(); } void SmViewShell::GetState(SfxItemSet &rSet) { SfxWhichIter aIter(rSet); SmEditWindow *pEditWin = GetEditWindow(); for (sal_uInt16 nWh = aIter.FirstWhich(); nWh != 0; nWh = aIter.NextWhich()) { switch (nWh) { case SID_CUT: case SID_COPY: case SID_DELETE: if (IsInlineEditEnabled()) { if (!GetDoc()->GetCursor().HasSelection()) rSet.DisableItem(nWh); } else if (! pEditWin || ! pEditWin->IsSelected()) rSet.DisableItem(nWh); break; case SID_PASTE: if (pEditWin) { TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromClipboard( pEditWin->GetClipboard()) ); mbPasteState = aDataHelper.GetTransferable().is() && ( aDataHelper.HasFormat( SotClipboardFormatId::STRING ) || aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ ) || (aDataHelper.HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) && aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE ))); } if( !mbPasteState ) rSet.DisableItem( nWh ); break; case SID_ATTR_ZOOM: rSet.Put(SvxZoomItem( SvxZoomType::PERCENT, mxGraphicWindow->GetZoom())); [[fallthrough]]; case SID_ZOOMIN: case SID_ZOOMOUT: case SID_ZOOM_OPTIMAL: if ( GetViewFrame().GetFrame().IsInPlace() ) rSet.DisableItem( nWh ); break; case SID_ATTR_ZOOMSLIDER : { const sal_uInt16 nCurrentZoom = mxGraphicWindow->GetZoom(); SvxZoomSliderItem aZoomSliderItem( nCurrentZoom, MINZOOM, MAXZOOM ); aZoomSliderItem.AddSnappingPoint( 100 ); rSet.Put( aZoomSliderItem ); } break; case SID_NEXTERR: case SID_PREVERR: case SID_NEXTMARK: case SID_PREVMARK: case SID_DRAW: case SID_SELECT: if (! pEditWin || pEditWin->IsEmpty()) rSet.DisableItem(nWh); break; case SID_TEXTSTATUS: { rSet.Put(SfxStringItem(nWh, maStatusText)); } break; case SID_FORMULACURSOR: { if (IsInlineEditEnabled()) rSet.DisableItem(nWh); else rSet.Put(SfxBoolItem(nWh, SmModule::get()->GetConfig()->IsShowFormulaCursor())); } break; case SID_ELEMENTSDOCKINGWINDOW: { const bool bState = sfx2::sidebar::Sidebar::IsPanelVisible( u"MathElementsPanel", GetViewFrame().GetFrame().GetFrameInterface()); rSet.Put(SfxBoolItem(SID_ELEMENTSDOCKINGWINDOW, bState)); } break; case SID_CMDBOXWINDOW: { bool bState = false; auto pCmdWin = GetViewFrame().GetChildWindow(SID_CMDBOXWINDOW); if (pCmdWin) bState = pCmdWin->IsVisible(); rSet.Put(SfxBoolItem(SID_CMDBOXWINDOW, bState)); } break; case SID_ATTR_PARA_LEFT_TO_RIGHT: rSet.Put(SfxBoolItem(nWh, !GetDoc()->GetFormat().IsRightToLeft())); break; case SID_ATTR_PARA_RIGHT_TO_LEFT: rSet.Put(SfxBoolItem(nWh, GetDoc()->GetFormat().IsRightToLeft())); break; case SID_SAVE_FORMULA: if (!pEditWin || pEditWin->IsEmpty()) rSet.DisableItem(nWh); break; } } } namespace { css::uno::Reference getSidebarFromModel(const css::uno::Reference& xModel) { css::uno::Reference xChild(xModel, css::uno::UNO_QUERY); if (!xChild.is()) return nullptr; css::uno::Reference xParent(xChild->getParent(), css::uno::UNO_QUERY); if (!xParent.is()) return nullptr; css::uno::Reference xController(xParent->getCurrentController(), css::uno::UNO_QUERY); if (!xController.is()) return nullptr; css::uno::Reference xSidebarProvider = xController->getSidebar(); if (!xSidebarProvider.is()) return nullptr; return xSidebarProvider->getSidebar(); } class SmController : public SfxBaseController { public: SmController(SfxViewShell& rViewShell) : SfxBaseController(&rViewShell) , mpSelectionChangeHandler(new svx::sidebar::SelectionChangeHandler( GetContextName, this, vcl::EnumContext::Context::Math)) { rViewShell.SetContextName(GetContextName()); } // No need to call mpSelectionChangeHandler->Disconnect() unless SmController implements XSelectionSupplier // ~SmController() { mpSelectionChangeHandler->Disconnect(); } // css::frame::XController void SAL_CALL attachFrame(const css::uno::Reference& xFrame) override { SfxBaseController::attachFrame(xFrame); if (comphelper::LibreOfficeKit::isActive()) { CopyLokViewCallbackFromFrameCreator(); // In lok mode, DocumentHolder::ShowUI is not called on OLE in-place activation, // because respective code is skipped in OCommonEmbeddedObject::SwitchStateTo_Impl, // so sidebar controller does not get registered properly; do it here if (auto xSidebar = getSidebarFromModel(getModel())) { auto pSidebar = dynamic_cast(xSidebar.get()); assert(pSidebar); pSidebar->registerSidebarForFrame(this); pSidebar->updateModel(getModel()); } } // No need to call mpSelectionChangeHandler->Connect() unless SmController implements XSelectionSupplier mpSelectionChangeHandler->selectionChanged({}); // Installs the correct context } virtual void SAL_CALL dispose() override { if (comphelper::LibreOfficeKit::isActive()) if (auto pViewShell = GetViewShell_Impl()) pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, OString::boolean(false)); SfxBaseController::dispose(); } private: static OUString GetContextName() { return u"Math"_ustr; } // Static constant for now rtl::Reference mpSelectionChangeHandler; }; } SmViewShell::SmViewShell(SfxViewFrame& rFrame_, SfxViewShell *) : SfxViewShell(rFrame_, SfxViewShellFlags::HAS_PRINTOPTIONS) , mxGraphicWindow(VclPtr::Create(*this)) , maGraphicController(mxGraphicWindow->GetGraphicWidget(), SID_GRAPHIC_SM, rFrame_.GetBindings()) , mbPasteState(false) { SetStatusText(OUString()); SetWindow(mxGraphicWindow.get()); SfxShell::SetName(u"SmView"_ustr); SfxShell::SetUndoManager( &GetDoc()->GetEditEngine().GetUndoManager() ); SetController(new SmController(*this)); } SmViewShell::~SmViewShell() { //!! this view shell is not active anymore !! // Thus 'SmGetActiveView' will give a 0 pointer. // Thus we need to supply this view as argument if (SmEditWindow *pEditWin = GetEditWindow()) pEditWin->DeleteEditView(); mxGraphicWindow.disposeAndClear(); } void SmViewShell::Deactivate( bool bIsMDIActivate ) { if (SmEditWindow *pEdit = GetEditWindow()) pEdit->Flush(); SfxViewShell::Deactivate( bIsMDIActivate ); } void SmViewShell::Activate( bool bIsMDIActivate ) { SfxViewShell::Activate( bIsMDIActivate ); if (IsInlineEditEnabled()) { // In LOK, activate in-place editing GetGraphicWidget().GrabFocus(); } else if (SmEditWindow *pEdit = GetEditWindow()) { //! Since there is no way to be informed if a "drag and drop" //! event has taken place, we call SetText here in order to //! synchronize the GraphicWindow display with the text in the //! EditEngine. SmDocShell *pDoc = GetDoc(); pDoc->SetText( pDoc->GetEditEngine().GetText() ); if ( bIsMDIActivate ) pEdit->GrabFocus(); } } IMPL_LINK( SmViewShell, DialogClosedHdl, sfx2::FileDialogHelper*, _pFileDlg, void ) { assert(_pFileDlg && "SmViewShell::DialogClosedHdl(): no file dialog"); assert(mpDocInserter && "ScDocShell::DialogClosedHdl(): no document inserter"); if ( ERRCODE_NONE == _pFileDlg->GetError() ) { std::unique_ptr pMedium = mpDocInserter->CreateMedium(); if ( pMedium ) { if ( pMedium->IsStorage() ) Insert( *pMedium ); else InsertFrom( *pMedium ); pMedium.reset(); SmDocShell* pDoc = GetDoc(); pDoc->UpdateText(); pDoc->ArrangeFormula(); pDoc->Repaint(); // adjust window, repaint, increment ModifyCount,... GetViewFrame().GetBindings().Invalidate(SID_GRAPHIC_SM); } } mpRequest->SetReturnValue( SfxBoolItem( mpRequest->GetSlot(), true ) ); mpRequest->Done(); } void SmViewShell::Notify( SfxBroadcaster& , const SfxHint& rHint ) { switch( rHint.GetId() ) { case SfxHintId::ModeChanged: case SfxHintId::DocChanged: GetViewFrame().GetBindings().InvalidateAll(false); break; default: break; } } bool SmViewShell::IsInlineEditEnabled() { return comphelper::LibreOfficeKit::isActive() || SmModule::get()->GetConfig()->IsInlineEditEnable(); } void SmViewShell::StartMainHelp() { Help* pHelp = Application::GetHelp(); if (pHelp) pHelp->Start(HID_SMA_MAIN_HELP, GetViewFrame().GetFrameWeld()); } void SmViewShell::ZoomByItemSet(const SfxItemSet *pSet) { assert(pSet); const SvxZoomItem &rZoom = pSet->Get(SID_ATTR_ZOOM); switch( rZoom.GetType() ) { case SvxZoomType::PERCENT: mxGraphicWindow->SetZoom(sal::static_int_cast(rZoom.GetValue ())); break; case SvxZoomType::OPTIMAL: mxGraphicWindow->ZoomToFitInWindow(); break; case SvxZoomType::PAGEWIDTH: case SvxZoomType::WHOLEPAGE: { const MapMode aMap( SmMapUnit() ); SfxPrinter *pPrinter = GetPrinter( true ); tools::Rectangle OutputRect(Point(), pPrinter->GetOutputSize()); Size OutputSize(pPrinter->LogicToPixel(Size(OutputRect.GetWidth(), OutputRect.GetHeight()), aMap)); Size GraphicSize(pPrinter->LogicToPixel(GetDoc()->GetSize(), aMap)); if (GraphicSize.Width() <= 0 || GraphicSize.Height() <= 0) break; sal_uInt16 nZ = std::min(o3tl::convert(OutputSize.Width(), 100, GraphicSize.Width()), o3tl::convert(OutputSize.Height(), 100, GraphicSize.Height())); mxGraphicWindow->SetZoom(nZ); break; } default: break; } } std::optional SmViewShell::getLOKPayload(int nType, int nViewId) const { switch (nType) { case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: { OString sRectangle; if (const SmGraphicWidget& widget = GetGraphicWidget(); widget.IsCursorVisible()) { SmCursor& rCursor = GetDoc()->GetCursor(); OutputDevice& rOutDev = const_cast(widget).GetOutputDevice(); tools::Rectangle aCaret = rCursor.GetCaretRectangle(rOutDev); Point aFormulaDrawPos = widget.GetFormulaDrawPos(); aCaret.Move(aFormulaDrawPos.X(), aFormulaDrawPos.Y()); LokStarMathHelper helper(SfxViewShell::Current()); tools::Rectangle aBounds = helper.GetBoundingBox(); aCaret.Move(aBounds.Left(), aBounds.Top()); sRectangle = aCaret.toString(); } return SfxLokHelper::makeVisCursorInvalidation(nViewId, sRectangle, false, {}); } case LOK_CALLBACK_TEXT_SELECTION: { OString sRectangle; if (const SmGraphicWidget& widget = GetGraphicWidget(); widget.IsCursorVisible()) { SmCursor& rCursor = GetDoc()->GetCursor(); OutputDevice& rOutDev = const_cast(widget).GetOutputDevice(); tools::Rectangle aSelection = rCursor.GetSelectionRectangle(rOutDev); if (!aSelection.IsEmpty()) { Point aFormulaDrawPos = widget.GetFormulaDrawPos(); aSelection.Move(aFormulaDrawPos.X(), aFormulaDrawPos.Y()); LokStarMathHelper helper(SfxViewShell::Current()); tools::Rectangle aBounds = helper.GetBoundingBox(); aSelection.Move(aBounds.Left(), aBounds.Top()); sRectangle = aSelection.toString(); } } return sRectangle; } case LOK_CALLBACK_TEXT_SELECTION_START: case LOK_CALLBACK_TEXT_SELECTION_END: case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: case LOK_CALLBACK_TEXT_VIEW_SELECTION: return {}; } return SfxViewShell::getLOKPayload(nType, nViewId); // aborts } void SmViewShell::SendCaretToLOK() const { const int nViewId = sal_Int32(GetViewShellId()); if (const auto payload = getLOKPayload(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, nViewId)) { libreOfficeKitViewCallbackWithViewId(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, *payload, nViewId); } if (const auto payload = getLOKPayload(LOK_CALLBACK_TEXT_SELECTION, nViewId)) { libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, *payload); } } void SmViewShell::InvalidateSlots() { auto& rBind = GetViewFrame().GetBindings(); rBind.Invalidate(SID_COPY); rBind.Invalidate(SID_CUT); rBind.Invalidate(SID_DELETE); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */