include/vcl/jsdialog/executor.hxx | 24 +++++++++++++++++++ include/vcl/weld.hxx | 2 + sc/source/ui/app/inputhdl.cxx | 44 ++++++++++++++++++++++++++++++++++- sc/source/ui/app/inputwin.cxx | 28 ++++++++++++++++++++++ sc/source/ui/inc/inputhdl.hxx | 2 + vcl/inc/jsdialog/jsdialogbuilder.hxx | 30 ++++++++++++++--------- vcl/jsdialog/executor.cxx | 42 +++++++++++++++++++++++++++++++++ vcl/jsdialog/jsdialogbuilder.cxx | 19 ++++++++------- 8 files changed, 170 insertions(+), 21 deletions(-)
New commits: commit c257a5d0ec204d926239e2f1b2bcc50d511aea2b Author: Szymon Kłos <[email protected]> AuthorDate: Thu Apr 14 12:09:38 2022 +0200 Commit: Szymon Kłos <[email protected]> CommitDate: Sat Apr 23 08:31:18 2022 +0200 jsdialog: handle formulabar as textinput mostly boilerplate code jsdialog changes: - added force parameter to sendAction - added support for key press/release and command events - moved ActionDataMap to jsdialog namespace for sharing formulabar changes: - added calls to send jsdialog messages with formula - added cursor moving support - on command event Change-Id: I714715133901941ba0758655e2d5907a3bae79f2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133010 Reviewed-by: Mert Tumer <[email protected]> Tested-by: Szymon Kłos <[email protected]> diff --git a/include/vcl/jsdialog/executor.hxx b/include/vcl/jsdialog/executor.hxx index 0f8fb821e2bd..6e388a6a3f79 100644 --- a/include/vcl/jsdialog/executor.hxx +++ b/include/vcl/jsdialog/executor.hxx @@ -12,6 +12,7 @@ #include <vcl/dllapi.h> #include <vcl/uitest/uiobject.hxx> #include <vcl/weld.hxx> +#include <unordered_map> class LOKTrigger { @@ -56,13 +57,36 @@ public: } static void trigger_closed(weld::Popover& rPopover) { rPopover.popdown(); } + + static void trigger_key_press(weld::Widget& rWidget, const KeyEvent& rEvent) + { + rWidget.m_aKeyPressHdl.Call(rEvent); + } + + static void trigger_key_release(weld::Widget& rWidget, const KeyEvent& rEvent) + { + rWidget.m_aKeyReleaseHdl.Call(rEvent); + } + + static void command(weld::DrawingArea& rArea, const CommandEvent& rCmd) + { + rArea.m_aCommandHdl.Call(rCmd); + } }; namespace jsdialog { +// type used to store key-value pairs to put in the generated messages +typedef std::unordered_map<std::string, OUString> ActionDataMap; + +/// execute action on a widget VCL_DLLPUBLIC bool ExecuteAction(const std::string& nWindowId, const OString& rWidget, StringMap& rData); +/// send full update message to the client VCL_DLLPUBLIC void SendFullUpdate(const std::string& nWindowId, const OString& rWidget); +/// send action message to the client +VCL_DLLPUBLIC void SendAction(const std::string& nWindowId, const OString& rWidget, + std::unique_ptr<ActionDataMap> pData); VCL_DLLPUBLIC StringMap jsonToStringMap(const char* pJSON); }; diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx index 561baaaa256b..5fab9b459a8b 100644 --- a/include/vcl/weld.hxx +++ b/include/vcl/weld.hxx @@ -79,6 +79,8 @@ class DialogController; class VCL_DLLPUBLIC Widget { + friend class ::LOKTrigger; + protected: Link<Widget&, void> m_aFocusInHdl; Link<Widget&, void> m_aFocusOutHdl; diff --git a/sc/source/ui/app/inputhdl.cxx b/sc/source/ui/app/inputhdl.cxx index 30f18a075c7c..72852aa79b58 100644 --- a/sc/source/ui/app/inputhdl.cxx +++ b/sc/source/ui/app/inputhdl.cxx @@ -50,6 +50,7 @@ #include <unotools/localedatawrapper.hxx> #include <unotools/charclass.hxx> #include <vcl/help.hxx> +#include <vcl/jsdialog/executor.hxx> #include <vcl/commandevent.hxx> #include <vcl/cursor.hxx> #include <vcl/settings.hxx> @@ -1793,6 +1794,23 @@ void ScInputHandler::LOKPasteFunctionData(const OUString& rFunctionName) } } +void ScInputHandler::LOKSendFormulabarUpdate(const SfxViewShell* pActiveViewSh, + const OUString& rText, + const ESelection& rSelection) +{ + OUString aSelection = + OUString::number(rSelection.nStartPos) + ";" + OUString::number(rSelection.nEndPos); + + std::unique_ptr<jsdialog::ActionDataMap> pData = std::make_unique<jsdialog::ActionDataMap>(); + (*pData)["action_type"] = "setText"; + (*pData)["text"] = rText; + (*pData)["selection"] = aSelection; + + sal_uInt64 nCurrentShellId = reinterpret_cast<sal_uInt64>(pActiveViewSh); + std::string sWindowId = std::to_string(nCurrentShellId) + "formulabar"; + jsdialog::SendAction(sWindowId, "sc_input_window", std::move(pData)); +} + // Calculate selection and display as tip help static OUString lcl_Calculate( const OUString& rFormula, ScDocument& rDoc, const ScAddress &rPos ) { @@ -2720,7 +2738,10 @@ void ScInputHandler::DataChanged( bool bFromTopNotify, bool bSetModified ) if (comphelper::LibreOfficeKit::isActive()) { if (pActiveViewSh) + { + // TODO: deprecated? pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_FORMULA, aText.toUtf8().getStr()); + } } } @@ -2733,6 +2754,7 @@ void ScInputHandler::DataChanged( bool bFromTopNotify, bool bSetModified ) mpEditEngine->QuickFormatDoc(); EditView* pActiveView = pTopView ? pTopView : pTableView; + ESelection aSel; if (pActiveView && pActiveViewSh) { ScViewData& rViewData = pActiveViewSh->GetViewData(); @@ -2741,7 +2763,7 @@ void ScInputHandler::DataChanged( bool bFromTopNotify, bool bSetModified ) if (!bNeedGrow) { // Cursor before the end? - ESelection aSel = pActiveView->GetSelection(); + aSel = pActiveView->GetSelection(); aSel.Adjust(); bNeedGrow = ( aSel.nEndPos != mpEditEngine->GetTextLen(aSel.nEndPara) ); } @@ -2757,6 +2779,13 @@ void ScInputHandler::DataChanged( bool bFromTopNotify, bool bSetModified ) } } + if (comphelper::LibreOfficeKit::isActive() && pActiveViewSh && pInputWin) + { + ScInputHandler::LOKSendFormulabarUpdate(pActiveViewSh, + ScEditUtil::GetMultilineString(*mpEditEngine), + aSel); + } + UpdateFormulaMode(); bTextValid = false; // Changes only in the EditEngine bInOwnChange = false; @@ -4209,7 +4238,13 @@ void ScInputHandler::NotifyChange( const ScInputHdlState* pState, pInputWin->SetTextString(aString); if (comphelper::LibreOfficeKit::isActive() && pActiveViewSh) + { + EditView* pActiveView = pTopView ? pTopView : pTableView; + ESelection aSel = pActiveView ? pActiveView->GetSelection() : ESelection(); + ScInputHandler::LOKSendFormulabarUpdate(pActiveViewSh, aString, aSel); + // TODO: deprecated? pActiveViewSh->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_FORMULA, aString.toUtf8().getStr()); + } } if ( pInputWin || comphelper::LibreOfficeKit::isActive()) // Named range input @@ -4365,6 +4400,13 @@ void ScInputHandler::InputSelection( const EditView* pView ) // When the selection is changed manually, stop overwriting parentheses ResetAutoPar(); + + if (comphelper::LibreOfficeKit::isActive() && pActiveViewSh) + { + EditView* pActiveView = pTopView ? pTopView : pTableView; + ESelection aSel = pActiveView ? pActiveView->GetSelection() : ESelection(); + ScInputHandler::LOKSendFormulabarUpdate(pActiveViewSh, GetEditString(), aSel); + } } void ScInputHandler::InputChanged( const EditView* pView, bool bFromNotify ) diff --git a/sc/source/ui/app/inputwin.cxx b/sc/source/ui/app/inputwin.cxx index 10af93751631..cfad09bd4484 100644 --- a/sc/source/ui/app/inputwin.cxx +++ b/sc/source/ui/app/inputwin.cxx @@ -1085,6 +1085,8 @@ ScTextWndGroup::ScTextWndGroup(ScInputBarGroup& rParent, ScTabViewShell* pViewSh , mrParent(rParent) { mxScrollWin->connect_vadjustment_changed(LINK(this, ScTextWndGroup, Impl_ScrollHdl)); + if (comphelper::LibreOfficeKit::isActive()) + ScInputHandler::LOKSendFormulabarUpdate(SfxViewShell::Current(), "", ESelection()); } Point ScTextWndGroup::GetCursorScreenPixelPos(bool bBelow) @@ -1704,6 +1706,26 @@ bool ScTextWnd::Command( const CommandEvent& rCEvt ) SC_MOD()->InputChanged( m_xEditView.get() ); } + if ( comphelper::LibreOfficeKit::isActive() && nCommand == CommandEventId::CursorPos ) + { + // LOK uses this to setup caret position because drawingarea is replaced + // with text input field, it sends logical caret position (start, end) not pixels + + StartEditEngine(); + TextGrabFocus(); + + if (!m_xEditView) + return true; + + Point aSelectionStartEnd = rCEvt.GetMousePosPixel(); + m_xEditView->SetSelection(ESelection(0, aSelectionStartEnd.X(), + 0, aSelectionStartEnd.Y())); + + SC_MOD()->InputSelection( m_xEditView.get() ); + + bConsumed = true; + } + bInputMode = false; return bConsumed; @@ -1859,6 +1881,12 @@ static sal_Int32 findFirstNonMatchingChar(const OUString& rStr1, const OUString& void ScTextWnd::SetTextString( const OUString& rNewString ) { + if (comphelper::LibreOfficeKit::isActive()) + { + ESelection aSel = m_xEditView ? m_xEditView->GetSelection() : ESelection(); + ScInputHandler::LOKSendFormulabarUpdate(SfxViewShell::Current(), rNewString, aSel); + } + // Ideally it would be best to create on demand the EditEngine/EditView here, but... for // the initialisation scenario where a cell is first clicked on we end up with the text in the // inputbar window scrolled to the bottom if we do that here ( because the tableview and topview diff --git a/sc/source/ui/inc/inputhdl.hxx b/sc/source/ui/inc/inputhdl.hxx index f66688a257ca..1450f26c8176 100644 --- a/sc/source/ui/inc/inputhdl.hxx +++ b/sc/source/ui/inc/inputhdl.hxx @@ -298,6 +298,8 @@ public: tools::Long nTab, const Color& rColor ); void LOKPasteFunctionData(const OUString& rFunctionName); + static void LOKSendFormulabarUpdate(const SfxViewShell* pActiveViewSh, + const OUString& rText, const ESelection& rSelection); }; // ScInputHdlState diff --git a/vcl/inc/jsdialog/jsdialogbuilder.hxx b/vcl/inc/jsdialog/jsdialogbuilder.hxx index 403b08a99b42..7ee18baef987 100644 --- a/vcl/inc/jsdialog/jsdialogbuilder.hxx +++ b/vcl/inc/jsdialog/jsdialogbuilder.hxx @@ -28,7 +28,6 @@ #include <deque> #include <list> #include <mutex> -#include <unordered_map> #define ACTION_TYPE "action_type" #define PARENT_ID "parent_id" @@ -42,7 +41,6 @@ class SvTabListBox; class IconView; typedef std::map<OString, weld::Widget*> WidgetMap; -typedef std::unordered_map<std::string, OUString> ActionDataMap; namespace jsdialog { @@ -63,7 +61,7 @@ class JSDialogMessageInfo public: jsdialog::MessageType m_eType; VclPtr<vcl::Window> m_pWindow; - std::unique_ptr<ActionDataMap> m_pData; + std::unique_ptr<jsdialog::ActionDataMap> m_pData; private: void copy(const JSDialogMessageInfo& rInfo) @@ -72,14 +70,15 @@ private: this->m_pWindow = rInfo.m_pWindow; if (rInfo.m_pData) { - std::unique_ptr<ActionDataMap> pData(new ActionDataMap(*rInfo.m_pData)); + std::unique_ptr<jsdialog::ActionDataMap> pData( + new jsdialog::ActionDataMap(*rInfo.m_pData)); this->m_pData = std::move(pData); } } public: JSDialogMessageInfo(jsdialog::MessageType eType, VclPtr<vcl::Window> pWindow, - std::unique_ptr<ActionDataMap> pData) + std::unique_ptr<jsdialog::ActionDataMap> pData) : m_eType(eType) , m_pWindow(pWindow) , m_pData(std::move(pData)) @@ -120,7 +119,7 @@ public: void clearQueue(); void forceUpdate(); void sendMessage(jsdialog::MessageType eType, VclPtr<vcl::Window> pWindow, - std::unique_ptr<ActionDataMap> pData = nullptr); + std::unique_ptr<jsdialog::ActionDataMap> pData = nullptr); private: void send(tools::JsonWriter& aJsonWriter); @@ -128,7 +127,8 @@ private: std::unique_ptr<tools::JsonWriter> generateWidgetUpdate(VclPtr<vcl::Window> pWindow) const; std::unique_ptr<tools::JsonWriter> generateCloseMessage() const; std::unique_ptr<tools::JsonWriter> - generateActionMessage(VclPtr<vcl::Window> pWindow, std::unique_ptr<ActionDataMap> pData) const; + generateActionMessage(VclPtr<vcl::Window> pWindow, + std::unique_ptr<jsdialog::ActionDataMap> pData) const; std::unique_ptr<tools::JsonWriter> generatePopupMessage(VclPtr<vcl::Window> pWindow, OUString sParentId, OUString sCloseId) const; std::unique_ptr<tools::JsonWriter> generateClosePopupMessage(OUString sWindowId) const; @@ -158,7 +158,8 @@ public: virtual void sendFullUpdate(bool bForce = false); void sendClose(); void sendUpdate(VclPtr<vcl::Window> pWindow, bool bForce = false); - virtual void sendAction(VclPtr<vcl::Window> pWindow, std::unique_ptr<ActionDataMap> pData); + virtual void sendAction(VclPtr<vcl::Window> pWindow, + std::unique_ptr<jsdialog::ActionDataMap> pData); virtual void sendPopup(VclPtr<vcl::Window> pWindow, OUString sParentId, OUString sCloseId); virtual void sendClosePopup(vcl::LOKWindowId nWindowId); void flush() { mpIdleNotify->Invoke(); } @@ -222,6 +223,9 @@ class JSInstanceBuilder final : public SalInstanceBuilder, public JSDialogSender const OString& rWidget, StringMap& rData); friend VCL_DLLPUBLIC void jsdialog::SendFullUpdate(const std::string& nWindowId, const OString& rWidget); + friend VCL_DLLPUBLIC void jsdialog::SendAction(const std::string& nWindowId, + const OString& rWidget, + std::unique_ptr<jsdialog::ActionDataMap> pData); static std::map<std::string, WidgetMap>& GetLOKWeldWidgetsMap(); static void InsertWindowToMap(const std::string& nWindowId); @@ -318,7 +322,7 @@ public: virtual void sendFullUpdate(bool bForce = false) = 0; - virtual void sendAction(std::unique_ptr<ActionDataMap> pData) = 0; + virtual void sendAction(std::unique_ptr<jsdialog::ActionDataMap> pData) = 0; virtual void sendPopup(vcl::Window* pPopup, OUString sParentId, OUString sCloseId) = 0; @@ -359,7 +363,8 @@ public: BaseInstanceClass::show(); if (!bWasVisible) { - std::unique_ptr<ActionDataMap> pMap = std::make_unique<ActionDataMap>(); + std::unique_ptr<jsdialog::ActionDataMap> pMap + = std::make_unique<jsdialog::ActionDataMap>(); (*pMap)[ACTION_TYPE] = "show"; sendAction(std::move(pMap)); } @@ -371,7 +376,8 @@ public: BaseInstanceClass::hide(); if (bWasVisible) { - std::unique_ptr<ActionDataMap> pMap = std::make_unique<ActionDataMap>(); + std::unique_ptr<jsdialog::ActionDataMap> pMap + = std::make_unique<jsdialog::ActionDataMap>(); (*pMap)[ACTION_TYPE] = "hide"; sendAction(std::move(pMap)); } @@ -425,7 +431,7 @@ public: m_pSender->sendFullUpdate(bForce); } - virtual void sendAction(std::unique_ptr<ActionDataMap> pData) override + virtual void sendAction(std::unique_ptr<jsdialog::ActionDataMap> pData) override { if (!m_bIsFreezed && m_pSender && pData) m_pSender->sendAction(BaseInstanceClass::m_xWidget, std::move(pData)); diff --git a/vcl/jsdialog/executor.cxx b/vcl/jsdialog/executor.cxx index 87183f504c67..130c48453815 100644 --- a/vcl/jsdialog/executor.cxx +++ b/vcl/jsdialog/executor.cxx @@ -41,6 +41,14 @@ void SendFullUpdate(const std::string& nWindowId, const OString& rWidget) pJSWidget->sendFullUpdate(); } +void SendAction(const std::string& nWindowId, const OString& rWidget, + std::unique_ptr<ActionDataMap> pData) +{ + weld::Widget* pWidget = JSInstanceBuilder::FindWeldWidgetsMap(nWindowId, rWidget); + if (auto pJSWidget = dynamic_cast<BaseJSWidget*>(pWidget)) + pJSWidget->sendAction(std::move(pData)); +} + bool ExecuteAction(const std::string& nWindowId, const OString& rWidget, StringMap& rData) { weld::Widget* pWidget = JSInstanceBuilder::FindWeldWidgetsMap(nWindowId, rWidget); @@ -192,6 +200,40 @@ bool ExecuteAction(const std::string& nWindowId, const OString& rWidget, StringM LOKTrigger::trigger_click(*pArea, Point(10, 10)); return true; } + else if (sAction == "keypress") + { + LOKTrigger::trigger_key_press( + *pArea, + KeyEvent(rData["data"].toUInt32(), vcl::KeyCode(rData["data"].toUInt32()))); + LOKTrigger::trigger_key_release( + *pArea, + KeyEvent(rData["data"].toUInt32(), vcl::KeyCode(rData["data"].toUInt32()))); + return true; + } + else if (sAction == "textselection") + { + // start;end + int nSeparatorPos = rData["data"].indexOf(';'); + if (nSeparatorPos <= 0) + return true; + + std::u16string_view aStartPos = rData["data"].subView(0, nSeparatorPos); + std::u16string_view aEndPos = rData["data"].subView(nSeparatorPos + 1); + + if (aStartPos.empty() || aEndPos.empty()) + return true; + + int nStart = std::atoi( + OUStringToOString(aStartPos.data(), RTL_TEXTENCODING_ASCII_US).getStr()); + int nEnd = std::atoi( + OUStringToOString(aEndPos.data(), RTL_TEXTENCODING_ASCII_US).getStr()); + + Point aPos(nStart, nEnd); + CommandEvent aCEvt(aPos, CommandEventId::CursorPos); + LOKTrigger::command(*pArea, aCEvt); + + return true; + } } } else if (sControlType == "spinfield") diff --git a/vcl/jsdialog/jsdialogbuilder.cxx b/vcl/jsdialog/jsdialogbuilder.cxx index 43fa054b3fd6..e46f02fbebfc 100644 --- a/vcl/jsdialog/jsdialogbuilder.cxx +++ b/vcl/jsdialog/jsdialogbuilder.cxx @@ -88,7 +88,7 @@ void JSDialogNotifyIdle::send(tools::JsonWriter& aJsonWriter) namespace { -OUString extractActionType(const ActionDataMap& rData) +OUString extractActionType(const jsdialog::ActionDataMap& rData) { auto it = rData.find(ACTION_TYPE); if (it != rData.end()) @@ -98,7 +98,7 @@ OUString extractActionType(const ActionDataMap& rData) }; void JSDialogNotifyIdle::sendMessage(jsdialog::MessageType eType, VclPtr<vcl::Window> pWindow, - std::unique_ptr<ActionDataMap> pData) + std::unique_ptr<jsdialog::ActionDataMap> pData) { std::scoped_lock aGuard(m_aQueueMutex); @@ -172,7 +172,7 @@ std::unique_ptr<tools::JsonWriter> JSDialogNotifyIdle::generateCloseMessage() co std::unique_ptr<tools::JsonWriter> JSDialogNotifyIdle::generateActionMessage(VclPtr<vcl::Window> pWindow, - std::unique_ptr<ActionDataMap> pData) const + std::unique_ptr<jsdialog::ActionDataMap> pData) const { std::unique_ptr<tools::JsonWriter> aJsonWriter(new tools::JsonWriter()); @@ -270,6 +270,9 @@ void JSDialogNotifyIdle::Invoke() { jsdialog::MessageType eType = rMessage.m_eType; + if (m_sTypeOfJSON == "formulabar" && eType != jsdialog::MessageType::Action) + continue; + switch (eType) { case jsdialog::MessageType::FullUpdate: @@ -344,7 +347,8 @@ void JSDialogSender::sendUpdate(VclPtr<vcl::Window> pWindow, bool bForce) mpIdleNotify->Start(); } -void JSDialogSender::sendAction(VclPtr<vcl::Window> pWindow, std::unique_ptr<ActionDataMap> pData) +void JSDialogSender::sendAction(VclPtr<vcl::Window> pWindow, + std::unique_ptr<jsdialog::ActionDataMap> pData) { if (!mpIdleNotify) return; @@ -358,7 +362,7 @@ void JSDialogSender::sendPopup(VclPtr<vcl::Window> pWindow, OUString sParentId, if (!mpIdleNotify) return; - std::unique_ptr<ActionDataMap> pData = std::make_unique<ActionDataMap>(); + std::unique_ptr<jsdialog::ActionDataMap> pData = std::make_unique<jsdialog::ActionDataMap>(); (*pData)[PARENT_ID] = sParentId; (*pData)[CLOSE_ID] = sCloseId; mpIdleNotify->sendMessage(jsdialog::MessageType::Popup, pWindow, std::move(pData)); @@ -370,7 +374,7 @@ void JSDialogSender::sendClosePopup(vcl::LOKWindowId nWindowId) if (!mpIdleNotify) return; - std::unique_ptr<ActionDataMap> pData = std::make_unique<ActionDataMap>(); + std::unique_ptr<jsdialog::ActionDataMap> pData = std::make_unique<jsdialog::ActionDataMap>(); (*pData)[WINDOW_ID] = OUString::number(nWindowId); mpIdleNotify->sendMessage(jsdialog::MessageType::PopupClose, nullptr, std::move(pData)); flush(); @@ -610,7 +614,6 @@ JSInstanceBuilder::JSInstanceBuilder(vcl::Window* pParent, const OUString& rUIRo } initializeSender(GetNotifierWindow(), GetContentWindow(), GetTypeOfJSON()); - sendFullUpdate(); } std::unique_ptr<JSInstanceBuilder> JSInstanceBuilder::CreateDialogBuilder(weld::Widget* pParent, @@ -1668,7 +1671,7 @@ void JSIconView::select(int pos) { SalInstanceIconView::select(pos); - std::unique_ptr<ActionDataMap> pMap = std::make_unique<ActionDataMap>(); + std::unique_ptr<jsdialog::ActionDataMap> pMap = std::make_unique<jsdialog::ActionDataMap>(); (*pMap)[ACTION_TYPE] = "select"; (*pMap)["position"] = OUString::number(pos); sendAction(std::move(pMap));
