comphelper/Library_comphelper.mk | 2 comphelper/source/misc/markdown.cxx | 74 ++++++++++++++++++++ include/comphelper/markdown.hxx | 20 +++++ include/svx/GenericDropDownFieldDialog.hxx | 32 ++++++++ include/svx/strings.hrc | 1 svx/Library_svx.mk | 1 svx/UIConfig_svx.mk | 1 svx/source/dialog/GenericDropDownFieldDialog.cxx | 40 ++++++++++ svx/uiconfig/ui/dropdownfielddialog.ui | 84 +++++++++++++++++++++++ sw/source/uibase/dochdl/swdtflvr.cxx | 70 +++++++++++++++---- 10 files changed, 311 insertions(+), 14 deletions(-)
New commits: commit 9179d7f676703fe598bba9acd5e0af9b29907c14 Author: Ujjawal Kumar <[email protected]> AuthorDate: Tue Jul 29 16:55:55 2025 +0530 Commit: Miklos Vajna <[email protected]> CommitDate: Mon Sep 29 11:26:43 2025 +0200 tdf#162153 Introduce a new dialog for pasting plain text as Markdown This commit adds a drop-down dialog that appears when the user pastes plain text containing Markdown-formatted content, prompting the user to choose between text and markdown. (cherry picked from commit 8c49b50c20527a971fa5f74e09cc07eb1e8ead0f) Conflicts: comphelper/Library_comphelper.mk sw/source/uibase/dochdl/swdtflvr.cxx Change-Id: I3b14b5a07f107de69825583f54023d2d98984288 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190277 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/comphelper/Library_comphelper.mk b/comphelper/Library_comphelper.mk index 85a43d6a779b..39ee2e9498fa 100644 --- a/comphelper/Library_comphelper.mk +++ b/comphelper/Library_comphelper.mk @@ -34,6 +34,7 @@ $(eval $(call gb_Library_add_defs,comphelper,\ $(eval $(call gb_Library_use_externals,comphelper,\ gpgmepp \ boost_headers \ + icui18n \ icuuc \ icu_headers \ zlib \ @@ -118,6 +119,7 @@ $(eval $(call gb_Library_add_exception_objects,comphelper,\ comphelper/source/misc/interaction \ comphelper/source/misc/logging \ comphelper/source/misc/lok \ + comphelper/source/misc/markdown \ comphelper/source/misc/mimeconfighelper \ comphelper/source/misc/namedvaluecollection \ comphelper/source/misc/numberedcollection \ diff --git a/comphelper/source/misc/markdown.cxx b/comphelper/source/misc/markdown.cxx new file mode 100644 index 000000000000..a06ef8d55beb --- /dev/null +++ b/comphelper/source/misc/markdown.cxx @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <comphelper/markdown.hxx> + +#include <unicode/regex.h> + +constexpr sal_Int32 MAX_SEARCH_CHARS = 2000; + +namespace comphelper +{ +bool IsMarkdownData(OUString& rData) +{ + icu::UnicodeString ustr(rData.getStr(), std::min(rData.getLength(), MAX_SEARCH_CHARS)); + + UErrorCode status = U_ZERO_ERROR; + + static std::unique_ptr<icu::RegexPattern> patternHeadings( + icu::RegexPattern::compile(uR"(^\s*#{1,6}\s+.*)", UREGEX_MULTILINE, status)); + + static std::unique_ptr<icu::RegexPattern> patternOLists( + icu::RegexPattern::compile(uR"(^\s*\d+\.\s+.+)", UREGEX_MULTILINE, status)); + + static std::unique_ptr<icu::RegexPattern> patternULists( + icu::RegexPattern::compile(uR"(^\s*[-*+]\s+.*)", UREGEX_MULTILINE, status)); + + static std::unique_ptr<icu::RegexPattern> patternLinks( + icu::RegexPattern::compile(uR"(\[.*?\]\(.*?\))", 0, status)); + + static std::unique_ptr<icu::RegexPattern> patternCode( + icu::RegexPattern::compile(uR"(```[\s\S]*?```)", 0, status)); + + static std::unique_ptr<icu::RegexPattern> patternItalic( + icu::RegexPattern::compile(uR"(([*_])(?!\s)(.+?)(?<!\s))", 0, status)); + + static std::unique_ptr<icu::RegexPattern> patternBold( + icu::RegexPattern::compile(uR"(([*_])(?!\s)(.+?)(?<!\s))", 0, status)); + + static std::unique_ptr<icu::RegexPattern> patternStrikethrough( + icu::RegexPattern::compile(uR"(~~(?!\s)(.+?)(?<!\s)~~)", 0, status)); + + static std::unique_ptr<icu::RegexPattern> patternHorizontalRule( + icu::RegexPattern::compile(uR"(^(?:-{3,}|\*{3,}|_{3,})$)", UREGEX_MULTILINE, status)); + + static std::unique_ptr<icu::RegexPattern> patternBlockQuotes( + icu::RegexPattern::compile(uR"(^\s*>+.*$)", UREGEX_MULTILINE, status)); + + static std::unique_ptr<icu::RegexPattern> patternTables(icu::RegexPattern::compile( + uR"(^\s*\|?(?:[^| ]+?\|)+[^| ]*\|?\s*$)", UREGEX_MULTILINE, status)); + + if (U_FAILURE(status)) + return false; + + return patternHeadings->matcher(ustr, status)->find() + || patternOLists->matcher(ustr, status)->find() + || patternULists->matcher(ustr, status)->find() + || patternLinks->matcher(ustr, status)->find() + || patternCode->matcher(ustr, status)->find() + || patternItalic->matcher(ustr, status)->find() + || patternBold->matcher(ustr, status)->find() + || patternStrikethrough->matcher(ustr, status)->find() + || patternHorizontalRule->matcher(ustr, status)->find() + || patternBlockQuotes->matcher(ustr, status)->find() + || patternTables->matcher(ustr, status)->find(); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/comphelper/markdown.hxx b/include/comphelper/markdown.hxx new file mode 100644 index 000000000000..8de674d3a1b8 --- /dev/null +++ b/include/comphelper/markdown.hxx @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <comphelper/comphelperdllapi.h> +#include <rtl/ustring.hxx> + +namespace comphelper +{ +COMPHELPER_DLLPUBLIC bool IsMarkdownData(OUString& rData); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/svx/GenericDropDownFieldDialog.hxx b/include/svx/GenericDropDownFieldDialog.hxx new file mode 100644 index 000000000000..bfee8a651451 --- /dev/null +++ b/include/svx/GenericDropDownFieldDialog.hxx @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <svx/svxdllapi.h> +#include <vcl/weld.hxx> + +class SVX_DLLPUBLIC GenericDropDownFieldDialog : public weld::GenericDialogController +{ + std::unique_ptr<weld::ComboBox> m_xComboBox; + std::unique_ptr<weld::Button> m_xOK; + std::unique_ptr<weld::Button> m_xCancel; + +public: + GenericDropDownFieldDialog(weld::Widget* pParent, const OUString& rTitle); + GenericDropDownFieldDialog(weld::Widget* pParent, const OUString& rTitle, + const std::vector<OUString>& rItems); + + void InsertItem(OUString aText); + void SetActiveItem(sal_uInt32 nPos) { m_xComboBox->set_active(nPos); } + OUString GetSelectedItem() { return m_xComboBox->get_active_text(); } + sal_Int32 ElementCount() { return m_xComboBox->get_count(); } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/svx/strings.hrc b/include/svx/strings.hrc index 05ba53a0c3d6..83883de58bf8 100644 --- a/include/svx/strings.hrc +++ b/include/svx/strings.hrc @@ -1037,6 +1037,7 @@ #define RID_SVXSTR_EXPORT_GRAPHIC_TITLE NC_("RID_SVXSTR_EXPORT_GRAPHIC_TITLE", "Image Export") #define RID_SVXSTR_SAVEAS_IMAGE NC_("RID_SVXSTR_SAVEAS_IMAGE", "Save as Image") +#define RID_SVXSTR_PASTE_AS_DIALOG_TITLE NC_("RID_SVXSTR_PASTE_AS_DIALOG_TITLE", "Paste As") // Strings for the Draw Dialog -------------------------------------------- #define RID_SVX_3D_UNDO_EXCHANGE_PASTE NC_("RID_SVX_3D_UNDO_EXCHANGE_PASTE", "Insert object(s)") diff --git a/svx/Library_svx.mk b/svx/Library_svx.mk index f4e4c7fe644d..4f5f7ba04793 100644 --- a/svx/Library_svx.mk +++ b/svx/Library_svx.mk @@ -124,6 +124,7 @@ $(eval $(call gb_Library_add_exception_objects,svx,\ svx/source/customshapes/EnhancedCustomShapeFontWork \ svx/source/customshapes/EnhancedCustomShapeHandle \ svx/source/dialog/GenericCheckDialog \ + svx/source/dialog/GenericDropDownFieldDialog \ svx/source/dialog/_bmpmask \ svx/source/dialog/charmap \ svx/source/dialog/cuicharmap \ diff --git a/svx/UIConfig_svx.mk b/svx/UIConfig_svx.mk index 7268f7f526a5..fc000f936e69 100644 --- a/svx/UIConfig_svx.mk +++ b/svx/UIConfig_svx.mk @@ -79,6 +79,7 @@ $(eval $(call gb_UIConfig_add_uifiles,svx,\ svx/uiconfig/ui/gallerymenu1 \ svx/uiconfig/ui/gallerymenu2 \ svx/uiconfig/ui/genericcheckdialog \ + svx/uiconfig/ui/dropdownfielddialog \ svx/uiconfig/ui/genericcheckentry \ svx/uiconfig/ui/gotopagedialog \ svx/uiconfig/ui/grafctrlbox \ diff --git a/svx/source/dialog/GenericDropDownFieldDialog.cxx b/svx/source/dialog/GenericDropDownFieldDialog.cxx new file mode 100644 index 000000000000..92f6b392cd73 --- /dev/null +++ b/svx/source/dialog/GenericDropDownFieldDialog.cxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <svx/GenericDropDownFieldDialog.hxx> + +GenericDropDownFieldDialog::GenericDropDownFieldDialog(weld::Widget* pParent, + const OUString& rTitle) + : GenericDialogController(pParent, u"svx/ui/dropdownfielddialog.ui"_ustr, + u"DropDownDialog"_ustr) + , m_xComboBox(m_xBuilder->weld_combo_box(u"combo_box"_ustr)) + , m_xOK(m_xBuilder->weld_button(u"OK"_ustr)) + , m_xCancel(m_xBuilder->weld_button(u"CANCEL"_ustr)) +{ + set_title(rTitle); +} + +GenericDropDownFieldDialog::GenericDropDownFieldDialog(weld::Widget* pParent, + const OUString& rTitle, + const std::vector<OUString>& rItems) + : GenericDropDownFieldDialog(pParent, rTitle) +{ + for (const OUString& rItem : rItems) + { + InsertItem(rItem); + } + SetActiveItem(0); +} + +void GenericDropDownFieldDialog::InsertItem(OUString aText) +{ + m_xComboBox->append_text(std::move(aText)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svx/uiconfig/ui/dropdownfielddialog.ui b/svx/uiconfig/ui/dropdownfielddialog.ui new file mode 100644 index 000000000000..1d6b88b4e049 --- /dev/null +++ b/svx/uiconfig/ui/dropdownfielddialog.ui @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface domain="svx"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkTreeStore" id="liststore1"> + <columns> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkDialog" id="DropDownDialog"> + <property name="can-focus">False</property> + <property name="border-width">6</property> + <property name="modal">True</property> + <property name="default-width">250</property> + <property name="default-height">150</property> + <property name="type-hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="base_box"> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="button_box"> + <property name="can-focus">False</property> + <property name="layout-style">end</property> + <child> + <object class="GtkButton" id="OK"> + <property name="label" translatable="yes" context="dropdownDialog|OK">OK</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="CANCEL"> + <property name="label" translatable="yes" context="dropdownDialog|CANCEL">Cancel</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="combo_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-5">OK</action-widget> + <action-widget response="-6">CANCEL</action-widget> + </action-widgets> + </object> +</interface> diff --git a/sw/source/uibase/dochdl/swdtflvr.cxx b/sw/source/uibase/dochdl/swdtflvr.cxx index 159682b2b6a1..ad31fd3407ab 100644 --- a/sw/source/uibase/dochdl/swdtflvr.cxx +++ b/sw/source/uibase/dochdl/swdtflvr.cxx @@ -28,6 +28,8 @@ #include <officecfg/Office/Writer.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> #include <svtools/embedtransfer.hxx> #include <svtools/insdlg.hxx> #include <unotools/tempfile.hxx> @@ -37,11 +39,14 @@ #include <comphelper/servicehelper.hxx> #include <comphelper/storagehelper.hxx> #include <comphelper/string.hxx> +#include <comphelper/markdown.hxx> #include <o3tl/deleter.hxx> #include <o3tl/temporary.hxx> #include <unotools/ucbstreamhelper.hxx> #include <sot/filelist.hxx> #include <svx/svxdlg.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> #include <toolkit/helper/vclunohelper.hxx> #include <osl/endian.h> #include <sfx2/linkmgr.hxx> @@ -130,6 +135,7 @@ #include <vcl/uitest/logger.hxx> #include <vcl/uitest/eventdescription.hxx> +#include <svx/GenericDropDownFieldDialog.hxx> #include <vcl/TypeSerializer.hxx> #include <comphelper/lok.hxx> #include <sfx2/classificationhelper.hxx> @@ -2195,28 +2201,64 @@ bool SwTransferable::PasteFileContent( const TransferableDataHelper& rData, { pRead = ReadAscii; - const SwPosition& rInsertPosition = *rSh.GetCursor()->Start(); - if (CanSkipInvalidateNumRules(rInsertPosition)) - { - // Insertion point is not a numbering and we paste plain text: then no need to - // invalidate all numberings. - bSkipInvalidateNumRules = true; - } - if( rData.GetString( nFormat, sData ) ) { - pStream = new SvMemoryStream( const_cast<sal_Unicode *>(sData.getStr()), + OUString aSelection; + std::vector<OUString> aFormats; + aFormats.push_back(SvtResId(STR_FORMAT_STRING)); + + if(comphelper::IsMarkdownData(sData)) //markdown + { + aFormats.push_back(SvtResId(STR_FORMAT_ID_MARKDOWN)); + } + + if(aFormats.size() > 1) + { + GenericDropDownFieldDialog aDialog(GetActiveView()->GetFrameWeld(), + SvxResId(RID_SVXSTR_PASTE_AS_DIALOG_TITLE), + aFormats); + short nRet = aDialog.run(); + if( nRet == RET_OK) + { + aSelection = aDialog.GetSelectedItem(); + } + else if(nRet == RET_CANCEL) + return false; + } + + if(aSelection == SvtResId(STR_FORMAT_ID_MARKDOWN)) + { + OString aData = OUStringToOString(sData, RTL_TEXTENCODING_UTF8); + + pStream = new SvMemoryStream(); + pStream->WriteBytes(aData.getStr(), aData.getLength()); + pStream->Seek(0); + + pRead = ReadMarkdown; + } + else + { + const SwPosition& rInsertPosition = *rSh.GetCursor()->Start(); + if (CanSkipInvalidateNumRules(rInsertPosition)) + { + // Insertion point is not a numbering and we paste plain text: then no need to + // invalidate all numberings. + bSkipInvalidateNumRules = true; + } + + pStream = new SvMemoryStream( const_cast<sal_Unicode *>(sData.getStr()), sData.getLength() * sizeof( sal_Unicode ), StreamMode::READ ); #ifdef OSL_BIGENDIAN - pStream->SetEndian( SvStreamEndian::BIG ); + pStream->SetEndian( SvStreamEndian::BIG ); #else - pStream->SetEndian( SvStreamEndian::LITTLE ); + pStream->SetEndian( SvStreamEndian::LITTLE ); #endif - SwAsciiOptions aAOpt; - aAOpt.SetCharSet( RTL_TEXTENCODING_UCS2 ); - pRead->GetReaderOpt().SetASCIIOpts( aAOpt ); + SwAsciiOptions aAOpt; + aAOpt.SetCharSet( RTL_TEXTENCODING_UCS2 ); + pRead->GetReaderOpt().SetASCIIOpts( aAOpt ); + } break; } }
