Files
ElyOffice/patches/libreoffice/0021-lok-calc-page-setup-protection.patch
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

396 lines
15 KiB
Diff

diff --git a/sc/sdi/scalc.sdi b/sc/sdi/scalc.sdi
index 63cc999..001366f 100644
--- a/sc/sdi/scalc.sdi
+++ b/sc/sdi/scalc.sdi
@@ -4238,7 +4238,7 @@ SfxBoolItem PagebreakMode FID_PAGEBREAKMODE
SfxVoidItem PageFormatDialog SID_FORMATPAGE
-()
+(SfxStringItem Page FN_PARAM_1)
[
AutoUpdate = FALSE,
FastCall = FALSE,
@@ -4274,7 +4274,7 @@ SfxVoidItem PreviousPage SID_PREVIEW_PREVIOUS
SfxVoidItem Protect FID_PROTECT_TABLE
-(SfxBoolItem Protect FID_PROTECT_TABLE)
+(SfxBoolItem Protect FID_PROTECT_TABLE,SfxStringItem Protection FN_PARAM_1)
[
AutoUpdate = FALSE,
FastCall = FALSE,
diff --git a/sc/source/core/data/table5.cxx b/sc/source/core/data/table5.cxx
index 5cc62bf..8e976d0 100644
--- a/sc/source/core/data/table5.cxx
+++ b/sc/source/core/data/table5.cxx
@@ -979,7 +979,8 @@ void ScTable::InvalidateTextWidth(const ScAddress* pAdrFrom, const ScAddress* pA
const SCCOL nCol1 = pAdrFrom ? pAdrFrom->Col() : 0;
const SCROW nRow1 = pAdrFrom ? pAdrFrom->Row() : 0;
- const SCCOL nCol2 = pAdrTo ? pAdrTo->Col() : aCol.size() - 1;
+ // Only allocated columns exist; a sheet-wide end is past them.
+ const SCCOL nCol2 = std::min<SCCOL>(pAdrTo ? pAdrTo->Col() : aCol.size() - 1, aCol.size() - 1);
const SCROW nRow2 = pAdrTo ? pAdrTo->Row() : rDocument.MaxRow();
for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
diff --git a/sc/source/ui/docshell/docsh4.cxx b/sc/source/ui/docshell/docsh4.cxx
index 226b37d..46f3547 100644
--- a/sc/source/ui/docshell/docsh4.cxx
+++ b/sc/source/ui/docshell/docsh4.cxx
@@ -48,6 +48,8 @@
#include <svx/fmshell.hxx>
#include <svx/pageitem.hxx>
#include <editeng/sizeitem.hxx>
+#include <attrib.hxx>
+#include <boost/property_tree/json_parser.hpp>
#include <editeng/paperinf.hxx>
#include <sfx2/passwd.hxx>
#include <sfx2/filedlghelper.hxx>
@@ -2244,6 +2246,82 @@ void ScDocShell::PageStyleModified( std::u16string_view rStyleName, bool bApi )
}
}
+namespace
+{
+/// Applies a kit client's page JSON to the page style.
+bool lcl_KitPage(ScDocShell& rDocSh, SCTAB nTab, const OUString& rJson)
+{
+ constexpr int nMinScale = 10;
+ constexpr int nMaxScale = 400;
+ constexpr int nMaxPages = 1000;
+ boost::property_tree::ptree aTree;
+ std::stringstream aStream{ std::string(rJson.toUtf8()) };
+ bool bLandscape = false;
+ try
+ {
+ boost::property_tree::read_json(aStream, aTree);
+ bLandscape = aTree.get<bool>("landscape");
+ }
+ catch (const boost::property_tree::ptree_error&)
+ {
+ return false;
+ }
+ sal_uInt16 nScale = 0;
+ sal_uInt16 nPages = 0;
+ ScPageScaleToItem aScaleTo;
+ if (const auto oScale = aTree.get_optional<int>("scale"))
+ {
+ if (*oScale < nMinScale || *oScale > nMaxScale)
+ return false;
+ nScale = *oScale;
+ }
+ else if (const auto oFit = aTree.get_child_optional("fit"))
+ {
+ std::vector<int> aFit;
+ for (const auto& rItem : *oFit)
+ aFit.push_back(rItem.second.get_value<int>(-1));
+ if (aFit.size() != 2 || aFit[0] < 0 || aFit[1] < 0 || aFit[0] > nMaxPages
+ || aFit[1] > nMaxPages || (aFit[0] == 0 && aFit[1] == 0))
+ return false;
+ aScaleTo.Set(aFit[0], aFit[1]);
+ }
+ else if (const auto oPages = aTree.get_optional<int>("pages"))
+ {
+ if (*oPages < 1 || *oPages > nMaxPages)
+ return false;
+ nPages = *oPages;
+ }
+ else
+ return false;
+
+ ScDocument& rDoc = rDocSh.GetDocument();
+ const OUString aName = rDoc.GetPageStyle(nTab);
+ SfxStyleSheetBase* pStyle = rDoc.GetStyleSheetPool()->Find(aName, SfxStyleFamily::Page);
+ if (!pStyle)
+ return false;
+ ScStyleSaveData aOld;
+ aOld.InitFromStyle(pStyle);
+ SfxItemSet aSet(pStyle->GetItemSet());
+ SvxPageItem aPage(aSet.Get(ATTR_PAGE));
+ aPage.SetLandscape(bLandscape);
+ aSet.Put(aPage);
+ const Size aSize = aSet.Get(ATTR_PAGE_SIZE).GetSize();
+ if ((aSize.Width() > aSize.Height()) != bLandscape)
+ aSet.Put(SvxSizeItem(ATTR_PAGE_SIZE, Size(aSize.Height(), aSize.Width())));
+ aSet.Put(SfxUInt16Item(ATTR_PAGE_SCALE, nScale));
+ aSet.Put(SfxUInt16Item(ATTR_PAGE_SCALETOPAGES, nPages));
+ aSet.Put(aScaleTo);
+ rDoc.ModifyStyleSheet(*pStyle, aSet);
+ ScStyleSaveData aNew;
+ aNew.InitFromStyle(pStyle);
+ if (rDoc.IsUndoEnabled())
+ rDocSh.GetUndoManager()->AddUndoAction(
+ std::make_unique<ScUndoModifyStyle>(rDocSh, SfxStyleFamily::Page, aOld, aNew));
+ rDocSh.PageStyleModified(aName, true);
+ return true;
+}
+}
+
void ScDocShell::ExecutePageStyle( const SfxViewShell& rCaller,
SfxRequest& rReq,
SCTAB nCurTab )
@@ -2255,7 +2333,14 @@ void ScDocShell::ExecutePageStyle( const SfxViewShell& rCaller,
case SID_STATUS_PAGESTYLE: // click on StatusBar control
case SID_FORMATPAGE:
{
- if ( pReqArgs == nullptr )
+ if (const SfxStringItem* pKit = rReq.GetArg<SfxStringItem>(FN_PARAM_1))
+ {
+ if (lcl_KitPage(*this, nCurTab, pKit->GetValue()))
+ rReq.Done();
+ else
+ rReq.Ignore();
+ }
+ else if ( pReqArgs == nullptr )
{
OUString aOldName = m_pDocument->GetPageStyle( nCurTab );
ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool();
diff --git a/sc/source/ui/inc/kitsheet.hxx b/sc/source/ui/inc/kitsheet.hxx
new file mode 100644
index 0000000..481f20d
--- /dev/null
+++ b/sc/source/ui/inc/kitsheet.hxx
@@ -0,0 +1,28 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <tabprotection.hxx>
+
+#include <string_view>
+#include <utility>
+
+/// Sheet protection options by their kit client names.
+constexpr std::pair<std::string_view, ScTableProtection::Option> aKitProtectOptions[]
+ = { { "selectLocked", ScTableProtection::SELECT_LOCKED_CELLS },
+ { "selectUnlocked", ScTableProtection::SELECT_UNLOCKED_CELLS },
+ { "insertColumns", ScTableProtection::INSERT_COLUMNS },
+ { "insertRows", ScTableProtection::INSERT_ROWS },
+ { "deleteColumns", ScTableProtection::DELETE_COLUMNS },
+ { "deleteRows", ScTableProtection::DELETE_ROWS },
+ { "autofilter", ScTableProtection::AUTOFILTER },
+ { "pivot", ScTableProtection::PIVOT_TABLES } };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/unoobj/docuno.cxx b/sc/source/ui/unoobj/docuno.cxx
index 31a7d6f..0715e00 100644
--- a/sc/source/ui/unoobj/docuno.cxx
+++ b/sc/source/ui/unoobj/docuno.cxx
@@ -158,6 +158,9 @@
#include <dpobject.hxx>
#include <dpshttab.hxx>
#include <kitpivot.hxx>
+#include <kitsheet.hxx>
+#include <attrib.hxx>
+#include <svx/pageitem.hxx>
#include <pivot.hxx>
#include <svx/svdoole2.hxx>
@@ -939,6 +942,65 @@ void lcl_Chart(tools::JsonWriter& rJson, ScDocument& rDoc, ScViewData& rData)
sctools::writeChartLook(rJson, rDoc.GetChartByName(aName));
}
+/// A repeated row or column span, 1-based.
+void lcl_PutSpan(tools::JsonWriter& rJson, std::string_view rName, const std::optional<ScRange>& oRange,
+ bool bRows)
+{
+ if (!oRange)
+ return;
+ auto aNode = rJson.startNode(rName);
+ rJson.put("first", (bRows ? oRange->aStart.Row() : oRange->aStart.Col()) + 1);
+ rJson.put("last", (bRows ? oRange->aEnd.Row() : oRange->aEnd.Col()) + 1);
+}
+
+/// Print ranges, titles, page setup and breaks at the cursor.
+void lcl_PrintLayout(tools::JsonWriter& rJson, ScDocument& rDoc, const ScAddress& rCursor)
+{
+ const SCTAB nTab = rCursor.Tab();
+ const ScAddress::Details aDetails(rDoc.GetAddressConvention(), 0, 0);
+ {
+ auto aAreas = rJson.startArray("areas");
+ for (sal_uInt16 i = 0, n = rDoc.GetPrintRangeCount(nTab); i < n; ++i)
+ rJson.putSimpleValue(rDoc.GetPrintRange(nTab, i)->Format(rDoc, ScRefFlags::RANGE_ABS, aDetails));
+ }
+ lcl_PutSpan(rJson, "rows", rDoc.GetRepeatRowRange(nTab), true);
+ lcl_PutSpan(rJson, "columns", rDoc.GetRepeatColRange(nTab), false);
+ rJson.put("rowBreak", bool(rDoc.HasRowBreak(rCursor.Row(), nTab) & ScBreakType::Manual));
+ rJson.put("columnBreak", bool(rDoc.HasColBreak(rCursor.Col(), nTab) & ScBreakType::Manual));
+ const OUString aStyle = rDoc.GetPageStyle(nTab);
+ SfxStyleSheetBase* pStyle = rDoc.GetStyleSheetPool()->Find(aStyle, SfxStyleFamily::Page);
+ if (!pStyle)
+ return;
+ const SfxItemSet& rSet = pStyle->GetItemSet();
+ rJson.put("landscape", rSet.Get(ATTR_PAGE).IsLandscape());
+ rJson.put("scale", rSet.Get(ATTR_PAGE_SCALE).GetValue());
+ rJson.put("pages", rSet.Get(ATTR_PAGE_SCALETOPAGES).GetValue());
+ rJson.put("fitWidth", rSet.Get(ATTR_PAGE_SCALETO).GetWidth());
+ rJson.put("fitHeight", rSet.Get(ATTR_PAGE_SCALETO).GetHeight());
+ // Every sheet the page style governs.
+ auto aSheets = rJson.startArray("sheets");
+ for (SCTAB i = 0; i < rDoc.GetTableCount(); ++i)
+ {
+ OUString aName;
+ if (rDoc.GetPageStyle(i) == aStyle && rDoc.GetName(i, aName))
+ rJson.putSimpleValue(aName);
+ }
+}
+
+/// The sheet's protection and its allowed actions.
+void lcl_SheetProtection(tools::JsonWriter& rJson, ScDocument& rDoc, SCTAB nTab)
+{
+ const ScTableProtection aNone;
+ const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab);
+ const ScTableProtection& rProtect = pProtect ? *pProtect : aNone;
+ rJson.put("protected", rProtect.isProtected());
+ rJson.put("password", rProtect.isProtected() && rProtect.isProtectedWithPass());
+ auto aAllow = rJson.startArray("allow");
+ for (const auto& [rName, eOption] : aKitProtectOptions)
+ if (rProtect.isOptionEnabled(eOption))
+ rJson.putSimpleValue(OUString::fromUtf8(rName));
+}
+
/// The pivot table at the cursor: ranges and fields.
void lcl_PivotTable(tools::JsonWriter& rJson, ScDocument& rDoc, const ScAddress& rCursor)
{
@@ -996,7 +1058,7 @@ bool ScModelObj::supportsCommand(std::u16string_view rCommand)
= { u"SheetReferences", u"DefinedNames", u"SelectedRange",
u"AutoFilterValues", u"DataSelectValues", u"Validation",
u"NumberFormatCode", u"ConditionalFormats", u"Chart",
- u"PivotTable" };
+ u"PivotTable", u"PrintLayout", u"SheetProtection" };
return std::find(std::begin(aCommands), std::end(aCommands), rCommand) != std::end(aCommands);
}
@@ -1033,6 +1095,10 @@ void ScModelObj::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_vi
return lcl_Chart(rJsonWriter, rDoc, *pCurrent);
if (rCommand == ".uno:PivotTable")
return lcl_PivotTable(rJsonWriter, rDoc, aCursor);
+ if (rCommand == ".uno:PrintLayout")
+ return lcl_PrintLayout(rJsonWriter, rDoc, aCursor);
+ if (rCommand == ".uno:SheetProtection")
+ return lcl_SheetProtection(rJsonWriter, rDoc, nTab);
}
if (pDocShell && rCommand == ".uno:SelectedRange")
{
diff --git a/sc/source/ui/view/formatsh.cxx b/sc/source/ui/view/formatsh.cxx
index e2a6202..caa636d 100644
--- a/sc/source/ui/view/formatsh.cxx
+++ b/sc/source/ui/view/formatsh.cxx
@@ -1065,6 +1065,18 @@ void ScFormatShell::ExecuteAttr( SfxRequest& rReq )
rBindings.Invalidate( nSlot );
rBindings.Update( nSlot );
break;
+ case SID_SCATTR_CELLPROTECTION:
+ {
+ // Sets the lock, keeps the hide flags.
+ const SfxBoolItem* pLock = rReq.GetArg<SfxBoolItem>(SID_SCATTR_CELLPROTECTION);
+ if (!pLock)
+ break;
+ const ScProtectionAttr& rOld = pTabViewShell->GetSelectionPattern()->GetItem(ATTR_PROTECTION);
+ pTabViewShell->ApplyAttr(ScProtectionAttr(pLock->GetValue(), rOld.GetHideFormula(), rOld.GetHideCell(), rOld.GetHidePrint()));
+ rBindings.Invalidate( nSlot );
+ rBindings.Update( nSlot );
+ break;
+ }
case SID_ATTR_CHAR_COLOR:
case SID_SCATTR_PROTECTION :
{
diff --git a/sc/source/ui/view/tabvwsh3.cxx b/sc/source/ui/view/tabvwsh3.cxx
index c15d7c5..49295ba 100644
--- a/sc/source/ui/view/tabvwsh3.cxx
+++ b/sc/source/ui/view/tabvwsh3.cxx
@@ -45,6 +45,9 @@
#include <reffact.hxx>
#include <tabprotection.hxx>
#include <protectiondlg.hxx>
+#include <kitsheet.hxx>
+#include <docfunc.hxx>
+#include <boost/property_tree/json_parser.hpp>
#include <duplicaterecordsdlg.hxx>
#include <markdata.hxx>
@@ -250,6 +253,56 @@ void ScTabViewShell::FinishProtectTable()
SelectionChanged();
}
+namespace
+{
+/// Protects or unprotects as a kit client's JSON asks.
+bool lcl_KitProtect(ScTabViewShell& rShell, SCTAB nTab, bool bOld, const OUString& rJson)
+{
+ boost::property_tree::ptree aTree;
+ std::stringstream aStream{ std::string(rJson.toUtf8()) };
+ try
+ {
+ boost::property_tree::read_json(aStream, aTree);
+ }
+ catch (const boost::property_tree::json_parser_error&)
+ {
+ return false;
+ }
+ const bool bProtect = aTree.get<bool>("protect", false);
+ const OUString aPassword = OUString::fromUtf8(aTree.get<std::string>("password", ""));
+ if (bProtect == bOld)
+ return false;
+ if (!bProtect)
+ {
+ // A wrong password changes nothing.
+ if (!rShell.GetViewData().GetDocShell()->GetDocFunc().Unprotect(nTab, aPassword, true))
+ return false;
+ rShell.SetTabProtectionSymbol(nTab, false);
+ return true;
+ }
+ ScTableProtection aNew;
+ aNew.setProtected(true);
+ aNew.setPassword(aPassword);
+ for (const auto& [rName, eOption] : aKitProtectOptions)
+ aNew.setOption(eOption, false);
+ if (const auto oAllow = aTree.get_child_optional("allow"))
+ {
+ for (const auto& rItem : *oAllow)
+ {
+ const std::string aName = rItem.second.get_value<std::string>();
+ const auto pFound = std::find_if(std::begin(aKitProtectOptions), std::end(aKitProtectOptions),
+ [&aName](const auto& rOption) { return rOption.first == aName; });
+ if (pFound == std::end(aKitProtectOptions))
+ return false;
+ aNew.setOption(pFound->second, true);
+ }
+ }
+ ScModule::get()->InputEnterHandler();
+ rShell.ProtectSheet(nTab, aNew);
+ return true;
+}
+}
+
void ScTabViewShell::ExecProtectTable( SfxRequest& rReq )
{
ScModule* pScMod = ScModule::get();
@@ -258,6 +311,18 @@ void ScTabViewShell::ExecProtectTable( SfxRequest& rReq )
SCTAB nTab = GetViewData().CurrentTabForData();
bool bOldProtection = rDoc.IsTabProtected(nTab);
+ if (const SfxStringItem* pKit = rReq.GetArg<SfxStringItem>(FN_PARAM_1))
+ {
+ if (!lcl_KitProtect(*this, nTab, bOldProtection, pKit->GetValue()))
+ {
+ rReq.Ignore();
+ return;
+ }
+ rReq.Done();
+ FinishProtectTable();
+ return;
+ }
+
if( pReqArgs )
{
const SfxPoolItem* pItem;