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;
             }
         }

Reply via email to