/* -*- 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 <sal/config.h>
 
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/frame/XFramesSupplier.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/container/XChild.hpp>
 
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/servicehelper.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/string.hxx>
#include <i18nutil/unicode.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <officecfg/Office/Common.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/docfilt.hxx>
#include <sfx2/docinsert.hxx>
#include <sfx2/filedlghelper.hxx>
#include <sfx2/infobar.hxx>
#include <sfx2/lokcomponenthelpers.hxx>
#include <sfx2/lokhelper.hxx>
#include <sfx2/msg.hxx>
#include <sfx2/objface.hxx>
#include <sfx2/printer.hxx>
#include <sfx2/request.hxx>
#include <sfx2/sfxbasecontroller.hxx>
#include <sfx2/sidebar/Sidebar.hxx>
#include <sfx2/sidebar/SidebarChildWindow.hxx>
#include <sfx2/sidebar/SidebarController.hxx>
#include <sfx2/viewfac.hxx>
#include <svl/eitem.hxx>
#include <svl/itemset.hxx>
#include <svl/poolitem.hxx>
#include <svl/stritem.hxx>
#include <svl/voiditem.hxx>
#include <vcl/transfer.hxx>
#include <svtools/colorcfg.hxx>
#include <svl/whiter.hxx>
#include <svx/sidebar/SelectionChangeHandler.hxx>
#include <svx/zoomslideritem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/editview.hxx>
#include <editeng/editund2.hxx>
#include <svx/svxdlg.hxx>
#include <sfx2/zoomitem.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/help.hxx>
#include <vcl/settings.hxx>
#include <vcl/virdev.hxx>
#include <sal/log.hxx>
#include <tools/svborder.hxx>
#include <o3tl/string_view.hxx>
#include <o3tl/temporary.hxx>
 
#include <unotools/streamwrap.hxx>
 
#include <unomodel.hxx>
#include <view.hxx>
#include <cfgitem.hxx>
#include <dialog.hxx>
#include <document.hxx>
#include <starmath.hrc>
#include <strings.hrc>
#include <smmod.hxx>
#include <mathmlimport.hxx>
#include <cursor.hxx>
#include "accessibility.hxx"
#include <ElementsDockingWindow.hxx>
#include <helpids.h>
 
// 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 <smslots.hxx>
 
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(SM_MOD()->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;
 
    SmModule *pp = SM_MOD();
 
    if (IsCursorVisible())
        ShowCursor(false);      // clean up remainings of old cursor
    aCursorRect = rRect;
    if (pp->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<sal_uInt16>(nRow), nCol);
 
            SmModule *pp = SM_MOD();
            if (pFound && pp->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<const SfxStringItem*>( 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<SmViewShell*>( 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<container::XChild> 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<SmCmdBoxWindow> 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<sal_uInt16>(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 *pp = SM_MOD();
        pp->GetConfig()->ItemSetToConfig(pNewPrinter->GetOptions());
    }
    return 0;
}
 
bool SmViewShell::HasPrintOptionsPage() const
{
    return true;
}
 
std::unique_ptr<SfxTabPage> SmViewShell::CreatePrintOptionsPage(weld::Container* pPage, weld::DialogController* pController,
                                                       const SfxItemSet &rOptions)
{
    return SmPrintOptionsTabPage::Create(pPage, pController, rOptions);
}
 
SmEditWindow *SmViewShell::GetEditWindow()
{
    SmCmdBoxWrapper* pWrapper = static_cast<SmCmdBoxWrapper*>(
                                    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 <embed::XStorage> xStorage = rMedium.GetStorage();
    if (xStorage.is() && xStorage->getElementNames().hasElements())
    {
        if (xStorage->hasByName(u"content.xml"_ustr))
        {
            // is this a fabulous math package ?
            rtl::Reference<SmModel> xModel(dynamic_cast<SmModel*>(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<SmModel> xModel(dynamic_cast<SmModel*>(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:
        {
            SmModule *pp = SM_MOD();
 
            const SfxItemSet  *pArgs = rReq.GetArgs();
            const SfxPoolItem *pItem;
 
            bool  bVal;
            if ( pArgs &&
                 SfxItemState::SET == pArgs->GetItemState( SID_FORMULACURSOR, false, &pItem))
                bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue();
            else
                bVal = !pp->GetConfig()->IsShowFormulaCursor();
 
            pp->GetConfig()->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<TransferableHelper*>(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<const SfxFilter> 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("<?xml"))
                                    aString = "<?xml version=\"1.0\"?>\n" + aString;
 
                                SfxMedium aClipboardMedium;
                                aClipboardMedium.GetItemSet(); //generates initial itemset, not sure if necessary
                                std::shared_ptr<const SfxFilter> pMathFilter =
                                    SfxFilter::GetFilterByName(MATHML_XML);
                                aClipboardMedium.SetFilter(pMathFilter);
 
                                SvMemoryStream aStrm( const_cast<sal_Unicode *>(aString.getStr()), aString.getLength() * sizeof(sal_Unicode), StreamMode::READ);
                                uno::Reference<io::XInputStream> 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<SID_ATTR_ZOOM, SID_ATTR_ZOOM> aSet( SmDocShell::GetPool() );
                    aSet.Put( SvxZoomItem( SvxZoomType::PERCENT, mxGraphicWindow->GetZoom()));
                    SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
                    ScopedVclPtr<AbstractSvxZoomDialog> 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<const SvxZoomSliderItem *>(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.nStartPos > aSel.nEndPos )
                    aSel.nEndPos = aSel.nStartPos;
 
                //calculate a valid end-position by reading logical characters
                sal_Int32 nUtf16Pos=0;
                while( (nUtf16Pos < sInput.getLength()) && (nUtf16Pos < aSel.nEndPos) )
                {
                    sInput.iterateCodePoints(&nUtf16Pos);
                    if( nUtf16Pos > aSel.nEndPos )
                        aSel.nEndPos = 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.nStartPos = aSel.nEndPos - aToggle.StringToReplace().getLength();
                    pWin->SetSelection( aSel );
                    pEditView->InsertText( sReplacement, true );
                    pEditEngine->UndoActionEnd();
                    pWin->Flush();
                }
            }
        }
        break;
 
        case SID_SYMBOLS_CATALOGUE:
        {
 
            // get device used to retrieve the FontList
            SmDocShell *pDoc = GetDoc();
            OutputDevice *pDev = pDoc->GetPrinter();
            if (!pDev || pDev->GetFontFaceCollectionCount() == 0)
                pDev = &SM_MOD()->GetDefaultVirtualDev();
            SAL_WARN_IF( !pDev, "starmath", "device for font list missing" );
 
            SmModule *pp = SM_MOD();
            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<SfxAbstractDialog> 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;
    }
    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, SM_MOD()->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;
        }
    }
}
 
namespace
{
css::uno::Reference<css::ui::XSidebar>
getSidebarFromModel(const css::uno::Reference<css::frame::XModel>& xModel)
{
    css::uno::Reference<css::container::XChild> xChild(xModel, css::uno::UNO_QUERY);
    if (!xChild.is())
        return nullptr;
    css::uno::Reference<css::frame::XModel> xParent(xChild->getParent(), css::uno::UNO_QUERY);
    if (!xParent.is())
        return nullptr;
    css::uno::Reference<css::frame::XController2> xController(xParent->getCurrentController(),
                                                              css::uno::UNO_QUERY);
    if (!xController.is())
        return nullptr;
    css::uno::Reference<css::ui::XSidebarProvider> 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<css::frame::XFrame>& 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<sfx2::sidebar::SidebarController*>(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<svx::sidebar::SelectionChangeHandler> mpSelectionChangeHandler;
};
}
 
SmViewShell::SmViewShell(SfxViewFrame& rFrame_, SfxViewShell *)
    : SfxViewShell(rFrame_, SfxViewShellFlags::HAS_PRINTOPTIONS)
    , mxGraphicWindow(VclPtr<SmGraphicWindow>::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<SfxMedium> 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()
           || SM_MOD()->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<sal_uInt16>(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<OString> 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<SmGraphicWidget&>(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<SmGraphicWidget&>(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: */

V530 The return value of function 'GetItemSet' is required to be utilized.

V530 The return value of function 'GetItemSet' is required to be utilized.

V522 There might be dereferencing of a potential null pointer 'pView'.