diff --git a/.gitignore b/.gitignore index ed69ef94f..7d0de8e33 100644 --- a/.gitignore +++ b/.gitignore @@ -200,3 +200,5 @@ gdbtrace.log # xml unit tests sometimes leave these lying around **/secmod.db + +tmp/ diff --git a/.gitmodules b/.gitmodules index a84e0f36b..55a794a58 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,15 @@ [submodule "dictionaries"] path = dictionaries - url = ../dictionaries + url = https://git.libreoffice.org/dictionaries branch = . shallow = true [submodule "helpcontent2"] path = helpcontent2 - url = ../help + url = https://git.libreoffice.org/help branch = . shallow = true [submodule "translations"] path = translations - url = ../translations + url = https://git.libreoffice.org/translations branch = . shallow = true diff --git a/desktop/source/app/app.cxx b/desktop/source/app/app.cxx index 78b6b98cc..26fddc0f5 100644 --- a/desktop/source/app/app.cxx +++ b/desktop/source/app/app.cxx @@ -57,6 +57,8 @@ #include #include #include +#include +#include #include #if HAVE_FEATURE_UPDATE_MAR #include @@ -298,6 +300,84 @@ bool cleanExtensionCache() { #endif +class HumonicalExtensionPasswordDialog : public weld::GenericDialogController +{ + std::unique_ptr m_xPrompt; + std::unique_ptr m_xPassword; + std::unique_ptr m_xOk; + std::unique_ptr m_xCancel; + + DECL_LINK(OkHdl, weld::Button&, void); + DECL_LINK(CancelHdl, weld::Button&, void); + +public: + HumonicalExtensionPasswordDialog() + : GenericDialogController(nullptr, u"svt/ui/inputbox.ui"_ustr, u"InputBox"_ustr) + , m_xPrompt(m_xBuilder->weld_label(u"prompt"_ustr)) + , m_xPassword(m_xBuilder->weld_entry(u"entry"_ustr)) + , m_xOk(m_xBuilder->weld_button(u"ok"_ustr)) + , m_xCancel(m_xBuilder->weld_button(u"cancel"_ustr)) + { + m_xDialog->set_title(u"Required HOffice extension"_ustr); + m_xPrompt->set_label(u"Required HOffice extension"_ustr); + m_xPassword->set_visibility(false); + m_xOk->connect_clicked(LINK(this, HumonicalExtensionPasswordDialog, OkHdl)); + m_xCancel->connect_clicked(LINK(this, HumonicalExtensionPasswordDialog, CancelHdl)); + } + + OUString GetPassword() const { return m_xPassword->get_text(); } +}; + +IMPL_LINK_NOARG(HumonicalExtensionPasswordDialog, OkHdl, weld::Button&, void) +{ + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(HumonicalExtensionPasswordDialog, CancelHdl, weld::Button&, void) +{ + m_xDialog->response(RET_CANCEL); +} + +#if HAVE_FEATURE_EXTENSIONS +bool lcl_IsHOfficeExtensionInstalled() +{ + try + { + Reference xPackageInfo + = css::deployment::PackageInformationProvider::get( + comphelper::getProcessComponentContext()); + return xPackageInfo.is() + && !xPackageInfo->getPackageLocation(u"kr.phue.hoffice"_ustr).isEmpty(); + } + catch (const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("desktop.app", "cannot query HOffice extension state"); + } + + return false; +} +#endif + +bool lcl_AllowStartupWithoutHOfficeExtension(bool bCanAskPassword) +{ +#if HAVE_FEATURE_EXTENSIONS + if (lcl_IsHOfficeExtensionInstalled()) + return true; + + if (!bCanAskPassword) + return false; + + HumonicalExtensionPasswordDialog aDialog; + if (aDialog.run() != RET_OK) + return false; + + return aDialog.GetPassword() == u"***"_ustr; +#else + (void)bCanAskPassword; + return false; +#endif +} + bool shouldLaunchQuickstart() { bool bQuickstart = Desktop::GetCommandLineArgs().IsQuickstart(); @@ -1538,6 +1618,13 @@ int Desktop::Main() if ( bAbort ) return EXIT_FAILURE; + const bool bRequiresHOfficeExtension = rCmdLineArgs.IsWriter() || rCmdLineArgs.IsCalc() + || rCmdLineArgs.IsImpress() || rCmdLineArgs.IsDraw(); + if (bRequiresHOfficeExtension + && !lcl_AllowStartupWithoutHOfficeExtension( + !rCmdLineArgs.IsHeadless() && !rCmdLineArgs.IsInvisible())) + return EXIT_FAILURE; + if (inst_fin == userinstall::CREATED) { Migration::migrateSettingsIfNecessary(); diff --git a/include/sfx2/notebookbar/SfxNotebookBar.hxx b/include/sfx2/notebookbar/SfxNotebookBar.hxx index b7d2e769d..e576a2fc4 100644 --- a/include/sfx2/notebookbar/SfxNotebookBar.hxx +++ b/include/sfx2/notebookbar/SfxNotebookBar.hxx @@ -16,6 +16,7 @@ #include #include +#include namespace com::sun::star::frame { @@ -60,6 +61,11 @@ public: static void RemoveListeners(SystemWindow const* pSysWindow); + static void SetNotebookBarPolicy(SfxViewFrame& rViewFrame, std::vector aHiddenPages, + std::vector aHiddenWidgets); + static void ClearNotebookBarPolicy(SfxViewFrame& rViewFrame); + static void ApplyNotebookBarPolicy(SfxViewFrame& rViewFrame); + /** Show menu bar in all frames of current application */ static void ShowMenubar(bool bShow); /** Show menu bar only in current frame */ diff --git a/include/sfx2/objsh.hxx b/include/sfx2/objsh.hxx index 4ac904592..eaa312ff1 100644 --- a/include/sfx2/objsh.hxx +++ b/include/sfx2/objsh.hxx @@ -64,6 +64,7 @@ class SvKeyValueIterator; class SfxBaseModel; class SfxModule; class SfxProgress; +class SfxWatermarkItem; class GDIMetaFile; class INetURLObject; class IndexBitSet; @@ -444,6 +445,13 @@ public: void SetMacroCallsSeenWhileLoading(); bool GetMacroCallsSeenWhileLoading() const; + // Humonical screen watermark: per-document master state so the watermark + // survives view-shell switches (e.g. Calc print preview and back). + // A null item with the state set means "explicitly cleared". + void SetScreenWatermarkState( const SfxWatermarkItem* pWatermark ); + bool HasScreenWatermarkState() const; + const SfxWatermarkItem* GetScreenWatermarkState() const; + // true if this type of link, from a document, is allowed by the user to be passed to uno:OpenDoc static bool AllowedLinkProtocolFromDocument(const OUString& rUrl, SfxObjectShell* pObjShell, weld::Window* pDialogParent); diff --git a/include/sfx2/sfxsids.hrc b/include/sfx2/sfxsids.hrc index 3da55d478..73d8a59a7 100644 --- a/include/sfx2/sfxsids.hrc +++ b/include/sfx2/sfxsids.hrc @@ -39,6 +39,7 @@ class SfxStringListItem; class SfxUInt16Item; class SfxUnoAnyItem; class SfxUnoFrameItem; +class SfxVoidItem; class SfxWatermarkItem; class SfxVisibilityItem; class SvxClipboardFormatItem; @@ -303,6 +304,25 @@ class SvxZoomItem; #define SID_MACROMANAGER (SID_SFX_START + 1750) #define SID_EXPORTDIRECTORY TypedWhichId(SID_SFX_START + 1751) #define SID_EXTENSION_MANAGER TypedWhichId(SID_SFX_START + 1752) + +// Humonical reserved block: 3900-3949. +// Upstream allocates the SFX ids sequentially from the low end (1752 as of +// 26.2.4.2), so a private id placed right above that would silently collide +// with an upstream id in a later release - same number, different macro name, +// which neither the preprocessor nor the compiler reports, and the duplicate +// slot check in idl/source/objects/slot.cxx is a DBG_ASSERT that is compiled +// out of release builds. Keep private ids in this block, far from upstream's +// growth, and never reuse a number even after a feature is removed. +#define SID_HUMONICAL_SCREEN_WATERMARK TypedWhichId(SID_SFX_START + 3900) +#define SID_HUMONICAL_SCREEN_WATERMARK_FONT_SIZE_PT TypedWhichId(SID_SFX_START + 3901) +#define SID_HUMONICAL_SCREEN_WATERMARK_LINE_GAP_PX TypedWhichId(SID_SFX_START + 3902) +#define SID_HUMONICAL_SCREEN_WATERMARK_COLOR TypedWhichId(SID_SFX_START + 3903) +#define SID_HUMONICAL_NOTEBOOKBAR_POLICY TypedWhichId(SID_SFX_START + 3904) +#define SID_HUMONICAL_NOTEBOOKBAR_HIDDEN_PAGES TypedWhichId(SID_SFX_START + 3905) +#define SID_HUMONICAL_NOTEBOOKBAR_HIDDEN_WIDGETS TypedWhichId(SID_SFX_START + 3906) +#define SID_HUMONICAL_NOTEBOOKBAR_RESTORE TypedWhichId(SID_SFX_START + 3907) +// SID_HUMONICAL_free_END (SID_SFX_START + 3949) + // SID_SFX_free_END (SID_SFX_START + 3999) #define SID_OPEN_NEW_VIEW TypedWhichId(SID_SFX_START + 520) diff --git a/include/sfx2/viewfrm.hxx b/include/sfx2/viewfrm.hxx index 7ef97412e..109231037 100644 --- a/include/sfx2/viewfrm.hxx +++ b/include/sfx2/viewfrm.hxx @@ -20,6 +20,7 @@ #define INCLUDED_SFX2_VIEWFRM_HXX #include +#include #include #include #include @@ -143,6 +144,13 @@ public: SfxFrame& GetFrame() const; SfxViewFrame* GetTopViewFrame() const; + SAL_DLLPRIVATE void SetNotebookBarPolicy(std::vector aHiddenPages, + std::vector aHiddenWidgets); + SAL_DLLPRIVATE void ClearNotebookBarPolicy(); + SAL_DLLPRIVATE bool HasNotebookBarPolicy() const; + SAL_DLLPRIVATE const std::vector& GetNotebookBarHiddenPages() const; + SAL_DLLPRIVATE const std::vector& GetNotebookBarHiddenWidgets() const; + bool DoClose(); static void GetTargetList( TargetList& rList ) { SfxFrame::GetDefaultTargetList( rList ); } diff --git a/include/sfx2/viewsh.hxx b/include/sfx2/viewsh.hxx index fd4a7fe28..5ed09704a 100644 --- a/include/sfx2/viewsh.hxx +++ b/include/sfx2/viewsh.hxx @@ -39,6 +39,7 @@ class SfxTabPage; class SfxBaseController; +class SfxWatermarkItem; class Fraction; namespace weld { class Container; @@ -189,6 +190,7 @@ friend class SfxPrinterController; protected: virtual void Activate(bool IsMDIActivate) override; virtual void Deactivate(bool IsMDIActivate) override; + virtual void ScreenWatermarkChanged(); virtual void InnerResizePixel( const Point &rOfs, const Size &rSize, bool inplaceEditModeChange ); virtual void OuterResizePixel( const Point &rOfs, const Size &rSize ); @@ -250,6 +252,12 @@ public: bool IsAllowChangeComments() const { return allowChangeComments; } void SetAllowManageRedlines(bool allow) { allowManageRedlines = allow; } bool IsAllowManageRedlines() const { return allowManageRedlines; } + void SetScreenWatermark(const SfxWatermarkItem& rWatermark); + void ClearScreenWatermark(); + const SfxWatermarkItem* GetScreenWatermark() const; + bool HasScreenWatermark() const; + void SetScreenWatermarkPaused(bool bPaused); + bool IsScreenWatermarkPaused() const; // Misc diff --git a/include/sfx2/watermarkitem.hxx b/include/sfx2/watermarkitem.hxx index 710158e58..4be33f839 100644 --- a/include/sfx2/watermarkitem.hxx +++ b/include/sfx2/watermarkitem.hxx @@ -34,6 +34,10 @@ public: void SetTransparency(const sal_Int16 nTransparency) { m_nTransparency = nTransparency; } Color GetColor() const { return m_nColor; } void SetColor(Color nColor) { m_nColor = nColor; } + sal_Int32 GetFontSizePt() const { return m_nFontSizePt; } + void SetFontSizePt(sal_Int32 nFontSizePt) { m_nFontSizePt = nFontSizePt; } + sal_Int32 GetLineGapPx() const { return m_nLineGapPx; } + void SetLineGapPx(sal_Int32 nLineGapPx) { m_nLineGapPx = nLineGapPx; } private: OUString m_aText; @@ -41,6 +45,8 @@ private: sal_Int16 m_nAngle; sal_Int16 m_nTransparency; Color m_nColor; + sal_Int32 m_nFontSizePt; + sal_Int32 m_nLineGapPx; }; #endif diff --git a/include/vcl/builder.hxx b/include/vcl/builder.hxx index 73fea3f62..a438d2a30 100644 --- a/include/vcl/builder.hxx +++ b/include/vcl/builder.hxx @@ -41,6 +41,7 @@ class SvTabListBox; class Slider; class VclExpander; class VclMultiLineEdit; +class ToolBox; namespace com::sun::star::frame { class XFrame; } /// Creates a hierarchy of vcl::Windows (widgets) from a .ui file for dialogs, sidebar, etc. @@ -66,6 +67,9 @@ public: vcl::Window* get_widget_root(); + bool get_widget_visibility(std::u16string_view sID, bool& rVisible); + bool set_widget_visibility(std::u16string_view sID, bool bVisible); + //release ownership of pWindow, i.e. don't delete it void drop_ownership(const vcl::Window *pWindow); @@ -118,6 +122,20 @@ private: }; std::vector m_aChildren; + struct ToolBoxItemAndId + { + OUString m_sID; + ToolBox* m_pToolBox; + ToolBoxItemId m_nItemId; + ToolBoxItemAndId(OUString sID, ToolBox* pToolBox, ToolBoxItemId nItemId) + : m_sID(std::move(sID)) + , m_pToolBox(pToolBox) + , m_nItemId(nItemId) + { + } + }; + std::vector m_aToolBoxItems; + struct ButtonImageWidgetMap { OUString m_sID; diff --git a/include/vcl/humonicalblend.hxx b/include/vcl/humonicalblend.hxx new file mode 100644 index 000000000..266c40130 --- /dev/null +++ b/include/vcl/humonicalblend.hxx @@ -0,0 +1,63 @@ +/* -*- 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 +#include + +#include +#include +#include +#include + +class OutputDevice; + +namespace vcl::humonical +{ +/** Metrics and appearance of the Humonical screen watermark. + + The colour is passed unmodified: the strength is applied while blending, + because both blend branches need the original colour and the opacity + separately. Callers must not pre-mix the colour towards white. +*/ +struct WatermarkParams +{ + OUString aText; + vcl::Font aFont; ///< size and rotation already set up by the caller + Color aColor = COL_LIGHTGRAY; + sal_Int16 nTransparency = 50; ///< 0..100, converted to opacity while blending + tools::Long nLineStepPx = 180; + tools::Long nMarginPx = 520; +}; + +/** Paint the screen watermark so that it stays visible on any background. + + Bright destination pixels are darkened (multiply), dark ones are lightened + (screen); both branches keep the watermark colour. A glyph coverage mask is + rendered first and the colour is applied per pixel while compositing, which + is what keeps the hue on dark backgrounds - reusing a coloured source layer + for both branches would invert it. + + Both rectangles are in the *current map mode* of rDevice. All conversion, + clamping, read-back and write-back happen inside, on that same device, so + callers never have to reason about pixel offsets. + + @param rAnchorRect reference for the pattern phase, so that partial + repaints and scrolling do not shift the pattern. + @param rTargetRect area to actually paint. The read-back is sized from + this, so callers pass the paint rectangle here rather + than a whole page or viewport. +*/ +VCL_DLLPUBLIC void PaintBlendedWatermark(OutputDevice& rDevice, const tools::Rectangle& rAnchorRect, + const tools::Rectangle& rTargetRect, + const WatermarkParams& rParams); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/vcl/notebookbar/notebookbar.hxx b/include/vcl/notebookbar/notebookbar.hxx index f8cc71cbb..bc9eca45c 100644 --- a/include/vcl/notebookbar/notebookbar.hxx +++ b/include/vcl/notebookbar/notebookbar.hxx @@ -13,6 +13,7 @@ #include #include #include +#include #include class NotebookBarContextChangeEventListener; @@ -48,6 +49,10 @@ public: const OUString & GetUIFilePath() const { return m_sUIXMLDescription; } void SetDisposeCallback(const Link rDisposeCallback, const SfxViewShell* pViewShell); + void SetNotebookBarPolicy(const std::vector& rHiddenPages, + const std::vector& rHiddenWidgets); + void ClearNotebookBarPolicy(); + private: VclPtr m_pSystemWindow; rtl::Reference m_pEventListener; @@ -58,6 +63,7 @@ private: bool m_bIsWelded; OUString m_sUIXMLDescription; Link m_rDisposeLink; + std::map m_aPolicyWidgetVisibility; AllSettings DefaultSettings; diff --git a/include/vcl/tabctrl.hxx b/include/vcl/tabctrl.hxx index 28987536a..733d40d4b 100644 --- a/include/vcl/tabctrl.hxx +++ b/include/vcl/tabctrl.hxx @@ -114,6 +114,8 @@ public: void SetPageEnabled(sal_uInt16 nPageId, bool bEnable = true); void SetPageVisible(sal_uInt16 nPageId, bool bVisible = true); + bool IsPageEnabled(sal_uInt16 nPageId) const; + bool IsPageVisible(sal_uInt16 nPageId) const; sal_uInt16 GetPagePos( sal_uInt16 nPageId ) const; sal_uInt16 GetPageCount() const; @@ -184,6 +186,7 @@ public: void dispose() override; void SetContext( vcl::EnumContext::Context eContext ) override; + void SetHiddenPageNames(std::vector aHiddenPageNames); void SetIconClickHdl( Link aHdl ); void SetToolBox( ToolBox* pToolBox ); ToolBox* GetToolBox() { return m_pShortcuts; } @@ -196,6 +199,10 @@ protected: virtual void ImplActivateTabPage( bool bNext ) override; private: + void RestoreHiddenPageVisibility(); + void ApplyHiddenPagePolicy(); + bool IsPolicyHiddenPage(sal_uInt16 nPageId) const; + std::vector m_aHiddenPageNames; bool bLastContextWasSupported; vcl::EnumContext::Context eLastContext; Link m_aIconClickHdl; diff --git a/officecfg/registry/schema/org/openoffice/Office/Calc.xcs b/officecfg/registry/schema/org/openoffice/Office/Calc.xcs index 1f85aab91..95cb837fd 100644 --- a/officecfg/registry/schema/org/openoffice/Office/Calc.xcs +++ b/officecfg/registry/schema/org/openoffice/Office/Calc.xcs @@ -1485,6 +1485,7 @@ + 1 diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk index 5960a50e1..cc334bd3f 100644 --- a/sc/Library_sc.mk +++ b/sc/Library_sc.mk @@ -664,6 +664,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\ sc/source/ui/view/gridwin3 \ sc/source/ui/view/gridwin4 \ sc/source/ui/view/gridwin5 \ + sc/source/ui/view/humonicalwatermark \ sc/source/ui/view/hdrcont \ sc/source/ui/view/hintwin \ sc/source/ui/view/imapwrap \ diff --git a/sc/inc/compiler.hxx b/sc/inc/compiler.hxx index 21393f9c5..0dc2a7ff7 100644 --- a/sc/inc/compiler.hxx +++ b/sc/inc/compiler.hxx @@ -563,7 +563,19 @@ private: /// Access the CharTable flags ScCharFlags GetCharTableFlags( sal_Unicode c, sal_Unicode cLast ) - { return c < 128 ? pConv->getCharTableFlags(c, cLast) : ScCharFlags::NONE; } + { + if (c < 128) + return pConv->getCharTableFlags(c, cLast); + + // Excel files produced by third-party tools may contain unquoted sheet + // names with non-ASCII symbols before '!'. Keep them in the candidate + // token; the existing reference parser still validates the final name. + if (formula::FormulaGrammar::isExcelSyntax(meGrammar)) + return ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent + | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name; + + return ScCharFlags::NONE; + } virtual void HandleIIOpCode(formula::FormulaToken* token, formula::FormulaToken*** pppToken, sal_uInt8 nNumParams) override; bool HandleIIOpCodeInternal(formula::FormulaToken* token, formula::FormulaToken*** pppToken, sal_uInt8 nNumParams); diff --git a/sc/source/core/tool/formulaopt.cxx b/sc/source/core/tool/formulaopt.cxx index 745428e36..eebb29d35 100644 --- a/sc/source/core/tool/formulaopt.cxx +++ b/sc/source/core/tool/formulaopt.cxx @@ -31,7 +31,7 @@ ScFormulaOptions::ScFormulaOptions() void ScFormulaOptions::SetDefaults() { bUseEnglishFuncName = false; - eFormulaGrammar = ::formula::FormulaGrammar::GRAM_NATIVE; + eFormulaGrammar = ::formula::FormulaGrammar::GRAM_NATIVE_XL_A1; mbWriteCalcConfig = true; meOOXMLRecalc = RECALC_ASK; meODFRecalc = RECALC_ASK; diff --git a/sc/source/ui/inc/humonicalwatermark.hxx b/sc/source/ui/inc/humonicalwatermark.hxx new file mode 100644 index 000000000..eafb8cb6c --- /dev/null +++ b/sc/source/ui/inc/humonicalwatermark.hxx @@ -0,0 +1,24 @@ +/* -*- 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 + +class OutputDevice; +class SfxViewShell; + +namespace sc::humonical +{ +void PaintWatermarkUnderContent(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rViewportRect, + const tools::Rectangle& rPaintRect, bool bTiledRendering); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx index 401a26b18..f830a1e27 100644 --- a/sc/source/ui/view/gridwin.cxx +++ b/sc/source/ui/view/gridwin.cxx @@ -5152,6 +5152,15 @@ void ScGridWindow::ScrollPixel( tools::Long nDifX, tools::Long nDifY ) ClickExtern(); HideNoteOverlay(); + if (ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + pViewShell && pViewShell->HasScreenWatermark()) + { + SetMapMode(GetDrawMapMode()); + UpdateEditViewPos(); + Invalidate(); + return; + } + SetMapMode(MapMode(MapUnit::MapPixel)); Scroll( nDifX, nDifY, ScrollFlags::Children ); SetMapMode( GetDrawMapMode() ); // generated shifted MapMode diff --git a/sc/source/ui/view/gridwin4.cxx b/sc/source/ui/view/gridwin4.cxx index ebe99a7fc..f1497cd68 100644 --- a/sc/source/ui/view/gridwin4.cxx +++ b/sc/source/ui/view/gridwin4.cxx @@ -52,6 +52,7 @@ #include #include +#include #include #include #include @@ -911,6 +912,16 @@ void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableI aOutputData.DrawBackground(*pContentDev); + // The screen-only watermark sits directly on top of the cell fill colors and + // below everything else - grid, borders, sparklines and cell text all stay + // crisp instead of being crossed by the pattern. (With bGridFirst the grid is + // already down before the backgrounds, so there the watermark covers it.) + sc::humonical::PaintWatermarkUnderContent( + *pContentDev, pCurTabViewShell, + tools::Rectangle(Point(), GetOutputSizePixel()), + tools::Rectangle(Point(nScrX, nScrY), Size(aOutputData.GetScrW(), aOutputData.GetScrH())), + bIsTiledRendering); + if (!bGridFirst && (bGrid || bPage) && !bNoBackgroundAndGrid) aOutputData.DrawGrid(*pContentDev, bGrid, bPage); @@ -1854,7 +1865,13 @@ void ScGridWindow::DrawPagePreview( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, vcl::Font aFont; std::unique_ptr pEditEng; const ScPatternAttr& rDefPattern(rDoc.getCellAttributeHelper().getDefaultCellAttribute()); - if ( nPageScript == SvtScriptType::LATIN ) + // tdf#170956 workaround: since the EditEngine StripPortions rework, + // EditEngine::DrawText_ToPosition() shifts all painting that follows it in + // this window when the localized page string is non-Latin (e.g. "1 페이지"), + // which garbles the page-break preview. Use the plain DrawText path for all + // scripts; vcl font fallback renders CJK glyphs fine. + const bool bDirectDrawPageNum = true; + if ( bDirectDrawPageNum ) { // use single font and call DrawText directly rDefPattern.fillFontOnly(aFont); diff --git a/sc/source/ui/view/humonicalwatermark.cxx b/sc/source/ui/view/humonicalwatermark.cxx new file mode 100644 index 000000000..19367a2c3 --- /dev/null +++ b/sc/source/ui/view/humonicalwatermark.cxx @@ -0,0 +1,74 @@ +/* -*- 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/. + */ + +#include + +#include +#include +#include +#include + +#include + +namespace +{ +constexpr tools::Long WATERMARK_FONT_HEIGHT = 28; +constexpr tools::Long WATERMARK_LINE_STEP = 180; +constexpr tools::Long WATERMARK_LINE_MARGIN_X = 520; +constexpr sal_Int32 WATERMARK_DEFAULT_FONT_SIZE_PT = 30; +constexpr sal_Int32 WATERMARK_DEFAULT_LINE_GAP_PX = 180; +} + +namespace sc::humonical +{ +void PaintWatermarkUnderContent(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rViewportRect, + const tools::Rectangle& rPaintRect, bool bTiledRendering) +{ + if (!pViewShell || bTiledRendering || rDevice.GetOutDevType() == OUTDEV_PRINTER + || rDevice.GetConnectMetaFile() || rViewportRect.IsEmpty() || rPaintRect.IsEmpty()) + { + return; + } + + const SfxWatermarkItem* pWatermark = pViewShell->GetScreenWatermark(); + if (pViewShell->IsScreenWatermarkPaused() || !pWatermark || pWatermark->GetText().isEmpty()) + return; + + // Both rectangles arrive in pixels, so put the device into pixel map mode + // before the blender reads them in the map mode that is current for it. + auto popIt = rDevice.ScopedPush(vcl::PushFlags::ALL); + rDevice.SetMapMode(MapMode(MapUnit::MapPixel)); + + vcl::humonical::WatermarkParams aParams; + aParams.aText = pWatermark->GetText(); + // Deliberately unmixed: the blender needs the colour and the transparency + // separately to derive its multiply and screen sources. + aParams.aColor = pWatermark->GetColor(); + aParams.nTransparency = pWatermark->GetTransparency(); + aParams.nLineStepPx = std::max(1, pWatermark->GetLineGapPx() * WATERMARK_LINE_STEP + / WATERMARK_DEFAULT_LINE_GAP_PX); + aParams.nMarginPx = WATERMARK_LINE_MARGIN_X; + + aParams.aFont.SetFamilyName(pWatermark->GetFont()); + aParams.aFont.SetFamily(FAMILY_SWISS); + aParams.aFont.SetWeight(WEIGHT_BOLD); + aParams.aFont.SetFontSize( + Size(0, std::max(1, pWatermark->GetFontSizePt() * WATERMARK_FONT_HEIGHT + / WATERMARK_DEFAULT_FONT_SIZE_PT))); + aParams.aFont.SetOrientation(Degree10(static_cast(pWatermark->GetAngle() * 10))); + + // The viewport anchors the pattern phase - page-break view frequently + // repaints from a cell in the middle of the viewport - while the repaint + // area bounds the read-back and therefore the actual work. + vcl::humonical::PaintBlendedWatermark(rDevice, rViewportRect, rPaintRect, aParams); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/preview.cxx b/sc/source/ui/view/preview.cxx index 081050d01..d4123e11b 100644 --- a/sc/source/ui/view/preview.cxx +++ b/sc/source/ui/view/preview.cxx @@ -35,6 +35,8 @@ #include #include +#include +#include #include #include #include @@ -608,6 +610,15 @@ void ScPreview::DoPrint( ScPreviewLocationData* pFillLocation ) aPixel.AdjustRight(SC_PREVIEW_SHADOWSIZE - 1 ); aPixel.AdjustBottom(SC_PREVIEW_SHADOWSIZE - 1 ); GetOutDev()->DrawRect( PixelToLogic( aPixel ) ); + + // Paint the screen-only watermark clipped to the paper area, matching the + // Writer print preview. Real print output never runs through this code + // path (and the helper skips printer devices and metafiles anyway). + const tools::Rectangle aPagePixel( LogicToPixel( + tools::Rectangle( -aOffset.X(), -aOffset.Y(), nPageEndX, nPageEndY ), aMMMode ) ); + sc::humonical::PaintWatermarkUnderContent( + *GetOutDev(), pViewShell, aPagePixel, aPagePixel, + comphelper::LibreOfficeKit::isActive()); } void ScPreview::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& /* rRect */ ) diff --git a/sd/Library_sd.mk b/sd/Library_sd.mk index d1bf7fc19..dad9daa51 100644 --- a/sd/Library_sd.mk +++ b/sd/Library_sd.mk @@ -349,6 +349,7 @@ $(eval $(call gb_Library_add_exception_objects,sd,\ sd/source/ui/func/unmovss \ sd/source/ui/func/unoaprms \ sd/source/ui/func/unprlout \ + sd/source/ui/view/humonicalwatermark \ sd/source/ui/presenter/CanvasUpdateRequester \ sd/source/ui/presenter/PresenterCanvas \ sd/source/ui/presenter/PresenterHelper \ diff --git a/sd/source/ui/inc/View.hxx b/sd/source/ui/inc/View.hxx index 95feac178..4307b3319 100644 --- a/sd/source/ui/inc/View.hxx +++ b/sd/source/ui/inc/View.hxx @@ -102,6 +102,8 @@ public: virtual ~View() override; void CompleteRedraw( OutputDevice* pOutDev, const vcl::Region& rReg, sdr::contact::ViewObjectContactRedirector* pRedirector = nullptr) override; + void EndCompleteRedraw(SdrPaintWindow& rPaintWindow, bool bPaintFormLayer, + sdr::contact::ViewObjectContactRedirector* pRedirector = nullptr) override; virtual void GetAttributes( SfxItemSet& rTargetSet, bool bOnlyHardAttr = false ) const; virtual bool SetAttributes(const SfxItemSet& rSet, bool bReplaceAll = false, bool bSlide = false, bool bMaster = false); diff --git a/sd/source/ui/inc/ViewShellBase.hxx b/sd/source/ui/inc/ViewShellBase.hxx index 2617fb569..1aba6025d 100644 --- a/sd/source/ui/inc/ViewShellBase.hxx +++ b/sd/source/ui/inc/ViewShellBase.hxx @@ -234,6 +234,7 @@ public: protected: virtual void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override; + void ScreenWatermarkChanged() override; virtual void InitializeFramework(); diff --git a/sd/source/ui/inc/humonicalwatermark.hxx b/sd/source/ui/inc/humonicalwatermark.hxx new file mode 100644 index 000000000..8e193e4c3 --- /dev/null +++ b/sd/source/ui/inc/humonicalwatermark.hxx @@ -0,0 +1,45 @@ +/* -*- 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 +#include + +class OutputDevice; +class SfxViewShell; + +namespace sd::humonical +{ +void PaintWatermark(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rPageRect, const vcl::Region* pPaintRegion = nullptr); + +/** Paint the screen watermark scaled to the given rectangle. + + Unlike PaintWatermark(), which uses the fixed metrics of a full-size slide, + the font size and line distance are derived from the rectangle itself. Use + this for small renderings of a slide - the slide sorter and the slide panel + thumbnails - where the fixed metrics would be far too large. Works in + whatever coordinate system the device currently uses, because all metrics + are proportional to rRect. +*/ +void PaintWatermarkScaled(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rRect); + +/** Paint the screen watermark over rRect with the normal on-screen density. + + The metrics are defined in pixels and converted to the device's units, so + this works regardless of the map mode a view uses. Use it for views that + show document content without a page frame, such as the outline view. +*/ +void PaintWatermarkScreen(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rRect); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx b/sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx index c6d5595d2..1980b1c13 100644 --- a/sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx +++ b/sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx @@ -25,6 +25,7 @@ namespace sd::slidesorter::cache { class PageCache; } namespace sd::slidesorter { class SlideSorter; } +namespace sd { class ViewShellBase; } namespace sd::slidesorter::view { @@ -66,6 +67,8 @@ public: private: const Layouter& mrLayouter; + /// View shell the previews belong to, for the screen watermark. May be null. + ViewShellBase* mpViewShellBase; std::shared_ptr mpCache; std::shared_ptr mpTheme; std::shared_ptr mpPageNumberFont; diff --git a/sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx b/sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx index 4ed190ecf..bc3df9bc6 100644 --- a/sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx +++ b/sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx @@ -28,6 +28,8 @@ #include "SlsFramePainter.hxx" #include #include +#include +#include #include #include #include @@ -41,6 +43,7 @@ namespace sd::slidesorter::view { PageObjectPainter::PageObjectPainter ( const SlideSorter& rSlideSorter) : mrLayouter(rSlideSorter.GetView().GetLayouter()), + mpViewShellBase(rSlideSorter.GetViewShellBase()), mpCache(rSlideSorter.GetView().GetPreviewCache()), mpTheme(rSlideSorter.GetTheme()), mpPageNumberFont(Theme::GetFont(Theme::Font_PageNumber, *rSlideSorter.GetContentWindow()->GetOutDev())), @@ -147,6 +150,10 @@ void PageObjectPainter::PaintPreview ( else rDevice.DrawBitmap(aBox.TopLeft(), aPreview); } + + // The slide previews of the slide sorter and of the slide panel show the + // document content on screen, so they need the screen watermark too. + humonical::PaintWatermarkScaled(rDevice, mpViewShellBase, aBox); } Bitmap PageObjectPainter::CreateMarkedPreview ( diff --git a/sd/source/ui/view/ViewShellBase.cxx b/sd/source/ui/view/ViewShellBase.cxx index 2994a7f90..802f2d5bd 100644 --- a/sd/source/ui/view/ViewShellBase.cxx +++ b/sd/source/ui/view/ViewShellBase.cxx @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -414,7 +415,12 @@ void ViewShellBase::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) { const sal_uInt16 nStartingSlide = GetDocument() ? GetDocument()->GetStartWithPresentation() : 0; - if (nStartingSlide) + // Auto-starting the show (.pps/.ppsx, or the StartWithPresentation + // load argument) goes through the SfxDispatcher, which bypasses the + // UNO dispatch a disabled command is filtered in. Honour the + // disabled-commands configuration here so that deployments which + // switch the slide show off do not get one through the back door. + if (nStartingSlide && !SvtCommandOptions().LookupDisabled(u"Presentation"_ustr)) { SfxUInt16Item aItem(FN_PARAM_1, nStartingSlide); GetViewFrame().GetDispatcher()->ExecuteList( @@ -1204,6 +1210,23 @@ ViewShellBase::Implementation::~Implementation() mpToolBarManager.reset(); } +void ViewShellBase::ScreenWatermarkChanged() +{ + std::shared_ptr pMainViewShell = GetMainViewShell(); + if (!pMainViewShell) + return; + + if (::sd::Window* pWindow = pMainViewShell->GetContentWindow()) + { + pWindow->Invalidate(); + pWindow->PaintImmediately(); + } + else + { + pMainViewShell->InvalidateWindows(); + } +} + void ViewShellBase::Implementation::LateInit() { mpController = new DrawController(mrBase); diff --git a/sd/source/ui/view/humonicalwatermark.cxx b/sd/source/ui/view/humonicalwatermark.cxx new file mode 100644 index 000000000..00d2843e9 --- /dev/null +++ b/sd/source/ui/view/humonicalwatermark.cxx @@ -0,0 +1,131 @@ +/* -*- 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/. + */ + +#include + +#include +#include +#include +#include + +#include + +namespace +{ +constexpr sal_Int32 WATERMARK_DEFAULT_FONT_SIZE_PT = 30; +constexpr tools::Long WATERMARK_FONT_PIXEL_AT_DEFAULT = 28; +constexpr tools::Long WATERMARK_MARGIN_PX = 520; + +/** Common guards. Returns the item only when the watermark has to be painted. */ +const SfxWatermarkItem* lcl_GetWatermark(const OutputDevice& rDevice, + const SfxViewShell* pViewShell) +{ + if (!pViewShell || rDevice.GetOutDevType() == OUTDEV_PRINTER + || rDevice.GetOutDevType() == OUTDEV_PDF || rDevice.GetConnectMetaFile()) + { + return nullptr; + } + + const SfxWatermarkItem* pWatermark = pViewShell->GetScreenWatermark(); + if (pViewShell->IsScreenWatermarkPaused() || !pWatermark || pWatermark->GetText().isEmpty()) + return nullptr; + + return pWatermark; +} + +/** Shared tail of the three entry points: build the parameters and blend. + + The metrics arrive in pixels because the blender works in pixel map mode. +*/ +void lcl_Paint(OutputDevice& rDevice, const SfxWatermarkItem& rWatermark, + const tools::Rectangle& rAnchor, const tools::Rectangle& rTarget, + tools::Long nFontHeightPx, tools::Long nLineStepPx, tools::Long nMarginPx) +{ + vcl::humonical::WatermarkParams aParams; + aParams.aText = rWatermark.GetText(); + // Deliberately unmixed: the blender needs the colour and the transparency + // separately to derive its multiply and screen sources. + aParams.aColor = rWatermark.GetColor(); + aParams.nTransparency = rWatermark.GetTransparency(); + aParams.nLineStepPx = std::max(1, nLineStepPx); + aParams.nMarginPx = std::max(1, nMarginPx); + + aParams.aFont.SetFamilyName(rWatermark.GetFont()); + aParams.aFont.SetFamily(FAMILY_SWISS); + aParams.aFont.SetWeight(WEIGHT_BOLD); + aParams.aFont.SetFontHeight(std::max(1, nFontHeightPx)); + aParams.aFont.SetOrientation(Degree10(static_cast(rWatermark.GetAngle() * 10))); + + vcl::humonical::PaintBlendedWatermark(rDevice, rAnchor, rTarget, aParams); +} +} + +namespace sd::humonical +{ +void PaintWatermark(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rPageRect, const vcl::Region* pPaintRegion) +{ + if (rPageRect.IsEmpty()) + return; + + const SfxWatermarkItem* pWatermark = lcl_GetWatermark(rDevice, pViewShell); + if (!pWatermark) + return; + + // The page anchors the pattern; the redraw region bounds the read-back. + tools::Rectangle aTarget(rPageRect); + if (pPaintRegion && !pPaintRegion->IsEmpty()) + aTarget.Intersection(pPaintRegion->GetBoundRect()); + if (aTarget.IsEmpty()) + return; + + lcl_Paint(rDevice, *pWatermark, rPageRect, aTarget, + pWatermark->GetFontSizePt() * WATERMARK_FONT_PIXEL_AT_DEFAULT + / WATERMARK_DEFAULT_FONT_SIZE_PT, + pWatermark->GetLineGapPx(), WATERMARK_MARGIN_PX); +} + +void PaintWatermarkScaled(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rRect) +{ + if (rRect.IsEmpty()) + return; + + const SfxWatermarkItem* pWatermark = lcl_GetWatermark(rDevice, pViewShell); + if (!pWatermark) + return; + + // Derive the metrics from the target rectangle so a thumbnail gets a + // proportionally small pattern, matching the proportions the full-size + // watermark has on a slide. + const tools::Rectangle aRectPx(rDevice.LogicToPixel(rRect)); + lcl_Paint(rDevice, *pWatermark, rRect, rRect, aRectPx.GetHeight() / 9, aRectPx.GetHeight() / 3, + aRectPx.GetWidth()); +} + +void PaintWatermarkScreen(OutputDevice& rDevice, const SfxViewShell* pViewShell, + const tools::Rectangle& rRect) +{ + if (rRect.IsEmpty()) + return; + + const SfxWatermarkItem* pWatermark = lcl_GetWatermark(rDevice, pViewShell); + if (!pWatermark) + return; + + // Screen density like the Calc grid watermark: the metrics are fixed in + // pixels, so the pattern keeps its size whatever map mode the view uses. + lcl_Paint(rDevice, *pWatermark, rRect, rRect, + pWatermark->GetFontSizePt() * WATERMARK_FONT_PIXEL_AT_DEFAULT + / WATERMARK_DEFAULT_FONT_SIZE_PT, + pWatermark->GetLineGapPx(), WATERMARK_MARGIN_PX); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/view/outlview.cxx b/sd/source/ui/view/outlview.cxx index ad33f0a01..19288de4a 100644 --- a/sd/source/ui/view/outlview.cxx +++ b/sd/source/ui/view/outlview.cxx @@ -66,6 +66,8 @@ #include #include #include +#include +#include #include #include #include @@ -205,6 +207,12 @@ void OutlineView::Paint(const ::tools::Rectangle& rRect, ::sd::Window const * pW xProcessor->process(aCursorOverlay.getOverlayObjectPrimitive2DSequence()); } + // The outline view shows the text of all slides, so it needs the screen + // watermark as well. It has no page frame - use the visible window area. + humonical::PaintWatermarkScreen( + *pTarget, &mrOutlineViewShell.GetViewShellBase(), + pTarget->PixelToLogic(::tools::Rectangle(Point(), pTarget->GetOutputSizePixel()))); + pOlView->ShowCursor(mbFirstPaint); mbFirstPaint = false; } diff --git a/sd/source/ui/view/sdview.cxx b/sd/source/ui/view/sdview.cxx index 218e8c1ad..7b9b805b0 100644 --- a/sd/source/ui/view/sdview.cxx +++ b/sd/source/ui/view/sdview.cxx @@ -64,9 +64,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -499,6 +501,18 @@ void View::CompleteRedraw(OutputDevice* pOutDev, const vcl::Region& rReg, sdr::c FmFormView::CompleteRedraw(pOutDev, rReg, pRedirector ? pRedirector : &aViewRedirector); } +void View::EndCompleteRedraw(SdrPaintWindow& rPaintWindow, bool bPaintFormLayer, + sdr::contact::ViewObjectContactRedirector* pRedirector) +{ + if (SdrPageView* pPgView = GetSdrPageView()) + { + ::sd::humonical::PaintWatermark(rPaintWindow.GetTargetOutputDevice(), GetSfxViewShell(), + pPgView->GetPageRect(), &rPaintWindow.GetRedrawRegion()); + } + + FmFormView::EndCompleteRedraw(rPaintWindow, bPaintFormLayer, pRedirector); +} + void View::MarkListHasChanged() { FmFormView::MarkListHasChanged(); diff --git a/sfx2/sdi/sfx.sdi b/sfx2/sdi/sfx.sdi index 76af1e208..0b49bc58d 100644 --- a/sfx2/sdi/sfx.sdi +++ b/sfx2/sdi/sfx.sdi @@ -5668,6 +5668,47 @@ SfxVoidItem ActivateStyleApply SID_ACTIVATE_STYLE_APPLY GroupId = SfxGroupId::Template; ] +SfxVoidItem HumonicalScreenWatermark SID_HUMONICAL_SCREEN_WATERMARK +(SfxStringItem Text SID_HUMONICAL_SCREEN_WATERMARK, + SfxInt32Item FontSizePt SID_HUMONICAL_SCREEN_WATERMARK_FONT_SIZE_PT, + SfxInt32Item LineGapPx SID_HUMONICAL_SCREEN_WATERMARK_LINE_GAP_PX, + SfxInt32Item Color SID_HUMONICAL_SCREEN_WATERMARK_COLOR) +[ + AutoUpdate = FALSE, + FastCall = FALSE, + ReadOnlyDoc = TRUE, + Toggle = FALSE, + Container = FALSE, + RecordAbsolute = FALSE, + RecordPerSet; + Asynchron; + + AccelConfig = FALSE, + MenuConfig = FALSE, + ToolBoxConfig = FALSE, + GroupId = SfxGroupId::View; +] + +SfxVoidItem HumonicalNotebookbarPolicy SID_HUMONICAL_NOTEBOOKBAR_POLICY +(SfxStringItem HiddenPages SID_HUMONICAL_NOTEBOOKBAR_HIDDEN_PAGES, + SfxStringItem HiddenWidgets SID_HUMONICAL_NOTEBOOKBAR_HIDDEN_WIDGETS, + SfxBoolItem Restore SID_HUMONICAL_NOTEBOOKBAR_RESTORE) +[ + AutoUpdate = FALSE, + FastCall = FALSE, + ReadOnlyDoc = TRUE, + Toggle = FALSE, + Container = FALSE, + RecordAbsolute = FALSE, + RecordPerSet; + Asynchron; + + AccelConfig = FALSE, + MenuConfig = FALSE, + ToolBoxConfig = FALSE, + GroupId = SfxGroupId::View; +] + SfxBoolItem DockingWindow0 SID_DOCKWIN_0 diff --git a/sfx2/sdi/viwslots.sdi b/sfx2/sdi/viwslots.sdi index 111180b30..4e6e72625 100644 --- a/sfx2/sdi/viwslots.sdi +++ b/sfx2/sdi/viwslots.sdi @@ -83,6 +83,14 @@ interface View ExecMethod = ExecMisc_Impl ; StateMethod = GetState_Impl ; ] + SID_HUMONICAL_SCREEN_WATERMARK + [ + ExecMethod = ExecMisc_Impl ; + ] + SID_HUMONICAL_NOTEBOOKBAR_POLICY + [ + ExecMethod = ExecMisc_Impl ; + ] } diff --git a/sfx2/source/doc/objstor.cxx b/sfx2/source/doc/objstor.cxx index 9c23ef5fd..07df1f494 100644 --- a/sfx2/source/doc/objstor.cxx +++ b/sfx2/source/doc/objstor.cxx @@ -4237,6 +4237,22 @@ bool SfxObjectShell::GetMacroCallsSeenWhileLoading() const return false; } +void SfxObjectShell::SetScreenWatermarkState( const SfxWatermarkItem* pWatermark ) +{ + pImpl->m_bScreenWatermarkStateSet = true; + pImpl->m_xScreenWatermarkState.reset(pWatermark ? pWatermark->Clone() : nullptr); +} + +bool SfxObjectShell::HasScreenWatermarkState() const +{ + return pImpl->m_bScreenWatermarkStateSet; +} + +const SfxWatermarkItem* SfxObjectShell::GetScreenWatermarkState() const +{ + return pImpl->m_xScreenWatermarkState.get(); +} + bool SfxObjectShell::QuerySaveSizeExceededModules_Impl( const uno::Reference< task::XInteractionHandler >& xHandler ) { #if !HAVE_FEATURE_SCRIPTING diff --git a/sfx2/source/doc/watermarkitem.cxx b/sfx2/source/doc/watermarkitem.cxx index b5a2846ce..fe391598f 100644 --- a/sfx2/source/doc/watermarkitem.cxx +++ b/sfx2/source/doc/watermarkitem.cxx @@ -14,10 +14,12 @@ SfxWatermarkItem::SfxWatermarkItem() : SfxPoolItem( SID_WATERMARK ) , m_aText( u""_ustr ) -, m_aFont( u"Liberation Sans"_ustr ) +, m_aFont( u"Tahoma"_ustr ) , m_nAngle( 45 ) , m_nTransparency( 50 ) , m_nColor( 0xc0c0c0 ) +, m_nFontSizePt( 30 ) +, m_nLineGapPx( 180 ) { } @@ -33,7 +35,9 @@ bool SfxWatermarkItem::operator==( const SfxPoolItem& rCmp ) const m_aFont == static_cast(rCmp).m_aFont && m_nAngle == static_cast(rCmp).m_nAngle && m_nTransparency == static_cast(rCmp).m_nTransparency && - m_nColor == static_cast(rCmp).m_nColor ); + m_nColor == static_cast(rCmp).m_nColor && + m_nFontSizePt == static_cast(rCmp).m_nFontSizePt && + m_nLineGapPx == static_cast(rCmp).m_nLineGapPx ); } SfxWatermarkItem* SfxWatermarkItem::Clone( SfxItemPool *) const @@ -49,6 +53,8 @@ bool SfxWatermarkItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ { "Angle", css::uno::Any( m_nAngle ) }, { "Transparency", css::uno::Any( m_nTransparency ) }, { "Color", css::uno::Any( m_nColor ) }, + { "FontSizePt", css::uno::Any( m_nFontSizePt ) }, + { "LineGapPx", css::uno::Any( m_nLineGapPx ) }, } ); return true; @@ -71,7 +77,17 @@ bool SfxWatermarkItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberI if(aEntry.Name == "Transparency") aEntry.Value >>= m_nTransparency; if(aEntry.Name == "Color") - aEntry.Value >>= m_nColor; + { + sal_Int32 nColor = 0; + if (aEntry.Value >>= nColor) + m_nColor = Color(static_cast(nColor) & 0x00ffffff); + else + aEntry.Value >>= m_nColor; + } + if(aEntry.Name == "FontSizePt") + aEntry.Value >>= m_nFontSizePt; + if(aEntry.Name == "LineGapPx") + aEntry.Value >>= m_nLineGapPx; } return true; } diff --git a/sfx2/source/inc/objshimp.hxx b/sfx2/source/inc/objshimp.hxx index 1e0680cd6..93d087155 100644 --- a/sfx2/source/inc/objshimp.hxx +++ b/sfx2/source/inc/objshimp.hxx @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -136,6 +137,10 @@ struct SfxObjectShell_Impl final : public ::sfx2::IMacroDocumentAccess mutable oslInterlockedCount m_nClosingLockLevel = 0; mutable bool m_bCloseModelScheduled = false; + // Humonical screen watermark master state, see SfxObjectShell::SetScreenWatermarkState + bool m_bScreenWatermarkStateSet = false; + std::unique_ptr m_xScreenWatermarkState; + SfxObjectShell_Impl( SfxObjectShell& _rDocShell ); ~SfxObjectShell_Impl(); diff --git a/sfx2/source/notebookbar/SfxNotebookBar.cxx b/sfx2/source/notebookbar/SfxNotebookBar.cxx index 4e8f392e9..a11e627bd 100644 --- a/sfx2/source/notebookbar/SfxNotebookBar.cxx +++ b/sfx2/source/notebookbar/SfxNotebookBar.cxx @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -104,6 +105,28 @@ public: } }; +SfxViewFrame* lcl_getViewFrame(const Reference& xFrame) +{ + if (!xFrame.is()) + return nullptr; + + for (SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst(nullptr, false); pViewFrame; + pViewFrame = SfxViewFrame::GetNext(*pViewFrame, nullptr, false)) + { + if (pViewFrame->GetFrame().GetFrameInterface() == xFrame) + return pViewFrame; + } + return nullptr; +} + +NotebookBar* lcl_getNotebookBar(SfxViewFrame& rViewFrame) +{ + SystemWindow* pSystemWindow = rViewFrame.GetWindow().GetSystemWindow(); + if (!pSystemWindow) + return nullptr; + return pSystemWindow->GetNotebookBar().get(); +} + } // end anonymous namespace static void NotebookbarAddonValues( @@ -546,6 +569,8 @@ bool SfxNotebookBar::StateMethod(SystemWindow* pSysWindow, pNotebookBar->SetupListener(true); } } + if (SfxViewFrame* pViewFrame = lcl_getViewFrame(xFrame)) + ApplyNotebookBarPolicy(*pViewFrame); return true; } @@ -693,6 +718,34 @@ void SfxNotebookBar::ReloadNotebookBar(std::u16string_view sUIPath) sfx2::SfxNotebookBar::StateMethod(pViewShell->GetViewFrame().GetBindings(), sUIPath, true); } +void SfxNotebookBar::SetNotebookBarPolicy(SfxViewFrame& rViewFrame, + std::vector aHiddenPages, + std::vector aHiddenWidgets) +{ + rViewFrame.SetNotebookBarPolicy(std::move(aHiddenPages), std::move(aHiddenWidgets)); + ApplyNotebookBarPolicy(rViewFrame); +} + +void SfxNotebookBar::ClearNotebookBarPolicy(SfxViewFrame& rViewFrame) +{ + if (NotebookBar* pNotebookBar = lcl_getNotebookBar(rViewFrame)) + pNotebookBar->ClearNotebookBarPolicy(); + rViewFrame.ClearNotebookBarPolicy(); +} + +void SfxNotebookBar::ApplyNotebookBarPolicy(SfxViewFrame& rViewFrame) +{ + NotebookBar* pNotebookBar = lcl_getNotebookBar(rViewFrame); + if (!pNotebookBar) + return; + + if (rViewFrame.HasNotebookBarPolicy()) + pNotebookBar->SetNotebookBarPolicy(rViewFrame.GetNotebookBarHiddenPages(), + rViewFrame.GetNotebookBarHiddenWidgets()); + else + pNotebookBar->ClearNotebookBarPolicy(); +} + IMPL_STATIC_LINK(SfxNotebookBar, VclDisposeHdl, const SfxViewShell*, pViewShell, void) { NotebookBarViewManager::get().removeViewData(pViewShell); diff --git a/sfx2/source/view/impviewframe.hxx b/sfx2/source/view/impviewframe.hxx index 18675d48e..9b2521f76 100644 --- a/sfx2/source/view/impviewframe.hxx +++ b/sfx2/source/view/impviewframe.hxx @@ -43,6 +43,9 @@ struct SfxViewFrame_Impl bool bEnabled:1; bool bWindowWasEnabled:1; OUString aFactoryName; + std::vector aNotebookBarHiddenPages; + std::vector aNotebookBarHiddenWidgets; + bool bNotebookBarPolicyActive; explicit SfxViewFrame_Impl(SfxFrame& i_rFrame) : rFrame(i_rFrame) @@ -56,6 +59,7 @@ struct SfxViewFrame_Impl , bModal(false) , bEnabled(false) , bWindowWasEnabled(true) + , bNotebookBarPolicyActive(false) { } }; diff --git a/sfx2/source/view/viewfrm.cxx b/sfx2/source/view/viewfrm.cxx index 977c2e928..28b81199c 100644 --- a/sfx2/source/view/viewfrm.cxx +++ b/sfx2/source/view/viewfrm.cxx @@ -20,6 +20,8 @@ #include #include +#include + #include #include #include @@ -2965,6 +2967,36 @@ SfxViewFrame* SfxViewFrame::GetTopViewFrame() const return GetFrame().GetCurrentViewFrame(); } +void SfxViewFrame::SetNotebookBarPolicy(std::vector aHiddenPages, + std::vector aHiddenWidgets) +{ + m_pImpl->aNotebookBarHiddenPages = std::move(aHiddenPages); + m_pImpl->aNotebookBarHiddenWidgets = std::move(aHiddenWidgets); + m_pImpl->bNotebookBarPolicyActive = true; +} + +void SfxViewFrame::ClearNotebookBarPolicy() +{ + m_pImpl->aNotebookBarHiddenPages.clear(); + m_pImpl->aNotebookBarHiddenWidgets.clear(); + m_pImpl->bNotebookBarPolicyActive = false; +} + +bool SfxViewFrame::HasNotebookBarPolicy() const +{ + return m_pImpl->bNotebookBarPolicyActive; +} + +const std::vector& SfxViewFrame::GetNotebookBarHiddenPages() const +{ + return m_pImpl->aNotebookBarHiddenPages; +} + +const std::vector& SfxViewFrame::GetNotebookBarHiddenWidgets() const +{ + return m_pImpl->aNotebookBarHiddenWidgets; +} + vcl::Window& SfxViewFrame::GetWindow() const { return m_pImpl->pWindow ? *m_pImpl->pWindow : GetFrame().GetWindow(); diff --git a/sfx2/source/view/viewimp.hxx b/sfx2/source/view/viewimp.hxx index 83279ae84..b6ca6040b 100644 --- a/sfx2/source/view/viewimp.hxx +++ b/sfx2/source/view/viewimp.hxx @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,8 @@ struct SfxViewShell_Impl const ViewShellId m_nViewShellId; const ViewShellDocId m_nDocId; svl::crypto::CertificateOrName m_aSigningCertificate; + std::unique_ptr m_xScreenWatermark; + bool m_bScreenWatermarkPaused = false; explicit SfxViewShell_Impl(SfxViewShellFlags const nFlags, ViewShellDocId nDocId); ~SfxViewShell_Impl(); diff --git a/sfx2/source/view/viewprn.cxx b/sfx2/source/view/viewprn.cxx index a49a4d9d9..99eae72fe 100644 --- a/sfx2/source/view/viewprn.cxx +++ b/sfx2/source/view/viewprn.cxx @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -168,6 +169,32 @@ SfxPrinterController::SfxPrinterController( const VclPtr& i_rPrinter, setValue( u"IsDirect"_ustr, Any( i_bDirect ) ); setValue( u"IsPrinter"_ustr, Any( true ) ); setValue( u"View"_ustr, i_rViewProp ); + + // Humonical: forward the view's screen watermark so the print dialog's + // on-screen preview pane can draw it (vcl PrintPreviewWindow). These + // values are only read by the dialog preview; the actual print output + // plays the page metafiles directly and stays clean. + if (mpViewShell && !mpViewShell->IsScreenWatermarkPaused()) + { + if (const SfxWatermarkItem* pWatermark = mpViewShell->GetScreenWatermark()) + { + if (!pWatermark->GetText().isEmpty()) + { + setValue(u"HumonicalScreenWatermarkText"_ustr, Any(pWatermark->GetText())); + setValue(u"HumonicalScreenWatermarkFont"_ustr, Any(pWatermark->GetFont())); + setValue(u"HumonicalScreenWatermarkAngle"_ustr, + Any(sal_Int32(pWatermark->GetAngle()))); + setValue(u"HumonicalScreenWatermarkTransparency"_ustr, + Any(sal_Int32(pWatermark->GetTransparency()))); + setValue(u"HumonicalScreenWatermarkColor"_ustr, + Any(sal_Int32(sal_uInt32(pWatermark->GetColor())))); + setValue(u"HumonicalScreenWatermarkFontSizePt"_ustr, + Any(pWatermark->GetFontSizePt())); + setValue(u"HumonicalScreenWatermarkLineGapPx"_ustr, + Any(pWatermark->GetLineGapPx())); + } + } + } } void SfxPrinterController::Notify( SfxBroadcaster& , const SfxHint& rHint ) diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx index da3ea1091..b73cae66d 100644 --- a/sfx2/source/view/viewsh.cxx +++ b/sfx2/source/view/viewsh.cxx @@ -19,6 +19,8 @@ #include +#include + #include #include @@ -108,6 +110,8 @@ #include #include #include +#include +#include #include #include #include @@ -2056,6 +2060,80 @@ void SfxViewShell::ExecMisc_Impl( SfxRequest &rReq ) } break; } + case SID_HUMONICAL_SCREEN_WATERMARK: + { + const SfxStringItem* pTextItem = rReq.GetArg(nId); + if (pTextItem && pTextItem->GetValue().isEmpty()) + { + ClearScreenWatermark(); + } + else + { + std::unique_ptr xWatermark( + GetScreenWatermark() ? GetScreenWatermark()->Clone() : new SfxWatermarkItem); + if (pTextItem) + xWatermark->SetText(pTextItem->GetValue()); + + const SfxInt32Item* pFontSizeItem + = rReq.GetArg(SID_HUMONICAL_SCREEN_WATERMARK_FONT_SIZE_PT); + if (pFontSizeItem) + xWatermark->SetFontSizePt(std::max( + 1, std::min(300, pFontSizeItem->GetValue()))); + + const SfxInt32Item* pLineGapItem + = rReq.GetArg(SID_HUMONICAL_SCREEN_WATERMARK_LINE_GAP_PX); + if (pLineGapItem) + xWatermark->SetLineGapPx(std::max( + 1, std::min(5000, pLineGapItem->GetValue()))); + + const SfxInt32Item* pColorItem + = rReq.GetArg(SID_HUMONICAL_SCREEN_WATERMARK_COLOR); + if (pColorItem) + xWatermark->SetColor(Color(static_cast(pColorItem->GetValue()) + & 0x00ffffff)); + + SetScreenWatermark(*xWatermark); + } + + rReq.Done(); + break; + } + case SID_HUMONICAL_NOTEBOOKBAR_POLICY: + { + auto parseList = [](const OUString& rValue) { + std::vector aValues; + sal_Int32 nIndex = 0; + do + { + OUString aValue = rValue.getToken(0, ';', nIndex).trim(); + if (!aValue.isEmpty() + && std::find(aValues.begin(), aValues.end(), aValue) == aValues.end()) + aValues.push_back(std::move(aValue)); + } + while (nIndex >= 0); + return aValues; + }; + + const SfxBoolItem* pRestoreItem + = rReq.GetArg(SID_HUMONICAL_NOTEBOOKBAR_RESTORE); + if (pRestoreItem && pRestoreItem->GetValue()) + { + sfx2::SfxNotebookBar::ClearNotebookBarPolicy(GetViewFrame()); + } + else + { + const SfxStringItem* pPagesItem + = rReq.GetArg(SID_HUMONICAL_NOTEBOOKBAR_HIDDEN_PAGES); + const SfxStringItem* pWidgetsItem + = rReq.GetArg(SID_HUMONICAL_NOTEBOOKBAR_HIDDEN_WIDGETS); + sfx2::SfxNotebookBar::SetNotebookBarPolicy( + GetViewFrame(), parseList(pPagesItem ? pPagesItem->GetValue() : OUString()), + parseList(pWidgetsItem ? pWidgetsItem->GetValue() : OUString())); + } + + rReq.Done(); + break; + } case SID_ACTIVATE_STYLE_APPLY: { uno::Reference< frame::XFrame > xFrame = @@ -2755,6 +2833,23 @@ SfxViewShell::SfxViewShell , mbLOKAccessibilityEnabled(false) , mbLOKColorPreviewEnabled(false) { + // Take over the per-document screen watermark state if one was stored; + // otherwise start with the built-in default. Without this, switching the + // view shell (e.g. Calc print preview and back) would reset a watermark + // set through SID_HUMONICAL_SCREEN_WATERMARK back to the default. + SfxObjectShell* pWatermarkDocSh = rViewFrame.GetObjectShell(); + if (pWatermarkDocSh && pWatermarkDocSh->HasScreenWatermarkState()) + { + if (const SfxWatermarkItem* pDocWatermark = pWatermarkDocSh->GetScreenWatermarkState()) + pImpl->m_xScreenWatermark.reset(pDocWatermark->Clone()); + } + else + { + SfxWatermarkItem aDefaultScreenWatermark; + aDefaultScreenWatermark.SetText(u"CONFIDENTIAL"_ustr); + pImpl->m_xScreenWatermark.reset(aDefaultScreenWatermark.Clone()); + } + SetMargin( rViewFrame.GetMargin_Impl() ); SetPool( &rViewFrame.GetObjectShell()->GetPool() ); @@ -2828,6 +2923,51 @@ svl::crypto::CertificateOrName SfxViewShell::GetSigningCertificate() const return pImpl->m_aSigningCertificate; } +void SfxViewShell::SetScreenWatermark(const SfxWatermarkItem& rWatermark) +{ + pImpl->m_xScreenWatermark.reset(rWatermark.Clone()); + if (SfxObjectShell* pDocSh = GetObjectShell()) + pDocSh->SetScreenWatermarkState(pImpl->m_xScreenWatermark.get()); + ScreenWatermarkChanged(); +} + +void SfxViewShell::ClearScreenWatermark() +{ + pImpl->m_xScreenWatermark.reset(); + if (SfxObjectShell* pDocSh = GetObjectShell()) + pDocSh->SetScreenWatermarkState(nullptr); + ScreenWatermarkChanged(); +} + +const SfxWatermarkItem* SfxViewShell::GetScreenWatermark() const +{ + return pImpl->m_xScreenWatermark.get(); +} + +bool SfxViewShell::HasScreenWatermark() const +{ + return pImpl->m_xScreenWatermark != nullptr; +} + +void SfxViewShell::SetScreenWatermarkPaused(bool bPaused) +{ + pImpl->m_bScreenWatermarkPaused = bPaused; +} + +bool SfxViewShell::IsScreenWatermarkPaused() const +{ + return pImpl->m_bScreenWatermarkPaused; +} + +void SfxViewShell::ScreenWatermarkChanged() +{ + if (vcl::Window* pViewWindow = GetWindow()) + { + pViewWindow->Invalidate(); + pViewWindow->PaintImmediately(); + } +} + namespace { uno::Reference diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk index f887b4fa2..ad416cc6b 100644 --- a/sw/Library_sw.mk +++ b/sw/Library_sw.mk @@ -347,6 +347,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\ sw/source/core/layout/flypos \ sw/source/core/layout/frmtool \ sw/source/core/layout/ftnfrm \ + sw/source/core/layout/humonicalwatermark \ sw/source/core/layout/hffrm \ sw/source/core/layout/layact \ sw/source/core/layout/laycache \ diff --git a/sw/inc/view.hxx b/sw/inc/view.hxx index eaf65e763..87a8f6467 100644 --- a/sw/inc/view.hxx +++ b/sw/inc/view.hxx @@ -745,7 +745,6 @@ private: size_t m_nBringToAttentionBlinkTimeOutsRemaining; std::unique_ptr m_xBringToAttentionOverlayObject; - DECL_LINK(BringToAttentionBlinkTimerHdl, Timer*, void); public: @@ -756,6 +755,9 @@ public: /// See SfxViewShell::libreOfficeKitViewInvalidateTilesCallback(). void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode) const override; + +protected: + void ScreenWatermarkChanged() override; }; std::unique_ptr CreatePrintOptionsPage(weld::Container* pPage, weld::DialogController* pController, diff --git a/sw/source/core/inc/humonicalwatermark.hxx b/sw/source/core/inc/humonicalwatermark.hxx new file mode 100644 index 000000000..09b0cf43e --- /dev/null +++ b/sw/source/core/inc/humonicalwatermark.hxx @@ -0,0 +1,22 @@ +/* -*- 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 + +class SwPageFrame; +class SwRect; +class SwViewShell; + +namespace sw::humonical +{ +void PaintWatermarkUnderContent(const SwPageFrame& rPage, const SwRect& rPaintRect, + const SwViewShell& rViewShell); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/humonicalwatermark.cxx b/sw/source/core/layout/humonicalwatermark.cxx new file mode 100644 index 000000000..280eb50d7 --- /dev/null +++ b/sw/source/core/layout/humonicalwatermark.cxx @@ -0,0 +1,82 @@ +/* -*- 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/. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace +{ +constexpr sal_Int32 WATERMARK_DEFAULT_FONT_SIZE_PT = 30; +constexpr tools::Long WATERMARK_FONT_PIXEL_AT_DEFAULT = 28; +constexpr tools::Long WATERMARK_MARGIN_PX = 520; +} + +namespace sw::humonical +{ +void PaintWatermarkUnderContent(const SwPageFrame& rPage, const SwRect& rPaintRect, + const SwViewShell& rViewShell) +{ + OutputDevice* pOut = rViewShell.GetOut(); + const SfxViewShell* pSfxViewShell = rViewShell.GetSfxViewShell(); + // Note: print preview (rViewShell.IsPreview()) is intentionally NOT + // excluded - the screen watermark must be visible on the preview screen. + // Real print/PDF output stays clean via the guards below. + if (!pOut || !pSfxViewShell || !rViewShell.GetWin() || pOut->GetOutDevType() == OUTDEV_PRINTER + || pOut->GetConnectMetaFile() || rViewShell.GetViewOptions()->IsPDFExport()) + { + return; + } + + const SfxWatermarkItem* pWatermark = pSfxViewShell->GetScreenWatermark(); + if (pSfxViewShell->IsScreenWatermarkPaused() || !pWatermark || pWatermark->GetText().isEmpty()) + return; + + const SwRect aPageRect(rPage.getFrameArea()); + SwRect aTarget(aPageRect); + aTarget.Intersection_(rPaintRect); + if (!aTarget.HasArea()) + return; + + vcl::humonical::WatermarkParams aParams; + aParams.aText = pWatermark->GetText(); + // Deliberately unmixed: the blender needs the colour and the transparency + // separately to derive its multiply and screen sources. + aParams.aColor = pWatermark->GetColor(); + aParams.nTransparency = pWatermark->GetTransparency(); + aParams.nLineStepPx = std::max(1, pWatermark->GetLineGapPx()); + aParams.nMarginPx = WATERMARK_MARGIN_PX; + + aParams.aFont.SetFamilyName(pWatermark->GetFont()); + aParams.aFont.SetFamily(FAMILY_SWISS); + aParams.aFont.SetWeight(WEIGHT_BOLD); + aParams.aFont.SetFontHeight(std::max(1, pWatermark->GetFontSizePt() + * WATERMARK_FONT_PIXEL_AT_DEFAULT + / WATERMARK_DEFAULT_FONT_SIZE_PT)); + aParams.aFont.SetOrientation(Degree10(static_cast(pWatermark->GetAngle() * 10))); + + // The page anchors the pattern so that it does not shift between partial + // repaints, while the target is clipped to the repaint area: a page sized + // target would make the read-back allocate a page sized bitmap every time. + // Both rectangles stay in twips - the blender converts them itself. + vcl::humonical::PaintBlendedWatermark(*pOut, aPageRect.SVRect(), aTarget.SVRect(), aParams); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/paintfrm.cxx b/sw/source/core/layout/paintfrm.cxx index aa3452d7d..3504d7a24 100644 --- a/sw/source/core/layout/paintfrm.cxx +++ b/sw/source/core/layout/paintfrm.cxx @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -3437,6 +3438,7 @@ void SwRootFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& // paint special sub-lines gProp.pSSpecSubsLines->PaintSubsidiary( pSh->GetOut(), nullptr, gProp ); } + sw::humonical::PaintWatermarkUnderContent(*pPage, aPaintRect, *pSh); pPage->PaintSwFrame( rRenderContext, aPaintRect, isPaintHellOverHF ? PAINT_NON_HEADER_FOOTER : PAINT_ALL); // no paint of page border and shadow, if writer is in place mode. diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx index c00ed44bb..977f547b4 100644 --- a/sw/source/core/view/viewsh.cxx +++ b/sw/source/core/view/viewsh.cxx @@ -2093,10 +2093,12 @@ void SwViewShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectang SwViewShell::sbLstAct = true; GetLayout()->PaintSwFrame( rRenderContext, aRect ); SwViewShell::sbLstAct = false; + // --> OD 2009-08-12 #i101192# // end Pre/PostPaint encapsulation DLPostPaint2(true); // <-- + } } SwRootFrame::SetNoVirDev( false ); diff --git a/sw/source/uibase/docvw/edtwin2.cxx b/sw/source/uibase/docvw/edtwin2.cxx index 7da57f94f..08761baf5 100644 --- a/sw/source/uibase/docvw/edtwin2.cxx +++ b/sw/source/uibase/docvw/edtwin2.cxx @@ -771,6 +771,7 @@ void SwEditWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle comphelper::LibreOfficeKit::setTiledPainting(bTiledPainting); } pWrtShell->setOutputToWindow(false); + } } diff --git a/sw/source/uibase/uiview/view.cxx b/sw/source/uibase/uiview/view.cxx index 855712b00..c44ea9c33 100644 --- a/sw/source/uibase/uiview/view.cxx +++ b/sw/source/uibase/uiview/view.cxx @@ -2142,6 +2142,15 @@ void SwView::BringToAttention(const SwNode* pNode) BringToAttention(std::move(aRanges)); } +void SwView::ScreenWatermarkChanged() +{ + if (m_pEditWin) + { + m_pEditWin->Invalidate(); + m_pEditWin->PaintImmediately(); + } +} + IMPL_LINK_NOARG(SwView, BringToAttentionBlinkTimerHdl, Timer*, void) { if (GetDrawView() && m_xBringToAttentionOverlayObject) diff --git a/sw/source/uibase/uiview/viewport.cxx b/sw/source/uibase/uiview/viewport.cxx index 1323218b6..2ab27b032 100644 --- a/sw/source/uibase/uiview/viewport.cxx +++ b/sw/source/uibase/uiview/viewport.cxx @@ -191,6 +191,7 @@ void SwView::DocSzChgd(const Size &rSz) !GetViewFrame().GetFrame().IsInPlace()) OuterResizePixel( Point(), GetViewFrame().GetWindow().GetOutputSizePixel() ); + } // Set VisArea newly @@ -775,6 +776,12 @@ void SwView::EndScrollHdl(const weld::Scrollbar& rScrollbar, bool bHorizontal) else SetVisArea( aPos, false ); + if (HasScreenWatermark()) + { + SetScreenWatermarkPaused(false); + GetEditWin().Invalidate(); + } + GetViewFrame().GetBindings().Update(FN_STAT_PAGE); } @@ -1229,6 +1236,8 @@ bool SwView::HandleWheelCommands( const CommandEvent& rCEvt ) { // This influences whether quick help is shown m_bWheelScrollInProgress=true; + if (HasScreenWatermark()) + SetScreenWatermarkPaused(true); } if (pWData && (CommandWheelMode::SCROLL==pWData->GetMode()) && @@ -1245,6 +1254,13 @@ bool SwView::HandleWheelCommands( const CommandEvent& rCEvt ) // Restore default state for case when scroll command comes from dragging scrollbar handle m_bWheelScrollInProgress=false; + + if (HasScreenWatermark()) + { + SetScreenWatermarkPaused(false); + if (bOk) + GetEditWin().Invalidate(); + } } return bOk; } diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk index 1452533b7..b9587e77c 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -340,6 +340,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/source/gdi/graph \ vcl/source/gdi/graphictools \ vcl/source/gdi/hatch \ + vcl/source/gdi/humonicalblend \ vcl/source/gdi/impglyphitem \ vcl/source/gdi/impgraph \ vcl/source/gdi/jobset \ diff --git a/vcl/inc/printdlg.hxx b/vcl/inc/printdlg.hxx index 8071da7af..321204c5f 100644 --- a/vcl/inc/printdlg.hxx +++ b/vcl/inc/printdlg.hxx @@ -50,6 +50,8 @@ namespace vcl OUString maVertText; void preparePreviewBitmap(); + void drawScreenWatermark(vcl::RenderContext& rRenderContext, + const tools::Rectangle& rPageRect); public: PrintPreviewWindow(PrintDialog* pDialog); diff --git a/vcl/inc/win/salgdi.h b/vcl/inc/win/salgdi.h index 09c0a415a..1d9141b1b 100644 --- a/vcl/inc/win/salgdi.h +++ b/vcl/inc/win/salgdi.h @@ -70,6 +70,7 @@ public: BYTE GetCharSet() const { return meWinCharSet; } BYTE GetPitchAndFamily() const { return mnPitchAndFamily; } + const wchar_t* GetNativeFamilyName() const { return maLogFont.lfFaceName; } hb_blob_t* GetHbTable(hb_tag_t nTag) const override; diff --git a/vcl/source/control/notebookbar.cxx b/vcl/source/control/notebookbar.cxx index 724898cf5..c983c51d3 100644 --- a/vcl/source/control/notebookbar.cxx +++ b/vcl/source/control/notebookbar.cxx @@ -9,12 +9,14 @@ #include +#include #include #include #include #include #include +#include #include #include #include @@ -129,6 +131,58 @@ void NotebookBar::SetDisposeCallback(const Link rDisp m_pViewShell = pViewShell; } +void NotebookBar::SetNotebookBarPolicy(const std::vector& rHiddenPages, + const std::vector& rHiddenWidgets) +{ + for (NotebookbarContextControl* pControl : m_pContextContainers) + { + if (auto* pTabControl = dynamic_cast(pControl)) + pTabControl->SetHiddenPageNames(rHiddenPages); + } + + if (!m_pUIBuilder) + return; + + const std::set aHiddenWidgets(rHiddenWidgets.begin(), rHiddenWidgets.end()); + for (auto it = m_aPolicyWidgetVisibility.begin(); it != m_aPolicyWidgetVisibility.end();) + { + if (aHiddenWidgets.find(it->first) == aHiddenWidgets.end()) + { + m_pUIBuilder->set_widget_visibility(it->first, it->second); + it = m_aPolicyWidgetVisibility.erase(it); + } + else + ++it; + } + + for (const OUString& rWidgetId : aHiddenWidgets) + { + if (rWidgetId.isEmpty()) + continue; + + if (m_aPolicyWidgetVisibility.find(rWidgetId) == m_aPolicyWidgetVisibility.end()) + { + bool bVisible = false; + if (!m_pUIBuilder->get_widget_visibility(rWidgetId, bVisible)) + { + SAL_INFO("vcl.notebookbar", "Notebookbar policy widget not found: " << rWidgetId); + continue; + } + m_aPolicyWidgetVisibility.emplace(rWidgetId, bVisible); + } + + m_pUIBuilder->set_widget_visibility(rWidgetId, false); + } + + queue_resize(StateChangedType::Layout); +} + +void NotebookBar::ClearNotebookBarPolicy() +{ + SetNotebookBarPolicy({}, {}); + m_aPolicyWidgetVisibility.clear(); +} + NotebookBar::~NotebookBar() { disposeOnce(); diff --git a/vcl/source/control/tabctrl.cxx b/vcl/source/control/tabctrl.cxx index 741bc53de..b4ddc3e4e 100644 --- a/vcl/source/control/tabctrl.cxx +++ b/vcl/source/control/tabctrl.cxx @@ -1778,6 +1778,18 @@ void TabControl::SetPageVisible( sal_uInt16 nPageId, bool bVisible ) Invalidate(); } +bool TabControl::IsPageEnabled(sal_uInt16 nPageId) const +{ + const ImplTabItem* pItem = ImplGetItem(nPageId); + return pItem && pItem->m_bEnabled; +} + +bool TabControl::IsPageVisible(sal_uInt16 nPageId) const +{ + const ImplTabItem* pItem = ImplGetItem(nPageId); + return pItem && pItem->m_bVisible; +} + sal_uInt16 TabControl::GetPageCount() const { return static_cast(mpTabCtrlData->maItemList.size()); @@ -2211,7 +2223,11 @@ NotebookbarTabControlBase::~NotebookbarTabControlBase() void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext ) { if (eLastContext == eContext) + { + // Repeated callbacks must not make a policy-hidden page visible again. + ApplyHiddenPagePolicy(); return; + } bool bHandled = false; @@ -2257,6 +2273,100 @@ void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext ) // For unknown reason this is needed by the tabbed compact toolbar for other than gtk // vcl backends. Resize(); + ApplyHiddenPagePolicy(); +} + +bool NotebookbarTabControlBase::IsPolicyHiddenPage(sal_uInt16 nPageId) const +{ + const OUString aPageName = GetPageName(nPageId); + return std::find(m_aHiddenPageNames.begin(), m_aHiddenPageNames.end(), aPageName) + != m_aHiddenPageNames.end(); +} + +void NotebookbarTabControlBase::RestoreHiddenPageVisibility() +{ + for (const OUString& rPageName : m_aHiddenPageNames) + { + const sal_uInt16 nPageId = GetPageId(rPageName); + TabPage* pPage = GetTabPage(nPageId); + if (!nPageId || !pPage) + continue; + + SetPageVisible(nPageId, pPage->HasContext(eLastContext) + || pPage->HasContext(vcl::EnumContext::Context::Any)); + } +} + +void NotebookbarTabControlBase::ApplyHiddenPagePolicy() +{ + if (m_aHiddenPageNames.empty()) + return; + + const sal_uInt16 nCurrentPageId = GetCurPageId(); + bool bCanHideCurrent = true; + if (IsPolicyHiddenPage(nCurrentPageId)) + { + sal_uInt16 nFallbackPageId = 0; + const sal_uInt16 nCurrentPos = GetPagePos(nCurrentPageId); + + if (nCurrentPos != TAB_PAGE_NOTFOUND) + { + for (sal_uInt16 nPos = nCurrentPos + 1; nPos < GetPageCount(); ++nPos) + { + const sal_uInt16 nCandidate = GetPageId(nPos); + if (!IsPolicyHiddenPage(nCandidate) && IsPageVisible(nCandidate) + && IsPageEnabled(nCandidate)) + { + nFallbackPageId = nCandidate; + break; + } + } + + for (sal_Int32 nPos = nCurrentPos - 1; !nFallbackPageId && nPos >= 0; --nPos) + { + const sal_uInt16 nCandidate = GetPageId(static_cast(nPos)); + if (!IsPolicyHiddenPage(nCandidate) && IsPageVisible(nCandidate) + && IsPageEnabled(nCandidate)) + { + nFallbackPageId = nCandidate; + break; + } + } + } + + if (nFallbackPageId) + SelectTabPage(nFallbackPageId); + + bCanHideCurrent = nFallbackPageId && GetCurPageId() != nCurrentPageId; + if (!bCanHideCurrent) + SAL_WARN("vcl.control", "Notebookbar policy kept the current page visible because no safe fallback was available"); + } + + for (const OUString& rPageName : m_aHiddenPageNames) + { + const sal_uInt16 nPageId = GetPageId(rPageName); + if (nPageId && (nPageId != nCurrentPageId || bCanHideCurrent)) + SetPageVisible(nPageId, false); + } + + Resize(); +} + +void NotebookbarTabControlBase::SetHiddenPageNames(std::vector aHiddenPageNames) +{ + RestoreHiddenPageVisibility(); + + m_aHiddenPageNames.clear(); + for (OUString& rPageName : aHiddenPageNames) + { + if (!rPageName.isEmpty() + && std::find(m_aHiddenPageNames.begin(), m_aHiddenPageNames.end(), rPageName) + == m_aHiddenPageNames.end()) + m_aHiddenPageNames.push_back(std::move(rPageName)); + } + + if (!m_aHiddenPageNames.empty()) + ApplyHiddenPagePolicy(); } void NotebookbarTabControlBase::dispose() diff --git a/vcl/source/gdi/humonicalblend.cxx b/vcl/source/gdi/humonicalblend.cxx new file mode 100644 index 000000000..5fc38f37f --- /dev/null +++ b/vcl/source/gdi/humonicalblend.cxx @@ -0,0 +1,192 @@ +/* -*- 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/. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace +{ +// Luminance band in which the two branches are cross-faded. A hard threshold +// would leave a visible seam where a gradient background crosses it. +constexpr int WATERMARK_LUMA_LOW = 96; +constexpr int WATERMARK_LUMA_SPAN = 64; + +// Upper bound for how often the text is repeated within one line. +constexpr sal_Int32 WATERMARK_MAX_REPEAT = 64; + +/** Repeat rText until the line is at least nNeededWidth wide. + + rLayer must already have the watermark font set, since the width is + measured with it. +*/ +OUString lcl_MakeLineText(const OutputDevice& rLayer, const OUString& rText, + tools::Long nNeededWidth) +{ + OUStringBuffer aBuffer(rText); + for (sal_Int32 i = 0; i < WATERMARK_MAX_REPEAT; ++i) + { + if (rLayer.GetTextWidth(aBuffer.toString()) >= nNeededWidth) + break; + aBuffer.append(" "); + aBuffer.append(rText); + } + return aBuffer.makeStringAndClear(); +} +} + +namespace vcl::humonical +{ +void PaintBlendedWatermark(OutputDevice& rDevice, const tools::Rectangle& rAnchorRect, + const tools::Rectangle& rTargetRect, const WatermarkParams& rParams) +{ + if (rParams.aText.isEmpty() || rParams.nTransparency >= 100 + || rDevice.GetOutDevType() == OUTDEV_PRINTER || rDevice.GetOutDevType() == OUTDEV_PDF + || rDevice.GetConnectMetaFile() || rAnchorRect.IsEmpty() || rTargetRect.IsEmpty()) + { + return; + } + + auto popIt = rDevice.ScopedPush(vcl::PushFlags::ALL); + + // Convert while the caller's map mode is still active; everything below + // works in pixels on this same device, so no coordinate space is mixed. + const tools::Rectangle aAnchorPx(rDevice.LogicToPixel(rAnchorRect)); + tools::Rectangle aTargetPx(rDevice.LogicToPixel(rTargetRect)); + const bool bHadClip = rDevice.IsClipRegion(); + const tools::Rectangle aClipPx( + bHadClip ? rDevice.LogicToPixel(rDevice.GetClipRegion().GetBoundRect()) + : tools::Rectangle()); + + rDevice.SetMapMode(MapMode(MapUnit::MapPixel)); + + // The read-back is sized from the target, never from the anchor: a page + // sized read-back would push GetBitmap onto its slow copy path on every + // repaint. + aTargetPx.Intersection(aAnchorPx); + if (bHadClip) + aTargetPx.Intersection(aClipPx); + if (aTargetPx.IsEmpty()) + return; + + const Size aLayerSize(aTargetPx.GetSize()); + + // Glyph coverage mask: white text on black. The colour is applied per + // pixel while blending, which is what keeps the hue in both branches. + ScopedVclPtrInstance pLayer(rDevice, DeviceFormat::WITHOUT_ALPHA); + if (!pLayer->SetOutputSizePixel(aLayerSize)) + return; + pLayer->SetMapMode(MapMode(MapUnit::MapPixel)); + pLayer->SetBackground(Wallpaper(COL_BLACK)); + pLayer->Erase(); + pLayer->SetFont(rParams.aFont); + pLayer->SetTextColor(COL_WHITE); + pLayer->SetTextFillColor(); + + const tools::Long nStep = std::max(1, rParams.nLineStepPx); + const tools::Long nStartX = aAnchorPx.Left() - rParams.nMarginPx; + const tools::Long nNeededWidth + = 2 * ((aTargetPx.Right() - nStartX) + aTargetPx.GetHeight() + rParams.nMarginPx); + const OUString aLineText(lcl_MakeLineText(*pLayer, rParams.aText, nNeededWidth)); + + // A rotated line rises to the right, so it covers a band above its + // baseline; lines whose band misses the layer can be skipped entirely. + const double fRadians = toRadians(rParams.aFont.GetOrientation()); + const tools::Long nRise = static_cast(std::abs(std::sin(fRadians)) * nNeededWidth); + const tools::Long nFontHeight = std::max(1, rParams.aFont.GetFontHeight()); + + // Line positions are derived from the anchor so that the pattern keeps its + // phase across partial repaints and scrolling. + const tools::Long nFirstY = aAnchorPx.Top() + rParams.nMarginPx - nStep; + const tools::Long nLastY + = aAnchorPx.Bottom() + rParams.nMarginPx + aAnchorPx.GetWidth() + nStep; + const tools::Long nLayerX = nStartX - aTargetPx.Left(); + + for (tools::Long nY = nFirstY; nY < nLastY; nY += nStep) + { + const tools::Long nLayerY = nY - aTargetPx.Top(); + if (nLayerY + nFontHeight < 0 || nLayerY - nRise > aLayerSize.Height()) + continue; + pLayer->DrawText(Point(nLayerX, nLayerY), aLineText); + } + + Bitmap aMask = pLayer->GetBitmap(Point(), aLayerSize); + Bitmap aDest = rDevice.GetBitmap(aTargetPx.TopLeft(), aLayerSize); + if (aMask.IsEmpty() || aDest.IsEmpty()) + return; + + const int nOpacity = std::clamp(100 - rParams.nTransparency, 0, 100); + const int nColorR = rParams.aColor.GetRed(); + const int nColorG = rParams.aColor.GetGreen(); + const int nColorB = rParams.aColor.GetBlue(); + + { + BitmapScopedReadAccess pMask(aMask); + BitmapScopedWriteAccess pDest(aDest); + if (!pMask || !pDest) + return; + + const tools::Long nHeight = std::min(pMask->Height(), pDest->Height()); + const tools::Long nWidth = std::min(pMask->Width(), pDest->Width()); + + // out = mix(screen, multiply) by destination luminance, where + // M = 255 - a*(255-C)/255 (white -> C) feeds multiply + // N = a*C/255 (black -> C) feeds screen + // and a = coverage * opacity. Both are identities at zero coverage. + auto blend = [nOpacity](int nDst, int nCoverage, int nColor, int nT) -> sal_uInt8 { + const int a = nCoverage * nOpacity / 100; + const int nM = 255 - a * (255 - nColor) / 255; + const int nN = a * nColor / 255; + const int nMul = nDst * nM / 255; + const int nScr = 255 - (255 - nDst) * (255 - nN) / 255; + return static_cast((nMul * nT + nScr * (255 - nT)) / 255); + }; + + for (tools::Long nY = 0; nY < nHeight; ++nY) + { + const Scanline pMaskLine = pMask->GetScanline(nY); + Scanline pDestLine = pDest->GetScanline(nY); + + for (tools::Long nX = 0; nX < nWidth; ++nX) + { + const BitmapColor aM = pMask->GetColorFromData(pMaskLine, nX); + // Zero coverage is an identity for both branches. Most of the + // area lies outside the glyphs, so this skips the bulk of the + // arithmetic. + if (aM.GetRed() == 0 && aM.GetGreen() == 0 && aM.GetBlue() == 0) + continue; + + const BitmapColor aD = pDest->GetColorFromData(pDestLine, nX); + const int nLuma = (77 * aD.GetRed() + 151 * aD.GetGreen() + 28 * aD.GetBlue()) >> 8; + const int nT + = std::clamp((nLuma - WATERMARK_LUMA_LOW) * 255 / WATERMARK_LUMA_SPAN, 0, 255); + + pDest->SetPixelOnData(pDestLine, nX, + BitmapColor(blend(aD.GetRed(), aM.GetRed(), nColorR, nT), + blend(aD.GetGreen(), aM.GetGreen(), nColorG, nT), + blend(aD.GetBlue(), aM.GetBlue(), nColorB, nT))); + } + } + } + + rDevice.DrawBitmap(aTargetPx.TopLeft(), aDest); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/builder.cxx b/vcl/source/window/builder.cxx index df072d404..00311c6b5 100644 --- a/vcl/source/window/builder.cxx +++ b/vcl/source/window/builder.cxx @@ -835,6 +835,8 @@ VclBuilder::~VclBuilder() void VclBuilder::disposeBuilder() { + m_aToolBoxItems.clear(); + for (std::vector::reverse_iterator aI = m_aChildren.rbegin(), aEnd = m_aChildren.rend(); aI != aEnd; ++aI) { @@ -1952,6 +1954,7 @@ VclPtr VclBuilder::makeObject(vcl::Window *pParent, const OUString } pToolBox->SetHelpId(nItemId, getHelpRoot() + id); + m_aToolBoxItems.emplace_back(id, pToolBox, nItemId); OUString sTooltip(extractTooltipText(rMap)); if (!sTooltip.isEmpty()) pToolBox->SetQuickHelpText(nItemId, sTooltip); @@ -3623,6 +3626,72 @@ vcl::Window *VclBuilder::get_widget_root() return m_aChildren.empty() ? nullptr : m_aChildren[0].m_pWindow.get(); } +bool VclBuilder::get_widget_visibility(std::u16string_view sID, bool& rVisible) +{ + for (const auto& rChild : m_aChildren) + { + if (rChild.m_sID == sID) + { + rVisible = rChild.m_pWindow->IsVisible(); + return true; + } + } + + for (const auto& rItem : m_aToolBoxItems) + { + if (rItem.m_sID == sID && rItem.m_pToolBox) + { + rVisible = rItem.m_pToolBox->IsItemVisible(rItem.m_nItemId); + return true; + } + } + + for (const auto& rMenu : m_aMenus) + { + const sal_uInt16 nItemId = rMenu.m_pMenu->GetItemId(sID); + if (nItemId != 0) + { + rVisible = rMenu.m_pMenu->IsItemPosVisible(rMenu.m_pMenu->GetItemPos(nItemId)); + return true; + } + } + + return false; +} + +bool VclBuilder::set_widget_visibility(std::u16string_view sID, bool bVisible) +{ + for (const auto& rChild : m_aChildren) + { + if (rChild.m_sID == sID) + { + rChild.m_pWindow->Show(bVisible); + return true; + } + } + + for (const auto& rItem : m_aToolBoxItems) + { + if (rItem.m_sID == sID && rItem.m_pToolBox) + { + rItem.m_pToolBox->ShowItem(rItem.m_nItemId, bVisible); + return true; + } + } + + for (const auto& rMenu : m_aMenus) + { + const sal_uInt16 nItemId = rMenu.m_pMenu->GetItemId(sID); + if (nItemId != 0) + { + rMenu.m_pMenu->ShowItem(nItemId, bVisible); + return true; + } + } + + return false; +} + void VclBuilder::resetParserState() { m_pVclParserState.reset(); diff --git a/vcl/source/window/printdlg.cxx b/vcl/source/window/printdlg.cxx index bbc8a7ee1..a64627092 100644 --- a/vcl/source/window/printdlg.cxx +++ b/vcl/source/window/printdlg.cxx @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,8 @@ #include +#include + using namespace vcl; using namespace com::sun::star; using namespace com::sun::star::uno; @@ -181,6 +184,11 @@ void PrintDialog::PrintPreviewWindow::Paint(vcl::RenderContext& rRenderContext, // a defined size or ::DrawOutDev aPreviewBitmap.Scale(maPreviewSize, BmpScaleFlag::BestQuality); rRenderContext.DrawBitmap(aOffset, aPreviewBitmap); + + // Humonical: the print dialog preview is a screen surface, so the + // screen-only watermark has to cover the page image here as well. + // The actual print job plays the metafile directly and is unaffected. + drawScreenWatermark(rRenderContext, tools::Rectangle(aOffset, maPreviewSize)); } tools::Rectangle aFrameRect(aOffset + Point(-1, -1), Size(maPreviewSize.Width() + 2, maPreviewSize.Height() + 2)); @@ -188,6 +196,67 @@ void PrintDialog::PrintPreviewWindow::Paint(vcl::RenderContext& rRenderContext, aDecorationView.DrawFrame(aFrameRect, DrawFrameStyle::Group); } +void PrintDialog::PrintPreviewWindow::drawScreenWatermark(vcl::RenderContext& rRenderContext, + const tools::Rectangle& rPageRect) +{ + // Humonical screen watermark for the preview pane. The parameters are + // forwarded by SfxPrinterController (sfx2/source/view/viewprn.cxx); when + // no watermark is active for the view, no text value is set. + if (!mpDialog || !mpDialog->maPController || rPageRect.IsEmpty()) + return; + + const vcl::PrinterController& rController = *mpDialog->maPController; + + OUString aText; + if (const css::beans::PropertyValue* pVal + = rController.getValue(u"HumonicalScreenWatermarkText"_ustr)) + pVal->Value >>= aText; + if (aText.isEmpty()) + return; + + OUString aFontName; + if (const css::beans::PropertyValue* pVal + = rController.getValue(u"HumonicalScreenWatermarkFont"_ustr)) + pVal->Value >>= aFontName; + + auto readInt = [&rController](const OUString& rName, sal_Int32 nDefault) { + if (const css::beans::PropertyValue* pVal = rController.getValue(rName)) + { + sal_Int32 nValue = nDefault; + if (pVal->Value >>= nValue) + return nValue; + } + return nDefault; + }; + const sal_Int32 nAngle = readInt(u"HumonicalScreenWatermarkAngle"_ustr, 45); + const sal_Int32 nTransparency + = std::clamp(readInt(u"HumonicalScreenWatermarkTransparency"_ustr, 50), 0, 100); + const sal_Int32 nColor = readInt(u"HumonicalScreenWatermarkColor"_ustr, 0xc0c0c0); + const sal_Int32 nFontSizePt = readInt(u"HumonicalScreenWatermarkFontSizePt"_ustr, 30); + const sal_Int32 nLineGapPx = readInt(u"HumonicalScreenWatermarkLineGapPx"_ustr, 180); + + if (nTransparency >= 100) + return; + + vcl::humonical::WatermarkParams aParams; + aParams.aText = aText; + // The colour stays untouched: the blender needs it together with the + // transparency to derive both of its branches. + aParams.aColor = Color(static_cast(nColor) & 0x00ffffff); + aParams.nTransparency = static_cast(nTransparency); + aParams.nLineStepPx = std::max(1, nLineGapPx); + + aParams.aFont.SetFamilyName(aFontName); + aParams.aFont.SetFamily(FAMILY_SWISS); + aParams.aFont.SetWeight(WEIGHT_BOLD); + // same pixel metrics as the Calc grid watermark: 28px per default 30pt + aParams.aFont.SetFontSize(Size(0, std::max(1, nFontSizePt * 28 / 30))); + aParams.aFont.SetOrientation(Degree10(static_cast(nAngle * 10))); + + // The paper image is the whole area here, so anchor and target coincide. + vcl::humonical::PaintBlendedWatermark(rRenderContext, rPageRect, rPageRect, aParams); +} + bool PrintDialog::PrintPreviewWindow::Command( const CommandEvent& rEvt ) { if( rEvt.GetCommand() == CommandEventId::Wheel ) diff --git a/vcl/win/gdi/salfont.cxx b/vcl/win/gdi/salfont.cxx index b6e3bd483..3a125efa2 100644 --- a/vcl/win/gdi/salfont.cxx +++ b/vcl/win/gdi/salfont.cxx @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -224,14 +225,17 @@ bool WinGlyphFallbackSubstititution::FindFontSubstitute(vcl::font::FontSelectPat std::unique_ptr pTestFontList = pFontCollection->GetFontFaceCollection(); // limit the count of fonts to be checked to prevent hangs static const int MAX_GFBFONT_COUNT = 600; - int nTestFontCount = pTestFontList->Count(); - if( nTestFontCount > MAX_GFBFONT_COUNT ) - nTestFontCount = MAX_GFBFONT_COUNT; bool bFound = false; - for( int i = 0; i < nTestFontCount; ++i ) + int nTestedFontCount = 0; + for (int i = 0; i < pTestFontList->Count() && nTestedFontCount < MAX_GFBFONT_COUNT; ++i) { vcl::font::PhysicalFontFace* pFace = pTestFontList->Get( i ); + const WinFontFace* pWinFontFace = static_cast(pFace); + if (pFace->GetFamilyName() + != OUString(o3tl::toU(pWinFontFace->GetNativeFamilyName()))) + continue; + ++nTestedFontCount; bFound = HasMissingChars( pFace, rMissingChars ); if( !bFound ) continue; @@ -250,6 +254,7 @@ struct ImplEnumInfo vcl::font::PhysicalFontCollection* mpList; OUString* mpName; bool mbPrinter; + std::map> maFamilyAliases; }; } @@ -660,7 +665,10 @@ void ImplGetLogFontFromFontSelect( const vcl::font::FontSelectPattern& rFont, { OUString aName; if (pFontFace) - aName = pFontFace->GetFamilyName(); + { + const WinFontFace* pWinFontData = static_cast(pFontFace); + aName = OUString(o3tl::toU(pWinFontData->GetNativeFamilyName())); + } else aName = rFont.GetFamilyName().getToken( 0, ';' ); @@ -881,11 +889,51 @@ static int CALLBACK SalEnumFontsProcExW( const LOGFONTW* lpelfe, return 1; } - rtl::Reference pData - = new WinFontFace(*reinterpret_cast(lpelfe), *pMetric); + const auto& rEnumFont = *reinterpret_cast(lpelfe); + rtl::Reference pData = new WinFontFace(rEnumFont, *pMetric); pInfo->mpList->Add( pData.get() ); SAL_INFO("vcl.fonts", "SalEnumFontsProcExW: font added: " << pData->GetFamilyName() << " " << pData->GetStyleName()); + + const bool bSfntFont = (nFontType & TRUETYPE_FONTTYPE) + || (pMetric->ntmFlags & (NTM_PS_OPENTYPE | NTM_TT_OPENTYPE)); + if (bSfntFont) + { + const LanguageTag aKorean(LANGUAGE_KOREAN); + auto [it, bInserted] = pInfo->maFamilyAliases.try_emplace(pData->GetFamilyName()); + if (bInserted) + { + auto addAlias = [&](vcl::font::NameID eNameId, bool bTypographic) + { + OUString aAlias = pData->GetName(eNameId, aKorean); + if (!aAlias.isEmpty() && aAlias != pData->GetFamilyName()) + it->second[aAlias] = it->second[aAlias] || bTypographic; + }; + addAlias(vcl::font::NAME_ID_FONT_FAMILY, false); + addAlias(vcl::font::NAME_ID_TYPOGRAPHIC_FAMILY, true); + } + + for (const auto& [rAlias, bTypographic] : it->second) + { + rtl::Reference pAliasData = new WinFontFace(rEnumFont, *pMetric); + pAliasData->SetFamilyName(rAlias); + + if (bTypographic) + { + OUString aStyle = pData->GetName( + vcl::font::NAME_ID_TYPOGRAPHIC_SUBFAMILY, aKorean); + if (aStyle.isEmpty()) + aStyle = pData->GetName(vcl::font::NAME_ID_TYPOGRAPHIC_SUBFAMILY); + if (!aStyle.isEmpty()) + pAliasData->SetStyleName(aStyle); + } + + pInfo->mpList->Add(pAliasData.get()); + SAL_INFO("vcl.fonts", + "SalEnumFontsProcExW: font alias added: " << rAlias << " -> " + << pData->GetFamilyName()); + } + } } return 1;