tdf#130199 sw Confirm deletion of hidden sections

To enhance the existing discoverability of hidden sections
via Navigator by a confirmation box when a hidden section
is to be deleted.

Also add an option to switch on/off this warning message named
"ShowWarningHiddenSection". The default setting is on.

Change-Id: I91a1a32524f8d8d8fd0f230c142654df4367e729
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/150231
Tested-by: Jenkins
Reviewed-by: Balazs Varga <balazs.varga.extern@allotropia.de>
diff --git a/officecfg/registry/schema/org/openoffice/Office/Writer.xcs b/officecfg/registry/schema/org/openoffice/Office/Writer.xcs
index dcd5dc7..917ea02 100644
--- a/officecfg/registry/schema/org/openoffice/Office/Writer.xcs
+++ b/officecfg/registry/schema/org/openoffice/Office/Writer.xcs
@@ -1084,6 +1084,14 @@
          </info>
          <value>false</value>
        </prop>
        <prop oor:name="ShowWarningHiddenSection" oor:type="xs:boolean" oor:nillable="false">
          <!-- UIHints: Not accessible via user interface -->
          <info>
            <desc>Enables the writer to prevent the display of a warning dialog for hidden text (sections, etc) deletion.</desc>
            <label>Show warning dialog for hidden text deletion</label>
          </info>
          <value>true</value>
        </prop>
        <prop oor:name="DefaultAnchor" oor:type="xs:int" oor:nillable="false">
          <!-- UIHints: Tools - Options - Writer - View - Default Anchor -->
          <info>
diff --git a/sw/UIConfig_swriter.mk b/sw/UIConfig_swriter.mk
index 7011ed1..a371b49 100644
--- a/sw/UIConfig_swriter.mk
+++ b/sw/UIConfig_swriter.mk
@@ -320,6 +320,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/swriter,\
	sw/uiconfig/swriter/ui/viewoptionspage \
	sw/uiconfig/swriter/ui/warndatasourcedialog \
	sw/uiconfig/swriter/ui/warnemaildialog \
	sw/uiconfig/swriter/ui/warnhiddensectiondialog \
	sw/uiconfig/swriter/ui/watermarkdialog \
	sw/uiconfig/swriter/ui/wordcount \
	sw/uiconfig/swriter/ui/wordcount-mobile \
diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx
index ddb7b30..319911a33 100644
--- a/sw/inc/crsrsh.hxx
+++ b/sw/inc/crsrsh.hxx
@@ -486,6 +486,7 @@ public:
    // Cursor is placed in something that is protected or selection contains
    // something that is protected.
    bool HasReadonlySel(bool isReplace = false) const;
    bool HasHiddenSections() const;

    // Can the cursor be set to read only ranges?
    bool IsReadOnlyAvailable() const { return m_bSetCursorInReadOnly; }
diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx
index 663a0ff..8711b13 100644
--- a/sw/inc/editsh.hxx
+++ b/sw/inc/editsh.hxx
@@ -81,6 +81,7 @@ class SwLineNumberInfo;
class SwAuthEntry;
class SwRewriter;
class SwView;
class SwWrtShell;
struct SwConversionArgs;
struct SvxSwAutoFormatFlags;
struct SwInsertTableOptions;
@@ -623,6 +624,19 @@ public:
    /// Apply ViewOptions with Start-/EndAction.
    virtual void ApplyViewOptions( const SwViewOption &rOpt ) override;

    /// Selected area has readonly content
    virtual void InfoReadOnlyDialog(bool /*bAsync*/) const
    {
        // override in SwWrtShell
    }

    /// Selected area has hidden content
    virtual bool WarnHiddenSectionDialog() const
    {
        // override in SwWrtShell
        return true;
    }

    /** Query text within selection. */
    void GetSelectedText( OUString &rBuf,
                        ParaBreakType nHndlParaBreak = ParaBreakType::ToBlank );
diff --git a/sw/inc/pam.hxx b/sw/inc/pam.hxx
index 746fa22..2a7a0cd 100644
--- a/sw/inc/pam.hxx
+++ b/sw/inc/pam.hxx
@@ -309,6 +309,8 @@ public:
    /** Is in something protected (readonly) or selection contains
       something protected. */
    bool HasReadonlySel(bool bFormView, bool isReplace) const;
    /** Is there hidden sections in the selected area. */
    bool HasHiddenSections() const;

    bool ContainsPosition(const SwPosition & rPos) const
    {
diff --git a/sw/inc/swcrsr.hxx b/sw/inc/swcrsr.hxx
index 486507b..5707800 100644
--- a/sw/inc/swcrsr.hxx
+++ b/sw/inc/swcrsr.hxx
@@ -283,6 +283,8 @@ public:
    SwCursor* MakeBoxSels( SwCursor* pCurrentCursor );
    // Any boxes protected?
    bool HasReadOnlyBoxSel() const;
    // Any boxes hidden?
    bool HasHiddenBoxSel() const;

    // Has table cursor been changed? If so, save new values immediately.
    bool IsCursorMovedUpdate();
diff --git a/sw/qa/extras/uiwriter/uiwriter7.cxx b/sw/qa/extras/uiwriter/uiwriter7.cxx
index 392a57e..3446823 100644
--- a/sw/qa/extras/uiwriter/uiwriter7.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter7.cxx
@@ -1801,6 +1801,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest7, testTdf151605)
    std::shared_ptr<comphelper::ConfigurationChanges> batch(
        comphelper::ConfigurationChanges::create());
    officecfg::Office::Writer::FilterFlags::ASCII::IncludeHiddenText::set(false, batch);
    officecfg::Office::Writer::Content::Display::ShowWarningHiddenSection::set(false, batch);
    batch->commit();

    dispatchCommand(mxComponent, ".uno:SelectAll", {});
@@ -1817,6 +1818,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest7, testTdf151605)

    // re-enable it
    officecfg::Office::Writer::FilterFlags::ASCII::IncludeHiddenText::set(true, batch);
    officecfg::Office::Writer::Content::Display::ShowWarningHiddenSection::set(true, batch);
    batch->commit();
}

diff --git a/sw/qa/uitest/writer_tests7/tdf130199.py b/sw/qa/uitest/writer_tests7/tdf130199.py
new file mode 100644
index 0000000..8f9dde5
--- /dev/null
+++ b/sw/qa/uitest/writer_tests7/tdf130199.py
@@ -0,0 +1,62 @@
# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/.
#
from uitest.framework import UITestCase
from libreoffice.uno.propertyvalue import mkPropertyValues
from uitest.uihelper.common import get_state_as_dict

class tdf130199(UITestCase):

    def test_tdf130199(self):

        with self.ui_test.create_doc_in_start_center("writer") as document:

            # Insert an empty para
            self.xUITest.executeCommand(".uno:InsertPara")

            # Insert an empty section
            with self.ui_test.execute_dialog_through_command(".uno:InsertSection"):
                pass

            xWriterDoc = self.xUITest.getTopFocusWindow()
            xWriterEdit = xWriterDoc.getChild("writer_edit")

            # Insert an extra empty para in the section
            xWriterEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "UP"}))
            self.xUITest.executeCommand(".uno:InsertPara")

            self.assertEqual(1, len(document.TextSections))
            self.assertTrue(document.TextSections.Section1.IsVisible)

            with self.ui_test.execute_dialog_through_command(".uno:EditRegion") as xDialog:
                xHide = xDialog.getChild('hide')
                self.assertEqual('false', get_state_as_dict(xHide)['Selected'])

                xHide.executeAction('CLICK', tuple())

            self.assertFalse(document.TextSections.Section1.IsVisible)

            # Select everything and do not delete the section
            self.xUITest.executeCommand(".uno:SelectAll")

            with self.ui_test.execute_dialog_through_command(".uno:Delete", close_button="no") as xDialog:
                pass

            self.assertEqual(1, len(document.TextSections))
            self.assertFalse(document.TextSections.Section1.IsVisible)

            # Select everything and delete the section
            self.xUITest.executeCommand(".uno:SelectAll")

            with self.ui_test.execute_dialog_through_command(".uno:Delete", close_button="yes") as xDialog:
                pass

            self.assertEqual(0, len(document.TextSections))


# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sw/qa/unit/data/sw-dialogs-test.txt b/sw/qa/unit/data/sw-dialogs-test.txt
index fae87ff..2631e27 100644
--- a/sw/qa/unit/data/sw-dialogs-test.txt
+++ b/sw/qa/unit/data/sw-dialogs-test.txt
@@ -200,6 +200,7 @@ modules/swriter/ui/tokenwidget.ui
modules/swriter/ui/viewoptionspage.ui
modules/swriter/ui/warndatasourcedialog.ui
modules/swriter/ui/warnemaildialog.ui
modules/swriter/ui/warnhiddensectiondialog.ui
modules/swriter/ui/watermarkdialog.ui
modules/swriter/ui/wordcount.ui
modules/swriter/ui/wrapdialog.ui
diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx
index e901616..ef87acc 100644
--- a/sw/source/core/crsr/crsrsh.cxx
+++ b/sw/source/core/crsr/crsrsh.cxx
@@ -3402,6 +3402,7 @@ bool SwCursorShell::HasReadonlySel(bool const isReplace) const
    {
        if ( m_pTableCursor != nullptr )
        {
            // TODO: handling when a table cell (cells) is selected
            bRet = m_pTableCursor->HasReadOnlyBoxSel()
                   || m_pTableCursor->HasReadonlySel(GetViewOptions()->IsFormView(), isReplace);
        }
@@ -3420,6 +3421,38 @@ bool SwCursorShell::HasReadonlySel(bool const isReplace) const
    return bRet;
}

bool SwCursorShell::HasHiddenSections() const
{
    // Treat selections that span over start or end of paragraph of an outline node
    // with folded outline content as read-only.
    if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
    {
        SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
        if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
            return true;
    }
    bool bRet = false;

    if ( m_pTableCursor != nullptr )
    {
        bRet = m_pTableCursor->HasHiddenBoxSel()
               || m_pTableCursor->HasHiddenSections();
    }
    else
    {
        for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
        {
            if (rCursor.HasHiddenSections())
            {
                bRet = true;
                break;
            }
        }
    }

    return bRet;
}

bool SwCursorShell::IsSelFullPara() const
{
    bool bRet = false;
diff --git a/sw/source/core/crsr/pam.cxx b/sw/source/core/crsr/pam.cxx
index 5b42435..0669035 100644
--- a/sw/source/core/crsr/pam.cxx
+++ b/sw/source/core/crsr/pam.cxx
@@ -1013,6 +1013,40 @@ bool SwPaM::HasReadonlySel(bool bFormView, bool const isReplace) const
    return bRet;
}

bool SwPaM::HasHiddenSections() const
{
    bool bRet = false;

    if (HasMark() && GetPoint()->nNode != GetMark()->nNode)
    {
        // check for hidden section inside the selection
        SwNodeOffset nSttIdx = Start()->GetNodeIndex(), nEndIdx = End()->GetNodeIndex();

        if (nSttIdx + SwNodeOffset(3) < nEndIdx)
        {
            const SwSectionFormats& rFormats = GetDoc().GetSections();
            for (SwSectionFormats::size_type n = rFormats.size(); n;)
            {
                const SwSectionFormat* pFormat = rFormats[--n];
                if (pFormat->GetSection()->IsHidden())
                {
                    const SwFormatContent& rContent = pFormat->GetContent(false);
                    OSL_ENSURE(rContent.GetContentIdx(), "where is the SectionNode?");
                    SwNodeOffset nIdx = rContent.GetContentIdx()->GetIndex();
                    if (nSttIdx <= nIdx && nEndIdx >= nIdx
                        && rContent.GetContentIdx()->GetNode().GetNodes().IsDocNodes())
                    {
                        bRet = true;
                        break;
                    }
                }
            }
        }
    }

    return bRet;
}

/// This function returns the next node in direction of search. If there is no
/// left or the next is out of the area, then a null-pointer is returned.
/// @param rbFirst If <true> then first time request. If so than the position of
diff --git a/sw/source/core/crsr/swcrsr.cxx b/sw/source/core/crsr/swcrsr.cxx
index 2d3ba90..a9e1d2d 100644
--- a/sw/source/core/crsr/swcrsr.cxx
+++ b/sw/source/core/crsr/swcrsr.cxx
@@ -2623,4 +2623,18 @@ bool SwTableCursor::HasReadOnlyBoxSel() const
    return bRet;
}

bool SwTableCursor::HasHiddenBoxSel() const
{
    bool bRet = false;
    for (size_t n = m_SelectedBoxes.size(); n; )
    {
        if (m_SelectedBoxes[--n]->GetFrameFormat()->IsHidden())
        {
            bRet = true;
            break;
        }
    }
    return bRet;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx
index f1d9824..e11bc3b 100644
--- a/sw/source/core/edit/eddel.cxx
+++ b/sw/source/core/edit/eddel.cxx
@@ -27,6 +27,8 @@
#include <undobj.hxx>
#include <SwRewriter.hxx>
#include <osl/diagnose.h>
#include <wrtsh.hxx>
#include <officecfg/Office/Writer.hxx>

#include <strings.hrc>
#include <vector>
@@ -128,8 +130,17 @@ bool SwEditShell::Delete(bool const isArtificialSelection)
    bool bRet = false;
    if ( !HasReadonlySel() || CursorInsideInputField() )
    {
        StartAllAction();
        if (HasHiddenSections() &&
            officecfg::Office::Writer::Content::Display::ShowWarningHiddenSection::get())
        {
            if (!WarnHiddenSectionDialog())
            {
                bRet = RemoveParagraphMetadataFieldAtCursor();
                return bRet;
            }
        }

        StartAllAction();
        bool bUndo = GetCursor()->GetNext() != GetCursor();
        if( bUndo ) // more than one selection?
        {
@@ -155,6 +166,10 @@ bool SwEditShell::Delete(bool const isArtificialSelection)
    else
    {
        bRet = RemoveParagraphMetadataFieldAtCursor();
        if (!bRet)
        {
            InfoReadOnlyDialog(false);
        }
    }

    return bRet;
diff --git a/sw/source/uibase/docvw/SidebarTxtControl.cxx b/sw/source/uibase/docvw/SidebarTxtControl.cxx
index 7be7fe3..5fc7dd1 100644
--- a/sw/source/uibase/docvw/SidebarTxtControl.cxx
+++ b/sw/source/uibase/docvw/SidebarTxtControl.cxx
@@ -332,7 +332,7 @@ bool SidebarTextControl::KeyInput( const KeyEvent& rKeyEvt )
                bDone = pEditView && pEditView->PostKeyEvent(rKeyEvt);
            }
            else
                mrDocView.GetWrtShell().InfoReadOnlyDialog();
                mrDocView.GetWrtShell().InfoReadOnlyDialog(false);
        }
        if (bDone)
            mrSidebarWin.ResizeIfNecessary( aOldHeight, mrSidebarWin.GetPostItTextHeight() );
diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx
index 114fa3c..a15dddf 100644
--- a/sw/source/uibase/docvw/edtwin.cxx
+++ b/sw/source/uibase/docvw/edtwin.cxx
@@ -1930,7 +1930,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
                    }
                    else if (!rSh.IsCursorInParagraphMetadataField())
                    {
                        rSh.InfoReadOnlyDialog();
                        rSh.InfoReadOnlyDialog(false);
                        eKeyState = SwKeyState::End;
                    }
                    break;
@@ -2084,7 +2084,7 @@ KEYINPUT_CHECKTABLE_INSDEL:
                    }
                    else if (!rSh.IsCursorInParagraphMetadataField())
                    {
                        rSh.InfoReadOnlyDialog();
                        rSh.InfoReadOnlyDialog(false);
                        eKeyState = SwKeyState::End;
                    }
                    break;
diff --git a/sw/source/uibase/inc/wrtsh.hxx b/sw/source/uibase/inc/wrtsh.hxx
index 801e52b..8e31158 100644
--- a/sw/source/uibase/inc/wrtsh.hxx
+++ b/sw/source/uibase/inc/wrtsh.hxx
@@ -509,12 +509,13 @@ typedef bool (SwWrtShell::*FNSimpleMove)();
    void MakeOutlineContentVisible(const size_t nPos, bool bMakeVisible = true, bool bSetAttrOutlineVisibility = true);
    void MakeAllFoldedOutlineContentVisible(bool bMakeVisible = true);
    void InvalidateOutlineContentVisibility();
    bool GetAttrOutlineContentVisible(const size_t nPos);
    bool GetAttrOutlineContentVisible(const size_t nPos) const;

    void MakeOutlineLevelsVisible(const int nLevel);

    bool HasFoldedOutlineContentSelected();
    void InfoReadOnlyDialog(bool bAsync = false);
    bool HasFoldedOutlineContentSelected() const;
    virtual void InfoReadOnlyDialog(bool bAsync) const override;
    virtual bool WarnHiddenSectionDialog() const override;

    std::optional<OString> getLOKPayload(int nType, int nViewId) const;

diff --git a/sw/source/uibase/shells/textsh1.cxx b/sw/source/uibase/shells/textsh1.cxx
index f5f33c1..08d5013 100644
--- a/sw/source/uibase/shells/textsh1.cxx
+++ b/sw/source/uibase/shells/textsh1.cxx
@@ -785,7 +785,7 @@ void SwTextShell::Execute(SfxRequest &rReq)
                if (rWrtSh.HasReadonlySel() && !rWrtSh.CursorInsideInputField())
                {
                    // Only break if there's something to do; don't nag with the dialog otherwise
                    rWrtSh.InfoReadOnlyDialog();
                    rWrtSh.InfoReadOnlyDialog(false);
                    break;
                }
                SwRewriter aRewriter;
diff --git a/sw/source/uibase/wrtsh/delete.cxx b/sw/source/uibase/wrtsh/delete.cxx
index 7c6117b2..c4c89df 100644
--- a/sw/source/uibase/wrtsh/delete.cxx
+++ b/sw/source/uibase/wrtsh/delete.cxx
@@ -283,10 +283,7 @@ bool SwWrtShell::DelLeft()
    if( !bRet && bSwap )
        SwCursorShell::SwapPam();
    CloseMark( bRet );
    if (!bRet)
    {   // false indicates HasReadonlySel failed
        InfoReadOnlyDialog();
    }

    return bRet;
}

@@ -402,10 +399,6 @@ bool SwWrtShell::DelRight(bool const isReplaceHeuristic)
        SwCursorShell::Right(1, SwCursorSkipMode::Cells);
        bRet = Delete(true);
        CloseMark( bRet );
        if (!bRet)
        {   // false indicates HasReadonlySel failed
            InfoReadOnlyDialog();
        }
        break;

    case SelectionType::Frame:
diff --git a/sw/source/uibase/wrtsh/wrtsh1.cxx b/sw/source/uibase/wrtsh/wrtsh1.cxx
index d195ae7..7073ce1 100644
--- a/sw/source/uibase/wrtsh/wrtsh1.cxx
+++ b/sw/source/uibase/wrtsh/wrtsh1.cxx
@@ -2627,14 +2627,14 @@ void SwWrtShell::MakeAllFoldedOutlineContentVisible(bool bMakeVisible)
    GetView().GetDocShell()->Broadcast(SfxHint(SfxHintId::DocChanged));
}

bool SwWrtShell::GetAttrOutlineContentVisible(const size_t nPos)
bool SwWrtShell::GetAttrOutlineContentVisible(const size_t nPos) const
{
    bool bVisibleAttr = true;
    GetNodes().GetOutLineNds()[nPos]->GetTextNode()->GetAttrOutlineContentVisible(bVisibleAttr);
    return bVisibleAttr;
}

bool SwWrtShell::HasFoldedOutlineContentSelected()
bool SwWrtShell::HasFoldedOutlineContentSelected() const
{
    for(const SwPaM& rPaM : GetCursor()->GetRingContainer())
    {
@@ -2656,7 +2656,7 @@ bool SwWrtShell::HasFoldedOutlineContentSelected()
    return false;
}

void SwWrtShell::InfoReadOnlyDialog(bool bAsync)
void SwWrtShell::InfoReadOnlyDialog(bool bAsync) const
{
    if (bAsync)
    {
@@ -2687,4 +2687,20 @@ void SwWrtShell::InfoReadOnlyDialog(bool bAsync)
    }
}

bool SwWrtShell::WarnHiddenSectionDialog() const
{
    std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(
        GetView().GetFrameWeld(), "modules/swriter/ui/warnhiddensectiondialog.ui"));
    std::unique_ptr<weld::MessageDialog> xQuery(
        xBuilder->weld_message_dialog("WarnHiddenSectionDialog"));
    if (GetViewOptions()->IsShowOutlineContentVisibilityButton()
        && HasFoldedOutlineContentSelected())
    {
        xQuery->set_primary_text(SwResId(STR_INFORODLG_FOLDED_PRIMARY));
        xQuery->set_secondary_text(SwResId(STR_INFORODLG_FOLDED_SECONDARY));
    }

    return (RET_YES == xQuery->run());
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/uiconfig/swriter/ui/warnhiddensectiondialog.ui b/sw/uiconfig/swriter/ui/warnhiddensectiondialog.ui
new file mode 100644
index 0000000..19aeec8
--- /dev/null
+++ b/sw/uiconfig/swriter/ui/warnhiddensectiondialog.ui
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.2 -->
<interface domain="sw">
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkMessageDialog" id="WarnHiddenSectionDialog">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes" context="warnhiddensectiondialog|WarnHiddenSectionDialog">Delete hidden section(s)?</property>
    <property name="resizable">False</property>
    <property name="modal">True</property>
    <property name="type_hint">dialog</property>
    <property name="skip_taskbar_hint">True</property>
    <property name="message_type">question</property>
    <property name="buttons">yes-no</property>
    <property name="text" translatable="yes" context="warnhiddensectiondialog|WarnHiddenSectionDialog">Would you like to delete the hidden section(s)?</property>
    <property name="secondary_text" translatable="yes" context="warnhiddensectiondialog|WarnHiddenSectionDialog">There are hidden sections in the deleted area.</property>
    <child internal-child="vbox">
      <object class="GtkBox" id="messagedialog-vbox">
        <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="messagedialog-action_area">
            <property name="can_focus">False</property>
          </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>
      </object>
    </child>
  </object>
</interface>