diff options
Diffstat (limited to 'sfx2/source/view')
-rw-r--r-- | sfx2/source/view/classificationcontroller.cxx | 364 | ||||
-rw-r--r-- | sfx2/source/view/classificationhelper.cxx | 988 | ||||
-rw-r--r-- | sfx2/source/view/frame.cxx | 720 | ||||
-rw-r--r-- | sfx2/source/view/frame2.cxx | 406 | ||||
-rw-r--r-- | sfx2/source/view/frmload.cxx | 826 | ||||
-rw-r--r-- | sfx2/source/view/impframe.hxx | 71 | ||||
-rw-r--r-- | sfx2/source/view/impviewframe.hxx | 81 | ||||
-rw-r--r-- | sfx2/source/view/ipclient.cxx | 1148 | ||||
-rw-r--r-- | sfx2/source/view/lokcharthelper.cxx | 369 | ||||
-rw-r--r-- | sfx2/source/view/lokhelper.cxx | 1066 | ||||
-rw-r--r-- | sfx2/source/view/lokstarmathhelper.cxx | 247 | ||||
-rw-r--r-- | sfx2/source/view/printer.cxx | 189 | ||||
-rw-r--r-- | sfx2/source/view/prnmon.hxx | 54 | ||||
-rw-r--r-- | sfx2/source/view/sfxbasecontroller.cxx | 1498 | ||||
-rw-r--r-- | sfx2/source/view/userinputinterception.cxx | 263 | ||||
-rw-r--r-- | sfx2/source/view/viewfac.cxx | 56 | ||||
-rw-r--r-- | sfx2/source/view/viewfrm.cxx | 3706 | ||||
-rw-r--r-- | sfx2/source/view/viewfrm2.cxx | 380 | ||||
-rw-r--r-- | sfx2/source/view/viewimp.hxx | 71 | ||||
-rw-r--r-- | sfx2/source/view/viewprn.cxx | 923 | ||||
-rw-r--r-- | sfx2/source/view/viewsh.cxx | 3912 |
21 files changed, 17338 insertions, 0 deletions
diff --git a/sfx2/source/view/classificationcontroller.cxx b/sfx2/source/view/classificationcontroller.cxx new file mode 100644 index 0000000000..1cda4a41ca --- /dev/null +++ b/sfx2/source/view/classificationcontroller.cxx @@ -0,0 +1,364 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/XFrame.hpp> + +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <sfx2/classificationhelper.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/sfxresid.hxx> +#include <vcl/event.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/svapp.hxx> +#include <vcl/vclptr.hxx> +#include <vcl/weld.hxx> + +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/configurationlistener.hxx> + +using namespace com::sun::star; + +namespace sfx2 +{ + +namespace { + +class ClassificationCategoriesController; + +} + +using ClassificationPropertyListenerBase = comphelper::ConfigurationListenerProperty<OUString>; + +namespace { + +/// Listens to configuration changes, so no restart is needed after setting the classification path. +class ClassificationPropertyListener : public ClassificationPropertyListenerBase +{ + ClassificationCategoriesController& m_rController; + +public: + ClassificationPropertyListener(const rtl::Reference<comphelper::ConfigurationListener>& xListener, ClassificationCategoriesController& rController); + void setProperty(const uno::Any& rProperty) override; +}; + +} + +using ClassificationCategoriesControllerBase = cppu::ImplInheritanceHelper<svt::ToolboxController, lang::XServiceInfo>; + +namespace { + +class ClassificationControl; + +/// Controller for .uno:ClassificationApply. +class ClassificationCategoriesController : public ClassificationCategoriesControllerBase +{ + VclPtr<ClassificationControl> m_pClassification; + rtl::Reference<comphelper::ConfigurationListener> m_xListener; + ClassificationPropertyListener m_aPropertyListener; + + DECL_LINK(SelectHdl, weld::ComboBox&, void); + +public: + explicit ClassificationCategoriesController(const uno::Reference<uno::XComponentContext>& rContext); + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override; + uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XComponent + void SAL_CALL dispose() override; + + // XToolbarController + uno::Reference<awt::XWindow> SAL_CALL createItemWindow(const uno::Reference<awt::XWindow>& rParent) override; + + // XStatusListener + void SAL_CALL statusChanged(const frame::FeatureStateEvent& rEvent) override; + + void removeEntries(); +}; + +/// Classification control is the parent of all widgets that belongs to ClassificationCategoriesController. +class SAL_WARN_UNUSED ClassificationControl final : public InterimItemWindow +{ + std::unique_ptr<weld::Label> m_xLabel; + std::unique_ptr<weld::ComboBox> m_xCategory; + + DECL_LINK(KeyInputHdl, const KeyEvent&, bool); + + void SetOptimalSize(); + void DataChanged(const DataChangedEvent& rEvent) override; + +public: + explicit ClassificationControl(vcl::Window* pParent); + ~ClassificationControl() override; + void dispose() override; + weld::ComboBox& getCategory() + { + return *m_xCategory; + } + void set_sensitive(bool bSensitive) + { + Enable(bSensitive); + m_xContainer->set_sensitive(bSensitive); + } + static sfx::ClassificationCreationOrigin getExistingClassificationOrigin(); + void toggleInteractivityOnOrigin(); + void setCategoryStateFromPolicy(const SfxClassificationHelper & rHelper); +}; + +OUString const & getCategoryType() +{ + return SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType()); +} + +} // end anonymous namespace + +ClassificationPropertyListener::ClassificationPropertyListener(const rtl::Reference<comphelper::ConfigurationListener>& xListener, ClassificationCategoriesController& rController) + : ClassificationPropertyListenerBase(xListener, "WritePath") + , m_rController(rController) +{ +} + +void ClassificationPropertyListener::setProperty(const uno::Any& /*rProperty*/) +{ + // So that its gets re-filled with entries from the new policy. + m_rController.removeEntries(); +} + +ClassificationCategoriesController::ClassificationCategoriesController(const uno::Reference<uno::XComponentContext>& rContext) + : ClassificationCategoriesControllerBase(rContext, uno::Reference<frame::XFrame>(), OUString(".uno:ClassificationApply")) + , m_pClassification(nullptr) + , m_xListener(new comphelper::ConfigurationListener("/org.openoffice.Office.Paths/Paths/Classification")) + , m_aPropertyListener(m_xListener, *this) +{ + +} + +OUString ClassificationCategoriesController::getImplementationName() +{ + return "com.sun.star.comp.sfx2.ClassificationCategoriesController"; +} + +sal_Bool ClassificationCategoriesController::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +uno::Sequence<OUString> ClassificationCategoriesController::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ToolbarController" }; +} + +void ClassificationCategoriesController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + svt::ToolboxController::dispose(); + m_pClassification.disposeAndClear(); + m_aPropertyListener.dispose(); + m_xListener->dispose(); +} + +uno::Reference<awt::XWindow> ClassificationCategoriesController::createItemWindow(const uno::Reference<awt::XWindow>& rParent) +{ + VclPtr<vcl::Window> pParent = VCLUnoHelper::GetWindow(rParent); + auto pToolbar = dynamic_cast<ToolBox*>(pParent.get()); + if (pToolbar) + { + m_pClassification = VclPtr<ClassificationControl>::Create(pToolbar); + m_pClassification->getCategory().connect_changed(LINK(this, ClassificationCategoriesController, SelectHdl)); + m_pClassification->Show(); + } + + return VCLUnoHelper::GetInterface(m_pClassification); +} + +IMPL_LINK(ClassificationCategoriesController, SelectHdl, weld::ComboBox&, rCategory, void) +{ + m_pClassification->toggleInteractivityOnOrigin(); + + if (ClassificationControl::getExistingClassificationOrigin() == sfx::ClassificationCreationOrigin::MANUAL) + { + SfxObjectShell* pObjectShell = SfxObjectShell::Current(); + if (!pObjectShell) + return; + SfxClassificationHelper aHelper(pObjectShell->getDocProperties()); + m_pClassification->setCategoryStateFromPolicy(aHelper); + } + else + { + OUString aEntry = rCategory.get_active_text(); + + const OUString& aType = getCategoryType(); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence({ + {"Name", uno::Any(aEntry)}, + {"Type", uno::Any(aType)}, + })); + comphelper::dispatchCommand(".uno:ClassificationApply", aPropertyValues); + } +} + +void ClassificationCategoriesController::statusChanged(const frame::FeatureStateEvent& /*rEvent*/) +{ + if (!m_pClassification) + return; + + SfxObjectShell* pObjectShell = SfxObjectShell::Current(); + if (!pObjectShell) + return; + + SfxClassificationHelper aHelper(pObjectShell->getDocProperties()); + + //toggle if the pop-up is enabled/disabled + m_pClassification->toggleInteractivityOnOrigin(); + + // check if classification was set via the advanced dialog + if (ClassificationControl::getExistingClassificationOrigin() != sfx::ClassificationCreationOrigin::MANUAL) + { + weld::ComboBox& rCategories = m_pClassification->getCategory(); + if (rCategories.get_count() == 0) + { + std::vector<OUString> aNames = aHelper.GetBACNames(); + for (const OUString& rName : aNames) + rCategories.append_text(rName); + } + } + + // Restore state based on the doc. model. + m_pClassification->setCategoryStateFromPolicy(aHelper); + +} + +void ClassificationCategoriesController::removeEntries() +{ + m_pClassification->getCategory().clear(); +} + +ClassificationControl::ClassificationControl(vcl::Window* pParent) + : InterimItemWindow(pParent, "sfx/ui/classificationbox.ui", "ClassificationBox") + , m_xLabel(m_xBuilder->weld_label("label")) + , m_xCategory(m_xBuilder->weld_combo_box("combobox")) +{ + InitControlBase(m_xCategory.get()); + + m_xCategory->connect_key_press(LINK(this, ClassificationControl, KeyInputHdl)); + + // WB_NOLABEL means here that the control won't be replaced with a label + // when it wouldn't fit the available space. + SetStyle(GetStyle() | WB_DIALOGCONTROL | WB_NOLABEL); + + OUString aText; + switch (SfxClassificationHelper::getPolicyType()) + { + case SfxClassificationPolicyType::IntellectualProperty: + aText = SfxResId(STR_CLASSIFIED_INTELLECTUAL_PROPERTY); + break; + case SfxClassificationPolicyType::NationalSecurity: + aText = SfxResId(STR_CLASSIFIED_NATIONAL_SECURITY); + break; + case SfxClassificationPolicyType::ExportControl: + aText = SfxResId(STR_CLASSIFIED_EXPORT_CONTROL); + break; + } + + m_xLabel->set_label(aText); + + // Same as SvxColorDockingWindow. + const Size aLogicalAttrSize(150, 0); + Size aSize(LogicToPixel(aLogicalAttrSize, MapMode(MapUnit::MapAppFont))); + m_xCategory->set_size_request(aSize.Width() - m_xLabel->get_preferred_size().Width(), -1); + + SetOptimalSize(); +} + +IMPL_LINK(ClassificationControl, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +ClassificationControl::~ClassificationControl() +{ + disposeOnce(); +} + +void ClassificationControl::dispose() +{ + m_xLabel.reset(); + m_xCategory.reset(); + InterimItemWindow::dispose(); +} + +void ClassificationControl::SetOptimalSize() +{ + SetSizePixel(get_preferred_size()); +} + +void ClassificationControl::DataChanged(const DataChangedEvent& rEvent) +{ + if ((rEvent.GetType() == DataChangedEventType::SETTINGS) && (rEvent.GetFlags() & AllSettingsFlags::STYLE)) + SetOptimalSize(); + + toggleInteractivityOnOrigin(); + + InterimItemWindow::DataChanged(rEvent); +} + +sfx::ClassificationCreationOrigin ClassificationControl::getExistingClassificationOrigin() +{ + SfxObjectShell* pObjectShell = SfxObjectShell::Current(); + if (!pObjectShell) + return sfx::ClassificationCreationOrigin::NONE; + + uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjectShell->getDocProperties(); + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + + sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + return sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator); +} + +void ClassificationControl::toggleInteractivityOnOrigin() +{ + if (getExistingClassificationOrigin() == sfx::ClassificationCreationOrigin::MANUAL) + { + set_sensitive(false); + } + else + { + set_sensitive(true); + } +} + +void ClassificationControl::setCategoryStateFromPolicy(const SfxClassificationHelper & rHelper) +{ + const OUString& rCategoryName = rHelper.GetBACName(SfxClassificationHelper::getPolicyType()); + if (!rCategoryName.isEmpty()) + { + getCategory().set_active_text(rCategoryName); + } +} + +} // namespace sfx2 + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* com_sun_star_sfx2_ClassificationCategoriesController_get_implementation(uno::XComponentContext* pContext, const uno::Sequence<uno::Any>&) +{ + return cppu::acquire(new sfx2::ClassificationCategoriesController(pContext)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/classificationhelper.cxx b/sfx2/source/view/classificationhelper.cxx new file mode 100644 index 0000000000..e9556e88c1 --- /dev/null +++ b/sfx2/source/view/classificationhelper.cxx @@ -0,0 +1,988 @@ +/* -*- 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 <sfx2/classificationhelper.hxx> + +#include <map> +#include <algorithm> +#include <iterator> + +#include <com/sun/star/beans/XPropertyContainer.hpp> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> + +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <sfx2/infobar.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/streamwrap.hxx> +#include <cppuhelper/implbase.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/sfxresid.hxx> +#include <sfx2/viewfrm.hxx> +#include <tools/datetime.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/datetime.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/weld.hxx> +#include <svl/fstathelper.hxx> + +#include <o3tl/string_view.hxx> +#include <officecfg/Office/Common.hxx> + +using namespace com::sun::star; + +namespace +{ + +const OUString& PROP_BACNAME() +{ + static constexpr OUString sProp(u"BusinessAuthorizationCategory:Name"_ustr); + return sProp; +} + +const OUString& PROP_STARTVALIDITY() +{ + static constexpr OUString sProp(u"Authorization:StartValidity"_ustr); + return sProp; +} + +const OUString& PROP_NONE() +{ + static constexpr OUString sProp(u"None"_ustr); + return sProp; +} + +const OUString& PROP_IMPACTSCALE() +{ + static constexpr OUString sProp(u"Impact:Scale"_ustr); + return sProp; +} + +const OUString& PROP_IMPACTLEVEL() +{ + static constexpr OUString sProp(u"Impact:Level:Confidentiality"_ustr); + return sProp; +} + +const OUString& PROP_PREFIX_EXPORTCONTROL() +{ + static constexpr OUString sProp(u"urn:bails:ExportControl:"_ustr); + return sProp; +} + +const OUString& PROP_PREFIX_NATIONALSECURITY() +{ + static constexpr OUString sProp(u"urn:bails:NationalSecurity:"_ustr); + return sProp; +} + +/// Represents one category of a classification policy. +class SfxClassificationCategory +{ +public: + /// PROP_BACNAME() is stored separately for easier lookup. + OUString m_aName; + OUString m_aAbbreviatedName; //< An abbreviation to display instead of m_aName. + OUString m_aIdentifier; //< The Identifier of this entry. + size_t m_nConfidentiality; //< 0 is the lowest (least-sensitive). + std::map<OUString, OUString> m_aLabels; +}; + +/// Parses a policy XML conforming to the TSCP BAF schema. +class SfxClassificationParser : public cppu::WeakImplHelper<xml::sax::XDocumentHandler> +{ +public: + std::vector<SfxClassificationCategory> m_aCategories; + std::vector<OUString> m_aMarkings; + std::vector<OUString> m_aIPParts; + std::vector<OUString> m_aIPPartNumbers; + + OUString m_aPolicyAuthorityName; + bool m_bInPolicyAuthorityName = false; + OUString m_aPolicyName; + bool m_bInPolicyName = false; + OUString m_aProgramID; + bool m_bInProgramID = false; + OUString m_aScale; + bool m_bInScale = false; + OUString m_aConfidentalityValue; + bool m_bInConfidentalityValue = false; + OUString m_aIdentifier; + bool m_bInIdentifier = false; + OUString m_aValue; + bool m_bInValue = false; + + /// Pointer to a value in m_aCategories, the currently parsed category. + SfxClassificationCategory* m_pCategory = nullptr; + + SfxClassificationParser(); + + void SAL_CALL startDocument() override; + + void SAL_CALL endDocument() override; + + void SAL_CALL startElement(const OUString& rName, const uno::Reference<xml::sax::XAttributeList>& xAttribs) override; + + void SAL_CALL endElement(const OUString& rName) override; + + void SAL_CALL characters(const OUString& rChars) override; + + void SAL_CALL ignorableWhitespace(const OUString& rWhitespaces) override; + + void SAL_CALL processingInstruction(const OUString& rTarget, const OUString& rData) override; + + void SAL_CALL setDocumentLocator(const uno::Reference<xml::sax::XLocator>& xLocator) override; +}; + +SfxClassificationParser::SfxClassificationParser() = default; + +void SAL_CALL SfxClassificationParser::startDocument() +{ +} + +void SAL_CALL SfxClassificationParser::endDocument() +{ +} + +void SAL_CALL SfxClassificationParser::startElement(const OUString& rName, const uno::Reference<xml::sax::XAttributeList>& xAttribs) +{ + if (rName == "baf:PolicyAuthorityName") + { + m_aPolicyAuthorityName.clear(); + m_bInPolicyAuthorityName = true; + } + else if (rName == "baf:PolicyName") + { + m_aPolicyName.clear(); + m_bInPolicyName = true; + } + else if (rName == "baf:ProgramID") + { + m_aProgramID.clear(); + m_bInProgramID = true; + } + else if (rName == "baf:BusinessAuthorizationCategory") + { + const OUString aName = xAttribs->getValueByName("Name"); + if (!m_pCategory && !aName.isEmpty()) + { + OUString aIdentifier = xAttribs->getValueByName("Identifier"); + + // Create a new category and initialize it with the data that's true for all categories. + m_aCategories.emplace_back(); + SfxClassificationCategory& rCategory = m_aCategories.back(); + + rCategory.m_aName = aName; + // Set the abbreviated name, if any, otherwise fallback on the full name. + const OUString aAbbreviatedName = xAttribs->getValueByName("loextAbbreviatedName"); + rCategory.m_aAbbreviatedName = !aAbbreviatedName.isEmpty() ? aAbbreviatedName : aName; + rCategory.m_aIdentifier = aIdentifier; + + rCategory.m_aLabels["PolicyAuthority:Name"] = m_aPolicyAuthorityName; + rCategory.m_aLabels["Policy:Name"] = m_aPolicyName; + rCategory.m_aLabels["BusinessAuthorization:Identifier"] = m_aProgramID; + rCategory.m_aLabels["BusinessAuthorizationCategory:Identifier"] = aIdentifier; + + // Also initialize defaults. + rCategory.m_aLabels["PolicyAuthority:Identifier"] = PROP_NONE(); + rCategory.m_aLabels["PolicyAuthority:Country"] = PROP_NONE(); + rCategory.m_aLabels["Policy:Identifier"] = PROP_NONE(); + rCategory.m_aLabels["BusinessAuthorization:Name"] = PROP_NONE(); + rCategory.m_aLabels["BusinessAuthorization:Locator"] = PROP_NONE(); + rCategory.m_aLabels["BusinessAuthorizationCategory:Identifier:OID"] = PROP_NONE(); + rCategory.m_aLabels["BusinessAuthorizationCategory:Locator"] = PROP_NONE(); + rCategory.m_aLabels["BusinessAuthorization:Locator"] = PROP_NONE(); + rCategory.m_aLabels["MarkingPrecedence"] = PROP_NONE(); + rCategory.m_aLabels["Marking:general-summary"].clear(); + rCategory.m_aLabels["Marking:general-warning-statement"].clear(); + rCategory.m_aLabels["Marking:general-warning-statement:ext:2"].clear(); + rCategory.m_aLabels["Marking:general-warning-statement:ext:3"].clear(); + rCategory.m_aLabels["Marking:general-warning-statement:ext:4"].clear(); + rCategory.m_aLabels["Marking:general-distribution-statement"].clear(); + rCategory.m_aLabels["Marking:general-distribution-statement:ext:2"].clear(); + rCategory.m_aLabels["Marking:general-distribution-statement:ext:3"].clear(); + rCategory.m_aLabels["Marking:general-distribution-statement:ext:4"].clear(); + rCategory.m_aLabels[SfxClassificationHelper::PROP_DOCHEADER()].clear(); + rCategory.m_aLabels[SfxClassificationHelper::PROP_DOCFOOTER()].clear(); + rCategory.m_aLabels[SfxClassificationHelper::PROP_DOCWATERMARK()].clear(); + rCategory.m_aLabels["Marking:email-first-line-of-text"].clear(); + rCategory.m_aLabels["Marking:email-last-line-of-text"].clear(); + rCategory.m_aLabels["Marking:email-subject-prefix"].clear(); + rCategory.m_aLabels["Marking:email-subject-suffix"].clear(); + rCategory.m_aLabels[PROP_STARTVALIDITY()] = PROP_NONE(); + rCategory.m_aLabels["Authorization:StopValidity"] = PROP_NONE(); + m_pCategory = &rCategory; + } + } + else if (rName == "loext:Marking") + { + OUString aName = xAttribs->getValueByName("Name"); + m_aMarkings.push_back(aName); + } + else if (rName == "loext:IntellectualPropertyPart") + { + OUString aName = xAttribs->getValueByName("Name"); + m_aIPParts.push_back(aName); + } + else if (rName == "loext:IntellectualPropertyPartNumber") + { + OUString aName = xAttribs->getValueByName("Name"); + m_aIPPartNumbers.push_back(aName); + } + else if (rName == "baf:Scale") + { + m_aScale.clear(); + m_bInScale = true; + } + else if (rName == "baf:ConfidentalityValue") + { + m_aConfidentalityValue.clear(); + m_bInConfidentalityValue = true; + } + else if (rName == "baf:Identifier") + { + m_aIdentifier.clear(); + m_bInIdentifier = true; + } + else if (rName == "baf:Value") + { + m_aValue.clear(); + m_bInValue = true; + } +} + +void SAL_CALL SfxClassificationParser::endElement(const OUString& rName) +{ + if (rName == "baf:PolicyAuthorityName") + m_bInPolicyAuthorityName = false; + else if (rName == "baf:PolicyName") + m_bInPolicyName = false; + else if (rName == "baf:ProgramID") + m_bInProgramID = false; + else if (rName == "baf:BusinessAuthorizationCategory") + m_pCategory = nullptr; + else if (rName == "baf:Scale") + { + m_bInScale = false; + if (m_pCategory) + m_pCategory->m_aLabels[PROP_IMPACTSCALE()] = m_aScale; + } + else if (rName == "baf:ConfidentalityValue") + { + m_bInConfidentalityValue = false; + if (m_pCategory) + { + std::map<OUString, OUString>& rLabels = m_pCategory->m_aLabels; + rLabels[PROP_IMPACTLEVEL()] = m_aConfidentalityValue; + m_pCategory->m_nConfidentiality = m_aConfidentalityValue.toInt32(); // 0-based class sensitivity; 0 is lowest. + // Set the two other type of levels as well, if they're not set + // yet: they're optional in BAF, but not in BAILS. + rLabels.try_emplace("Impact:Level:Integrity", m_aConfidentalityValue); + rLabels.try_emplace("Impact:Level:Availability", m_aConfidentalityValue); + } + } + else if (rName == "baf:Identifier") + m_bInIdentifier = false; + else if (rName == "baf:Value") + { + if (m_pCategory) + { + if (m_aIdentifier == "Document: Header") + m_pCategory->m_aLabels[SfxClassificationHelper::PROP_DOCHEADER()] = m_aValue; + else if (m_aIdentifier == "Document: Footer") + m_pCategory->m_aLabels[SfxClassificationHelper::PROP_DOCFOOTER()] = m_aValue; + else if (m_aIdentifier == "Document: Watermark") + m_pCategory->m_aLabels[SfxClassificationHelper::PROP_DOCWATERMARK()] = m_aValue; + } + } +} + +void SAL_CALL SfxClassificationParser::characters(const OUString& rChars) +{ + if (m_bInPolicyAuthorityName) + m_aPolicyAuthorityName += rChars; + else if (m_bInPolicyName) + m_aPolicyName += rChars; + else if (m_bInProgramID) + m_aProgramID += rChars; + else if (m_bInScale) + m_aScale += rChars; + else if (m_bInConfidentalityValue) + m_aConfidentalityValue += rChars; + else if (m_bInIdentifier) + m_aIdentifier += rChars; + else if (m_bInValue) + m_aValue += rChars; +} + +void SAL_CALL SfxClassificationParser::ignorableWhitespace(const OUString& /*rWhitespace*/) +{ +} + +void SAL_CALL SfxClassificationParser::processingInstruction(const OUString& /*rTarget*/, const OUString& /*rData*/) +{ +} + +void SAL_CALL SfxClassificationParser::setDocumentLocator(const uno::Reference<xml::sax::XLocator>& /*xLocator*/) +{ +} + +} // anonymous namespace + +/// Implementation details of SfxClassificationHelper. +class SfxClassificationHelper::Impl +{ +public: + /// Selected categories, one category for each policy type. + std::map<SfxClassificationPolicyType, SfxClassificationCategory> m_aCategory; + /// Possible categories of a policy to choose from. + std::vector<SfxClassificationCategory> m_aCategories; + std::vector<OUString> m_aMarkings; + std::vector<OUString> m_aIPParts; + std::vector<OUString> m_aIPPartNumbers; + + uno::Reference<document::XDocumentProperties> m_xDocumentProperties; + + bool m_bUseLocalized; + + explicit Impl(uno::Reference<document::XDocumentProperties> xDocumentProperties, bool bUseLocalized); + void parsePolicy(); + /// Synchronize m_aLabels back to the document properties. + void pushToDocumentProperties(); + /// Set the classification start date to the system time. + void setStartValidity(SfxClassificationPolicyType eType); +}; + +SfxClassificationHelper::Impl::Impl(uno::Reference<document::XDocumentProperties> xDocumentProperties, bool bUseLocalized) + : m_xDocumentProperties(std::move(xDocumentProperties)) + , m_bUseLocalized(bUseLocalized) +{ + parsePolicy(); +} + +void SfxClassificationHelper::Impl::parsePolicy() +{ + uno::Reference<uno::XComponentContext> xComponentContext = comphelper::getProcessComponentContext(); + SvtPathOptions aOptions; + OUString aPath = aOptions.GetClassificationPath(); + + // See if there is a localized variant next to the configured XML. + OUString aExtension(".xml"); + if (aPath.endsWith(aExtension) && m_bUseLocalized) + { + std::u16string_view aBase = aPath.subView(0, aPath.getLength() - aExtension.getLength()); + const LanguageTag& rLanguageTag = Application::GetSettings().GetLanguageTag(); + // Expected format is "<original path>_xx-XX.xml". + OUString aLocalized = OUString::Concat(aBase) + "_" + rLanguageTag.getBcp47() + aExtension; + if (FStatHelper::IsDocument(aLocalized)) + aPath = aLocalized; + } + + std::unique_ptr<SvStream> pStream = utl::UcbStreamHelper::CreateStream(aPath, StreamMode::READ); + uno::Reference<io::XInputStream> xInputStream(new utl::OStreamWrapper(std::move(pStream))); + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; + + uno::Reference<xml::sax::XParser> xParser = xml::sax::Parser::create(xComponentContext); + rtl::Reference<SfxClassificationParser> xClassificationParser(new SfxClassificationParser()); + xParser->setDocumentHandler(xClassificationParser); + try + { + xParser->parseStream(aParserInput); + } + catch (const xml::sax::SAXParseException&) + { + TOOLS_WARN_EXCEPTION("sfx.view", "parsePolicy() failed"); + } + m_aCategories = xClassificationParser->m_aCategories; + m_aMarkings = xClassificationParser->m_aMarkings; + m_aIPParts = xClassificationParser->m_aIPParts; + m_aIPPartNumbers = xClassificationParser->m_aIPPartNumbers; +} + +static bool lcl_containsProperty(const uno::Sequence<beans::Property>& rProperties, std::u16string_view rName) +{ + return std::any_of(rProperties.begin(), rProperties.end(), [&](const beans::Property& rProperty) + { + return rProperty.Name == rName; + }); +} + +void SfxClassificationHelper::Impl::setStartValidity(SfxClassificationPolicyType eType) +{ + auto itCategory = m_aCategory.find(eType); + if (itCategory == m_aCategory.end()) + return; + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(policyTypeToString(eType) + PROP_STARTVALIDITY()); + if (it != rCategory.m_aLabels.end()) + { + if (it->second == PROP_NONE()) + { + // The policy left the start date unchanged, replace it with the system time. + util::DateTime aDateTime = DateTime(DateTime::SYSTEM).GetUNODateTime(); + it->second = utl::toISO8601(aDateTime); + } + } +} + +void SfxClassificationHelper::Impl::pushToDocumentProperties() +{ + uno::Reference<beans::XPropertyContainer> xPropertyContainer = m_xDocumentProperties->getUserDefinedProperties(); + uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY); + uno::Sequence<beans::Property> aProperties = xPropertySet->getPropertySetInfo()->getProperties(); + for (auto& rPair : m_aCategory) + { + SfxClassificationPolicyType eType = rPair.first; + SfxClassificationCategory& rCategory = rPair.second; + std::map<OUString, OUString> aLabels = rCategory.m_aLabels; + aLabels[policyTypeToString(eType) + PROP_BACNAME()] = rCategory.m_aName; + for (const auto& rLabel : aLabels) + { + try + { + if (lcl_containsProperty(aProperties, rLabel.first)) + xPropertySet->setPropertyValue(rLabel.first, uno::Any(rLabel.second)); + else + xPropertyContainer->addProperty(rLabel.first, beans::PropertyAttribute::REMOVABLE, uno::Any(rLabel.second)); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx.view", "pushDocumentProperties() failed for property " << rLabel.first); + } + } + } +} + +bool SfxClassificationHelper::IsClassified(const uno::Reference<document::XDocumentProperties>& xDocumentProperties) +{ + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + if (!xPropertyContainer.is()) + return false; + + uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY); + const uno::Sequence<beans::Property> aProperties = xPropertySet->getPropertySetInfo()->getProperties(); + for (const beans::Property& rProperty : aProperties) + { + if (rProperty.Name.startsWith("urn:bails:")) + return true; + } + + return false; +} + +SfxClassificationCheckPasteResult SfxClassificationHelper::CheckPaste(const uno::Reference<document::XDocumentProperties>& xSource, + const uno::Reference<document::XDocumentProperties>& xDestination) +{ + if (!SfxClassificationHelper::IsClassified(xSource)) + // No classification on the source side. Return early, regardless the + // state of the destination side. + return SfxClassificationCheckPasteResult::None; + + if (!SfxClassificationHelper::IsClassified(xDestination)) + { + // Paste from a classified document to a non-classified one -> deny. + return SfxClassificationCheckPasteResult::TargetDocNotClassified; + } + + // Remaining case: paste between two classified documents. + SfxClassificationHelper aSource(xSource); + SfxClassificationHelper aDestination(xDestination); + if (aSource.GetImpactScale() != aDestination.GetImpactScale()) + // It's possible to compare them if they have the same scale. + return SfxClassificationCheckPasteResult::None; + + if (aSource.GetImpactLevel() > aDestination.GetImpactLevel()) + // Paste from a doc that has higher classification -> deny. + return SfxClassificationCheckPasteResult::DocClassificationTooLow; + + return SfxClassificationCheckPasteResult::None; +} + +bool SfxClassificationHelper::ShowPasteInfo(SfxClassificationCheckPasteResult eResult) +{ + switch (eResult) + { + case SfxClassificationCheckPasteResult::None: + { + return true; + } + break; + case SfxClassificationCheckPasteResult::TargetDocNotClassified: + { + if (!Application::IsHeadlessModeEnabled()) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_TARGET_DOC_NOT_CLASSIFIED))); + xBox->run(); + } + return false; + } + break; + case SfxClassificationCheckPasteResult::DocClassificationTooLow: + { + if (!Application::IsHeadlessModeEnabled()) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_DOC_CLASSIFICATION_TOO_LOW))); + xBox->run(); + } + return false; + } + break; + } + + return true; +} + +SfxClassificationHelper::SfxClassificationHelper(const uno::Reference<document::XDocumentProperties>& xDocumentProperties, bool bUseLocalizedPolicy) + : m_pImpl(std::make_unique<Impl>(xDocumentProperties, bUseLocalizedPolicy)) +{ + if (!xDocumentProperties.is()) + return; + + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + if (!xPropertyContainer.is()) + return; + + uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY); + const uno::Sequence<beans::Property> aProperties = xPropertySet->getPropertySetInfo()->getProperties(); + for (const beans::Property& rProperty : aProperties) + { + if (!rProperty.Name.startsWith("urn:bails:")) + continue; + + uno::Any aAny = xPropertySet->getPropertyValue(rProperty.Name); + OUString aValue; + if (aAny >>= aValue) + { + SfxClassificationPolicyType eType = stringToPolicyType(rProperty.Name); + OUString aPrefix = policyTypeToString(eType); + if (!rProperty.Name.startsWith(aPrefix)) + // It's a prefix we did not recognize, ignore. + continue; + + //TODO: Support abbreviated names(?) + if (rProperty.Name == Concat2View(aPrefix + PROP_BACNAME())) + m_pImpl->m_aCategory[eType].m_aName = aValue; + else + m_pImpl->m_aCategory[eType].m_aLabels[rProperty.Name] = aValue; + } + } +} + +SfxClassificationHelper::~SfxClassificationHelper() = default; + +std::vector<OUString> const & SfxClassificationHelper::GetMarkings() const +{ + return m_pImpl->m_aMarkings; +} + +std::vector<OUString> const & SfxClassificationHelper::GetIntellectualPropertyParts() const +{ + return m_pImpl->m_aIPParts; +} + +std::vector<OUString> const & SfxClassificationHelper::GetIntellectualPropertyPartNumbers() const +{ + return m_pImpl->m_aIPPartNumbers; +} + +const OUString& SfxClassificationHelper::GetBACName(SfxClassificationPolicyType eType) const +{ + return m_pImpl->m_aCategory[eType].m_aName; +} + +const OUString& SfxClassificationHelper::GetAbbreviatedBACName(const OUString& sFullName) +{ + for (const auto& category : m_pImpl->m_aCategories) + { + if (category.m_aName == sFullName) + return category.m_aAbbreviatedName; + } + + return sFullName; +} + +OUString SfxClassificationHelper::GetBACNameForIdentifier(std::u16string_view sIdentifier) +{ + if (sIdentifier.empty()) + return ""; + + for (const auto& category : m_pImpl->m_aCategories) + { + if (category.m_aIdentifier == sIdentifier) + return category.m_aName; + } + + return ""; +} + +OUString SfxClassificationHelper::GetHigherClass(const OUString& first, const OUString& second) +{ + size_t nFirstConfidentiality = 0; + size_t nSecondConfidentiality = 0; + for (const auto& category : m_pImpl->m_aCategories) + { + if (category.m_aName == first) + nFirstConfidentiality = category.m_nConfidentiality; + if (category.m_aName == second) + nSecondConfidentiality = category.m_nConfidentiality; + } + + return nFirstConfidentiality >= nSecondConfidentiality ? first : second; +} + +bool SfxClassificationHelper::HasImpactLevel() +{ + auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty); + if (itCategory == m_pImpl->m_aCategory.end()) + return false; + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE()); + if (it == rCategory.m_aLabels.end()) + return false; + + it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTLEVEL()); + return it != rCategory.m_aLabels.end(); +} + +bool SfxClassificationHelper::HasDocumentHeader() +{ + auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty); + if (itCategory == m_pImpl->m_aCategory.end()) + return false; + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_DOCHEADER()); + return it != rCategory.m_aLabels.end() && !it->second.isEmpty(); +} + +bool SfxClassificationHelper::HasDocumentFooter() +{ + auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty); + if (itCategory == m_pImpl->m_aCategory.end()) + return false; + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_DOCFOOTER()); + return it != rCategory.m_aLabels.end() && !it->second.isEmpty(); +} + +InfobarType SfxClassificationHelper::GetImpactLevelType() +{ + InfobarType aRet; + + aRet = InfobarType::WARNING; + + auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty); + if (itCategory == m_pImpl->m_aCategory.end()) + return aRet; + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE()); + if (it == rCategory.m_aLabels.end()) + return aRet; + OUString aScale = it->second; + + it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTLEVEL()); + if (it == rCategory.m_aLabels.end()) + return aRet; + OUString aLevel = it->second; + + // The spec defines two valid scale values: FIPS-199 and UK-Cabinet. + if (aScale == "UK-Cabinet") + { + if (aLevel == "0") + aRet = InfobarType::SUCCESS; + else if (aLevel == "1") + aRet = InfobarType::WARNING; + else if (aLevel == "2") + aRet = InfobarType::WARNING; + else if (aLevel == "3") + aRet = InfobarType::DANGER; + } + else if (aScale == "FIPS-199") + { + if (aLevel == "Low") + aRet = InfobarType::SUCCESS; + else if (aLevel == "Moderate") + aRet = InfobarType::WARNING; + else if (aLevel == "High") + aRet = InfobarType::DANGER; + } + return aRet; +} + +sal_Int32 SfxClassificationHelper::GetImpactLevel() +{ + sal_Int32 nRet = -1; + + auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty); + if (itCategory == m_pImpl->m_aCategory.end()) + return nRet; + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE()); + if (it == rCategory.m_aLabels.end()) + return nRet; + OUString aScale = it->second; + + it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTLEVEL()); + if (it == rCategory.m_aLabels.end()) + return nRet; + OUString aLevel = it->second; + + if (aScale == "UK-Cabinet") + { + sal_Int32 nValue = aLevel.toInt32(); + if (nValue < 0 || nValue > 3) + return nRet; + nRet = nValue; + } + else if (aScale == "FIPS-199") + { + static std::map<OUString, sal_Int32> const aValues + { + { "Low", 0 }, + { "Moderate", 1 }, + { "High", 2 } + }; + auto itValues = aValues.find(aLevel); + if (itValues == aValues.end()) + return nRet; + nRet = itValues->second; + } + + return nRet; +} + +OUString SfxClassificationHelper::GetImpactScale() +{ + auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty); + if (itCategory == m_pImpl->m_aCategory.end()) + return OUString(); + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_IMPACTSCALE()); + if (it != rCategory.m_aLabels.end()) + return it->second; + + return OUString(); +} + +OUString SfxClassificationHelper::GetDocumentWatermark() +{ + auto itCategory = m_pImpl->m_aCategory.find(SfxClassificationPolicyType::IntellectualProperty); + if (itCategory == m_pImpl->m_aCategory.end()) + return OUString(); + + SfxClassificationCategory& rCategory = itCategory->second; + auto it = rCategory.m_aLabels.find(PROP_PREFIX_INTELLECTUALPROPERTY() + PROP_DOCWATERMARK()); + if (it != rCategory.m_aLabels.end()) + return it->second; + + return OUString(); +} + +std::vector<OUString> SfxClassificationHelper::GetBACNames() +{ + if (m_pImpl->m_aCategories.empty()) + m_pImpl->parsePolicy(); + + std::vector<OUString> aRet; + std::transform(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), std::back_inserter(aRet), [](const SfxClassificationCategory& rCategory) + { + return rCategory.m_aName; + }); + return aRet; +} + +std::vector<OUString> SfxClassificationHelper::GetBACIdentifiers() +{ + if (m_pImpl->m_aCategories.empty()) + m_pImpl->parsePolicy(); + + std::vector<OUString> aRet; + std::transform(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), std::back_inserter(aRet), [](const SfxClassificationCategory& rCategory) + { + return rCategory.m_aIdentifier; + }); + return aRet; +} + +std::vector<OUString> SfxClassificationHelper::GetAbbreviatedBACNames() +{ + if (m_pImpl->m_aCategories.empty()) + m_pImpl->parsePolicy(); + + std::vector<OUString> aRet; + std::transform(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), std::back_inserter(aRet), [](const SfxClassificationCategory& rCategory) + { + return rCategory.m_aAbbreviatedName; + }); + return aRet; +} + +void SfxClassificationHelper::SetBACName(const OUString& rName, SfxClassificationPolicyType eType) +{ + if (m_pImpl->m_aCategories.empty()) + m_pImpl->parsePolicy(); + + auto it = std::find_if(m_pImpl->m_aCategories.begin(), m_pImpl->m_aCategories.end(), [&](const SfxClassificationCategory& rCategory) + { + return rCategory.m_aName == rName; + }); + if (it == m_pImpl->m_aCategories.end()) + { + SAL_WARN("sfx.view", "'" << rName << "' is not a recognized category name"); + return; + } + + m_pImpl->m_aCategory[eType].m_aName = it->m_aName; + m_pImpl->m_aCategory[eType].m_aAbbreviatedName = it->m_aAbbreviatedName; + m_pImpl->m_aCategory[eType].m_nConfidentiality = it->m_nConfidentiality; + m_pImpl->m_aCategory[eType].m_aLabels.clear(); + const OUString& rPrefix = policyTypeToString(eType); + for (const auto& rLabel : it->m_aLabels) + m_pImpl->m_aCategory[eType].m_aLabels[rPrefix + rLabel.first] = rLabel.second; + + m_pImpl->setStartValidity(eType); + m_pImpl->pushToDocumentProperties(); + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if (!pViewFrame) + return; + + UpdateInfobar(*pViewFrame); +} + +void SfxClassificationHelper::UpdateInfobar(SfxViewFrame& rViewFrame) +{ + OUString aBACName = GetBACName(SfxClassificationPolicyType::IntellectualProperty); + bool bImpactLevel = HasImpactLevel(); + if (!aBACName.isEmpty() && bImpactLevel) + { + OUString aMessage = SfxResId(STR_CLASSIFIED_DOCUMENT); + aMessage = aMessage.replaceFirst("%1", aBACName); + + rViewFrame.RemoveInfoBar(u"classification"); + rViewFrame.AppendInfoBar("classification", "", aMessage, GetImpactLevelType()); + } +} + +SfxClassificationPolicyType SfxClassificationHelper::stringToPolicyType(std::u16string_view rType) +{ + if (o3tl::starts_with(rType, PROP_PREFIX_EXPORTCONTROL())) + return SfxClassificationPolicyType::ExportControl; + else if (o3tl::starts_with(rType, PROP_PREFIX_NATIONALSECURITY())) + return SfxClassificationPolicyType::NationalSecurity; + else + return SfxClassificationPolicyType::IntellectualProperty; +} + +const OUString& SfxClassificationHelper::policyTypeToString(SfxClassificationPolicyType eType) +{ + switch (eType) + { + case SfxClassificationPolicyType::ExportControl: + return PROP_PREFIX_EXPORTCONTROL(); + case SfxClassificationPolicyType::NationalSecurity: + return PROP_PREFIX_NATIONALSECURITY(); + case SfxClassificationPolicyType::IntellectualProperty: + break; + } + + return PROP_PREFIX_INTELLECTUALPROPERTY(); +} + +const OUString& SfxClassificationHelper::PROP_DOCHEADER() +{ + static constexpr OUString sProp(u"Marking:document-header"_ustr); + return sProp; +} + +const OUString& SfxClassificationHelper::PROP_DOCFOOTER() +{ + static constexpr OUString sProp(u"Marking:document-footer"_ustr); + return sProp; +} + +const OUString& SfxClassificationHelper::PROP_DOCWATERMARK() +{ + static constexpr OUString sProp(u"Marking:document-watermark"_ustr); + return sProp; +} + +const OUString& SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() +{ + static constexpr OUString sProp(u"urn:bails:IntellectualProperty:"_ustr); + return sProp; +} + +SfxClassificationPolicyType SfxClassificationHelper::getPolicyType() +{ + if (utl::ConfigManager::IsFuzzing()) + return SfxClassificationPolicyType::IntellectualProperty; + sal_Int32 nPolicyTypeNumber = officecfg::Office::Common::Classification::Policy::get(); + auto eType = static_cast<SfxClassificationPolicyType>(nPolicyTypeNumber); + return eType; +} + +namespace sfx +{ + +namespace +{ + +OUString getProperty(uno::Reference<beans::XPropertyContainer> const& rxPropertyContainer, + OUString const& rName) +{ + try + { + uno::Reference<beans::XPropertySet> xPropertySet(rxPropertyContainer, uno::UNO_QUERY); + return xPropertySet->getPropertyValue(rName).get<OUString>(); + } + catch (const css::uno::Exception&) + { + } + + return OUString(); +} + +} // end anonymous namespace + +sfx::ClassificationCreationOrigin getCreationOriginProperty(uno::Reference<beans::XPropertyContainer> const & rxPropertyContainer, + sfx::ClassificationKeyCreator const & rKeyCreator) +{ + OUString sValue = getProperty(rxPropertyContainer, rKeyCreator.makeCreationOriginKey()); + if (sValue.isEmpty()) + return sfx::ClassificationCreationOrigin::NONE; + + return (sValue == "BAF_POLICY") + ? sfx::ClassificationCreationOrigin::BAF_POLICY + : sfx::ClassificationCreationOrigin::MANUAL; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/frame.cxx b/sfx2/source/view/frame.cxx new file mode 100644 index 0000000000..ad04da3534 --- /dev/null +++ b/sfx2/source/view/frame.cxx @@ -0,0 +1,720 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <tools/svborder.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <appdata.hxx> +#include <sfx2/app.hxx> +#include <sfx2/event.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/frmdescr.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewsh.hxx> +#include <sfx2/viewfrm.hxx> +#include "impframe.hxx" +#include <utility> +#include <workwin.hxx> +#include <sfx2/ipclient.hxx> +#include <vector> + +using namespace com::sun::star; + +static std::vector<SfxFrame*> gaFramesArr_Impl; + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::container; + +SfxPoolItem* SfxUnoAnyItem::CreateDefault() +{ + return new SfxUnoAnyItem(0, uno::Any()); +} + +SfxPoolItem* SfxUnoFrameItem::CreateDefault() +{ + return new SfxUnoFrameItem(); +} +void SfxFrame::Construct_Impl() +{ + m_pImpl.reset(new SfxFrame_Impl); + gaFramesArr_Impl.push_back( this ); +} + + +SfxFrame::~SfxFrame() +{ + RemoveTopFrame_Impl( this ); + m_pWindow.disposeAndClear(); + + auto it = std::find( gaFramesArr_Impl.begin(), gaFramesArr_Impl.end(), this ); + if ( it != gaFramesArr_Impl.end() ) + gaFramesArr_Impl.erase( it ); + + delete m_pImpl->pDescr; +} + +bool SfxFrame::DoClose() +{ + // Actually, one more PrepareClose is still needed! + bool bRet = false; + if ( !m_pImpl->bClosing ) + { + m_pImpl->bClosing = true; + CancelTransfers(); + + // now close frame; it will be deleted if this call is successful, so don't use any members after that! + bRet = true; + try + { + Reference< XCloseable > xCloseable ( m_pImpl->xFrame, UNO_QUERY ); + if (xCloseable.is()) + xCloseable->close(true); + else if ( m_pImpl->xFrame.is() ) + { + Reference < XFrame > xFrame = m_pImpl->xFrame; + xFrame->setComponent( Reference < css::awt::XWindow >(), Reference < XController >() ); + xFrame->dispose(); + } + else + DoClose_Impl(); + } + catch( css::util::CloseVetoException& ) + { + m_pImpl->bClosing = false; + bRet = false; + } + catch( css::lang::DisposedException& ) + { + } + } + + return bRet; +} + +void SfxFrame::DoClose_Impl() +{ + SfxBindings* pBindings = nullptr; + if ( m_pImpl->pCurrentViewFrame ) + pBindings = &m_pImpl->pCurrentViewFrame->GetBindings(); + + // For internal tasks Controllers and Tools must be cleared + if ( m_pImpl->pWorkWin ) + m_pImpl->pWorkWin->DeleteControllers_Impl(); + + if ( m_pImpl->pCurrentViewFrame ) + m_pImpl->pCurrentViewFrame->Close(); + + if ( m_pImpl->bOwnsBindings ) + { + delete pBindings; + pBindings = nullptr; + } + + delete this; +} + +bool SfxFrame::DocIsModified_Impl() +{ + return m_pImpl->pCurrentViewFrame && m_pImpl->pCurrentViewFrame->GetObjectShell() && + m_pImpl->pCurrentViewFrame->GetObjectShell()->IsModified(); +} + +bool SfxFrame::PrepareClose_Impl( bool bUI ) +{ + bool bRet = true; + + // prevent recursive calls + if( !m_pImpl->bPrepClosing ) + { + m_pImpl->bPrepClosing = true; + + SfxObjectShell* pCur = GetCurrentDocument() ; + if( pCur ) + { + // SFX components have a known behaviour + // First check if this frame is the only view to its current document + bool bOther = false; + for ( const SfxViewFrame *pFrame = SfxViewFrame::GetFirst( pCur ); + !bOther && pFrame; pFrame = SfxViewFrame::GetNext( *pFrame, pCur ) ) + { + bOther = ( &pFrame->GetFrame() != this ); + } + + SfxGetpApp()->NotifyEvent( SfxViewEventHint(SfxEventHintId::PrepareCloseView, GlobalEventConfig::GetEventName( GlobalEventId::PREPARECLOSEVIEW ), pCur, GetController() ) ); + + if ( bOther ) + // if there are other views only the current view of this frame must be asked + bRet = GetCurrentViewFrame()->GetViewShell()->PrepareClose( bUI ); + else + // otherwise ask the document + bRet = pCur->PrepareClose( bUI ); + } + + m_pImpl->bPrepClosing = false; + } + + if ( bRet && m_pImpl->pWorkWin ) + // if closing was accepted by the component the UI subframes must be asked also + bRet = m_pImpl->pWorkWin->PrepareClose_Impl(); + + return bRet; +} + + +bool SfxFrame::IsClosing_Impl() const +{ + return m_pImpl->bClosing; +} + +void SfxFrame::SetIsClosing_Impl() +{ + m_pImpl->bClosing = true; +} + +void SfxFrame::CancelTransfers() +{ + if( m_pImpl->bInCancelTransfers ) + return; + + m_pImpl->bInCancelTransfers = true; + SfxObjectShell* pObj = GetCurrentDocument(); + if( pObj ) //&& !( pObj->Get_Impl()->nLoadedFlags & SfxLoadedFlags::ALL )) + { + SfxViewFrame* pFrm; + for( pFrm = SfxViewFrame::GetFirst( pObj ); + pFrm && &pFrm->GetFrame() == this; + pFrm = SfxViewFrame::GetNext( *pFrm, pObj ) ) ; + // No more Frame in Document -> Cancel + if( !pFrm ) + { + pObj->CancelTransfers(); + GetCurrentDocument()->Broadcast( SfxHint(SfxHintId::TitleChanged) ); + } + } + + // Check if StarOne-Loader should be canceled + SfxFrameWeakRef wFrame( this ); + if (wFrame.is()) + m_pImpl->bInCancelTransfers = false; +} + +SfxViewFrame* SfxFrame::GetCurrentViewFrame() const +{ + return m_pImpl->pCurrentViewFrame; +} + +bool SfxFrame::IsAutoLoadLocked_Impl() const +{ + // Its own Document is locked? + const SfxObjectShell* pObjSh = GetCurrentDocument(); + if ( !pObjSh || !pObjSh->IsAutoLoadLocked() ) + return false; + + // otherwise allow AutoLoad + return true; +} + +SfxObjectShell* SfxFrame::GetCurrentDocument() const +{ + return m_pImpl->pCurrentViewFrame ? + m_pImpl->pCurrentViewFrame->GetObjectShell() : + nullptr; +} + +void SfxFrame::SetCurrentViewFrame_Impl( SfxViewFrame *pFrame ) +{ + m_pImpl->pCurrentViewFrame = pFrame; +} + +bool SfxFrame::GetHasTitle() const +{ + return m_pImpl->mbHasTitle; +} + +void SfxFrame::SetHasTitle( bool n ) +{ + m_pImpl->mbHasTitle = n; +} + +void SfxFrame::GetViewData_Impl() +{ + // Update all modifiable data between load and unload, the + // fixed data is only processed once (after PrepareForDoc_Impl in + // updateDescriptor) to save time. + + SfxViewFrame* pViewFrame = GetCurrentViewFrame(); + if( pViewFrame && pViewFrame->GetViewShell() ) + { + SfxItemSet *pSet = GetDescriptor()->GetArgs(); + if ( GetController().is() && pSet->GetItemState( SID_VIEW_DATA ) != SfxItemState::SET ) + { + css::uno::Any aData = GetController()->getViewData(); + pSet->Put( SfxUnoAnyItem( SID_VIEW_DATA, aData ) ); + } + + if ( pViewFrame->GetCurViewId() ) + pSet->Put( SfxUInt16Item( SID_VIEW_ID, static_cast<sal_uInt16>(pViewFrame->GetCurViewId()) ) ); + } +} + +void SfxFrame::UpdateDescriptor( SfxObjectShell const *pDoc ) +{ + // For PrepareForDoc_Impl frames, the descriptor of the updated + // and new itemset to be initialized. All data fir restoring the view + // are thus saved. If the document be replaced, GetViewData_Impl (so) + // the latest information hinzugef by "added. All together then the + // browser-history saved in. When you activate such frame pick entry + // is complete itemsets and the descriptor in the OpenDoc sent;. + // Here only the fixed properties identified "other adjustable, the + // retrieved by GetViewData (saves time). + + assert(pDoc && "NULL-Document inserted ?!"); + + const SfxMedium *pMed = pDoc->GetMedium(); + GetDescriptor()->SetActualURL(); + + // Mark FileOpen parameter + SfxItemSet& rItemSet = pMed->GetItemSet(); + + const std::shared_ptr<const SfxFilter>& pFilter = pMed->GetFilter(); + OUString aFilter; + if ( pFilter ) + aFilter = pFilter->GetFilterName(); + + const SfxStringItem* pRefererItem = rItemSet.GetItem<SfxStringItem>(SID_REFERER, false); + const SfxStringItem* pOptionsItem = rItemSet.GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS, false); + const SfxStringItem* pTitle1Item = rItemSet.GetItem<SfxStringItem>(SID_DOCINFO_TITLE, false); + + SfxItemSet *pSet = GetDescriptor()->GetArgs(); + + // Delete all old Items + pSet->ClearItem(); + + if ( pRefererItem ) + pSet->Put( *pRefererItem ); + else + pSet->Put( SfxStringItem( SID_REFERER, OUString() ) ); + + if ( pOptionsItem ) + pSet->Put( *pOptionsItem ); + + if ( pTitle1Item ) + pSet->Put( *pTitle1Item ); + + pSet->Put( SfxStringItem( SID_FILTER_NAME, aFilter )); +} + + +SfxFrameDescriptor* SfxFrame::GetDescriptor() const +{ + // Create a FrameDescriptor On Demand; if there is no TopLevel-Frame + // will result in an error, as no valid link is created. + + if ( !m_pImpl->pDescr ) + { + DBG_ASSERT( true, "No TopLevel-Frame, but no Descriptor!" ); + m_pImpl->pDescr = new SfxFrameDescriptor; + if ( GetCurrentDocument() ) + m_pImpl->pDescr->SetURL( GetCurrentDocument()->GetMedium()->GetOrigURL() ); + } + return m_pImpl->pDescr; +} + +void SfxFrame::GetDefaultTargetList(TargetList& rList) +{ + // An empty string for 'No Target' + rList.emplace_back( ); + rList.emplace_back( "_top" ); + rList.emplace_back( "_parent" ); + rList.emplace_back( "_blank" ); + rList.emplace_back( "_self" ); +} + +void SfxFrame::InsertTopFrame_Impl( SfxFrame* pFrame ) +{ + auto& rArr = SfxGetpApp()->Get_Impl()->vTopFrames; + rArr.push_back( pFrame ); +} + +void SfxFrame::RemoveTopFrame_Impl( SfxFrame* pFrame ) +{ + auto& rArr = SfxGetpApp()->Get_Impl()->vTopFrames; + auto it = std::find( rArr.begin(), rArr.end(), pFrame ); + if ( it != rArr.end() ) + rArr.erase( it ); +} + +SfxFrameItem::SfxFrameItem( sal_uInt16 nWhichId, SfxViewFrame const *p ) + : SfxPoolItem( nWhichId ), pFrame( p ? &p->GetFrame() : nullptr ) +{ + wFrame = pFrame; +} + +SfxFrameItem::SfxFrameItem( sal_uInt16 nWhichId, SfxFrame *p ): + SfxPoolItem( nWhichId ), + pFrame( p ), wFrame( p ) +{ +} + +SfxFrameItem::SfxFrameItem( SfxFrame *p ): + SfxPoolItem( 0 ), + pFrame( p ), wFrame( p ) +{ +} + +bool SfxFrameItem::operator==( const SfxPoolItem &rItem ) const +{ + return SfxPoolItem::operator==(rItem) && + static_cast<const SfxFrameItem&>(rItem).pFrame == pFrame && + static_cast<const SfxFrameItem&>(rItem).wFrame == wFrame; +} + +SfxFrameItem* SfxFrameItem::Clone( SfxItemPool *) const +{ + SfxFrameItem* pNew = new SfxFrameItem( wFrame); + pNew->pFrame = pFrame; + return pNew; +} + +bool SfxFrameItem::QueryValue( css::uno::Any& rVal, sal_uInt8 ) const +{ + if ( wFrame ) + { + rVal <<= wFrame->GetFrameInterface(); + return true; + } + + return false; +} + +bool SfxFrameItem::PutValue( const css::uno::Any& rVal, sal_uInt8 ) +{ + Reference < XFrame > xFrame; + if ( (rVal >>= xFrame) && xFrame.is() ) + { + SfxFrame* pFr = SfxFrame::GetFirst(); + while ( pFr ) + { + if ( pFr->GetFrameInterface() == xFrame ) + { + wFrame = pFrame = pFr; + return true; + } + + pFr = SfxFrame::GetNext( *pFr ); + } + return true; + } + + return false; +} + + +SfxUnoAnyItem::SfxUnoAnyItem( sal_uInt16 nWhichId, const css::uno::Any& rAny ) + : SfxPoolItem( nWhichId ) +{ + aValue = rAny; +} + +bool SfxUnoAnyItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); (void)rItem; + return false; +} + +SfxUnoAnyItem* SfxUnoAnyItem::Clone( SfxItemPool *) const +{ + return new SfxUnoAnyItem( *this ); +} + +bool SfxUnoAnyItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + rVal = aValue; + return true; +} + +bool SfxUnoAnyItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + aValue = rVal; + return true; +} + +SfxUnoFrameItem::SfxUnoFrameItem() +{ +} + +SfxUnoFrameItem::SfxUnoFrameItem( sal_uInt16 nWhichId, css::uno::Reference< css::frame::XFrame > i_xFrame ) + : SfxPoolItem( nWhichId ) + , m_xFrame(std::move( i_xFrame )) +{ +} + +bool SfxUnoFrameItem::operator==( const SfxPoolItem& i_rItem ) const +{ + return SfxPoolItem::operator==(i_rItem) && + static_cast< const SfxUnoFrameItem& >( i_rItem ).m_xFrame == m_xFrame; +} + +SfxUnoFrameItem* SfxUnoFrameItem::Clone( SfxItemPool* ) const +{ + return new SfxUnoFrameItem( *this ); +} + +bool SfxUnoFrameItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + rVal <<= m_xFrame; + return true; +} + +bool SfxUnoFrameItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + return ( rVal >>= m_xFrame ); +} + +css::uno::Reference< css::frame::XController > SfxFrame::GetController() const +{ + if ( m_pImpl->pCurrentViewFrame && m_pImpl->pCurrentViewFrame->GetViewShell() ) + return m_pImpl->pCurrentViewFrame->GetViewShell()->GetController(); + else + return css::uno::Reference< css::frame::XController > (); +} + +const css::uno::Reference< css::frame::XFrame >& SfxFrame::GetFrameInterface() const +{ + return m_pImpl->xFrame; +} + +void SfxFrame::SetFrameInterface_Impl( const css::uno::Reference< css::frame::XFrame >& rFrame ) +{ + m_pImpl->xFrame = rFrame; + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder; + if ( !rFrame.is() && GetCurrentViewFrame() ) + GetCurrentViewFrame()->GetBindings().SetRecorder_Impl( xRecorder ); +} + +void SfxFrame::Appear() +{ + if ( GetCurrentViewFrame() ) + { + GetCurrentViewFrame()->Show(); + GetWindow().Show(); + m_pImpl->xFrame->getContainerWindow()->setVisible( true ); + Reference < css::awt::XTopWindow > xTopWindow( m_pImpl->xFrame->getContainerWindow(), UNO_QUERY ); + if ( xTopWindow.is() ) + xTopWindow->toFront(); + } +} + +void SfxFrame::AppearWithUpdate() +{ + Appear(); + if ( GetCurrentViewFrame() ) + GetCurrentViewFrame()->GetDispatcher()->Update_Impl( true ); +} + +void SfxFrame::SetOwnsBindings_Impl( bool bSet ) +{ + m_pImpl->bOwnsBindings = bSet; +} + +bool SfxFrame::OwnsBindings_Impl() const +{ + return m_pImpl->bOwnsBindings; +} + +void SfxFrame::SetToolSpaceBorderPixel_Impl( const SvBorder& rBorder ) +{ + m_pImpl->aBorder = rBorder; + SfxViewFrame *pF = GetCurrentViewFrame(); + if ( !pF ) + return; + + Point aPos ( rBorder.Left(), rBorder.Top() ); + Size aSize( GetWindow().GetOutputSizePixel() ); + tools::Long nDeltaX = rBorder.Left() + rBorder.Right(); + if ( aSize.Width() > nDeltaX ) + aSize.AdjustWidth( -nDeltaX ); + else + aSize.setWidth( 0 ); + + tools::Long nDeltaY = rBorder.Top() + rBorder.Bottom(); + if ( aSize.Height() > nDeltaY ) + aSize.AdjustHeight( -nDeltaY ); + else + aSize.setHeight( 0 ); + + pF->GetWindow().SetPosSizePixel( aPos, aSize ); +} + +tools::Rectangle SfxFrame::GetTopOuterRectPixel_Impl() const +{ + Size aSize( GetWindow().GetOutputSizePixel() ); + return tools::Rectangle( Point(), aSize ); +} + +SfxWorkWindow* SfxFrame::GetWorkWindow_Impl() const +{ + return m_pImpl->pWorkWin; +} + +void SfxFrame::CreateWorkWindow_Impl() +{ + SfxFrame* pFrame = this; + + if ( IsInPlace() ) + { + // this makes sense only for inplace activated objects + try + { + Reference < XChild > xChild( GetCurrentDocument()->GetModel(), UNO_QUERY ); + if ( xChild.is() ) + { + Reference < XModel > xParent( xChild->getParent(), UNO_QUERY ); + if ( xParent.is() ) + { + Reference< XController > xParentCtrler = xParent->getCurrentController(); + if ( xParentCtrler.is() ) + { + Reference < XFrame > xFrame( xParentCtrler->getFrame() ); + SfxFrame* pFr = SfxFrame::GetFirst(); + while ( pFr ) + { + if ( pFr->GetFrameInterface() == xFrame ) + { + pFrame = pFr; + break; + } + + pFr = SfxFrame::GetNext( *pFr ); + } + } + } + } + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION( "sfx.view", "SfxFrame::CreateWorkWindow_Impl: Exception caught. Please try to submit a reproducible bug!"); + } + } + + m_pImpl->pWorkWin = new SfxWorkWindow( &pFrame->GetWindow(), this, pFrame ); +} + +void SfxFrame::GrabFocusOnComponent_Impl() +{ + if ( m_pImpl->bReleasingComponent ) + { + GetWindow().GrabFocus(); + return; + } + + vcl::Window* pFocusWindow = &GetWindow(); + if ( GetCurrentViewFrame() && GetCurrentViewFrame()->GetViewShell() && GetCurrentViewFrame()->GetViewShell()->GetWindow() ) + pFocusWindow = GetCurrentViewFrame()->GetViewShell()->GetWindow(); + + if( !pFocusWindow->HasChildPathFocus() ) + pFocusWindow->GrabFocus(); +} + +void SfxFrame::ReleasingComponent_Impl() +{ + m_pImpl->bReleasingComponent = true; +} + +bool SfxFrame::IsInPlace() const +{ + return m_pImpl->bInPlace; +} + +void SfxFrame::Resize() +{ + if ( IsClosing_Impl() ) + return; + + if ( OwnsBindings_Impl() ) + { + if ( IsInPlace() ) + { + SetToolSpaceBorderPixel_Impl( SvBorder() ); + } + else + { + // check for IPClient that contains UIactive object or object that is currently UI activating + SfxWorkWindow *pWork = GetWorkWindow_Impl(); + SfxInPlaceClient* pClient = GetCurrentViewFrame()->GetViewShell() ? GetCurrentViewFrame()->GetViewShell()->GetUIActiveIPClient_Impl() : nullptr; + if ( pClient ) + { + SfxObjectShell* pDoc + = SfxObjectShell::GetShellFromComponent(pClient->GetObject()->getComponent()); + SfxViewFrame* pFrame = SfxViewFrame::GetFirst(pDoc); + pWork = pFrame ? pFrame->GetFrame().GetWorkWindow_Impl() : nullptr; + } + + if ( pWork ) + { + pWork->ArrangeChildren_Impl(); + pWork->ShowChildren_Impl(); + } + + // problem in presence of UIActive object: when the window is resized, but the toolspace border + // remains the same, setting the toolspace border at the ContainerEnvironment doesn't force a + // resize on the IPEnvironment; without that no resize is called for the SfxViewFrame. So always + // set the window size of the SfxViewFrame explicit. + SetToolSpaceBorderPixel_Impl( m_pImpl->aBorder ); + } + } + else if ( m_pImpl->pCurrentViewFrame ) + { + m_pImpl->pCurrentViewFrame->GetWindow().SetSizePixel( GetWindow().GetOutputSizePixel() ); + } + +} + +SfxFrame* SfxFrame::GetFirst() +{ + return gaFramesArr_Impl.empty() ? nullptr : gaFramesArr_Impl.front(); +} + +SfxFrame* SfxFrame::GetNext( SfxFrame& rFrame ) +{ + auto it = std::find( gaFramesArr_Impl.begin(), gaFramesArr_Impl.end(), &rFrame ); + if ( it != gaFramesArr_Impl.end() && (++it) != gaFramesArr_Impl.end() ) + return *it; + else + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/frame2.cxx b/sfx2/source/view/frame2.cxx new file mode 100644 index 0000000000..1ae9ff4ad1 --- /dev/null +++ b/sfx2/source/view/frame2.cxx @@ -0,0 +1,406 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "impframe.hxx" +#include <workwin.hxx> + +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/sfxuno.hxx> +#include <sfx2/viewsh.hxx> + +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XComponentLoader.hpp> +#include <com/sun/star/frame/Frame.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> + +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/processfactory.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/event.hxx> +#include <vcl/syswin.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; +using ::com::sun::star::frame::XComponentLoader; + +class SfxFrameWindow_Impl : public vcl::Window +{ + DECL_LINK(ModalHierarchyHdl, bool, void); +public: + SfxFrame* m_pFrame; + + SfxFrameWindow_Impl( SfxFrame* pF, vcl::Window& i_rContainerWindow ); + + virtual void DataChanged( const DataChangedEvent& rDCEvt ) override; + virtual void StateChanged( StateChangedType nStateChange ) override; + virtual bool PreNotify( NotifyEvent& rNEvt ) override; + virtual bool EventNotify( NotifyEvent& rEvt ) override; + virtual void Resize() override; + virtual void GetFocus() override; + virtual void dispose() override; + void DoResize(); +}; + +SfxFrameWindow_Impl::SfxFrameWindow_Impl(SfxFrame* pF, vcl::Window& i_rContainerWindow) + : Window(&i_rContainerWindow, WB_BORDER | WB_CLIPCHILDREN | WB_NODIALOGCONTROL | WB_3DLOOK) + , m_pFrame(pF) +{ + i_rContainerWindow.SetModalHierarchyHdl(LINK(this, SfxFrameWindow_Impl, ModalHierarchyHdl)); +} + +void SfxFrameWindow_Impl::dispose() +{ + GetParent()->SetModalHierarchyHdl(Link<bool, void>()); + vcl::Window::dispose(); +} + +void SfxFrameWindow_Impl::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + // tdf#131613 the printers changing has no effect on window layout + if (rDCEvt.GetType() == DataChangedEventType::PRINTER) + return; + SfxWorkWindow *pWorkWin = m_pFrame->GetWorkWindow_Impl(); + if ( pWorkWin ) + pWorkWin->DataChanged_Impl(); +} + +bool SfxFrameWindow_Impl::EventNotify( NotifyEvent& rNEvt ) +{ + if ( m_pFrame->IsClosing_Impl() || !m_pFrame->GetFrameInterface().is() ) + return false; + + SfxViewFrame* pView = m_pFrame->GetCurrentViewFrame(); + if ( !pView || !pView->GetObjectShell() ) + return Window::EventNotify( rNEvt ); + + if ( rNEvt.GetType() == NotifyEventType::GETFOCUS ) + { + if ( pView->GetViewShell() && !pView->GetViewShell()->GetUIActiveIPClient_Impl() && !m_pFrame->IsInPlace() ) + { + SAL_INFO("sfx", "SfxFrame: GotFocus"); + pView->MakeActive_Impl( false ); + } + + // if focus was on an external window, the clipboard content might have been changed + pView->GetBindings().Invalidate( SID_PASTE ); + pView->GetBindings().Invalidate( SID_PASTE_SPECIAL ); + return true; + } + else if( rNEvt.GetType() == NotifyEventType::KEYINPUT ) + { + if ( pView->GetViewShell()->KeyInput( *rNEvt.GetKeyEvent() ) ) + return true; + } + + return Window::EventNotify( rNEvt ); +} + +IMPL_LINK(SfxFrameWindow_Impl, ModalHierarchyHdl, bool, bSetModal, void) +{ + SfxViewFrame* pView = m_pFrame->GetCurrentViewFrame(); + if (!pView || !pView->GetObjectShell()) + return; + pView->SetModalMode(bSetModal); +} + +bool SfxFrameWindow_Impl::PreNotify( NotifyEvent& rNEvt ) +{ + NotifyEventType nType = rNEvt.GetType(); + if ( nType == NotifyEventType::KEYINPUT || nType == NotifyEventType::KEYUP ) + { + SfxViewFrame* pView = m_pFrame->GetCurrentViewFrame(); + SfxViewShell* pShell = pView ? pView->GetViewShell() : nullptr; + if ( pShell && pShell->HasKeyListeners_Impl() && pShell->HandleNotifyEvent_Impl( rNEvt ) ) + return true; + } + else if ( nType == NotifyEventType::MOUSEBUTTONUP || nType == NotifyEventType::MOUSEBUTTONDOWN ) + { + vcl::Window* pWindow = rNEvt.GetWindow(); + SfxViewFrame* pView = m_pFrame->GetCurrentViewFrame(); + SfxViewShell* pShell = pView ? pView->GetViewShell() : nullptr; + if ( pShell ) + if ( pWindow == pShell->GetWindow() || pShell->GetWindow()->IsChild( pWindow ) ) + if ( pShell->HasMouseClickListeners_Impl() && pShell->HandleNotifyEvent_Impl( rNEvt ) ) + return true; + } + + if ( nType == NotifyEventType::MOUSEBUTTONDOWN ) + { + vcl::Window* pWindow = rNEvt.GetWindow(); + const MouseEvent* pMEvent = rNEvt.GetMouseEvent(); + Point aPos = pWindow->OutputToScreenPixel( pMEvent->GetPosPixel() ); + SfxWorkWindow *pWorkWin = m_pFrame->GetWorkWindow_Impl(); + if ( pWorkWin ) + pWorkWin->EndAutoShow_Impl( aPos ); + } + + return Window::PreNotify( rNEvt ); +} + +void SfxFrameWindow_Impl::GetFocus() +{ + if ( m_pFrame && !m_pFrame->IsClosing_Impl() && + m_pFrame->GetCurrentViewFrame() && + m_pFrame->GetFrameInterface().is() ) + m_pFrame->GetCurrentViewFrame()->MakeActive_Impl( true ); +} + +void SfxFrameWindow_Impl::Resize() +{ + if ( IsReallyVisible() || IsReallyShown() || GetOutputSizePixel().Width() ) + DoResize(); +} + +void SfxFrameWindow_Impl::StateChanged( StateChangedType nStateChange ) +{ + if ( nStateChange == StateChangedType::InitShow ) + { + m_pFrame->m_pImpl->bHidden = false; + if ( m_pFrame->IsInPlace() ) + // TODO/MBA: workaround for bug in LayoutManager: the final resize does not get through because the + // LayoutManager works asynchronously and between resize and time execution the DockingAcceptor was exchanged so that + // the resize event never is sent to the component + SetSizePixel( GetParent()->GetOutputSizePixel() ); + + DoResize(); + SfxViewFrame* pView = m_pFrame->GetCurrentViewFrame(); + if ( pView ) + pView->GetBindings().GetWorkWindow_Impl()->ShowChildren_Impl(); + } + + Window::StateChanged( nStateChange ); +} + +void SfxFrameWindow_Impl::DoResize() +{ + if ( !m_pFrame->m_pImpl->bLockResize ) + m_pFrame->Resize(); +} + +Reference < XFrame > SfxFrame::CreateBlankFrame() +{ + Reference < XFrame > xFrame; + try + { + Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); + xFrame.set( xDesktop->findFrame( "_blank", 0 ), UNO_SET_THROW ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + return xFrame; +} + +SfxFrame* SfxFrame::CreateHidden( SfxObjectShell const & rDoc, vcl::Window& rWindow, SfxInterfaceId nViewId ) +{ + SfxFrame* pFrame = nullptr; + try + { + // create and initialize new top level frame for this window + Reference < XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + Reference < XDesktop2 > xDesktop = Desktop::create( xContext ); + Reference < XFrame2 > xFrame = Frame::create( xContext ); + + Reference< awt::XWindow2 > xWin( VCLUnoHelper::GetInterface ( &rWindow ), uno::UNO_QUERY_THROW ); + xFrame->initialize( xWin ); + xDesktop->getFrames()->append( xFrame ); + + if ( xWin->isActive() ) + xFrame->activate(); + + // create load arguments + Sequence< PropertyValue > aLoadArgs; + TransformItems( SID_OPENDOC, rDoc.GetMedium()->GetItemSet(), aLoadArgs ); + + ::comphelper::NamedValueCollection aArgs( aLoadArgs ); + aArgs.put( "Model", rDoc.GetModel() ); + aArgs.put( "Hidden", true ); + if ( nViewId != SFX_INTERFACE_NONE ) + aArgs.put( "ViewId", static_cast<sal_uInt16>(nViewId) ); + + aLoadArgs = aArgs.getPropertyValues(); + + // load the doc into that frame + Reference< XComponentLoader > xLoader( xFrame, UNO_QUERY_THROW ); + xLoader->loadComponentFromURL( + "private:object", + "_self", + 0, + aLoadArgs + ); + + for ( pFrame = SfxFrame::GetFirst(); + pFrame; + pFrame = SfxFrame::GetNext( *pFrame ) + ) + { + if ( pFrame->GetFrameInterface() == xFrame ) + break; + } + + OSL_ENSURE( pFrame, "SfxFrame::Create: load succeeded, but no SfxFrame was created during this!" ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + + return pFrame; +} + +SfxFrame* SfxFrame::Create( const Reference < XFrame >& i_rFrame ) +{ + // create a new TopFrame to an external XFrame object ( wrap controller ) + ENSURE_OR_THROW( i_rFrame.is(), "NULL frame not allowed" ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( i_rFrame->getContainerWindow() ); + ENSURE_OR_THROW( pWindow, "frame without container window not allowed" ); + + SfxFrame* pFrame = new SfxFrame( *pWindow ); + pFrame->SetFrameInterface_Impl( i_rFrame ); + return pFrame; +} + +SfxFrame::SfxFrame( vcl::Window& i_rContainerWindow ) + :SvCompatWeakBase<SfxFrame>( this ) + ,m_pWindow( nullptr ) +{ + Construct_Impl(); + + m_pImpl->bHidden = false; + InsertTopFrame_Impl( this ); + m_pImpl->pExternalContainerWindow = &i_rContainerWindow; + + m_pWindow = VclPtr<SfxFrameWindow_Impl>::Create( this, i_rContainerWindow ); + + // always show pWindow, which is the ComponentWindow of the XFrame we live in + // nowadays, since SfxFrames can be created with an XFrame only, hiding or showing the complete XFrame + // is not done at level of the container window, not at SFX level. Thus, the component window can + // always be visible. + m_pWindow->Show(); +} + +void SfxFrame::SetPresentationMode( bool bSet ) +{ + if ( GetCurrentViewFrame() ) + GetCurrentViewFrame()->GetWindow().SetBorderStyle( bSet ? WindowBorderStyle::NOBORDER : WindowBorderStyle::NORMAL ); + + Reference< css::beans::XPropertySet > xPropSet( GetFrameInterface(), UNO_QUERY ); + Reference< css::frame::XLayoutManager > xLayoutManager; + + if ( xPropSet.is() ) + { + Any aValue = xPropSet->getPropertyValue("LayoutManager"); + aValue >>= xLayoutManager; + } + + if ( xLayoutManager.is() ) + xLayoutManager->setVisible( !bSet ); // we don't want to have ui in presentation mode + + SetMenuBarOn_Impl( !bSet ); + if ( GetWorkWindow_Impl() ) + GetWorkWindow_Impl()->SetDockingAllowed( !bSet ); + if ( GetCurrentViewFrame() ) + GetCurrentViewFrame()->GetDispatcher()->Update_Impl( true ); +} + +SystemWindow* SfxFrame::GetSystemWindow() const +{ + return GetTopWindow_Impl(); +} + +SystemWindow* SfxFrame::GetTopWindow_Impl() const +{ + if ( m_pImpl->pExternalContainerWindow->IsSystemWindow() ) + return static_cast<SystemWindow*>( m_pImpl->pExternalContainerWindow.get() ); + else + return nullptr; +} + + +void SfxFrame::LockResize_Impl( bool bLock ) +{ + m_pImpl->bLockResize = bLock; +} + +void SfxFrame::SetMenuBarOn_Impl( bool bOn ) +{ + m_pImpl->bMenuBarOn = bOn; + + Reference< css::beans::XPropertySet > xPropSet( GetFrameInterface(), UNO_QUERY ); + Reference< css::frame::XLayoutManager > xLayoutManager; + + if ( xPropSet.is() ) + { + Any aValue = xPropSet->getPropertyValue("LayoutManager"); + aValue >>= xLayoutManager; + } + + if ( xLayoutManager.is() ) + { + OUString aMenuBarURL( "private:resource/menubar/menubar" ); + + if ( bOn ) + xLayoutManager->showElement( aMenuBarURL ); + else + xLayoutManager->hideElement( aMenuBarURL ); + } +} + +bool SfxFrame::IsMenuBarOn_Impl() const +{ + return m_pImpl->bMenuBarOn; +} + +void SfxFrame::PrepareForDoc_Impl( const SfxObjectShell& i_rDoc ) +{ + const ::comphelper::NamedValueCollection aDocumentArgs( i_rDoc.GetModel()->getArgs2( { "Hidden", "PluginMode" } ) ); + + // hidden? + OSL_ENSURE( !m_pImpl->bHidden, "when does this happen?" ); + m_pImpl->bHidden = aDocumentArgs.getOrDefault( "Hidden", m_pImpl->bHidden ); + + // update our descriptor + UpdateDescriptor( &i_rDoc ); + + // plugin mode + sal_Int16 nPluginMode = aDocumentArgs.getOrDefault( "PluginMode", sal_Int16( 0 ) ); + if ( nPluginMode && ( nPluginMode != 2 ) ) + m_pImpl->bInPlace = true; +} + +bool SfxFrame::IsMarkedHidden_Impl() const +{ + return m_pImpl->bHidden; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/frmload.cxx b/sfx2/source/view/frmload.cxx new file mode 100644 index 0000000000..7f58a397cc --- /dev/null +++ b/sfx2/source/view/frmload.cxx @@ -0,0 +1,826 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/docfac.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/doctempl.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfac.hxx> + +#include <com/sun/star/container/XContainerQuery.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XLoadable.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/XInteractionHandler2.hpp> +#include <com/sun/star/document/XViewDataSupplier.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/frame/XSynchronousFrameLoader.hpp> +#include <com/sun/star/frame/XController2.hpp> +#include <com/sun/star/frame/XModel2.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/XCloseable.hpp> + +#include <comphelper/interaction.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <framework/interaction.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <svl/eitem.hxx> +#include <svl/stritem.hxx> +#include <unotools/fcm.hxx> +#include <unotools/moduleoptions.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <vcl/svapp.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star; +using ::com::sun::star::beans::PropertyValue; +using ::com::sun::star::container::XContainerQuery; +using ::com::sun::star::container::XEnumeration; +using ::com::sun::star::document::XTypeDetection; +using ::com::sun::star::frame::XFrame; +using ::com::sun::star::frame::XLoadable; +using ::com::sun::star::task::XInteractionHandler; +using ::com::sun::star::task::XInteractionHandler2; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::RuntimeException; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::util::XCloseable; +using ::com::sun::star::document::XViewDataSupplier; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::frame::XController2; +using ::com::sun::star::frame::XModel2; + +namespace { + +class SfxFrameLoader_Impl : public ::cppu::WeakImplHelper< css::frame::XSynchronousFrameLoader, css::lang::XServiceInfo > +{ + css::uno::Reference < css::uno::XComponentContext > m_aContext; + +public: + explicit SfxFrameLoader_Impl( const css::uno::Reference < css::uno::XComponentContext >& _rxContext ); + + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + + // XSynchronousFrameLoader + + virtual sal_Bool SAL_CALL load( const css::uno::Sequence< css::beans::PropertyValue >& _rArgs, const css::uno::Reference< css::frame::XFrame >& _rxFrame ) override; + virtual void SAL_CALL cancel() override; + +protected: + virtual ~SfxFrameLoader_Impl() override; + +private: + std::shared_ptr<const SfxFilter> impl_getFilterFromServiceName_nothrow( + const OUString& i_rServiceName + ) const; + + static OUString impl_askForFilter_nothrow( + const css::uno::Reference< css::task::XInteractionHandler >& i_rxHandler, + const OUString& i_rDocumentURL + ); + + std::shared_ptr<const SfxFilter> impl_detectFilterForURL( + const OUString& _rURL, + const ::comphelper::NamedValueCollection& i_rDescriptor, + const SfxFilterMatcher& rMatcher + ) const; + + static bool impl_createNewDocWithSlotParam( + const sal_uInt16 _nSlotID, + const css::uno::Reference< css::frame::XFrame >& i_rxFrame, + const bool i_bHidden + ); + + void impl_determineFilter( + ::comphelper::NamedValueCollection& io_rDescriptor + ) const; + + bool impl_determineTemplateDocument( + ::comphelper::NamedValueCollection& io_rDescriptor + ) const; + + static sal_uInt16 impl_findSlotParam( + std::u16string_view i_rFactoryURL + ); + + static SfxObjectShellRef impl_findObjectShell( + const css::uno::Reference< css::frame::XModel2 >& i_rxDocument + ); + + static void impl_handleCaughtError_nothrow( + const css::uno::Any& i_rCaughtError, + const ::comphelper::NamedValueCollection& i_rDescriptor + ); + + static void impl_removeLoaderArguments( + ::comphelper::NamedValueCollection& io_rDescriptor + ); + + static SfxInterfaceId impl_determineEffectiveViewId_nothrow( + const SfxObjectShell& i_rDocument, + const ::comphelper::NamedValueCollection& i_rDescriptor + ); + + static ::comphelper::NamedValueCollection + impl_extractViewCreationArgs( + ::comphelper::NamedValueCollection& io_rDescriptor + ); + + static css::uno::Reference< css::frame::XController2 > + impl_createDocumentView( + const css::uno::Reference< css::frame::XModel2 >& i_rModel, + const css::uno::Reference< css::frame::XFrame >& i_rFrame, + const ::comphelper::NamedValueCollection& i_rViewFactoryArgs, + const OUString& i_rViewName + ); +}; + +SfxFrameLoader_Impl::SfxFrameLoader_Impl( const Reference< css::uno::XComponentContext >& _rxContext ) + :m_aContext( _rxContext ) +{ +} + +SfxFrameLoader_Impl::~SfxFrameLoader_Impl() +{ +} + + +std::shared_ptr<const SfxFilter> SfxFrameLoader_Impl::impl_detectFilterForURL( const OUString& sURL, + const ::comphelper::NamedValueCollection& i_rDescriptor, const SfxFilterMatcher& rMatcher ) const +{ + OUString sFilter; + try + { + if ( sURL.isEmpty() ) + return nullptr; + + Reference< XTypeDetection > xDetect( + m_aContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", m_aContext), + UNO_QUERY_THROW); + + ::comphelper::NamedValueCollection aNewArgs; + aNewArgs.put( "URL", sURL ); + + if ( i_rDescriptor.has( "InteractionHandler" ) ) + aNewArgs.put( "InteractionHandler", i_rDescriptor.get( "InteractionHandler" ) ); + if ( i_rDescriptor.has( "StatusIndicator" ) ) + aNewArgs.put( "StatusIndicator", i_rDescriptor.get( "StatusIndicator" ) ); + + Sequence< PropertyValue > aQueryArgs( aNewArgs.getPropertyValues() ); + OUString sType = xDetect->queryTypeByDescriptor( aQueryArgs, true ); + if ( !sType.isEmpty() ) + { + std::shared_ptr<const SfxFilter> pFilter = rMatcher.GetFilter4EA( sType ); + if ( pFilter ) + sFilter = pFilter->GetName(); + } + } + catch ( const RuntimeException& ) + { + throw; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + sFilter.clear(); + } + + std::shared_ptr<const SfxFilter> pFilter; + if (!sFilter.isEmpty()) + pFilter = rMatcher.GetFilter4FilterName(sFilter); + return pFilter; +} + + +std::shared_ptr<const SfxFilter> SfxFrameLoader_Impl::impl_getFilterFromServiceName_nothrow( const OUString& i_rServiceName ) const +{ + try + { + ::comphelper::NamedValueCollection aQuery; + aQuery.put( "DocumentService", i_rServiceName ); + + const Reference< XContainerQuery > xQuery( + m_aContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", m_aContext), + UNO_QUERY_THROW ); + + const SfxFilterMatcher& rMatcher = SfxGetpApp()->GetFilterMatcher(); + const SfxFilterFlags nMust = SfxFilterFlags::IMPORT; + const SfxFilterFlags nDont = SFX_FILTER_NOTINSTALLED; + + Reference < XEnumeration > xEnum( xQuery->createSubSetEnumerationByProperties( + aQuery.getNamedValues() ), UNO_SET_THROW ); + while ( xEnum->hasMoreElements() ) + { + ::comphelper::NamedValueCollection aType( xEnum->nextElement() ); + OUString sFilterName = aType.getOrDefault( "Name", OUString() ); + if ( sFilterName.isEmpty() ) + continue; + + std::shared_ptr<const SfxFilter> pFilter = rMatcher.GetFilter4FilterName( sFilterName ); + if ( !pFilter ) + continue; + + SfxFilterFlags nFlags = pFilter->GetFilterFlags(); + if ( ( ( nFlags & nMust ) == nMust ) + && ( ( nFlags & nDont ) == SfxFilterFlags::NONE ) + ) + { + return pFilter; + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + return nullptr; +} + + +OUString SfxFrameLoader_Impl::impl_askForFilter_nothrow( const Reference< XInteractionHandler >& i_rxHandler, + const OUString& i_rDocumentURL ) +{ + ENSURE_OR_THROW( i_rxHandler.is(), "invalid interaction handler" ); + + OUString sFilterName; + try + { + ::framework::RequestFilterSelect aRequest( i_rDocumentURL ); + i_rxHandler->handle( aRequest.GetRequest() ); + if( !aRequest.isAbort() ) + sFilterName = aRequest.getFilter(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + + return sFilterName; +} + +bool lcl_getDispatchResult(const SfxPoolItemHolder& rResult) +{ + if (nullptr == rResult.getItem()) + return false; + + // default must be set to true, because some return values + // can't be checked, but nonetheless indicate "success"! + bool bSuccess = true; + + // On the other side some special slots return a boolean state, + // which can be set to FALSE. + const SfxBoolItem* pItem(dynamic_cast<const SfxBoolItem*>(rResult.getItem())); + if ( pItem ) + bSuccess = pItem->GetValue(); + + return bSuccess; +} + +bool SfxFrameLoader_Impl::impl_createNewDocWithSlotParam( const sal_uInt16 _nSlotID, const Reference< XFrame >& i_rxFrame, + const bool i_bHidden ) +{ + SfxRequest aRequest( _nSlotID, SfxCallMode::SYNCHRON, SfxGetpApp()->GetPool() ); + aRequest.AppendItem( SfxUnoFrameItem( SID_FILLFRAME, i_rxFrame ) ); + if ( i_bHidden ) + aRequest.AppendItem( SfxBoolItem( SID_HIDDEN, true ) ); + return lcl_getDispatchResult(SfxGetpApp()->ExecuteSlot(aRequest)); +} + + +void SfxFrameLoader_Impl::impl_determineFilter( ::comphelper::NamedValueCollection& io_rDescriptor ) const +{ + const OUString sURL = io_rDescriptor.getOrDefault( "URL", OUString() ); + const OUString sTypeName = io_rDescriptor.getOrDefault( "TypeName", OUString() ); + const OUString sFilterName = io_rDescriptor.getOrDefault( "FilterName", OUString() ); + const OUString sServiceName = io_rDescriptor.getOrDefault( "DocumentService", OUString() ); + const Reference< XInteractionHandler > + xInteraction = io_rDescriptor.getOrDefault( "InteractionHandler", Reference< XInteractionHandler >() ); + + const SfxFilterMatcher& rMatcher = SfxGetpApp()->GetFilterMatcher(); + std::shared_ptr<const SfxFilter> pFilter; + + // get filter by its name directly ... + if ( !sFilterName.isEmpty() ) + pFilter = rMatcher.GetFilter4FilterName( sFilterName ); + + // or search the preferred filter for the detected type ... + if ( !pFilter && !sTypeName.isEmpty() ) + pFilter = rMatcher.GetFilter4EA( sTypeName ); + + // or use given document service for detection, too + if ( !pFilter && !sServiceName.isEmpty() ) + pFilter = impl_getFilterFromServiceName_nothrow( sServiceName ); + + // or use interaction to ask user for right filter. + if ( !pFilter && xInteraction.is() && !sURL.isEmpty() ) + { + OUString sSelectedFilter = impl_askForFilter_nothrow( xInteraction, sURL ); + if ( !sSelectedFilter.isEmpty() ) + pFilter = rMatcher.GetFilter4FilterName( sSelectedFilter ); + } + + if ( !pFilter ) + return; + + io_rDescriptor.put( "FilterName", pFilter->GetFilterName() ); + + // If detected filter indicates using of an own template format + // add property "AsTemplate" to descriptor. But suppress this step + // if such property already exists. + if ( pFilter->IsOwnTemplateFormat() && !io_rDescriptor.has( "AsTemplate" ) ) + io_rDescriptor.put( "AsTemplate", true ); + + // The DocumentService property will finally be used to determine the document type to create, so + // override it with the service name as indicated by the found filter. + io_rDescriptor.put( "DocumentService", pFilter->GetServiceName() ); +} + + +SfxObjectShellRef SfxFrameLoader_Impl::impl_findObjectShell( const Reference< XModel2 >& i_rxDocument ) +{ + for ( SfxObjectShell* pDoc = SfxObjectShell::GetFirst( nullptr, false ); pDoc; + pDoc = SfxObjectShell::GetNext( *pDoc, nullptr, false ) ) + { + if ( i_rxDocument == pDoc->GetModel() ) + { + return pDoc; + } + } + + SAL_WARN( "sfx.view", "SfxFrameLoader_Impl::impl_findObjectShell: model is not based on SfxObjectShell - wrong frame loader usage!" ); + return nullptr; +} + + +bool SfxFrameLoader_Impl::impl_determineTemplateDocument( ::comphelper::NamedValueCollection& io_rDescriptor ) const +{ + try + { + const OUString sTemplateRegioName = io_rDescriptor.getOrDefault( "TemplateRegionName", OUString() ); + const OUString sTemplateName = io_rDescriptor.getOrDefault( "TemplateName", OUString() ); + const OUString sServiceName = io_rDescriptor.getOrDefault( "DocumentService", OUString() ); + const OUString sURL = io_rDescriptor.getOrDefault( "URL", OUString() ); + + // determine the full URL of the template to use, if any + OUString sTemplateURL; + if ( !sTemplateRegioName.isEmpty() && !sTemplateName.isEmpty() ) + { + SfxDocumentTemplates aTmpFac; + aTmpFac.GetFull( sTemplateRegioName, sTemplateName, sTemplateURL ); + } + else + { + if ( !sServiceName.isEmpty() ) + sTemplateURL = SfxObjectFactory::GetStandardTemplate( sServiceName ); + else + sTemplateURL = SfxObjectFactory::GetStandardTemplate( SfxObjectShell::GetServiceNameFromFactory( sURL ) ); + } + + if ( !sTemplateURL.isEmpty() ) + { + // detect the filter for the template. Might still be NULL (if the template is broken, or does not + // exist, or some such), but this is handled by our caller the same way as if no template/URL was present. + std::shared_ptr<const SfxFilter> pTemplateFilter = impl_detectFilterForURL( sTemplateURL, io_rDescriptor, SfxGetpApp()->GetFilterMatcher() ); + if ( pTemplateFilter ) + { + // load the template document, but, well, "as template" + io_rDescriptor.put( "FilterName", pTemplateFilter->GetName() ); + io_rDescriptor.put( "FileName", sTemplateURL ); + io_rDescriptor.put( "AsTemplate", true ); + + // #i21583# + // the DocumentService property will finally be used to create the document. Thus, override any possibly + // present value with the document service of the template. + io_rDescriptor.put( "DocumentService", pTemplateFilter->GetServiceName() ); + return true; + } + } + } + catch (...) + { + } + return false; +} + + +sal_uInt16 SfxFrameLoader_Impl::impl_findSlotParam( std::u16string_view i_rFactoryURL ) +{ + std::u16string_view sSlotParam; + const size_t nParamPos = i_rFactoryURL.find( '?' ); + if ( nParamPos != std::u16string_view::npos ) + { + // currently only the "slot" parameter is supported + const size_t nSlotPos = i_rFactoryURL.find( u"slot=", nParamPos ); + if ( nSlotPos > 0 && nSlotPos != std::u16string_view::npos ) + sSlotParam = i_rFactoryURL.substr( nSlotPos + 5 ); + } + + if ( !sSlotParam.empty() ) + return sal_uInt16( o3tl::toInt32(sSlotParam) ); + + return 0; +} + + +void SfxFrameLoader_Impl::impl_handleCaughtError_nothrow( const Any& i_rCaughtError, const ::comphelper::NamedValueCollection& i_rDescriptor ) +{ + try + { + const Reference< XInteractionHandler > xInteraction = + i_rDescriptor.getOrDefault( "InteractionHandler", Reference< XInteractionHandler >() ); + if ( !xInteraction.is() ) + return; + ::rtl::Reference< ::comphelper::OInteractionRequest > pRequest( new ::comphelper::OInteractionRequest( i_rCaughtError ) ); + ::rtl::Reference< ::comphelper::OInteractionApprove > pApprove( new ::comphelper::OInteractionApprove ); + pRequest->addContinuation( pApprove ); + + const Reference< XInteractionHandler2 > xHandler( xInteraction, UNO_QUERY ); + #if OSL_DEBUG_LEVEL > 0 + const bool bHandled = + #endif + xHandler.is() && xHandler->handleInteractionRequest( pRequest ); + + #if OSL_DEBUG_LEVEL > 0 + if ( !bHandled ) + // the interaction handler couldn't deal with this error + // => report it as assertion, at least (done in the DBG_UNHANDLED_EXCEPTION below) + ::cppu::throwException( i_rCaughtError ); + #endif + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } +} + + +void SfxFrameLoader_Impl::impl_removeLoaderArguments( ::comphelper::NamedValueCollection& io_rDescriptor ) +{ + // remove the arguments which are for the loader only, and not for a call to attachResource + io_rDescriptor.remove( "StatusIndicator" ); + io_rDescriptor.remove( "Model" ); +} + + +::comphelper::NamedValueCollection SfxFrameLoader_Impl::impl_extractViewCreationArgs( ::comphelper::NamedValueCollection& io_rDescriptor ) +{ + static const std::u16string_view sKnownViewArgs[] = { u"JumpMark", u"PickListEntry" }; + + ::comphelper::NamedValueCollection aViewArgs; + for (const auto& rKnownViewArg : sKnownViewArgs) + { + const OUString sKnownViewArg(rKnownViewArg); + if ( io_rDescriptor.has( sKnownViewArg ) ) + { + aViewArgs.put( sKnownViewArg, io_rDescriptor.get( sKnownViewArg ) ); + io_rDescriptor.remove( sKnownViewArg ); + } + } + return aViewArgs; +} + + +SfxInterfaceId SfxFrameLoader_Impl::impl_determineEffectiveViewId_nothrow( const SfxObjectShell& i_rDocument, const ::comphelper::NamedValueCollection& i_rDescriptor ) +{ + SfxInterfaceId nViewId(i_rDescriptor.getOrDefault( "ViewId", sal_Int16( 0 ) )); + try + { + if ( nViewId == SFX_INTERFACE_NONE ) + do + { + Reference< XViewDataSupplier > xViewDataSupplier( i_rDocument.GetModel(), UNO_QUERY ); + Reference< XIndexAccess > xViewData; + if ( xViewDataSupplier.is() ) + xViewData.set( xViewDataSupplier->getViewData() ); + + if ( !xViewData.is() || ( xViewData->getCount() == 0 ) ) + // no view data stored together with the model + break; + + // obtain the ViewID from the view data + Sequence< PropertyValue > aViewData; + if ( !( xViewData->getByIndex( 0 ) >>= aViewData ) ) + break; + + OUString sViewId = ::comphelper::NamedValueCollection::getOrDefault( aViewData, u"ViewId", OUString() ); + if ( sViewId.isEmpty() ) + break; + + // somewhat weird convention here ... in the view data, the ViewId is a string, effectively describing + // a view name. In the document load descriptor, the ViewId is in fact the numeric ID. + + SfxViewFactory* pViewFactory = i_rDocument.GetFactory().GetViewFactoryByViewName( sViewId ); + if ( pViewFactory ) + nViewId = pViewFactory->GetOrdinal(); + } + while ( false ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + + if ( nViewId == SFX_INTERFACE_NONE ) + nViewId = i_rDocument.GetFactory().GetViewFactory().GetOrdinal(); + return nViewId; +} + + +Reference< XController2 > SfxFrameLoader_Impl::impl_createDocumentView( const Reference< XModel2 >& i_rModel, + const Reference< XFrame >& i_rFrame, const ::comphelper::NamedValueCollection& i_rViewFactoryArgs, + const OUString& i_rViewName ) +{ + // let the model create a new controller + const Reference< XController2 > xController( i_rModel->createViewController( + i_rViewName, + i_rViewFactoryArgs.getPropertyValues(), + i_rFrame + ), UNO_SET_THROW ); + + // introduce model/view/controller to each other + utl::ConnectFrameControllerModel(i_rFrame, xController, i_rModel); + + return xController; +} + +std::shared_ptr<const SfxFilter> getEmptyURLFilter(std::u16string_view sURL) +{ + INetURLObject aParser(sURL); + const OUString aExt = aParser.getExtension(INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset); + const SfxFilterMatcher& rMatcher = SfxGetpApp()->GetFilterMatcher(); + + // Requiring the export+preferred flags helps to find the relevant filter, e.g. .doc -> WW8 (and + // not WW6 or Mac_Word). + std::shared_ptr<const SfxFilter> pFilter = rMatcher.GetFilter4Extension( + aExt, SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT | SfxFilterFlags::PREFERED); + if (!pFilter) + { + // retry without PREFERED so we can find at least something for 0-byte *.ods + pFilter + = rMatcher.GetFilter4Extension(aExt, SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT); + } + return pFilter; +} + +sal_Bool SAL_CALL SfxFrameLoader_Impl::load( const Sequence< PropertyValue >& rArgs, + const Reference< XFrame >& _rTargetFrame ) +{ + ENSURE_OR_THROW( _rTargetFrame.is(), "illegal NULL frame" ); + + SAL_INFO( "sfx.view", "SfxFrameLoader::load" ); + + ::comphelper::NamedValueCollection aDescriptor( rArgs ); + + // ensure the descriptor contains a referrer + if ( !aDescriptor.has( "Referer" ) ) + aDescriptor.put( "Referer", OUString() ); + + // did the caller already pass a model? + Reference< XModel2 > xModel = aDescriptor.getOrDefault( "Model", Reference< XModel2 >() ); + const bool bExternalModel = xModel.is(); + + // check for factory URLs to create a new doc, instead of loading one + const OUString sURL = aDescriptor.getOrDefault( "URL", OUString() ); + const bool bIsFactoryURL = sURL.startsWith( "private:factory/" ); + std::shared_ptr<const SfxFilter> pEmptyURLFilter; + bool bInitNewModel = bIsFactoryURL; + const bool bIsDefault = bIsFactoryURL && !bExternalModel; + if (!aDescriptor.has("Replaceable")) + aDescriptor.put("Replaceable", bIsDefault); + if (bIsDefault) + { + const OUString sFactory = sURL.copy( sizeof( "private:factory/" ) -1 ); + // special handling for some weird factory URLs a la private:factory/swriter?slot=21053 + const sal_uInt16 nSlotParam = impl_findSlotParam( sFactory ); + if ( nSlotParam != 0 ) + { + return impl_createNewDocWithSlotParam( nSlotParam, _rTargetFrame, aDescriptor.getOrDefault( "Hidden", false ) ); + } + + const bool bDescribesValidTemplate = impl_determineTemplateDocument( aDescriptor ); + if ( bDescribesValidTemplate ) + { + // if the media descriptor allowed us to determine a template document to create the new document + // from, then do not init a new document model from scratch (below), but instead load the + // template document + bInitNewModel = false; + } + else + { + const OUString sServiceName = SfxObjectShell::GetServiceNameFromFactory( sFactory ); + aDescriptor.put( "DocumentService", sServiceName ); + } + } + else + { + // compatibility + aDescriptor.put( "FileName", aDescriptor.get( "URL" ) ); + + if (!bIsFactoryURL && !bExternalModel && tools::isEmptyFileUrl(sURL)) + { + pEmptyURLFilter = getEmptyURLFilter(sURL); + if (pEmptyURLFilter) + { + aDescriptor.put("DocumentService", pEmptyURLFilter->GetServiceName()); + if (impl_determineTemplateDocument(aDescriptor)) + { + // if the media descriptor allowed us to determine a template document + // to create the new document from, then do not init a new document model + // from scratch (below), but instead load the template document + bInitNewModel = false; + // Do not try to load from empty UCB content + aDescriptor.remove("UCBContent"); + } + else + { + bInitNewModel = true; + } + } + } + } + + bool bLoadSuccess = false; + try + { + // extract view relevant arguments from the loader args + ::comphelper::NamedValueCollection aViewCreationArgs( impl_extractViewCreationArgs( aDescriptor ) ); + + // no model passed from outside? => create one from scratch + if ( !bExternalModel ) + { + bool bInternalFilter = aDescriptor.getOrDefault<OUString>("FilterProvider", OUString()).isEmpty(); + + if (bInternalFilter && !bInitNewModel) + { + // Ensure that the current SfxFilter instance is loaded before + // going further. We don't need to do this for external + // filter providers. + impl_determineFilter(aDescriptor); + } + + // create the new doc + const OUString sServiceName = aDescriptor.getOrDefault( "DocumentService", OUString() ); + xModel.set( m_aContext->getServiceManager()->createInstanceWithContext(sServiceName, m_aContext), UNO_QUERY_THROW ); + + // load resp. init it + const Reference< XLoadable > xLoadable( xModel, UNO_QUERY_THROW ); + if ( bInitNewModel ) + { + xLoadable->initNew(); + + impl_removeLoaderArguments( aDescriptor ); + xModel->attachResource( OUString(), aDescriptor.getPropertyValues() ); + } + else + { + xLoadable->load( aDescriptor.getPropertyValues() ); + } + } + else + { + // tell the doc its (current) load args. + impl_removeLoaderArguments( aDescriptor ); + xModel->attachResource( xModel->getURL(), aDescriptor.getPropertyValues() ); + } + + SolarMutexGuard aGuard; + + // get the SfxObjectShell (still needed at the moment) + // SfxObjectShellRef is used here ( instead of ...Lock ) since the model is closed below if necessary + // SfxObjectShellLock would be even dangerous here, since the lifetime control should be done outside in case of success + const SfxObjectShellRef xDoc = impl_findObjectShell( xModel ); + ENSURE_OR_THROW( xDoc.is(), "no SfxObjectShell for the given model" ); + + if (pEmptyURLFilter) + { + // Detach the medium from the template, and set proper document name and filter + auto pMedium = xDoc->GetMedium(); + auto& rItemSet = pMedium->GetItemSet(); + rItemSet.ClearItem(SID_TEMPLATE); + rItemSet.Put(SfxStringItem(SID_FILTER_NAME, pEmptyURLFilter->GetFilterName())); + pMedium->SetName(sURL, true); + pMedium->SetFilter(pEmptyURLFilter); + pMedium->GetInitFileDate(true); + xDoc->SetLoading(SfxLoadedFlags::NONE); + xDoc->FinishedLoading(); + } + + // ensure the ID of the to-be-created view is in the descriptor, if possible + const SfxInterfaceId nViewId = impl_determineEffectiveViewId_nothrow( *xDoc, aDescriptor ); + const sal_Int16 nViewNo = xDoc->GetFactory().GetViewNo_Impl( nViewId, 0 ); + const OUString sViewName( xDoc->GetFactory().GetViewFactory( nViewNo ).GetAPIViewName() ); + + // plug the document into the frame + Reference<XController2> xController = + impl_createDocumentView( xModel, _rTargetFrame, aViewCreationArgs, sViewName ); + + Reference<lang::XInitialization> xInit(xController, UNO_QUERY); + if (xInit.is()) + { + uno::Sequence<uno::Any> aArgs; // empty for now. + xInit->initialize(aArgs); + } + + bLoadSuccess = true; + } + catch ( Exception& ) + { + const Any aError( ::cppu::getCaughtException() ); + if ( !aDescriptor.getOrDefault( "Silent", false ) ) + impl_handleCaughtError_nothrow( aError, aDescriptor ); + } + + // if loading was not successful, close the document + if ( !bLoadSuccess && !bExternalModel ) + { + try + { + const Reference< XCloseable > xCloseable( xModel, UNO_QUERY_THROW ); + xCloseable->close( true ); + } + catch ( Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + } + + return bLoadSuccess; +} + +void SfxFrameLoader_Impl::cancel() +{ +} + +/* XServiceInfo */ +OUString SAL_CALL SfxFrameLoader_Impl::getImplementationName() +{ + return "com.sun.star.comp.office.FrameLoader"; +} + +/* XServiceInfo */ +sal_Bool SAL_CALL SfxFrameLoader_Impl::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +/* XServiceInfo */ +Sequence< OUString > SAL_CALL SfxFrameLoader_Impl::getSupportedServiceNames() +{ + return { "com.sun.star.frame.SynchronousFrameLoader", "com.sun.star.frame.OfficeFrameLoader" }; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_office_FrameLoader_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SfxFrameLoader_Impl(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/impframe.hxx b/sfx2/source/view/impframe.hxx new file mode 100644 index 0000000000..b98d9170fc --- /dev/null +++ b/sfx2/source/view/impframe.hxx @@ -0,0 +1,71 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SFX2_SOURCE_VIEW_IMPFRAME_HXX +#define INCLUDED_SFX2_SOURCE_VIEW_IMPFRAME_HXX + +#include <sfx2/frame.hxx> +#include <sfx2/viewfrm.hxx> + +#include <tools/svborder.hxx> +#include <vcl/window.hxx> + +class SfxFrame_Impl : public SfxBroadcaster +{ +public: + css::uno::Reference< css::frame::XFrame > xFrame; + bool mbHasTitle; + SfxViewFrame* pCurrentViewFrame; + SfxFrameDescriptor* pDescr; + bool bClosing : 1; + bool bPrepClosing : 1; + bool bInCancelTransfers : 1; + bool bOwnsBindings : 1; + bool bReleasingComponent : 1; + bool bInPlace : 1; + SfxWorkWindow* pWorkWin; + SvBorder aBorder; + // formerly SfxTopFrame + VclPtr<vcl::Window> pExternalContainerWindow; + bool bHidden; + bool bLockResize; + bool bMenuBarOn; + + explicit SfxFrame_Impl() + :mbHasTitle( false ) + ,pCurrentViewFrame( nullptr ) + ,pDescr( nullptr ) + ,bClosing(false) + ,bPrepClosing(false) + ,bInCancelTransfers( false ) + ,bOwnsBindings( false ) + ,bReleasingComponent( false ) + ,bInPlace( false ) + ,pWorkWin( nullptr ) + ,pExternalContainerWindow( nullptr ) + ,bHidden( false ) + ,bLockResize( false ) + ,bMenuBarOn( true ) + { + } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/impviewframe.hxx b/sfx2/source/view/impviewframe.hxx new file mode 100644 index 0000000000..18675d48e2 --- /dev/null +++ b/sfx2/source/view/impviewframe.hxx @@ -0,0 +1,81 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SFX2_SOURCE_VIEW_IMPVIEWFRAME_HXX +#define INCLUDED_SFX2_SOURCE_VIEW_IMPVIEWFRAME_HXX + +#include <sfx2/viewfrm.hxx> + +#include <tools/svborder.hxx> +#include <vcl/window.hxx> + +struct SfxViewFrame_Impl +{ + SvBorder aBorder; + Size aMargin; + Size aSize; + OUString aActualURL; + SfxFrame& rFrame; + VclPtr<vcl::Window> pWindow; + sal_uInt16 nDocViewNo; + SfxInterfaceId nCurViewId; + bool bResizeInToOut:1; + bool bObjLocked:1; + bool bReloading:1; + bool bIsDowning:1; + bool bModal:1; + bool bEnabled:1; + bool bWindowWasEnabled:1; + OUString aFactoryName; + + explicit SfxViewFrame_Impl(SfxFrame& i_rFrame) + : rFrame(i_rFrame) + , pWindow(nullptr) + , nDocViewNo(0) + , nCurViewId(0) + , bResizeInToOut(false) + , bObjLocked(false) + , bReloading(false) + , bIsDowning(false) + , bModal(false) + , bEnabled(false) + , bWindowWasEnabled(true) + { + } +}; + +class SfxFrameViewWindow_Impl : public vcl::Window +{ + SfxViewFrame* pFrame; + +public: + SfxFrameViewWindow_Impl( SfxViewFrame* p, vcl::Window& rParent ) : + Window( &rParent, WB_CLIPCHILDREN ), + pFrame( p ) + { + p->GetFrame().GetWindow().SetBorderStyle( WindowBorderStyle::NOBORDER ); + } + + virtual void Resize() override; + virtual void StateChanged( StateChangedType nStateChange ) override; +}; + +#endif // INCLUDED_SFX2_SOURCE_VIEW_IMPVIEWFRAME_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/ipclient.cxx b/sfx2/source/view/ipclient.cxx new file mode 100644 index 0000000000..e6b9beb364 --- /dev/null +++ b/sfx2/source/view/ipclient.cxx @@ -0,0 +1,1148 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/UnreachableStateException.hpp> +#include <com/sun/star/embed/XEmbeddedClient.hpp> +#include <com/sun/star/embed/XInplaceClient.hpp> +#include <com/sun/star/embed/XInplaceObject.hpp> +#include <com/sun/star/embed/XWindowSupplier.hpp> +#include <com/sun/star/embed/EmbedVerbs.hpp> +#include <com/sun/star/embed/XEmbeddedOleObject.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/XStateChangeListener.hpp> +#include <com/sun/star/embed/StateChangeInProgressException.hpp> +#include <com/sun/star/embed/XLinkageSupport.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/task/ErrorCodeIOException.hpp> +#include <com/sun/star/task/StatusIndicatorFactory.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> + +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <svtools/embedhlp.hxx> +#include <vcl/svapp.hxx> + +#include <sfx2/ipclient.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/objsh.hxx> +#include <guisaveas.hxx> +#include <cppuhelper/implbase.hxx> +#include <svtools/ehdl.hxx> + +#include <vcl/timer.hxx> +#include <vcl/window.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <toolkit/helper/convert.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/fract.hxx> +#include <tools/gen.hxx> +#include <svtools/soerr.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/exc_hlp.hxx> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#define SFX_CLIENTACTIVATE_TIMEOUT 100 + +using namespace com::sun::star; + +namespace { + +// SfxEmbedResizeGuard +class SfxBooleanFlagGuard +{ + bool& m_rFlag; +public: + explicit SfxBooleanFlagGuard(bool& bFlag) + : m_rFlag( bFlag ) + { + m_rFlag = true; + } + + ~SfxBooleanFlagGuard() + { + m_rFlag = false; + } +}; + +tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect) +{ + return tools::Rectangle( + std::max(static_cast<tools::Long>(0l), -rRect.Right()), + rRect.Top(), + std::max(static_cast<tools::Long>(0l), -rRect.Left()), + rRect.Bottom()); +} + +} + +// SfxInPlaceClient_Impl + + +class SfxInPlaceClient_Impl : public ::cppu::WeakImplHelper< embed::XEmbeddedClient, + embed::XInplaceClient, + document::XEventListener, + embed::XStateChangeListener, + embed::XWindowSupplier > +{ +public: + Timer m_aTimer { "sfx::SfxInPlaceClient m_xImpl::m_aTimer" }; // activation timeout, starts after object connection + tools::Rectangle m_aObjArea; // area of object in coordinate system of the container (without scaling) + Fraction m_aScaleWidth; // scaling that was applied to the object when it was not active + Fraction m_aScaleHeight; + SfxInPlaceClient* m_pClient; + sal_Int64 m_nAspect; // ViewAspect that is assigned from the container + bool m_bStoreObject; + bool m_bUIActive; // set and cleared when notification for UI (de)activation is sent + bool m_bResizeNoScale; + bool m_bNegativeX; + + uno::Reference < embed::XEmbeddedObject > m_xObject; + + + SfxInPlaceClient_Impl() + : m_pClient( nullptr ) + , m_nAspect( 0 ) + , m_bStoreObject( true ) + , m_bUIActive( false ) + , m_bResizeNoScale( false ) + , m_bNegativeX( false ) + {} + + void SizeHasChanged(); + DECL_LINK(TimerHdl, Timer *, void); + uno::Reference < frame::XFrame > const & GetFrame() const; + + // XEmbeddedClient + virtual void SAL_CALL saveObject() override; + virtual void SAL_CALL visibilityChanged( sal_Bool bVisible ) override; + + // XInplaceClient + virtual sal_Bool SAL_CALL canInplaceActivate() override; + virtual void SAL_CALL activatingInplace() override; + virtual void SAL_CALL activatingUI() override; + virtual void SAL_CALL deactivatedInplace() override; + virtual void SAL_CALL deactivatedUI() override; + virtual uno::Reference< css::frame::XLayoutManager > SAL_CALL getLayoutManager() override; + virtual uno::Reference< frame::XDispatchProvider > SAL_CALL getInplaceDispatchProvider() override; + virtual awt::Rectangle SAL_CALL getPlacement() override; + virtual awt::Rectangle SAL_CALL getClipRectangle() override; + virtual void SAL_CALL translateAccelerators( const uno::Sequence< awt::KeyEvent >& aKeys ) override; + virtual void SAL_CALL scrollObject( const awt::Size& aOffset ) override; + virtual void SAL_CALL changedPlacement( const awt::Rectangle& aPosRect ) override; + + // XComponentSupplier + virtual uno::Reference< util::XCloseable > SAL_CALL getComponent() override; + + // XWindowSupplier + virtual uno::Reference< awt::XWindow > SAL_CALL getWindow() override; + + // document::XEventListener + virtual void SAL_CALL notifyEvent( const document::EventObject& aEvent ) override; + + // XStateChangeListener + virtual void SAL_CALL changingState( const css::lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL stateChanged( const css::lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override; +}; + +void SAL_CALL SfxInPlaceClient_Impl::changingState( + const css::lang::EventObject& /*aEvent*/, + ::sal_Int32 /*nOldState*/, + ::sal_Int32 /*nNewState*/ ) +{ +} + +void SAL_CALL SfxInPlaceClient_Impl::stateChanged( + const css::lang::EventObject& /*aEvent*/, + ::sal_Int32 nOldState, + ::sal_Int32 nNewState ) +{ + if ( m_pClient && nOldState != embed::EmbedStates::LOADED && nNewState == embed::EmbedStates::RUNNING ) + { + // deactivation of object + uno::Reference< frame::XModel > xDocument; + if ( m_pClient->GetViewShell()->GetObjectShell() ) + xDocument = m_pClient->GetViewShell()->GetObjectShell()->GetModel(); + SfxObjectShell::SetCurrentComponent( xDocument ); + } +} + +void SAL_CALL SfxInPlaceClient_Impl::notifyEvent( const document::EventObject& aEvent ) +{ + SolarMutexGuard aGuard; + + if ( m_pClient && aEvent.EventName == "OnVisAreaChanged" && m_nAspect != embed::Aspects::MSOLE_ICON ) + { + m_pClient->FormatChanged(); // for Writer when format of the object is changed with the area + m_pClient->ViewChanged(); + m_pClient->Invalidate(); + } +} + +void SAL_CALL SfxInPlaceClient_Impl::disposing( const css::lang::EventObject& /*aEvent*/ ) +{ + delete m_pClient; + m_pClient = nullptr; +} + +// XEmbeddedClient + +uno::Reference < frame::XFrame > const & SfxInPlaceClient_Impl::GetFrame() const +{ + if ( !m_pClient ) + throw uno::RuntimeException(); + return m_pClient->GetViewShell()->GetViewFrame().GetFrame().GetFrameInterface(); +} + +void SAL_CALL SfxInPlaceClient_Impl::saveObject() +{ + if (!m_bStoreObject || (m_pClient && m_pClient->IsProtected())) + // client wants to discard the object (usually it means the container document is closed while an object is active + // and the user didn't request saving the changes + return; + + // the common persistence is supported by objects and links + uno::Reference< embed::XCommonEmbedPersist > xPersist( m_xObject, uno::UNO_QUERY_THROW ); + + uno::Reference< frame::XFrame > xFrame; + uno::Reference< task::XStatusIndicator > xStatusIndicator; + uno::Reference< frame::XModel > xModel( m_xObject->getComponent(), uno::UNO_QUERY ); + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + + if ( xModel.is() ) + { + uno::Reference< frame::XController > xController = xModel->getCurrentController(); + if ( xController.is() ) + xFrame = xController->getFrame(); + } + + if ( xFrame.is() ) + { + // set non-reschedule progress to prevent problems when asynchronous calls are made + // during storing of the embedded object + uno::Reference< task::XStatusIndicatorFactory > xStatusIndicatorFactory = + task::StatusIndicatorFactory::createWithFrame( xContext, xFrame, true/*DisableReschedule*/, false/*AllowParentShow*/ ); + + uno::Reference< beans::XPropertySet > xPropSet( xFrame, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xStatusIndicator = xStatusIndicatorFactory->createStatusIndicator(); + xPropSet->setPropertyValue( "IndicatorInterception" , uno::Any( xStatusIndicator )); + } + catch ( const uno::RuntimeException& ) + { + throw; + } + catch ( uno::Exception& ) + { + } + } + } + + try + { + xPersist->storeOwn(); + m_xObject->update(); + } + catch ( uno::Exception& ) + { + //TODO/LATER: what should happen if object can't be saved?! + } + + // reset status indicator interception after storing + try + { + uno::Reference< beans::XPropertySet > xPropSet( xFrame, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + xStatusIndicator.clear(); + xPropSet->setPropertyValue( "IndicatorInterception" , uno::Any( xStatusIndicator )); + } + } + catch ( const uno::RuntimeException& ) + { + throw; + } + catch ( uno::Exception& ) + { + } + + // the client can exist only in case there is a view shell + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + SfxObjectShell* pDocShell = m_pClient->GetViewShell()->GetObjectShell(); + if ( !pDocShell ) + throw uno::RuntimeException(); + + pDocShell->SetModified(); + + //TODO/LATER: invalidation might be necessary when object was modified, but is not + //saved through this method + // m_pClient->Invalidate(); +} + + +void SAL_CALL SfxInPlaceClient_Impl::visibilityChanged( sal_Bool bVisible ) +{ + SolarMutexGuard aGuard; + + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + m_pClient->GetViewShell()->OutplaceActivated( bVisible ); + if (m_pClient) // it can change in the above code + m_pClient->Invalidate(); +} + + +// XInplaceClient + +sal_Bool SAL_CALL SfxInPlaceClient_Impl::canInplaceActivate() +{ + if ( !m_xObject.is() ) + throw uno::RuntimeException(); + + // we don't want to switch directly from outplace to inplace mode + if ( m_xObject->getCurrentState() == embed::EmbedStates::ACTIVE || m_nAspect == embed::Aspects::MSOLE_ICON ) + return false; + + return true; +} + + +void SAL_CALL SfxInPlaceClient_Impl::activatingInplace() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + if ( !comphelper::LibreOfficeKit::isActive() ) + return; + + if ( SfxViewShell* pViewShell = m_pClient->GetViewShell() ) + { + tools::Rectangle aRect(m_pClient->GetObjArea()); + + if (m_pClient->GetEditWin()) + { + if (m_pClient->GetEditWin()->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + aRect = o3tl::convert(aRect, o3tl::Length::mm100, o3tl::Length::twip); + } + + OString str = (m_bNegativeX ? lcl_negateRectX(aRect) : aRect).toString() + ", \"INPLACE\""; + pViewShell->libreOfficeKitViewCallback( LOK_CALLBACK_GRAPHIC_SELECTION, str ); + } + +} + + +void SAL_CALL SfxInPlaceClient_Impl::activatingUI() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + m_pClient->GetViewShell()->ResetAllClients_Impl(m_pClient); + m_bUIActive = true; + m_pClient->GetViewShell()->UIActivating( m_pClient ); +} + + +void SAL_CALL SfxInPlaceClient_Impl::deactivatedInplace() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + if ( comphelper::LibreOfficeKit::isActive() ) + { + if ( SfxViewShell* pViewShell = m_pClient->GetViewShell() ) { + pViewShell->libreOfficeKitViewCallback( LOK_CALLBACK_GRAPHIC_SELECTION, "INPLACE EXIT"_ostr ); + } + } +} + + +void SAL_CALL SfxInPlaceClient_Impl::deactivatedUI() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + m_pClient->GetViewShell()->UIDeactivated( m_pClient ); + m_bUIActive = false; +} + + +uno::Reference< css::frame::XLayoutManager > SAL_CALL SfxInPlaceClient_Impl::getLayoutManager() +{ + uno::Reference < beans::XPropertySet > xFrame( GetFrame(), uno::UNO_QUERY_THROW ); + + uno::Reference< css::frame::XLayoutManager > xMan; + try + { + uno::Any aAny = xFrame->getPropertyValue( "LayoutManager" ); + aAny >>= xMan; + } + catch ( uno::Exception& ex ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( ex.Message, + nullptr, anyEx ); + } + + return xMan; +} + + +uno::Reference< frame::XDispatchProvider > SAL_CALL SfxInPlaceClient_Impl::getInplaceDispatchProvider() +{ + return uno::Reference < frame::XDispatchProvider >( GetFrame(), uno::UNO_QUERY_THROW ); +} + + +awt::Rectangle SAL_CALL SfxInPlaceClient_Impl::getPlacement() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + // apply scaling to object area and convert to pixels + tools::Rectangle aRealObjArea( m_aObjArea ); + aRealObjArea.SetSize( Size( tools::Long( aRealObjArea.GetWidth() * m_aScaleWidth), + tools::Long( aRealObjArea.GetHeight() * m_aScaleHeight) ) ); + + vcl::Window* pEditWin = m_pClient->GetEditWin(); + // In Writer and Impress the map mode is disabled. So when a chart is + // activated (for in place editing) we get the chart win size in 100th mm + // and any method that should return pixels returns 100th mm and the chart + // window map mode has a ~26.485 scale factor. + // All that does not fit with current implementation for handling chart + // editing in LOK. + if (comphelper::LibreOfficeKit::isActive()) + { + bool bMapModeEnabled = pEditWin->IsMapModeEnabled(); + if (!bMapModeEnabled) + pEditWin->EnableMapMode(); + aRealObjArea = pEditWin->LogicToPixel(aRealObjArea); + if (!bMapModeEnabled && pEditWin->IsMapModeEnabled()) + pEditWin->EnableMapMode(false); + } + else + { + aRealObjArea = pEditWin->LogicToPixel(aRealObjArea); + } + + return AWTRectangle( aRealObjArea ); +} + + +awt::Rectangle SAL_CALL SfxInPlaceClient_Impl::getClipRectangle() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + // currently(?) same as placement + tools::Rectangle aRealObjArea( m_aObjArea ); + aRealObjArea.SetSize( Size( tools::Long( aRealObjArea.GetWidth() * m_aScaleWidth), + tools::Long( aRealObjArea.GetHeight() * m_aScaleHeight) ) ); + + vcl::Window* pEditWin = m_pClient->GetEditWin(); + // See comment for SfxInPlaceClient_Impl::getPlacement. + if (comphelper::LibreOfficeKit::isActive()) + { + bool bMapModeEnabled = pEditWin->IsMapModeEnabled(); + if (!bMapModeEnabled) + pEditWin->EnableMapMode(); + aRealObjArea = pEditWin->LogicToPixel(aRealObjArea); + if (!bMapModeEnabled && pEditWin->IsMapModeEnabled()) + pEditWin->EnableMapMode(false); + } + else + { + aRealObjArea = pEditWin->LogicToPixel(aRealObjArea); + } + + return AWTRectangle( aRealObjArea ); +} + + +void SAL_CALL SfxInPlaceClient_Impl::translateAccelerators( const uno::Sequence< awt::KeyEvent >& /*aKeys*/ ) +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + // TODO/MBA: keyboard accelerators +} + + +void SAL_CALL SfxInPlaceClient_Impl::scrollObject( const awt::Size& /*aOffset*/ ) +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); +} + + +void SAL_CALL SfxInPlaceClient_Impl::changedPlacement( const awt::Rectangle& aPosRect ) +{ + uno::Reference< embed::XInplaceObject > xInplace( m_xObject, uno::UNO_QUERY_THROW ); + if ( !m_pClient || !m_pClient->GetEditWin() || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + // check if the change is at least one pixel in size + awt::Rectangle aOldRect = getPlacement(); + tools::Rectangle aNewPixelRect = VCLRectangle( aPosRect ); + tools::Rectangle aOldPixelRect = VCLRectangle( aOldRect ); + if ( aOldPixelRect == aNewPixelRect ) + // nothing has changed + return; + + // new scaled object area + tools::Rectangle aNewLogicRect = m_pClient->GetEditWin()->PixelToLogic( aNewPixelRect ); + + // all the size changes in this method should happen without scaling + // SfxBooleanFlagGuard aGuard( m_bResizeNoScale, sal_True ); + + // allow container to apply restrictions on the requested new area; + // the container might change the object view during size calculation; + // currently only writer does it + m_pClient->RequestNewObjectArea( aNewLogicRect); + + if ( aNewLogicRect != m_pClient->GetScaledObjArea() ) + { + // the calculation of the object area has not changed the object size + // it should be done here then + SfxBooleanFlagGuard aGuard( m_bResizeNoScale ); + + // new size of the object area without scaling + Size aNewObjSize( tools::Long( aNewLogicRect.GetWidth() / m_aScaleWidth ), + tools::Long( aNewLogicRect.GetHeight() / m_aScaleHeight ) ); + + // now remove scaling from new placement and keep this at the new object area + aNewLogicRect.SetSize( aNewObjSize ); + m_aObjArea = aNewLogicRect; + + // let the window size be recalculated + SizeHasChanged(); + } + + // notify container view about changes + m_pClient->ObjectAreaChanged(); +} + +// XComponentSupplier + +uno::Reference< util::XCloseable > SAL_CALL SfxInPlaceClient_Impl::getComponent() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + SfxObjectShell* pDocShell = m_pClient->GetViewShell()->GetObjectShell(); + if ( !pDocShell ) + throw uno::RuntimeException(); + + // all the components must implement XCloseable + uno::Reference< util::XCloseable > xComp( pDocShell->GetModel(), uno::UNO_QUERY_THROW ); + return xComp; +} + + +// XWindowSupplier + +uno::Reference< awt::XWindow > SAL_CALL SfxInPlaceClient_Impl::getWindow() +{ + if ( !m_pClient || !m_pClient->GetEditWin() ) + throw uno::RuntimeException(); + + uno::Reference< awt::XWindow > xWin( m_pClient->GetEditWin()->GetComponentInterface(), uno::UNO_QUERY ); + return xWin; +} + + +// notification to the client implementation that either the object area or the scaling has been changed +// as a result the logical size of the window has changed also +void SfxInPlaceClient_Impl::SizeHasChanged() +{ + if ( !m_pClient || !m_pClient->GetViewShell() ) + throw uno::RuntimeException(); + + try { + if ( m_xObject.is() + && ( m_xObject->getCurrentState() == embed::EmbedStates::INPLACE_ACTIVE + || m_xObject->getCurrentState() == embed::EmbedStates::UI_ACTIVE ) ) + { + // only possible in active states + uno::Reference< embed::XInplaceObject > xInplace( m_xObject, uno::UNO_QUERY_THROW ); + + if ( m_bResizeNoScale ) + { + // the resizing should be done without scaling + // set the correct size to the object to avoid the scaling + MapMode aObjectMap( VCLUnoHelper::UnoEmbed2VCLMapUnit( m_xObject->getMapUnit( m_nAspect ) ) ); + MapMode aClientMap( m_pClient->GetEditWin()->GetMapMode().GetMapUnit() ); + + // convert to logical coordinates of the embedded object + Size aNewSize = m_pClient->GetEditWin()->LogicToLogic( m_aObjArea.GetSize(), &aClientMap, &aObjectMap ); + m_xObject->setVisualAreaSize( m_nAspect, awt::Size( aNewSize.Width(), aNewSize.Height() ) ); + } + + xInplace->setObjectRectangles( getPlacement(), getClipRectangle() ); + } + } + catch( uno::Exception& ) + { + // TODO/LATER: handle error + } +} + + +IMPL_LINK_NOARG(SfxInPlaceClient_Impl, TimerHdl, Timer *, void) +{ + if ( m_pClient && m_xObject.is() ) + { + m_pClient->GetViewShell()->CheckIPClient_Impl(m_pClient, + m_pClient->GetViewShell()->GetObjectShell()->GetVisArea()); + } +} + + +// SfxInPlaceClient + + +SfxInPlaceClient::SfxInPlaceClient( SfxViewShell* pViewShell, vcl::Window *pDraw, sal_Int64 nAspect ) : + m_xImp( new SfxInPlaceClient_Impl ), + m_pViewSh( pViewShell ), + m_pEditWin( pDraw ) +{ + m_xImp->m_pClient = this; + m_xImp->m_nAspect = nAspect; + m_xImp->m_aScaleWidth = m_xImp->m_aScaleHeight = Fraction(1,1); + pViewShell->NewIPClient_Impl(this); + m_xImp->m_aTimer.SetTimeout( SFX_CLIENTACTIVATE_TIMEOUT ); + m_xImp->m_aTimer.SetInvokeHandler( LINK( m_xImp.get(), SfxInPlaceClient_Impl, TimerHdl ) ); +} + + +SfxInPlaceClient::~SfxInPlaceClient() +{ + m_pViewSh->IPClientGone_Impl(this); + + // deleting the client before storing the object means discarding all changes + m_xImp->m_bStoreObject = false; + SetObject(nullptr); + + m_xImp->m_pClient = nullptr; + + // the next call will destroy m_xImp if no other reference to it exists + m_xImp.clear(); + + // TODO/LATER: + // the class is not intended to be used in multithreaded environment; + // if it will this disconnection and all the parts that use the m_pClient + // must be guarded with mutex +} + + +void SfxInPlaceClient::SetObjectState( sal_Int32 nState ) +{ + if ( !GetObject().is() ) + return; + + if ( m_xImp->m_nAspect == embed::Aspects::MSOLE_ICON + && ( nState == embed::EmbedStates::UI_ACTIVE || nState == embed::EmbedStates::INPLACE_ACTIVE ) ) + { + OSL_FAIL( "Iconified object should not be activated inplace!" ); + return; + } + + try + { + GetObject()->changeState( nState ); + } + catch ( uno::Exception& ) + {} +} + + +sal_Int64 SfxInPlaceClient::GetObjectMiscStatus() const +{ + if ( GetObject().is() ) + return GetObject()->getStatus( m_xImp->m_nAspect ); + return 0; +} + + +const uno::Reference < embed::XEmbeddedObject >& SfxInPlaceClient::GetObject() const +{ + return m_xImp->m_xObject; +} + + +void SfxInPlaceClient::SetObject( const uno::Reference < embed::XEmbeddedObject >& rObject ) +{ + if ( m_xImp->m_xObject.is() && rObject != m_xImp->m_xObject ) + { + DBG_ASSERT( GetObject()->getClientSite() == getXWeak(m_xImp.get()), "Wrong ClientSite!" ); + if ( GetObject()->getClientSite() == getXWeak(m_xImp.get()) ) + { + if ( GetObject()->getCurrentState() != embed::EmbedStates::LOADED ) + SetObjectState( embed::EmbedStates::RUNNING ); + m_xImp->m_xObject->removeEventListener( m_xImp ); + m_xImp->m_xObject->removeStateChangeListener( m_xImp ); + try + { + m_xImp->m_xObject->setClientSite( nullptr ); + } + catch( uno::Exception& ) + { + OSL_FAIL( "Can not clean the client site!" ); + } + } + } + + if ( m_pViewSh->GetViewFrame().GetFrame().IsClosing_Impl() ) + // sometimes applications reconnect clients on shutting down because it happens in their Paint methods + return; + + m_xImp->m_xObject = rObject; + + if ( rObject.is() ) + { + // as soon as an object was connected to a client it has to be checked whether the object wants + // to be activated + rObject->addStateChangeListener( m_xImp ); + rObject->addEventListener( m_xImp ); + + try + { + rObject->setClientSite( m_xImp ); + } + catch( uno::Exception& ) + { + OSL_FAIL( "Can not set the client site!" ); + } + + m_xImp->m_aTimer.Start(); + } + else + m_xImp->m_aTimer.Stop(); +} + + +bool SfxInPlaceClient::SetObjArea( const tools::Rectangle& rArea ) +{ + if( rArea != m_xImp->m_aObjArea ) + { + m_xImp->m_aObjArea = rArea; + m_xImp->SizeHasChanged(); + + Invalidate(); + return true; + } + + return false; +} + + +const tools::Rectangle& SfxInPlaceClient::GetObjArea() const +{ + return m_xImp->m_aObjArea; +} + +tools::Rectangle SfxInPlaceClient::GetScaledObjArea() const +{ + tools::Rectangle aRealObjArea( m_xImp->m_aObjArea ); + aRealObjArea.SetSize( Size( tools::Long( aRealObjArea.GetWidth() * m_xImp->m_aScaleWidth ), + tools::Long( aRealObjArea.GetHeight() * m_xImp->m_aScaleHeight ) ) ); + return aRealObjArea; +} + + +void SfxInPlaceClient::SetSizeScale( const Fraction & rScaleWidth, const Fraction & rScaleHeight ) +{ + if ( m_xImp->m_aScaleWidth != rScaleWidth || m_xImp->m_aScaleHeight != rScaleHeight ) + { + m_xImp->m_aScaleWidth = rScaleWidth; + m_xImp->m_aScaleHeight = rScaleHeight; + + m_xImp->SizeHasChanged(); + + // TODO/LATER: Invalidate seems to trigger (wrong) recalculations of the ObjArea, so it's better + // not to call it here, but maybe it sounds reasonable to do so. + //Invalidate(); + } +} + + +void SfxInPlaceClient::SetObjAreaAndScale( const tools::Rectangle& rArea, const Fraction& rScaleWidth, const Fraction& rScaleHeight ) +{ + if( rArea != m_xImp->m_aObjArea || m_xImp->m_aScaleWidth != rScaleWidth || m_xImp->m_aScaleHeight != rScaleHeight ) + { + m_xImp->m_aObjArea = rArea; + m_xImp->m_aScaleWidth = rScaleWidth; + m_xImp->m_aScaleHeight = rScaleHeight; + + m_xImp->SizeHasChanged(); + + Invalidate(); + } +} + + +const Fraction& SfxInPlaceClient::GetScaleWidth() const +{ + return m_xImp->m_aScaleWidth; +} + + +const Fraction& SfxInPlaceClient::GetScaleHeight() const +{ + return m_xImp->m_aScaleHeight; +} + + +void SfxInPlaceClient::Invalidate() +{ + // TODO/LATER: do we need both? + + // the object area is provided in logical coordinates of the window but without scaling applied + tools::Rectangle aRealObjArea( m_xImp->m_aObjArea ); + aRealObjArea.SetSize( Size( tools::Long( aRealObjArea.GetWidth() * m_xImp->m_aScaleWidth ), + tools::Long( aRealObjArea.GetHeight() * m_xImp->m_aScaleHeight ) ) ); + + m_pEditWin->Invalidate( IsNegativeX() ? lcl_negateRectX(aRealObjArea) : aRealObjArea ); + + ViewChanged(); +} + + +bool SfxInPlaceClient::IsObjectUIActive() const +{ + try { + return ( m_xImp->m_xObject.is() && ( m_xImp->m_xObject->getCurrentState() == embed::EmbedStates::UI_ACTIVE ) ); + } + catch( uno::Exception& ) + {} + + return false; +} + + +bool SfxInPlaceClient::IsObjectInPlaceActive() const +{ + try { + return( + ( + m_xImp->m_xObject.is() && + (m_xImp->m_xObject->getCurrentState() == embed::EmbedStates::INPLACE_ACTIVE) + ) || + ( + m_xImp->m_xObject.is() && + (m_xImp->m_xObject->getCurrentState() == embed::EmbedStates::UI_ACTIVE) + ) + ); + } + catch( uno::Exception& ) + {} + + return false; +} + + +SfxInPlaceClient* SfxInPlaceClient::GetClient( SfxObjectShell const * pDoc, const css::uno::Reference < css::embed::XEmbeddedObject >& xObject ) +{ + for ( SfxViewFrame* pFrame = SfxViewFrame::GetFirst(pDoc); pFrame; pFrame=SfxViewFrame::GetNext(*pFrame,pDoc) ) + { + if( pFrame->GetViewShell() ) + { + SfxInPlaceClient* pClient = pFrame->GetViewShell()->FindIPClient( xObject, nullptr ); + if ( pClient ) + return pClient; + } + } + + return nullptr; +} + +sal_Int64 SfxInPlaceClient::GetAspect() const +{ + return m_xImp->m_nAspect; +} + +ErrCodeMsg SfxInPlaceClient::DoVerb(sal_Int32 nVerb) +{ + SfxErrorContext aEc(ERRCTX_SO_DOVERB, m_pViewSh->GetFrameWeld(), RID_SO_ERRCTX); + ErrCodeMsg nError = ERRCODE_NONE; + + if ( m_xImp->m_xObject.is() ) + { + bool bSaveCopyAs = false; + if ( nVerb == -8 ) // "Save Copy as..." + { + svt::EmbeddedObjectRef::TryRunningState( m_xImp->m_xObject ); + // TODO/LATER: this special verb should disappear when outplace activation is completely available + uno::Reference< frame::XModel > xEmbModel( m_xImp->m_xObject->getComponent(), uno::UNO_QUERY ); + if ( xEmbModel.is() ) + { + bSaveCopyAs = true; + + try + { + SfxStoringHelper aHelper; + uno::Sequence< beans::PropertyValue > aDispatchArgs{ + comphelper::makePropertyValue("SaveTo", true) + }; + + aHelper.GUIStoreModel( xEmbModel, + u"SaveAs", + aDispatchArgs, + false, + SignatureState::NOSIGNATURES, + false ); + } + catch( const task::ErrorCodeIOException& aErrorEx ) + { + nError = ErrCode(aErrorEx.ErrCode); + } + catch( uno::Exception& ) + { + nError = ERRCODE_IO_GENERAL; + // TODO/LATER: better error handling + } + } + } + + if ( !bSaveCopyAs ) + { + if ( m_xImp->m_nAspect == embed::Aspects::MSOLE_ICON ) + { + // the common persistence is supported by objects and links + + uno::Reference< embed::XEmbeddedOleObject > xEmbeddedOleObject( m_xImp->m_xObject, uno::UNO_QUERY ); + + if ( xEmbeddedOleObject.is() && (nVerb == embed::EmbedVerbs::MS_OLEVERB_PRIMARY || nVerb == embed::EmbedVerbs::MS_OLEVERB_OPEN || nVerb == embed::EmbedVerbs::MS_OLEVERB_SHOW )) + nVerb = embed::EmbedVerbs::MS_OLEVERB_SHOW; + else if ( nVerb == embed::EmbedVerbs::MS_OLEVERB_PRIMARY || nVerb == embed::EmbedVerbs::MS_OLEVERB_SHOW ) + nVerb = embed::EmbedVerbs::MS_OLEVERB_OPEN; // outplace activation + else if ( nVerb == embed::EmbedVerbs::MS_OLEVERB_UIACTIVATE + || nVerb == embed::EmbedVerbs::MS_OLEVERB_IPACTIVATE ) + nError = ERRCODE_SO_GENERALERROR; + } + + if ( !nError ) + { + // See comment for SfxInPlaceClient_Impl::getPlacement. + vcl::Window* pEditWin = GetEditWin(); + bool bMapModeEnabled = pEditWin->IsMapModeEnabled(); + if (comphelper::LibreOfficeKit::isActive() && !bMapModeEnabled) + { + pEditWin->EnableMapMode(); + } + m_pViewSh->GetViewFrame().GetFrame().LockResize_Impl(true); + try + { + m_xImp->m_xObject->setClientSite( m_xImp ); + + m_xImp->m_xObject->doVerb( nVerb ); + } + catch ( embed::UnreachableStateException& e ) + { + if (nVerb == embed::EmbedVerbs::MS_OLEVERB_PRIMARY || nVerb == embed::EmbedVerbs::MS_OLEVERB_OPEN || nVerb == embed::EmbedVerbs::MS_OLEVERB_SHOW) + { + // a workaround for the default verb, usually makes sense for alien objects + try + { + m_xImp->m_xObject->doVerb( -9 ); // open own view, a workaround verb that is not visible + + if ( m_xImp->m_xObject->getCurrentState() == embed::EmbedStates::UI_ACTIVE ) + { + // the object was converted to OOo object + awt::Size aSize = m_xImp->m_xObject->getVisualAreaSize( m_xImp->m_nAspect ); + MapMode aObjectMap( VCLUnoHelper::UnoEmbed2VCLMapUnit( m_xImp->m_xObject->getMapUnit( m_xImp->m_nAspect ) ) ); + MapMode aClientMap( GetEditWin()->GetMapMode().GetMapUnit() ); + Size aNewSize = GetEditWin()->LogicToLogic( Size( aSize.Width, aSize.Height ), &aObjectMap, &aClientMap ); + + tools::Rectangle aScaledArea = GetScaledObjArea(); + m_xImp->m_aObjArea.SetSize( aNewSize ); + m_xImp->m_aScaleWidth = Fraction( aScaledArea.GetWidth(), aNewSize.Width() ); + m_xImp->m_aScaleHeight = Fraction( aScaledArea.GetHeight(), aNewSize.Height() ); + } + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("embeddedobj", "SfxInPlaceClient::DoVerb: -9 fallback path"); + nError = ErrCodeMsg(ERRCODE_SO_GENERALERROR, e.Message); + } + } + } + catch ( embed::StateChangeInProgressException& ) + { + // TODO/LATER: it would be nice to be able to provide the current target state outside + nError = ERRCODE_SO_CANNOT_DOVERB_NOW; + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("embeddedobj", "SfxInPlaceClient::DoVerb"); + nError = ERRCODE_SO_GENERALERROR; + //TODO/LATER: better error handling + + } + if (comphelper::LibreOfficeKit::isActive() && !bMapModeEnabled + && pEditWin->IsMapModeEnabled()) + { + pEditWin->EnableMapMode(false); + } + SfxViewFrame& rFrame = m_pViewSh->GetViewFrame(); + rFrame.GetFrame().LockResize_Impl(false); + rFrame.GetFrame().Resize(); + } + } + } + + if( nError ) + ErrorHandler::HandleError( nError ); + + return nError; +} + +void SfxInPlaceClient::VisAreaChanged() +{ + uno::Reference < embed::XInplaceObject > xObj( m_xImp->m_xObject, uno::UNO_QUERY ); + if ( xObj.is() ) + m_xImp->SizeHasChanged(); +} + +void SfxInPlaceClient::ObjectAreaChanged() +{ + // dummy implementation +} + +void SfxInPlaceClient::RequestNewObjectArea( tools::Rectangle& ) +{ + // dummy implementation +} + +void SfxInPlaceClient::ViewChanged() +{ + // dummy implementation +} + +void SfxInPlaceClient::FormatChanged() +{ + // dummy implementation +} + +bool SfxInPlaceClient::IsProtected() const { return false; } + +void SfxInPlaceClient::DeactivateObject() +{ + if ( !GetObject().is() ) + return; + + try + { + m_xImp->m_bUIActive = false; + bool bHasFocus = false; + uno::Reference< frame::XModel > xModel( m_xImp->m_xObject->getComponent(), uno::UNO_QUERY ); + if ( xModel.is() ) + { + uno::Reference< frame::XController > xController = xModel->getCurrentController(); + if ( xController.is() ) + { + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xController->getFrame()->getContainerWindow() ); + bHasFocus = pWindow->HasChildPathFocus( true ); + } + } + + m_pViewSh->GetViewFrame().GetFrame().LockResize_Impl(true); + + if ( m_xImp->m_xObject->getStatus( m_xImp->m_nAspect ) & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE ) + { + m_xImp->m_xObject->changeState( embed::EmbedStates::INPLACE_ACTIVE ); + if (bHasFocus) + m_pViewSh->GetWindow()->GrabFocus(); + } + else + { + // the links should not stay in running state for long time because of locking + uno::Reference< embed::XLinkageSupport > xLink( m_xImp->m_xObject, uno::UNO_QUERY ); + if ( xLink.is() && xLink->isLink() ) + m_xImp->m_xObject->changeState( embed::EmbedStates::LOADED ); + else + m_xImp->m_xObject->changeState( embed::EmbedStates::RUNNING ); + } + + SfxViewFrame& rFrame = m_pViewSh->GetViewFrame(); + SfxViewFrame::SetViewFrame( &rFrame ); + rFrame.GetFrame().LockResize_Impl(false); + rFrame.GetFrame().Resize(); + } + catch (css::uno::Exception& ) + {} +} + +void SfxInPlaceClient::ResetObject() +{ + if ( !GetObject().is() ) + return; + + try + { + m_xImp->m_bUIActive = false; + if ( m_xImp->m_xObject->getStatus( m_xImp->m_nAspect ) & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE ) + m_xImp->m_xObject->changeState( embed::EmbedStates::INPLACE_ACTIVE ); + else + { + // the links should not stay in running state for long time because of locking + uno::Reference< embed::XLinkageSupport > xLink( m_xImp->m_xObject, uno::UNO_QUERY ); + if ( xLink.is() && xLink->isLink() ) + m_xImp->m_xObject->changeState( embed::EmbedStates::LOADED ); + else + m_xImp->m_xObject->changeState( embed::EmbedStates::RUNNING ); + } + } + catch (css::uno::Exception& ) + {} +} + +bool SfxInPlaceClient::IsUIActive() const +{ + return m_xImp->m_bUIActive; +} + +void SfxInPlaceClient::SetNegativeX(bool bSet) +{ + m_xImp->m_bNegativeX = bSet; +} + +bool SfxInPlaceClient::IsNegativeX() const +{ + return m_xImp->m_bNegativeX; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/lokcharthelper.cxx b/sfx2/source/view/lokcharthelper.cxx new file mode 100644 index 0000000000..f8e8ec47ea --- /dev/null +++ b/sfx2/source/view/lokcharthelper.cxx @@ -0,0 +1,369 @@ +/* -*- 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 <sfx2/lokcomponenthelpers.hxx> + +#include <comphelper/lok.hxx> +#include <comphelper/propertyvalue.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <sfx2/ipclient.hxx> +#include <sfx2/lokhelper.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/fract.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> + +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> + +using namespace com::sun::star; + +css::uno::Reference<css::frame::XController>& LokChartHelper::GetXController() +{ + if(!mxController.is() && mpViewShell) + { + SfxInPlaceClient* pIPClient = mpViewShell->GetIPClient(); + if (pIPClient) + { + const css::uno::Reference< ::css::embed::XEmbeddedObject >& xEmbObj = pIPClient->GetObject(); + if( xEmbObj.is() ) + { + ::css::uno::Reference< ::css::chart2::XChartDocument > xChart( xEmbObj->getComponent(), uno::UNO_QUERY ); + if( xChart.is() ) + { + ::css::uno::Reference< ::css::frame::XController > xChartController = xChart->getCurrentController(); + if( xChartController.is() ) + { + mxController = xChartController; + } + } + } + } + } + + return mxController; +} + +css::uno::Reference<css::frame::XDispatch>& LokChartHelper::GetXDispatcher() +{ + if( !mxDispatcher.is() ) + { + ::css::uno::Reference< ::css::frame::XController >& xChartController = GetXController(); + if( xChartController.is() ) + { + ::css::uno::Reference< ::css::frame::XDispatch > xDispatcher( xChartController, uno::UNO_QUERY ); + if( xDispatcher.is() ) + { + mxDispatcher = xDispatcher; + } + } + } + + return mxDispatcher; +} + +vcl::Window* LokChartHelper::GetWindow() +{ + if (!mpWindow) + { + ::css::uno::Reference< ::css::frame::XController >& xChartController = GetXController(); + if( xChartController.is() ) + { + ::css::uno::Reference< ::css::frame::XFrame > xFrame = xChartController->getFrame(); + if (xFrame.is()) + { + ::css::uno::Reference< ::css::awt::XWindow > xDockerWin = xFrame->getContainerWindow(); + vcl::Window* pParent = VCLUnoHelper::GetWindow( xDockerWin ); + if (pParent) + { + sal_uInt16 nTotChildren = pParent->GetChildCount(); + while (nTotChildren--) + { + vcl::Window* pChildWin = pParent->GetChild(nTotChildren); + if (pChildWin && pChildWin->IsChart()) + { + mpWindow = pChildWin; + break; + } + } + } + } + } + } + + return mpWindow.get(); +} + +tools::Rectangle LokChartHelper::GetChartBoundingBox() +{ + tools::Rectangle aBBox; + if (mpViewShell) + { + SfxInPlaceClient* pIPClient = mpViewShell->GetIPClient(); + if (pIPClient) + { + vcl::Window* pRootWin = pIPClient->GetEditWin(); + if (pRootWin) + { + vcl::Window* pWindow = GetWindow(); + if (pWindow) + { + // In all cases, the following code fragment + // returns the chart bounding box in twips. + const MapMode& aCWMapMode = pWindow->GetMapMode(); + constexpr auto p = o3tl::getConversionMulDiv(o3tl::Length::px, o3tl::Length::twip); + const auto& scaleX = aCWMapMode.GetScaleX(); + const auto& scaleY = aCWMapMode.GetScaleY(); + const auto nXNum = p.first * scaleX.GetDenominator(); + const auto nXDen = p.second * scaleX.GetNumerator(); + const auto nYNum = p.first * scaleY.GetDenominator(); + const auto nYDen = p.second * scaleY.GetNumerator(); + + Point aOffset = pWindow->GetOffsetPixelFrom(*pRootWin); + if (mbNegativeX && AllSettings::GetLayoutRTL()) + { + // If global RTL flag is set, vcl-window X offset of chart window is + // mirrored w.r.t parent window rectangle. This needs to be reverted. + aOffset.setX(pRootWin->GetOutOffXPixel() + pRootWin->GetSizePixel().Width() + - pWindow->GetOutOffXPixel() - pWindow->GetSizePixel().Width()); + + } + + aOffset = aOffset.scale(nXNum, nXDen, nYNum, nYDen); + Size aSize = pWindow->GetSizePixel().scale(nXNum, nXDen, nYNum, nYDen); + aBBox = tools::Rectangle(aOffset, aSize); + } + } + } + } + return aBBox; +} + +void LokChartHelper::Invalidate() +{ + mpWindow = nullptr; + mxDispatcher.clear(); + mxController.clear(); +} + +bool LokChartHelper::Hit(const Point& aPos) +{ + if (mpViewShell) + { + vcl::Window* pChartWindow = GetWindow(); + if (pChartWindow) + { + tools::Rectangle rChartBBox = GetChartBoundingBox(); + return rChartBBox.Contains(aPos); + } + } + return false; +} + +bool LokChartHelper::HitAny(const Point& aPos, bool bNegativeX) +{ + SfxViewShell* pCurView = SfxViewShell::Current(); + int nPartForCurView = pCurView ? pCurView->getPart() : -1; + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pCurView && pViewShell->GetDocId() == pCurView->GetDocId() && pViewShell->getPart() == nPartForCurView) + { + LokChartHelper aChartHelper(pViewShell, bNegativeX); + if (aChartHelper.Hit(aPos)) + return true; + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + return false; +} + +void LokChartHelper::PaintTile(VirtualDevice& rRenderContext, const tools::Rectangle& rTileRect) +{ + if (!mpViewShell) + return; + + vcl::Window* pChartWindow = GetWindow(); + if (!pChartWindow) + return; + + tools::Rectangle aChartRect = GetChartBoundingBox(); + tools::Rectangle aTestRect = rTileRect; + aTestRect.Intersection( aChartRect ); + if (aTestRect.IsEmpty()) + return; + + Point aOffset( aChartRect.Left() - rTileRect.Left(), aChartRect.Top() - rTileRect.Top() ); + Point aOffsetFromTile = convertTwipToMm100(aOffset); + Size aSize = convertTwipToMm100(aChartRect.GetSize()); + tools::Rectangle aRectangle(Point(0,0), aSize); + + bool bEnableMapMode = !pChartWindow->IsMapModeEnabled(); + pChartWindow->EnableMapMode(); + bool bRenderContextEnableMapMode = !rRenderContext.IsMapModeEnabled(); + rRenderContext.EnableMapMode(); + + rRenderContext.Push(vcl::PushFlags::MAPMODE); + + MapMode aCWMapMode = pChartWindow->GetMapMode(); + aCWMapMode.SetScaleX(rRenderContext.GetMapMode().GetScaleX()); + aCWMapMode.SetScaleY(rRenderContext.GetMapMode().GetScaleY()); + + aCWMapMode.SetOrigin(aOffsetFromTile); + rRenderContext.SetMapMode(aCWMapMode); + + pChartWindow->Paint(rRenderContext, aRectangle); + + rRenderContext.Pop(); + + if (bRenderContextEnableMapMode) + rRenderContext.EnableMapMode(false); + if (bEnableMapMode) + pChartWindow->EnableMapMode(false); +} + +void LokChartHelper::PaintAllChartsOnTile(VirtualDevice& rDevice, + int nOutputWidth, int nOutputHeight, + int nTilePosX, int nTilePosY, + tools::Long nTileWidth, tools::Long nTileHeight, + bool bNegativeX) +{ + if (comphelper::LibreOfficeKit::isTiledAnnotations()) + return; + + // Resizes the virtual device so to contain the entries context + rDevice.SetOutputSizePixel(Size(nOutputWidth, nOutputHeight)); + + rDevice.Push(vcl::PushFlags::MAPMODE); + MapMode aMapMode(rDevice.GetMapMode()); + + // Scaling. Must convert from pixels to twips. We know + // that VirtualDevices use a DPI of 96. + const Fraction scale = conversionFract(o3tl::Length::px, o3tl::Length::twip); + Fraction scaleX = Fraction(nOutputWidth, nTileWidth) * scale; + Fraction scaleY = Fraction(nOutputHeight, nTileHeight) * scale; + aMapMode.SetScaleX(scaleX); + aMapMode.SetScaleY(scaleY); + rDevice.SetMapMode(aMapMode); + + SfxViewShell* pCurView = SfxViewShell::Current(); + int nPartForCurView = pCurView ? pCurView->getPart() : -1; + tools::Long nTileRectLeft = bNegativeX ? -nTilePosX - nTileWidth : nTilePosX; + tools::Rectangle aTileRect(Point(nTileRectLeft, nTilePosY), Size(nTileWidth, nTileHeight)); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pCurView && pViewShell->GetDocId() == pCurView->GetDocId() && pViewShell->getPart() == nPartForCurView) + { + LokChartHelper aChartHelper(pViewShell, bNegativeX); + aChartHelper.PaintTile(rDevice, aTileRect); + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + rDevice.Pop(); +} + +bool LokChartHelper::postMouseEvent(int nType, int nX, int nY, + int nCount, int nButtons, int nModifier, + double fScaleX, double fScaleY) +{ + Point aMousePos(nX, nY); + vcl::Window* pChartWindow = GetWindow(); + if (pChartWindow) + { + tools::Rectangle rChartBBox = GetChartBoundingBox(); + if (rChartBBox.Contains(aMousePos)) + { + int nChartWinX = nX - rChartBBox.Left(); + int nChartWinY = nY - rChartBBox.Top(); + + // chart window expects pixels, but the conversion factor + // can depend on the client zoom + Point aPos(nChartWinX * fScaleX, nChartWinY * fScaleY); + + LokMouseEventData aMouseEventData(nType, aPos, nCount, MouseEventModifiers::SIMPLECLICK, + nButtons, nModifier); + SfxLokHelper::postMouseEventAsync(pChartWindow, aMouseEventData); + + return true; + } + } + return false; +} + +bool LokChartHelper::setTextSelection(int nType, int nX, int nY) +{ + tools::Rectangle rChartBBox = GetChartBoundingBox(); + if (rChartBBox.Contains(Point(nX, nY))) + { + css::uno::Reference<css::frame::XDispatch> xDispatcher = GetXDispatcher(); + if (xDispatcher.is()) + { + int nChartWinX = nX - rChartBBox.Left(); + int nChartWinY = nY - rChartBBox.Top(); + + // no scale here the chart controller expects twips + // that are converted to hmm + util::URL aURL; + aURL.Path = "LOKSetTextSelection"; + uno::Sequence< beans::PropertyValue > aArgs{ + comphelper::makePropertyValue({}, static_cast<sal_Int32>(nType)), // Why no name? + comphelper::makePropertyValue({}, static_cast<sal_Int32>(nChartWinX)), + comphelper::makePropertyValue({}, static_cast<sal_Int32>(nChartWinY)) + }; + xDispatcher->dispatch(aURL, aArgs); + } + return true; + } + return false; +} + +bool LokChartHelper::setGraphicSelection(int nType, int nX, int nY, + double fScaleX, double fScaleY) +{ + tools::Rectangle rChartBBox = GetChartBoundingBox(); + if (rChartBBox.Contains(Point(nX, nY))) + { + int nChartWinX = nX - rChartBBox.Left(); + int nChartWinY = nY - rChartBBox.Top(); + + vcl::Window* pChartWindow = GetWindow(); + + Point aPos(nChartWinX * fScaleX, nChartWinY * fScaleY); + switch (nType) + { + case LOK_SETGRAPHICSELECTION_START: + { + MouseEvent aClickEvent(aPos, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + pChartWindow->MouseButtonDown(aClickEvent); + MouseEvent aMoveEvent(aPos, 0, MouseEventModifiers::SIMPLEMOVE, MOUSE_LEFT); + pChartWindow->MouseMove(aMoveEvent); + } + break; + case LOK_SETGRAPHICSELECTION_END: + { + MouseEvent aMoveEvent(aPos, 0, MouseEventModifiers::SIMPLEMOVE, MOUSE_LEFT); + pChartWindow->MouseMove(aMoveEvent); + MouseEvent aClickEvent(aPos, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + pChartWindow->MouseButtonUp(aClickEvent); + } + break; + default: + assert(false); + break; + } + return true; + } + return false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/lokhelper.cxx b/sfx2/source/view/lokhelper.cxx new file mode 100644 index 0000000000..6ef68f2e42 --- /dev/null +++ b/sfx2/source/view/lokhelper.cxx @@ -0,0 +1,1066 @@ +/* -*- 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 <sal/config.h> + +#include <string> +#include <string_view> +#include <list> + +#include <sfx2/lokcomponenthelpers.hxx> +#include <sfx2/lokhelper.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/ui/ContextChangeEventObject.hpp> + +#include <comphelper/processfactory.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/strbuf.hxx> +#include <vcl/lok.hxx> +#include <vcl/svapp.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/window.hxx> +#include <sal/log.hxx> +#include <sfx2/app.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <sfx2/msgpool.hxx> + +#include <boost/property_tree/json_parser.hpp> + +using namespace com::sun::star; + +namespace { +/// Used to disable callbacks. +/// Needed to avoid recursion when switching views, +/// which can cause clients to invoke LOKit API and +/// implicitly set the view, which might cause an +/// infinite recursion if not detected and prevented. +class DisableCallbacks +{ +public: + DisableCallbacks() + { + assert(m_nDisabled >= 0 && "Expected non-negative DisabledCallbacks state when disabling."); + ++m_nDisabled; + } + + ~DisableCallbacks() + { + assert(m_nDisabled > 0 && "Expected positive DisabledCallbacks state when re-enabling."); + --m_nDisabled; + } + + static inline bool disabled() + { + return !comphelper::LibreOfficeKit::isActive() || m_nDisabled != 0; + } + +private: + static int m_nDisabled; +}; + +int DisableCallbacks::m_nDisabled = 0; +} + +namespace +{ +LanguageTag g_defaultLanguageTag("en-US", true); +LanguageTag g_loadLanguageTag("en-US", true); //< The language used to load. +LOKDeviceFormFactor g_deviceFormFactor = LOKDeviceFormFactor::UNKNOWN; +bool g_isDefaultTimezoneSet = false; +OUString g_DefaultTimezone; +const std::size_t g_logNotifierCacheMaxSize = 50; +::std::list<::std::string> g_logNotifierCache; +} + +int SfxLokHelper::createView(SfxViewFrame& rViewFrame, ViewShellDocId docId) +{ + assert(docId >= ViewShellDocId(0) && "Cannot createView for invalid (negative) DocId."); + + SfxViewShell::SetCurrentDocId(docId); + SfxRequest aRequest(rViewFrame, SID_NEWWINDOW); + rViewFrame.ExecView_Impl(aRequest); + SfxViewShell* pViewShell = SfxViewShell::Current(); + if (pViewShell == nullptr) + return -1; + + assert(pViewShell->GetDocId() == docId && "DocId must be already set!"); + return static_cast<sal_Int32>(pViewShell->GetViewShellId()); +} + +int SfxLokHelper::createView() +{ + // Assumes a single document, or at least that the + // current view belongs to the document on which the + // view will be created. + SfxViewShell* pViewShell = SfxViewShell::Current(); + if (pViewShell == nullptr) + return -1; + + return createView(pViewShell->GetViewFrame(), pViewShell->GetDocId()); +} + +std::unordered_map<OUString, css::uno::Reference<com::sun::star::ui::XAcceleratorConfiguration>>& SfxLokHelper::getAcceleratorConfs() +{ + return SfxApplication::GetOrCreate()->GetAcceleratorConfs_Impl(); +} + +int SfxLokHelper::createView(int nDocId) +{ + const SfxApplication* pApp = SfxApplication::Get(); + if (pApp == nullptr) + return -1; + + // Find a shell with the given DocId. + const ViewShellDocId docId(nDocId); + for (const SfxViewShell* pViewShell : pApp->GetViewShells_Impl()) + { + if (pViewShell->GetDocId() == docId) + return createView(pViewShell->GetViewFrame(), docId); + } + + // No frame with nDocId found. + return -1; +} + +void SfxLokHelper::setEditMode(int nMode, vcl::ITiledRenderable* pDoc) +{ + DisableCallbacks dc; + pDoc->setEditMode(nMode); +} + +void SfxLokHelper::destroyView(int nId) +{ + const SfxApplication* pApp = SfxApplication::Get(); + if (pApp == nullptr) + return; + + const ViewShellId nViewShellId(nId); + std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl(); + + for (SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell->GetViewShellId() == nViewShellId) + { + pViewShell->SetLOKAccessibilityState(false); + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + SfxRequest aRequest(rViewFrame, SID_CLOSEWIN); + rViewFrame.Exec_Impl(aRequest); + break; + } + } +} + +void SfxLokHelper::setView(int nId) +{ + SfxApplication* pApp = SfxApplication::Get(); + if (pApp == nullptr) + return; + + const ViewShellId nViewShellId(nId); + std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl(); + + for (const SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell->GetViewShellId() == nViewShellId) + { + DisableCallbacks dc; + + if (pViewShell == SfxViewShell::Current()) + return; + + // update the current LOK language and locale for the dialog tunneling + comphelper::LibreOfficeKit::setLanguageTag(pViewShell->GetLOKLanguageTag()); + comphelper::LibreOfficeKit::setLocale(pViewShell->GetLOKLocale()); + + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + rViewFrame.MakeActive_Impl(false); + + // Make comphelper::dispatchCommand() find the correct frame. + uno::Reference<frame::XFrame> xFrame = rViewFrame.GetFrame().GetFrameInterface(); + uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext()); + xDesktop->setActiveFrame(xFrame); + return; + } + } + +} + +SfxViewShell* SfxLokHelper::getViewOfId(int nId) +{ + SfxApplication* pApp = SfxApplication::Get(); + if (pApp == nullptr) + return nullptr; + + const ViewShellId nViewShellId(nId); + std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl(); + for (SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell->GetViewShellId() == nViewShellId) + return pViewShell; + } + + return nullptr; +} + +int SfxLokHelper::getView(const SfxViewShell* pViewShell) +{ + if (!pViewShell) + pViewShell = SfxViewShell::Current(); + // Still no valid view shell? Then no idea. + if (!pViewShell) + return -1; + + return static_cast<sal_Int32>(pViewShell->GetViewShellId()); +} + +std::size_t SfxLokHelper::getViewsCount(int nDocId) +{ + assert(nDocId != -1 && "Cannot getViewsCount for invalid DocId -1"); + + SfxApplication* pApp = SfxApplication::Get(); + if (!pApp) + return 0; + + const ViewShellDocId nCurrentDocId(nDocId); + std::size_t n = 0; + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell->GetDocId() == nCurrentDocId) + n++; + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + + return n; +} + +bool SfxLokHelper::getViewIds(int nDocId, int* pArray, size_t nSize) +{ + assert(nDocId != -1 && "Cannot getViewsIds for invalid DocId -1"); + + SfxApplication* pApp = SfxApplication::Get(); + if (!pApp) + return false; + + const ViewShellDocId nCurrentDocId(nDocId); + std::size_t n = 0; + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell->GetDocId() == nCurrentDocId) + { + if (n == nSize) + return false; + + pArray[n] = static_cast<sal_Int32>(pViewShell->GetViewShellId()); + n++; + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + + return true; +} + +int SfxLokHelper::getDocumentIdOfView(int nViewId) +{ + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell->GetViewShellId() == ViewShellId(nViewId)) + return static_cast<int>(pViewShell->GetDocId()); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + return -1; +} + +const LanguageTag & SfxLokHelper::getDefaultLanguage() +{ + return g_defaultLanguageTag; +} + +void SfxLokHelper::setDefaultLanguage(const OUString& rBcp47LanguageTag) +{ + g_defaultLanguageTag = LanguageTag(rBcp47LanguageTag, true); +} + +const LanguageTag& SfxLokHelper::getLoadLanguage() { return g_loadLanguageTag; } + +void SfxLokHelper::setLoadLanguage(const OUString& rBcp47LanguageTag) +{ + g_loadLanguageTag = LanguageTag(rBcp47LanguageTag, true); +} + +void SfxLokHelper::setViewLanguage(int nId, const OUString& rBcp47LanguageTag) +{ + std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl(); + + for (SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell->GetViewShellId() == ViewShellId(nId)) + { + pViewShell->SetLOKLanguageTag(rBcp47LanguageTag); + return; + } + } +} + +void SfxLokHelper::setAccessibilityState(int nId, bool nEnabled) +{ + std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl(); + + for (SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId)) + { + LOK_INFO("lok.a11y", "SfxLokHelper::setAccessibilityState: view id: " << nId << ", nEnabled: " << nEnabled); + pViewShell->SetLOKAccessibilityState(nEnabled); + return; + } + } +} + +void SfxLokHelper::setViewLocale(int nId, const OUString& rBcp47LanguageTag) +{ + std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl(); + + for (SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell->GetViewShellId() == ViewShellId(nId)) + { + pViewShell->SetLOKLocale(rBcp47LanguageTag); + return; + } + } +} + +LOKDeviceFormFactor SfxLokHelper::getDeviceFormFactor() +{ + return g_deviceFormFactor; +} + +void SfxLokHelper::setDeviceFormFactor(std::u16string_view rDeviceFormFactor) +{ + if (rDeviceFormFactor == u"desktop") + g_deviceFormFactor = LOKDeviceFormFactor::DESKTOP; + else if (rDeviceFormFactor == u"tablet") + g_deviceFormFactor = LOKDeviceFormFactor::TABLET; + else if (rDeviceFormFactor == u"mobile") + g_deviceFormFactor = LOKDeviceFormFactor::MOBILE; + else + g_deviceFormFactor = LOKDeviceFormFactor::UNKNOWN; +} + +void SfxLokHelper::setDefaultTimezone(bool isSet, const OUString& rTimezone) +{ + g_isDefaultTimezoneSet = isSet; + g_DefaultTimezone = rTimezone; +} + +std::pair<bool, OUString> SfxLokHelper::getDefaultTimezone() +{ + return { g_isDefaultTimezoneSet, g_DefaultTimezone }; +} + +void SfxLokHelper::setViewTimezone(int nId, bool isSet, const OUString& rTimezone) +{ + std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl(); + + for (SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell->GetViewShellId() == ViewShellId(nId)) + { + pViewShell->SetLOKTimezone(isSet, rTimezone); + return; + } + } +} + +std::pair<bool, OUString> SfxLokHelper::getViewTimezone(int nId) +{ + std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl(); + + for (SfxViewShell* pViewShell : rViewArr) + { + if (pViewShell->GetViewShellId() == ViewShellId(nId)) + { + return pViewShell->GetLOKTimezone(); + } + } + + return {}; +} + +/* +* Used for putting a whole JSON string into a string value +* e.g { key: "{JSON}" } +*/ +static OString lcl_sanitizeJSONAsValue(const OString &rStr) +{ + if (rStr.getLength() < 1) + return rStr; + // FIXME: need an optimized 'escape' method for O[U]String. + OStringBuffer aBuf(rStr.getLength() + 8); + for (sal_Int32 i = 0; i < rStr.getLength(); ++i) + { + if (rStr[i] == '"' || rStr[i] == '\\') + aBuf.append('\\'); + + if (rStr[i] != '\n') + aBuf.append(rStr[i]); + } + return aBuf.makeStringAndClear(); +} + +static OString lcl_generateJSON(const SfxViewShell* pView, const boost::property_tree::ptree& rTree) +{ + assert(pView != nullptr && "pView must be valid"); + boost::property_tree::ptree aMessageProps = rTree; + aMessageProps.put("viewId", SfxLokHelper::getView(pView)); + aMessageProps.put("part", pView->getPart()); + aMessageProps.put("mode", pView->getEditMode()); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aMessageProps, false /* pretty */); + return OString(o3tl::trim(aStream.str())); +} + +static inline OString lcl_generateJSON(const SfxViewShell* pView, int nViewId, std::string_view rKey, + const OString& rPayload) +{ + assert(pView != nullptr && "pView must be valid"); + return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) + + "\", \"part\": \"" + OString::number(pView->getPart()) + "\", \"mode\": \"" + + OString::number(pView->getEditMode()) + "\", \"" + rKey + "\": \"" + + lcl_sanitizeJSONAsValue(rPayload) + "\" }"; +} + +static inline OString lcl_generateJSON(const SfxViewShell* pView, std::string_view rKey, + const OString& rPayload) +{ + return lcl_generateJSON(pView, SfxLokHelper::getView(pView), rKey, rPayload); +} + +void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell const* pOtherView, + int nType, std::string_view rKey, const OString& rPayload) +{ + assert(pThisView != nullptr && "pThisView must be valid"); + if (DisableCallbacks::disabled()) + return; + + const OString aPayload = lcl_generateJSON(pThisView, rKey, rPayload); + const int viewId = SfxLokHelper::getView(pThisView); + pOtherView->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId); +} + +void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell const* pOtherView, + int nType, const boost::property_tree::ptree& rTree) +{ + assert(pThisView != nullptr && "pThisView must be valid"); + if (DisableCallbacks::disabled() || !pOtherView) + return; + + const int viewId = SfxLokHelper::getView(pThisView); + pOtherView->libreOfficeKitViewCallbackWithViewId(nType, lcl_generateJSON(pThisView, rTree), viewId); +} + +void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType, std::string_view rKey, + const OString& rPayload) +{ + assert(pThisView != nullptr && "pThisView must be valid"); + if (DisableCallbacks::disabled()) + return; + + // Cache the payload so we only have to generate it once, at most. + OString aPayload; + int viewId = -1; + + const ViewShellDocId nCurrentDocId = pThisView->GetDocId(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId()) + { + // Payload is only dependent on pThisView. + if (aPayload.isEmpty()) + { + aPayload = lcl_generateJSON(pThisView, rKey, rPayload); + viewId = SfxLokHelper::getView(pThisView); + } + + pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId); + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType, + const boost::property_tree::ptree& rTree) +{ + assert(pThisView != nullptr && "pThisView must be valid"); + if (!pThisView || DisableCallbacks::disabled()) + return; + + // Cache the payload so we only have to generate it once, at most. + OString aPayload; + int viewId = -1; + + const ViewShellDocId nCurrentDocId = pThisView->GetDocId(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId()) + { + // Payload is only dependent on pThisView. + if (aPayload.isEmpty()) + { + aPayload = lcl_generateJSON(pThisView, rTree); + viewId = SfxLokHelper::getView(pThisView); + } + + pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId); + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +OString SfxLokHelper::makePayloadJSON(const SfxViewShell* pThisView, int nViewId, std::string_view rKey, const OString& rPayload) +{ + return lcl_generateJSON(pThisView, nViewId, rKey, rPayload); +} + +namespace { + OUString lcl_getNameForSlot(const SfxViewShell* pShell, sal_uInt16 nWhich) + { + if (pShell && pShell->GetFrame()) + { + const SfxSlot* pSlot = SfxSlotPool::GetSlotPool(pShell->GetFrame()).GetSlot(nWhich); + if (pSlot) + { + if (!pSlot->GetUnoName().isEmpty()) + { + return pSlot->GetCommand(); + } + } + } + + return ""; + } +} + +void SfxLokHelper::sendUnoStatus(const SfxViewShell* pShell, const SfxPoolItem* pItem) +{ + if (!pShell || !pItem || IsInvalidItem(pItem) || DisableCallbacks::disabled()) + return; + + boost::property_tree::ptree aItem = pItem->dumpAsJSON(); + + if (aItem.count("state")) + { + OUString sCommand = lcl_getNameForSlot(pShell, pItem->Which()); + if (!sCommand.isEmpty()) + aItem.put("commandName", sCommand); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aItem); + pShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, OString(aStream.str())); + } +} + +void SfxLokHelper::notifyViewRenderState(const SfxViewShell* pShell, vcl::ITiledRenderable* pDoc) +{ + pShell->libreOfficeKitViewCallback(LOK_CALLBACK_VIEW_RENDER_STATE, pDoc->getViewRenderState()); +} + +void SfxLokHelper::notifyWindow(const SfxViewShell* pThisView, + vcl::LOKWindowId nLOKWindowId, + std::u16string_view rAction, + const std::vector<vcl::LOKPayloadItem>& rPayload) +{ + assert(pThisView != nullptr && "pThisView must be valid"); + + if (nLOKWindowId == 0 || DisableCallbacks::disabled()) + return; + + OStringBuffer aPayload = + "{ \"id\": \"" + OString::number(nLOKWindowId) + "\"" + ", \"action\": \"" + OUStringToOString(rAction, RTL_TEXTENCODING_UTF8) + "\""; + + for (const auto& rItem: rPayload) + { + if (!rItem.first.isEmpty() && !rItem.second.isEmpty()) + { + auto aFirst = rItem.first.replaceAll("\""_ostr, "\\\""_ostr); + auto aSecond = rItem.second.replaceAll("\""_ostr, "\\\""_ostr); + aPayload.append(", \"" + aFirst + "\": \"" + aSecond + "\""); + } + } + aPayload.append('}'); + + const OString s = aPayload.makeStringAndClear(); + pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_WINDOW, s); +} + +void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, tools::Rectangle const* pRect) +{ + // -1 means all parts + const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? pThisView->getPart() : INT_MIN; + SfxLokHelper::notifyInvalidation(pThisView, nPart, pRect); +} + +void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, const int nInPart, tools::Rectangle const* pRect) +{ + if (DisableCallbacks::disabled()) + return; + + // -1 means all parts + const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? nInPart : INT_MIN; + const int nMode = pThisView->getEditMode(); + pThisView->libreOfficeKitViewInvalidateTilesCallback(pRect, nPart, nMode); +} + +void SfxLokHelper::notifyDocumentSizeChanged(SfxViewShell const* pThisView, const OString& rPayload, vcl::ITiledRenderable* pDoc, bool bInvalidateAll) +{ + if (!pDoc || pDoc->isDisposed() || DisableCallbacks::disabled()) + return; + + if (bInvalidateAll) + { + for (int i = 0; i < pDoc->getParts(); ++i) + { + tools::Rectangle aRectangle(0, 0, 1000000000, 1000000000); + const int nMode = pThisView->getEditMode(); + pThisView->libreOfficeKitViewInvalidateTilesCallback(&aRectangle, i, nMode); + } + } + pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_DOCUMENT_SIZE_CHANGED, rPayload); +} + +void SfxLokHelper::notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc, bool bInvalidateAll) +{ + if (DisableCallbacks::disabled()) + return; + + // FIXME: Do we know whether it is the views for the document that is in the "current" view that has changed? + const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + // FIXME: What if SfxViewShell::Current() returned null? + // Should we then do this for all views of all open documents + // or not? + if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId()) + { + SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, bInvalidateAll); + bInvalidateAll = false; // we direct invalidations to all views anyway. + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void SfxLokHelper::notifyPartSizeChangedAllViews(vcl::ITiledRenderable* pDoc, int nPart) +{ + if (DisableCallbacks::disabled()) + return; + + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell->getPart() == nPart) + SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, false); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +OString SfxLokHelper::makeVisCursorInvalidation(int nViewId, const OString& rRectangle, + bool bMispelledWord, const OString& rHyperlink) +{ + if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + { + OString sHyperlink = rHyperlink.isEmpty() ? "{}"_ostr : rHyperlink; + return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) + + "\", \"rectangle\": \"" + rRectangle + + "\", \"mispelledWord\": \"" + OString::number(bMispelledWord ? 1 : 0) + + "\", \"hyperlink\": " + sHyperlink + " }"; + } + else + { + return rRectangle; + } +} + +void SfxLokHelper::notifyAllViews(int nType, const OString& rPayload) +{ + if (DisableCallbacks::disabled()) + return; + + const auto payload = rPayload.getStr(); + const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current(); + if (!pCurrentViewShell) + return; + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell->GetDocId() == pCurrentViewShell->GetDocId()) + pViewShell->libreOfficeKitViewCallback(nType, payload); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void SfxLokHelper::notifyContextChange(const css::ui::ContextChangeEventObject& rEvent) +{ + if (DisableCallbacks::disabled()) + return; + + SfxViewShell* pViewShell = SfxViewShell::Get({ rEvent.Source, css::uno::UNO_QUERY }); + if (!pViewShell) + return; + + OUString aBuffer = + rEvent.ApplicationName.replace(' ', '_') + + " " + + rEvent.ContextName.replace(' ', '_'); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_CHANGED, aBuffer.toUtf8()); +} + +void SfxLokHelper::notifyLog(const std::ostringstream& stream) +{ + if (DisableCallbacks::disabled()) + return; + + SfxViewShell* pViewShell = SfxViewShell::Current(); + if (!pViewShell) + return; + if (pViewShell->getLibreOfficeKitViewCallback()) + { + if (!g_logNotifierCache.empty()) + { + for (const auto& msg : g_logNotifierCache) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, msg.c_str()); + } + g_logNotifierCache.clear(); + } + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, stream.str().c_str()); + } + else + { + while (g_logNotifierCache.size() >= g_logNotifierCacheMaxSize) + g_logNotifierCache.pop_front(); + g_logNotifierCache.push_back(stream.str()); + } +} + +void SfxLokHelper::notifyUpdate(SfxViewShell const* pThisView, int nType) +{ + if (DisableCallbacks::disabled()) + return; + + pThisView->libreOfficeKitViewUpdatedCallback(nType); +} + +void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pThisView, int nType) +{ + notifyUpdatePerViewId(pThisView, pThisView, pThisView, nType); +} + +void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pTargetShell, SfxViewShell const* pViewShell, + SfxViewShell const* pSourceShell, int nType) +{ + if (DisableCallbacks::disabled()) + return; + + int viewId = SfxLokHelper::getView(pViewShell); + int sourceViewId = SfxLokHelper::getView(pSourceShell); + pTargetShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, sourceViewId); +} + +void SfxLokHelper::notifyOtherViewsUpdatePerViewId(SfxViewShell const* pThisView, int nType) +{ + assert(pThisView != nullptr && "pThisView must be valid"); + if (DisableCallbacks::disabled()) + return; + + int viewId = SfxLokHelper::getView(pThisView); + const ViewShellDocId nCurrentDocId = pThisView->GetDocId(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId()) + pViewShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, viewId); + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +namespace +{ + struct LOKAsyncEventData + { + int mnView; // Window is not enough. + VclPtr<vcl::Window> mpWindow; + VclEventId mnEvent; + MouseEvent maMouseEvent; + KeyEvent maKeyEvent; + OUString maText; + }; + + void LOKPostAsyncEvent(void* pEv, void*) + { + std::unique_ptr<LOKAsyncEventData> pLOKEv(static_cast<LOKAsyncEventData*>(pEv)); + if (pLOKEv->mpWindow->isDisposed()) + return; + + int nView = SfxLokHelper::getView(nullptr); + if (nView != pLOKEv->mnView) + { + SAL_INFO("sfx.view", "LOK - view mismatch " << nView << " vs. " << pLOKEv->mnView); + SfxLokHelper::setView(pLOKEv->mnView); + } + + if (!pLOKEv->mpWindow->HasChildPathFocus(true)) + { + SAL_INFO("sfx.view", "LOK - focus mismatch, switching focus"); + pLOKEv->mpWindow->GrabFocus(); + } + + VclPtr<vcl::Window> pFocusWindow = pLOKEv->mpWindow->GetFocusedWindow(); + if (!pFocusWindow) + pFocusWindow = pLOKEv->mpWindow; + + if (pLOKEv->mpWindow->isDisposed()) + return; + + switch (pLOKEv->mnEvent) + { + case VclEventId::WindowKeyInput: + { + sal_uInt16 nRepeat = pLOKEv->maKeyEvent.GetRepeat(); + KeyEvent singlePress(pLOKEv->maKeyEvent.GetCharCode(), + pLOKEv->maKeyEvent.GetKeyCode()); + for (sal_uInt16 i = 0; i <= nRepeat; ++i) + if (!pFocusWindow->isDisposed()) + pFocusWindow->KeyInput(singlePress); + break; + } + case VclEventId::WindowKeyUp: + if (!pFocusWindow->isDisposed()) + pFocusWindow->KeyUp(pLOKEv->maKeyEvent); + break; + case VclEventId::WindowMouseButtonDown: + pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel()); + pLOKEv->mpWindow->MouseButtonDown(pLOKEv->maMouseEvent); + // Invoke the context menu + if (pLOKEv->maMouseEvent.GetButtons() & MOUSE_RIGHT) + { + const CommandEvent aCEvt(pLOKEv->maMouseEvent.GetPosPixel(), CommandEventId::ContextMenu, true, nullptr); + pLOKEv->mpWindow->Command(aCEvt); + } + break; + case VclEventId::WindowMouseButtonUp: + pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel()); + pLOKEv->mpWindow->MouseButtonUp(pLOKEv->maMouseEvent); + + // sometimes MouseButtonDown captures mouse and starts tracking, and VCL + // will not take care of releasing that with tiled rendering + if (pLOKEv->mpWindow->IsTracking()) + pLOKEv->mpWindow->EndTracking(); + + break; + case VclEventId::WindowMouseMove: + pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel()); + pLOKEv->mpWindow->MouseMove(pLOKEv->maMouseEvent); + break; + case VclEventId::ExtTextInput: + case VclEventId::EndExtTextInput: + pLOKEv->mpWindow->PostExtTextInputEvent(pLOKEv->mnEvent, pLOKEv->maText); + break; + default: + assert(false); + break; + } + } + + void postEventAsync(LOKAsyncEventData *pEvent) + { + if (!pEvent->mpWindow || pEvent->mpWindow->isDisposed()) + { + SAL_WARN("vcl", "Async event post - but no valid window as destination " << pEvent->mpWindow.get()); + delete pEvent; + return; + } + + pEvent->mnView = SfxLokHelper::getView(nullptr); + if (vcl::lok::isUnipoll()) + { + if (!Application::IsMainThread()) + SAL_WARN("lok", "Posting event directly but not called from main thread!"); + LOKPostAsyncEvent(pEvent, nullptr); + } + else + Application::PostUserEvent(Link<void*, void>(pEvent, LOKPostAsyncEvent)); + } +} + +void SfxLokHelper::postKeyEventAsync(const VclPtr<vcl::Window> &xWindow, + int nType, int nCharCode, int nKeyCode, int nRepeat) +{ + LOKAsyncEventData* pLOKEv = new LOKAsyncEventData; + switch (nType) + { + case LOK_KEYEVENT_KEYINPUT: + pLOKEv->mnEvent = VclEventId::WindowKeyInput; + break; + case LOK_KEYEVENT_KEYUP: + pLOKEv->mnEvent = VclEventId::WindowKeyUp; + break; + default: + assert(false); + } + pLOKEv->maKeyEvent = KeyEvent(nCharCode, nKeyCode, nRepeat); + pLOKEv->mpWindow = xWindow; + postEventAsync(pLOKEv); +} + +void SfxLokHelper::setBlockedCommandList(int nViewId, const char* blockedCommandList) +{ + SfxViewShell* pViewShell = SfxLokHelper::getViewOfId(nViewId); + + if(pViewShell) + { + pViewShell->setBlockedCommandList(blockedCommandList); + } +} + +void SfxLokHelper::postExtTextEventAsync(const VclPtr<vcl::Window> &xWindow, + int nType, const OUString &rText) +{ + LOKAsyncEventData* pLOKEv = new LOKAsyncEventData; + switch (nType) + { + case LOK_EXT_TEXTINPUT: + pLOKEv->mnEvent = VclEventId::ExtTextInput; + pLOKEv->maText = rText; + break; + case LOK_EXT_TEXTINPUT_END: + pLOKEv->mnEvent = VclEventId::EndExtTextInput; + pLOKEv->maText = ""; + break; + default: + assert(false); + } + pLOKEv->mpWindow = xWindow; + postEventAsync(pLOKEv); +} + +void SfxLokHelper::postMouseEventAsync(const VclPtr<vcl::Window> &xWindow, LokMouseEventData const & rLokMouseEventData) +{ + LOKAsyncEventData* pLOKEv = new LOKAsyncEventData; + switch (rLokMouseEventData.mnType) + { + case LOK_MOUSEEVENT_MOUSEBUTTONDOWN: + pLOKEv->mnEvent = VclEventId::WindowMouseButtonDown; + break; + case LOK_MOUSEEVENT_MOUSEBUTTONUP: + pLOKEv->mnEvent = VclEventId::WindowMouseButtonUp; + break; + case LOK_MOUSEEVENT_MOUSEMOVE: + pLOKEv->mnEvent = VclEventId::WindowMouseMove; + break; + default: + assert(false); + } + + // no reason - just always true so far. + assert (rLokMouseEventData.meModifiers == MouseEventModifiers::SIMPLECLICK); + + pLOKEv->maMouseEvent = MouseEvent(rLokMouseEventData.maPosition, rLokMouseEventData.mnCount, + rLokMouseEventData.meModifiers, rLokMouseEventData.mnButtons, + rLokMouseEventData.mnModifier); + if (rLokMouseEventData.maLogicPosition) + { + pLOKEv->maMouseEvent.setLogicPosition(*rLokMouseEventData.maLogicPosition); + } + pLOKEv->mpWindow = xWindow; + postEventAsync(pLOKEv); +} + +void SfxLokHelper::dumpState(rtl::OStringBuffer &rState) +{ + SfxViewShell* pShell = SfxViewShell::Current(); + sal_Int32 nDocId = pShell ? static_cast<sal_Int32>(pShell->GetDocId().get()) : -1; + + rState.append("\n\tDocId:\t"); + rState.append(nDocId); + + if (nDocId < 0) + return; + + rState.append("\n\tViewCount:\t"); + rState.append(static_cast<sal_Int32>(getViewsCount(nDocId))); + + const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId()) + pViewShell->dumpLibreOfficeKitViewState(rState); + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +bool SfxLokHelper::testInPlaceComponentMouseEventHit(SfxViewShell* pViewShell, int nType, int nX, + int nY, int nCount, int nButtons, + int nModifier, double fScaleX, double fScaleY, + bool bNegativeX) +{ + // In LOK RTL mode draw/svx operates in negative X coordinates + // But the coordinates from client is always positive, so negate nX. + if (bNegativeX) + nX = -nX; + + // check if the user hit a chart/math object which is being edited by this view + if (LokChartHelper aChartHelper(pViewShell, bNegativeX); + aChartHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY)) + return true; + + if (LokStarMathHelper aMathHelper(pViewShell); + aMathHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY)) + return true; + + // check if the user hit a chart which is being edited by someone else + // and, if so, skip current mouse event + if (nType != LOK_MOUSEEVENT_MOUSEMOVE) + { + if (LokChartHelper::HitAny({nX, nY}, bNegativeX)) + return true; + } + + return false; +} + +VclPtr<vcl::Window> SfxLokHelper::getInPlaceDocWindow(SfxViewShell* pViewShell) +{ + if (VclPtr<vcl::Window> pWindow = LokChartHelper(pViewShell).GetWindow()) + return pWindow; + if (VclPtr<vcl::Window> pWindow = LokStarMathHelper(pViewShell).GetWidgetWindow()) + return pWindow; + return {}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/lokstarmathhelper.cxx b/sfx2/source/view/lokstarmathhelper.cxx new file mode 100644 index 0000000000..9b2df19ecd --- /dev/null +++ b/sfx2/source/view/lokstarmathhelper.cxx @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <sfx2/ipclient.hxx> +#include <sfx2/lokcomponenthelpers.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/objsh.hxx> + +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/lok.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/fract.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/layout.hxx> +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> + +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +LokStarMathHelper::LokStarMathHelper(const SfxViewShell* pViewShell) + : mpViewShell(pViewShell) +{ + if (mpViewShell) + { + if (const SfxInPlaceClient* pIPClient = mpViewShell->GetIPClient()) + { + if (const auto& xEmbObj = pIPClient->GetObject()) + { + css::uno::Reference<css::lang::XServiceInfo> xComp(xEmbObj->getComponent(), + css::uno::UNO_QUERY); + if (xComp && xComp->supportsService("com.sun.star.formula.FormulaProperties")) + { + if (css::uno::Reference<css::frame::XModel> xModel{ xComp, + css::uno::UNO_QUERY }) + { + if (auto xController = xModel->getCurrentController()) + { + mpIPClient = pIPClient; + mxFrame = xController->getFrame(); + } + } + } + } + } + } +} + +void LokStarMathHelper::Dispatch( + const OUString& cmd, const css::uno::Sequence<css::beans::PropertyValue>& rArguments) const +{ + if (mxFrame) + comphelper::dispatchCommand(cmd, mxFrame, rArguments); +} + +namespace +{ +// Find a child SmGraphicWindow* +vcl::Window* FindSmGraphicWindow(vcl::Window* pWin) +{ + if (!pWin) + return nullptr; + + if (pWin->IsStarMath()) + return pWin; + + pWin = pWin->GetWindow(GetWindowType::FirstChild); + while (pWin) + { + if (vcl::Window* pSmGraphicWindow = FindSmGraphicWindow(pWin)) + return pSmGraphicWindow; + pWin = pWin->GetWindow(GetWindowType::Next); + } + return nullptr; +} + +// Find a child window that corresponds to SmGraphicWidget +vcl::Window* FindChildSmGraphicWidgetWindow(vcl::Window* pWin) +{ + if (!pWin) + return nullptr; + + // The needed window is a VclDrawingArea + if (dynamic_cast<VclDrawingArea*>(pWin)) + return pWin; + + pWin = pWin->GetWindow(GetWindowType::FirstChild); + while (pWin) + { + if (vcl::Window* pSmGraphicWidgetWindow = FindChildSmGraphicWidgetWindow(pWin)) + return pSmGraphicWidgetWindow; + pWin = pWin->GetWindow(GetWindowType::Next); + } + return nullptr; +} +} + +vcl::Window* LokStarMathHelper::GetGraphicWindow() +{ + if (!mpGraphicWindow) + { + if (mxFrame) + { + css::uno::Reference<css::awt::XWindow> xDockerWin = mxFrame->getContainerWindow(); + mpGraphicWindow.set(FindSmGraphicWindow(VCLUnoHelper::GetWindow(xDockerWin))); + } + } + + return mpGraphicWindow.get(); +} + +vcl::Window* LokStarMathHelper::GetWidgetWindow() +{ + if (!mpWidgetWindow) + mpWidgetWindow.set(FindChildSmGraphicWidgetWindow(GetGraphicWindow())); + + return mpWidgetWindow.get(); +} + +const SfxViewShell* LokStarMathHelper::GetSmViewShell() +{ + if (vcl::Window* pGraphWindow = GetGraphicWindow()) + { + return SfxViewShell::GetFirst(false, [pGraphWindow](const SfxViewShell* shell) { + return shell->GetWindow() && shell->GetWindow()->IsChild(pGraphWindow); + }); + } + return nullptr; +} + +tools::Rectangle LokStarMathHelper::GetBoundingBox() const +{ + if (mpIPClient) + { + tools::Rectangle r(mpIPClient->GetObjArea()); + if (SfxObjectShell* pObjShell = const_cast<SfxViewShell*>(mpViewShell)->GetObjectShell()) + { + const o3tl::Length unit = MapToO3tlLength(pObjShell->GetMapUnit()); + if (unit != o3tl::Length::twip && unit != o3tl::Length::invalid) + r = o3tl::convert(r, unit, o3tl::Length::twip); + } + return r; + } + return {}; +} + +bool LokStarMathHelper::postMouseEvent(int nType, int nX, int nY, int nCount, int nButtons, + int nModifier, double fPPTScaleX, double fPPTScaleY) +{ + const tools::Rectangle rBBox = GetBoundingBox(); + if (Point aMousePos(nX, nY); rBBox.Contains(aMousePos)) + { + if (vcl::Window* pWindow = GetWidgetWindow()) + { + aMousePos -= rBBox.TopLeft(); + + // In lok, Math does not convert coordinates (see SmGraphicWidget::SetDrawingArea, + // which disables MapMode), and uses twips internally (see SmDocShell ctor and + // SmMapUnit), but the conversion factor can depend on the client zoom. + // 1. Remove the twip->pixel factor in the passed scales + double fScaleX = o3tl::convert(fPPTScaleX, o3tl::Length::px, o3tl::Length::twip); + double fScaleY = o3tl::convert(fPPTScaleY, o3tl::Length::px, o3tl::Length::twip); + // 2. Adjust the position according to the scales + aMousePos + = Point(std::round(aMousePos.X() * fScaleX), std::round(aMousePos.Y() * fScaleY)); + // 3. Take window own scaling into account (reverses the conversion done in + // SmGraphicWidget::MouseButtonDown, albeit incompletely - it does not handle + // GetFormulaDrawPos; hopefully, in lok/in-place case, it's always [ 0, 0 ]?) + aMousePos = pWindow->LogicToPixel(aMousePos); + + LokMouseEventData aMouseEventData( + nType, aMousePos, nCount, MouseEventModifiers::SIMPLECLICK, nButtons, nModifier); + SfxLokHelper::postMouseEventAsync(pWindow, aMouseEventData); + + return true; + } + } + return false; +} + +void LokStarMathHelper::PaintTile(VirtualDevice& rDevice, const tools::Rectangle& rTileRect) +{ + const tools::Rectangle aMathRect = GetBoundingBox(); + if (rTileRect.GetIntersection(aMathRect).IsEmpty()) + return; + + vcl::Window* pWidgetWindow = GetWidgetWindow(); + if (!pWidgetWindow) + return; + + Point aOffset(aMathRect.Left() - rTileRect.Left(), aMathRect.Top() - rTileRect.Top()); + + MapMode newMode = rDevice.GetMapMode(); + newMode.SetOrigin(aOffset); + rDevice.SetMapMode(newMode); // Push/Pop is done in PaintAllInPlaceOnTile + + pWidgetWindow->Paint(rDevice, {}); // SmGraphicWidget::Paint does not use the passed rectangle +} + +void LokStarMathHelper::PaintAllInPlaceOnTile(VirtualDevice& rDevice, int nOutputWidth, + int nOutputHeight, int nTilePosX, int nTilePosY, + tools::Long nTileWidth, tools::Long nTileHeight) +{ + if (comphelper::LibreOfficeKit::isTiledAnnotations()) + return; + + SfxViewShell* pCurView = SfxViewShell::Current(); + if (!pCurView) + return; + const ViewShellDocId nDocId = pCurView->GetDocId(); + const int nPartForCurView = pCurView->getPart(); + + // Resizes the virtual device to contain the entries context + rDevice.SetOutputSizePixel({ nOutputWidth, nOutputHeight }); + + rDevice.Push(vcl::PushFlags::MAPMODE); + MapMode aMapMode(rDevice.GetMapMode()); + + // Scaling. Must convert from pixels to twips. We know that VirtualDevices use a DPI of 96. + const Fraction scale = conversionFract(o3tl::Length::px, o3tl::Length::twip); + const Fraction scaleX = Fraction(nOutputWidth, nTileWidth) * scale; + const Fraction scaleY = Fraction(nOutputHeight, nTileHeight) * scale; + aMapMode.SetScaleX(scaleX); + aMapMode.SetScaleY(scaleY); + aMapMode.SetMapUnit(MapUnit::MapTwip); + rDevice.SetMapMode(aMapMode); + + const tools::Rectangle aTileRect(Point(nTilePosX, nTilePosY), Size(nTileWidth, nTileHeight)); + + for (SfxViewShell* pViewShell = SfxViewShell::GetFirst(); pViewShell; + pViewShell = SfxViewShell::GetNext(*pViewShell)) + if (pViewShell->GetDocId() == nDocId && pViewShell->getPart() == nPartForCurView) + LokStarMathHelper(pViewShell).PaintTile(rDevice, aTileRect); + + rDevice.Pop(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sfx2/source/view/printer.cxx b/sfx2/source/view/printer.cxx new file mode 100644 index 0000000000..7b77453495 --- /dev/null +++ b/sfx2/source/view/printer.cxx @@ -0,0 +1,189 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/debug.hxx> + +#include <utility> + +#include <sfx2/printer.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/tabdlg.hxx> +#include "prnmon.hxx" + +// class SfxPrinter ------------------------------------------------------ + +VclPtr<SfxPrinter> SfxPrinter::Create( SvStream& rStream, std::unique_ptr<SfxItemSet>&& pOptions ) + +/* [Description] + + Creates a <SfxPrinter> from the stream. Loading is really only a jobsetup. + If such a printer is not available on the system, then the original is + marked as the original Job-setup and a comparable printer is selected from + existing ones. + + The 'pOptions' are taken over in the generated SfxPrinter, the return + value belongs to the caller. +*/ + +{ + // Load JobSetup + JobSetup aFileJobSetup; + ReadJobSetup( rStream, aFileJobSetup ); + + // Get printers + VclPtr<SfxPrinter> pPrinter = VclPtr<SfxPrinter>::Create( std::move(pOptions), aFileJobSetup ); + return pPrinter; +} + + +void SfxPrinter::Store( SvStream& rStream ) const + +/* [Description] + + Saves the used JobSetup of <SfxPrinter>s. +*/ + +{ + WriteJobSetup( rStream, GetJobSetup() ); +} + + +SfxPrinter::SfxPrinter( std::unique_ptr<SfxItemSet>&& pTheOptions ) : + +/* [Description] + + This constructor creates a default printer. +*/ + pOptions( std::move(pTheOptions) ), + bKnown( true ) +{ + assert(pOptions); +} + + +SfxPrinter::SfxPrinter( std::unique_ptr<SfxItemSet>&& pTheOptions, + const JobSetup& rTheOrigJobSetup ) : + Printer( rTheOrigJobSetup.GetPrinterName() ), + pOptions( std::move(pTheOptions) ) +{ + assert(pOptions); + bKnown = GetName() == rTheOrigJobSetup.GetPrinterName(); + + if ( bKnown ) + SetJobSetup( rTheOrigJobSetup ); +} + + +SfxPrinter::SfxPrinter( std::unique_ptr<SfxItemSet>&& pTheOptions, + const OUString& rPrinterName ) : + Printer( rPrinterName ), + pOptions( std::move(pTheOptions) ), + bKnown( GetName() == rPrinterName ) +{ + assert(pOptions); +} + + +SfxPrinter::SfxPrinter( const SfxPrinter& rPrinter ) : + VclReferenceBase(), + Printer( rPrinter.GetName() ), + pOptions( rPrinter.GetOptions().Clone() ), + bKnown( rPrinter.IsKnown() ) +{ + assert(pOptions); + SetJobSetup( rPrinter.GetJobSetup() ); + SetPrinterProps( &rPrinter ); + SetMapMode( rPrinter.GetMapMode() ); +} + + +VclPtr<SfxPrinter> SfxPrinter::Clone() const +{ + if ( IsDefPrinter() ) + { + VclPtr<SfxPrinter> pNewPrinter = VclPtr<SfxPrinter>::Create( GetOptions().Clone() ); + pNewPrinter->SetJobSetup( GetJobSetup() ); + pNewPrinter->SetPrinterProps( this ); + pNewPrinter->SetMapMode( GetMapMode() ); + return pNewPrinter; + } + else + return VclPtr<SfxPrinter>::Create( *this ); +} + + +SfxPrinter::~SfxPrinter() +{ + disposeOnce(); +} + +void SfxPrinter::dispose() +{ + pOptions.reset(); + Printer::dispose(); +} + + +void SfxPrinter::SetOptions( const SfxItemSet &rNewOptions ) +{ + pOptions->Set(rNewOptions); +} + + +SfxPrintOptionsDialog::SfxPrintOptionsDialog(weld::Window *pParent, + SfxViewShell *pViewShell, + const SfxItemSet *pSet) + : GenericDialogController(pParent, "sfx/ui/printeroptionsdialog.ui", "PrinterOptionsDialog") + , pOptions(pSet->Clone()) + , m_xHelpBtn(m_xBuilder->weld_widget("help")) + , m_xContainer(m_xDialog->weld_content_area()) + , m_xPage(pViewShell->CreatePrintOptionsPage(m_xContainer.get(), this, *pOptions)) // Insert TabPage +{ + DBG_ASSERT( m_xPage, "CreatePrintOptions != SFX_VIEW_HAS_PRINTOPTIONS" ); + if (m_xPage) + { + m_xPage->Reset( pOptions.get() ); + m_xDialog->set_help_id(m_xPage->GetHelpId()); + } +} + +SfxPrintOptionsDialog::~SfxPrintOptionsDialog() +{ +} + +short SfxPrintOptionsDialog::run() +{ + if (!m_xPage) + return RET_CANCEL; + + short nRet = GenericDialogController::run(); + + if (nRet == RET_OK) + m_xPage->FillItemSet( pOptions.get() ); + else + m_xPage->Reset( pOptions.get() ); + return nRet; +} + +void SfxPrintOptionsDialog::DisableHelp() +{ + m_xHelpBtn->set_sensitive(false); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/prnmon.hxx b/sfx2/source/view/prnmon.hxx new file mode 100644 index 0000000000..06b3217fa9 --- /dev/null +++ b/sfx2/source/view/prnmon.hxx @@ -0,0 +1,54 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_SFX2_PRNMON_HXX +#define INCLUDED_SFX2_PRNMON_HXX + +#include <memory> +#include <sal/config.h> +#include <vcl/weld.hxx> + + +class SfxViewShell; +class SfxTabPage; +class SfxItemSet; + + +class SfxPrintOptionsDialog final : public weld::GenericDialogController +{ +private: + std::unique_ptr<SfxItemSet> pOptions; + std::unique_ptr<weld::Widget> m_xHelpBtn; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<SfxTabPage> m_xPage; + +public: + SfxPrintOptionsDialog(weld::Window *pParent, + SfxViewShell *pViewShell, + const SfxItemSet *rOptions); + virtual ~SfxPrintOptionsDialog() override; + + virtual short run() override; + + const SfxItemSet& GetOptions() const { return *pOptions; } + void DisableHelp(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/sfxbasecontroller.cxx b/sfx2/source/view/sfxbasecontroller.cxx new file mode 100644 index 0000000000..e9cd1f4642 --- /dev/null +++ b/sfx2/source/view/sfxbasecontroller.cxx @@ -0,0 +1,1498 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <time.h> +#include <sfx2/sfxbasecontroller.hxx> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/util/XCloseBroadcaster.hpp> +#include <com/sun/star/util/XCloseListener.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/document/XCmisDocument.hpp> +#include <com/sun/star/document/XViewDataSupplier.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/frame/FrameActionEvent.hpp> +#include <com/sun/star/frame/FrameAction.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/frame/CommandGroup.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XBorderResizeListener.hpp> +#include <com/sun/star/frame/XUntitledNumbers.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <comphelper/interfacecontainer3.hxx> +#include <comphelper/multicontainer2.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/docfac.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/app.hxx> +#include <sfx2/msgpool.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/userinputinterception.hxx> + +#include <unoctitm.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/sfxresid.hxx> +#include <workwin.hxx> +#include <sfx2/infobar.hxx> + +#include <osl/mutex.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/sequence.hxx> +#include <toolkit/helper/convert.hxx> +#include <framework/titlehelper.hxx> +#include <comphelper/processfactory.hxx> +#include <vcl/svapp.hxx> +#include <tools/svborder.hxx> + +#include <sfx2/event.hxx> +#include <sfx2/viewfac.hxx> +#include <sfx2/strings.hrc> +#include <sfxbasecontroller_internal.hxx> + +#include <unordered_map> + +#include <com/sun/star/ui/XSidebarProvider.hpp> +#include <sidebar/UnoSidebar.hxx> + +#define TIMEOUT_START_RESCHEDULE 10L /* 10th s */ + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::RuntimeException; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::lang::DisposedException; +using ::com::sun::star::awt::XWindow; +using ::com::sun::star::frame::XController; +using ::com::sun::star::frame::XDispatchProvider; +using ::com::sun::star::document::XViewDataSupplier; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::beans::PropertyValue; +using ::com::sun::star::beans::StringPair; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::frame::XFrame; +using ::com::sun::star::frame::XFrameActionListener; +using ::com::sun::star::util::XCloseListener; +using ::com::sun::star::task::XStatusIndicator; +using ::com::sun::star::frame::XTitle; +using ::com::sun::star::ui::XSidebarProvider; + + +typedef std::unordered_map< SfxGroupId, sal_Int16 > GroupHashMap; + +sal_Int16 MapGroupIDToCommandGroup( SfxGroupId nGroupID ) +{ + static GroupHashMap s_aHashMap + { + { SfxGroupId::Intern , frame::CommandGroup::INTERNAL }, + { SfxGroupId::Application , frame::CommandGroup::APPLICATION }, + { SfxGroupId::Document , frame::CommandGroup::DOCUMENT }, + { SfxGroupId::View , frame::CommandGroup::VIEW }, + { SfxGroupId::Edit , frame::CommandGroup::EDIT }, + { SfxGroupId::Macro , frame::CommandGroup::MACRO }, + { SfxGroupId::Options , frame::CommandGroup::OPTIONS }, + { SfxGroupId::Math , frame::CommandGroup::MATH }, + { SfxGroupId::Navigator , frame::CommandGroup::NAVIGATOR }, + { SfxGroupId::Insert , frame::CommandGroup::INSERT }, + { SfxGroupId::Format , frame::CommandGroup::FORMAT }, + { SfxGroupId::Template , frame::CommandGroup::TEMPLATE }, + { SfxGroupId::Text , frame::CommandGroup::TEXT }, + { SfxGroupId::Frame , frame::CommandGroup::FRAME }, + { SfxGroupId::Graphic , frame::CommandGroup::GRAPHIC }, + { SfxGroupId::Table , frame::CommandGroup::TABLE }, + { SfxGroupId::Enumeration , frame::CommandGroup::ENUMERATION }, + { SfxGroupId::Data , frame::CommandGroup::DATA }, + { SfxGroupId::Special , frame::CommandGroup::SPECIAL }, + { SfxGroupId::Image , frame::CommandGroup::IMAGE }, + { SfxGroupId::Chart , frame::CommandGroup::CHART }, + { SfxGroupId::Explorer , frame::CommandGroup::EXPLORER }, + { SfxGroupId::Connector , frame::CommandGroup::CONNECTOR }, + { SfxGroupId::Modify , frame::CommandGroup::MODIFY }, + { SfxGroupId::Drawing , frame::CommandGroup::DRAWING }, + { SfxGroupId::Controls , frame::CommandGroup::CONTROLS }, + }; + + + GroupHashMap::const_iterator pIter = s_aHashMap.find( nGroupID ); + if ( pIter != s_aHashMap.end() ) + return pIter->second; + else + return frame::CommandGroup::INTERNAL; +} + +sal_uInt32 Get10ThSec() +{ + sal_uInt32 n10Ticks = 10 * static_cast<sal_uInt32>(clock()); + return n10Ticks / CLOCKS_PER_SEC; +} + +static sal_Int32 m_nInReschedule = 0; /// static counter for rescheduling + +static void reschedule() +{ + if ( m_nInReschedule == 0 ) + { + ++m_nInReschedule; + Application::Reschedule(); + --m_nInReschedule; + } +} + +namespace { + +class SfxStatusIndicator : public ::cppu::WeakImplHelper< task::XStatusIndicator, lang::XEventListener > +{ + Reference < XController > xOwner; + Reference < task::XStatusIndicator > xProgress; + SfxWorkWindow* pWorkWindow; + tools::Long _nStartTime; +public: + SfxStatusIndicator(SfxBaseController* pController, SfxWorkWindow* pWork) + : xOwner( pController ) + , pWorkWindow( pWork ) + , _nStartTime(0) + { + osl_atomic_increment(&m_refCount); + Reference< lang::XComponent > xComponent = pController; + if (xComponent.is()) + xComponent->addEventListener(this); + osl_atomic_decrement(&m_refCount); + } + + virtual void SAL_CALL start(const OUString& aText, sal_Int32 nRange) override; + virtual void SAL_CALL end() override; + virtual void SAL_CALL setText(const OUString& aText) override; + virtual void SAL_CALL setValue(sal_Int32 nValue) override; + virtual void SAL_CALL reset() override; + + virtual void SAL_CALL disposing( const lang::EventObject& Source ) override; +}; + +} + +void SAL_CALL SfxStatusIndicator::start(const OUString& aText, sal_Int32 nRange) +{ + SolarMutexGuard aGuard; + if ( xOwner.is() ) + { + if ( !xProgress.is() ) + xProgress = pWorkWindow->GetStatusIndicator(); + + if ( xProgress.is() ) + xProgress->start( aText, nRange ); + + _nStartTime = Get10ThSec(); + reschedule(); + } +} + +void SAL_CALL SfxStatusIndicator::end() +{ + SolarMutexGuard aGuard; + if ( xOwner.is() ) + { + if ( !xProgress.is() ) + xProgress = pWorkWindow->GetStatusIndicator(); + + if ( xProgress.is() ) + xProgress->end(); + + reschedule(); + } +} + +void SAL_CALL SfxStatusIndicator::setText(const OUString& aText) +{ + SolarMutexGuard aGuard; + if ( xOwner.is() ) + { + if ( !xProgress.is() ) + xProgress = pWorkWindow->GetStatusIndicator(); + + if ( xProgress.is() ) + xProgress->setText( aText ); + + reschedule(); + } +} + +void SAL_CALL SfxStatusIndicator::setValue( sal_Int32 nValue ) +{ + SolarMutexGuard aGuard; + if ( xOwner.is() ) + { + if ( !xProgress.is() ) + xProgress = pWorkWindow->GetStatusIndicator(); + + if ( xProgress.is() ) + xProgress->setValue( nValue ); + + bool bReschedule = (( Get10ThSec() - _nStartTime ) > TIMEOUT_START_RESCHEDULE ); + if ( bReschedule ) + reschedule(); + } +} + +void SAL_CALL SfxStatusIndicator::reset() +{ + SolarMutexGuard aGuard; + if ( xOwner.is() ) + { + if ( !xProgress.is() ) + xProgress = pWorkWindow->GetStatusIndicator(); + + if ( xProgress.is() ) + xProgress->reset(); + + reschedule(); + } +} + +void SAL_CALL SfxStatusIndicator::disposing( const lang::EventObject& /*Source*/ ) +{ + SolarMutexGuard aGuard; + xOwner = nullptr; + xProgress.clear(); +} + + +// declaration IMPL_SfxBaseController_ListenerHelper + +namespace { + +class IMPL_SfxBaseController_ListenerHelper : public ::cppu::WeakImplHelper< frame::XFrameActionListener > +{ +public: + explicit IMPL_SfxBaseController_ListenerHelper( SfxBaseController* pController ) ; + + virtual void SAL_CALL frameAction( const frame::FrameActionEvent& aEvent ) override ; + virtual void SAL_CALL disposing( const lang::EventObject& aEvent ) override ; + +private: + + SfxBaseController* m_pController ; + +} ; // class IMPL_SfxBaseController_ListenerContainer + +class IMPL_SfxBaseController_CloseListenerHelper : public ::cppu::WeakImplHelper< util::XCloseListener > +{ +public: + explicit IMPL_SfxBaseController_CloseListenerHelper( SfxBaseController* pController ) ; + + virtual void SAL_CALL queryClosing( const lang::EventObject& aEvent, sal_Bool bDeliverOwnership ) override ; + virtual void SAL_CALL notifyClosing( const lang::EventObject& aEvent ) override ; + virtual void SAL_CALL disposing( const lang::EventObject& aEvent ) override ; + +private: + + SfxBaseController* m_pController; + +} ; // class IMPL_SfxBaseController_ListenerContainer + +} + +IMPL_SfxBaseController_CloseListenerHelper::IMPL_SfxBaseController_CloseListenerHelper( SfxBaseController* pController ) + : m_pController ( pController ) +{ +} + +void SAL_CALL IMPL_SfxBaseController_CloseListenerHelper::disposing( const lang::EventObject& /*aEvent*/ ) +{ +} + +void SAL_CALL IMPL_SfxBaseController_CloseListenerHelper::queryClosing( const lang::EventObject& /*aEvent*/, sal_Bool /*bDeliverOwnership*/ ) +{ + SolarMutexGuard aGuard; + SfxViewShell* pShell = m_pController->GetViewShell_Impl(); + if (pShell) + { + bool bCanClose = pShell->PrepareClose( false ); + if ( !bCanClose ) + { + throw util::CloseVetoException("Controller disagree ...",getXWeak()); + } + } +} + +void SAL_CALL IMPL_SfxBaseController_CloseListenerHelper::notifyClosing( const lang::EventObject& /*aEvent*/ ) +{ +} + + +// declaration IMPL_SfxBaseController_DataContainer + + +struct IMPL_SfxBaseController_DataContainer +{ + Reference< XFrame > m_xFrame ; + Reference< XFrameActionListener > m_xListener ; + Reference< XCloseListener > m_xCloseListener ; + ::sfx2::UserInputInterception m_aUserInputInterception; + ::comphelper::OMultiTypeInterfaceContainerHelper2 m_aListenerContainer ; + ::comphelper::OInterfaceContainerHelper3<ui::XContextMenuInterceptor> m_aInterceptorContainer ; + Reference< XStatusIndicator > m_xIndicator ; + SfxViewShell* m_pViewShell ; + SfxBaseController* m_pController ; + bool m_bDisposing ; + bool m_bSuspendState ; + Reference< XTitle > m_xTitleHelper ; + Sequence< PropertyValue > m_aCreationArgs ; + + IMPL_SfxBaseController_DataContainer( ::osl::Mutex& aMutex , + SfxViewShell* pViewShell , + SfxBaseController* pController ) + : m_xListener ( new IMPL_SfxBaseController_ListenerHelper( pController ) ) + , m_xCloseListener ( new IMPL_SfxBaseController_CloseListenerHelper( pController ) ) + , m_aUserInputInterception ( *pController, aMutex ) + , m_aListenerContainer ( aMutex ) + , m_aInterceptorContainer ( aMutex ) + , m_pViewShell ( pViewShell ) + , m_pController ( pController ) + , m_bDisposing ( false ) + , m_bSuspendState ( false ) + { + } + +} ; // struct IMPL_SfxBaseController_DataContainer + + +// IMPL_SfxBaseController_ListenerHelper constructor + + +IMPL_SfxBaseController_ListenerHelper::IMPL_SfxBaseController_ListenerHelper( SfxBaseController* pController ) + : m_pController ( pController ) +{ +} + +void SAL_CALL IMPL_SfxBaseController_ListenerHelper::frameAction( const frame::FrameActionEvent& aEvent ) +{ + SolarMutexGuard aGuard; + if ( + ( m_pController != nullptr ) && + ( aEvent.Frame == m_pController->getFrame() ) && + ( m_pController->GetViewShell_Impl() && m_pController->GetViewShell_Impl()->GetWindow() != nullptr ) + ) + { + if ( aEvent.Action == frame::FrameAction_FRAME_UI_ACTIVATED ) + { + if ( !m_pController->GetViewShell_Impl()->GetUIActiveIPClient_Impl() ) + m_pController->GetViewShell_Impl()->GetViewFrame().MakeActive_Impl( false ); + } + else if ( aEvent.Action == frame::FrameAction_CONTEXT_CHANGED ) + { + m_pController->GetViewShell_Impl()->GetViewFrame().GetBindings().ContextChanged_Impl(); + } + } +} + + +// IMPL_SfxBaseController_ListenerHelper -> XEventListener + + +void SAL_CALL IMPL_SfxBaseController_ListenerHelper::disposing( const lang::EventObject& /*aEvent*/ ) +{ + SolarMutexGuard aGuard; + if ( m_pController && m_pController->getFrame().is() ) + m_pController->getFrame()->removeFrameActionListener( this ) ; +} + +SfxBaseController::SfxBaseController( SfxViewShell* pViewShell ) + : m_pData ( new IMPL_SfxBaseController_DataContainer( m_aMutex, pViewShell, this )) +{ + m_pData->m_pViewShell->SetController( this ); +} + + +// SfxBaseController -> destructor + + +SfxBaseController::~SfxBaseController() +{ +} + + +// SfxBaseController -> XController2 + + +Reference< XWindow > SAL_CALL SfxBaseController::getComponentWindow() +{ + SolarMutexGuard aGuard; + if ( !m_pData->m_pViewShell ) + throw DisposedException(); + + return Reference< XWindow >( GetViewFrame_Impl().GetFrame().GetWindow().GetComponentInterface(), UNO_QUERY_THROW ); +} + +OUString SAL_CALL SfxBaseController::getViewControllerName() +{ + SolarMutexGuard aGuard; + if ( !m_pData->m_pViewShell || !m_pData->m_pViewShell->GetObjectShell() ) + throw DisposedException(); + + const SfxObjectFactory& rDocFac( m_pData->m_pViewShell->GetObjectShell()->GetFactory() ); + sal_uInt16 nViewNo = rDocFac.GetViewNo_Impl( GetViewFrame_Impl().GetCurViewId(), rDocFac.GetViewFactoryCount() ); + OSL_ENSURE( nViewNo < rDocFac.GetViewFactoryCount(), "SfxBaseController::getViewControllerName: view ID not found in view factories!" ); + + OUString sViewName; + if ( nViewNo < rDocFac.GetViewFactoryCount() ) + sViewName = rDocFac.GetViewFactory( nViewNo ).GetAPIViewName(); + + return sViewName; +} + +Sequence< PropertyValue > SAL_CALL SfxBaseController::getCreationArguments() +{ + SolarMutexGuard aGuard; + if ( !m_pData->m_pViewShell || !m_pData->m_pViewShell->GetObjectShell() ) + throw DisposedException(); + + return m_pData->m_aCreationArgs; +} + +void SfxBaseController::SetCreationArguments_Impl( const Sequence< PropertyValue >& i_rCreationArgs ) +{ + OSL_ENSURE( !m_pData->m_aCreationArgs.hasElements(), "SfxBaseController::SetCreationArguments_Impl: not intended to be called twice!" ); + m_pData->m_aCreationArgs = i_rCreationArgs; +} + +SfxViewFrame& SfxBaseController::GetViewFrame_Impl() const +{ + ENSURE_OR_THROW( m_pData->m_pViewShell, "not to be called without a view shell" ); + SfxViewFrame* pActFrame = m_pData->m_pViewShell->GetFrame(); + ENSURE_OR_THROW( pActFrame, "a view shell without a view frame is pretty pathological" ); + return *pActFrame; +} + + +Reference<XSidebarProvider> SAL_CALL SfxBaseController::getSidebar() +{ + SfxViewFrame& rViewFrame = GetViewFrame_Impl(); + SfxFrame& rFrame = rViewFrame.GetFrame(); + + Reference<XSidebarProvider> rSidebar = new SfxUnoSidebar(rFrame.GetFrameInterface()); + return rSidebar; +} + + +// SfxBaseController -> XController2 -> XController + + +void SAL_CALL SfxBaseController::attachFrame( const Reference< frame::XFrame >& xFrame ) +{ + Reference< frame::XFrame > xTemp( getFrame() ) ; + + SolarMutexGuard aGuard; + if ( xTemp.is() ) + { + xTemp->removeFrameActionListener( m_pData->m_xListener ) ; + Reference < util::XCloseBroadcaster > xCloseable( xTemp, uno::UNO_QUERY ); + if ( xCloseable.is() ) + xCloseable->removeCloseListener( m_pData->m_xCloseListener ); + } + + m_pData->m_xFrame = xFrame; + + if ( !xFrame.is() ) + return; + + xFrame->addFrameActionListener( m_pData->m_xListener ) ; + Reference < util::XCloseBroadcaster > xCloseable( xFrame, uno::UNO_QUERY ); + if ( xCloseable.is() ) + xCloseable->addCloseListener( m_pData->m_xCloseListener ); + + if ( m_pData->m_pViewShell ) + { + ConnectSfxFrame_Impl( E_CONNECT ); + ShowInfoBars( ); + + // attaching the frame to the controller is the last step in the creation of a new view, so notify this + SfxViewEventHint aHint( SfxEventHintId::ViewCreated, GlobalEventConfig::GetEventName( GlobalEventId::VIEWCREATED ), m_pData->m_pViewShell->GetObjectShell(), Reference< frame::XController2 >( this ) ); + SfxGetpApp()->NotifyEvent( aHint ); + } +} + + +// SfxBaseController -> XController + + +sal_Bool SAL_CALL SfxBaseController::attachModel( const Reference< frame::XModel >& xModel ) +{ + if ( m_pData->m_pViewShell && xModel.is() && xModel != m_pData->m_pViewShell->GetObjectShell()->GetModel() ) + { + // don't allow to reattach a model! + OSL_FAIL("Can't reattach model!"); + return false; + } + + Reference < util::XCloseBroadcaster > xCloseable( xModel, uno::UNO_QUERY ); + if ( xCloseable.is() ) + xCloseable->addCloseListener( m_pData->m_xCloseListener ); + return true; +} + + +// SfxBaseController -> XController + + +sal_Bool SAL_CALL SfxBaseController::suspend( sal_Bool bSuspend ) +{ + SolarMutexGuard aGuard; + + // ignore duplicate calls, which doesn't change anything real + if (bool(bSuspend) == m_pData->m_bSuspendState) + return true; + + if ( bSuspend ) + { + if ( !m_pData->m_pViewShell ) + { + m_pData->m_bSuspendState = true; + return true; + } + + if ( !m_pData->m_pViewShell->PrepareClose() ) + return false; + + if ( getFrame().is() ) + getFrame()->removeFrameActionListener( m_pData->m_xListener ) ; + SfxViewFrame* pActFrame = m_pData->m_pViewShell->GetFrame() ; + + // More Views on the same document? + SfxObjectShell* pDocShell = m_pData->m_pViewShell->GetObjectShell() ; + bool bOther = false ; + + for ( const SfxViewFrame* pFrame = SfxViewFrame::GetFirst( pDocShell ); !bOther && pFrame; pFrame = SfxViewFrame::GetNext( *pFrame, pDocShell ) ) + bOther = (pFrame != pActFrame); + + bool bRet = bOther || pDocShell->PrepareClose(); + if ( bRet ) + { + ConnectSfxFrame_Impl( E_DISCONNECT ); + m_pData->m_bSuspendState = true; + } + + return bRet; + } + else + { + if ( getFrame().is() ) + getFrame()->addFrameActionListener( m_pData->m_xListener ) ; + + if ( m_pData->m_pViewShell ) + { + ConnectSfxFrame_Impl( E_RECONNECT ); + } + + m_pData->m_bSuspendState = false; + return true ; + } +} + + +// SfxBaseController -> XController + + +uno::Any SfxBaseController::getViewData() +{ + uno::Any aAny; + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell ) + { + OUString sData; + m_pData->m_pViewShell->WriteUserData( sData ) ; + aAny <<= sData ; + } + + return aAny ; +} + + +// SfxBaseController -> XController + + +void SAL_CALL SfxBaseController::restoreViewData( const uno::Any& aValue ) +{ + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell ) + { + OUString sData; + aValue >>= sData ; + m_pData->m_pViewShell->ReadUserData( sData ) ; + } +} + + +// SfxBaseController -> XController + + +Reference< frame::XFrame > SAL_CALL SfxBaseController::getFrame() +{ + SolarMutexGuard aGuard; + return m_pData->m_xFrame; +} + + +// SfxBaseController -> XController + + +Reference< frame::XModel > SAL_CALL SfxBaseController::getModel() +{ + SolarMutexGuard aGuard; + return m_pData->m_pViewShell ? m_pData->m_pViewShell->GetObjectShell()->GetModel() : Reference < frame::XModel > () ; +} + + +// SfxBaseController -> XDispatchProvider + +static css::uno::Reference<css::frame::XDispatch> +GetSlotDispatchWithFallback(SfxViewFrame* pViewFrame, const css::util::URL& aURL, + const OUString& sActCommand, bool bMasterCommand, const SfxSlot* pSlot) +{ + assert(pViewFrame); + + if (pSlot && (!pViewFrame->GetFrame().IsInPlace() || !pSlot->IsMode(SfxSlotMode::CONTAINER))) + return pViewFrame->GetBindings().GetDispatch(pSlot, aURL, bMasterCommand); + + // try to find parent SfxViewFrame + if (const auto& xOwnFrame = pViewFrame->GetFrame().GetFrameInterface()) + { + if (const auto& xParentFrame = xOwnFrame->getCreator()) + { + // TODO/LATER: in future probably SfxViewFrame hierarchy should be the same as XFrame hierarchy + // SfxViewFrame* pParentFrame = pViewFrame->GetParentViewFrame(); + + // search the related SfxViewFrame + SfxViewFrame* pParentFrame = nullptr; + for (SfxViewFrame* pFrame = SfxViewFrame::GetFirst(); pFrame; + pFrame = SfxViewFrame::GetNext(*pFrame)) + { + if (pFrame->GetFrame().GetFrameInterface() == xParentFrame) + { + pParentFrame = pFrame; + break; + } + } + + if (pParentFrame) + { + const SfxSlotPool& rFrameSlotPool = SfxSlotPool::GetSlotPool(pParentFrame); + if (const SfxSlot* pSlot2 = rFrameSlotPool.GetUnoSlot(sActCommand)) + return pParentFrame->GetBindings().GetDispatch(pSlot2, aURL, bMasterCommand); + } + } + } + + return {}; +} + +Reference< frame::XDispatch > SAL_CALL SfxBaseController::queryDispatch( const util::URL& aURL , + const OUString& sTargetFrameName, + sal_Int32 eSearchFlags ) +{ + SolarMutexGuard aGuard; + + if (!m_pData->m_bDisposing && m_pData->m_pViewShell) + { + SfxViewFrame& rAct = m_pData->m_pViewShell->GetViewFrame() ; + if ( sTargetFrameName == "_beamer" ) + { + if ( eSearchFlags & frame::FrameSearchFlag::CREATE ) + rAct.SetChildWindow( SID_BROWSER, true ); + if (SfxChildWindow* pChildWin = rAct.GetChildWindow(SID_BROWSER)) + { + if (Reference<frame::XFrame> xFrame{ pChildWin->GetFrame() }) + { + xFrame->setName(sTargetFrameName); + if (Reference<XDispatchProvider> xProv{ xFrame, uno::UNO_QUERY }) + return xProv->queryDispatch(aURL, sTargetFrameName, frame::FrameSearchFlag::SELF); + } + } + } + + if ( aURL.Protocol == ".uno:" ) + { + OUString aActCommand = SfxOfficeDispatch::GetMasterUnoCommand(aURL); + bool bMasterCommand(!aActCommand.isEmpty()); + if (!bMasterCommand) + aActCommand = aURL.Path; + const SfxSlot* pSlot = SfxSlotPool::GetSlotPool(&rAct).GetUnoSlot(aActCommand); + return GetSlotDispatchWithFallback(&rAct, aURL, aActCommand, bMasterCommand, pSlot); + } + else if ( aURL.Protocol == "slot:" ) + { + sal_uInt16 nId = static_cast<sal_uInt16>(aURL.Path.toInt32()); + + if (nId >= SID_VERB_START && nId <= SID_VERB_END) + { + const SfxSlot* pSlot = m_pData->m_pViewShell->GetVerbSlot_Impl(nId); + if ( pSlot ) + return rAct.GetBindings().GetDispatch( pSlot, aURL, false ); + } + + const SfxSlot* pSlot = SfxSlotPool::GetSlotPool(&rAct).GetSlot(nId); + return GetSlotDispatchWithFallback(&rAct, aURL, aURL.Path, false, pSlot); + } + else if( sTargetFrameName == "_self" || sTargetFrameName.isEmpty() ) + { + // check for already loaded URL ... but with additional jumpmark! + Reference< frame::XModel > xModel = getModel(); + if( xModel.is() && !aURL.Mark.isEmpty() ) + { + SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool( &rAct ); + const SfxSlot* pSlot = rSlotPool.GetSlot( SID_JUMPTOMARK ); + if( !aURL.Main.isEmpty() && aURL.Main == xModel->getURL() && pSlot ) + return Reference< frame::XDispatch >( new SfxOfficeDispatch( rAct.GetBindings(), rAct.GetDispatcher(), pSlot, aURL) ); + } + } + } + + return {}; +} + + +// SfxBaseController -> XDispatchProvider + + +uno::Sequence< Reference< frame::XDispatch > > SAL_CALL SfxBaseController::queryDispatches( const uno::Sequence< frame::DispatchDescriptor >& seqDescripts ) +{ + // Create return list - which must have same size then the given descriptor + // It's not allowed to pack it! + sal_Int32 nCount = seqDescripts.getLength(); + uno::Sequence< Reference< frame::XDispatch > > lDispatcher( nCount ); + + std::transform(seqDescripts.begin(), seqDescripts.end(), lDispatcher.getArray(), + [this](const frame::DispatchDescriptor& rDesc) -> Reference< frame::XDispatch > { + return queryDispatch(rDesc.FeatureURL, rDesc.FrameName, rDesc.SearchFlags); }); + + return lDispatcher; +} + + +// SfxBaseController -> XControllerBorder + + +frame::BorderWidths SAL_CALL SfxBaseController::getBorder() +{ + frame::BorderWidths aResult; + + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell ) + { + SvBorder aBorder = m_pData->m_pViewShell->GetBorderPixel(); + aResult.Left = aBorder.Left(); + aResult.Top = aBorder.Top(); + aResult.Right = aBorder.Right(); + aResult.Bottom = aBorder.Bottom(); + } + + return aResult; +} + +void SAL_CALL SfxBaseController::addBorderResizeListener( const Reference< frame::XBorderResizeListener >& xListener ) +{ + m_pData->m_aListenerContainer.addInterface( cppu::UnoType<frame::XBorderResizeListener>::get(), + xListener ); +} + +void SAL_CALL SfxBaseController::removeBorderResizeListener( const Reference< frame::XBorderResizeListener >& xListener ) +{ + m_pData->m_aListenerContainer.removeInterface( cppu::UnoType<frame::XBorderResizeListener>::get(), + xListener ); +} + +awt::Rectangle SAL_CALL SfxBaseController::queryBorderedArea( const awt::Rectangle& aPreliminaryRectangle ) +{ + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell ) + { + tools::Rectangle aTmpRect = VCLRectangle( aPreliminaryRectangle ); + m_pData->m_pViewShell->QueryObjAreaPixel( aTmpRect ); + return AWTRectangle( aTmpRect ); + } + + return aPreliminaryRectangle; +} + +void SfxBaseController::BorderWidthsChanged_Impl() +{ + ::comphelper::OInterfaceContainerHelper2* pContainer = m_pData->m_aListenerContainer.getContainer( + cppu::UnoType<frame::XBorderResizeListener>::get()); + if ( !pContainer ) + return; + + frame::BorderWidths aBWidths = getBorder(); + Reference< uno::XInterface > xThis( getXWeak() ); + + ::comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + { + try + { + static_cast<frame::XBorderResizeListener*>(pIterator.next())->borderWidthsChanged( xThis, aBWidths ); + } + catch (const RuntimeException&) + { + pIterator.remove(); + } + } +} + + +// SfxBaseController -> XComponent + + +void SAL_CALL SfxBaseController::dispose() +{ + SolarMutexGuard aGuard; + Reference< XController > xKeepAlive( this ); + m_pData->m_bDisposing = true ; + + lang::EventObject aEventObject; + aEventObject.Source = *this ; + m_pData->m_aListenerContainer.disposeAndClear( aEventObject ) ; + + if ( m_pData->m_pController && m_pData->m_pController->getFrame().is() ) + m_pData->m_pController->getFrame()->removeFrameActionListener( m_pData->m_xListener ) ; + + if ( !m_pData->m_pViewShell ) + return; + + SfxViewFrame& rFrame = m_pData->m_pViewShell->GetViewFrame() ; + if (rFrame.GetViewShell() == m_pData->m_pViewShell ) + rFrame.GetFrame().SetIsClosing_Impl(); + m_pData->m_pViewShell->DisconnectAllClients(); + + lang::EventObject aObject; + aObject.Source = *this ; + + SfxObjectShell* pDoc = rFrame.GetObjectShell() ; + SfxViewFrame *pView = SfxViewFrame::GetFirst(pDoc); + while( pView ) + { + // if there is another ViewFrame or currently the ViewShell in my ViewFrame is switched (PagePreview) + if ( pView != &rFrame || pView->GetViewShell() != m_pData->m_pViewShell ) + break; + pView = SfxViewFrame::GetNext( *pView, pDoc ); + } + + SfxGetpApp()->NotifyEvent( SfxViewEventHint(SfxEventHintId::CloseView, GlobalEventConfig::GetEventName( GlobalEventId::CLOSEVIEW ), pDoc, Reference< frame::XController2 >( this ) ) ); + if ( !pView ) + SfxGetpApp()->NotifyEvent( SfxEventHint(SfxEventHintId::CloseDoc, GlobalEventConfig::GetEventName( GlobalEventId::CLOSEDOC ), pDoc) ); + + Reference< frame::XModel > xModel = pDoc->GetModel(); + Reference < util::XCloseable > xCloseable( xModel, uno::UNO_QUERY ); + if ( xModel.is() ) + { + xModel->disconnectController( this ); + if ( xCloseable.is() ) + xCloseable->removeCloseListener( m_pData->m_xCloseListener ); + } + + Reference < frame::XFrame > aXFrame; + attachFrame( aXFrame ); + + m_pData->m_xListener->disposing( aObject ); + SfxViewShell *pShell = m_pData->m_pViewShell; + m_pData->m_pViewShell = nullptr; + if (rFrame.GetViewShell() == pShell) + { + // Enter registrations only allowed if we are the owner! + if ( rFrame.GetFrame().OwnsBindings_Impl() ) + rFrame.GetBindings().ENTERREGISTRATIONS(); + rFrame.GetFrame().SetFrameInterface_Impl( aXFrame ); + rFrame.GetFrame().DoClose_Impl(); + } +} + + +// SfxBaseController -> XComponent + + +void SAL_CALL SfxBaseController::addEventListener( const Reference< lang::XEventListener >& aListener ) +{ + m_pData->m_aListenerContainer.addInterface( cppu::UnoType<lang::XEventListener>::get(), aListener ); +} + + +// SfxBaseController -> XComponent + + +void SAL_CALL SfxBaseController::removeEventListener( const Reference< lang::XEventListener >& aListener ) +{ + m_pData->m_aListenerContainer.removeInterface( cppu::UnoType<lang::XEventListener>::get(), aListener ); +} + +void SfxBaseController::ReleaseShell_Impl() +{ + SolarMutexGuard aGuard; + if ( !m_pData->m_pViewShell ) + return; + + SfxObjectShell* pDoc = m_pData->m_pViewShell->GetObjectShell() ; + Reference< frame::XModel > xModel = pDoc->GetModel(); + Reference < util::XCloseable > xCloseable( xModel, uno::UNO_QUERY ); + if ( xModel.is() ) + { + xModel->disconnectController( this ); + if ( xCloseable.is() ) + xCloseable->removeCloseListener( m_pData->m_xCloseListener ); + } + m_pData->m_pViewShell = nullptr; + + Reference < frame::XFrame > aXFrame; + attachFrame( aXFrame ); +} + +void SfxBaseController::CopyLokViewCallbackFromFrameCreator() +{ + if (!m_pData->m_pViewShell) + return; + SfxLokCallbackInterface* pCallback = nullptr; + if (m_pData->m_xFrame) + if (auto xCreator = m_pData->m_xFrame->getCreator()) + if (auto parentVS = SfxViewShell::Get(xCreator->getController())) + pCallback = parentVS->getLibreOfficeKitViewCallback(); + m_pData->m_pViewShell->setLibreOfficeKitViewCallback(pCallback); +} + +SfxViewShell* SfxBaseController::GetViewShell_Impl() const +{ + return m_pData->m_pViewShell; +} + +Reference< task::XStatusIndicator > SAL_CALL SfxBaseController::getStatusIndicator( ) +{ + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell && !m_pData->m_xIndicator.is() ) + m_pData->m_xIndicator = new SfxStatusIndicator( this, m_pData->m_pViewShell->GetViewFrame().GetFrame().GetWorkWindow_Impl() ); + return m_pData->m_xIndicator; +} + +void SAL_CALL SfxBaseController::registerContextMenuInterceptor( const Reference< ui::XContextMenuInterceptor >& xInterceptor ) + +{ + m_pData->m_aInterceptorContainer.addInterface( xInterceptor ); + + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell ) + m_pData->m_pViewShell->AddContextMenuInterceptor_Impl( xInterceptor ); +} + +void SAL_CALL SfxBaseController::releaseContextMenuInterceptor( const Reference< ui::XContextMenuInterceptor >& xInterceptor ) + +{ + m_pData->m_aInterceptorContainer.removeInterface( xInterceptor ); + + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell ) + m_pData->m_pViewShell->RemoveContextMenuInterceptor_Impl( xInterceptor ); +} + +void SAL_CALL SfxBaseController::addKeyHandler( const Reference< awt::XKeyHandler >& xHandler ) +{ + SolarMutexGuard aGuard; + m_pData->m_aUserInputInterception.addKeyHandler( xHandler ); +} + +void SAL_CALL SfxBaseController::removeKeyHandler( const Reference< awt::XKeyHandler >& xHandler ) +{ + SolarMutexGuard aGuard; + m_pData->m_aUserInputInterception.removeKeyHandler( xHandler ); +} + +void SAL_CALL SfxBaseController::addMouseClickHandler( const Reference< awt::XMouseClickHandler >& xHandler ) +{ + SolarMutexGuard aGuard; + m_pData->m_aUserInputInterception.addMouseClickHandler( xHandler ); +} + +void SAL_CALL SfxBaseController::removeMouseClickHandler( const Reference< awt::XMouseClickHandler >& xHandler ) +{ + SolarMutexGuard aGuard; + m_pData->m_aUserInputInterception.removeMouseClickHandler( xHandler ); +} + +uno::Sequence< sal_Int16 > SAL_CALL SfxBaseController::getSupportedCommandGroups() +{ + SolarMutexGuard aGuard; + + std::vector< sal_Int16 > aGroupList; + SfxViewFrame* pViewFrame = m_pData->m_pViewShell ? m_pData->m_pViewShell->GetFrame() : nullptr; + SfxSlotPool* pSlotPool = pViewFrame ? &SfxSlotPool::GetSlotPool(pViewFrame) : &SFX_SLOTPOOL(); + const SfxSlotMode nMode( SfxSlotMode::TOOLBOXCONFIG|SfxSlotMode::ACCELCONFIG|SfxSlotMode::MENUCONFIG ); + + // Select Group ( Group 0 is internal ) + for ( sal_uInt16 i=0; i<pSlotPool->GetGroupCount(); i++ ) + { + pSlotPool->SeekGroup( i ); + const SfxSlot* pSfxSlot = pSlotPool->FirstSlot(); + while ( pSfxSlot ) + { + if ( pSfxSlot->GetMode() & nMode ) + { + sal_Int16 nCommandGroup = MapGroupIDToCommandGroup( pSfxSlot->GetGroupId() ); + aGroupList.push_back( nCommandGroup ); + break; + } + pSfxSlot = pSlotPool->NextSlot(); + } + } + + return comphelper::containerToSequence( aGroupList ); +} + +uno::Sequence< frame::DispatchInformation > SAL_CALL SfxBaseController::getConfigurableDispatchInformation( sal_Int16 nCmdGroup ) +{ + std::vector< frame::DispatchInformation > aCmdVector; + + SolarMutexGuard aGuard; + if ( m_pData->m_pViewShell ) + { + const SfxSlotMode nMode( SfxSlotMode::TOOLBOXCONFIG|SfxSlotMode::ACCELCONFIG|SfxSlotMode::MENUCONFIG ); + + SfxViewFrame* pViewFrame( m_pData->m_pViewShell->GetFrame() ); + SfxSlotPool* pSlotPool + = pViewFrame ? &SfxSlotPool::GetSlotPool(pViewFrame) : &SFX_SLOTPOOL(); + for ( sal_uInt16 i=0; i<pSlotPool->GetGroupCount(); i++ ) + { + pSlotPool->SeekGroup( i ); + const SfxSlot* pSfxSlot = pSlotPool->FirstSlot(); + if ( pSfxSlot ) + { + sal_Int16 nCommandGroup = MapGroupIDToCommandGroup( pSfxSlot->GetGroupId() ); + if ( nCommandGroup == nCmdGroup ) + { + while ( pSfxSlot ) + { + if ( pSfxSlot->GetMode() & nMode ) + { + frame::DispatchInformation aCmdInfo; + aCmdInfo.Command = pSfxSlot->GetCommand(); + aCmdInfo.GroupId = nCommandGroup; + aCmdVector.push_back( aCmdInfo ); + } + pSfxSlot = pSlotPool->NextSlot(); + } + } + } + } + } + + return comphelper::containerToSequence( aCmdVector ); +} + +bool SfxBaseController::HandleEvent_Impl( NotifyEvent const & rEvent ) +{ + return m_pData->m_aUserInputInterception.handleNotifyEvent( rEvent ); +} + +bool SfxBaseController::HasKeyListeners_Impl() const +{ + return m_pData->m_aUserInputInterception.hasKeyHandlers(); +} + +bool SfxBaseController::HasMouseClickListeners_Impl() const +{ + return m_pData->m_aUserInputInterception.hasMouseClickListeners(); +} + +void SfxBaseController::ConnectSfxFrame_Impl( const ConnectSfxFrame i_eConnect ) +{ + ENSURE_OR_THROW( m_pData->m_pViewShell, "not to be called without a view shell" ); + SfxViewFrame* pViewFrame = m_pData->m_pViewShell->GetFrame(); + ENSURE_OR_THROW( pViewFrame, "a view shell without a view frame is pretty pathological" ); + + const bool bConnect = ( i_eConnect != E_DISCONNECT ); + + // disable window and dispatcher + pViewFrame->Enable( bConnect ); + pViewFrame->GetDispatcher()->Lock( !bConnect ); + + if ( bConnect ) + { + if ( i_eConnect == E_CONNECT ) + { + if ( ( m_pData->m_pViewShell->GetObjectShell() != nullptr ) + && ( m_pData->m_pViewShell->GetObjectShell()->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + ) + { + SfxViewFrame& rViewFrm = m_pData->m_pViewShell->GetViewFrame(); + if ( !rViewFrm.GetFrame().IsInPlace() ) + { + // for outplace embedded objects, we want the layout manager to keep the content window + // size constant, if possible + try + { + Reference< beans::XPropertySet > xFrameProps( m_pData->m_xFrame, uno::UNO_QUERY_THROW ); + Reference< beans::XPropertySet > xLayouterProps( + xFrameProps->getPropertyValue("LayoutManager"), uno::UNO_QUERY_THROW ); + xLayouterProps->setPropertyValue("PreserveContentSize", uno::Any( true ) ); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + } + } + } + + // upon DISCONNECT, we did *not* pop the shells from the stack (this is done elsewhere), so upon + // RECONNECT, we're not allowed to push them + if ( i_eConnect != E_RECONNECT ) + { + pViewFrame->GetDispatcher()->Push( *m_pData->m_pViewShell ); + m_pData->m_pViewShell->PushSubShells_Impl(); + pViewFrame->GetDispatcher()->Flush(); + } + + vcl::Window* pEditWin = m_pData->m_pViewShell->GetWindow(); + if ( pEditWin ) + pEditWin->Show(); + + if ( SfxViewFrame::Current() == pViewFrame ) + pViewFrame->GetDispatcher()->Update_Impl( true ); + + vcl::Window* pFrameWin = &pViewFrame->GetWindow(); + if ( pFrameWin != &pViewFrame->GetFrame().GetWindow() ) + pFrameWin->Show(); + + if ( i_eConnect == E_CONNECT ) + { + css::uno::Reference<css::frame::XModel3> xModel(getModel(), css::uno::UNO_QUERY_THROW); + const sal_Int16 nPluginMode = ::comphelper::NamedValueCollection::getOrDefault( xModel->getArgs2( { "PluginMode" } ), u"PluginMode", sal_Int16( 0 ) ); + const bool bHasPluginMode = ( nPluginMode != 0 ); + + SfxFrame& rFrame = pViewFrame->GetFrame(); + SfxObjectShell& rDoc = *m_pData->m_pViewShell->GetObjectShell(); + if ( !rFrame.IsMarkedHidden_Impl() ) + { + if ( rDoc.IsHelpDocument() || ( nPluginMode == 2 ) ) + pViewFrame->GetDispatcher()->HideUI(); + else + pViewFrame->GetDispatcher()->HideUI( false ); + + if ( rFrame.IsInPlace() ) + pViewFrame->LockAdjustPosSizePixel(); + + if ( nPluginMode == 3 ) + rFrame.GetWorkWindow_Impl()->SetInternalDockingAllowed( false ); + + if ( !rFrame.IsInPlace() ) + pViewFrame->GetDispatcher()->Update_Impl(); + pViewFrame->Show(); + rFrame.GetWindow().Show(); + if ( !rFrame.IsInPlace() || ( nPluginMode == 3 ) ) + pViewFrame->MakeActive_Impl( rFrame.GetFrameInterface()->isActive() ); + + if ( rFrame.IsInPlace() ) + { + pViewFrame->UnlockAdjustPosSizePixel(); + // force resize for OLE server to fix layout problems of writer and math + // see i53651 + if ( nPluginMode == 3 ) + pViewFrame->Resize( true ); + } + } + else + { + DBG_ASSERT( !rFrame.IsInPlace() && !bHasPluginMode, "Special modes not compatible with hidden mode!" ); + rFrame.GetWindow().Show(); + } + + // UpdateTitle now, hidden TopFrames have otherwise no Name! + pViewFrame->UpdateTitle(); + + if ( !rFrame.IsInPlace() ) + pViewFrame->Resize( true ); + + ::comphelper::NamedValueCollection aViewArgs(getCreationArguments()); + + // sometimes we want to avoid adding to the recent documents + bool bAllowPickListEntry = aViewArgs.getOrDefault("PickListEntry", true); + m_pData->m_pViewShell->GetObjectShell()->AvoidRecentDocs(!bAllowPickListEntry); + + // if there's a JumpMark given, then, well, jump to it + const OUString sJumpMark = aViewArgs.getOrDefault( "JumpMark", OUString() ); + const bool bHasJumpMark = !sJumpMark.isEmpty(); + OSL_ENSURE( ( !m_pData->m_pViewShell->GetObjectShell()->IsLoading() ) + || ( sJumpMark.isEmpty() ), + "SfxBaseController::ConnectSfxFrame_Impl: so this code wasn't dead?" ); + // Before CWS autorecovery, there was code which postponed jumping to the Mark to a later time + // (SfxObjectShell::PositionView_Impl), but it seems this branch was never used, since this method + // here is never called before the load process finished. At least not with a non-empty jump mark + if ( !sJumpMark.isEmpty() ) + m_pData->m_pViewShell->JumpToMark( sJumpMark ); + + // if no plugin mode and no jump mark was supplied, check whether the document itself can provide view data, and + // if so, forward it to the view/shell. + if ( !bHasPluginMode && !bHasJumpMark ) + { + // Note that this might not be the ideal place here. Restoring view data should, IMO, be the + // responsibility of the loader, not an implementation detail buried here deep within the controller's + // implementation. + // What I think should be done to replace the below code: + // - change SfxBaseController::restoreViewData to also accept a PropertyValue[] (it currently accepts + // a string only), and forward it to its ViewShell's ReadUserDataSequence + // - change the frame loader so that when a new document is loaded (as opposed to an existing + // document being loaded into a new frame), the model's view data is examine the very same + // way as below, and the proper view data is set via XController::restoreViewData + // - extend SfxViewFrame::SwitchToViewShell_Impl. Currently, it cares for the case where a non-PrintPreview + // view is exchanged, and sets the old view's data at the model. It should also care for the other + // way, were the PrintPreview view is left: in this case, the new view should also be initialized + // with the model's view data + try + { + Reference< XViewDataSupplier > xViewDataSupplier( getModel(), UNO_QUERY_THROW ); + Reference< XIndexAccess > xViewData( xViewDataSupplier->getViewData() ); + + // find the view data item whose ViewId matches the ID of the view we're just connecting to + const SfxObjectFactory& rDocFactory( rDoc.GetFactory() ); + const sal_Int32 nCount = xViewData.is() ? xViewData->getCount() : 0; + sal_Int32 nViewDataIndex = 0; + for ( sal_Int32 i=0; i<nCount; ++i ) + { + const ::comphelper::NamedValueCollection aViewData( xViewData->getByIndex(i) ); + OUString sViewId( aViewData.getOrDefault( "ViewId", OUString() ) ); + if ( sViewId.isEmpty() ) + continue; + + const SfxViewFactory* pViewFactory = rDocFactory.GetViewFactoryByViewName( sViewId ); + if ( pViewFactory == nullptr ) + continue; + + if ( pViewFactory->GetOrdinal() == pViewFrame->GetCurViewId() ) + { + nViewDataIndex = i; + break; + } + } + if (nViewDataIndex < nCount || !xViewData.is()) + { + Sequence< PropertyValue > aViewData; + if (xViewData.is()) + { + OSL_VERIFY(xViewData->getByIndex(nViewDataIndex) >>= aViewData); + } + if (aViewData.hasElements() || !xViewData.is()) + { + // Tolerate empty xViewData, ReadUserDataSequence() has side effects. + m_pData->m_pViewShell->ReadUserDataSequence( aViewData ); + } + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + } + } + } + + // invalidate slot corresponding to the view shell + const sal_uInt16 nViewNo = m_pData->m_pViewShell->GetObjectShell()->GetFactory().GetViewNo_Impl( pViewFrame->GetCurViewId(), USHRT_MAX ); + DBG_ASSERT( nViewNo != USHRT_MAX, "view shell id not found" ); + if ( nViewNo != USHRT_MAX ) + pViewFrame->GetBindings().Invalidate( nViewNo + SID_VIEWSHELL0 ); +} + +void SfxBaseController::ShowInfoBars( ) +{ + if ( !m_pData->m_pViewShell ) + return; + + // CMIS verifications + Reference< document::XCmisDocument > xCmisDoc( m_pData->m_pViewShell->GetObjectShell()->GetModel(), uno::UNO_QUERY ); + if ( !xCmisDoc.is( ) || !xCmisDoc->canCheckOut( ) ) + return; + + const uno::Sequence< document::CmisProperty> aCmisProperties = xCmisDoc->getCmisProperties( ); + + if ( !(xCmisDoc->isVersionable( ) && aCmisProperties.hasElements( )) ) + return; + + // Loop over the CMIS Properties to find cmis:isVersionSeriesCheckedOut + // and find if it is a Google Drive file. + bool bIsGoogleFile = false; + bool bCheckedOut = false; + for ( const auto& rCmisProp : aCmisProperties ) + { + if ( rCmisProp.Id == "cmis:isVersionSeriesCheckedOut" ) { + uno::Sequence< sal_Bool > bTmp; + rCmisProp.Value >>= bTmp; + bCheckedOut = bTmp[0]; + } + // if it is a Google Drive file, we don't need the checkout bar, + // still need the checkout feature for the version dialog. + if ( rCmisProp.Name == "title" ) + bIsGoogleFile = true; + } + + if ( bCheckedOut || bIsGoogleFile ) + return; + + // Get the Frame and show the InfoBar if not checked out + SfxViewFrame* pViewFrame = m_pData->m_pViewShell->GetFrame(); + auto pInfoBar = pViewFrame->AppendInfoBar("checkout", "", SfxResId(STR_NONCHECKEDOUT_DOCUMENT), + InfobarType::WARNING); + if (pInfoBar) + { + weld::Button &rBtn = pInfoBar->addButton(); + rBtn.set_label(SfxResId(STR_CHECKOUT)); + rBtn.connect_clicked(LINK(this, SfxBaseController, CheckOutHandler)); + } +} + +IMPL_LINK_NOARG ( SfxBaseController, CheckOutHandler, weld::Button&, void ) +{ + if ( m_pData->m_pViewShell ) + m_pData->m_pViewShell->GetObjectShell()->CheckOut( ); +} + + +Reference< frame::XTitle > SfxBaseController::impl_getTitleHelper () +{ + SolarMutexGuard aGuard; + + if ( ! m_pData->m_xTitleHelper.is ()) + { + Reference< frame::XModel > xModel = getModel (); + Reference< frame::XUntitledNumbers > xUntitledProvider(xModel , uno::UNO_QUERY ); + + m_pData->m_xTitleHelper = new ::framework::TitleHelper(::comphelper::getProcessComponentContext(), + Reference< frame::XController >(this), xUntitledProvider); + } + + return m_pData->m_xTitleHelper; +} + + +// frame::XTitle +OUString SAL_CALL SfxBaseController::getTitle() +{ + return impl_getTitleHelper()->getTitle (); +} + + +// frame::XTitle +void SAL_CALL SfxBaseController::setTitle(const OUString& sTitle) +{ + impl_getTitleHelper()->setTitle (sTitle); +} + + +// frame::XTitleChangeBroadcaster +void SAL_CALL SfxBaseController::addTitleChangeListener(const Reference< frame::XTitleChangeListener >& xListener) +{ + Reference< frame::XTitleChangeBroadcaster > xBroadcaster(impl_getTitleHelper(), uno::UNO_QUERY); + if (xBroadcaster.is ()) + xBroadcaster->addTitleChangeListener (xListener); +} + + +// frame::XTitleChangeBroadcaster +void SAL_CALL SfxBaseController::removeTitleChangeListener(const Reference< frame::XTitleChangeListener >& xListener) +{ + Reference< frame::XTitleChangeBroadcaster > xBroadcaster(impl_getTitleHelper(), uno::UNO_QUERY); + if (xBroadcaster.is ()) + xBroadcaster->removeTitleChangeListener (xListener); +} + +void SfxBaseController::initialize( const css::uno::Sequence< css::uno::Any >& /*aArguments*/ ) +{ +} + +void SAL_CALL SfxBaseController::appendInfobar(const OUString& sId, const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, + sal_Int32 aInfobarType, + const Sequence<StringPair>& actionButtons, + sal_Bool bShowCloseButton) +{ + SolarMutexGuard aGuard; + + if (aInfobarType < static_cast<sal_Int32>(InfobarType::INFO) + || aInfobarType > static_cast<sal_Int32>(InfobarType::DANGER)) + throw lang::IllegalArgumentException("Undefined InfobarType: " + + OUString::number(aInfobarType), + getXWeak(), 0); + SfxViewFrame* pViewFrame = m_pData->m_pViewShell->GetFrame(); + if (pViewFrame->HasInfoBarWithID(sId)) + throw lang::IllegalArgumentException("Infobar with ID '" + sId + "' already existing.", + getXWeak(), 0); + + auto pInfoBar + = pViewFrame->AppendInfoBar(sId, sPrimaryMessage, sSecondaryMessage, + static_cast<InfobarType>(aInfobarType), bShowCloseButton); + if (!pInfoBar) + throw uno::RuntimeException("Could not create Infobar"); + + for (const StringPair & actionButton : std::as_const(actionButtons)) + { + if (actionButton.First.isEmpty() || actionButton.Second.isEmpty()) + continue; + weld::Button& rBtn = pInfoBar->addButton(&actionButton.Second); + rBtn.set_label(actionButton.First); + } +} + +void SAL_CALL SfxBaseController::updateInfobar(const OUString& sId, const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, + sal_Int32 aInfobarType) +{ + SolarMutexGuard aGuard; + + if (aInfobarType < static_cast<sal_Int32>(InfobarType::INFO) + || aInfobarType > static_cast<sal_Int32>(InfobarType::DANGER)) + throw lang::IllegalArgumentException("Undefined InfobarType: " + + OUString::number(aInfobarType), + getXWeak(), 0); + SfxViewFrame* pViewFrame = m_pData->m_pViewShell->GetFrame(); + if (!pViewFrame->HasInfoBarWithID(sId)) + throw css::container::NoSuchElementException("Infobar with ID '" + sId + "' not found."); + + pViewFrame->UpdateInfoBar(sId, sPrimaryMessage, sSecondaryMessage, + static_cast<InfobarType>(aInfobarType)); +} + +void SAL_CALL SfxBaseController::removeInfobar(const OUString& sId) +{ + SolarMutexGuard aGuard; + + SfxViewFrame* pViewFrame = m_pData->m_pViewShell->GetFrame(); + if (!pViewFrame->HasInfoBarWithID(sId)) + throw css::container::NoSuchElementException("Infobar with ID '" + sId + "' not found."); + pViewFrame->RemoveInfoBar(sId); +} + +sal_Bool SAL_CALL SfxBaseController::hasInfobar(const OUString& sId) +{ + SolarMutexGuard aGuard; + SfxViewFrame* pViewFrame = m_pData->m_pViewShell->GetFrame(); + return pViewFrame->HasInfoBarWithID(sId); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/userinputinterception.cxx b/sfx2/source/view/userinputinterception.cxx new file mode 100644 index 0000000000..f609652ea7 --- /dev/null +++ b/sfx2/source/view/userinputinterception.cxx @@ -0,0 +1,263 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <sfx2/userinputinterception.hxx> + +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> +#include <com/sun/star/awt/InputEvent.hpp> +#include <com/sun/star/awt/KeyEvent.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/XKeyHandler.hpp> +#include <com/sun/star/awt/XMouseClickHandler.hpp> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/uno/XInterface.hpp> + +#include <comphelper/interfacecontainer3.hxx> +#include <cppuhelper/weak.hxx> +#include <vcl/event.hxx> +#include <vcl/window.hxx> +#include <osl/diagnose.h> + +namespace sfx2 +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::awt::MouseEvent; + using ::com::sun::star::awt::KeyEvent; + using ::com::sun::star::awt::InputEvent; + using ::com::sun::star::awt::XKeyHandler; + using ::com::sun::star::awt::XMouseClickHandler; + using ::com::sun::star::lang::DisposedException; + + namespace MouseButton = ::com::sun::star::awt::MouseButton; + namespace KeyModifier = ::com::sun::star::awt::KeyModifier; + + struct UserInputInterception_Data + { + public: + ::cppu::OWeakObject& m_rControllerImpl; + ::comphelper::OInterfaceContainerHelper3<XKeyHandler> m_aKeyHandlers; + ::comphelper::OInterfaceContainerHelper3<XMouseClickHandler> m_aMouseClickHandlers; + + public: + UserInputInterception_Data( ::cppu::OWeakObject& _rControllerImpl, ::osl::Mutex& _rMutex ) + :m_rControllerImpl( _rControllerImpl ) + ,m_aKeyHandlers( _rMutex ) + ,m_aMouseClickHandlers( _rMutex ) + { + } + }; + + namespace + { + template< class VCLEVENT > + void lcl_initModifiers( InputEvent& _rEvent, const VCLEVENT& _rVclEvent ) + { + _rEvent.Modifiers = 0; + + if ( _rVclEvent.IsShift() ) + _rEvent.Modifiers |= KeyModifier::SHIFT; + if ( _rVclEvent.IsMod1() ) + _rEvent.Modifiers |= KeyModifier::MOD1; + if ( _rVclEvent.IsMod2() ) + _rEvent.Modifiers |= KeyModifier::MOD2; + if ( _rVclEvent.IsMod3() ) + _rEvent.Modifiers |= KeyModifier::MOD3; + } + + void lcl_initKeyEvent( KeyEvent& rEvent, const ::KeyEvent& rEvt ) + { + lcl_initModifiers( rEvent, rEvt.GetKeyCode() ); + + rEvent.KeyCode = rEvt.GetKeyCode().GetCode(); + rEvent.KeyChar = rEvt.GetCharCode(); + rEvent.KeyFunc = sal::static_int_cast< sal_Int16 >( rEvt.GetKeyCode().GetFunction()); + } + + void lcl_initMouseEvent( MouseEvent& rEvent, const ::MouseEvent& rEvt ) + { + lcl_initModifiers( rEvent, rEvt ); + + rEvent.Buttons = 0; + if ( rEvt.IsLeft() ) + rEvent.Buttons |= MouseButton::LEFT; + if ( rEvt.IsRight() ) + rEvent.Buttons |= MouseButton::RIGHT; + if ( rEvt.IsMiddle() ) + rEvent.Buttons |= MouseButton::MIDDLE; + + rEvent.X = rEvt.GetPosPixel().X(); + rEvent.Y = rEvt.GetPosPixel().Y(); + rEvent.ClickCount = rEvt.GetClicks(); + rEvent.PopupTrigger = false; + } + + } + + + //= UserInputInterception + + + UserInputInterception::UserInputInterception( ::cppu::OWeakObject& _rControllerImpl, ::osl::Mutex& _rMutex ) + :m_pData( new UserInputInterception_Data( _rControllerImpl, _rMutex ) ) + { + } + + + UserInputInterception::~UserInputInterception() + { + } + + + void UserInputInterception::addKeyHandler( const Reference< XKeyHandler >& _rxHandler ) + { + if ( _rxHandler.is() ) + m_pData->m_aKeyHandlers.addInterface( _rxHandler ); + } + + + void UserInputInterception::removeKeyHandler( const Reference< XKeyHandler >& _rxHandler ) + { + m_pData->m_aKeyHandlers.removeInterface( _rxHandler ); + } + + + void UserInputInterception::addMouseClickHandler( const Reference< XMouseClickHandler >& _rxHandler ) + { + if ( _rxHandler.is() ) + m_pData->m_aMouseClickHandlers.addInterface( _rxHandler ); + } + + + void UserInputInterception::removeMouseClickHandler( const Reference< XMouseClickHandler >& _rxHandler ) + { + m_pData->m_aMouseClickHandlers.removeInterface( _rxHandler ); + } + + + bool UserInputInterception::hasKeyHandlers() const + { + return m_pData->m_aKeyHandlers.getLength() > 0; + } + + + bool UserInputInterception::hasMouseClickListeners() const + { + return m_pData->m_aMouseClickHandlers.getLength() > 0; + } + + + bool UserInputInterception::handleNotifyEvent( const NotifyEvent& _rEvent ) + { + Reference < XInterface > xHoldAlive( m_pData->m_rControllerImpl ); + + NotifyEventType nType = _rEvent.GetType(); + bool bHandled = false; + + switch ( nType ) + { + case NotifyEventType::KEYINPUT: + case NotifyEventType::KEYUP: + { + KeyEvent aEvent; + lcl_initKeyEvent( aEvent, *_rEvent.GetKeyEvent() ); + if ( _rEvent.GetWindow() ) + aEvent.Source = _rEvent.GetWindow()->GetComponentInterface(); + + ::comphelper::OInterfaceIteratorHelper3 aIterator( m_pData->m_aKeyHandlers ); + while ( aIterator.hasMoreElements() ) + { + Reference< XKeyHandler > xHandler( aIterator.next() ); + try + { + if ( nType == NotifyEventType::KEYINPUT ) + bHandled = xHandler->keyPressed( aEvent ); + else + bHandled = xHandler->keyReleased( aEvent ); + } + catch( const DisposedException& e ) + { + if ( e.Context == xHandler ) + aIterator.remove(); + } + catch( const RuntimeException& ) + { + throw; + } + catch( const Exception& ) + { + } + } + } + break; + + case NotifyEventType::MOUSEBUTTONDOWN: + case NotifyEventType::MOUSEBUTTONUP: + { + MouseEvent aEvent; + lcl_initMouseEvent( aEvent, *_rEvent.GetMouseEvent() ); + if ( _rEvent.GetWindow() ) + aEvent.Source = _rEvent.GetWindow()->GetComponentInterface(); + + ::comphelper::OInterfaceIteratorHelper3 aIterator( m_pData->m_aMouseClickHandlers ); + while ( aIterator.hasMoreElements() ) + { + Reference< XMouseClickHandler > xHandler( aIterator.next() ); + try + { + if ( nType == NotifyEventType::MOUSEBUTTONDOWN ) + bHandled = xHandler->mousePressed( aEvent ); + else + bHandled = xHandler->mouseReleased( aEvent ); + } + catch( const DisposedException& e ) + { + if ( e.Context == xHandler ) + aIterator.remove(); + } + catch( const RuntimeException& ) + { + throw; + } + catch( const Exception& ) + { + } + } + } + break; + + default: + OSL_FAIL( "UserInputInterception::handleNotifyEvent: illegal event type!" ); + break; + } + + return bHandled; + } + + +} // namespace sfx2 + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/viewfac.cxx b/sfx2/source/view/viewfac.cxx new file mode 100644 index 0000000000..fbcb8c2c14 --- /dev/null +++ b/sfx2/source/view/viewfac.cxx @@ -0,0 +1,56 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sfx2/viewfac.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/viewsh.hxx> + +SfxViewShell *SfxViewFactory::CreateInstance(SfxViewFrame& rFrame, SfxViewShell *pOldSh) +{ + return (*fnCreate)(rFrame, pOldSh); +} + +OUString SfxViewFactory::GetLegacyViewName() const +{ + return "view" + OUString::number( sal_uInt16( GetOrdinal() ) ); +} + +OUString SfxViewFactory::GetAPIViewName() const +{ + if ( !m_sViewName.isEmpty() ) + return m_sViewName; + + if ( GetOrdinal() == SFX_INTERFACE_NONE ) + return "Default"; + + return GetLegacyViewName(); +} + +// CTOR / DTOR ----------------------------------------------------------- + +SfxViewFactory::SfxViewFactory( SfxViewCtor fnC, + SfxInterfaceId nOrdinal, const char* asciiViewName ): + fnCreate(fnC), + nOrd(nOrdinal), + m_sViewName( OUString::createFromAscii( asciiViewName ) ) +{ +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/viewfrm.cxx b/sfx2/source/view/viewfrm.cxx new file mode 100644 index 0000000000..fe0dc0adc7 --- /dev/null +++ b/sfx2/source/view/viewfrm.cxx @@ -0,0 +1,3706 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_feature_desktop.h> +#include <config_wasm_strip.h> + +#include <osl/file.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <sfx2/classificationhelper.hxx> +#include <sfx2/notebookbar/SfxNotebookBar.hxx> +#include <sfx2/pageids.hxx> +#include <com/sun/star/document/MacroExecMode.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/DispatchRecorder.hpp> +#include <com/sun/star/frame/DispatchRecorderSupplier.hpp> +#include <com/sun/star/frame/XLoadable.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/frame/XComponentLoader.hpp> +#include <com/sun/star/task/PasswordContainer.hpp> +#include <officecfg/Office/Common.hxx> +#include <officecfg/Setup.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/wrkwin.hxx> +#include <unotools/moduleoptions.hxx> +#include <svl/intitem.hxx> +#include <svl/visitem.hxx> +#include <svl/stritem.hxx> +#include <svl/eitem.hxx> +#include <svl/whiter.hxx> +#include <svl/undo.hxx> +#include <vcl/help.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> +#if !ENABLE_WASM_STRIP_PINGUSER +#include <unotools/VersionConfig.hxx> +#endif +#include <unotools/securityoptions.hxx> +#include <svtools/miscopt.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/frame/XFramesSupplier.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/frame/XDispatchRecorderSupplier.hpp> +#include <com/sun/star/document/UpdateDocMode.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrl.hpp> +#include <com/sun/star/document/XViewDataSupplier.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include <unotools/ucbhelper.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/docpasswordrequest.hxx> +#include <comphelper/docpasswordhelper.hxx> + +#include <com/sun/star/uno/Reference.h> + +#include <basic/basmgr.hxx> +#include <basic/sbmod.hxx> +#include <basic/sbmeth.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <framework/framelistanalyzer.hxx> + +#include <optional> + +#include <comphelper/sequenceashashmap.hxx> + +#include <commandpopup/CommandPopup.hxx> + +// Due to ViewFrame::Current +#include <appdata.hxx> +#include <sfx2/app.hxx> +#include <sfx2/objface.hxx> +#include <openflag.hxx> +#include <objshimp.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/request.hxx> +#include <sfx2/docfac.hxx> +#include <sfx2/ipclient.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/viewfac.hxx> +#include <sfx2/event.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/module.hxx> +#include <sfx2/sfxuno.hxx> +#include <sfx2/progress.hxx> +#include <sfx2/sidebar/Sidebar.hxx> +#include <workwin.hxx> +#include <sfx2/minfitem.hxx> +#include <sfx2/strings.hrc> +#include "impviewframe.hxx" +#include <vcl/commandinfoprovider.hxx> +#include <vcl/svapp.hxx> + +#define ShellClass_SfxViewFrame +#include <sfxslots.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using ::com::sun::star::awt::XWindow; +using ::com::sun::star::beans::PropertyValue; +using ::com::sun::star::document::XViewDataSupplier; +using ::com::sun::star::container::XIndexContainer; + +constexpr OUString CHANGES_STR = u"private:resource/toolbar/changes"_ustr; + +SFX_IMPL_SUPERCLASS_INTERFACE(SfxViewFrame,SfxShell) + +void SfxViewFrame::InitInterface_Impl() +{ + GetStaticInterface()->RegisterChildWindow(SID_BROWSER); + GetStaticInterface()->RegisterChildWindow(SID_RECORDING_FLOATWINDOW); +#if HAVE_FEATURE_DESKTOP + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_FULLSCREEN, SfxVisibilityFlags::FullScreen, ToolbarId::FullScreenToolbox); + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_APPLICATION, SfxVisibilityFlags::Standard, ToolbarId::EnvToolbox); +#endif +} + +namespace { +/// Asks the user if editing a read-only document is really wanted. +class SfxEditDocumentDialog : public weld::MessageDialogController +{ +public: + SfxEditDocumentDialog(weld::Widget* pParent); +}; + +SfxEditDocumentDialog::SfxEditDocumentDialog(weld::Widget* pParent) + : MessageDialogController(pParent, "sfx/ui/editdocumentdialog.ui", + "EditDocumentDialog") +{ +} + +class SfxQueryOpenAsTemplate +{ +private: + std::unique_ptr<weld::MessageDialog> m_xQueryBox; +public: + SfxQueryOpenAsTemplate(weld::Window* pParent, bool bAllowIgnoreLock, LockFileEntry& rLockData) + : m_xQueryBox(Application::CreateMessageDialog(pParent, VclMessageType::Question, + VclButtonsType::NONE, "")) + { + m_xQueryBox->add_button(SfxResId(STR_QUERY_OPENASTEMPLATE_OPENCOPY_BTN), RET_YES); + bAllowIgnoreLock + = bAllowIgnoreLock && officecfg::Office::Common::Misc::AllowOverrideLocking::get(); + if (bAllowIgnoreLock) + m_xQueryBox->add_button(SfxResId(STR_QUERY_OPENASTEMPLATE_OPEN_BTN), RET_IGNORE); + m_xQueryBox->add_button(GetStandardText( StandardButtonType::Cancel ), RET_CANCEL); + m_xQueryBox->set_primary_text(QueryString(bAllowIgnoreLock, rLockData)); + m_xQueryBox->set_default_response(RET_YES); + } + short run() { return m_xQueryBox->run(); } + +private: + static OUString QueryString(bool bAllowIgnoreLock, LockFileEntry& rLockData) + { + OUString sLockUserData; + if (!rLockData[LockFileComponent::OOOUSERNAME].isEmpty()) + sLockUserData = rLockData[LockFileComponent::OOOUSERNAME]; + else + sLockUserData = rLockData[LockFileComponent::SYSUSERNAME]; + + if (!sLockUserData.isEmpty() && !rLockData[LockFileComponent::EDITTIME].isEmpty()) + sLockUserData += " ( " + rLockData[LockFileComponent::EDITTIME] + " )"; + + if (!sLockUserData.isEmpty()) + sLockUserData = "\n\n" + sLockUserData + "\n"; + + const bool bUseLockStr = bAllowIgnoreLock || !sLockUserData.isEmpty(); + + OUString sMsg( + SfxResId(bUseLockStr ? STR_QUERY_OPENASTEMPLATE_LOCKED : STR_QUERY_OPENASTEMPLATE)); + + if (bAllowIgnoreLock) + sMsg += "\n\n" + SfxResId(STR_QUERY_OPENASTEMPLATE_ALLOW_IGNORE); + + return sMsg.replaceFirst("%LOCKINFO", sLockUserData); + } +}; + +bool AskPasswordToModify_Impl( const uno::Reference< task::XInteractionHandler >& xHandler, const OUString& aPath, const std::shared_ptr<const SfxFilter>& pFilter, sal_uInt32 nPasswordHash, const uno::Sequence< beans::PropertyValue >& aInfo ) +{ + // TODO/LATER: In future the info should replace the direct hash completely + bool bResult = ( !nPasswordHash && !aInfo.hasElements() ); + + SAL_WARN_IF( !(pFilter && ( pFilter->GetFilterFlags() & SfxFilterFlags::PASSWORDTOMODIFY )), "sfx.view", + "PasswordToModify feature is active for a filter that does not support it!"); + + if ( pFilter && xHandler.is() ) + { + bool bCancel = false; + bool bFirstTime = true; + + while ( !bResult && !bCancel ) + { + bool bMSType = !pFilter->IsOwnFormat(); + + ::rtl::Reference< ::comphelper::DocPasswordRequest > pPasswordRequest( + new ::comphelper::DocPasswordRequest( + bMSType ? ::comphelper::DocPasswordRequestType::MS : ::comphelper::DocPasswordRequestType::Standard, + bFirstTime ? css::task::PasswordRequestMode_PASSWORD_ENTER : css::task::PasswordRequestMode_PASSWORD_REENTER, + aPath, + true ) ); + + xHandler->handle( pPasswordRequest ); + + if ( pPasswordRequest->isPassword() ) + { + if ( aInfo.hasElements() ) + { + bResult = ::comphelper::DocPasswordHelper::IsModifyPasswordCorrect( pPasswordRequest->getPasswordToModify(), aInfo ); + } + else + { + // the binary format + bResult = ( SfxMedium::CreatePasswordToModifyHash( pPasswordRequest->getPasswordToModify(), pFilter->GetServiceName()=="com.sun.star.text.TextDocument" ) == nPasswordHash ); + } + } + else + bCancel = true; + + bFirstTime = false; + } + } + + return bResult; +} + +bool physObjIsOlder(INetURLObject const & aMedObj, INetURLObject const & aPhysObj) { + return ::utl::UCBContentHelper::IsYounger(aMedObj.GetMainURL( INetURLObject::DecodeMechanism::NONE), + aPhysObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); +} +} + +void SfxViewFrame::ExecReload_Impl( SfxRequest& rReq ) +{ + SfxObjectShell* pSh = GetObjectShell(); + switch ( rReq.GetSlot() ) + { + case SID_EDITDOC: + case SID_READONLYDOC: + { + // Due to Double occupancy in toolboxes (with or without Ctrl), + // it is also possible that the slot is enabled, but Ctrl-click + // despite this is not! + if( !pSh || !pSh->HasName() || !(pSh->Get_Impl()->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT )) + break; + + if (pSh->isEditDocLocked()) + break; + + // Only change read-only UI and remove info bar when we succeed + struct ReadOnlyUIGuard + { + SfxViewFrame* m_pFrame; + SfxObjectShell* m_pSh; + SfxMedium* m_pMed = nullptr; + bool m_bSetRO; + ReadOnlyUIGuard(SfxViewFrame* pFrame, SfxObjectShell* p_Sh) + : m_pFrame(pFrame), m_pSh(p_Sh), m_bSetRO(p_Sh->IsReadOnlyUI()) + {} + ~ReadOnlyUIGuard() COVERITY_NOEXCEPT_FALSE + { + if (m_bSetRO != m_pSh->IsReadOnlyUI()) + { + m_pSh->SetReadOnlyUI(m_bSetRO); + if (!m_bSetRO) + m_pFrame->RemoveInfoBar(u"readonly"); + if (m_pMed) + { + bool const isEnableSetModified(m_pSh->IsEnableSetModified()); + m_pSh->EnableSetModified(false); + // tdf#116066: DoSaveCompleted should be called after SetReadOnlyUI + m_pSh->DoSaveCompleted(m_pMed); + m_pSh->Broadcast(SfxHint(SfxHintId::ModeChanged)); + m_pSh->EnableSetModified(isEnableSetModified); + } + } + } + } aReadOnlyUIGuard(this, pSh); + + SfxMedium* pMed = pSh->GetMedium(); + + std::shared_ptr<std::recursive_mutex> pChkEditMutex = pMed->GetCheckEditableMutex(); + std::unique_lock<std::recursive_mutex> chkEditLock; + if (pChkEditMutex != nullptr) + chkEditLock = std::unique_lock<std::recursive_mutex>(*pChkEditMutex); + pMed->CancelCheckEditableEntry(); + + const SfxBoolItem* pItem = pMed->GetItemSet().GetItem(SID_VIEWONLY, false); + if ( pItem && pItem->GetValue() ) + { + SfxApplication* pApp = SfxGetpApp(); + SfxAllItemSet aSet( pApp->GetPool() ); + aSet.Put( SfxStringItem( SID_FILE_NAME, pMed->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE) ) ); + aSet.Put( SfxBoolItem( SID_TEMPLATE, true ) ); + aSet.Put( SfxStringItem( SID_TARGETNAME, "_blank" ) ); + const SfxStringItem* pReferer = pMed->GetItemSet().GetItem(SID_REFERER, false); + if ( pReferer ) + aSet.Put( *pReferer ); + const SfxInt16Item* pVersionItem = pMed->GetItemSet().GetItem(SID_VERSION, false); + if ( pVersionItem ) + aSet.Put( *pVersionItem ); + + if( pMed->GetFilter() ) + { + aSet.Put( SfxStringItem( SID_FILTER_NAME, pMed->GetFilter()->GetFilterName() ) ); + const SfxStringItem* pOptions = pMed->GetItemSet().GetItem(SID_FILE_FILTEROPTIONS, false); + if ( pOptions ) + aSet.Put( *pOptions ); + } + + GetDispatcher()->Execute( SID_OPENDOC, SfxCallMode::ASYNCHRON, aSet ); + return; + } + + StreamMode nOpenMode; + bool bNeedsReload = false; + bool bPasswordEntered = false; + if ( !pSh->IsReadOnly() ) + { + // Save and reload Readonly + if( pSh->IsModified() ) + { + if ( pSh->PrepareClose() ) + { + // the storing could let the medium be changed + pMed = pSh->GetMedium(); + bNeedsReload = true; + } + else + { + rReq.SetReturnValue( SfxBoolItem( rReq.GetSlot(), false ) ); + return; + } + } + nOpenMode = SFX_STREAM_READONLY; + aReadOnlyUIGuard.m_bSetRO = true; + } + else + { + if ( pSh->IsReadOnlyMedium() + && ( pSh->GetModifyPasswordHash() || pSh->GetModifyPasswordInfo().hasElements() ) + && !pSh->IsModifyPasswordEntered() ) + { + const OUString aDocumentName = INetURLObject( pMed->GetOrigURL() ).GetMainURL( INetURLObject::DecodeMechanism::WithCharset ); + if( !AskPasswordToModify_Impl( pMed->GetInteractionHandler(), aDocumentName, pMed->GetFilter(), pSh->GetModifyPasswordHash(), pSh->GetModifyPasswordInfo() ) ) + { + // this is a read-only document, if it has "Password to modify" + // the user should enter password before he can edit the document + rReq.SetReturnValue( SfxBoolItem( rReq.GetSlot(), false ) ); + return; + } + + pSh->SetModifyPasswordEntered(); + bPasswordEntered = true; + } + + nOpenMode = pSh->IsOriginallyReadOnlyMedium() ? SFX_STREAM_READONLY : SFX_STREAM_READWRITE; + aReadOnlyUIGuard.m_bSetRO = false; + + // if only the view was in the readonly mode then there is no need to do the reload + if ( !pSh->IsReadOnlyMedium() ) + { + // SetReadOnlyUI causes recomputation of window title, using + // open mode among other things, so call SetOpenMode before + // SetReadOnlyUI: + pMed->SetOpenMode( nOpenMode ); + return; + } + } + + if ( rReq.IsAPI() ) + { + // Control through API if r/w or r/o + const SfxBoolItem* pEditItem = rReq.GetArg<SfxBoolItem>(SID_EDITDOC); + if ( pEditItem ) + nOpenMode = pEditItem->GetValue() ? SFX_STREAM_READWRITE : SFX_STREAM_READONLY; + } + + // doing + + OUString sTemp; + osl::FileBase::getFileURLFromSystemPath( pMed->GetPhysicalName(), sTemp ); + INetURLObject aPhysObj( sTemp ); + const SfxInt16Item* pVersionItem = pMed->GetItemSet().GetItem(SID_VERSION, false); + + INetURLObject aMedObj( pMed->GetName() ); + + // -> tdf#82744 + // the logic below is following: + // if the document seems not to need to be reloaded + // and the physical name is different to the logical one, + // then on file system it can be checked that the copy is still newer than the original and no document reload is required. + // Did some semplification to enhance readability of the 'if' expression + // + // when the 'http/https' protocol is active, the bool bPhysObjIsYounger relies upon the getlastmodified Property of a WebDAV resource. + // Said property should be implemented, but sometimes it's not. + // implemented. On this case the reload activated here will not work properly. + // TODO: change the check age method for WebDAV to etag (entity-tag) property value, need some rethinking, since the + // etag tells that the cache representation (e.g. in LO) is different from the one on the server, + // but tells nothing about the age + // Details at this link: http://tools.ietf.org/html/rfc4918#section-15, section 15.7 + bool bIsWebDAV = aMedObj.isAnyKnownWebDAVScheme(); + + // tdf#118938 Reload the document when the user enters the editing password, + // even if the physical name isn't different to the logical name. + if ( ( !bNeedsReload && ( ( aMedObj.GetProtocol() == INetProtocol::File && + ( aMedObj.getFSysPath( FSysStyle::Detect ) != aPhysObj.getFSysPath( FSysStyle::Detect ) + || bPasswordEntered ) && + !physObjIsOlder(aMedObj, aPhysObj)) + || (bIsWebDAV && !physObjIsOlder(aMedObj, aPhysObj)) + || ( pMed->IsRemote() && !bIsWebDAV ) ) ) + || pVersionItem ) + // <- tdf#82744 + { + bool bOK = false; + bool bRetryIgnoringLock = false; + bool bOpenTemplate = false; + std::optional<bool> aOrigROVal; + if (!pVersionItem) + { + auto pRO = pMed->GetItemSet().GetItem<SfxBoolItem>(SID_DOC_READONLY, false); + if (pRO) + aOrigROVal = pRO->GetValue(); + } + do { + LockFileEntry aLockData; + if ( !pVersionItem ) + { + if (bRetryIgnoringLock) + pMed->ResetError(); + + bool bHasStorage = pMed->HasStorage_Impl(); + // switching edit mode could be possible without reload + if ( bHasStorage && pMed->GetStorage() == pSh->GetStorage() ) + { + // TODO/LATER: faster creation of copy + if ( !pSh->ConnectTmpStorage_Impl( pMed->GetStorage(), pMed ) ) + return; + } + + pMed->CloseAndRelease(); + pMed->SetOpenMode( nOpenMode ); + // We need to clear the SID_DOC_READONLY item from the set, to allow + // MediaDescriptor::impl_openStreamWithURL (called indirectly by + // SfxMedium::CompleteReOpen) to properly fill input stream of the + // descriptor, even when the file can't be open in read-write mode. + // Only then can following call to SfxMedium::LockOrigFileOnDemand + // return proper information about who has locked the file, to show + // in the SfxQueryOpenAsTemplate box below; otherwise it exits right + // after call to SfxMedium::GetMedium_Impl. This mimics what happens + // when the file is opened initially, when filter detection code also + // calls MediaDescriptor::impl_openStreamWithURL without the item set. + pMed->GetItemSet().ClearItem(SID_DOC_READONLY); + pMed->CompleteReOpen(); + pMed->GetItemSet().Put( + SfxBoolItem(SID_DOC_READONLY, !(nOpenMode & StreamMode::WRITE))); + if ( nOpenMode & StreamMode::WRITE ) + { + auto eResult = pMed->LockOrigFileOnDemand( + true, true, bRetryIgnoringLock, &aLockData); + bRetryIgnoringLock + = eResult == SfxMedium::LockFileResult::FailedLockFile; + } + + // LockOrigFileOnDemand might set the readonly flag itself, it should be set back + pMed->GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, !( nOpenMode & StreamMode::WRITE ) ) ); + + if ( !pMed->GetErrorCode() ) + bOK = true; + } + + if( !bOK ) + { + if (nOpenMode == SFX_STREAM_READWRITE && !rReq.IsAPI()) + { + // css::sdbcx::User offering to open it as a template + SfxQueryOpenAsTemplate aBox(GetWindow().GetFrameWeld(), + bRetryIgnoringLock, aLockData); + + short nUserAnswer = aBox.run(); + bOpenTemplate = RET_YES == nUserAnswer; + // Always reset this here to avoid infinite loop + bRetryIgnoringLock = RET_IGNORE == nUserAnswer; + if (RET_CANCEL == nUserAnswer) + pMed->AddToCheckEditableWorkerList(); + } + else + bRetryIgnoringLock = false; + } + } + while ( !bOK && bRetryIgnoringLock ); + + if( !bOK ) + { + ErrCodeMsg nErr = pMed->GetErrorCode(); + if ( pVersionItem ) + nErr = ERRCODE_IO_ACCESSDENIED; + else + { + pMed->ResetError(); + pMed->SetOpenMode( SFX_STREAM_READONLY ); + if (aOrigROVal) + pMed->GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, *aOrigROVal)); + else + pMed->GetItemSet().ClearItem(SID_DOC_READONLY); + pMed->ReOpen(); + pSh->DoSaveCompleted( pMed ); + } + + // Readonly document can not be switched to edit mode? + rReq.Done(); + + if ( nOpenMode == SFX_STREAM_READWRITE && !rReq.IsAPI() ) + { + if ( bOpenTemplate ) + { + SfxApplication* pApp = SfxGetpApp(); + SfxAllItemSet aSet( pApp->GetPool() ); + aSet.Put( SfxStringItem( SID_FILE_NAME, pMed->GetName() ) ); + const SfxStringItem* pReferer = pMed->GetItemSet().GetItem(SID_REFERER, false); + if ( pReferer ) + aSet.Put( *pReferer ); + aSet.Put( SfxBoolItem( SID_TEMPLATE, true ) ); + if ( pVersionItem ) + aSet.Put( *pVersionItem ); + + if( pMed->GetFilter() ) + { + aSet.Put( SfxStringItem( SID_FILTER_NAME, pMed->GetFilter()->GetFilterName() ) ); + const SfxStringItem* pOptions = pMed->GetItemSet().GetItem(SID_FILE_FILTEROPTIONS, false); + if ( pOptions ) + aSet.Put( *pOptions ); + } + + GetDispatcher()->Execute( SID_OPENDOC, SfxCallMode::ASYNCHRON, aSet ); + return; + } + + nErr = ERRCODE_NONE; + } + + // Keep the read-only UI + aReadOnlyUIGuard.m_bSetRO = true; + + ErrorHandler::HandleError( nErr ); + rReq.SetReturnValue( + SfxBoolItem( rReq.GetSlot(), false ) ); + return; + } + else + { + aReadOnlyUIGuard.m_pMed = pMed; + rReq.SetReturnValue( SfxBoolItem( rReq.GetSlot(), true ) ); + rReq.Done( true ); + return; + } + } + + rReq.AppendItem( SfxBoolItem(SID_FORCERELOAD, + (rReq.GetSlot() == SID_EDITDOC + // tdf#151715 exclude files loaded from /tmp to avoid Notebookbar bugs + && (!pSh->IsOriginallyReadOnlyMedium() || pSh->IsOriginallyLoadedReadOnlyMedium())) + || bNeedsReload) ); + rReq.AppendItem( SfxBoolItem( SID_SILENT, true )); + + [[fallthrough]]; //TODO ??? + } + + case SID_RELOAD: + { + // Due to Double occupancy in toolboxes (with or without Ctrl), + // it is also possible that the slot is enabled, but Ctrl-click + // despite this is not! + if ( !pSh || !pSh->CanReload_Impl() ) + break; + SfxApplication* pApp = SfxGetpApp(); + const SfxBoolItem* pForceReloadItem = rReq.GetArg<SfxBoolItem>(SID_FORCERELOAD); + if( pForceReloadItem && !pForceReloadItem->GetValue() && + !pSh->GetMedium()->IsExpired() ) + return; + if( m_pImpl->bReloading || pSh->IsInModalMode() ) + return; + + // AutoLoad is prohibited if possible + const SfxBoolItem* pAutoLoadItem = rReq.GetArg<SfxBoolItem>(SID_AUTOLOAD); + if ( pAutoLoadItem && pAutoLoadItem->GetValue() && + GetFrame().IsAutoLoadLocked_Impl() ) + return; + + SfxObjectShellLock xOldObj( pSh ); + m_pImpl->bReloading = true; + const SfxStringItem* pURLItem = rReq.GetArg<SfxStringItem>(SID_FILE_NAME); + // Open as editable? + bool bForEdit = !pSh->IsReadOnly(); + + // If possible ask the User + bool bDo = GetViewShell()->PrepareClose(); + const SfxBoolItem* pSilentItem = rReq.GetArg<SfxBoolItem>(SID_SILENT); + if (getenv("SAL_NO_QUERYSAVE")) + bDo = true; + else if (bDo && GetFrame().DocIsModified_Impl() && !rReq.IsAPI() + && (!pSilentItem || !pSilentItem->GetValue())) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog( + GetWindow().GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_QUERY_LASTVERSION))); + bDo = RET_YES == xBox->run(); + } + + if ( bDo ) + { + SfxMedium *pMedium = xOldObj->GetMedium(); + std::shared_ptr<std::recursive_mutex> pChkEditMutex + = pMedium->GetCheckEditableMutex(); + std::unique_lock<std::recursive_mutex> chkEditLock; + if (pChkEditMutex != nullptr) + chkEditLock = std::unique_lock<std::recursive_mutex>(*pChkEditMutex); + pMedium->CancelCheckEditableEntry(); + + bool bHandsOff = + ( pMedium->GetURLObject().GetProtocol() == INetProtocol::File && !xOldObj->IsDocShared() ); + + // Empty existing SfxMDIFrames for this Document + // in native format or R/O, open it now for editing? + SfxObjectShellLock xNewObj; + + // collect the views of the document + // TODO: when UNO ViewFactories are available for SFX-based documents, the below code should + // be UNOized, too + typedef ::std::pair< Reference< XFrame >, SfxInterfaceId > ViewDescriptor; + ::std::vector< ViewDescriptor > aViewFrames; + SfxViewFrame *pView = GetFirst( xOldObj ); + while ( pView ) + { + Reference< XFrame > xFrame( pView->GetFrame().GetFrameInterface() ); + SAL_WARN_IF( !xFrame.is(), "sfx.view", "SfxViewFrame::ExecReload_Impl: no XFrame?!"); + aViewFrames.emplace_back( xFrame, pView->GetCurViewId() ); + + pView = GetNext( *pView, xOldObj ); + } + + xOldObj->Get_Impl()->pReloadTimer.reset(); + + std::optional<SfxAllItemSet> pNewSet; + std::shared_ptr<const SfxFilter> pFilter = pMedium->GetFilter(); + if( pURLItem ) + { + pNewSet.emplace( pApp->GetPool() ); + pNewSet->Put( *pURLItem ); + + // Filter Detection + OUString referer; + const SfxStringItem* refererItem = rReq.GetArg<SfxStringItem>(SID_REFERER); + if (refererItem != nullptr) { + referer = refererItem->GetValue(); + } + SfxMedium aMedium( pURLItem->GetValue(), referer, SFX_STREAM_READWRITE ); + SfxFilterMatcher().GuessFilter( aMedium, pFilter ); + if ( pFilter ) + pNewSet->Put( SfxStringItem( SID_FILTER_NAME, pFilter->GetName() ) ); + pNewSet->Put( aMedium.GetItemSet() ); + } + else + { + pNewSet.emplace( pMedium->GetItemSet() ); + pNewSet->ClearItem( SID_VIEW_ID ); + pNewSet->ClearItem( SID_STREAM ); + pNewSet->ClearItem( SID_INPUTSTREAM ); + pNewSet->Put( SfxStringItem( SID_FILTER_NAME, pMedium->GetFilter()->GetName() ) ); + + // let the current security settings be checked again + pNewSet->Put( SfxUInt16Item( SID_MACROEXECMODE, document::MacroExecMode::USE_CONFIG ) ); + + if ( pSh->IsOriginallyReadOnlyMedium() + || pSh->IsOriginallyLoadedReadOnlyMedium() ) + // edit mode is switched or reload of readonly document + pNewSet->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + else + // Reload of file opened for writing + pNewSet->ClearItem( SID_DOC_READONLY ); + } + + // If a salvaged file is present, do not enclose the OrigURL + // again, since the Template is invalid after reload. + const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(&*pNewSet, SID_DOC_SALVAGE, false); + if( pSalvageItem ) + { + pNewSet->ClearItem( SID_DOC_SALVAGE ); + } + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + // TODO/LATER: Temporary solution, the SfxMedium must know the original URL as aLogicName + // SfxMedium::Transfer_Impl() will be forbidden then. + if ( xOldObj->IsDocShared() ) + pNewSet->Put( SfxStringItem( SID_FILE_NAME, xOldObj->GetSharedFileURL() ) ); +#endif + if ( pURLItem ) + pNewSet->Put( SfxStringItem( SID_REFERER, pMedium->GetName() ) ); + else + pNewSet->Put( SfxStringItem( SID_REFERER, OUString() ) ); + + xOldObj->CancelTransfers(); + + + if ( pSilentItem && pSilentItem->GetValue() ) + pNewSet->Put( SfxBoolItem( SID_SILENT, true ) ); + + const SfxUnoAnyItem* pInteractionItem = SfxItemSet::GetItem<SfxUnoAnyItem>(&*pNewSet, SID_INTERACTIONHANDLER, false); + const SfxUInt16Item* pMacroExecItem = SfxItemSet::GetItem<SfxUInt16Item>(&*pNewSet, SID_MACROEXECMODE, false); + const SfxUInt16Item* pDocTemplateItem = SfxItemSet::GetItem<SfxUInt16Item>(&*pNewSet, SID_UPDATEDOCMODE, false); + + if (!pInteractionItem) + { + Reference < task::XInteractionHandler2 > xHdl = task::InteractionHandler::createWithParent( ::comphelper::getProcessComponentContext(), nullptr ); + if (xHdl.is()) + pNewSet->Put( SfxUnoAnyItem(SID_INTERACTIONHANDLER,css::uno::Any(xHdl)) ); + } + + if (!pMacroExecItem) + pNewSet->Put( SfxUInt16Item(SID_MACROEXECMODE,css::document::MacroExecMode::USE_CONFIG) ); + if (!pDocTemplateItem) + pNewSet->Put( SfxUInt16Item(SID_UPDATEDOCMODE,css::document::UpdateDocMode::ACCORDING_TO_CONFIG) ); + + xOldObj->SetModified( false ); + // Do not cache the old Document! Is invalid when loading + // another document. + + bool bHasStorage = pMedium->HasStorage_Impl(); + if( bHandsOff ) + { + if ( bHasStorage && pMedium->GetStorage() == xOldObj->GetStorage() ) + { + // TODO/LATER: faster creation of copy + if ( !xOldObj->ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium ) ) + return; + } + + pMedium->CloseAndRelease(); + } + + xNewObj = SfxObjectShell::CreateObject( pFilter->GetServiceName() ); + + if ( xOldObj->IsModifyPasswordEntered() ) + xNewObj->SetModifyPasswordEntered(); + + uno::Sequence < beans::PropertyValue > aLoadArgs; + TransformItems( SID_OPENDOC, *pNewSet, aLoadArgs ); + try + { + uno::Reference < frame::XLoadable > xLoad( xNewObj->GetModel(), uno::UNO_QUERY ); + xLoad->load( aLoadArgs ); + } + catch ( uno::Exception& ) + { + xNewObj->DoClose(); + xNewObj = nullptr; + pMedium->AddToCheckEditableWorkerList(); + } + + pNewSet.reset(); + + if( !xNewObj.Is() ) + { + if( bHandsOff ) + { + // back to old medium + pMedium->ReOpen(); + pMedium->LockOrigFileOnDemand( false, true ); + + xOldObj->DoSaveCompleted( pMedium ); + } + } + else + { + if ( xNewObj->GetModifyPasswordHash() && xNewObj->GetModifyPasswordHash() != xOldObj->GetModifyPasswordHash() ) + { + xNewObj->SetModifyPasswordEntered( false ); + xNewObj->SetReadOnly(); + } + else if ( rReq.GetSlot() == SID_EDITDOC || rReq.GetSlot() == SID_READONLYDOC ) + { + xNewObj->SetReadOnlyUI( !bForEdit ); + } + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + if ( xNewObj->IsDocShared() ) + { + // the file is shared but the closing can change the sharing control file + xOldObj->DoNotCleanShareControlFile(); + } +#endif + // the Reload and Silent items were only temporary, remove them + xNewObj->GetMedium()->GetItemSet().ClearItem( SID_RELOAD ); + xNewObj->GetMedium()->GetItemSet().ClearItem( SID_SILENT ); + TransformItems( SID_OPENDOC, xNewObj->GetMedium()->GetItemSet(), aLoadArgs ); + + UpdateDocument_Impl(); + + auto sModule = vcl::CommandInfoProvider::GetModuleIdentifier(GetFrame().GetFrameInterface()); + OUString sReloadNotebookBar; + if (sModule == "com.sun.star.text.TextDocument") + sReloadNotebookBar = u"modules/swriter/ui/"_ustr; + else if (sModule == "com.sun.star.sheet.SpreadsheetDocument") + sReloadNotebookBar = u"modules/scalc/ui/"_ustr; + else if (sfx2::SfxNotebookBar::IsActive() + && sModule != "presentation.PresentationDocument" + && sModule != "com.sun.star.drawing.DrawingDocument") + { + assert(false && "SID_RELOAD Notebookbar active, but not refreshed here"); + } + + try + { + for (auto const& viewFrame : aViewFrames) + { + LoadViewIntoFrame_Impl( *xNewObj, viewFrame.first, aLoadArgs, viewFrame.second, false ); + } + aViewFrames.clear(); + } + catch( const Exception& ) + { + // close the remaining frames + // Don't catch exceptions herein, if this fails, then we're left in an indetermined state, and + // crashing is better than trying to proceed + for (auto const& viewFrame : aViewFrames) + { + Reference< util::XCloseable > xClose( viewFrame.first, UNO_QUERY_THROW ); + xClose->close( true ); + } + aViewFrames.clear(); + } + + const SfxInt32Item* pPageNumber = rReq.GetArg<SfxInt32Item>(SID_PAGE_NUMBER); + if (pPageNumber && pPageNumber->GetValue() >= 0) + { + // Restore current page after reload. + uno::Reference<drawing::XDrawView> xController( + xNewObj->GetModel()->getCurrentController(), uno::UNO_QUERY); + uno::Reference<drawing::XDrawPagesSupplier> xSupplier(xNewObj->GetModel(), + uno::UNO_QUERY); + uno::Reference<drawing::XDrawPages> xDrawPages = xSupplier->getDrawPages(); + uno::Reference<drawing::XDrawPage> xDrawPage( + xDrawPages->getByIndex(pPageNumber->GetValue()), uno::UNO_QUERY); + xController->setCurrentPage(xDrawPage); + } + + // Propagate document closure. + SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::CloseDoc, GlobalEventConfig::GetEventName( GlobalEventId::CLOSEDOC ), xOldObj ) ); + + // tdf#126006 Calc needs to reload the notebookbar after closing the document + if (!sReloadNotebookBar.isEmpty()) + sfx2::SfxNotebookBar::ReloadNotebookBar(sReloadNotebookBar); + } + + // Record as done + rReq.Done( true ); + rReq.SetReturnValue(SfxBoolItem(rReq.GetSlot(), true)); + return; + } + else + { + // Record as not done + rReq.Done(); + rReq.SetReturnValue(SfxBoolItem(rReq.GetSlot(), false)); + m_pImpl->bReloading = false; + return; + } + } + } +} + +void SfxViewFrame::StateReload_Impl( SfxItemSet& rSet ) +{ + SfxObjectShell* pSh = GetObjectShell(); + if ( !pSh ) + { + // I'm just on reload and am yielding myself ... + return; + } + + SfxWhichIter aIter( rSet ); + for ( sal_uInt16 nWhich = aIter.FirstWhich(); nWhich; nWhich = aIter.NextWhich() ) + { + switch ( nWhich ) + { + case SID_EDITDOC: + case SID_READONLYDOC: + { + const SfxViewShell *pVSh; + const SfxShell *pFSh; + if ( !pSh->HasName() || + !( pSh->Get_Impl()->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT ) || + (pSh->isEditDocLocked()) || + ( pSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED && + ( !(pVSh = pSh->GetViewShell()) || + !(pFSh = pVSh->GetFormShell()) || + !pFSh->IsDesignMode()))) + rSet.DisableItem( nWhich ); + else + { + const SfxBoolItem* pItem = pSh->GetMedium()->GetItemSet().GetItem(SID_EDITDOC, false); + if ( pItem && !pItem->GetValue() ) + rSet.DisableItem( nWhich ); + else + { + if (nWhich==SID_EDITDOC) + rSet.Put( SfxBoolItem( nWhich, !pSh->IsReadOnly() ) ); + else if (nWhich==SID_READONLYDOC) + rSet.Put( SfxBoolItem( nWhich, pSh->IsReadOnly() ) ); + } + } + break; + } + + case SID_RELOAD: + { + if ( !pSh->CanReload_Impl() || pSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + rSet.DisableItem(nWhich); + else + { + // If any ChildFrame is reloadable, the slot is enabled, + // so you can perform CTRL-Reload + rSet.Put( SfxBoolItem( nWhich, false)); + } + + break; + } + } + } +} + +void SfxViewFrame::ExecHistory_Impl( SfxRequest &rReq ) +{ + // Is there an Undo-Manager on the top Shell? + SfxShell *pSh = GetDispatcher()->GetShell(0); + if (!pSh) + return; + + SfxUndoManager* pShUndoMgr = pSh->GetUndoManager(); + bool bOK = false; + if ( pShUndoMgr ) + { + switch ( rReq.GetSlot() ) + { + case SID_CLEARHISTORY: + pShUndoMgr->Clear(); + bOK = true; + break; + + case SID_UNDO: + pShUndoMgr->Undo(); + GetBindings().InvalidateAll(false); + bOK = true; + break; + + case SID_REDO: + pShUndoMgr->Redo(); + GetBindings().InvalidateAll(false); + bOK = true; + break; + + case SID_REPEAT: + if ( pSh->GetRepeatTarget() ) + pShUndoMgr->Repeat( *pSh->GetRepeatTarget() ); + bOK = true; + break; + } + } + else if ( GetViewShell() ) + { + // The SW has its own undo in the View + const SfxPoolItemHolder& rResult(GetViewShell()->ExecuteSlot(rReq)); + if (nullptr != rResult.getItem()) + bOK = static_cast<const SfxBoolItem*>(rResult.getItem())->GetValue(); + } + + rReq.SetReturnValue( SfxBoolItem( rReq.GetSlot(), bOK ) ); + rReq.Done(); +} + +void SfxViewFrame::StateHistory_Impl( SfxItemSet &rSet ) +{ + // Search for Undo-Manager + SfxShell *pSh = GetDispatcher()->GetShell(0); + if ( !pSh ) + // I'm just on reload and am yielding myself ... + return; + + SfxUndoManager *pShUndoMgr = pSh->GetUndoManager(); + if ( !pShUndoMgr ) + { + // The SW has its own undo in the View + SfxWhichIter aIter( rSet ); + SfxViewShell *pViewSh = GetViewShell(); + if( !pViewSh ) return; + for ( sal_uInt16 nSID = aIter.FirstWhich(); nSID; nSID = aIter.NextWhich() ) + pViewSh->GetSlotState( nSID, nullptr, &rSet ); + return; + } + + if ( pShUndoMgr->GetUndoActionCount() == 0 && + pShUndoMgr->GetRedoActionCount() == 0 && + pShUndoMgr->GetRepeatActionCount() == 0 ) + rSet.DisableItem( SID_CLEARHISTORY ); + + if (pShUndoMgr->GetUndoActionCount()) + { + const SfxUndoAction* pAction = pShUndoMgr->GetUndoAction(); + SfxViewShell *pViewSh = GetViewShell(); + if (pViewSh && pAction->GetViewShellId() != pViewSh->GetViewShellId()) + { + rSet.Put(SfxUInt32Item(SID_UNDO, static_cast<sal_uInt32>(SID_REPAIRPACKAGE))); + } + else + { + rSet.Put( SfxStringItem( SID_UNDO, SvtResId(STR_UNDO)+pShUndoMgr->GetUndoActionComment() ) ); + } + } + else + rSet.DisableItem( SID_UNDO ); + + if (pShUndoMgr->GetRedoActionCount()) + { + const SfxUndoAction* pAction = pShUndoMgr->GetRedoAction(); + SfxViewShell *pViewSh = GetViewShell(); + if (pViewSh && pAction->GetViewShellId() != pViewSh->GetViewShellId()) + { + rSet.Put(SfxUInt32Item(SID_REDO, static_cast<sal_uInt32>(SID_REPAIRPACKAGE))); + } + else + { + rSet.Put(SfxStringItem(SID_REDO, SvtResId(STR_REDO) + pShUndoMgr->GetRedoActionComment())); + } + } + else + rSet.DisableItem( SID_REDO ); + + SfxRepeatTarget *pTarget = pSh->GetRepeatTarget(); + if (pTarget && pShUndoMgr->GetRepeatActionCount() && pShUndoMgr->CanRepeat(*pTarget)) + rSet.Put( SfxStringItem( SID_REPEAT, SvtResId(STR_REPEAT)+pShUndoMgr->GetRepeatActionComment(*pTarget) ) ); + else + rSet.DisableItem( SID_REPEAT ); +} + +void SfxViewFrame::PopShellAndSubShells_Impl( SfxViewShell& i_rViewShell ) +{ + i_rViewShell.PopSubShells_Impl(); + sal_uInt16 nLevel = m_pDispatcher->GetShellLevel( i_rViewShell ); + if ( nLevel != USHRT_MAX ) + { + if ( nLevel ) + { + // more sub shells on the stack, which were not affected by PopSubShells_Impl + SfxShell *pSubShell = m_pDispatcher->GetShell( nLevel-1 ); + m_pDispatcher->Pop( *pSubShell, SfxDispatcherPopFlags::POP_UNTIL | SfxDispatcherPopFlags::POP_DELETE ); + } + m_pDispatcher->Pop( i_rViewShell ); + m_pDispatcher->Flush(); + } + +} + +/* [Description] + + This method empties the SfxViewFrame, i.e. takes the <SfxObjectShell> + from the dispatcher and ends its <SfxListener> Relationship to this + SfxObjectShell (by which they may even destroy themselves). + + Thus, by invoking ReleaseObjectShell() and SetObjectShell() the + SfxObjectShell can be replaced. + + Between ReleaseObjectShell() and SetObjectShell() the control cannot + be handed over to the system. + + [Cross-reference] + + <SfxViewFrame::SetObjectShell(SfxObjectShell&)> +*/ +void SfxViewFrame::ReleaseObjectShell_Impl() +{ + DBG_ASSERT( m_xObjSh.is(), "no SfxObjectShell to release!" ); + + GetFrame().ReleasingComponent_Impl(); + if ( GetWindow().HasChildPathFocus( true ) ) + { + GetWindow().GrabFocus(); + } + + SfxViewShell *pDyingViewSh = GetViewShell(); + if ( pDyingViewSh ) + { + PopShellAndSubShells_Impl( *pDyingViewSh ); + pDyingViewSh->DisconnectAllClients(); + SetViewShell_Impl(nullptr); + delete pDyingViewSh; + } +#ifdef DBG_UTIL + else + OSL_FAIL("No Shell"); +#endif + + if ( m_xObjSh.is() ) + { + m_pDispatcher->Pop( *m_xObjSh ); + SfxModule* pModule = m_xObjSh->GetModule(); + if( pModule ) + m_pDispatcher->RemoveShell_Impl( *pModule ); + m_pDispatcher->Flush(); + EndListening( *m_xObjSh ); + + Notify( *m_xObjSh, SfxHint(SfxHintId::TitleChanged) ); + Notify( *m_xObjSh, SfxHint(SfxHintId::DocChanged) ); + + if ( 1 == m_xObjSh->GetOwnerLockCount() && m_pImpl->bObjLocked && m_xObjSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + m_xObjSh->DoClose(); + SfxObjectShellRef xDyingObjSh = m_xObjSh; + m_xObjSh.clear(); + if( GetFrame().GetHasTitle() && m_pImpl->nDocViewNo ) + xDyingObjSh->GetNoSet_Impl().ReleaseIndex(m_pImpl->nDocViewNo-1); + if ( m_pImpl->bObjLocked ) + { + xDyingObjSh->OwnerLock( false ); + m_pImpl->bObjLocked = false; + } + } + + GetDispatcher()->SetDisableFlags( SfxDisableFlags::NONE ); +} + +void SfxViewFrame::Close() +{ + + DBG_ASSERT( GetFrame().IsClosing_Impl() || !GetFrame().GetFrameInterface().is(), "ViewFrame closed too early!" ); + + // If no saving have been made up until now, then embedded Objects should + // not be saved automatically anymore. + if ( GetViewShell() ) + GetViewShell()->DisconnectAllClients(); + Broadcast( SfxHint( SfxHintId::Dying ) ); + + if (SfxViewFrame::Current() == this) + SfxViewFrame::SetViewFrame( nullptr ); + + // Since the Dispatcher is emptied, it can not be used in any reasonable + // manner, thus it is better to let the dispatcher be. + GetDispatcher()->Lock(true); + delete this; +} + +void SfxViewFrame::DoActivate( bool bUI ) +{ + m_pDispatcher->DoActivate_Impl( bUI ); +} + +void SfxViewFrame::DoDeactivate(bool bUI, SfxViewFrame const * pNewFrame ) +{ + m_pDispatcher->DoDeactivate_Impl( bUI, pNewFrame ); +} + +void SfxViewFrame::InvalidateBorderImpl( const SfxViewShell* pSh ) +{ + if( !pSh || m_nAdjustPosPixelLock ) + return; + + if ( GetViewShell() && GetWindow().IsVisible() ) + { + if ( GetFrame().IsInPlace() ) + { + return; + } + + DoAdjustPosSizePixel( GetViewShell(), Point(), + GetWindow().GetOutputSizePixel(), + false ); + } +} + +void SfxViewFrame::SetBorderPixelImpl +( + const SfxViewShell* pVSh, + const SvBorder& rBorder +) + +{ + m_pImpl->aBorder = rBorder; + + if ( m_pImpl->bResizeInToOut && !GetFrame().IsInPlace() ) + { + Size aSize = pVSh->GetWindow()->GetOutputSizePixel(); + if ( aSize.Width() && aSize.Height() ) + { + aSize.AdjustWidth(rBorder.Left() + rBorder.Right() ); + aSize.AdjustHeight(rBorder.Top() + rBorder.Bottom() ); + + Size aOldSize = GetWindow().GetOutputSizePixel(); + GetWindow().SetOutputSizePixel( aSize ); + vcl::Window* pParent = &GetWindow(); + while ( pParent->GetParent() ) + pParent = pParent->GetParent(); + Size aOuterSize = pParent->GetOutputSizePixel(); + aOuterSize.AdjustWidth( aSize.Width() - aOldSize.Width() ); + aOuterSize.AdjustHeight( aSize.Height() - aOldSize.Height() ); + pParent->SetOutputSizePixel( aOuterSize ); + } + } + else + { + tools::Rectangle aEditArea( Point(), GetWindow().GetOutputSizePixel() ); + aEditArea.AdjustLeft(rBorder.Left() ); + aEditArea.AdjustRight( -(rBorder.Right()) ); + aEditArea.AdjustTop(rBorder.Top() ); + aEditArea.AdjustBottom( -(rBorder.Bottom()) ); + pVSh->GetWindow()->SetPosSizePixel( aEditArea.TopLeft(), aEditArea.GetSize() ); + } +} + +const SvBorder& SfxViewFrame::GetBorderPixelImpl() const +{ + return m_pImpl->aBorder; +} + +void SfxViewFrame::AppendReadOnlyInfobar() +{ + bool bSignPDF = m_xObjSh->IsSignPDF(); + bool bSignWithCert = false; + if (bSignPDF) + { + SfxObjectShell* pObjectShell = GetObjectShell(); + uno::Reference<security::XCertificate> xCertificate = pObjectShell->GetSignPDFCertificate(); + bSignWithCert = xCertificate.is(); + } + + auto pInfoBar = AppendInfoBar("readonly", "", + SfxResId(bSignPDF ? STR_READONLY_PDF : STR_READONLY_DOCUMENT), + InfobarType::INFO); + if (!pInfoBar) + return; + + if (bSignPDF) + { + // SID_SIGNPDF opened a read-write PDF + // read-only for signing purposes. + weld::Button& rSignButton = pInfoBar->addButton(); + if (bSignWithCert) + { + rSignButton.set_label(SfxResId(STR_READONLY_FINISH_SIGN)); + } + else + { + rSignButton.set_label(SfxResId(STR_READONLY_SIGN)); + } + + rSignButton.connect_clicked(LINK(this, SfxViewFrame, SignDocumentHandler)); + } + + bool showEditDocumentButton = true; + if (m_xObjSh->isEditDocLocked()) + showEditDocumentButton = false; + + if (showEditDocumentButton) + { + weld::Button& rBtn = pInfoBar->addButton(); + rBtn.set_label(SfxResId(STR_READONLY_EDIT)); + rBtn.connect_clicked(LINK(this, SfxViewFrame, SwitchReadOnlyHandler)); + } +} + +void SfxViewFrame::HandleSecurityInfobar(const OUString& sSecondaryMessage) +{ + if (!HasInfoBarWithID(u"securitywarn")) + { + // new info bar + if (!sSecondaryMessage.isEmpty()) + { + auto pInfoBar = AppendInfoBar("securitywarn", SfxResId(STR_HIDDENINFO_CONTAINS).replaceAll("\n\n", " "), + sSecondaryMessage, InfobarType::WARNING); + if (!pInfoBar) + return; + + weld::Button& rGetInvolvedButton = pInfoBar->addButton(); + rGetInvolvedButton.set_label(SfxResId(STR_SECURITY_OPTIONS)); + rGetInvolvedButton.connect_clicked(LINK(this, SfxViewFrame, SecurityButtonHandler)); + } + } + else + { + // info bar exists already + if (sSecondaryMessage.isEmpty()) + { + RemoveInfoBar(u"securitywarn"); + } + else + { + UpdateInfoBar(u"securitywarn", SfxResId(STR_HIDDENINFO_CONTAINS).replaceAll("\n\n", " "), + sSecondaryMessage, InfobarType::WARNING); + } + } +} + +void SfxViewFrame::AppendContainsMacrosInfobar() +{ + SfxObjectShell_Impl* pObjImpl = m_xObjSh->Get_Impl(); + + // what's the difference between pObjImpl->documentStorageHasMacros() and pObjImpl->aMacroMode.hasMacroLibrary() ? + bool bHasDocumentMacros = pObjImpl->aMacroMode.hasMacroLibrary(); + + Reference<XModel> xModel = m_xObjSh->GetModel(); + uno::Reference<document::XEventsSupplier> xSupplier(xModel, uno::UNO_QUERY); + bool bHasBoundConfigEvents(false); + if (xSupplier.is()) + { + css::uno::Reference<css::container::XNameReplace> xDocumentEvents = xSupplier->getEvents(); + + Sequence<OUString> eventNames = xDocumentEvents->getElementNames(); + sal_Int32 nEventCount = eventNames.getLength(); + for (sal_Int32 nEvent = 0; nEvent < nEventCount; ++nEvent) + { + OUString url; + try + { + Any aAny(xDocumentEvents->getByName(eventNames[nEvent])); + Sequence<beans::PropertyValue> props; + if (aAny >>= props) + { + ::comphelper::NamedValueCollection aProps(props); + url = aProps.getOrDefault("Script", url); + } + } + catch (const Exception&) + { + } + if (!url.isEmpty()) + { + bHasBoundConfigEvents = true; + break; + } + } + } + + if (bHasDocumentMacros || bHasBoundConfigEvents) + { + auto aResId = STR_CONTAINS_MACROS; + if (SvtSecurityOptions::IsMacroDisabled()) + aResId = STR_MACROS_DISABLED; + else if (pObjImpl->aMacroMode.hasUnsignedContentError()) + aResId = STR_MACROS_DISABLED_CONTENT_UNSIGNED; + auto pInfoBar = AppendInfoBar("macro", SfxResId(STR_MACROS_DISABLED_TITLE), + SfxResId(aResId), InfobarType::WARNING); + if (!pInfoBar) + return; + + // No access to macro dialog when macros are disabled globally. + if (SvtSecurityOptions::IsMacroDisabled()) + return; + + if (bHasDocumentMacros) + { + weld::Button& rMacroButton = pInfoBar->addButton(); + rMacroButton.set_label(SfxResId(STR_MACROS)); + rMacroButton.connect_clicked(LINK(this, SfxViewFrame, MacroButtonHandler)); + } + + if (bHasBoundConfigEvents) + { + weld::Button& rEventButton = pInfoBar->addButton(); + rEventButton.set_label(SfxResId(STR_EVENTS)); + rEventButton.connect_clicked(LINK(this, SfxViewFrame, EventButtonHandler)); + } + } +} + +namespace +{ +css::uno::Reference<css::frame::XLayoutManager> getLayoutManager(const SfxFrame& rFrame) +{ + css::uno::Reference<css::frame::XLayoutManager> xLayoutManager; + css::uno::Reference<css::beans::XPropertySet> xPropSet(rFrame.GetFrameInterface(), + uno::UNO_QUERY); + if (xPropSet.is()) + { + try + { + xLayoutManager.set(xPropSet->getPropertyValue("LayoutManager"), uno::UNO_QUERY); + } + catch (const Exception& e) + { + SAL_WARN("sfx.view", "Failure getting layout manager: " + e.Message); + } + } + return xLayoutManager; +} +} + +bool SfxApplication::IsHeadlessOrUITest() +{ + if (Application::IsHeadlessModeEnabled()) + return true; + + bool bIsUITest = false; //uitest.uicheck fails when the dialog is open + for (sal_uInt16 i = 0, nCount = Application::GetCommandLineParamCount(); i < nCount; ++i) + { + if (Application::GetCommandLineParam(i) == "--nologo") + { + bIsUITest = true; + break; + } + } + return bIsUITest; +} + +bool SfxApplication::IsTipOfTheDayDue() +{ + const bool bShowTipOfTheDay = officecfg::Office::Common::Misc::ShowTipOfTheDay::get(); + if (!bShowTipOfTheDay) + return false; + + const auto t0 = std::chrono::system_clock::now().time_since_epoch(); + + // show tip-of-the-day dialog ? + const sal_Int32 nLastTipOfTheDay = officecfg::Office::Common::Misc::LastTipOfTheDayShown::get(); + const sal_Int32 nDay = std::chrono::duration_cast<std::chrono::hours>(t0).count()/24; // days since 1970-01-01 + return nDay - nLastTipOfTheDay > 0; //only once per day +} + +void SfxViewFrame::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + if(m_pImpl->bIsDowning) + return; + + // we know only SfxEventHint or simple SfxHint + if (rHint.GetId() == SfxHintId::ThisIsAnSfxEventHint) + { + // When the Document is loaded asynchronously, was the Dispatcher + // set as ReadOnly, to what must be returned when the document itself + // is not read only, and the loading is finished. + switch (static_cast<const SfxEventHint&>(rHint).GetEventId()) + { + case SfxEventHintId::ModifyChanged: + { + SfxBindings& rBind = GetBindings(); + rBind.Invalidate( SID_DOC_MODIFIED ); + rBind.Invalidate( SID_RELOAD ); + rBind.Invalidate( SID_EDITDOC ); + break; + } + + case SfxEventHintId::OpenDoc: + case SfxEventHintId::CreateDoc: + { + if ( !m_xObjSh.is() ) + break; + + SfxBindings& rBind = GetBindings(); + rBind.Invalidate( SID_RELOAD ); + rBind.Invalidate( SID_EDITDOC ); + +#if !ENABLE_WASM_STRIP_PINGUSER + bool bIsHeadlessOrUITest = SfxApplication::IsHeadlessOrUITest(); //uitest.uicheck fails when the dialog is open + + //what's new infobar + if (utl::isProductVersionUpgraded(true) && !bIsHeadlessOrUITest) + { + VclPtr<SfxInfoBarWindow> pInfoBar = AppendInfoBar("whatsnew", "", SfxResId(STR_WHATSNEW_TEXT), InfobarType::INFO); + if (pInfoBar) + { + weld::Button& rWhatsNewButton = pInfoBar->addButton(); + rWhatsNewButton.set_label(SfxResId(STR_WHATSNEW_BUTTON)); + rWhatsNewButton.connect_clicked(LINK(this, SfxViewFrame, WhatsNewHandler)); + } + } + + // show tip-of-the-day dialog if it due, but not if there is the impress modal template dialog + // open where SdModule::ExecuteNewDocument will launch it instead when that dialog is dismissed + if (SfxApplication::IsTipOfTheDayDue() && !bIsHeadlessOrUITest && !IsInModalMode()) + { + // tdf#127946 pass in argument for dialog parent + SfxUnoFrameItem aDocFrame(SID_FILLFRAME, GetFrame().GetFrameInterface()); + GetDispatcher()->ExecuteList(SID_TIPOFTHEDAY, SfxCallMode::SLOT, {}, { &aDocFrame }); + } + + // inform about the community involvement + const auto t0 = std::chrono::system_clock::now().time_since_epoch(); + const sal_Int64 nLastGetInvolvedShown = officecfg::Setup::Product::LastTimeGetInvolvedShown::get(); + const sal_Int64 nNow = std::chrono::duration_cast<std::chrono::seconds>(t0).count(); + const sal_Int64 nPeriodSec(60 * 60 * 24 * 180); // 180 days in seconds + bool bUpdateLastTimeGetInvolvedShown = false; + + if (nLastGetInvolvedShown == 0) + bUpdateLastTimeGetInvolvedShown = true; + else if (nPeriodSec < nNow && nLastGetInvolvedShown < (nNow + nPeriodSec/2) - nPeriodSec) // 90d alternating with donation + { + bUpdateLastTimeGetInvolvedShown = true; + + VclPtr<SfxInfoBarWindow> pInfoBar = AppendInfoBar("getinvolved", "", SfxResId(STR_GET_INVOLVED_TEXT), InfobarType::INFO); + + if (pInfoBar) + { + weld::Button& rGetInvolvedButton = pInfoBar->addButton(); + rGetInvolvedButton.set_label(SfxResId(STR_GET_INVOLVED_BUTTON)); + rGetInvolvedButton.connect_clicked(LINK(this, SfxViewFrame, GetInvolvedHandler)); + } + } + + if (bUpdateLastTimeGetInvolvedShown + && !officecfg::Setup::Product::LastTimeGetInvolvedShown::isReadOnly()) + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Setup::Product::LastTimeGetInvolvedShown::set(nNow, batch); + batch->commit(); + } + + // inform about donations + const sal_Int64 nLastDonateShown = officecfg::Setup::Product::LastTimeDonateShown::get(); + bool bUpdateLastTimeDonateShown = false; + + if (nLastDonateShown == 0) + bUpdateLastTimeDonateShown = true; + else if (nPeriodSec < nNow && nLastDonateShown < nNow - nPeriodSec) // 90d alternating with getinvolved + { + bUpdateLastTimeDonateShown = true; + + VclPtr<SfxInfoBarWindow> pInfoBar = AppendInfoBar("donate", "", SfxResId(STR_DONATE_TEXT), InfobarType::INFO); + if (pInfoBar) + { + weld::Button& rDonateButton = pInfoBar->addButton(); + rDonateButton.set_label(SfxResId(STR_DONATE_BUTTON)); + rDonateButton.connect_clicked(LINK(this, SfxViewFrame, DonationHandler)); + } + } + + if (bUpdateLastTimeDonateShown + && !officecfg::Setup::Product::LastTimeDonateShown::isReadOnly()) + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Setup::Product::LastTimeDonateShown::set(nNow, batch); + batch->commit(); + } +#endif + if (officecfg::Office::Common::Passwords::HasMaster::get() && + officecfg::Office::Common::Passwords::StorageVersion::get() == 0) + { + // master password stored in deprecated format + VclPtr<SfxInfoBarWindow> pOldMasterPasswordInfoBar = + AppendInfoBar("oldmasterpassword", "", + SfxResId(STR_REFRESH_MASTER_PASSWORD), InfobarType::DANGER, false); + if (pOldMasterPasswordInfoBar) + { + weld::Button& rButton = pOldMasterPasswordInfoBar->addButton(); + rButton.set_label(SfxResId(STR_REFRESH_PASSWORD)); + rButton.connect_clicked(LINK(this, + SfxViewFrame, RefreshMasterPasswordHdl)); + if (Application::GetHelp()) + { + weld::Button& rHelp = pOldMasterPasswordInfoBar->addButton(); + rHelp.set_label(SfxResId(RID_STR_HELP)); + rHelp.connect_clicked(LINK(this, SfxViewFrame, HelpMasterPasswordHdl)); + } + } + } + + const bool bEmbedded = m_xObjSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED; + + // read-only infobar if necessary + const SfxViewShell *pVSh; + const SfxShell *pFSh; + if ( m_xObjSh->IsReadOnly() && + ! m_xObjSh->IsSecurityOptOpenReadOnly() && + ( !bEmbedded || + (( pVSh = m_xObjSh->GetViewShell()) && (pFSh = pVSh->GetFormShell()) && pFSh->IsDesignMode()))) + { + AppendReadOnlyInfobar(); + } + + if (!bEmbedded && m_xObjSh->Get_Impl()->getCurrentMacroExecMode() == css::document::MacroExecMode::NEVER_EXECUTE) + AppendContainsMacrosInfobar(); + + if (vcl::CommandInfoProvider::GetModuleIdentifier(GetFrame().GetFrameInterface()) == "com.sun.star.text.TextDocument") + sfx2::SfxNotebookBar::ReloadNotebookBar(u"modules/swriter/ui/"); + + if (SfxClassificationHelper::IsClassified(m_xObjSh->getDocProperties())) + { + // Document has BAILS properties, display an infobar accordingly. + SfxClassificationHelper aHelper(m_xObjSh->getDocProperties()); + aHelper.UpdateInfobar(*this); + } + + // Add pending infobars + std::vector<InfobarData>& aPendingInfobars = m_xObjSh->getPendingInfobars(); + while (!aPendingInfobars.empty()) + { + InfobarData& aInfobarData = aPendingInfobars.back(); + + // don't show Track Changes infobar, if Track Changes toolbar is visible + if (aInfobarData.msId == "hiddentrackchanges") + { + if (auto xLayoutManager = getLayoutManager(GetFrame())) + { + if ( xLayoutManager->getElement(CHANGES_STR).is() ) + { + aPendingInfobars.pop_back(); + continue; + } + } + } + + // Track Changes infobar: add a button to show/hide Track Changes functions + // Hyphenation infobar: add a button to get more information + // tdf#148913 limit VclPtr usage for these + bool bTrackChanges = aInfobarData.msId == "hiddentrackchanges"; + if ( bTrackChanges || aInfobarData.msId == "hyphenationmissing" ) + { + VclPtr<SfxInfoBarWindow> pInfoBar = + AppendInfoBar(aInfobarData.msId, aInfobarData.msPrimaryMessage, + aInfobarData.msSecondaryMessage, aInfobarData.maInfobarType, + aInfobarData.mbShowCloseButton); + + // tdf#148913 don't extend this condition to keep it thread-safe + if (pInfoBar) + { + weld::Button& rButton = pInfoBar->addButton(); + rButton.set_label(SfxResId(bTrackChanges + ? STR_TRACK_CHANGES_BUTTON + : STR_HYPHENATION_BUTTON)); + if (bTrackChanges) + { + rButton.connect_clicked(LINK(this, + SfxViewFrame, HiddenTrackChangesHandler)); + } + else + { + rButton.connect_clicked(LINK(this, + SfxViewFrame, HyphenationMissingHandler)); + } + } + } + else + { + AppendInfoBar(aInfobarData.msId, aInfobarData.msPrimaryMessage, + aInfobarData.msSecondaryMessage, aInfobarData.maInfobarType, + aInfobarData.mbShowCloseButton); + } + + aPendingInfobars.pop_back(); + } + + break; + } + default: break; + } + } + else + { + switch( rHint.GetId() ) + { + case SfxHintId::ModeChanged: + { + UpdateTitle(); + + if ( !m_xObjSh.is() ) + break; + + // Switch r/o? + SfxBindings& rBind = GetBindings(); + rBind.Invalidate( SID_RELOAD ); + SfxDispatcher *pDispat = GetDispatcher(); + bool bWasReadOnly = pDispat->GetReadOnly_Impl(); + bool bIsReadOnly = m_xObjSh->IsReadOnly(); + if ( bWasReadOnly != bIsReadOnly ) + { + // Then also TITLE_CHANGED + UpdateTitle(); + rBind.Invalidate( SID_FILE_NAME ); + rBind.Invalidate( SID_DOCINFO_TITLE ); + rBind.Invalidate( SID_EDITDOC ); + + pDispat->GetBindings()->InvalidateAll(true); + pDispat->SetReadOnly_Impl( bIsReadOnly ); + + // Only force and Dispatcher-Update, if it is done next + // anyway, otherwise flickering or GPF is possible since + // the Writer for example prefers in Resize perform some + // actions which has a SetReadOnlyUI in Dispatcher as a + // result! + + if ( pDispat->IsUpdated_Impl() ) + pDispat->Update_Impl(true); + } + + Enable( !m_xObjSh->IsInModalMode() ); + break; + } + + case SfxHintId::TitleChanged: + { + UpdateTitle(); + SfxBindings& rBind = GetBindings(); + rBind.Invalidate( SID_FILE_NAME ); + rBind.Invalidate( SID_DOCINFO_TITLE ); + rBind.Invalidate( SID_EDITDOC ); + rBind.Invalidate( SID_RELOAD ); + break; + } + + case SfxHintId::DocumentRepair: + { + GetBindings().Invalidate( SID_DOC_REPAIR ); + break; + } + + case SfxHintId::Deinitializing: + { + vcl::Window* pFrameWin = GetWindow().GetFrameWindow(); + if (pFrameWin && pFrameWin->GetLOKNotifier()) + pFrameWin->ReleaseLOKNotifier(); + + GetFrame().DoClose(); + break; + } + case SfxHintId::Dying: + // when the Object is being deleted, destroy the view too + if ( m_xObjSh.is() ) + ReleaseObjectShell_Impl(); + else + GetFrame().DoClose(); + break; + default: break; + } + } +} + +#if !ENABLE_WASM_STRIP_PINGUSER +IMPL_LINK_NOARG(SfxViewFrame, WhatsNewHandler, weld::Button&, void) +{ + GetDispatcher()->Execute(SID_WHATSNEW); +} + +IMPL_LINK_NOARG(SfxViewFrame, GetInvolvedHandler, weld::Button&, void) +{ + GetDispatcher()->Execute(SID_GETINVOLVED); +} + +IMPL_LINK_NOARG(SfxViewFrame, DonationHandler, weld::Button&, void) +{ + GetDispatcher()->Execute(SID_DONATION); +} +#endif + +IMPL_LINK(SfxViewFrame, SwitchReadOnlyHandler, weld::Button&, rButton, void) +{ + if (m_xObjSh.is() && m_xObjSh->IsSignPDF()) + { + SfxEditDocumentDialog aDialog(&rButton); + if (aDialog.run() != RET_OK) + return; + } + GetDispatcher()->Execute(SID_EDITDOC); +} + +IMPL_LINK_NOARG(SfxViewFrame, SignDocumentHandler, weld::Button&, void) +{ + GetDispatcher()->Execute(SID_SIGNATURE); +} + +IMPL_LINK(SfxViewFrame, HiddenTrackChangesHandler, weld::Button&, rButton, void) +{ + // enable Track Changes toolbar, if it is disabled. + // Otherwise disable the toolbar, and close the infobar + auto xLayoutManager = getLayoutManager(GetFrame()); + if (!xLayoutManager) + return; + + if (!xLayoutManager->getElement(CHANGES_STR).is()) + { + xLayoutManager->createElement(CHANGES_STR); + xLayoutManager->showElement(CHANGES_STR); + rButton.set_label(SfxResId(STR_TRACK_CHANGES_BUTTON_HIDE)); + } + else + { + xLayoutManager->hideElement(CHANGES_STR); + xLayoutManager->destroyElement(CHANGES_STR); + RemoveInfoBar(u"hiddentrackchanges"); + } +} + +IMPL_LINK_NOARG(SfxViewFrame, HyphenationMissingHandler, weld::Button&, void) +{ + GetDispatcher()->Execute(SID_HYPHENATIONMISSING); + RemoveInfoBar(u"hyphenationmissing"); +} + +IMPL_LINK_NOARG(SfxViewFrame, MacroButtonHandler, weld::Button&, void) +{ + // start with tab 0 displayed + SfxUInt16Item aTabItem(SID_MACROORGANIZER, 0); + SfxBoolItem aCurrentDocItem(FN_PARAM_2, true); + SfxUnoFrameItem aDocFrame(SID_FILLFRAME, GetFrame().GetFrameInterface()); + GetDispatcher()->ExecuteList(SID_MACROORGANIZER, SfxCallMode::ASYNCHRON, + { &aTabItem, &aCurrentDocItem }, { &aDocFrame }); +} + +IMPL_LINK_NOARG(SfxViewFrame, SecurityButtonHandler, weld::Button&, void) +{ + SfxUInt16Item aPageID(SID_OPTIONS_PAGEID, sal_uInt16(RID_SVXPAGE_INET_SECURITY)); + GetDispatcher()->ExecuteList(SID_OPTIONS_TREEDIALOG, SfxCallMode::SYNCHRON, { &aPageID }); + RemoveInfoBar(u"securitywarn"); +} + +IMPL_LINK_NOARG(SfxViewFrame, EventButtonHandler, weld::Button&, void) +{ + SfxUnoFrameItem aDocFrame(SID_FILLFRAME, GetFrame().GetFrameInterface()); + GetDispatcher()->ExecuteList(SID_CONFIGEVENT, SfxCallMode::ASYNCHRON, + {}, { &aDocFrame }); +} + +IMPL_LINK_NOARG(SfxViewFrame, RefreshMasterPasswordHdl, weld::Button&, void) +{ + bool bChanged = false; + try + { + Reference< task::XPasswordContainer2 > xMasterPasswd( + task::PasswordContainer::create(comphelper::getProcessComponentContext())); + + css::uno::Reference<css::frame::XFrame> xFrame = GetFrame().GetFrameInterface(); + css::uno::Reference<css::awt::XWindow> xContainerWindow = xFrame->getContainerWindow(); + + uno::Reference<task::XInteractionHandler> xTmpHandler(task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), + xContainerWindow)); + bChanged = xMasterPasswd->changeMasterPassword(xTmpHandler); + } + catch (const Exception&) + {} + if (bChanged) + RemoveInfoBar(u"oldmasterpassword"); +} + +IMPL_STATIC_LINK_NOARG(SfxViewFrame, HelpMasterPasswordHdl, weld::Button&, void) +{ + if (Help* pHelp = Application::GetHelp()) + pHelp->Start("cui/ui/optsecuritypage/savepassword"); +} + +void SfxViewFrame::Construct_Impl( SfxObjectShell *pObjSh ) +{ + m_pImpl->bResizeInToOut = true; + m_pImpl->bObjLocked = false; + m_pImpl->nCurViewId = SFX_INTERFACE_NONE; + m_pImpl->bReloading = false; + m_pImpl->bIsDowning = false; + m_pImpl->bModal = false; + m_pImpl->bEnabled = true; + m_pImpl->nDocViewNo = 0; + m_pImpl->aMargin = Size( -1, -1 ); + m_pImpl->pWindow = nullptr; + + SetPool( &SfxGetpApp()->GetPool() ); + m_pDispatcher.reset( new SfxDispatcher(this) ); + if ( !GetBindings().GetDispatcher() ) + GetBindings().SetDispatcher( m_pDispatcher.get() ); + + m_xObjSh = pObjSh; + if ( m_xObjSh.is() && m_xObjSh->IsPreview() ) + GetDispatcher()->SetQuietMode_Impl( true ); + + if ( pObjSh ) + { + m_pDispatcher->Push( *SfxGetpApp() ); + SfxModule* pModule = m_xObjSh->GetModule(); + if( pModule ) + m_pDispatcher->Push( *pModule ); + m_pDispatcher->Push( *this ); + m_pDispatcher->Push( *pObjSh ); + m_pDispatcher->Flush(); + StartListening( *pObjSh ); + Notify( *pObjSh, SfxHint(SfxHintId::TitleChanged) ); + Notify( *pObjSh, SfxHint(SfxHintId::DocChanged) ); + m_pDispatcher->SetReadOnly_Impl( pObjSh->IsReadOnly() ); + } + else + { + m_pDispatcher->Push( *SfxGetpApp() ); + m_pDispatcher->Push( *this ); + m_pDispatcher->Flush(); + } + + SfxGetpApp()->GetViewFrames_Impl().push_back(this); +} + +/* [Description] + + Constructor of SfxViewFrame for a <SfxObjectShell> from the Resource. + The 'nViewId' to the created <SfxViewShell> can be returned. + (default is the SfxViewShell-Subclass that was registered first). +*/ +SfxViewFrame::SfxViewFrame +( + SfxFrame& rFrame, + SfxObjectShell* pObjShell +) + : m_pImpl( new SfxViewFrame_Impl( rFrame ) ) + , m_pBindings( new SfxBindings ) + , m_pHelpData(CreateSVHelpData()) + , m_pWinData(CreateSVWinData()) + , m_nAdjustPosPixelLock( 0 ) + , m_pCommandPopupHandler(new CommandPopupHandler) +{ + + rFrame.SetCurrentViewFrame_Impl( this ); + rFrame.SetHasTitle( true ); + Construct_Impl( pObjShell ); + + m_pImpl->pWindow = VclPtr<SfxFrameViewWindow_Impl>::Create( this, rFrame.GetWindow() ); + m_pImpl->pWindow->SetSizePixel( rFrame.GetWindow().GetOutputSizePixel() ); + rFrame.SetOwnsBindings_Impl( true ); + rFrame.CreateWorkWindow_Impl(); +} + +SfxViewFrame::~SfxViewFrame() +{ + m_pImpl->bIsDowning = true; + + if ( SfxViewFrame::Current() == this ) + SfxViewFrame::SetViewFrame( nullptr ); + + ReleaseObjectShell_Impl(); + + if ( GetFrame().OwnsBindings_Impl() ) + // The Bindings delete the Frame! + KillDispatcher_Impl(); + + m_pImpl->pWindow.disposeAndClear(); + + if ( GetFrame().GetCurrentViewFrame() == this ) + GetFrame().SetCurrentViewFrame_Impl( nullptr ); + + // Unregister from the Frame List. + SfxApplication *pSfxApp = SfxApplication::Get(); + if (pSfxApp) + { + auto &rFrames = pSfxApp->GetViewFrames_Impl(); + auto it = std::find( rFrames.begin(), rFrames.end(), this ); + rFrames.erase( it ); + } + + // Delete Member + KillDispatcher_Impl(); + + DestroySVHelpData(m_pHelpData); + m_pHelpData = nullptr; + + DestroySVWinData(m_pWinData); + m_pWinData = nullptr; +} + +// Remove and delete the Dispatcher. +void SfxViewFrame::KillDispatcher_Impl() +{ + + SfxModule* pModule = m_xObjSh.is() ? m_xObjSh->GetModule() : nullptr; + if ( m_xObjSh.is() ) + ReleaseObjectShell_Impl(); + if ( m_pDispatcher ) + { + if( pModule ) + m_pDispatcher->Pop( *pModule, SfxDispatcherPopFlags::POP_UNTIL ); + else + m_pDispatcher->Pop( *this ); + m_pDispatcher.reset(); + } +} + +SfxViewFrame* SfxViewFrame::Current() +{ + SfxApplication* pApp = SfxApplication::Get(); + return pApp ? pApp->Get_Impl()->pViewFrame : nullptr; +} + +// returns the first window of spec. type viewing the specified doc. +SfxViewFrame* SfxViewFrame::GetFirst +( + const SfxObjectShell* pDoc, + bool bOnlyIfVisible +) +{ + SfxApplication *pSfxApp = SfxApplication::Get(); + if (!pSfxApp) + return nullptr; + + // search for a SfxDocument of the specified type + for (SfxViewFrame* pFrame : pSfxApp->GetViewFrames_Impl()) + { + if ( ( !pDoc || pDoc == pFrame->GetObjectShell() ) + && ( !bOnlyIfVisible || pFrame->IsVisible() ) + ) + return pFrame; + } + + return nullptr; +} + +// returns the next window of spec. type viewing the specified doc. +SfxViewFrame* SfxViewFrame::GetNext +( + const SfxViewFrame& rPrev, + const SfxObjectShell* pDoc, + bool bOnlyIfVisible +) +{ + SfxApplication *pSfxApp = SfxApplication::Get(); + if (!pSfxApp) + return nullptr; + + auto &rFrames = pSfxApp->GetViewFrames_Impl(); + + // refind the specified predecessor + size_t nPos; + for ( nPos = 0; nPos < rFrames.size(); ++nPos ) + if ( rFrames[nPos] == &rPrev ) + break; + + // search for a Frame of the specified type + for ( ++nPos; nPos < rFrames.size(); ++nPos ) + { + SfxViewFrame *pFrame = rFrames[nPos]; + if ( ( !pDoc || pDoc == pFrame->GetObjectShell() ) + && ( !bOnlyIfVisible || pFrame->IsVisible() ) + ) + return pFrame; + } + return nullptr; +} + +SfxProgress* SfxViewFrame::GetProgress() const +{ + SfxObjectShell *pObjSh = m_xObjSh.get(); + return pObjSh ? pObjSh->GetProgress() : nullptr; +} + +void SfxViewFrame::DoAdjustPosSizePixel //! divide on Inner.../Outer... +( + SfxViewShell* pSh, + const Point& rPos, + const Size& rSize, + bool inplaceEditModeChange +) +{ + + // Components do not use this Method! + if( pSh && pSh->GetWindow() && !m_nAdjustPosPixelLock ) + { + m_nAdjustPosPixelLock++; + if ( m_pImpl->bResizeInToOut ) + pSh->InnerResizePixel( rPos, rSize, inplaceEditModeChange ); + else + pSh->OuterResizePixel( rPos, rSize ); + m_nAdjustPosPixelLock--; + } +} + +bool SfxViewFrameItem::operator==( const SfxPoolItem &rItem ) const +{ + return SfxPoolItem::operator==(rItem) && + static_cast<const SfxViewFrameItem&>(rItem).pFrame == pFrame; +} + +SfxViewFrameItem* SfxViewFrameItem::Clone( SfxItemPool *) const +{ + return new SfxViewFrameItem( *this ); +} + +void SfxViewFrame::SetViewShell_Impl( SfxViewShell *pVSh ) +/* [Description] + + Internal Method to set the current <SfxViewShell> Instance, + that is active int this SfxViewFrame at the moment. +*/ +{ + SfxShell::SetViewShell_Impl( pVSh ); + + // Hack: InPlaceMode + if ( pVSh ) + m_pImpl->bResizeInToOut = false; +} + +void SfxViewFrame::ForceOuterResize_Impl() +{ + m_pImpl->bResizeInToOut = true; +} + +void SfxViewFrame::GetDocNumber_Impl() +{ + DBG_ASSERT( GetObjectShell(), "No Document!" ); + GetObjectShell()->SetNamedVisibility_Impl(); + m_pImpl->nDocViewNo = GetObjectShell()->GetNoSet_Impl().GetFreeIndex()+1; +} + +void SfxViewFrame::Enable( bool bEnable ) +{ + if ( bEnable == m_pImpl->bEnabled ) + return; + + m_pImpl->bEnabled = bEnable; + + vcl::Window *pWindow = &GetFrame().GetWindow(); + if ( !bEnable ) + m_pImpl->bWindowWasEnabled = pWindow->IsInputEnabled(); + if ( !bEnable || m_pImpl->bWindowWasEnabled ) + pWindow->EnableInput( bEnable ); + + // cursor and focus + SfxViewShell* pViewSh = GetViewShell(); + if ( bEnable ) + { + // show cursor + if ( pViewSh ) + pViewSh->ShowCursor(); + } + else + { + // hide cursor + if ( pViewSh ) + pViewSh->ShowCursor(false); + } +} + +/* [Description] + + This method makes the Frame-Window visible and before transmits the + window name. In addition, the document is held. In general one can never + show the window directly! +*/ +void SfxViewFrame::Show() +{ + // First lock the objectShell so that UpdateTitle() is valid: + // IsVisible() == true (:#) + if ( m_xObjSh.is() ) + { + m_xObjSh->GetMedium()->GetItemSet().ClearItem( SID_HIDDEN ); + if ( !m_pImpl->bObjLocked ) + LockObjectShell_Impl(); + + // Adjust Doc-Shell title number, get unique view-no + if ( 0 == m_pImpl->nDocViewNo ) + { + GetDocNumber_Impl(); + UpdateTitle(); + } + } + else + UpdateTitle(); + + // Display Frame-window, but only if the ViewFrame has no window of its + // own or if it does not contain a Component + GetWindow().Show(); + GetFrame().GetWindow().Show(); +} + + +bool SfxViewFrame::IsVisible() const +{ + return m_pImpl->bObjLocked; +} + + +void SfxViewFrame::LockObjectShell_Impl() +{ + DBG_ASSERT( !m_pImpl->bObjLocked, "Wrong Locked status!" ); + + DBG_ASSERT( GetObjectShell(), "No Document!" ); + GetObjectShell()->OwnerLock(true); + m_pImpl->bObjLocked = true; +} + + +void SfxViewFrame::MakeActive_Impl( bool bGrabFocus ) +{ + if ( !GetViewShell() || GetFrame().IsClosing_Impl() ) + return; + + if ( !IsVisible() ) + return; + + bool bPreview = false; + if (GetObjectShell()->IsPreview()) + { + bPreview = true; + } + + css::uno::Reference<css::frame::XFrame> xFrame = GetFrame().GetFrameInterface(); + if (!bPreview) + { + SetViewFrame(this); + GetBindings().SetActiveFrame(css::uno::Reference<css::frame::XFrame>()); + uno::Reference<frame::XFramesSupplier> xSupp(xFrame, uno::UNO_QUERY); + if (xSupp.is()) + xSupp->setActiveFrame(uno::Reference<frame::XFrame>()); + + css::uno::Reference< css::awt::XWindow > xContainerWindow = xFrame->getContainerWindow(); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xContainerWindow); + if (pWindow && pWindow->HasChildPathFocus() && bGrabFocus) + { + SfxInPlaceClient *pCli = GetViewShell()->GetUIActiveClient(); + if (!pCli || !pCli->IsObjectUIActive()) + GetFrame().GrabFocusOnComponent_Impl(); + } + } + else + { + GetBindings().SetDispatcher(GetDispatcher()); + GetBindings().SetActiveFrame(css::uno::Reference<css::frame::XFrame>()); + GetDispatcher()->Update_Impl(); + } +} + +SfxObjectShell* SfxViewFrame::GetObjectShell() +{ + return m_xObjSh.get(); +} + +const Size& SfxViewFrame::GetMargin_Impl() const +{ + return m_pImpl->aMargin; +} + +SfxViewFrame* SfxViewFrame::LoadViewIntoFrame_Impl_NoThrow( const SfxObjectShell& i_rDoc, const Reference< XFrame >& i_rFrame, + const SfxInterfaceId i_nViewId, const bool i_bHidden ) +{ + Reference< XFrame > xFrame( i_rFrame ); + bool bOwnFrame = false; + SfxViewShell* pSuccessView = nullptr; + try + { + if ( !xFrame.is() ) + { + Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); + + if ( !i_bHidden ) + { + try + { + // if there is a backing component, use it + ::framework::FrameListAnalyzer aAnalyzer( xDesktop, Reference< XFrame >(), FrameAnalyzerFlags::BackingComponent ); + + if ( aAnalyzer.m_xBackingComponent.is() ) + xFrame = aAnalyzer.m_xBackingComponent; + } + catch( uno::Exception& ) + {} + } + + if ( !xFrame.is() ) + xFrame.set( xDesktop->findFrame( "_blank", 0 ), UNO_SET_THROW ); + + bOwnFrame = true; + } + + pSuccessView = LoadViewIntoFrame_Impl( + i_rDoc, + xFrame, + Sequence< PropertyValue >(), // means "reuse existing model's args" + i_nViewId, + i_bHidden + ); + + if ( bOwnFrame && !i_bHidden ) + { + // ensure the frame/window is visible + Reference< XWindow > xContainerWindow( xFrame->getContainerWindow(), UNO_SET_THROW ); + xContainerWindow->setVisible( true ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + + if ( pSuccessView ) + return &pSuccessView->GetViewFrame(); + + if ( bOwnFrame ) + { + try + { + xFrame->dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } + } + + return nullptr; +} + +SfxViewShell* SfxViewFrame::LoadViewIntoFrame_Impl( const SfxObjectShell& i_rDoc, const Reference< XFrame >& i_rFrame, + const Sequence< PropertyValue >& i_rLoadArgs, const SfxInterfaceId i_nViewId, + const bool i_bHidden ) +{ + Reference< XModel > xDocument( i_rDoc.GetModel(), UNO_SET_THROW ); + + ::comphelper::NamedValueCollection aTransformLoadArgs( i_rLoadArgs.hasElements() ? i_rLoadArgs : xDocument->getArgs() ); + aTransformLoadArgs.put( "Model", xDocument ); + if ( i_nViewId ) + aTransformLoadArgs.put( "ViewId", sal_uInt16( i_nViewId ) ); + if ( i_bHidden ) + aTransformLoadArgs.put( "Hidden", i_bHidden ); + else + aTransformLoadArgs.remove( "Hidden" ); + + Reference< XComponentLoader > xLoader( i_rFrame, UNO_QUERY_THROW ); + xLoader->loadComponentFromURL( "private:object", "_self", 0, + aTransformLoadArgs.getPropertyValues() ); + + SfxViewShell* pViewShell = SfxViewShell::Get( i_rFrame->getController() ); + ENSURE_OR_THROW( pViewShell, + "SfxViewFrame::LoadViewIntoFrame_Impl: loading an SFX doc into a frame resulted in a non-SFX view - quite impossible" ); + return pViewShell; +} + +SfxViewFrame* SfxViewFrame::LoadHiddenDocument( SfxObjectShell const & i_rDoc, SfxInterfaceId i_nViewId ) +{ + return LoadViewIntoFrame_Impl_NoThrow( i_rDoc, Reference< XFrame >(), i_nViewId, true ); +} + +SfxViewFrame* SfxViewFrame::LoadDocument( SfxObjectShell const & i_rDoc, SfxInterfaceId i_nViewId ) +{ + return LoadViewIntoFrame_Impl_NoThrow( i_rDoc, Reference< XFrame >(), i_nViewId, false ); +} + +SfxViewFrame* SfxViewFrame::LoadDocumentIntoFrame( SfxObjectShell const & i_rDoc, const Reference< XFrame >& i_rTargetFrame ) +{ + return LoadViewIntoFrame_Impl_NoThrow( i_rDoc, i_rTargetFrame, SFX_INTERFACE_NONE, false ); +} + +SfxViewFrame* SfxViewFrame::LoadDocumentIntoFrame( SfxObjectShell const & i_rDoc, const SfxFrameItem* i_pFrameItem, SfxInterfaceId i_nViewId ) +{ + return LoadViewIntoFrame_Impl_NoThrow( i_rDoc, i_pFrameItem && i_pFrameItem->GetFrame() ? i_pFrameItem->GetFrame()->GetFrameInterface() : nullptr, i_nViewId, false ); +} + +SfxViewFrame* SfxViewFrame::DisplayNewDocument( SfxObjectShell const & i_rDoc, const SfxRequest& i_rCreateDocRequest ) +{ + const SfxUnoFrameItem* pFrameItem = i_rCreateDocRequest.GetArg<SfxUnoFrameItem>(SID_FILLFRAME); + const SfxBoolItem* pHiddenItem = i_rCreateDocRequest.GetArg<SfxBoolItem>(SID_HIDDEN); + + return LoadViewIntoFrame_Impl_NoThrow( + i_rDoc, + pFrameItem ? pFrameItem->GetFrame() : nullptr, + SFX_INTERFACE_NONE, + pHiddenItem && pHiddenItem->GetValue() + ); +} + +SfxViewFrame* SfxViewFrame::Get( const Reference< XController>& i_rController, const SfxObjectShell* i_pDoc ) +{ + if ( !i_rController.is() ) + return nullptr; + + const SfxObjectShell* pDoc = i_pDoc; + if ( !pDoc ) + { + Reference< XModel > xDocument( i_rController->getModel() ); + for ( pDoc = SfxObjectShell::GetFirst( nullptr, false ); + pDoc; + pDoc = SfxObjectShell::GetNext( *pDoc, nullptr, false ) + ) + { + if ( pDoc->GetModel() == xDocument ) + break; + } + } + + SfxViewFrame* pViewFrame = nullptr; + for ( pViewFrame = SfxViewFrame::GetFirst( pDoc, false ); + pViewFrame; + pViewFrame = SfxViewFrame::GetNext( *pViewFrame, pDoc, false ) + ) + { + if ( pViewFrame->GetViewShell()->GetController() == i_rController ) + break; + } + + return pViewFrame; +} + +void SfxViewFrame::SaveCurrentViewData_Impl( const SfxInterfaceId i_nNewViewId ) +{ + SfxViewShell* pCurrentShell = GetViewShell(); + ENSURE_OR_RETURN_VOID( pCurrentShell != nullptr, "SfxViewFrame::SaveCurrentViewData_Impl: no current view shell -> no current view data!" ); + + // determine the logical (API) view name + const SfxObjectFactory& rDocFactory( pCurrentShell->GetObjectShell()->GetFactory() ); + const sal_uInt16 nCurViewNo = rDocFactory.GetViewNo_Impl( GetCurViewId(), 0 ); + const OUString sCurrentViewName = rDocFactory.GetViewFactory( nCurViewNo ).GetAPIViewName(); + const sal_uInt16 nNewViewNo = rDocFactory.GetViewNo_Impl( i_nNewViewId, 0 ); + const OUString sNewViewName = rDocFactory.GetViewFactory( nNewViewNo ).GetAPIViewName(); + if ( sCurrentViewName.isEmpty() || sNewViewName.isEmpty() ) + { + // can't say anything about the view, the respective application did not yet migrate its code to + // named view factories => bail out + OSL_FAIL( "SfxViewFrame::SaveCurrentViewData_Impl: views without API names? Shouldn't happen anymore?" ); + return; + } + SAL_WARN_IF(sNewViewName == sCurrentViewName, "sfx.view", "SfxViewFrame::SaveCurrentViewData_Impl: suspicious: new and old view name are identical!"); + + // save the view data only when we're moving from a non-print-preview to the print-preview view + if ( sNewViewName != "PrintPreview" ) + return; + + // retrieve the view data from the view + Sequence< PropertyValue > aViewData; + pCurrentShell->WriteUserDataSequence( aViewData ); + + try + { + // retrieve view data (for *all* views) from the model + const Reference< XController > xController( pCurrentShell->GetController(), UNO_SET_THROW ); + const Reference< XViewDataSupplier > xViewDataSupplier( xController->getModel(), UNO_QUERY_THROW ); + const Reference< XIndexContainer > xViewData( xViewDataSupplier->getViewData(), UNO_QUERY_THROW ); + + // look up the one view data item which corresponds to our current view, and remove it + const sal_Int32 nCount = xViewData->getCount(); + for ( sal_Int32 i=0; i<nCount; ++i ) + { + const ::comphelper::NamedValueCollection aCurViewData( xViewData->getByIndex(i) ); + const OUString sViewId( aCurViewData.getOrDefault( "ViewId", OUString() ) ); + if ( sViewId.isEmpty() ) + continue; + + const SfxViewFactory* pViewFactory = rDocFactory.GetViewFactoryByViewName( sViewId ); + if ( pViewFactory == nullptr ) + continue; + + if ( pViewFactory->GetOrdinal() == GetCurViewId() ) + { + xViewData->removeByIndex(i); + break; + } + } + + // then replace it with the most recent view data we just obtained + xViewData->insertByIndex( 0, Any( aViewData ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.view"); + } +} + +/* [Description] + + Internal Method for switching to another <SfxViewShell> subclass, + which should be created in this SfxMDIFrame. If no SfxViewShell exist + in this SfxMDIFrame, then one will first be created. + + + [Return Value] + + bool true + requested SfxViewShell was created and a + possibly existing one deleted + + false + SfxViewShell requested could not be created, + the existing SfxViewShell thus continue to exist +*/ +bool SfxViewFrame::SwitchToViewShell_Impl +( + sal_uInt16 nViewIdOrNo, /* > 0 + Registration-Id of the View, to which the + method should switch, for example the one + that will be created. + + == 0 + First use the Default view. */ + + bool bIsIndex /* true + 'nViewIdOrNo' is no Registration-Id instead + an Index of <SfxViewFrame> in <SfxObjectShell>. + */ +) +{ + try + { + ENSURE_OR_THROW( GetObjectShell() != nullptr, "not possible without a document" ); + + // if we already have a view shell, remove it + SfxViewShell* pOldSh = GetViewShell(); + OSL_PRECOND( pOldSh, "SfxViewFrame::SwitchToViewShell_Impl: that's called *switch* (not for *initial-load*) for a reason" ); + if ( pOldSh ) + { + // ask whether it can be closed + if ( !pOldSh->PrepareClose() ) + return false; + + // remove sub shells from Dispatcher before switching to new ViewShell + PopShellAndSubShells_Impl( *pOldSh ); + } + + GetBindings().ENTERREGISTRATIONS(); + LockAdjustPosSizePixel(); + + // ID of the new view + SfxObjectFactory& rDocFact = GetObjectShell()->GetFactory(); + const SfxInterfaceId nViewId = ( bIsIndex || !nViewIdOrNo ) ? rDocFact.GetViewFactory( nViewIdOrNo ).GetOrdinal() : SfxInterfaceId(nViewIdOrNo); + + // save the view data of the old view, so it can be restored later on (when needed) + SaveCurrentViewData_Impl( nViewId ); + + if (pOldSh) + pOldSh->SetDying(); + + // create and load new ViewShell + SfxViewShell* pNewSh = LoadViewIntoFrame_Impl( + *GetObjectShell(), + GetFrame().GetFrameInterface(), + Sequence< PropertyValue >(), // means "reuse existing model's args" + nViewId, + false + ); + + // allow resize events to be processed + UnlockAdjustPosSizePixel(); + + if ( GetWindow().IsReallyVisible() ) + DoAdjustPosSizePixel( pNewSh, Point(), GetWindow().GetOutputSizePixel(), false ); + + GetBindings().LEAVEREGISTRATIONS(); + delete pOldSh; + } + catch ( const css::uno::Exception& ) + { + // the SfxCode is not able to cope with exceptions thrown while creating views + // the code will crash in the stack unwinding procedure, so we shouldn't let exceptions go through here + DBG_UNHANDLED_EXCEPTION("sfx.view"); + return false; + } + + DBG_ASSERT( SfxGetpApp()->GetViewFrames_Impl().size() == SfxGetpApp()->GetViewShells_Impl().size(), "Inconsistent view arrays!" ); + return true; +} + +void SfxViewFrame::SetCurViewId_Impl( const SfxInterfaceId i_nID ) +{ + m_pImpl->nCurViewId = i_nID; +} + +SfxInterfaceId SfxViewFrame::GetCurViewId() const +{ + return m_pImpl->nCurViewId; +} + +/* [Description] + + Internal method to run the slot for the <SfxShell> Subclass in the + SfxViewFrame <SVIDL> described slots. +*/ +void SfxViewFrame::ExecView_Impl +( + SfxRequest& rReq // The executable <SfxRequest> +) +{ + + // If the Shells are just being replaced... + if ( !GetObjectShell() || !GetViewShell() ) + return; + + switch ( rReq.GetSlot() ) + { + case SID_TERMINATE_INPLACEACTIVATION : + { + SfxInPlaceClient* pClient = GetViewShell()->GetUIActiveClient(); + if ( pClient ) + pClient->DeactivateObject(); + break; + } + + case SID_VIEWSHELL: + { + const SfxUInt16Item *pItem = nullptr; + if ( rReq.GetArgs() + && (pItem = rReq.GetArgs()->GetItemIfSet( SID_VIEWSHELL, false )) + ) + { + const sal_uInt16 nViewId = pItem->GetValue(); + bool bSuccess = SwitchToViewShell_Impl( nViewId ); + rReq.SetReturnValue( SfxBoolItem( 0, bSuccess ) ); + } + break; + } + + case SID_VIEWSHELL0: + case SID_VIEWSHELL1: + case SID_VIEWSHELL2: + case SID_VIEWSHELL3: + case SID_VIEWSHELL4: + { + const sal_uInt16 nViewNo = rReq.GetSlot() - SID_VIEWSHELL0; + bool bSuccess = SwitchToViewShell_Impl( nViewNo, true ); + rReq.SetReturnValue( SfxBoolItem( 0, bSuccess ) ); + break; + } + + case SID_NEWWINDOW: + { + // Hack. at the moment a virtual Function + if ( !GetViewShell()->NewWindowAllowed() ) + { + OSL_FAIL( "You should have disabled the 'Window/New Window' slot!" ); + return; + } + + // Get ViewData of FrameSets recursively. + GetFrame().GetViewData_Impl(); + SfxMedium* pMed = GetObjectShell()->GetMedium(); + + // do not open the new window hidden + pMed->GetItemSet().ClearItem( SID_HIDDEN ); + + // the view ID (optional arg. TODO: this is currently not supported in the slot definition ...) + const SfxUInt16Item* pViewIdItem = rReq.GetArg<SfxUInt16Item>(SID_VIEW_ID); + const SfxInterfaceId nViewId = pViewIdItem ? SfxInterfaceId(pViewIdItem->GetValue()) : GetCurViewId(); + + Reference < XFrame > xFrame; + // the frame (optional arg. TODO: this is currently not supported in the slot definition ...) + const SfxUnoFrameItem* pFrameItem = rReq.GetArg<SfxUnoFrameItem>(SID_FILLFRAME); + if ( pFrameItem ) + xFrame = pFrameItem->GetFrame(); + + LoadViewIntoFrame_Impl_NoThrow( *GetObjectShell(), xFrame, nViewId, false ); + + rReq.Done(); + break; + } + + case SID_OBJECT: + { + const SfxInt16Item* pItem = rReq.GetArg<SfxInt16Item>(SID_OBJECT); + + if (pItem) + { + GetViewShell()->DoVerb( pItem->GetValue() ); + rReq.Done(); + break; + } + } + } +} + +/* TODO as96863: + This method try to collect information about the count of currently open documents. + But the algorithm is implemented very simple ... + E.g. hidden documents should be ignored here ... but they are counted. + TODO: export special helper "framework::FrameListAnalyzer" within the framework module + and use it here. +*/ +static bool impl_maxOpenDocCountReached() +{ + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + std::optional<sal_Int32> x(officecfg::Office::Common::Misc::MaxOpenDocuments::get()); + // NIL means: count of allowed documents = infinite ! + if (!x) + return false; + sal_Int32 nMaxDocs(*x); + sal_Int32 nOpenDocs = 0; + + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(xContext); + css::uno::Reference< css::container::XIndexAccess > xCont(xDesktop->getFrames(), css::uno::UNO_QUERY_THROW); + + sal_Int32 c = xCont->getCount(); + sal_Int32 i = 0; + + for (i=0; i<c; ++i) + { + try + { + css::uno::Reference< css::frame::XFrame > xFrame; + xCont->getByIndex(i) >>= xFrame; + if ( ! xFrame.is()) + continue; + + // a) do not count the help window + if ( xFrame->getName() == "OFFICE_HELP_TASK" ) + continue; + + // b) count all other frames + ++nOpenDocs; + } + catch(const css::uno::Exception&) + // An IndexOutOfBoundsException can happen in multithreaded + // environments, where any other thread can change this + // container ! + { continue; } + } + + return (nOpenDocs >= nMaxDocs); +} + +/* [Description] + + This internal method returns in 'rSet' the Status for the <SfxShell> + Subclass SfxViewFrame in the <SVIDL> described <Slots>. + + Thus exactly those Slots-IDs that are recognized as being invalid by Sfx + are included as Which-ranges in 'rSet'. If there exists a mapping for + single slot-IDs of the <SfxItemPool> set in the shell, then the respective + Which-IDs are used so that items can be replaced directly with a working + Core::sun::com::star::script::Engine of the Which-IDs if possible. . +*/ +void SfxViewFrame::StateView_Impl +( + SfxItemSet& rSet /* empty <SfxItemSet> with <Which-Ranges>, + which describes the Slot Ids */ +) +{ + + SfxObjectShell *pDocSh = GetObjectShell(); + + if ( !pDocSh ) + // I'm just on reload and am yielding myself ... + return; + + const WhichRangesContainer & pRanges = rSet.GetRanges(); + assert(!pRanges.empty() && "Set with no Range"); + for ( auto const & pRange : pRanges ) + { + sal_uInt16 nStartWhich = pRange.first; + sal_uInt16 nEndWhich = pRange.second; + for ( sal_uInt16 nWhich = nStartWhich; nWhich <= nEndWhich; ++nWhich ) + { + switch(nWhich) + { + case SID_VIEWSHELL: + { + rSet.Put( SfxUInt16Item( nWhich, sal_uInt16(m_pImpl->nCurViewId )) ); + break; + } + + case SID_VIEWSHELL0: + case SID_VIEWSHELL1: + case SID_VIEWSHELL2: + case SID_VIEWSHELL3: + case SID_VIEWSHELL4: + { + sal_uInt16 nViewNo = nWhich - SID_VIEWSHELL0; + if ( GetObjectShell()->GetFactory().GetViewFactoryCount() > + nViewNo && !GetObjectShell()->IsInPlaceActive() ) + { + SfxViewFactory &rViewFactory = + GetObjectShell()->GetFactory().GetViewFactory(nViewNo); + rSet.Put( SfxBoolItem( + nWhich, m_pImpl->nCurViewId == rViewFactory.GetOrdinal() ) ); + } + else + rSet.DisableItem( nWhich ); + break; + } + + case SID_NEWWINDOW: + { + if ( !GetViewShell()->NewWindowAllowed() + || impl_maxOpenDocCountReached() + ) + rSet.DisableItem( nWhich ); + break; + } + } + } + } +} + + +void SfxViewFrame::ToTop() +{ + GetFrame().Appear(); +} + + +/* [Description] + + GetFrame returns the Frame, in which the ViewFrame is located. +*/ +SfxFrame& SfxViewFrame::GetFrame() const +{ + return m_pImpl->rFrame; +} + +SfxViewFrame* SfxViewFrame::GetTopViewFrame() const +{ + return GetFrame().GetCurrentViewFrame(); +} + +vcl::Window& SfxViewFrame::GetWindow() const +{ + return m_pImpl->pWindow ? *m_pImpl->pWindow : GetFrame().GetWindow(); +} + +weld::Window* SfxViewFrame::GetFrameWeld() const +{ + return GetWindow().GetFrameWeld(); +} + +bool SfxViewFrame::DoClose() +{ + return GetFrame().DoClose(); +} + +OUString SfxViewFrame::GetActualPresentationURL_Impl() const +{ + if ( m_xObjSh.is() ) + return m_xObjSh->GetMedium()->GetName(); + return OUString(); +} + +void SfxViewFrame::SetModalMode( bool bModal ) +{ + // no real modality for LOK + if (comphelper::LibreOfficeKit::isActive()) + return; + + m_pImpl->bModal = bModal; + if ( m_xObjSh.is() ) + { + for ( SfxViewFrame* pFrame = SfxViewFrame::GetFirst( m_xObjSh.get() ); + !bModal && pFrame; pFrame = SfxViewFrame::GetNext( *pFrame, m_xObjSh.get() ) ) + bModal = pFrame->m_pImpl->bModal; + m_xObjSh->SetModalMode_Impl( bModal ); + } +} + +bool SfxViewFrame::IsInModalMode() const +{ + return m_pImpl->bModal || GetFrame().GetWindow().IsInModalMode(); +} + +void SfxViewFrame::Resize( bool bForce ) +{ + Size aSize = GetWindow().GetOutputSizePixel(); + if ( !bForce && aSize == m_pImpl->aSize ) + return; + + m_pImpl->aSize = aSize; + SfxViewShell *pShell = GetViewShell(); + if ( pShell ) + { + if ( GetFrame().IsInPlace() ) + { + Point aPoint = GetWindow().GetPosPixel(); + DoAdjustPosSizePixel( pShell, aPoint, aSize, true ); + } + else + { + DoAdjustPosSizePixel( pShell, Point(), aSize, false ); + } + } +} + +#if HAVE_FEATURE_SCRIPTING + +#define LINE_SEP 0x0A + +static void CutLines( OUString& rStr, sal_Int32 nStartLine, sal_Int32 nLines ) +{ + sal_Int32 nStartPos = 0; + sal_Int32 nLine = 0; + while ( nLine < nStartLine ) + { + nStartPos = rStr.indexOf( LINE_SEP, nStartPos ); + if( nStartPos == -1 ) + break; + nStartPos++; // not the \n. + nLine++; + } + + SAL_WARN_IF(nStartPos == -1, "sfx.view", "CutLines: Start row not found!"); + + if ( nStartPos != -1 ) + { + sal_Int32 nEndPos = nStartPos; + for ( sal_Int32 i = 0; i < nLines; i++ ) + nEndPos = rStr.indexOf( LINE_SEP, nEndPos+1 ); + + if ( nEndPos == -1 ) // Can happen at the last row. + nEndPos = rStr.getLength(); + else + nEndPos++; + + rStr = OUString::Concat(rStr.subView( 0, nStartPos )) + rStr.subView( nEndPos ); + } + // erase trailing lines + if ( nStartPos != -1 ) + { + sal_Int32 n = nStartPos; + sal_Int32 nLen = rStr.getLength(); + while ( ( n < nLen ) && ( rStr[ n ] == LINE_SEP ) ) + n++; + + if ( n > nStartPos ) + rStr = OUString::Concat(rStr.subView( 0, nStartPos )) + rStr.subView( n ); + } +} + +#endif + +/* + add new recorded dispatch macro script into the application global basic + lib container. It generates a new unique id for it and insert the macro + by using this number as name for the module + */ +void SfxViewFrame::AddDispatchMacroToBasic_Impl( const OUString& sMacro ) +{ +#if !HAVE_FEATURE_SCRIPTING + (void) sMacro; +#else + if ( sMacro.isEmpty() ) + return; + + SfxApplication* pSfxApp = SfxGetpApp(); + SfxItemPool& rPool = pSfxApp->GetPool(); + SfxRequest aReq(SID_BASICCHOOSER, SfxCallMode::SYNCHRON, rPool); + + //seen in tdf#122598, no parent for subsequent dialog + SfxAllItemSet aSet(rPool); + css::uno::Reference< css::frame::XFrame > xFrame = + GetFrame().GetFrameInterface(); + aSet.Put(SfxUnoFrameItem(SID_FILLFRAME, xFrame)); + aReq.SetInternalArgs_Impl(aSet); + + aReq.AppendItem( SfxBoolItem(SID_RECORDMACRO,true) ); + const SfxPoolItemHolder& rResult(SfxGetpApp()->ExecuteSlot(aReq)); + OUString aScriptURL; + if (nullptr != rResult.getItem()) + aScriptURL = static_cast<const SfxStringItem*>(rResult.getItem())->GetValue(); + if ( !aScriptURL.isEmpty() ) + { + // parse scriptURL + OUString aLibName; + OUString aModuleName; + OUString aMacroName; + OUString aLocation; + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< css::uri::XUriReferenceFactory > xFactory = + css::uri::UriReferenceFactory::create( xContext ); + Reference< css::uri::XVndSunStarScriptUrl > xUrl( xFactory->parse( aScriptURL ), UNO_QUERY ); + if ( xUrl.is() ) + { + // get name + const OUString aName = xUrl->getName(); + const sal_Unicode cTok = '.'; + sal_Int32 nIndex = 0; + aLibName = aName.getToken( 0, cTok, nIndex ); + if ( nIndex != -1 ) + aModuleName = aName.getToken( 0, cTok, nIndex ); + if ( nIndex != -1 ) + aMacroName = aName.getToken( 0, cTok, nIndex ); + + // get location + aLocation = xUrl->getParameter( "location" ); + } + + BasicManager* pBasMgr = nullptr; + if ( aLocation == "application" ) + { + // application basic + pBasMgr = SfxApplication::GetBasicManager(); + } + else if ( aLocation == "document" ) + { + pBasMgr = GetObjectShell()->GetBasicManager(); + } + + OUString aOUSource; + if ( pBasMgr) + { + StarBASIC* pBasic = pBasMgr->GetLib( aLibName ); + if ( pBasic ) + { + SbModule* pModule = pBasic->FindModule( aModuleName ); + SbMethod* pMethod = pModule ? pModule->FindMethod(aMacroName, SbxClassType::Method) : nullptr; + if (pMethod) + { + aOUSource = pModule->GetSource32(); + sal_uInt16 nStart, nEnd; + pMethod->GetLineRange( nStart, nEnd ); + sal_uInt16 nlStart = nStart; + sal_uInt16 nlEnd = nEnd; + CutLines( aOUSource, nlStart-1, nlEnd-nlStart+1 ); + } + } + } + + // open lib container and break operation if it couldn't be opened + css::uno::Reference< css::script::XLibraryContainer > xLibCont; + if ( aLocation == "application" ) + { + xLibCont = SfxGetpApp()->GetBasicContainer(); + } + else if ( aLocation == "document" ) + { + xLibCont = GetObjectShell()->GetBasicContainer(); + } + + if(!xLibCont.is()) + { + SAL_WARN("sfx.view", "couldn't get access to the basic lib container. Adding of macro isn't possible."); + return; + } + + // get LibraryContainer + css::uno::Any aTemp; + + css::uno::Reference< css::container::XNameAccess > xLib; + if(xLibCont->hasByName(aLibName)) + { + // library must be loaded + aTemp = xLibCont->getByName(aLibName); + xLibCont->loadLibrary(aLibName); + aTemp >>= xLib; + } + else + { + xLib = xLibCont->createLibrary(aLibName); + } + + // pack the macro as direct usable "sub" routine + OUStringBuffer sRoutine(10000); + bool bReplace = false; + + // get module + if(xLib->hasByName(aModuleName)) + { + if ( !aOUSource.isEmpty() ) + { + sRoutine.append( aOUSource ); + } + else + { + OUString sCode; + aTemp = xLib->getByName(aModuleName); + aTemp >>= sCode; + sRoutine.append( sCode ); + } + + bReplace = true; + } + + // append new method + sRoutine.append( "\nsub " + + aMacroName + + "\n" + + sMacro + + "\nend sub\n" ); + + // create the module inside the library and insert the macro routine + aTemp <<= sRoutine.makeStringAndClear(); + if ( bReplace ) + { + css::uno::Reference< css::container::XNameContainer > xModulCont( + xLib, + css::uno::UNO_QUERY); + xModulCont->replaceByName(aModuleName,aTemp); + } + else + { + css::uno::Reference< css::container::XNameContainer > xModulCont( + xLib, + css::uno::UNO_QUERY); + xModulCont->insertByName(aModuleName,aTemp); + } + + // #i17355# update the Basic IDE + for ( SfxViewShell* pViewShell = SfxViewShell::GetFirst(); pViewShell; pViewShell = SfxViewShell::GetNext( *pViewShell ) ) + { + if ( pViewShell->GetName() == "BasicIDE" ) + { + SfxViewFrame& rViewFrame = pViewShell->GetViewFrame(); + SfxDispatcher* pDispat = rViewFrame.GetDispatcher(); + if ( pDispat ) + { + SfxMacroInfoItem aInfoItem( SID_BASICIDE_ARG_MACROINFO, pBasMgr, aLibName, aModuleName, OUString(), OUString() ); + pDispat->ExecuteList(SID_BASICIDE_UPDATEMODULESOURCE, + SfxCallMode::SYNCHRON, { &aInfoItem }); + } + } + } + } + else + { + // add code for "session only" macro + } +#endif +} + +void SfxViewFrame::MiscExec_Impl( SfxRequest& rReq ) +{ + switch ( rReq.GetSlot() ) + { + case SID_STOP_RECORDING : + case SID_RECORDMACRO : + { + // try to find any active recorder on this frame + static constexpr OUString sProperty(u"DispatchRecorderSupplier"_ustr); + css::uno::Reference< css::frame::XFrame > xFrame = + GetFrame().GetFrameInterface(); + + css::uno::Reference< css::beans::XPropertySet > xSet(xFrame,css::uno::UNO_QUERY); + css::uno::Any aProp = xSet->getPropertyValue(sProperty); + css::uno::Reference< css::frame::XDispatchRecorderSupplier > xSupplier; + aProp >>= xSupplier; + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder; + if (xSupplier.is()) + xRecorder = xSupplier->getDispatchRecorder(); + + bool bIsRecording = xRecorder.is(); + const SfxBoolItem* pItem = rReq.GetArg<SfxBoolItem>(SID_RECORDMACRO); + if ( pItem && pItem->GetValue() == bIsRecording ) + return; + + if ( xRecorder.is() ) + { + // disable active recording + aProp <<= css::uno::Reference< css::frame::XDispatchRecorderSupplier >(); + xSet->setPropertyValue(sProperty,aProp); + + const SfxBoolItem* pRecordItem = rReq.GetArg<SfxBoolItem>(FN_PARAM_1); + if ( !pRecordItem || !pRecordItem->GetValue() ) + // insert script into basic library container of application + AddDispatchMacroToBasic_Impl(xRecorder->getRecordedMacro()); + + xRecorder->endRecording(); + xRecorder = nullptr; + GetBindings().SetRecorder_Impl( xRecorder ); + + SetChildWindow( SID_RECORDING_FLOATWINDOW, false ); + if ( rReq.GetSlot() != SID_RECORDMACRO ) + GetBindings().Invalidate( SID_RECORDMACRO ); + } + else if ( rReq.GetSlot() == SID_RECORDMACRO ) + { + // enable recording + css::uno::Reference< css::uno::XComponentContext > xContext( + ::comphelper::getProcessComponentContext()); + + xRecorder = css::frame::DispatchRecorder::create( xContext ); + + xSupplier = css::frame::DispatchRecorderSupplier::create( xContext ); + + xSupplier->setDispatchRecorder(xRecorder); + xRecorder->startRecording(xFrame); + aProp <<= xSupplier; + xSet->setPropertyValue(sProperty,aProp); + GetBindings().SetRecorder_Impl( xRecorder ); + SetChildWindow( SID_RECORDING_FLOATWINDOW, true ); + } + + rReq.Done(); + break; + } + + case SID_TOGGLESTATUSBAR: + { + if ( auto xLayoutManager = getLayoutManager(GetFrame()) ) + { + static constexpr OUString aStatusbarResString( u"private:resource/statusbar/statusbar"_ustr ); + // Evaluate parameter. + const SfxBoolItem* pShowItem = rReq.GetArg<SfxBoolItem>(rReq.GetSlot()); + bool bShow( true ); + if ( !pShowItem ) + bShow = xLayoutManager->isElementVisible( aStatusbarResString ); + else + bShow = pShowItem->GetValue(); + + if ( bShow ) + { + xLayoutManager->createElement( aStatusbarResString ); + xLayoutManager->showElement( aStatusbarResString ); + } + else + xLayoutManager->hideElement( aStatusbarResString ); + + if ( !pShowItem ) + rReq.AppendItem( SfxBoolItem( SID_TOGGLESTATUSBAR, bShow ) ); + } + rReq.Done(); + break; + } + case SID_COMMAND_POPUP: + { + tools::Rectangle aRectangle(Point(0,0), GetWindow().GetSizePixel()); + weld::Window* pParent = weld::GetPopupParent(GetWindow(), aRectangle); + m_pCommandPopupHandler->showPopup(pParent, GetFrame().GetFrameInterface()); + + rReq.Done(); + break; + } + case SID_WIN_FULLSCREEN: + { + const SfxBoolItem* pItem = rReq.GetArg<SfxBoolItem>(rReq.GetSlot()); + SfxViewFrame *pTop = GetTopViewFrame(); + if ( pTop ) + { + WorkWindow* pWork = static_cast<WorkWindow*>( pTop->GetFrame().GetTopWindow_Impl() ); + if ( pWork ) + { + Reference< css::frame::XLayoutManager > xLayoutManager = getLayoutManager(GetFrame()); + bool bNewFullScreenMode = pItem ? pItem->GetValue() : !pWork->IsFullScreenMode(); + if ( bNewFullScreenMode != pWork->IsFullScreenMode() ) + { + if ( bNewFullScreenMode ) + sfx2::SfxNotebookBar::LockNotebookBar(); + else + sfx2::SfxNotebookBar::UnlockNotebookBar(); + + Reference< css::beans::XPropertySet > xLMPropSet( xLayoutManager, UNO_QUERY ); + if ( xLMPropSet.is() ) + { + try + { + xLMPropSet->setPropertyValue( + "HideCurrentUI", + Any( bNewFullScreenMode )); + } + catch ( css::beans::UnknownPropertyException& ) + { + } + } + pWork->ShowFullScreenMode( bNewFullScreenMode ); + pWork->SetMenuBarMode( bNewFullScreenMode ? MenuBarMode::Hide : MenuBarMode::Normal ); + GetFrame().GetWorkWindow_Impl()->SetFullScreen_Impl( bNewFullScreenMode ); + if ( !pItem ) + rReq.AppendItem( SfxBoolItem( SID_WIN_FULLSCREEN, bNewFullScreenMode ) ); + rReq.Done(); + } + else + rReq.Ignore(); + } + } + else + rReq.Ignore(); + + GetDispatcher()->Update_Impl( true ); + break; + } + } +} + +void SfxViewFrame::MiscState_Impl(SfxItemSet &rSet) +{ + const WhichRangesContainer & pRanges = rSet.GetRanges(); + DBG_ASSERT(!pRanges.empty(), "Set without range"); + for ( auto const & pRange : pRanges ) + { + for(sal_uInt16 nWhich = pRange.first; nWhich <= pRange.second; ++nWhich) + { + switch(nWhich) + { + case SID_CURRENT_URL: + { + rSet.Put( SfxStringItem( nWhich, GetActualPresentationURL_Impl() ) ); + break; + } + + case SID_RECORDMACRO : + { + const OUString& sName{GetObjectShell()->GetFactory().GetFactoryName()}; + bool bMacrosDisabled = officecfg::Office::Common::Security::Scripting::DisableMacrosExecution::get(); + if (bMacrosDisabled || + !officecfg::Office::Common::Misc::MacroRecorderMode::get() || + ( sName!="swriter" && sName!="scalc" ) ) + { + rSet.DisableItem( nWhich ); + rSet.Put(SfxVisibilityItem(nWhich, false)); + break; + } + + css::uno::Reference< css::beans::XPropertySet > xSet( + GetFrame().GetFrameInterface(), + css::uno::UNO_QUERY); + + css::uno::Any aProp = xSet->getPropertyValue("DispatchRecorderSupplier"); + css::uno::Reference< css::frame::XDispatchRecorderSupplier > xSupplier; + if ( aProp >>= xSupplier ) + rSet.Put( SfxBoolItem( nWhich, xSupplier.is() ) ); + else + rSet.DisableItem( nWhich ); + break; + } + + case SID_STOP_RECORDING : + { + const OUString& sName{GetObjectShell()->GetFactory().GetFactoryName()}; + if ( !officecfg::Office::Common::Misc::MacroRecorderMode::get() || + ( sName!="swriter" && sName!="scalc" ) ) + { + rSet.DisableItem( nWhich ); + break; + } + + css::uno::Reference< css::beans::XPropertySet > xSet( + GetFrame().GetFrameInterface(), + css::uno::UNO_QUERY); + + css::uno::Any aProp = xSet->getPropertyValue("DispatchRecorderSupplier"); + css::uno::Reference< css::frame::XDispatchRecorderSupplier > xSupplier; + if ( !(aProp >>= xSupplier) || !xSupplier.is() ) + rSet.DisableItem( nWhich ); + break; + } + + case SID_TOGGLESTATUSBAR: + { + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + css::uno::Reference< css::beans::XPropertySet > xSet( + GetFrame().GetFrameInterface(), + css::uno::UNO_QUERY); + css::uno::Any aProp = xSet->getPropertyValue( "LayoutManager" ); + + if ( !( aProp >>= xLayoutManager )) + rSet.Put( SfxBoolItem( nWhich, false )); + else + { + bool bShow = xLayoutManager->isElementVisible( "private:resource/statusbar/statusbar" ); + rSet.Put( SfxBoolItem( nWhich, bShow )); + } + break; + } + + case SID_WIN_FULLSCREEN: + { + SfxViewFrame* pTop = GetTopViewFrame(); + if ( pTop ) + { + WorkWindow* pWork = static_cast<WorkWindow*>( pTop->GetFrame().GetTopWindow_Impl() ); + if ( pWork ) + { + rSet.Put( SfxBoolItem( nWhich, pWork->IsFullScreenMode() ) ); + break; + } + } + + rSet.DisableItem( nWhich ); + break; + } + + default: + break; + } + } + } +} + +/* [Description] + + This method can be included in the Execute method for the on- and off- + switching of ChildWindows, to implement this and API-bindings. + + Simply include as 'ExecuteMethod' in the IDL. +*/ +void SfxViewFrame::ChildWindowExecute( SfxRequest &rReq ) +{ + // Evaluate Parameter + sal_uInt16 nSID = rReq.GetSlot(); + + if (nSID == SID_SIDEBAR_DECK) + { + const SfxStringItem* pDeckIdItem = rReq.GetArg<SfxStringItem>(SID_SIDEBAR_DECK); + if (pDeckIdItem) + { + const OUString aDeckId(pDeckIdItem->GetValue()); + const SfxBoolItem* pToggleItem = rReq.GetArg<SfxBoolItem>(SID_SIDEBAR_DECK_TOGGLE); + bool bToggle = pToggleItem && pToggleItem->GetValue(); + ::sfx2::sidebar::Sidebar::ShowDeck(aDeckId, this, bToggle); + } + rReq.Done(); + return; + } + + const SfxBoolItem* pShowItem = rReq.GetArg<SfxBoolItem>(nSID); + if ( nSID == SID_VIEW_DATA_SOURCE_BROWSER ) + { + if (!SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::DATABASE)) + return; + Reference < XFrame > xFrame = GetFrame().GetFrameInterface(); + Reference < XFrame > xBeamer( xFrame->findFrame( "_beamer", FrameSearchFlag::CHILDREN ) ); + bool bHasChild = xBeamer.is(); + bool bShow = pShowItem ? pShowItem->GetValue() : !bHasChild; + if ( pShowItem ) + { + if( bShow == bHasChild ) + return; + } + else + rReq.AppendItem( SfxBoolItem( nSID, bShow ) ); + + if ( !bShow ) + { + SetChildWindow( SID_BROWSER, false ); + } + else + { + css::util::URL aTargetURL; + aTargetURL.Complete = ".component:DB/DataSourceBrowser"; + Reference < css::util::XURLTransformer > xTrans( + css::util::URLTransformer::create( + ::comphelper::getProcessComponentContext() ) ); + xTrans->parseStrict( aTargetURL ); + + Reference < XDispatchProvider > xProv( xFrame, UNO_QUERY ); + Reference < css::frame::XDispatch > xDisp; + if ( xProv.is() ) + xDisp = xProv->queryDispatch( aTargetURL, "_beamer", 31 ); + if ( xDisp.is() ) + { + Sequence < css::beans::PropertyValue > aArgs(1); + css::beans::PropertyValue* pArg = aArgs.getArray(); + pArg[0].Name = "Referer"; + pArg[0].Value <<= OUString("private:user"); + xDisp->dispatch( aTargetURL, aArgs ); + } + } + + rReq.Done(); + return; + } + if (nSID == SID_STYLE_DESIGNER) + { + // First make sure that the sidebar is visible + ShowChildWindow(SID_SIDEBAR); + + ::sfx2::sidebar::Sidebar::ShowPanel(u"StyleListPanel", + GetFrame().GetFrameInterface(), true); + rReq.Done(); + return; + } + if (nSID == SID_NAVIGATOR) + { + if (comphelper::LibreOfficeKit::isActive()) + { + ShowChildWindow(SID_SIDEBAR); + ::sfx2::sidebar::Sidebar::ShowDeck(u"NavigatorDeck", this, true); + rReq.Done(); + return; + } + } + + bool bHasChild = HasChildWindow(nSID); + bool bShow = pShowItem ? pShowItem->GetValue() : !bHasChild; + GetDispatcher()->Update_Impl( true ); + + // Perform action. + if ( !pShowItem || bShow != bHasChild ) + ToggleChildWindow( nSID ); + + GetBindings().Invalidate( nSID ); + + // Record if possible. + if ( nSID == SID_HYPERLINK_DIALOG || nSID == SID_SEARCH_DLG ) + { + rReq.Ignore(); + } + else + { + rReq.AppendItem( SfxBoolItem( nSID, bShow ) ); + rReq.Done(); + } +} + +/* [Description] + + This method can be used in the state method for the on and off-state + of child-windows, in order to implement this. + + Just register the IDL as 'StateMethod'. +*/ +void SfxViewFrame::ChildWindowState( SfxItemSet& rState ) +{ + SfxWhichIter aIter( rState ); + for ( sal_uInt16 nSID = aIter.FirstWhich(); nSID; nSID = aIter.NextWhich() ) + { + if ( nSID == SID_VIEW_DATA_SOURCE_BROWSER ) + { + rState.Put( SfxBoolItem( nSID, HasChildWindow( SID_BROWSER ) ) ); + } + else if ( nSID == SID_HYPERLINK_DIALOG ) + { + SfxPoolItemHolder aDummy; + SfxItemState eState = GetDispatcher()->QueryState(SID_HYPERLINK_SETLINK, aDummy); + if ( SfxItemState::DISABLED == eState ) + rState.DisableItem(nSID); + else + { + if ( KnowsChildWindow(nSID) ) + rState.Put( SfxBoolItem( nSID, HasChildWindow(nSID)) ); + else + rState.DisableItem(nSID); + } + } + else if ( nSID == SID_BROWSER ) + { + Reference < XFrame > xFrame = GetFrame().GetFrameInterface()-> + findFrame( "_beamer", FrameSearchFlag::CHILDREN ); + if ( !xFrame.is() ) + rState.DisableItem( nSID ); + else if ( KnowsChildWindow(nSID) ) + rState.Put( SfxBoolItem( nSID, HasChildWindow(nSID) ) ); + } + else if ( nSID == SID_SIDEBAR ) + { + if ( !KnowsChildWindow( nSID ) ) + { + SAL_INFO("sfx.view", "SID_SIDEBAR state requested, but no task pane child window exists for this ID!"); + rState.DisableItem( nSID ); + } + else + { + rState.Put( SfxBoolItem( nSID, HasChildWindow( nSID ) ) ); + } + } + else if ( KnowsChildWindow(nSID) ) + rState.Put( SfxBoolItem( nSID, HasChildWindow(nSID) ) ); + else + rState.DisableItem(nSID); + } +} + +SfxWorkWindow* SfxViewFrame::GetWorkWindow_Impl() +{ + SfxWorkWindow* pWork = GetFrame().GetWorkWindow_Impl(); + return pWork; +} + +void SfxViewFrame::SetChildWindow(sal_uInt16 nId, bool bOn, bool bSetFocus ) +{ + SfxWorkWindow* pWork = GetWorkWindow_Impl(); + if ( pWork ) + pWork->SetChildWindow_Impl( nId, bOn, bSetFocus ); +} + +void SfxViewFrame::ToggleChildWindow(sal_uInt16 nId) +{ + SfxWorkWindow* pWork = GetWorkWindow_Impl(); + if ( pWork ) + pWork->ToggleChildWindow_Impl( nId, true ); +} + +bool SfxViewFrame::HasChildWindow( sal_uInt16 nId ) +{ + SfxWorkWindow* pWork = GetWorkWindow_Impl(); + return pWork && pWork->HasChildWindow_Impl(nId); +} + +bool SfxViewFrame::KnowsChildWindow( sal_uInt16 nId ) +{ + SfxWorkWindow* pWork = GetWorkWindow_Impl(); + return pWork && pWork->KnowsChildWindow_Impl(nId); +} + +void SfxViewFrame::ShowChildWindow( sal_uInt16 nId, bool bVisible ) +{ + SfxWorkWindow* pWork = GetWorkWindow_Impl(); + if ( pWork ) + { + GetDispatcher()->Update_Impl(true); + pWork->ShowChildWindow_Impl(nId, bVisible, true ); + } +} + +SfxChildWindow* SfxViewFrame::GetChildWindow(sal_uInt16 nId) +{ + SfxWorkWindow* pWork = GetWorkWindow_Impl(); + return pWork ? pWork->GetChildWindow_Impl(nId) : nullptr; +} + +void SfxViewFrame::UpdateDocument_Impl() +{ + SfxObjectShell* pDoc = GetObjectShell(); + if ( pDoc->IsLoadingFinished() ) + pDoc->CheckSecurityOnLoading_Impl(); + + // check if document depends on a template + pDoc->UpdateFromTemplate_Impl(); +} + +void SfxViewFrame::SetViewFrame( SfxViewFrame* pFrame ) +{ + if(pFrame) + SetSVHelpData(pFrame->m_pHelpData); + + SetSVWinData(pFrame ? pFrame->m_pWinData : nullptr); + + SfxGetpApp()->SetViewFrame_Impl( pFrame ); +} + +VclPtr<SfxInfoBarWindow> SfxViewFrame::AppendInfoBar(const OUString& sId, + const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, + InfobarType aInfobarType, bool bShowCloseButton) +{ + SfxChildWindow* pChild = GetChildWindow(SfxInfoBarContainerChild::GetChildWindowId()); + if (!pChild) + return nullptr; + + if (HasInfoBarWithID(sId)) + return nullptr; + + SfxInfoBarContainerWindow* pInfoBarContainer = static_cast<SfxInfoBarContainerWindow*>(pChild->GetWindow()); + auto pInfoBar = pInfoBarContainer->appendInfoBar(sId, sPrimaryMessage, sSecondaryMessage, + aInfobarType, bShowCloseButton); + ShowChildWindow(SfxInfoBarContainerChild::GetChildWindowId()); + return pInfoBar; +} + +void SfxViewFrame::UpdateInfoBar(std::u16string_view sId, const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, InfobarType eType) +{ + const sal_uInt16 nId = SfxInfoBarContainerChild::GetChildWindowId(); + + // Make sure the InfoBar container is visible + if (!HasChildWindow(nId)) + ToggleChildWindow(nId); + + SfxChildWindow* pChild = GetChildWindow(nId); + if (pChild) + { + SfxInfoBarContainerWindow* pInfoBarContainer = static_cast<SfxInfoBarContainerWindow*>(pChild->GetWindow()); + auto pInfoBar = pInfoBarContainer->getInfoBar(sId); + + if (pInfoBar) + pInfoBar->Update(sPrimaryMessage, sSecondaryMessage, eType); + } +} + +void SfxViewFrame::RemoveInfoBar( std::u16string_view sId ) +{ + const sal_uInt16 nId = SfxInfoBarContainerChild::GetChildWindowId(); + + // Make sure the InfoBar container is visible + if (!HasChildWindow(nId)) + ToggleChildWindow(nId); + + SfxChildWindow* pChild = GetChildWindow(nId); + if (pChild) + { + SfxInfoBarContainerWindow* pInfoBarContainer = static_cast<SfxInfoBarContainerWindow*>(pChild->GetWindow()); + auto pInfoBar = pInfoBarContainer->getInfoBar(sId); + pInfoBarContainer->removeInfoBar(pInfoBar); + ShowChildWindow(nId); + } +} + +bool SfxViewFrame::HasInfoBarWithID( std::u16string_view sId ) +{ + const sal_uInt16 nId = SfxInfoBarContainerChild::GetChildWindowId(); + + SfxChildWindow* pChild = GetChildWindow(nId); + if (pChild) + { + SfxInfoBarContainerWindow* pInfoBarContainer = static_cast<SfxInfoBarContainerWindow*>(pChild->GetWindow()); + return pInfoBarContainer->hasInfoBarWithID(sId); + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/viewfrm2.cxx b/sfx2/source/view/viewfrm2.cxx new file mode 100644 index 0000000000..0af0c2c5bc --- /dev/null +++ b/sfx2/source/view/viewfrm2.cxx @@ -0,0 +1,380 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "impviewframe.hxx" +#include <statcach.hxx> +#include <workwin.hxx> + +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/ctrlitem.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfac.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/objitem.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <sfx2/viewsh.hxx> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/embed/VerbDescriptor.hpp> + +#include <osl/diagnose.h> +#include <svl/eitem.hxx> +#include <svl/stritem.hxx> +#include <tools/urlobj.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; + + +void SfxFrameViewWindow_Impl::StateChanged( StateChangedType nStateChange ) +{ + if ( nStateChange == StateChangedType::InitShow ) + { + SfxObjectShell* pDoc = pFrame->GetObjectShell(); + if ( pDoc && !pFrame->IsVisible() ) + pFrame->Show(); + + pFrame->Resize(); + } + else + Window::StateChanged( nStateChange ); +} + +void SfxFrameViewWindow_Impl::Resize() +{ + if ( IsReallyVisible() || IsReallyShown() || GetOutputSizePixel().Width() ) + pFrame->Resize(); +} + + +void SfxViewFrame::UpdateTitle() + +/* [Description] + + With this method, can the SfxViewFrame be forced to immediately provide + the new title from the <SfxObjectShell>. + + [Note] + + This is for example necessary if one listens to the SfxObjectShell as + SfxListener and then react on the <SfxSimpleHint> SfxHintId::TitleChanged, + then query the title of his views. However these views (SfxTopViewFrames) + are also SfxListener and because the order of notifications might not be + fixed, the title update will be enforced in advance. + + [Example] + + void SwDocShell::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) + { + if ( dynamic_cast<const SfxSimpleHint *>(&rHint) != nullptr ) + { + switch( ( (SfxSimpleHint&) rHint ).GetId() ) + { + case SfxHintId::TitleChanged: + for ( SfxViewFrame *pTop = SfxViewFrame::GetFirst( this ); + pTop; + pTop = SfxViewFrame::GetNext( this ); + { + pTop->UpdateTitle(); + ... pTop->GetName() ... + } + break; + ... + } + } + } +*/ + +{ + + const SfxObjectFactory &rFact = GetObjectShell()->GetFactory(); + m_pImpl->aFactoryName = rFact.GetFactoryName(); + + SfxObjectShell *pObjSh = GetObjectShell(); + if ( !pObjSh ) + return; + + + const SfxMedium *pMedium = pObjSh->GetMedium(); + OUString aURL; + GetFrame(); // -Wall required?? + if ( pObjSh->HasName() ) + { + INetURLObject aTmp( pMedium->GetName() ); + aURL = aTmp.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + } + + if ( aURL != m_pImpl->aActualURL ) + // URL has changed + m_pImpl->aActualURL = aURL; + + // SbxObjects name + OUString aSbxName = pObjSh->SfxShell::GetName(); + if ( IsVisible() ) + { + aSbxName += ":" + OUString::number(m_pImpl->nDocViewNo); + } + + SetName( aSbxName ); + GetBindings().Invalidate( SID_CURRENT_URL ); + GetBindings().Invalidate( SID_NEWDOCDIRECT ); +} + +void SfxViewFrame::Exec_Impl(SfxRequest &rReq ) +{ + // If presently the shells are replaced... + if ( !GetObjectShell() || !GetViewShell() ) + return; + + switch ( rReq.GetSlot() ) + { + case SID_SHOWPOPUPS : + { + const SfxBoolItem* pShowItem = rReq.GetArg<SfxBoolItem>(SID_SHOWPOPUPS); + bool bShow = pShowItem == nullptr || pShowItem->GetValue(); + + SfxWorkWindow *pWorkWin = GetFrame().GetWorkWindow_Impl(); + if ( bShow ) + { + // First, make the floats viewable + pWorkWin->MakeChildrenVisible_Impl(true); + GetDispatcher()->Update_Impl( true ); + + // Then view it + GetBindings().HidePopups(false); + } + else + { + pWorkWin->HidePopups_Impl(true); + pWorkWin->MakeChildrenVisible_Impl(false); + } + + Invalidate( rReq.GetSlot() ); + rReq.Done(); + break; + } + + case SID_ACTIVATE: + { + MakeActive_Impl( true ); + rReq.SetReturnValue(SfxObjectItem(0, this)); + break; + } + + case SID_NEWDOCDIRECT : + { + const SfxStringItem* pFactoryItem = rReq.GetArg<SfxStringItem>(SID_NEWDOCDIRECT); + OUString aFactName; + if ( pFactoryItem ) + aFactName = pFactoryItem->GetValue(); + else if ( !m_pImpl->aFactoryName.isEmpty() ) + aFactName = m_pImpl->aFactoryName; + else + { + SAL_WARN("sfx.view", "Missing argument!"); + break; + } + + SfxRequest aReq( SID_OPENDOC, SfxCallMode::SYNCHRON, GetPool() ); + const OUString aFact("private:factory/" + aFactName); + aReq.AppendItem( SfxStringItem( SID_FILE_NAME, aFact ) ); + aReq.AppendItem( SfxFrameItem( SID_DOCFRAME, &GetFrame() ) ); + aReq.AppendItem( SfxStringItem( SID_TARGETNAME, "_blank" ) ); + SfxGetpApp()->ExecuteSlot( aReq ); + + const SfxViewFrameItem* pItem(dynamic_cast<const SfxViewFrameItem*>(aReq.GetReturnValue().getItem())); + if (nullptr != pItem) + rReq.SetReturnValue(SfxFrameItem(0, pItem->GetFrame())); + break; + } + + case SID_CLOSEWIN: + { + // disable CloseWin, if frame is not a task + Reference < XCloseable > xTask( GetFrame().GetFrameInterface(), UNO_QUERY ); + if ( !xTask.is() ) + break; + + if ( GetViewShell()->PrepareClose() ) + { + // More Views on the same Document? + SfxObjectShell *pDocSh = GetObjectShell(); + bool bOther = false; + for ( const SfxViewFrame* pFrame = SfxViewFrame::GetFirst( pDocSh ); + !bOther && pFrame; + pFrame = SfxViewFrame::GetNext( *pFrame, pDocSh ) ) + bOther = (pFrame != this); + + // Document only needs to be queried, if no other View present. + bool bClosed = false; + if ( bOther || pDocSh->PrepareClose( true/*bUI*/ ) ) + { + if ( !bOther ) + pDocSh->SetModified( false ); + rReq.Done(); // Must call this before Close()! + bClosed = false; + try + { + xTask->close(true); + bClosed = true; + } + catch (css::lang::DisposedException &) { + // already closed; ignore + } + catch( CloseVetoException& ) + { + bClosed = false; + } + } + + rReq.SetReturnValue(SfxBoolItem(rReq.GetSlot(), bClosed)); + } + return; + } + } + + rReq.Done(); +} + +void SfxViewFrame::GetState_Impl( SfxItemSet &rSet ) +{ + SfxObjectShell *pDocSh = GetObjectShell(); + + if ( !pDocSh ) + return; + + const WhichRangesContainer & pRanges = rSet.GetRanges(); + DBG_ASSERT(!pRanges.empty(), "Set without Range"); + for ( auto const & pRange : pRanges ) + { + for ( sal_uInt16 nWhich = pRange.first; nWhich <= pRange.second; ++nWhich ) + { + switch(nWhich) + { + case SID_NEWDOCDIRECT : + { + if ( !m_pImpl->aFactoryName.isEmpty() ) + { + rSet.Put( SfxStringItem( nWhich, "private:factory/"+m_pImpl->aFactoryName ) ); + } + break; + } + + case SID_NEWWINDOW: + rSet.DisableItem(nWhich); + break; + + case SID_CLOSEWIN: + { + // disable CloseWin, if frame is not a task + Reference < XCloseable > xTask( GetFrame().GetFrameInterface(), UNO_QUERY ); + if ( !xTask.is() ) + rSet.DisableItem(nWhich); + break; + } + + case SID_SHOWPOPUPS : + break; + + case SID_OBJECT: + if ( GetViewShell() && GetViewShell()->GetVerbs().hasElements() && !GetObjectShell()->IsInPlaceActive() ) + { + uno::Any aAny(GetViewShell()->GetVerbs()); + rSet.Put( SfxUnoAnyItem( sal_uInt16( SID_OBJECT ), aAny ) ); + } + else + rSet.DisableItem( SID_OBJECT ); + break; + + default: + OSL_FAIL( "invalid message-id" ); + } + } + } +} + +void SfxViewFrame::INetExecute_Impl( SfxRequest &rRequest ) +{ + sal_uInt16 nSlotId = rRequest.GetSlot(); + switch( nSlotId ) + { + case SID_BROWSE_FORWARD: + case SID_BROWSE_BACKWARD: + OSL_FAIL( "SfxViewFrame::INetExecute_Impl: SID_BROWSE_FORWARD/BACKWARD are dead!" ); + break; + case SID_CREATELINK: + { +/*! (pb) we need new implementation to create a link +*/ + break; + } + case SID_FOCUSURLBOX: + { + SfxStateCache *pCache = GetBindings().GetAnyStateCache_Impl( SID_OPENURL ); + if( pCache ) + { + SfxControllerItem* pCtrl = pCache->GetItemLink(); + while( pCtrl ) + { + pCtrl->StateChangedAtToolBoxControl( SID_FOCUSURLBOX, SfxItemState::UNKNOWN, nullptr ); + pCtrl = pCtrl->GetItemLink(); + } + } + } + } + + // Recording + rRequest.Done(); +} + +void SfxViewFrame::INetState_Impl( SfxItemSet &rItemSet ) +{ + rItemSet.DisableItem( SID_BROWSE_FORWARD ); + rItemSet.DisableItem( SID_BROWSE_BACKWARD ); + + // Add/SaveToBookmark at BASIC-IDE, QUERY-EDITOR etc. disable + SfxObjectShell *pDocSh = GetObjectShell(); + bool bEmbedded = pDocSh && pDocSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED; + if ( !pDocSh || bEmbedded || !pDocSh->HasName() ) + rItemSet.DisableItem( SID_CREATELINK ); +} + +void SfxViewFrame::Activate( bool /*bMDI*/ ) +{ + DBG_ASSERT(GetViewShell(), "No Shell"); +//(mba): here maybe as in Beanframe NotifyEvent ?! +} + +void SfxViewFrame::Deactivate( bool /*bMDI*/ ) +{ + DBG_ASSERT(GetViewShell(), "No Shell"); +//(mba): here maybe as in Beanframe NotifyEvent ?! +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/viewimp.hxx b/sfx2/source/view/viewimp.hxx new file mode 100644 index 0000000000..aa03945910 --- /dev/null +++ b/sfx2/source/view/viewimp.hxx @@ -0,0 +1,71 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_SFX2_SOURCE_VIEW_VIEWIMP_HXX +#define INCLUDED_SFX2_SOURCE_VIEW_VIEWIMP_HXX + +#include <com/sun/star/ui/XContextMenuInterceptor.hpp> +#include <memory> +#include <sfx2/viewsh.hxx> +#include <mutex> +#include <comphelper/interfacecontainer4.hxx> +#include <svtools/acceleratorexecute.hxx> +#include <rtl/ref.hxx> +#include <vcl/print.hxx> +#include <chrono> +#include <vector> + +class SfxBaseController; +typedef std::vector<SfxShell*> SfxShellArr_Impl; +class SfxClipboardChangeListener; + +struct SfxViewShell_Impl +{ + std::mutex aMutex; + ::comphelper::OInterfaceContainerHelper4<css::ui::XContextMenuInterceptor> + aInterceptorContainer; + SfxShellArr_Impl aArr; + Size aMargin; + OUString m_sDefaultPrinterName; + std::chrono::steady_clock::time_point m_nDefaultPrinterNameFetchTime; + bool m_bHasPrintOptions; + sal_uInt16 m_nFamily; + ::rtl::Reference<SfxBaseController> m_pController; + std::unique_ptr<::svt::AcceleratorExecute> m_xAccExec; + ::rtl::Reference<SfxClipboardChangeListener> xClipboardListener; + std::shared_ptr<vcl::PrinterController> m_xPrinterController; + + mutable std::vector<SfxInPlaceClient*> maIPClients; + + SfxLokCallbackInterface* m_pLibreOfficeKitViewCallback; + /// Set if we are in the middle of a tiled search. + bool m_bTiledSearching; + static sal_uInt32 m_nLastViewShellId; + const ViewShellId m_nViewShellId; + const ViewShellDocId m_nDocId; + + explicit SfxViewShell_Impl(SfxViewShellFlags const nFlags, ViewShellDocId nDocId); + ~SfxViewShell_Impl(); + + std::vector<SfxInPlaceClient*>& GetIPClients_Impl(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/viewprn.cxx b/sfx2/source/view/viewprn.cxx new file mode 100644 index 0000000000..19fddbffdf --- /dev/null +++ b/sfx2/source/view/viewprn.cxx @@ -0,0 +1,923 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> + +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/view/XRenderable.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <officecfg/Office/Common.hxx> +#include <sal/log.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svtools/prnsetup.hxx> +#include <svl/flagitem.hxx> +#include <svl/stritem.hxx> +#include <svl/eitem.hxx> +#include <unotools/useroptions.hxx> +#include <tools/datetime.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/viewsh.hxx> +#include "viewimp.hxx" +#include <sfx2/viewfrm.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/request.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/event.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/strings.hrc> +#include <sfx2/sfxuno.hxx> +#include <sfx2/tabdlg.hxx> + +#include <toolkit/awt/vclxdevice.hxx> + +#include "prnmon.hxx" + +using namespace com::sun::star; +using namespace com::sun::star::uno; + +class SfxPrinterController : public vcl::PrinterController, public SfxListener +{ + Any maCompleteSelection; + Any maSelection; + Reference< view::XRenderable > mxRenderable; + mutable VclPtr<Printer> mpLastPrinter; + mutable Reference<awt::XDevice> mxDevice; + SfxViewShell* mpViewShell; + SfxObjectShell* mpObjectShell; + bool m_bOrigStatus; + bool m_bNeedsChange; + bool m_bApi; + bool m_bTempPrinter; + util::DateTime m_aLastPrinted; + OUString m_aLastPrintedBy; + + Sequence< beans::PropertyValue > getMergedOptions() const; + const Any& getSelectionObject() const; + +public: + SfxPrinterController( const VclPtr<Printer>& i_rPrinter, + Any i_Complete, + Any i_Selection, + const Any& i_rViewProp, + const Reference< view::XRenderable >& i_xRender, + bool i_bApi, bool i_bDirect, + SfxViewShell* pView, + const uno::Sequence< beans::PropertyValue >& rProps + ); + + virtual void Notify( SfxBroadcaster&, const SfxHint& ) override; + + virtual int getPageCount() const override; + virtual Sequence< beans::PropertyValue > getPageParameters( int i_nPage ) const override; + virtual void printPage( int i_nPage ) const override; + virtual void jobStarted() override; + virtual void jobFinished( css::view::PrintableState ) override; +}; + +SfxPrinterController::SfxPrinterController( const VclPtr<Printer>& i_rPrinter, + Any i_Complete, + Any i_Selection, + const Any& i_rViewProp, + const Reference< view::XRenderable >& i_xRender, + bool i_bApi, bool i_bDirect, + SfxViewShell* pView, + const uno::Sequence< beans::PropertyValue >& rProps + ) + : PrinterController(i_rPrinter, pView ? pView->GetFrameWeld() : nullptr) + , maCompleteSelection(std::move( i_Complete )) + , maSelection(std::move( i_Selection )) + , mxRenderable( i_xRender ) + , mpLastPrinter( nullptr ) + , mpViewShell( pView ) + , mpObjectShell(nullptr) + , m_bOrigStatus( false ) + , m_bNeedsChange( false ) + , m_bApi(i_bApi) + , m_bTempPrinter( i_rPrinter ) +{ + if ( mpViewShell ) + { + StartListening( *mpViewShell ); + mpObjectShell = mpViewShell->GetObjectShell(); + StartListening( *mpObjectShell ); + } + + // initialize extra ui options + if( mxRenderable.is() ) + { + for (const auto& rProp : rProps) + setValue( rProp.Name, rProp.Value ); + + Sequence< beans::PropertyValue > aRenderOptions{ + comphelper::makePropertyValue("ExtraPrintUIOptions", Any{}), + comphelper::makePropertyValue("View", i_rViewProp), + comphelper::makePropertyValue("IsPrinter", true) + }; + try + { + const Sequence< beans::PropertyValue > aRenderParms( mxRenderable->getRenderer( 0 , getSelectionObject(), aRenderOptions ) ); + for( const auto& rRenderParm : aRenderParms ) + { + if ( rRenderParm.Name == "ExtraPrintUIOptions" ) + { + Sequence< beans::PropertyValue > aUIProps; + rRenderParm.Value >>= aUIProps; + setUIOptions( aUIProps ); + } + else if( rRenderParm.Name == "NUp" ) + { + setValue( rRenderParm.Name, rRenderParm.Value ); + } + } + } + catch( lang::IllegalArgumentException& ) + { + // the first renderer should always be available for the UI options, + // but catch the exception to be safe + } + } + + // set some job parameters + setValue( "IsApi", Any( i_bApi ) ); + setValue( "IsDirect", Any( i_bDirect ) ); + setValue( "IsPrinter", Any( true ) ); + setValue( "View", i_rViewProp ); +} + +void SfxPrinterController::Notify( SfxBroadcaster& , const SfxHint& rHint ) +{ + if ( rHint.GetId() == SfxHintId::Dying ) + { + EndListening(*mpViewShell); + EndListening(*mpObjectShell); + dialogsParentClosing(); + mpViewShell = nullptr; + mpObjectShell = nullptr; + } +} + +const Any& SfxPrinterController::getSelectionObject() const +{ + const beans::PropertyValue* pVal = getValue( OUString( "PrintSelectionOnly" ) ); + if( pVal ) + { + bool bSel = false; + pVal->Value >>= bSel; + return bSel ? maSelection : maCompleteSelection; + } + + sal_Int32 nChoice = 0; + pVal = getValue( OUString( "PrintContent" ) ); + if( pVal ) + pVal->Value >>= nChoice; + + return (nChoice > 1) ? maSelection : maCompleteSelection; +} + +Sequence< beans::PropertyValue > SfxPrinterController::getMergedOptions() const +{ + VclPtr<Printer> xPrinter( getPrinter() ); + if( xPrinter.get() != mpLastPrinter ) + { + mpLastPrinter = xPrinter.get(); + rtl::Reference<VCLXDevice> pXDevice = new VCLXDevice(); + pXDevice->SetOutputDevice( mpLastPrinter ); + mxDevice.set( pXDevice ); + } + + Sequence< beans::PropertyValue > aRenderOptions{ comphelper::makePropertyValue( + "RenderDevice", mxDevice) }; + + aRenderOptions = getJobProperties( aRenderOptions ); + return aRenderOptions; +} + +int SfxPrinterController::getPageCount() const +{ + int nPages = 0; + VclPtr<Printer> xPrinter( getPrinter() ); + if( mxRenderable.is() && xPrinter ) + { + Sequence< beans::PropertyValue > aJobOptions( getMergedOptions() ); + try + { + nPages = mxRenderable->getRendererCount( getSelectionObject(), aJobOptions ); + } + catch (lang::DisposedException &) + { + SAL_WARN("sfx", "SfxPrinterController: document disposed while printing"); + const_cast<SfxPrinterController*>(this)->setJobState( + view::PrintableState_JOB_ABORTED); + } + } + return nPages; +} + +Sequence< beans::PropertyValue > SfxPrinterController::getPageParameters( int i_nPage ) const +{ + VclPtr<Printer> xPrinter( getPrinter() ); + Sequence< beans::PropertyValue > aResult; + + if (mxRenderable.is() && xPrinter) + { + Sequence< beans::PropertyValue > aJobOptions( getMergedOptions() ); + try + { + aResult = mxRenderable->getRenderer( i_nPage, getSelectionObject(), aJobOptions ); + } + catch( lang::IllegalArgumentException& ) + { + } + catch (lang::DisposedException &) + { + SAL_WARN("sfx", "SfxPrinterController: document disposed while printing"); + const_cast<SfxPrinterController*>(this)->setJobState( + view::PrintableState_JOB_ABORTED); + } + } + return aResult; +} + +void SfxPrinterController::printPage( int i_nPage ) const +{ + VclPtr<Printer> xPrinter( getPrinter() ); + if( !mxRenderable.is() || !xPrinter ) + return; + + Sequence< beans::PropertyValue > aJobOptions( getMergedOptions() ); + try + { + mxRenderable->render( i_nPage, getSelectionObject(), aJobOptions ); + } + catch( lang::IllegalArgumentException& ) + { + // don't care enough about nonexistent page here + // to provoke a crash + } + catch (lang::DisposedException &) + { + SAL_WARN("sfx", "SfxPrinterController: document disposed while printing"); + const_cast<SfxPrinterController*>(this)->setJobState( + view::PrintableState_JOB_ABORTED); + } +} + +void SfxPrinterController::jobStarted() +{ + if ( !mpObjectShell ) + return; + + m_bOrigStatus = mpObjectShell->IsEnableSetModified(); + + // check configuration: shall update of printing information in DocInfo set the document to "modified"? + if (m_bOrigStatus && !officecfg::Office::Common::Print::PrintingModifiesDocument::get()) + { + mpObjectShell->EnableSetModified( false ); + m_bNeedsChange = true; + } + + // refresh document info + uno::Reference<document::XDocumentProperties> xDocProps(mpObjectShell->getDocProperties()); + m_aLastPrintedBy = xDocProps->getPrintedBy(); + m_aLastPrinted = xDocProps->getPrintDate(); + + xDocProps->setPrintedBy( mpObjectShell->IsUseUserData() + ? SvtUserOptions().GetFullName() + : OUString() ); + ::DateTime now( ::DateTime::SYSTEM ); + + xDocProps->setPrintDate( now.GetUNODateTime() ); + + uno::Sequence < beans::PropertyValue > aOpts; + aOpts = getJobProperties( aOpts ); + + uno::Reference< frame::XController2 > xController; + if ( mpViewShell ) + xController.set( mpViewShell->GetController(), uno::UNO_QUERY ); + + mpObjectShell->Broadcast( SfxPrintingHint( + view::PrintableState_JOB_STARTED, aOpts, mpObjectShell, xController ) ); +} + +void SfxPrinterController::jobFinished( css::view::PrintableState nState ) +{ + if ( !mpObjectShell ) + return; + + bool bCopyJobSetup = false; + mpObjectShell->Broadcast( SfxPrintingHint( nState ) ); + switch ( nState ) + { + case view::PrintableState_JOB_SPOOLING_FAILED : + case view::PrintableState_JOB_FAILED : + { + // "real" problem (not simply printing cancelled by user) + OUString aMsg( SfxResId(STR_NOSTARTPRINTER) ); + if ( !m_bApi ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(mpViewShell->GetFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, + aMsg)); + xBox->run(); + } + [[fallthrough]]; + } + case view::PrintableState_JOB_ABORTED : + { + // printing not successful, reset DocInfo + uno::Reference<document::XDocumentProperties> xDocProps(mpObjectShell->getDocProperties()); + xDocProps->setPrintedBy(m_aLastPrintedBy); + xDocProps->setPrintDate(m_aLastPrinted); + break; + } + + case view::PrintableState_JOB_SPOOLED : + case view::PrintableState_JOB_COMPLETED : + { + SfxBindings& rBind = mpViewShell->GetViewFrame().GetBindings(); + rBind.Invalidate( SID_PRINTDOC ); + rBind.Invalidate( SID_PRINTDOCDIRECT ); + rBind.Invalidate( SID_SETUPPRINTER ); + bCopyJobSetup = ! m_bTempPrinter; + break; + } + + default: + break; + } + + if( bCopyJobSetup && mpViewShell ) + { + // #i114306# + // Note: this possibly creates a printer that gets immediately replaced + // by a new one. The reason for this is that otherwise we would not get + // the printer's SfxItemSet here to copy. Awkward, but at the moment there is no + // other way here to get the item set. + SfxPrinter* pDocPrt = mpViewShell->GetPrinter(true); + if( pDocPrt ) + { + if( pDocPrt->GetName() == getPrinter()->GetName() ) + pDocPrt->SetJobSetup( getPrinter()->GetJobSetup() ); + else + { + VclPtr<SfxPrinter> pNewPrt = VclPtr<SfxPrinter>::Create( pDocPrt->GetOptions().Clone(), getPrinter()->GetName() ); + pNewPrt->SetJobSetup( getPrinter()->GetJobSetup() ); + mpViewShell->SetPrinter( pNewPrt, SfxPrinterChangeFlags::PRINTER | SfxPrinterChangeFlags::JOBSETUP ); + } + } + } + + if ( m_bNeedsChange ) + mpObjectShell->EnableSetModified( m_bOrigStatus ); + + if ( mpViewShell ) + { + mpViewShell->pImpl->m_xPrinterController.reset(); + } +} + +namespace { + +/** + An instance of this class is created for the life span of the + printer dialogue, to create in its click handler for the additions by the + virtual method of the derived SfxViewShell generated print options dialogue + and to cache the options set there as SfxItemSet. +*/ +class SfxDialogExecutor_Impl +{ +private: + SfxViewShell* _pViewSh; + PrinterSetupDialog& _rSetupParent; + std::unique_ptr<SfxItemSet> _pOptions; + bool _bHelpDisabled; + + DECL_LINK( Execute, weld::Button&, void ); + +public: + SfxDialogExecutor_Impl( SfxViewShell* pViewSh, PrinterSetupDialog& rParent ); + + Link<weld::Button&, void> GetLink() const { return LINK(const_cast<SfxDialogExecutor_Impl*>(this), SfxDialogExecutor_Impl, Execute); } + const SfxItemSet* GetOptions() const { return _pOptions.get(); } + void DisableHelp() { _bHelpDisabled = true; } +}; + +} + +SfxDialogExecutor_Impl::SfxDialogExecutor_Impl( SfxViewShell* pViewSh, PrinterSetupDialog& rParent ) : + + _pViewSh ( pViewSh ), + _rSetupParent ( rParent ), + _bHelpDisabled ( false ) + +{ +} + +IMPL_LINK_NOARG(SfxDialogExecutor_Impl, Execute, weld::Button&, void) +{ + // Options noted locally + if ( !_pOptions ) + { + _pOptions = static_cast<SfxPrinter*>( _rSetupParent.GetPrinter() )->GetOptions().Clone(); + } + + assert(_pOptions); + if (!_pOptions) + return; + + // Create Dialog + SfxPrintOptionsDialog aDlg(_rSetupParent.GetFrameWeld(), _pViewSh, _pOptions.get() ); + if (_bHelpDisabled) + aDlg.DisableHelp(); + if (aDlg.run() == RET_OK) + { + _pOptions = aDlg.GetOptions().Clone(); + } +} + +/** + Internal method for setting the differences between 'pNewPrinter' to the + current printer. pNewPrinter is either taken over or deleted. +*/ +void SfxViewShell::SetPrinter_Impl( VclPtr<SfxPrinter>& pNewPrinter ) +{ + // get current Printer + SfxPrinter *pDocPrinter = GetPrinter(); + + // Evaluate Printer Options + const SfxFlagItem *pFlagItem = pDocPrinter->GetOptions().GetItemIfSet( SID_PRINTER_CHANGESTODOC, false ); + bool bOriToDoc = pFlagItem && (static_cast<SfxPrinterChangeFlags>(pFlagItem->GetValue()) & SfxPrinterChangeFlags::CHG_ORIENTATION); + bool bSizeToDoc = pFlagItem && (static_cast<SfxPrinterChangeFlags>(pFlagItem->GetValue()) & SfxPrinterChangeFlags::CHG_SIZE); + + // Determine the previous format and size + Orientation eOldOri = pDocPrinter->GetOrientation(); + Size aOldPgSz = pDocPrinter->GetPaperSizePixel(); + + // Determine the new format and size + Orientation eNewOri = pNewPrinter->GetOrientation(); + Size aNewPgSz = pNewPrinter->GetPaperSizePixel(); + + // Determine the changes in page format + bool bOriChg = (eOldOri != eNewOri) && bOriToDoc; + bool bPgSzChg = ( aOldPgSz.Height() != + ( bOriChg ? aNewPgSz.Width() : aNewPgSz.Height() ) || + aOldPgSz.Width() != + ( bOriChg ? aNewPgSz.Height() : aNewPgSz.Width() ) ) && + bSizeToDoc; + + // Message and Flags for page format changes + OUString aMsg; + SfxPrinterChangeFlags nNewOpt = SfxPrinterChangeFlags::NONE; + if( bOriChg && bPgSzChg ) + { + aMsg = SfxResId(STR_PRINT_NEWORISIZE); + nNewOpt = SfxPrinterChangeFlags::CHG_ORIENTATION | SfxPrinterChangeFlags::CHG_SIZE; + } + else if (bOriChg ) + { + aMsg = SfxResId(STR_PRINT_NEWORI); + nNewOpt = SfxPrinterChangeFlags::CHG_ORIENTATION; + } + else if (bPgSzChg) + { + aMsg = SfxResId(STR_PRINT_NEWSIZE); + nNewOpt = SfxPrinterChangeFlags::CHG_SIZE; + } + + // Summarize in this variable what has been changed. + SfxPrinterChangeFlags nChangedFlags = SfxPrinterChangeFlags::NONE; + + // Ask if possible, if page format should be taken over from printer. + if (bOriChg || bPgSzChg) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Question, VclButtonsType::YesNo, + aMsg)); + if (RET_YES == xBox->run()) + { + // Flags with changes for <SetPrinter(SfxPrinter*)> are maintained + nChangedFlags |= nNewOpt; + } + } + + // Was the printer selection changed from Default to Specific + // or the other way around? + if ( (pNewPrinter->GetName() != pDocPrinter->GetName()) + || (pDocPrinter->IsDefPrinter() != pNewPrinter->IsDefPrinter()) ) + { + nChangedFlags |= SfxPrinterChangeFlags::PRINTER|SfxPrinterChangeFlags::JOBSETUP; + if ( ! (pNewPrinter->GetOptions() == pDocPrinter->GetOptions()) ) + { + nChangedFlags |= SfxPrinterChangeFlags::OPTIONS; + } + + pDocPrinter = pNewPrinter; + } + else + { + // Compare extra options + if ( ! (pNewPrinter->GetOptions() == pDocPrinter->GetOptions()) ) + { + // Option have changed + pDocPrinter->SetOptions( pNewPrinter->GetOptions() ); + nChangedFlags |= SfxPrinterChangeFlags::OPTIONS; + } + + // Compare JobSetups + JobSetup aNewJobSetup = pNewPrinter->GetJobSetup(); + JobSetup aOldJobSetup = pDocPrinter->GetJobSetup(); + if ( aNewJobSetup != aOldJobSetup ) + { + nChangedFlags |= SfxPrinterChangeFlags::JOBSETUP; + } + + // Keep old changed Printer. + pDocPrinter->SetPrinterProps( pNewPrinter ); + pNewPrinter.disposeAndClear(); + } + + if ( SfxPrinterChangeFlags::NONE != nChangedFlags ) + // SetPrinter will delete the old printer if it changes + SetPrinter( pDocPrinter, nChangedFlags ); +} + +void SfxViewShell::StartPrint( const uno::Sequence < beans::PropertyValue >& rProps, bool bIsAPI, bool bIsDirect ) +{ + assert( !pImpl->m_xPrinterController ); + + // get the current selection; our controller should know it + Reference< frame::XController > xController( GetController() ); + Reference< view::XSelectionSupplier > xSupplier( xController, UNO_QUERY ); + + Any aSelection; + if( xSupplier.is() ) + aSelection = xSupplier->getSelection(); + else + aSelection <<= GetObjectShell()->GetModel(); + Any aComplete( Any( GetObjectShell()->GetModel() ) ); + Any aViewProp( xController ); + VclPtr<Printer> aPrt; + + const beans::PropertyValue* pVal = std::find_if(rProps.begin(), rProps.end(), + [](const beans::PropertyValue& rVal) { return rVal.Name == "PrinterName"; }); + if (pVal != rProps.end()) + { + OUString aPrinterName; + pVal->Value >>= aPrinterName; + aPrt.reset( VclPtr<Printer>::Create( aPrinterName ) ); + } + + std::shared_ptr<vcl::PrinterController> xNewController(std::make_shared<SfxPrinterController>( + aPrt, + aComplete, + aSelection, + aViewProp, + GetRenderable(), + bIsAPI, + bIsDirect, + this, + rProps + )); + pImpl->m_xPrinterController = xNewController; + + // When no JobName was specified via com::sun::star::view::PrintOptions::JobName , + // use the document title as default job name + css::beans::PropertyValue* pJobNameVal = xNewController->getValue("JobName"); + if (!pJobNameVal) + { + if (SfxObjectShell* pDoc = GetObjectShell()) + { + xNewController->setValue("JobName", Any(pDoc->GetTitle(1))); + xNewController->setPrinterModified(mbPrinterSettingsModified); + } + } +} + +void SfxViewShell::ExecPrint( const uno::Sequence < beans::PropertyValue >& rProps, bool bIsAPI, bool bIsDirect ) +{ + StartPrint( rProps, bIsAPI, bIsDirect ); + // FIXME: job setup + SfxPrinter* pDocPrt = GetPrinter(); + JobSetup aJobSetup = pDocPrt ? pDocPrt->GetJobSetup() : JobSetup(); + Printer::PrintJob( GetPrinterController(), aJobSetup ); +} + +const std::shared_ptr< vcl::PrinterController >& SfxViewShell::GetPrinterController() const +{ + return pImpl->m_xPrinterController; +} + +Printer* SfxViewShell::GetActivePrinter() const +{ + return pImpl->m_xPrinterController + ? pImpl->m_xPrinterController->getPrinter().get() : nullptr; +} + +void SfxViewShell::ExecPrint_Impl( SfxRequest &rReq ) +{ + sal_uInt16 nDialogRet = RET_CANCEL; + VclPtr<SfxPrinter> pPrinter; + bool bSilent = false; + + // does the function have been called by the user interface or by an API call + bool bIsAPI = rReq.GetArgs() && rReq.GetArgs()->Count(); + if ( bIsAPI ) + { + // the function have been called by the API + + // Should it be visible on the user interface, + // should it launch popup dialogue ? + const SfxBoolItem* pSilentItem = rReq.GetArg<SfxBoolItem>(SID_SILENT); + bSilent = pSilentItem && pSilentItem->GetValue(); + } + + // no help button in dialogs if called from the help window + // (pressing help button would exchange the current page inside the help + // document that is going to be printed!) + SfxMedium* pMedium = GetViewFrame().GetObjectShell()->GetMedium(); + std::shared_ptr<const SfxFilter> pFilter = pMedium ? pMedium->GetFilter() : nullptr; + bool bPrintOnHelp = ( pFilter && pFilter->GetFilterName() == "writer_web_HTML_help" ); + + const sal_uInt16 nId = rReq.GetSlot(); + switch( nId ) + { + case SID_PRINTDOC: // display the printer selection and properties dialogue : File > Print... + case SID_PRINTDOCDIRECT: // Print the document directly, without displaying the dialogue + { + SfxObjectShell* pDoc = GetObjectShell(); + + // derived class may decide to abort this + if( pDoc == nullptr || !pDoc->QuerySlotExecutable( nId ) ) + { + rReq.SetReturnValue( SfxBoolItem( 0, false ) ); + return; + } + + pDoc->QueryHiddenInformation(HiddenWarningFact::WhenPrinting); + + // should we print only the selection or the whole document + const SfxBoolItem* pSelectItem = rReq.GetArg<SfxBoolItem>(SID_SELECTION); + bool bSelection = ( pSelectItem != nullptr && pSelectItem->GetValue() ); + // detect non api call from writer ( that adds SID_SELECTION ) and reset bIsAPI + if ( pSelectItem && rReq.GetArgs()->Count() == 1 ) + bIsAPI = false; + + uno::Sequence < beans::PropertyValue > aProps; + if ( bIsAPI ) + { + // supported properties: + // String PrinterName + // String FileName + // Int16 From + // Int16 To + // In16 Copies + // String RangeText + // bool Selection + // bool Asynchron + // bool Collate + // bool Silent + + // the TransformItems function overwrite aProps + TransformItems( nId, *rReq.GetArgs(), aProps, GetInterface()->GetSlot(nId) ); + + for ( auto& rProp : asNonConstRange(aProps) ) + { + if ( rProp.Name == "Copies" ) + { + rProp.Name = "CopyCount"; + } + else if ( rProp.Name == "RangeText" ) + { + rProp.Name = "Pages"; + } + else if ( rProp.Name == "Asynchron" ) + { + rProp.Name = "Wait"; + bool bAsynchron = false; + rProp.Value >>= bAsynchron; + rProp.Value <<= !bAsynchron; + } + else if ( rProp.Name == "Silent" ) + { + rProp.Name = "MonitorVisible"; + bool bPrintSilent = false; + rProp.Value >>= bPrintSilent; + rProp.Value <<= !bPrintSilent; + } + } + } + + // we will add the "PrintSelectionOnly" or "HideHelpButton" properties + // we have to increase the capacity of aProps + sal_Int32 nLen = aProps.getLength(); + aProps.realloc( nLen + 1 ); + auto pProps = aProps.getArray(); + + // HACK: writer sets the SID_SELECTION item when printing directly and expects + // to get only the selection document in that case (see getSelectionObject) + // however it also reacts to the PrintContent property. We need this distinction here, too, + // else one of the combinations print / print direct and selection / all will not work. + // it would be better if writer handled this internally + if( nId == SID_PRINTDOCDIRECT ) + { + pProps[nLen].Name = "PrintSelectionOnly"; + pProps[nLen].Value <<= bSelection; + } + else // if nId == SID_PRINTDOC ; nothing to do with the previous HACK + { + // should the printer selection and properties dialogue display an help button + pProps[nLen].Name = "HideHelpButton"; + pProps[nLen].Value <<= bPrintOnHelp; + } + + ExecPrint( aProps, bIsAPI, (nId == SID_PRINTDOCDIRECT) ); + + // FIXME: Recording + rReq.Done(); + break; + } + + case SID_PRINTER_NAME: // for recorded macros + { + // get printer and printer settings from the document + SfxPrinter* pDocPrinter = GetPrinter(true); + const SfxStringItem* pPrinterItem = rReq.GetArg<SfxStringItem>(SID_PRINTER_NAME); + if (!pPrinterItem) + { + rReq.Ignore(); + break; + } + // use PrinterName parameter to create a printer + pPrinter = VclPtr<SfxPrinter>::Create(pDocPrinter->GetOptions().Clone(), + pPrinterItem->GetValue()); + + if (!pPrinter->IsKnown()) + { + pPrinter.disposeAndClear(); + rReq.Ignore(); + break; + } + SetPrinter(pPrinter, SfxPrinterChangeFlags::PRINTER); + rReq.Done(); + break; + } + case SID_SETUPPRINTER : // display the printer settings dialog : File > Printer Settings... + { + // get printer and printer settings from the document + SfxPrinter *pDocPrinter = GetPrinter(true); + + // look for printer in parameters + const SfxStringItem* pPrinterItem = rReq.GetArg<SfxStringItem>(SID_PRINTER_NAME); + if ( pPrinterItem ) + { + // use PrinterName parameter to create a printer + pPrinter = VclPtr<SfxPrinter>::Create( pDocPrinter->GetOptions().Clone(), pPrinterItem->GetValue() ); + + // if printer is unknown, it can't be used - now printer from document will be used + if ( !pPrinter->IsKnown() ) + pPrinter.disposeAndClear(); + } + + // no PrinterName parameter in ItemSet or the PrinterName points to an unknown printer + if ( !pPrinter ) + // use default printer from document + pPrinter = pDocPrinter; + + if( !pPrinter || !pPrinter->IsValid() ) + { + // no valid printer either in ItemSet or at the document + if ( !bSilent ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, + SfxResId(STR_NODEFPRINTER))); + xBox->run(); + } + + rReq.SetReturnValue(SfxBoolItem(0,false)); + + break; + } + + // FIXME: printer isn't used for printing anymore! + if( pPrinter->IsPrinting() ) + { + // if printer is busy, abort configuration + if ( !bSilent ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_ERROR_PRINTER_BUSY))); + xBox->run(); + } + rReq.SetReturnValue(SfxBoolItem(0,false)); + + return; + } + + // Open Printer Setup dialog (needs a temporary printer) + VclPtr<SfxPrinter> pDlgPrinter = pPrinter->Clone(); + PrinterSetupDialog aPrintSetupDlg(GetFrameWeld()); + std::unique_ptr<SfxDialogExecutor_Impl> pExecutor; + + if (pImpl->m_bHasPrintOptions && HasPrintOptionsPage()) + { + // additional controls for dialog + pExecutor.reset(new SfxDialogExecutor_Impl(this, aPrintSetupDlg)); + if (bPrintOnHelp) + pExecutor->DisableHelp(); + aPrintSetupDlg.SetOptionsHdl(pExecutor->GetLink()); + } + + aPrintSetupDlg.SetPrinter(pDlgPrinter); + nDialogRet = aPrintSetupDlg.run(); + + if (pExecutor && pExecutor->GetOptions()) + { + if (nDialogRet == RET_OK) + // remark: have to be recorded if possible! + pDlgPrinter->SetOptions(*pExecutor->GetOptions()); + else + { + pPrinter->SetOptions(*pExecutor->GetOptions()); + SetPrinter(pPrinter, SfxPrinterChangeFlags::OPTIONS); + } + } + + // no recording of PrinterSetup except printer name (is printer dependent) + rReq.Ignore(); + + if (nDialogRet == RET_OK) + { + if (pPrinter->GetName() != pDlgPrinter->GetName()) + { + // user has changed the printer -> macro recording + SfxRequest aReq(GetViewFrame(), SID_PRINTER_NAME); + aReq.AppendItem(SfxStringItem(SID_PRINTER_NAME, pDlgPrinter->GetName())); + aReq.Done(); + } + + // take the changes made in the dialog + SetPrinter_Impl(pDlgPrinter); + + // forget new printer, it was taken over (as pPrinter) or deleted + pDlgPrinter = nullptr; + mbPrinterSettingsModified = true; + } + else + { + // PrinterDialog is used to transfer information on printing, + // so it will only be deleted here if dialog was cancelled + pDlgPrinter.disposeAndClear(); + rReq.Ignore(); + } + break; + } + } +} + +SfxPrinter* SfxViewShell::GetPrinter( bool /*bCreate*/ ) +{ + return nullptr; +} + +sal_uInt16 SfxViewShell::SetPrinter( SfxPrinter* /*pNewPrinter*/, SfxPrinterChangeFlags /*nDiffFlags*/ ) +{ + return 0; +} + +std::unique_ptr<SfxTabPage> SfxViewShell::CreatePrintOptionsPage(weld::Container*, weld::DialogController*, const SfxItemSet&) +{ + return nullptr; +} + +bool SfxViewShell::HasPrintOptionsPage() const +{ + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx new file mode 100644 index 0000000000..93c18a16e2 --- /dev/null +++ b/sfx2/source/view/viewsh.cxx @@ -0,0 +1,3912 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <boost/property_tree/json_parser.hpp> + +#include <sal/log.hxx> +#include <svl/stritem.hxx> +#include <svl/eitem.hxx> +#include <svl/whiter.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/weld.hxx> +#include <svl/intitem.hxx> +#include <svtools/langhelp.hxx> +#include <com/sun/star/awt/XPopupMenu.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/container/XContainerQuery.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp> +#include <com/sun/star/view/XRenderable.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/accessibility/XAccessibleTable.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/ui/XAcceleratorConfiguration.hpp> + +#include <cppuhelper/weakref.hxx> + +#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/awt/FontSlant.hpp> + +#include <comphelper/diagnose_ex.hxx> +#include <editeng/unoprnms.hxx> +#include <tools/urlobj.hxx> +#include <unotools/tempfile.hxx> +#include <svtools/soerr.hxx> +#include <tools/svborder.hxx> + +#include <framework/actiontriggerhelper.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include <officecfg/Setup.hxx> +#include <sfx2/app.hxx> +#include <sfx2/flatpak.hxx> +#include <sfx2/viewsh.hxx> +#include "viewimp.hxx" +#include <sfx2/sfxresid.hxx> +#include <sfx2/request.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/sfxbasecontroller.hxx> +#include <sfx2/mailmodelapi.hxx> +#include <bluthsndapi.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/event.hxx> +#include <sfx2/ipclient.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/objface.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/lokcallback.hxx> +#include <openuriexternally.hxx> +#include <iostream> +#include <vector> +#include <list> +#include <libxml/xmlwriter.h> +#include <toolkit/awt/vclxmenu.hxx> +#include <unordered_map> +#include <unordered_set> + +#define ShellClass_SfxViewShell +#include <sfxslots.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::cppu; + +class SfxClipboardChangeListener : public ::cppu::WeakImplHelper< + datatransfer::clipboard::XClipboardListener > +{ +public: + SfxClipboardChangeListener( SfxViewShell* pView, uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClpbrdNtfr ); + + // XEventListener + virtual void SAL_CALL disposing( const lang::EventObject& rEventObject ) override; + + // XClipboardListener + virtual void SAL_CALL changedContents( const datatransfer::clipboard::ClipboardEvent& rEventObject ) override; + + void DisconnectViewShell() { m_pViewShell = nullptr; } + void ChangedContents(); + + enum AsyncExecuteCmd + { + ASYNCEXECUTE_CMD_DISPOSING, + ASYNCEXECUTE_CMD_CHANGEDCONTENTS + }; + + struct AsyncExecuteInfo + { + AsyncExecuteInfo( AsyncExecuteCmd eCmd, SfxClipboardChangeListener* pListener ) : + m_eCmd( eCmd ), m_xListener( pListener ) {} + + AsyncExecuteCmd m_eCmd; + rtl::Reference<SfxClipboardChangeListener> m_xListener; + }; + +private: + SfxViewShell* m_pViewShell; + uno::Reference< datatransfer::clipboard::XClipboardNotifier > m_xClpbrdNtfr; + uno::Reference< lang::XComponent > m_xCtrl; + + DECL_STATIC_LINK( SfxClipboardChangeListener, AsyncExecuteHdl_Impl, void*, void ); +}; + +SfxClipboardChangeListener::SfxClipboardChangeListener( SfxViewShell* pView, uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClpbrdNtfr ) + : m_pViewShell( nullptr ), m_xClpbrdNtfr(std::move( xClpbrdNtfr )), m_xCtrl(pView->GetController()) +{ + if ( m_xCtrl.is() ) + { + m_xCtrl->addEventListener( uno::Reference < lang::XEventListener > ( static_cast < lang::XEventListener* >( this ) ) ); + m_pViewShell = pView; + } + if ( m_xClpbrdNtfr.is() ) + { + m_xClpbrdNtfr->addClipboardListener( uno::Reference< datatransfer::clipboard::XClipboardListener >( + static_cast< datatransfer::clipboard::XClipboardListener* >( this ))); + } +} + +void SfxClipboardChangeListener::ChangedContents() +{ + const SolarMutexGuard aGuard; + if (!m_pViewShell) + return; + + SfxBindings& rBind = m_pViewShell->GetViewFrame().GetBindings(); + rBind.Invalidate(SID_PASTE); + rBind.Invalidate(SID_PASTE_SPECIAL); + rBind.Invalidate(SID_CLIPBOARD_FORMAT_ITEMS); + + if (comphelper::LibreOfficeKit::isActive()) + { + // In the future we might send the payload as well. + SfxLokHelper::notifyAllViews(LOK_CALLBACK_CLIPBOARD_CHANGED, ""_ostr); + } +} + +IMPL_STATIC_LINK( SfxClipboardChangeListener, AsyncExecuteHdl_Impl, void*, p, void ) +{ + AsyncExecuteInfo* pAsyncExecuteInfo = static_cast<AsyncExecuteInfo*>(p); + if ( pAsyncExecuteInfo ) + { + if ( pAsyncExecuteInfo->m_xListener.is() ) + { + if ( pAsyncExecuteInfo->m_eCmd == ASYNCEXECUTE_CMD_DISPOSING ) + pAsyncExecuteInfo->m_xListener->DisconnectViewShell(); + else if ( pAsyncExecuteInfo->m_eCmd == ASYNCEXECUTE_CMD_CHANGEDCONTENTS ) + pAsyncExecuteInfo->m_xListener->ChangedContents(); + } + } + delete pAsyncExecuteInfo; +} + +void SAL_CALL SfxClipboardChangeListener::disposing( const lang::EventObject& /*rEventObject*/ ) +{ + // Either clipboard or ViewShell is going to be destroyed -> no interest in listening anymore + uno::Reference< lang::XComponent > xCtrl( m_xCtrl ); + uno::Reference< datatransfer::clipboard::XClipboardNotifier > xNotify( m_xClpbrdNtfr ); + + uno::Reference< datatransfer::clipboard::XClipboardListener > xThis( static_cast< datatransfer::clipboard::XClipboardListener* >( this )); + if ( xCtrl.is() ) + xCtrl->removeEventListener( uno::Reference < lang::XEventListener > ( static_cast < lang::XEventListener* >( this ))); + if ( xNotify.is() ) + xNotify->removeClipboardListener( xThis ); + + // Make asynchronous call to avoid locking SolarMutex which is the + // root for many deadlocks, especially in conjunction with the "Windows" + // based single thread apartment clipboard code! + AsyncExecuteInfo* pInfo = new AsyncExecuteInfo( ASYNCEXECUTE_CMD_DISPOSING, this ); + if (!Application::PostUserEvent( LINK( nullptr, SfxClipboardChangeListener, AsyncExecuteHdl_Impl ), pInfo )) + delete pInfo; +} + +void SAL_CALL SfxClipboardChangeListener::changedContents( const datatransfer::clipboard::ClipboardEvent& ) +{ + // Make asynchronous call to avoid locking SolarMutex which is the + // root for many deadlocks, especially in conjunction with the "Windows" + // based single thread apartment clipboard code! + AsyncExecuteInfo* pInfo = new AsyncExecuteInfo( ASYNCEXECUTE_CMD_CHANGEDCONTENTS, this ); + if (!Application::PostUserEvent( LINK( nullptr, SfxClipboardChangeListener, AsyncExecuteHdl_Impl ), pInfo )) + delete pInfo; +} + +namespace +{ +struct TableSizeType +{ + sal_Int32 nRowCount; + sal_Int32 nColCount; +}; +} + +typedef std::list<uno::Reference<accessibility::XAccessibleTable>> XAccessibleTableList; + +namespace +{ +constexpr +bool isText(sal_Int16 nRole) +{ + return nRole == accessibility::AccessibleRole::DOCUMENT_TEXT; +} + +constexpr +bool isSpreadsheet(sal_Int16 nRole) +{ + return nRole == accessibility::AccessibleRole::DOCUMENT_SPREADSHEET; +} + +constexpr +bool isPresentation(sal_Int16 nRole) +{ + return nRole == accessibility::AccessibleRole::DOCUMENT_PRESENTATION; +} + +constexpr +bool isDocument(sal_Int16 nRole) +{ + return isText(nRole) || isSpreadsheet(nRole) || isPresentation(nRole); +} + +bool hasState(const accessibility::AccessibleEventObject& aEvent, ::sal_Int64 nState) +{ + bool res = false; + uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY); + if (xContext.is()) + { + ::sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + res = (nStateSet & nState) != 0; + } + return res; +} + +bool isFocused(const accessibility::AccessibleEventObject& aEvent) +{ + return hasState(aEvent, accessibility::AccessibleStateType::FOCUSED); +} + +uno::Reference<accessibility::XAccessibleContext> +getParentContext(const uno::Reference<accessibility::XAccessibleContext>& xContext) +{ + uno::Reference<accessibility::XAccessibleContext> xParentContext; + uno::Reference<accessibility::XAccessible> xParent = xContext->getAccessibleParent(); + if (xParent.is()) + xParentContext = uno::Reference<accessibility::XAccessibleContext>(xParent, uno::UNO_QUERY); + return xParentContext; +} + +OUString selectionEventTypeToString(sal_Int16 nEventId) +{ + using namespace accessibility; + switch(nEventId) + { + case AccessibleEventId::SELECTION_CHANGED: + return "create"; + case AccessibleEventId::SELECTION_CHANGED_ADD: + return "add"; + case AccessibleEventId::SELECTION_CHANGED_REMOVE: + return "remove"; + default: + return ""; + } +} + +bool selectionHasToBeNotified(const uno::Reference<accessibility::XAccessibleContext>& xContext) +{ + sal_Int16 nRole = xContext->getAccessibleRole(); + return + nRole == accessibility::AccessibleRole::GRAPHIC || + nRole == accessibility::AccessibleRole::EMBEDDED_OBJECT || + nRole == accessibility::AccessibleRole::SHAPE; +} + +bool hasToBeActiveForEditing(sal_Int16 nRole) +{ + return + nRole == accessibility::AccessibleRole::SHAPE; +} + +sal_Int16 getParentRole(const uno::Reference<accessibility::XAccessibleContext>& xContext) +{ + sal_Int16 nRole = 0; + if (xContext.is()) + { + uno::Reference<accessibility::XAccessibleContext> xParentContext = getParentContext(xContext); + if (xParentContext.is()) + nRole = xParentContext->getAccessibleRole(); + } + return nRole; +} + +sal_Int64 getAccessibleSiblingCount(const Reference<accessibility::XAccessibleContext>& xContext) +{ + if (!xContext.is()) + return -1; + + sal_Int64 nChildCount = 0; + Reference<accessibility::XAccessible> xParent = xContext->getAccessibleParent(); + if (xParent.is()) + { + Reference<accessibility::XAccessibleContext> xParentContext = xParent->getAccessibleContext(); + if (xParentContext.is()) + { + nChildCount = xParentContext->getAccessibleChildCount(); + } + } + return nChildCount - 1; +} + +// Put in rAncestorList all ancestors of xTable up to xAncestorTable or +// up to the first not-a-table ancestor if xAncestorTable is not an ancestor. +// xTable is included in the list, xAncestorTable is not included. +// The list is ordered from the ancient ancestor to xTable. +// Return true if xAncestorTable is an ancestor of xTable. +bool getAncestorList(XAccessibleTableList& rAncestorList, + const uno::Reference<accessibility::XAccessibleTable>& xTable, + const uno::Reference<accessibility::XAccessibleTable>& xAncestorTable = uno::Reference<accessibility::XAccessibleTable>()) +{ + uno::Reference<accessibility::XAccessibleTable> xCurrentTable = xTable; + while (xCurrentTable.is() && xCurrentTable != xAncestorTable) + { + rAncestorList.push_front(xCurrentTable); + + uno::Reference<accessibility::XAccessibleContext> xContext(xCurrentTable, uno::UNO_QUERY); + xCurrentTable.clear(); + if (xContext.is()) + { + uno::Reference<accessibility::XAccessible> xParent = xContext->getAccessibleParent(); + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent, uno::UNO_QUERY); + if (xParentContext.is() + && xParentContext->getAccessibleRole() == accessibility::AccessibleRole::TABLE_CELL) + { + uno::Reference<accessibility::XAccessible> xCellParent = xParentContext->getAccessibleParent(); + if (xCellParent.is()) + { + xCurrentTable = uno::Reference<accessibility::XAccessibleTable>(xCellParent, uno::UNO_QUERY); + } + } + } + } + + return xCurrentTable.is() && xCurrentTable == xAncestorTable; +} + +void lookForParentTable(const uno::Reference<accessibility::XAccessibleContext>& xContext, + uno::Reference<accessibility::XAccessibleTable>& xTable, + sal_Int64& nChildIndex) +{ + using namespace accessibility; + uno::Reference<XAccessibleContext> xParentContext = getParentContext(xContext); + if (xParentContext.is() && xParentContext->getAccessibleRole() == AccessibleRole::TABLE_CELL) + { + uno::Reference<XAccessible> xCellParent = xParentContext->getAccessibleParent(); + if (xCellParent.is()) + { + xTable = uno::Reference<XAccessibleTable>(xCellParent, uno::UNO_QUERY); + if (xTable.is()) + { + nChildIndex = xParentContext->getAccessibleIndexInParent(); + } + } + } +} + +OUString truncateText(OUString& sText, sal_Int32 nNewLength) +{ + // truncate test to given length + OUString sNewText = sText.copy(0, nNewLength); + // try to truncate at a word + nNewLength = sNewText.lastIndexOf(" "); + if (nNewLength > 0) + sNewText = sNewText.copy(0, nNewLength); + return sNewText; +} + +std::string stateSetToString(::sal_Int64 stateSet) +{ + static const std::string states[35] = { + "ACTIVE", "ARMED", "BUSY", "CHECKED", "DEFUNC", + "EDITABLE", "ENABLED", "EXPANDABLE", "EXPANDED", "FOCUSABLE", + "FOCUSED", "HORIZONTAL", "ICONIFIED", "INDETERMINATE", "MANAGES_DESCENDANTS", + "MODAL", "MULTI_LINE", "MULTI_SELECTABLE", "OPAQUE", "PRESSED", + "RESIZABLE", "SELECTABLE", "SELECTED", "SENSITIVE", "SHOWING", + "SINGLE_LINE", "STALE", "TRANSIENT", "VERTICAL", "VISIBLE", + "MOVEABLE", "DEFAULT", "OFFSCREEN", "COLLAPSE", "CHECKABLE" + }; + + if (stateSet == 0) + return "INVALID"; + ::sal_Int64 state = 1; + std::string s; + for (int i = 0; i < 35; ++i) + { + if (stateSet & state) + { + s += states[i]; + s += "|"; + } + state <<= 1; + } + return s; +} + +void aboutView(std::string msg, const void* pInstance, const SfxViewShell* pViewShell) +{ + if (!pViewShell) + return; + + SAL_INFO("lok.a11y", ">>> " << msg << ": instance: " << pInstance + << ", VIED ID: " << pViewShell->GetViewShellId().get() << " <<<"); +} + +void aboutEvent(std::string msg, const accessibility::AccessibleEventObject& aEvent) +{ + try + { + uno::Reference< accessibility::XAccessible > xSource(aEvent.Source, uno::UNO_QUERY); + if (xSource.is()) + { + uno::Reference< accessibility::XAccessibleContext > xContext = + xSource->getAccessibleContext(); + + if (xContext.is()) + { + SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId + << "\n xSource: " << xSource.get() + << "\n role: " << xContext->getAccessibleRole() + << "\n name: " << xContext->getAccessibleName() + << "\n index in parent: " << xContext->getAccessibleIndexInParent() + << "\n state set: " << stateSetToString(xContext->getAccessibleStateSet()) + << "\n parent: " << xContext->getAccessibleParent().get() + << "\n child count: " << xContext->getAccessibleChildCount()); + } + else + { + SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId + << ", no accessible context!"); + } + } + else + { + SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId + << ", no accessible source!"); + } + uno::Reference< accessibility::XAccessible > xOldValue; + aEvent.OldValue >>= xOldValue; + if (xOldValue.is()) + { + uno::Reference< accessibility::XAccessibleContext > xContext = + xOldValue->getAccessibleContext(); + + if (xContext.is()) + { + SAL_INFO("lok.a11y", msg << ": " + "\n xOldValue: " << xOldValue.get() + << "\n role: " << xContext->getAccessibleRole() + << "\n name: " << xContext->getAccessibleName() + << "\n index in parent: " << xContext->getAccessibleIndexInParent() + << "\n state set: " << stateSetToString(xContext->getAccessibleStateSet()) + << "\n parent: " << xContext->getAccessibleParent().get() + << "\n child count: " << xContext->getAccessibleChildCount()); + } + } + uno::Reference< accessibility::XAccessible > xNewValue; + aEvent.NewValue >>= xNewValue; + if (xNewValue.is()) + { + uno::Reference< accessibility::XAccessibleContext > xContext = + xNewValue->getAccessibleContext(); + + if (xContext.is()) + { + SAL_INFO("lok.a11y", msg << ": " + "\n xNewValue: " << xNewValue.get() + << "\n role: " << xContext->getAccessibleRole() + << "\n name: " << xContext->getAccessibleName() + << "\n index in parent: " << xContext->getAccessibleIndexInParent() + << "\n state set: " << stateSetToString(xContext->getAccessibleStateSet()) + << "\n parent: " << xContext->getAccessibleParent().get() + << "\n child count: " << xContext->getAccessibleChildCount()); + } + } + } + catch( const lang::IndexOutOfBoundsException& /*e*/ ) + { + LOK_WARN("lok.a11y", "Focused object has invalid index in parent"); + } +} + +sal_Int32 getListPrefixSize(const uno::Reference<css::accessibility::XAccessibleText>& xAccText) +{ + if (!xAccText.is()) + return 0; + + OUString sText = xAccText->getText(); + sal_Int32 nLength = sText.getLength(); + if (nLength <= 0) + return 0; + + css::uno::Sequence< css::beans::PropertyValue > aRunAttributeList; + css::uno::Sequence< OUString > aRequestedAttributes = {UNO_NAME_NUMBERING_LEVEL, UNO_NAME_NUMBERING}; + aRunAttributeList = xAccText->getCharacterAttributes(0, aRequestedAttributes); + + sal_Int16 nLevel = -1; + bool bIsCounted = false; + for (const auto& attribute: aRunAttributeList) + { + if (attribute.Name.isEmpty()) + continue; + if (attribute.Name == UNO_NAME_NUMBERING_LEVEL) + attribute.Value >>= nLevel; + else if (attribute.Name == UNO_NAME_NUMBERING) + attribute.Value >>= bIsCounted; + } + if (nLevel < 0 || !bIsCounted) + return 0; + + css::accessibility::TextSegment aTextSegment = + xAccText->getTextAtIndex(0, css::accessibility::AccessibleTextType::ATTRIBUTE_RUN); + + SAL_INFO("lok.a11y", "getListPrefixSize: prefix: " << aTextSegment.SegmentText << ", level: " << nLevel); + + return aTextSegment.SegmentEnd; +} + +void aboutTextFormatting(std::string msg, const uno::Reference<css::accessibility::XAccessibleText>& xAccText) +{ + if (!xAccText.is()) + return; + + OUString sText = xAccText->getText(); + sal_Int32 nLength = sText.getLength(); + if (nLength <= 0) + return; + + css::uno::Reference<css::accessibility::XAccessibleTextAttributes> + xAccTextAttr(xAccText, uno::UNO_QUERY); + css::uno::Sequence< OUString > aRequestedAttributes; + + sal_Int32 nPos = 0; + while (nPos < nLength) + { + css::accessibility::TextSegment aTextSegment = + xAccText->getTextAtIndex(nPos, css::accessibility::AccessibleTextType::ATTRIBUTE_RUN); + SAL_INFO("lok.a11y", msg << ": " + "text segment: '" << aTextSegment.SegmentText + << "', start: " << aTextSegment.SegmentStart + << ", end: " << aTextSegment.SegmentEnd); + + css::uno::Sequence< css::beans::PropertyValue > aRunAttributeList; + if (xAccTextAttr.is()) + { + aRunAttributeList = xAccTextAttr->getRunAttributes(nPos, aRequestedAttributes); + } + else + { + aRunAttributeList = xAccText->getCharacterAttributes(nPos, aRequestedAttributes); + } + + sal_Int32 nSize = aRunAttributeList.getLength(); + SAL_INFO("lok.a11y", + msg << ": attribute list size: " << nSize); + if (nSize) + { + OUString sValue; + OUString sAttributes = "{ "; + for (const auto& attribute: aRunAttributeList) + { + if (attribute.Name.isEmpty()) + continue; + + if (attribute.Name == "CharHeight" || attribute.Name == "CharWeight") + { + float fValue = 0; + attribute.Value >>= fValue; + sValue = OUString::number(fValue); + } + else if (attribute.Name == "CharPosture") + { + awt::FontSlant nValue = awt::FontSlant_NONE; + attribute.Value >>= nValue; + sValue = OUString::number(static_cast<unsigned int>(nValue)); + } + else if (attribute.Name == "CharUnderline") + { + sal_Int16 nValue = 0; + attribute.Value >>= nValue; + sValue = OUString::number(nValue); + } + else if (attribute.Name == "CharFontName") + { + attribute.Value >>= sValue; + } + else if (attribute.Name == "Rsid") + { + sal_uInt32 nValue = 0; + attribute.Value >>= nValue; + sValue = OUString::number(nValue); + } + else if (attribute.Name == UNO_NAME_NUMBERING_LEVEL) + { + sal_Int16 nValue(-1); + attribute.Value >>= nValue; + sValue = OUString::number(nValue); + } + else if (attribute.Name == UNO_NAME_NUMBERING) + { + bool bValue(false); + attribute.Value >>= bValue; + sValue = OUString::boolean(bValue); + } + else if (attribute.Name == UNO_NAME_NUMBERING_RULES) + { + attribute.Value >>= sValue; + } + + if (!sValue.isEmpty()) + { + if (sAttributes != "{ ") + sAttributes += ", "; + sAttributes += attribute.Name + ": " + sValue; + sValue = ""; + } + } + sAttributes += " }"; + SAL_INFO("lok.a11y", + msg << ": " << sAttributes); + } + nPos = aTextSegment.SegmentEnd + 1; + } +} + +void aboutParagraph(std::string msg, const OUString& rsParagraphContent, sal_Int32 nCaretPosition, + sal_Int32 nSelectionStart, sal_Int32 nSelectionEnd, sal_Int32 nListPrefixLength, + bool force = false) +{ + SAL_INFO("lok.a11y", msg << ": " + "\n text content: \"" << rsParagraphContent << "\"" + "\n caret pos: " << nCaretPosition + << "\n selection: start: " << nSelectionStart << ", end: " << nSelectionEnd + << "\n list prefix length: " << nListPrefixLength + << "\n force: " << force + ); +} + +void aboutParagraph(std::string msg, const uno::Reference<css::accessibility::XAccessibleText>& xAccText, + bool force = false) +{ + if (!xAccText.is()) + return; + + OUString sText = xAccText->getText(); + sal_Int32 nCaretPosition = xAccText->getCaretPosition(); + sal_Int32 nSelectionStart = xAccText->getSelectionStart(); + sal_Int32 nSelectionEnd = xAccText->getSelectionEnd(); + sal_Int32 nListPrefixLength = getListPrefixSize(xAccText); + aboutParagraph(msg, sText, nCaretPosition, nSelectionStart, nSelectionEnd, nListPrefixLength, force); +} + +void aboutFocusedCellChanged(sal_Int32 nOutCount, const std::vector<TableSizeType>& aInList, + sal_Int32 nRow, sal_Int32 nCol, sal_Int32 nRowSpan, sal_Int32 nColSpan) +{ + std::stringstream inListStream; + inListStream << "[ "; + for (const auto& rTableSize: aInList) + { + inListStream << "{ rowCount: " << rTableSize.nRowCount << " colCount: " << rTableSize.nColCount << " } "; + } + inListStream << "]"; + + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyFocusedCellChanged: " + "\n outCount: " << nOutCount + << "\n inList: " << inListStream.str() + << "\n row: " << nRow + << "\n column: " << nCol + << "\n rowSpan: " << nRowSpan + << "\n colSpan: " << nColSpan + ); +} +} // anonymous namespace + +class LOKDocumentFocusListener : + public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener > +{ + static constexpr sal_Int64 MAX_ATTACHABLE_CHILDREN = 100; + + const SfxViewShell* m_pViewShell; + sal_Int16 m_nDocumentType; + std::unordered_set<uno::Reference<uno::XInterface>> m_aRefList; + OUString m_sFocusedParagraph; + sal_Int32 m_nCaretPosition; + sal_Int32 m_nSelectionStart; + sal_Int32 m_nSelectionEnd; + sal_Int32 m_nListPrefixLength; + uno::Reference<accessibility::XAccessibleTable> m_xLastTable; + OUString m_sSelectedText; + bool m_bIsEditingCell; + // used for text content of a shape + bool m_bIsEditingInSelection; + OUString m_sSelectedCellAddress; + uno::Reference<accessibility::XAccessible> m_xSelectedObject; + +public: + explicit LOKDocumentFocusListener(const SfxViewShell* pViewShell); + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + void attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible + ); + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + void attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + const uno::Reference< accessibility::XAccessibleContext >& xContext + ); + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + void attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + const uno::Reference< accessibility::XAccessibleContext >& xContext, + const sal_Int64 nStateSet + ); + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + void detachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + bool bForce = false + ); + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + void detachRecursive( + const uno::Reference< accessibility::XAccessibleContext >& xContext, + bool bForce = false + ); + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + void detachRecursive( + const uno::Reference< accessibility::XAccessibleContext >& xContext, + const sal_Int64 nStateSet, + bool bForce = false + ); + + /// @throws lang::IndexOutOfBoundsException + /// @throws uno::RuntimeException + static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent ); + + // XEventListener + virtual void SAL_CALL disposing( const lang::EventObject& Source ) override; + + // XAccessibleEventListener + virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override; + + void notifyEditingInSelectionState(bool bParagraph = true); + void notifyFocusedParagraphChanged(bool force = false); + void notifyCaretChanged(); + void notifyTextSelectionChanged(); + void notifyFocusedCellChanged(sal_Int32 nOutCount, const std::vector<TableSizeType>& aInList, sal_Int32 nRow, sal_Int32 nCol, sal_Int32 nRowSpan, sal_Int32 nColSpan); + void notifySelectionChanged(const uno::Reference<accessibility::XAccessible>& xAccObj, const OUString& sAction); + + OUString getFocusedParagraph() const; + int getCaretPosition() const; + +private: + void paragraphPropertiesToTree(boost::property_tree::ptree& aPayloadTree, bool force = false) const; + void paragraphPropertiesToJson(std::string& aPayload, bool force = false) const; + bool updateParagraphInfo(const uno::Reference<css::accessibility::XAccessibleText>& xAccText, + bool force, std::string msg = ""); + void updateAndNotifyParagraph(const uno::Reference<css::accessibility::XAccessibleText>& xAccText, + bool force, std::string msg = ""); + void resetParagraphInfo(); + void onFocusedParagraphInWriterTable(const uno::Reference<accessibility::XAccessibleTable>& xTable, + sal_Int64& nChildIndex, + const uno::Reference<accessibility::XAccessibleText>& xAccText); + uno::Reference< accessibility::XAccessible > + getSelectedObject(const accessibility::AccessibleEventObject& aEvent) const; + void onShapeSelectionChanged(const Reference<accessibility::XAccessible>& xSelectedObject, + const OUString& sAction); +}; + +LOKDocumentFocusListener::LOKDocumentFocusListener(const SfxViewShell* pViewShell) + : m_pViewShell(pViewShell) + , m_nDocumentType(0) + , m_nCaretPosition(0) + , m_nSelectionStart(0) + , m_nSelectionEnd(0) + , m_nListPrefixLength(0) + , m_bIsEditingCell(false) + , m_bIsEditingInSelection(false) +{ +} + +void LOKDocumentFocusListener::paragraphPropertiesToTree(boost::property_tree::ptree& aPayloadTree, bool force) const +{ + bool bLeftToRight = m_nCaretPosition == m_nSelectionEnd; + aPayloadTree.put("content", m_sFocusedParagraph.toUtf8().getStr()); + aPayloadTree.put("position", m_nCaretPosition); + aPayloadTree.put("start", bLeftToRight ? m_nSelectionStart : m_nSelectionEnd); + aPayloadTree.put("end", bLeftToRight ? m_nSelectionEnd : m_nSelectionStart); + if (m_nListPrefixLength > 0) + aPayloadTree.put("listPrefixLength", m_nListPrefixLength); + if (force) + aPayloadTree.put("force", 1); +} + +void LOKDocumentFocusListener::paragraphPropertiesToJson(std::string& aPayload, bool force) const +{ + boost::property_tree::ptree aPayloadTree; + paragraphPropertiesToTree(aPayloadTree, force); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + aPayload = aStream.str(); +} + +OUString LOKDocumentFocusListener::getFocusedParagraph() const +{ + aboutView("LOKDocumentFocusListener::getFocusedParagraph", this, m_pViewShell); + aboutParagraph("LOKDocumentFocusListener::getFocusedParagraph", + m_sFocusedParagraph, m_nCaretPosition, + m_nSelectionStart, m_nSelectionEnd, m_nListPrefixLength); + + std::string aPayload; + paragraphPropertiesToJson(aPayload); + OUString sRet = OUString::fromUtf8(aPayload); + return sRet; +} + +int LOKDocumentFocusListener::getCaretPosition() const +{ + aboutView("LOKDocumentFocusListener::getCaretPosition", this, m_pViewShell); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::getCaretPosition: " << m_nCaretPosition); + return m_nCaretPosition; +} + +// notifyEditingInSelectionState +// Used for notifying when editing becomes active/disabled for a shape +// bParagraph: should we append currently focused paragraph ? +// The problem is that the initially focused paragraph could not be the one user has clicked on, +// when there are more than a single paragraph. +// So in some case sending the focused paragraph could be misleading. +void LOKDocumentFocusListener::notifyEditingInSelectionState(bool bParagraph) +{ + aboutView("LOKDocumentFocusListener::notifyEditingInSelectionState", this, m_pViewShell); + + boost::property_tree::ptree aPayloadTree; + bool bIsCell = !m_sSelectedCellAddress.isEmpty(); + aPayloadTree.put("cell", bIsCell ? 1 : 0); + if (bIsCell) + { + aPayloadTree.put("enabled", m_bIsEditingCell ? 1 : 0); + if (m_bIsEditingCell) + { + aPayloadTree.put("selection", m_sSelectedCellAddress); + if (bParagraph) + aPayloadTree.put("paragraph", m_sFocusedParagraph); + } + } + else + { + aPayloadTree.put("enabled", m_bIsEditingInSelection ? 1 : 0); + if (m_bIsEditingInSelection && m_xSelectedObject.is()) + { + uno::Reference<accessibility::XAccessibleContext> xContext = m_xSelectedObject->getAccessibleContext(); + if (xContext.is()) + { + OUString sSelectionDescr = xContext->getAccessibleName(); + sSelectionDescr = sSelectionDescr.trim(); + aPayloadTree.put("selection", sSelectionDescr); + if (bParagraph) + aPayloadTree.put("paragraph", m_sFocusedParagraph); + } + } + } + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEditingInSelectionState: payload: \n" << aPayload); + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_EDITING_IN_SELECTION_STATE, aPayload.c_str()); + } +} + +/// notifyFocusedParagraphChanged +// +// Notify content, caret position and text selection start/end for the focused paragraph +// in current view. +// For focused we don't mean to be necessarily the currently focused accessibility node. +// It's enough that the caret is present in the paragraph (position != -1). +// In fact each view has its own accessibility node per each text paragraph. +// Anyway there can be only one focused accessibility node at time. +// So when text changes are performed in one view, both accessibility nodes emit +// a text changed event, anyway only the accessibility node belonging to the view +// where the text change has occurred is the focused one. +// +// force: when true update the clipboard content even if client is composing. +// +// Usually when editing on the client involves composing the clipboard area updating +// is skipped until the composition is over. +// On the contrary the composition would be aborted, making dictation not possible. +// Anyway when the text change has been performed by another view we are in due +// to update the clipboard content even if the user is in the middle of a composition. +void LOKDocumentFocusListener::notifyFocusedParagraphChanged(bool force) +{ + aboutView("LOKDocumentFocusListener::notifyFocusedParagraphChanged", this, m_pViewShell); + std::string aPayload; + paragraphPropertiesToJson(aPayload, force); + if (m_pViewShell) + { + aboutParagraph("LOKDocumentFocusListener::notifyFocusedParagraphChanged", + m_sFocusedParagraph, m_nCaretPosition, + m_nSelectionStart, m_nSelectionEnd, m_nListPrefixLength, force); + + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_FOCUS_CHANGED, aPayload.c_str()); + } +} + +void LOKDocumentFocusListener::notifyCaretChanged() +{ + aboutView("LOKDocumentFocusListener::notifyCaretChanged", this, m_pViewShell); + boost::property_tree::ptree aPayloadTree; + aPayloadTree.put("position", m_nCaretPosition); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyCaretChanged: " << m_nCaretPosition); + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_CARET_CHANGED, aPayload.c_str()); + } +} + +void LOKDocumentFocusListener::notifyTextSelectionChanged() +{ + aboutView("LOKDocumentFocusListener::notifyTextSelectionChanged", this, m_pViewShell); + bool bLeftToRight = m_nCaretPosition == m_nSelectionEnd; + boost::property_tree::ptree aPayloadTree; + aPayloadTree.put("start", bLeftToRight ? m_nSelectionStart : m_nSelectionEnd); + aPayloadTree.put("end", bLeftToRight ? m_nSelectionEnd : m_nSelectionStart); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyTextSelectionChanged: " + "start: " << m_nSelectionStart << ", end: " << m_nSelectionEnd); + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED, aPayload.c_str()); + } +} + +void LOKDocumentFocusListener::notifyFocusedCellChanged( + sal_Int32 nOutCount, const std::vector<TableSizeType>& aInList, + sal_Int32 nRow, sal_Int32 nCol, sal_Int32 nRowSpan, sal_Int32 nColSpan) +{ + aboutView("LOKDocumentFocusListener::notifyTablePositionChanged", this, m_pViewShell); + boost::property_tree::ptree aPayloadTree; + if (nOutCount > 0) + { + aPayloadTree.put("outCount", nOutCount); + } + if (!aInList.empty()) + { + boost::property_tree::ptree aInListNode; + for (const auto& rTableSize: aInList) + { + boost::property_tree::ptree aTableSizeNode; + aTableSizeNode.put("rowCount", rTableSize.nRowCount); + aTableSizeNode.put("colCount", rTableSize.nColCount); + + aInListNode.push_back(std::make_pair(std::string(), aTableSizeNode)); + } + aPayloadTree.add_child("inList", aInListNode); + } + + aPayloadTree.put("row", nRow); + aPayloadTree.put("col", nCol); + + if (nRowSpan > 1) + { + aPayloadTree.put("rowSpan", nRowSpan); + } + if (nColSpan > 1) + { + aPayloadTree.put("colSpan", nColSpan); + } + + boost::property_tree::ptree aContentNode; + paragraphPropertiesToTree(aContentNode); + aPayloadTree.add_child("paragraph", aContentNode); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + aboutFocusedCellChanged(nOutCount, aInList, nRow, nCol, nRowSpan, nColSpan); + aboutParagraph("LOKDocumentFocusListener::notifyFocusedCellChanged: paragraph: ", + m_sFocusedParagraph, m_nCaretPosition, m_nSelectionStart, m_nSelectionEnd, + m_nListPrefixLength, false); + + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED, aPayload.c_str()); + } +} + +void LOKDocumentFocusListener::notifySelectionChanged(const uno::Reference<accessibility::XAccessible>& xAccObj, + const OUString& sAction) +{ + using namespace accessibility; + if (!xAccObj.is()) + return; + + aboutView("LOKDocumentFocusListener::notifySelectionChanged", this, m_pViewShell); + uno::Reference<XAccessibleContext> xContext = xAccObj->getAccessibleContext(); + if (xContext.is()) + { + OUString sName = xContext->getAccessibleName(); + sName = sName.trim(); + if (sName == "GraphicObjectShape") + sName = "Graphic"; + + // check for text content and send it with some limitations: + // no more than 10 paragraphs, no more than 1000 chars + bool bIsCell = xContext->getAccessibleRole() == AccessibleRole::TABLE_CELL; + OUString sTextContent; + if (sAction == "create" || sAction == "add") + { + const sal_Int64 nMaxJoinedParagraphs = 10; + const sal_Int32 nMaxTextContentLength = 1000; + if (bIsCell) + { + uno::Reference<XAccessibleText> xAccText(xAccObj, uno::UNO_QUERY); + if (xAccText.is()) + { + sTextContent = xAccText->getText(); + sal_Int32 nTextLength = sTextContent.getLength(); + if (nTextLength > nMaxTextContentLength) + { + sTextContent = truncateText(sTextContent, nMaxTextContentLength); + } + } + } + else + { + sal_Int32 nTotalTextLength = 0; + sal_Int64 nChildCount = xContext->getAccessibleChildCount(); + if (nChildCount > nMaxJoinedParagraphs) + nChildCount = nMaxJoinedParagraphs; + for (sal_Int64 i = 0; i < nChildCount; ++i) + { + uno::Reference<XAccessible> xChild = xContext->getAccessibleChild(i); + uno::Reference<XAccessibleText> xAccText(xChild, uno::UNO_QUERY); + if (!xAccText.is()) + continue; + OUString sText = xAccText->getText(); + sal_Int32 nTextLength = sText.getLength(); + if (nTextLength < 1) + continue; + if (nTotalTextLength + nTextLength < nMaxTextContentLength) + { + nTotalTextLength += nTextLength; + sTextContent += sText + " \n"; + } + else + { + // truncate paragraph + sal_Int32 nNewLength = nMaxTextContentLength - nTotalTextLength; + sTextContent += truncateText(sText, nNewLength); + break; + } + } + } + } + + boost::property_tree::ptree aPayloadTree; + aPayloadTree.put("cell", bIsCell ? 1 : 0); + aPayloadTree.put("action", sAction); + aPayloadTree.put("name", sName); + if (!sTextContent.isEmpty()) + aPayloadTree.put("text", sTextContent); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aPayloadTree); + std::string aPayload = aStream.str(); + if (m_pViewShell) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifySelectionChanged: " + "action: " << sAction << ", name: " << sName); + m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_SELECTION_CHANGED, aPayload.c_str()); + } + } +} + +void LOKDocumentFocusListener::disposing( const lang::EventObject& aEvent ) +{ + // Unref the object here, but do not remove as listener since the object + // might no longer be in a state that safely allows this. + if( aEvent.Source.is() ) + m_aRefList.erase(aEvent.Source); + +} + +bool LOKDocumentFocusListener::updateParagraphInfo(const uno::Reference<css::accessibility::XAccessibleText>& xAccText, + bool force, std::string msg) +{ + if (!xAccText.is()) + return false; + + bool bNotify = false; + // If caret is present inside the paragraph (pos != -1), it means that paragraph has focus in the current view. + sal_Int32 nCaretPosition = xAccText->getCaretPosition(); + if (nCaretPosition >= 0) + { + OUString sText = xAccText->getText(); + m_nCaretPosition = nCaretPosition; + m_nSelectionStart = xAccText->getSelectionStart(); + m_nSelectionEnd = xAccText->getSelectionEnd(); + m_nListPrefixLength = getListPrefixSize(xAccText); + + // Inside a text shape when there is no selection, selection-start and selection-end are + // set to current caret position instead of -1. Moreover, inside a text shape pressing + // delete or backspace with an empty selection really deletes text and not only the empty + // selection as it occurs in a text paragraph in Writer. + // So whenever selection-start == selection-end, and we are inside a shape we need + // to set these parameters to -1 in order to have the client to handle delete and + // backspace properly. + if (m_nSelectionStart == m_nSelectionEnd && m_nSelectionStart != -1) + { + uno::Reference<accessibility::XAccessibleContext> xContext(xAccText, uno::UNO_QUERY); + sal_Int16 nParentRole = getParentRole(xContext); + if (nParentRole == accessibility::AccessibleRole::SHAPE || + nParentRole == accessibility::AccessibleRole::TEXT_FRAME) // spreadsheet cell editing + m_nSelectionStart = m_nSelectionEnd = -1; + } + + // In case only caret position or text selection are different we can rely on specific events. + if (m_sFocusedParagraph != sText) + { + m_sFocusedParagraph = sText; + bNotify = true; + } + } + else + { + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::updateParagraphInfo: skipped since no caret is present"); + } + + std::string header = "LOKDocumentFocusListener::updateParagraphInfo"; + if (msg.size()) + header += ": " + msg; + aboutParagraph(header, xAccText, force); + return bNotify; + +} + +void LOKDocumentFocusListener::updateAndNotifyParagraph( + const uno::Reference<css::accessibility::XAccessibleText>& xAccText, + bool force, std::string msg) +{ + if (updateParagraphInfo(xAccText, force, msg)) + notifyFocusedParagraphChanged(force); +} + +void LOKDocumentFocusListener::resetParagraphInfo() +{ + m_sFocusedParagraph = ""; + m_nCaretPosition = 0; + m_nSelectionStart = -1; + m_nSelectionEnd = -1; + m_nListPrefixLength = 0; +} + +// For a presentation document when an accessible event of type SELECTION_CHANGED_XXX occurs +// the selected (or unselected) object is put in NewValue, instead for a text document +// the selected object is put in Source. +// The following function helps to retrieve the selected object independently on where it has been put. +uno::Reference< accessibility::XAccessible > +LOKDocumentFocusListener::getSelectedObject(const accessibility::AccessibleEventObject& aEvent) const +{ + uno::Reference< accessibility::XAccessible > xSelectedObject; + if (isText(m_nDocumentType)) + { + xSelectedObject.set(aEvent.Source, uno::UNO_QUERY); + } + else + { + aEvent.NewValue >>= xSelectedObject; + } + return xSelectedObject; +} + +void LOKDocumentFocusListener::onShapeSelectionChanged( + const uno::Reference<accessibility::XAccessible>& xSelectedObject, + const OUString& sAction) +{ + // when a shape is selected or unselected we could need to notify that text content editing + // is no more active, that allows on the client side to prevent default input. + resetParagraphInfo(); + if (m_bIsEditingInSelection) + { + m_bIsEditingInSelection = false; + notifyEditingInSelectionState(); + } + notifySelectionChanged(xSelectedObject, sAction); +} + +void LOKDocumentFocusListener::onFocusedParagraphInWriterTable( + const uno::Reference<accessibility::XAccessibleTable>& xTable, + sal_Int64& nChildIndex, + const uno::Reference<accessibility::XAccessibleText>& xAccText +) +{ + std::vector<TableSizeType> aInList; + sal_Int32 nOutCount = 0; + + if (m_xLastTable.is()) + { + if (xTable != m_xLastTable) + { + // do we get in one or more nested tables ? + // check if xTable is a descendant of m_xLastTable + XAccessibleTableList newTableAncestorList; + bool isLastAncestorOfNew = getAncestorList(newTableAncestorList, xTable, m_xLastTable); + bool isNewAncestorOfLast = false; + if (!isLastAncestorOfNew) + { + // do we get out of one or more nested tables ? + // check if m_xLastTable is a descendant of xTable + XAccessibleTableList lastTableAncestorList; + isNewAncestorOfLast = getAncestorList(lastTableAncestorList, m_xLastTable, xTable); + // we have to notify "out of table" for all m_xLastTable ancestors up to xTable + // or the first not-a-table ancestor + nOutCount = lastTableAncestorList.size(); + } + if (isLastAncestorOfNew || !isNewAncestorOfLast) + { + // we have to notify row/col count for all xTable ancestors starting from the ancestor + // which is a child of m_xLastTable (isLastAncestorOfNew) or the first not-a-table ancestor + for (const auto& ancestor: newTableAncestorList) + { + TableSizeType aTableSize{ancestor->getAccessibleRowCount(), + ancestor->getAccessibleColumnCount()}; + aInList.push_back(aTableSize); + } + } + } + } + else + { + // cursor was not inside any table and gets inside one or more tables + // we have to notify row/col count for all xTable ancestors starting from first not-a-table ancestor + XAccessibleTableList newTableAncestorList; + getAncestorList(newTableAncestorList, xTable); + for (const auto& ancestor: newTableAncestorList) + { + TableSizeType aTableSize{ancestor->getAccessibleRowCount(), + ancestor->getAccessibleColumnCount()}; + aInList.push_back(aTableSize); + } + } + + // we have to notify current row/col of xTable and related row/col span + sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex); + sal_Int32 nCol = xTable->getAccessibleColumn(nChildIndex); + sal_Int32 nRowSpan = xTable->getAccessibleRowExtentAt(nRow, nCol); + sal_Int32 nColSpan = xTable->getAccessibleColumnExtentAt(nRow, nCol); + + m_xLastTable = xTable; + updateParagraphInfo(xAccText, false, "STATE_CHANGED: FOCUSED"); + notifyFocusedCellChanged(nOutCount, aInList, nRow, nCol, nRowSpan, nColSpan); +} + +void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventObject& aEvent) +{ + using namespace accessibility; + aboutView("LOKDocumentFocusListener::notifyEvent", this, m_pViewShell); + + try + { + aboutEvent("LOKDocumentFocusListener::notifyEvent", aEvent); + + switch (aEvent.EventId) + { + case AccessibleEventId::STATE_CHANGED: + { + // logging + sal_Int64 nState = accessibility::AccessibleStateType::INVALID; + aEvent.NewValue >>= nState; + sal_Int64 nOldState = accessibility::AccessibleStateType::INVALID; + aEvent.OldValue >>= nOldState; + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: STATE_CHANGED: " + " New State: " << stateSetToString(nState) + << ", Old State: " << stateSetToString(nOldState)); + + // check validity + uno::Reference< XAccessible > xAccessibleObject = getAccessible(aEvent); + if (!xAccessibleObject.is()) + return; + uno::Reference<XAccessibleContext> xContext(aEvent.Source, uno::UNO_QUERY); + if (!xContext) + return; + + sal_Int16 nRole = xContext->getAccessibleRole(); + + if (nRole == AccessibleRole::PARAGRAPH) + { + uno::Reference<XAccessibleText> xAccText(xAccessibleObject, uno::UNO_QUERY); + if (!xAccText.is()) + return; + + switch (nState) + { + case AccessibleStateType::ACTIVE: + { + if (!m_bIsEditingInSelection && hasToBeActiveForEditing(getParentRole(xContext))) + { + m_bIsEditingInSelection = true; + } + break; + } + case AccessibleStateType::FOCUSED: + { + if (m_bIsEditingInSelection && m_xSelectedObject.is()) + { + updateParagraphInfo(xAccText, true, "STATE_CHANGED: FOCUSED"); + notifyEditingInSelectionState(getAccessibleSiblingCount(xContext) == 0); + notifyFocusedParagraphChanged(true); + // we clear selected object so when editing is over but shape is + // still selected, the selection event is notified the same to the client + m_xSelectedObject.clear(); + return; + } + if (isText(m_nDocumentType)) + { + // check if we are inside a table: in case notify table and current cell info + bool isInsideTable = false; + uno::Reference<XAccessibleTable> xTable; + sal_Int64 nChildIndex; + lookForParentTable(xContext, xTable, nChildIndex); + if (xTable.is()) + { + onFocusedParagraphInWriterTable(xTable, nChildIndex, xAccText); + isInsideTable = true; + } + // paragraph is not inside any table + if (!isInsideTable) + { + if (m_xLastTable.is()) + { + // we get out one or more tables + // we have to notify "out of table" for all m_xLastTable ancestors + // up to the first not-a-table ancestor + XAccessibleTableList lastTableAncestorList; + getAncestorList(lastTableAncestorList, m_xLastTable); + sal_Int32 nOutCount = lastTableAncestorList.size(); + // no more inside a table + m_xLastTable.clear(); + // notify + std::vector<TableSizeType> aInList; + updateParagraphInfo(xAccText, false, "STATE_CHANGED: FOCUSED"); + notifyFocusedCellChanged(nOutCount, aInList, -1, -1, 1, 1); + } + else + { + updateAndNotifyParagraph(xAccText, false, "STATE_CHANGED: FOCUSED"); + } + } + } + else if (isSpreadsheet(m_nDocumentType)) + { + if (m_bIsEditingCell) + { + if (!hasState(aEvent, AccessibleStateType::ACTIVE)) + { + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: FOCUSED: " + "cell not ACTIVE for editing yet"); + return; + } + else if (m_xSelectedObject.is()) + { + updateParagraphInfo(xAccText, true, "STATE_CHANGED: ACTIVE"); + notifyEditingInSelectionState(getAccessibleSiblingCount(xContext) == 0); + notifyFocusedParagraphChanged(true); + m_xSelectedObject.clear(); + return; + } + + updateAndNotifyParagraph(xAccText, false, "STATE_CHANGED: FOCUSED"); + } + } + else if (isPresentation(m_nDocumentType)) + { + updateAndNotifyParagraph(xAccText, false, "STATE_CHANGED: FOCUSED"); + } + aboutTextFormatting("LOKDocumentFocusListener::notifyEvent: STATE_CHANGED: FOCUSED", xAccText); + + break; + } + default: + break; + } + } + break; + } + case AccessibleEventId::CARET_CHANGED: + { + sal_Int32 nNewPos = -1; + aEvent.NewValue >>= nNewPos; + sal_Int32 nOldPos = -1; + aEvent.OldValue >>= nOldPos; + + if (nNewPos >= 0) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: CARET_CHANGED: " + "new pos: " << nNewPos << ", nOldPos: " << nOldPos); + + uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); + if (xAccText.is()) + { + m_nCaretPosition = nNewPos; + // Let's say we are in the following case: 'Hello wor|ld', + // where '|' is the cursor position for the current view. + // Suppose that in another view it's typed <enter> soon before 'world'. + // Now the new paragraph content and caret position is: 'wor|ld'. + // Anyway no new paragraph focused event is emitted for current view. + // Only a new caret position event is emitted. + // So we could need to notify a new focused paragraph changed message. + if (!isFocused(aEvent)) + { + if (updateParagraphInfo(xAccText, false, "CARET_CHANGED")) + notifyFocusedParagraphChanged(true); + } + else + { + notifyCaretChanged(); + } + aboutParagraph("LOKDocumentFocusListener::notifyEvent: CARET_CHANGED", xAccText); + } + } + break; + } + case AccessibleEventId::TEXT_CHANGED: + { + TextSegment aDeletedText; + TextSegment aInsertedText; + + if (aEvent.OldValue >>= aDeletedText) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: " + "deleted text: >" << aDeletedText.SegmentText << "<"); + } + if (aEvent.NewValue >>= aInsertedText) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: " + "inserted text: >" << aInsertedText.SegmentText << "<"); + } + uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); + + // When the change has been performed in another view we need to force + // paragraph content updating on the client, even if current editing involves composing. + // We make a guess that if the paragraph accessibility node is not focused, + // it means that the text change has been performed in another view. + updateAndNotifyParagraph(xAccText, !isFocused(aEvent), "TEXT_CHANGED"); + + break; + } + case AccessibleEventId::TEXT_SELECTION_CHANGED: + { + if (!isFocused(aEvent)) + { + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: TEXT_SELECTION_CHANGED: " + "skip non focused paragraph"); + return; + } + + uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); + if (xAccText.is()) + { + // We send a message to client also when start/end are -1, in this way the client knows + // if a text selection object exists or not. That's needed because of the odd behavior + // occurring when <backspace>/<delete> are hit and a text selection is empty, + // but it still exists. + // Such keys delete the empty selection instead of the previous/next char. + updateParagraphInfo(xAccText, false, "TEXT_SELECTION_CHANGED"); + + m_sSelectedText = xAccText->getSelectedText(); + SAL_INFO("lok.a11y", + "LOKDocumentFocusListener::notifyEvent: TEXT_SELECTION_CHANGED: selected text: >" + << m_sSelectedText << "<"); + + // Calc: when editing a formula send the update content + if (m_bIsEditingCell) + { + OUString sText = xAccText->getText(); + if (!m_sSelectedCellAddress.isEmpty() && + !m_sSelectedText.isEmpty() && sText.startsWith("=")) + { + notifyFocusedParagraphChanged(); + } + } + notifyTextSelectionChanged(); + } + break; + } + case AccessibleEventId::SELECTION_CHANGED: + case AccessibleEventId::SELECTION_CHANGED_REMOVE: + { + uno::Reference< XAccessible > xSelectedObject = getSelectedObject(aEvent); + if (!xSelectedObject.is()) + return; + uno::Reference< XAccessibleContext > xContext = xSelectedObject->getAccessibleContext(); + if (!xContext.is()) + return; + + if (aEvent.EventId == AccessibleEventId::SELECTION_CHANGED_REMOVE) + m_xSelectedObject.clear(); + else if (m_xSelectedObject.is() && m_xSelectedObject == xSelectedObject) + return; // selecting the same object; note: on editing selected object is cleared + else + m_xSelectedObject = xSelectedObject; + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: SELECTION_CHANGED: " + "m_xSelectedObject.is(): " << m_xSelectedObject.is()); + + OUString sAction = selectionEventTypeToString(aEvent.EventId); + sal_Int16 nRole = xContext->getAccessibleRole(); + switch(nRole) + { + case AccessibleRole::GRAPHIC: + case AccessibleRole::EMBEDDED_OBJECT: + case AccessibleRole::SHAPE: + { + onShapeSelectionChanged(xSelectedObject, sAction); + break; + } + case AccessibleRole::TABLE_CELL: + { + notifySelectionChanged(xSelectedObject, sAction); + + if (aEvent.EventId == AccessibleEventId::SELECTION_CHANGED) + { + m_sSelectedCellAddress = xContext->getAccessibleName(); + if (m_bIsEditingCell && !m_sSelectedCellAddress.isEmpty()) + { + // Check cell address: "$Sheet1.A10". + // On cell editing SELECTION_CHANGED is not emitted when selection is expanded. + // So selection can't be a cell range. + sal_Int32 nDotIndex = m_sSelectedText.indexOf('.'); + OUString sCellAddress = m_sSelectedText.copy(nDotIndex + 1); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: SELECTION_CHANGED: " + "cell address: >" << sCellAddress << "<"); + if (m_sSelectedCellAddress == sCellAddress) + { + notifyFocusedParagraphChanged(); + notifyTextSelectionChanged(); + } + } + } + break; + } + default: + break; + } + break; + } + case AccessibleEventId::CHILD: + { + uno::Reference< accessibility::XAccessible > xChild; + if( (aEvent.OldValue >>= xChild) && xChild.is() ) + detachRecursive(xChild); + + if( (aEvent.NewValue >>= xChild) && xChild.is() ) + attachRecursive(xChild); + + break; + } + case AccessibleEventId::INVALIDATE_ALL_CHILDREN: + { + SAL_INFO("lok.a11y", "Invalidate all children called"); + break; + } + default: + break; + } + } + catch( const lang::IndexOutOfBoundsException& ) + { + LOK_WARN("lok.a11y", + "LOKDocumentFocusListener::notifyEvent:Focused object has invalid index in parent"); + } +} + +uno::Reference< accessibility::XAccessible > LOKDocumentFocusListener::getAccessible(const lang::EventObject& aEvent ) +{ + uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY); + + if( xAccessible.is() ) + return xAccessible; + + SAL_WARN("lok.a11y", + "LOKDocumentFocusListener::getAccessible: Event source doesn't implement XAccessible."); + + uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY); + + if( xContext.is() ) + { + uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() ); + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + if( xParentContext.is() ) + { + return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() ); + } + } + } + + LOK_WARN("lok.a11y", + "LOKDocumentFocusListener::getAccessible: Can't get any accessible object from event source."); + + return uno::Reference< accessibility::XAccessible >(); +} + +void LOKDocumentFocusListener::attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible +) +{ + LOK_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(1): xAccessible: " << xAccessible.get()); + + uno::Reference< accessibility::XAccessibleContext > xContext = + xAccessible->getAccessibleContext(); + + if( xContext.is() ) + attachRecursive(xAccessible, xContext); +} + +void LOKDocumentFocusListener::attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + const uno::Reference< accessibility::XAccessibleContext >& xContext +) +{ + try + { + LOK_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(2): xAccessible: " + << xAccessible.get() << ", role: " << xContext->getAccessibleRole() + << ", name: " << xContext->getAccessibleName() + << ", parent: " << xContext->getAccessibleParent().get() + << ", child count: " << xContext->getAccessibleChildCount()); + + sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + + if (!m_bIsEditingCell) + { + ::rtl::OUString sName = xContext->getAccessibleName(); + m_bIsEditingCell = sName.startsWith("Cell"); + } + + attachRecursive(xAccessible, xContext, nStateSet); + } + catch (const uno::Exception& e) + { + LOK_WARN("lok.a11y", "LOKDocumentFocusListener::attachRecursive(2): raised exception: " << e.Message); + } +} + +void LOKDocumentFocusListener::attachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + const uno::Reference< accessibility::XAccessibleContext >& xContext, + const sal_Int64 nStateSet +) +{ + aboutView("LOKDocumentFocusListener::attachRecursive (3)", this, m_pViewShell); + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #1: this: " << this + << ", xAccessible: " << xAccessible.get() + << ", role: " << xContext->getAccessibleRole() + << ", name: " << xContext->getAccessibleName() + << ", index in parent: " << xContext->getAccessibleIndexInParent() + << ", state: " << stateSetToString(nStateSet) + << ", parent: " << xContext->getAccessibleParent().get() + << ", child count: " << xContext->getAccessibleChildCount()); + + uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY); + + if (!xBroadcaster.is()) + return; + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #2: xBroadcaster.is()"); + // If not already done, add the broadcaster to the list and attach as listener. + const uno::Reference< uno::XInterface >& xInterface = xBroadcaster; + if( m_aRefList.insert(xInterface).second ) + { + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #3: m_aRefList.insert(xInterface).second"); + xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this)); + + if (isDocument(xContext->getAccessibleRole())) + { + m_nDocumentType = xContext->getAccessibleRole(); + } + + if (!(nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS)) + { + if ((nStateSet & accessibility::AccessibleStateType::SELECTED) && + selectionHasToBeNotified(xContext)) + { + uno::Reference< accessibility::XAccessible > xAccObj(xContext, uno::UNO_QUERY); + onShapeSelectionChanged(xAccObj, "create"); + } + + sal_Int64 nmax = xContext->getAccessibleChildCount(); + if( nmax > MAX_ATTACHABLE_CHILDREN ) + nmax = MAX_ATTACHABLE_CHILDREN; + + for( sal_Int64 n = 0; n < nmax; n++ ) + { + uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) ); + + if( xChild.is() ) + attachRecursive(xChild); + } + } + else + { + // Usually, when the document is loaded, a CARET_CHANGED accessibility event is automatically emitted + // for the first paragraph. That allows to notify the paragraph content to the client, even if no input + // event occurred yet. However, when switching to a11y enabled in the client and in Cypress tests + // no accessibility event is automatically emitted until some input event occurs. + // So we use the following workaround to notify the content of the focused paragraph, + // without waiting for an input event. + // Here we update the paragraph info related to the focused paragraph, + // later when afterCallbackRegistered is executed we notify the paragraph content. + sal_Int64 nChildCount = xContext->getAccessibleChildCount(); + if (nChildCount > 0 && nChildCount < 10) + { + for (sal_Int64 n = 0; n < nChildCount; ++n) + { + uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(n)); + if (xChild.is()) + { + uno::Reference<css::accessibility::XAccessibleText> xAccText(xChild, uno::UNO_QUERY); + if (xAccText.is()) + { + sal_Int32 nPos = xAccText->getCaretPosition(); + if (nPos >= 0) + { + attachRecursive(xChild); + updateParagraphInfo(xAccText, false, "LOKDocumentFocusListener::attachRecursive(3)"); + break; + } + } + } + } + } + } + } +} + +void LOKDocumentFocusListener::detachRecursive( + const uno::Reference< accessibility::XAccessible >& xAccessible, + bool bForce +) +{ + uno::Reference< accessibility::XAccessibleContext > xContext = + xAccessible->getAccessibleContext(); + + if( xContext.is() ) + detachRecursive(xContext, bForce); +} + +void LOKDocumentFocusListener::detachRecursive( + const uno::Reference< accessibility::XAccessibleContext >& xContext, + bool bForce +) +{ + aboutView("LOKDocumentFocusListener::detachRecursive (2)", this, m_pViewShell); + sal_Int64 nStateSet = xContext->getAccessibleStateSet(); + + SAL_INFO("lok.a11y", "LOKDocumentFocusListener::detachRecursive(2): this: " << this + << ", name: " << xContext->getAccessibleName() + << ", parent: " << xContext->getAccessibleParent().get() + << ", child count: " << xContext->getAccessibleChildCount()); + + if (m_bIsEditingCell) + { + ::rtl::OUString sName = xContext->getAccessibleName(); + m_bIsEditingCell = !sName.startsWith("Cell"); + if (!m_bIsEditingCell) + { + m_sFocusedParagraph = ""; + m_nCaretPosition = 0; + notifyFocusedParagraphChanged(); + } + } + + detachRecursive(xContext, nStateSet, bForce); +} + +void LOKDocumentFocusListener::detachRecursive( + const uno::Reference< accessibility::XAccessibleContext >& xContext, + const sal_Int64 nStateSet, + bool bForce +) +{ + uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY); + + if (xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster)) + { + xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this)); + + if ((nStateSet & accessibility::AccessibleStateType::SELECTED) && + selectionHasToBeNotified(xContext)) + { + uno::Reference< accessibility::XAccessible > xAccObj(xContext, uno::UNO_QUERY); + onShapeSelectionChanged(xAccObj, "delete"); + } + + if (bForce || !(nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS)) + { + sal_Int64 nmax = xContext->getAccessibleChildCount(); + if (nmax > MAX_ATTACHABLE_CHILDREN) + nmax = MAX_ATTACHABLE_CHILDREN; + for (sal_Int64 n = 0; n < nmax; n++) + { + uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(n)); + + if (xChild.is()) + detachRecursive(xChild); + } + } + } +} + +sal_uInt32 SfxViewShell_Impl::m_nLastViewShellId = 0; + +SfxViewShell_Impl::SfxViewShell_Impl(SfxViewShellFlags const nFlags, ViewShellDocId nDocId) +: m_bHasPrintOptions(nFlags & SfxViewShellFlags::HAS_PRINTOPTIONS) +, m_nFamily(0xFFFF) // undefined, default set by TemplateDialog +, m_pLibreOfficeKitViewCallback(nullptr) +, m_bTiledSearching(false) +, m_nViewShellId(SfxViewShell_Impl::m_nLastViewShellId++) +, m_nDocId(nDocId) +{ +} + +SfxViewShell_Impl::~SfxViewShell_Impl() +{ +} + +std::vector< SfxInPlaceClient* >& SfxViewShell_Impl::GetIPClients_Impl() +{ + return maIPClients; +} + +SFX_IMPL_SUPERCLASS_INTERFACE(SfxViewShell,SfxShell) + +void SfxViewShell::InitInterface_Impl() +{ +} + + +/** search for a filter name dependent on type and module + */ +static OUString impl_retrieveFilterNameFromTypeAndModule( + const css::uno::Reference< css::container::XContainerQuery >& rContainerQuery, + const OUString& rType, + const OUString& rModuleIdentifier, + const sal_Int32 nFlags ) +{ + // Retrieve filter from type + css::uno::Sequence< css::beans::NamedValue > aQuery { + { "Type", css::uno::Any( rType ) }, + { "DocumentService", css::uno::Any( rModuleIdentifier ) } + }; + + css::uno::Reference< css::container::XEnumeration > xEnumeration = + rContainerQuery->createSubSetEnumerationByProperties( aQuery ); + + OUString aFoundFilterName; + while ( xEnumeration->hasMoreElements() ) + { + ::comphelper::SequenceAsHashMap aFilterPropsHM( xEnumeration->nextElement() ); + OUString aFilterName = aFilterPropsHM.getUnpackedValueOrDefault( + "Name", + OUString() ); + + sal_Int32 nFilterFlags = aFilterPropsHM.getUnpackedValueOrDefault( + "Flags", + sal_Int32( 0 ) ); + + if ( nFilterFlags & nFlags ) + { + aFoundFilterName = aFilterName; + break; + } + } + + return aFoundFilterName; +} + +namespace { + +/** search for an internal typename, which map to the current app module + and map also to a "family" of file formats as e.g. PDF/MS Doc/OOo Doc. + */ +enum ETypeFamily +{ + E_MS_DOC, + E_OOO_DOC +}; + +} + +static OUString impl_searchFormatTypeForApp(const css::uno::Reference< css::frame::XFrame >& xFrame , + ETypeFamily eTypeFamily) +{ + try + { + css::uno::Reference< css::uno::XComponentContext > xContext (::comphelper::getProcessComponentContext()); + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager(css::frame::ModuleManager::create(xContext)); + + OUString sModule = xModuleManager->identify(xFrame); + OUString sType ; + + switch(eTypeFamily) + { + case E_MS_DOC: + { + if ( sModule == "com.sun.star.text.TextDocument" ) + sType = "writer_MS_Word_2007"; + else + if ( sModule == "com.sun.star.sheet.SpreadsheetDocument" ) + sType = "MS Excel 2007 XML"; + else + if ( sModule == "com.sun.star.presentation.PresentationDocument" ) + sType = "MS PowerPoint 2007 XML"; + } + break; + + case E_OOO_DOC: + { + if ( sModule == "com.sun.star.text.TextDocument" ) + sType = "writer8"; + else + if ( sModule == "com.sun.star.sheet.SpreadsheetDocument" ) + sType = "calc8"; + else + if ( sModule == "com.sun.star.drawing.DrawingDocument" ) + sType = "draw8"; + else + if ( sModule == "com.sun.star.presentation.PresentationDocument" ) + sType = "impress8"; + } + break; + } + + return sType; + } + catch (const css::uno::RuntimeException&) + { + throw; + } + catch (const css::uno::Exception&) + { + } + + return OUString(); +} + +void SfxViewShell::NewIPClient_Impl( SfxInPlaceClient *pIPClient ) +{ + pImpl->GetIPClients_Impl().push_back(pIPClient); +} + +void SfxViewShell::IPClientGone_Impl( SfxInPlaceClient const *pIPClient ) +{ + std::vector< SfxInPlaceClient* >& pClients = pImpl->GetIPClients_Impl(); + + auto it = std::find(pClients.begin(), pClients.end(), pIPClient); + if (it != pClients.end()) + pClients.erase( it ); +} + + +void SfxViewShell::ExecMisc_Impl( SfxRequest &rReq ) +{ + const sal_uInt16 nId = rReq.GetSlot(); + switch( nId ) + { + case SID_STYLE_FAMILY : + { + const SfxUInt16Item* pItem = rReq.GetArg<SfxUInt16Item>(nId); + if (pItem) + { + pImpl->m_nFamily = pItem->GetValue(); + } + break; + } + case SID_ACTIVATE_STYLE_APPLY: + { + uno::Reference< frame::XFrame > xFrame = + GetViewFrame().GetFrame().GetFrameInterface(); + + Reference< beans::XPropertySet > xPropSet( xFrame, UNO_QUERY ); + Reference< frame::XLayoutManager > xLayoutManager; + if ( xPropSet.is() ) + { + try + { + Any aValue = xPropSet->getPropertyValue("LayoutManager"); + aValue >>= xLayoutManager; + if ( xLayoutManager.is() ) + { + uno::Reference< ui::XUIElement > xElement = xLayoutManager->getElement( "private:resource/toolbar/textobjectbar" ); + if(!xElement.is()) + { + xElement = xLayoutManager->getElement( "private:resource/toolbar/frameobjectbar" ); + } + if(!xElement.is()) + { + xElement = xLayoutManager->getElement( "private:resource/toolbar/oleobjectbar" ); + } + if(xElement.is()) + { + uno::Reference< awt::XWindow > xWin( xElement->getRealInterface(), uno::UNO_QUERY_THROW ); + VclPtr<vcl::Window> pWin = VCLUnoHelper::GetWindow( xWin ); + ToolBox* pTextToolbox = dynamic_cast< ToolBox* >( pWin.get() ); + if( pTextToolbox ) + { + ToolBox::ImplToolItems::size_type nItemCount = pTextToolbox->GetItemCount(); + for( ToolBox::ImplToolItems::size_type nItem = 0; nItem < nItemCount; ++nItem ) + { + ToolBoxItemId nItemId = pTextToolbox->GetItemId( nItem ); + const OUString& rCommand = pTextToolbox->GetItemCommand( nItemId ); + if (rCommand == ".uno:StyleApply") + { + vcl::Window* pItemWin = pTextToolbox->GetItemWindow( nItemId ); + if( pItemWin ) + pItemWin->GrabFocus(); + break; + } + } + } + } + } + } + catch (const Exception&) + { + } + } + rReq.Done(); + } + break; + + case SID_MAIL_SENDDOCASMS: + case SID_MAIL_SENDDOCASOOO: + case SID_MAIL_SENDDOCASPDF: + case SID_MAIL_SENDDOC: + case SID_MAIL_SENDDOCASFORMAT: + { + SfxObjectShell* pDoc = GetObjectShell(); + if (!pDoc) + break; + pDoc->QueryHiddenInformation(HiddenWarningFact::WhenSaving); + SfxMailModel aModel; + OUString aDocType; + + const SfxStringItem* pMailRecipient = rReq.GetArg<SfxStringItem>(SID_MAIL_RECIPIENT); + if ( pMailRecipient ) + { + OUString aRecipient( pMailRecipient->GetValue() ); + OUString aMailToStr("mailto:"); + + if ( aRecipient.startsWith( aMailToStr ) ) + aRecipient = aRecipient.copy( aMailToStr.getLength() ); + aModel.AddToAddress( aRecipient ); + } + const SfxStringItem* pMailDocType = rReq.GetArg<SfxStringItem>(SID_TYPE_NAME); + if ( pMailDocType ) + aDocType = pMailDocType->GetValue(); + + uno::Reference < frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() ); + SfxMailModel::SendMailResult eResult = SfxMailModel::SEND_MAIL_ERROR; + + if ( nId == SID_MAIL_SENDDOC ) + eResult = aModel.SaveAndSend( xFrame, OUString() ); + else if ( nId == SID_MAIL_SENDDOCASPDF ) + eResult = aModel.SaveAndSend( xFrame, "pdf_Portable_Document_Format"); + else if ( nId == SID_MAIL_SENDDOCASMS ) + { + aDocType = impl_searchFormatTypeForApp(xFrame, E_MS_DOC); + if (!aDocType.isEmpty()) + eResult = aModel.SaveAndSend( xFrame, aDocType ); + } + else if ( nId == SID_MAIL_SENDDOCASOOO ) + { + aDocType = impl_searchFormatTypeForApp(xFrame, E_OOO_DOC); + if (!aDocType.isEmpty()) + eResult = aModel.SaveAndSend( xFrame, aDocType ); + } + + if ( eResult == SfxMailModel::SEND_MAIL_ERROR ) + { + weld::Window* pWin = SfxGetpApp()->GetTopWindow(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin, + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_ERROR_SEND_MAIL))); + xBox->run(); + rReq.Ignore(); + } + else + rReq.Done(); + } + break; + + case SID_BLUETOOTH_SENDDOC: + { + SfxBluetoothModel aModel; + SfxObjectShell* pDoc = GetObjectShell(); + if (!pDoc) + break; + pDoc->QueryHiddenInformation(HiddenWarningFact::WhenSaving); + uno::Reference < frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() ); + SfxMailModel::SendMailResult eResult = aModel.SaveAndSend( xFrame ); + if( eResult == SfxMailModel::SEND_MAIL_ERROR ) + { + weld::Window* pWin = SfxGetpApp()->GetTopWindow(); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin, + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_ERROR_SEND_MAIL))); + xBox->run(); + rReq.Ignore(); + } + else + rReq.Done(); + } + break; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + case SID_WEBHTML: + { + css::uno::Reference< lang::XMultiServiceFactory > xSMGR(::comphelper::getProcessServiceFactory(), css::uno::UNO_SET_THROW); + css::uno::Reference< uno::XComponentContext > xContext(::comphelper::getProcessComponentContext(), css::uno::UNO_SET_THROW); + css::uno::Reference< css::frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() ); + css::uno::Reference< css::frame::XModel > xModel; + + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager( css::frame::ModuleManager::create(xContext) ); + + OUString aModule; + try + { + aModule = xModuleManager->identify( xFrame ); + } + catch (const css::uno::RuntimeException&) + { + throw; + } + catch (const css::uno::Exception&) + { + } + + if ( xFrame.is() ) + { + css::uno::Reference< css::frame::XController > xController = xFrame->getController(); + if ( xController.is() ) + xModel = xController->getModel(); + } + + // We need at least a valid module name and model reference + css::uno::Reference< css::frame::XStorable > xStorable( xModel, css::uno::UNO_QUERY ); + if ( xModel.is() && xStorable.is() ) + { + OUString aFilterName; + OUString aTypeName( "generic_HTML" ); + OUString aFileName; + + OUString aLocation = xStorable->getLocation(); + INetURLObject aFileObj( aLocation ); + + bool bPrivateProtocol = ( aFileObj.GetProtocol() == INetProtocol::PrivSoffice ); + bool bHasLocation = !aLocation.isEmpty() && !bPrivateProtocol; + + css::uno::Reference< css::container::XContainerQuery > xContainerQuery( + xSMGR->createInstance( "com.sun.star.document.FilterFactory" ), + css::uno::UNO_QUERY_THROW ); + + // Retrieve filter from type + + sal_Int32 nFilterFlags = 0x00000002; // export + aFilterName = impl_retrieveFilterNameFromTypeAndModule( xContainerQuery, aTypeName, aModule, nFilterFlags ); + if ( aFilterName.isEmpty() ) + { + // Draw/Impress uses a different type. 2nd chance try to use alternative type name + aFilterName = impl_retrieveFilterNameFromTypeAndModule( + xContainerQuery, "graphic_HTML", aModule, nFilterFlags ); + } + + // No filter found => error + // No type and no location => error + if ( aFilterName.isEmpty() || aTypeName.isEmpty()) + { + rReq.Done(); + return; + } + + // Use provided save file name. If empty determine file name + if ( !bHasLocation ) + { + // Create a default file name with the correct extension + aFileName = "webpreview"; + } + else + { + // Determine file name from model + INetURLObject aFObj( xStorable->getLocation() ); + aFileName = aFObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::NONE ); + } + + OSL_ASSERT( !aFilterName.isEmpty() ); + OSL_ASSERT( !aFileName.isEmpty() ); + + // Creates a temporary directory to store our predefined file into it (for the + // flatpak case, create it in XDG_CACHE_HOME instead of /tmp for technical reasons, + // so that it can be accessed by the browser running outside the sandbox): + OUString * parent = nullptr; + if (flatpak::isFlatpak() && !flatpak::createTemporaryHtmlDirectory(&parent)) + { + SAL_WARN("sfx.view", "cannot create Flatpak html temp dir"); + } + + INetURLObject aFilePathObj( ::utl::CreateTempURL(parent, true) ); + aFilePathObj.insertName( aFileName ); + aFilePathObj.setExtension( u"htm" ); + + OUString aFileURL = aFilePathObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + css::uno::Sequence< css::beans::PropertyValue > aArgs{ + comphelper::makePropertyValue("FilterName", aFilterName) + }; + + // Store document in the html format + try + { + xStorable->storeToURL( aFileURL, aArgs ); + } + catch (const io::IOException&) + { + rReq.Done(); + return; + } + + sfx2::openUriExternally(aFileURL, true, rReq.GetFrameWeld()); + rReq.Done(true); + break; + } + else + { + rReq.Done(); + return; + } + } + } +} + + +void SfxViewShell::GetState_Impl( SfxItemSet &rSet ) +{ + + SfxWhichIter aIter( rSet ); + SfxObjectShell *pSh = GetViewFrame().GetObjectShell(); + for ( sal_uInt16 nSID = aIter.FirstWhich(); nSID; nSID = aIter.NextWhich() ) + { + switch ( nSID ) + { + + case SID_BLUETOOTH_SENDDOC: + case SID_MAIL_SENDDOC: + case SID_MAIL_SENDDOCASFORMAT: + case SID_MAIL_SENDDOCASMS: + case SID_MAIL_SENDDOCASOOO: + case SID_MAIL_SENDDOCASPDF: + { +#if HAVE_FEATURE_MACOSX_SANDBOX + rSet.DisableItem(nSID); +#endif + if (pSh && pSh->isExportLocked()) + rSet.DisableItem(nSID); + break; + } + case SID_WEBHTML: + { + if (pSh && pSh->isExportLocked()) + rSet.DisableItem(nSID); + break; + } + // Printer functions + case SID_PRINTDOC: + case SID_PRINTDOCDIRECT: + case SID_SETUPPRINTER: + case SID_PRINTER_NAME: + { + if (Application::GetSettings().GetMiscSettings().GetDisablePrinting() + || (pSh && pSh->isPrintLocked())) + { + rSet.DisableItem(nSID); + break; + } + + SfxPrinter *pPrinter = GetPrinter(); + + if ( SID_PRINTDOCDIRECT == nSID ) + { + OUString aPrinterName; + if ( pPrinter != nullptr ) + aPrinterName = pPrinter->GetName(); + else + { + // tdf#109149 don't poll the Default Printer Name on every query. + // We are queried on every change, so on every + // keystroke, and we are only using this to fill in the + // printername inside the label of "Print Directly (printer-name)" + // On Printer::GetDefaultPrinterName() is implemented with + // GetDefaultPrinter so don't call this excessively. 5 mins + // seems a reasonable refresh time. + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + std::chrono::minutes five_mins(5); + if (now > pImpl->m_nDefaultPrinterNameFetchTime + five_mins) + { + pImpl->m_sDefaultPrinterName = Printer::GetDefaultPrinterName(); + pImpl->m_nDefaultPrinterNameFetchTime = now; + } + aPrinterName = pImpl->m_sDefaultPrinterName; + } + if ( !aPrinterName.isEmpty() ) + { + uno::Reference < frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() ); + + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(".uno:PrintDefault", + vcl::CommandInfoProvider::GetModuleIdentifier(xFrame)); + OUString val = vcl::CommandInfoProvider::GetLabelForCommand(aProperties) + + " (" + aPrinterName + ")"; + + rSet.Put( SfxStringItem( SID_PRINTDOCDIRECT, val ) ); + } + } + break; + } + case SID_STYLE_FAMILY : + { + rSet.Put( SfxUInt16Item( SID_STYLE_FAMILY, pImpl->m_nFamily ) ); + break; + } + } + } +} + +void SfxViewShell::SetZoomFactor( const Fraction &rZoomX, + const Fraction &rZoomY ) +{ + DBG_ASSERT( GetWindow(), "no window" ); + MapMode aMap( GetWindow()->GetMapMode() ); + aMap.SetScaleX( rZoomX ); + aMap.SetScaleY( rZoomY ); + GetWindow()->SetMapMode( aMap ); +} + +ErrCode SfxViewShell::DoVerb(sal_Int32 /*nVerb*/) + +/* [Description] + + Virtual Method used to perform a Verb on a selected Object. + Since this Object is only known by the derived classes, they must override + DoVerb. +*/ + +{ + return ERRCODE_SO_NOVERBS; +} + +void SfxViewShell::OutplaceActivated( bool bActive ) +{ + if ( !bActive ) + GetFrame()->GetFrame().Appear(); +} + +void SfxViewShell::UIActivating( SfxInPlaceClient* /*pClient*/ ) +{ + uno::Reference < frame::XFrame > xOwnFrame( rFrame.GetFrame().GetFrameInterface() ); + uno::Reference < frame::XFramesSupplier > xParentFrame = xOwnFrame->getCreator(); + if ( xParentFrame.is() ) + xParentFrame->setActiveFrame( xOwnFrame ); + + rFrame.GetBindings().HidePopups(); + rFrame.GetDispatcher()->Update_Impl( true ); +} + +void SfxViewShell::UIDeactivated( SfxInPlaceClient* /*pClient*/ ) +{ + if ( !rFrame.GetFrame().IsClosing_Impl() || SfxViewFrame::Current() != &rFrame ) + rFrame.GetDispatcher()->Update_Impl( true ); + rFrame.GetBindings().HidePopups(false); + + rFrame.GetBindings().InvalidateAll(true); +} + +SfxInPlaceClient* SfxViewShell::FindIPClient +( + const uno::Reference < embed::XEmbeddedObject >& xObj, + vcl::Window* pObjParentWin +) const +{ + std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl(); + if ( rClients.empty() ) + return nullptr; + + if( !pObjParentWin ) + pObjParentWin = GetWindow(); + for (SfxInPlaceClient* pIPClient : rClients) + { + if ( pIPClient->GetObject() == xObj && pIPClient->GetEditWin() == pObjParentWin ) + return pIPClient; + } + + return nullptr; +} + + +SfxInPlaceClient* SfxViewShell::GetIPClient() const +{ + return GetUIActiveClient(); +} + + +SfxInPlaceClient* SfxViewShell::GetUIActiveIPClient_Impl() const +{ + // this method is needed as long as SFX still manages the border space for ChildWindows (see SfxFrame::Resize) + std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl(); + if ( rClients.empty() ) + return nullptr; + + for (SfxInPlaceClient* pIPClient : rClients) + { + if ( pIPClient->IsUIActive() ) + return pIPClient; + } + + return nullptr; +} + +SfxInPlaceClient* SfxViewShell::GetUIActiveClient() const +{ + std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl(); + if ( rClients.empty() ) + return nullptr; + + const bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + + for (SfxInPlaceClient* pIPClient : rClients) + { + if ( pIPClient->IsObjectUIActive() || ( bIsTiledRendering && pIPClient->IsObjectInPlaceActive() ) ) + return pIPClient; + } + + return nullptr; +} + + +void SfxViewShell::Activate( bool bMDI ) +{ + if ( bMDI ) + { + SfxObjectShell *pSh = GetViewFrame().GetObjectShell(); + if (const auto xModel = pSh->GetModel()) + xModel->setCurrentController(GetController()); + + SetCurrentDocument(); + } +} + + +void SfxViewShell::Deactivate(bool /*bMDI*/) +{ +} + + +void SfxViewShell::Move() + +/* [Description] + + This virtual Method is called when the window displayed in the + SfxViewShell gets a StarView-Move() notification. + + This base implementation does not have to be called. . + + [Note] + + This Method can be used to cancel a selection, in order to catch the + mouse movement which is due to moving a window. + + For now the notification does not work In-Place. +*/ + +{ +} + + +void SfxViewShell::OuterResizePixel +( + const Point& /*rToolOffset*/,// Upper left corner Tools in Frame-Window + const Size& /*rSize*/ // All available sizes. +) + +/* [Description] + + Override this Method to be able to react to the size-change of + the View. Thus the View is defined as the Edit window and also the + attached Tools are defined (for example the ruler). + + The Edit window must not be changed either in size or position. + + The Vis-Area of SfxObjectShell, its scale and position can be changed + here. The main use is to change the size of the Vis-Area. + + If the Border is changed due to the new calculation then this has to be set + by <SfxViewShell::SetBorderPixel(const SvBorder&)>. The Positioning of Tools + is only allowed after the calling of 'SetBorderPixel'. + + [Example] + + void AppViewSh::OuterViewResizePixel( const Point &rOfs, const Size &rSz ) + { + // Calculate Tool position and size externally, do not set! + // (due to the following Border calculation) + Point aHLinPos...; Size aHLinSz...; + ... + + // Calculate and Set a Border of Tools which matches rSize. + SvBorder aBorder... + SetBorderPixel( aBorder ); // Allow Positioning from here on. + + // Arrange Tools + pHLin->SetPosSizePixel( aHLinPos, aHLinSz ); + ... + } + + [Cross-reference] + + <SfxViewShell::InnerResizePixel(const Point&,const Size& rSize)> +*/ + +{ + SetBorderPixel( SvBorder() ); +} + + +void SfxViewShell::InnerResizePixel +( + const Point& /*rToolOffset*/,// Upper left corner Tools in Frame-Window + const Size& /*rSize*/, // All available sizes. + bool +) + +/* [Description] + + Override this Method to be able to react to the size-change of + the Edit window. + + The Edit window must not be changed either in size or position. + Neither the Vis-Area of SfxObjectShell nor its scale or position are + allowed to be changed + + If the Border is changed due to the new calculation then is has to be set + by <SfxViewShell::SetBorderPixel(const SvBorder&)>. + The Positioning of Tools is only allowed after the calling of + 'SetBorderPixel'. + + + [Note] + + void AppViewSh::InnerViewResizePixel( const Point &rOfs, const Size &rSz ) + { + // Calculate Tool position and size internally, do not set! + // (due to the following Border calculation) + Point aHLinPos...; Size aHLinSz...; + ... + + // Calculate and Set a Border of Tools which matches rSize. + SvBorder aBorder... + SetBorderPixel( aBorder ); // Allow Positioning from here on. + + // Arrange Tools + pHLin->SetPosSizePixel( aHLinPos, aHLinSz ); + ... + } + + [Cross-reference] + + <SfxViewShell::OuterResizePixel(const Point&,const Size& rSize)> +*/ + +{ + SetBorderPixel( SvBorder() ); +} + +void SfxViewShell::InvalidateBorder() +{ + GetViewFrame().InvalidateBorderImpl( this ); + if (pImpl->m_pController.is()) + { + pImpl->m_pController->BorderWidthsChanged_Impl(); + } +} + +void SfxViewShell::SetBorderPixel( const SvBorder &rBorder ) +{ + GetViewFrame().SetBorderPixelImpl( this, rBorder ); + + // notify related controller that border size is changed + if (pImpl->m_pController.is()) + { + pImpl->m_pController->BorderWidthsChanged_Impl(); + } +} + +const SvBorder& SfxViewShell::GetBorderPixel() const +{ + return GetViewFrame().GetBorderPixelImpl(); +} + +void SfxViewShell::SetWindow +( + vcl::Window* pViewPort // For example Null pointer in the Destructor. +) + +/* [Description] + + With this method the SfxViewShell is set in the data window. This is + needed for the in-place container and for restoring the proper focus. + + Even in-place-active the conversion of the ViewPort Windows is forbidden. +*/ + +{ + if( pWindow == pViewPort ) + return; + + // Disconnect existing IP-Clients if possible + DisconnectAllClients(); + + // Switch View-Port + bool bHadFocus = pWindow && pWindow->HasChildPathFocus( true ); + pWindow = pViewPort; + + if( pWindow ) + { + // Disable automatic GUI mirroring (right-to-left) for document windows + pWindow->EnableRTL( false ); + } + + if ( bHadFocus && pWindow ) + pWindow->GrabFocus(); + //TODO/CLEANUP + //Do we still need this Method?! + //SfxGetpApp()->GrabFocus( pWindow ); +} + +ViewShellDocId SfxViewShell::mnCurrentDocId(0); + +SfxViewShell::SfxViewShell +( + SfxViewFrame& rViewFrame, /* <SfxViewFrame>, which will be + displayed in this View */ + SfxViewShellFlags nFlags /* See <SfxViewShell-Flags> */ +) + +: SfxShell(this) +, pImpl( new SfxViewShell_Impl(nFlags, SfxViewShell::mnCurrentDocId) ) +, rFrame(rViewFrame) +, pWindow(nullptr) +, bNoNewWindow( nFlags & SfxViewShellFlags::NO_NEWWINDOW ) +, mbPrinterSettingsModified(false) +, maLOKLanguageTag(LANGUAGE_NONE) +, maLOKLocale(LANGUAGE_NONE) +, maLOKDeviceFormFactor(LOKDeviceFormFactor::UNKNOWN) +, mbLOKAccessibilityEnabled(false) +{ + SetMargin( rViewFrame.GetMargin_Impl() ); + + SetPool( &rViewFrame.GetObjectShell()->GetPool() ); + StartListening(*rViewFrame.GetObjectShell()); + + // Insert into list + std::vector<SfxViewShell*> &rViewArr = SfxGetpApp()->GetViewShells_Impl(); + rViewArr.push_back(this); + + if (comphelper::LibreOfficeKit::isActive()) + { + maLOKLanguageTag = SfxLokHelper::getDefaultLanguage(); + maLOKLocale = SfxLokHelper::getDefaultLanguage(); + + const auto [isTimezoneSet, aTimezone] = SfxLokHelper::getDefaultTimezone(); + maLOKIsTimezoneSet = isTimezoneSet; + maLOKTimezone = aTimezone; + + maLOKDeviceFormFactor = SfxLokHelper::getDeviceFormFactor(); + + vcl::Window* pFrameWin = rViewFrame.GetWindow().GetFrameWindow(); + if (pFrameWin && !pFrameWin->GetLOKNotifier()) + pFrameWin->SetLOKNotifier(this, true); + } +} + +SfxViewShell::~SfxViewShell() +{ + // Remove from list + const SfxViewShell *pThis = this; + std::vector<SfxViewShell*> &rViewArr = SfxGetpApp()->GetViewShells_Impl(); + auto it = std::find( rViewArr.begin(), rViewArr.end(), pThis ); + rViewArr.erase( it ); + + if ( pImpl->xClipboardListener.is() ) + { + pImpl->xClipboardListener->DisconnectViewShell(); + pImpl->xClipboardListener = nullptr; + } + + if (pImpl->m_pController.is()) + { + pImpl->m_pController->ReleaseShell_Impl(); + pImpl->m_pController.clear(); + } + + vcl::Window* pFrameWin = GetViewFrame().GetWindow().GetFrameWindow(); + if (pFrameWin && pFrameWin->GetLOKNotifier() == this) + pFrameWin->ReleaseLOKNotifier(); +} + +OUString SfxViewShell::getA11yFocusedParagraph() const +{ + const LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener(); + return rDocFocusListener.getFocusedParagraph(); +} + +int SfxViewShell::getA11yCaretPosition() const +{ + const LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener(); + return rDocFocusListener.getCaretPosition(); +} + +bool SfxViewShell::PrepareClose +( + bool bUI // TRUE: Allow Dialog and so on, FALSE: silent-mode +) +{ + if (GetViewFrame().GetWindow().GetLOKNotifier() == this) + GetViewFrame().GetWindow().ReleaseLOKNotifier(); + + SfxPrinter *pPrinter = GetPrinter(); + if ( pPrinter && pPrinter->IsPrinting() ) + { + if ( bUI ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetViewFrame().GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_CANT_CLOSE))); + xBox->run(); + } + + return false; + } + + if( GetViewFrame().IsInModalMode() ) + return false; + + if( bUI && GetViewFrame().GetDispatcher()->IsLocked() ) + return false; + + return true; +} + + +SfxViewShell* SfxViewShell::Current() +{ + SfxViewFrame *pCurrent = SfxViewFrame::Current(); + return pCurrent ? pCurrent->GetViewShell() : nullptr; +} + + +SfxViewShell* SfxViewShell::Get( const Reference< XController>& i_rController ) +{ + if ( !i_rController.is() ) + return nullptr; + + for ( SfxViewShell* pViewShell = SfxViewShell::GetFirst( false ); + pViewShell; + pViewShell = SfxViewShell::GetNext( *pViewShell, false ) + ) + { + if ( pViewShell->GetController() == i_rController ) + return pViewShell; + } + return nullptr; +} + + +SdrView* SfxViewShell::GetDrawView() const + +/* [Description] + + This virtual Method has to be overloaded by the sub classes, to be able + make the Property-Editor available. + + The default implementation does always return zero. +*/ + +{ + return nullptr; +} + + +OUString SfxViewShell::GetSelectionText +( + bool /*bCompleteWords*/, /* FALSE (default) + Only the actual selected text is returned. + + TRUE + The selected text is expanded so that only + whole words are returned. As word separators + these are used: white spaces and punctuation + ".,;" and single and double quotes. + */ + bool /*bOnlyASample*/ /* used by some dialogs to avoid constructing monster strings e.g. in calc */ +) + +/* [Description] + + Override this Method to return a text that + is included in the current selection. This is for example used when + sending emails. + + When called with "CompleteWords == TRUE", it is for example sufficient + with having the Cursor positioned somewhere within a URL in-order + to have the entire URL returned. +*/ + +{ + return OUString(); +} + + +bool SfxViewShell::HasSelection( bool ) const + +/* [Description] + + With this virtual Method can a for example a Dialog be queried, to + check if something is selected in the current view. If the Parameter + is <BOOL> TRUE then it is checked whether some text is selected. +*/ + +{ + return false; +} + +void SfxViewShell::AddSubShell( SfxShell& rShell ) +{ + pImpl->aArr.push_back(&rShell); + SfxDispatcher *pDisp = rFrame.GetDispatcher(); + if ( pDisp->IsActive(*this) ) + { + pDisp->Push(rShell); + pDisp->Flush(); + } +} + +void SfxViewShell::RemoveSubShell( SfxShell* pShell ) +{ + SfxDispatcher *pDisp = rFrame.GetDispatcher(); + if ( !pShell ) + { + size_t nCount = pImpl->aArr.size(); + if ( pDisp->IsActive(*this) ) + { + for(size_t n = nCount; n > 0; --n) + pDisp->Pop(*pImpl->aArr[n - 1]); + pDisp->Flush(); + } + pImpl->aArr.clear(); + } + else + { + SfxShellArr_Impl::iterator i = std::find(pImpl->aArr.begin(), pImpl->aArr.end(), pShell); + if(i != pImpl->aArr.end()) + { + pImpl->aArr.erase(i); + if(pDisp->IsActive(*this)) + { + pDisp->RemoveShell_Impl(*pShell); + pDisp->Flush(); + } + } + } +} + +SfxShell* SfxViewShell::GetSubShell( sal_uInt16 nNo ) +{ + sal_uInt16 nCount = pImpl->aArr.size(); + if(nNo < nCount) + return pImpl->aArr[nCount - nNo - 1]; + return nullptr; +} + +void SfxViewShell::PushSubShells_Impl( bool bPush ) +{ + SfxDispatcher *pDisp = rFrame.GetDispatcher(); + if ( bPush ) + { + for (auto const& elem : pImpl->aArr) + pDisp->Push(*elem); + } + else if(!pImpl->aArr.empty()) + { + SfxShell& rPopUntil = *pImpl->aArr[0]; + if ( pDisp->GetShellLevel( rPopUntil ) != USHRT_MAX ) + pDisp->Pop( rPopUntil, SfxDispatcherPopFlags::POP_UNTIL ); + } + + pDisp->Flush(); +} + + +void SfxViewShell::WriteUserData( OUString&, bool ) +{ +} + + +void SfxViewShell::ReadUserData(const OUString&, bool ) +{ +} + +void SfxViewShell::ReadUserDataSequence ( const uno::Sequence < beans::PropertyValue >& ) +{ +} + +void SfxViewShell::WriteUserDataSequence ( uno::Sequence < beans::PropertyValue >& ) +{ +} + + +// returns the first shell of spec. type viewing the specified doc. +SfxViewShell* SfxViewShell::GetFirst +( + bool bOnlyVisible, + const std::function< bool ( const SfxViewShell* ) >& isViewShell +) +{ + // search for a SfxViewShell of the specified type + std::vector<SfxViewShell*> &rShells = SfxGetpApp()->GetViewShells_Impl(); + for (SfxViewShell* pShell : rShells) + { + if ( pShell ) + { + // This code used to check that the frame exists in the other list, + // because of https://bz.apache.org/ooo/show_bug.cgi?id=62084, with the explanation: + // sometimes dangling SfxViewShells exist that point to a dead SfxViewFrame + // these ViewShells shouldn't be accessible anymore + // a destroyed ViewFrame is not in the ViewFrame array anymore, so checking this array helps + // That doesn't seem to be needed anymore, but keep an assert, just in case. + assert(std::find(SfxGetpApp()->GetViewFrames_Impl().begin(), SfxGetpApp()->GetViewFrames_Impl().end(), + &pShell->GetViewFrame()) != SfxGetpApp()->GetViewFrames_Impl().end()); + if ( ( !bOnlyVisible || pShell->GetViewFrame().IsVisible() ) && (!isViewShell || isViewShell(pShell))) + return pShell; + } + } + + return nullptr; +} + +// returns the next shell of spec. type viewing the specified doc. +SfxViewShell* SfxViewShell::GetNext +( + const SfxViewShell& rPrev, + bool bOnlyVisible, + const std::function<bool ( const SfxViewShell* )>& isViewShell +) +{ + std::vector<SfxViewShell*> &rShells = SfxGetpApp()->GetViewShells_Impl(); + size_t nPos; + for ( nPos = 0; nPos < rShells.size(); ++nPos ) + if ( rShells[nPos] == &rPrev ) + break; + + for ( ++nPos; nPos < rShells.size(); ++nPos ) + { + SfxViewShell *pShell = rShells[nPos]; + if ( pShell ) + { + assert(std::find(SfxGetpApp()->GetViewFrames_Impl().begin(), SfxGetpApp()->GetViewFrames_Impl().end(), + &pShell->GetViewFrame()) != SfxGetpApp()->GetViewFrames_Impl().end()); + if ( ( !bOnlyVisible || pShell->GetViewFrame().IsVisible() ) && (!isViewShell || isViewShell(pShell)) ) + return pShell; + } + } + + return nullptr; +} + + +void SfxViewShell::Notify( SfxBroadcaster& rBC, + const SfxHint& rHint ) +{ + if (rHint.GetId() != SfxHintId::ThisIsAnSfxEventHint || + static_cast<const SfxEventHint&>(rHint).GetEventId() != SfxEventHintId::LoadFinished) + { + return; + } + + if ( !GetController().is() ) + return; + + // avoid access to dangling ViewShells + auto &rFrames = SfxGetpApp()->GetViewFrames_Impl(); + for (SfxViewFrame* frame : rFrames) + { + if ( frame == &GetViewFrame() && &rBC == GetObjectShell() ) + { + SfxItemSet& rSet = GetObjectShell()->GetMedium()->GetItemSet(); + const SfxUnoAnyItem* pItem = rSet.GetItem(SID_VIEW_DATA, false); + if ( pItem ) + { + pImpl->m_pController->restoreViewData( pItem->GetValue() ); + rSet.ClearItem( SID_VIEW_DATA ); + } + break; + } + } +} + +bool SfxViewShell::ExecKey_Impl(const KeyEvent& aKey) +{ + bool setModuleConfig = false; // In case libreofficekit is active, we will re-set the module config class. + if (!pImpl->m_xAccExec) + { + pImpl->m_xAccExec = ::svt::AcceleratorExecute::createAcceleratorHelper(); + pImpl->m_xAccExec->init(::comphelper::getProcessComponentContext(), + rFrame.GetFrame().GetFrameInterface()); + setModuleConfig = true; + } + + if (comphelper::LibreOfficeKit::isActive()) + { + // Get the module name. + css::uno::Reference< css::uno::XComponentContext > xContext (::comphelper::getProcessComponentContext()); + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager(css::frame::ModuleManager::create(xContext)); + OUString sModule = xModuleManager->identify(rFrame.GetFrame().GetFrameInterface()); + + // Get the language name. + OUString viewLang = GetLOKLanguageTag().getBcp47(); + + // Merge them & have a key. + OUString key = sModule + viewLang; + + // Check it in configurations map. Create a configuration manager if there isn't one for the key. + std::unordered_map<OUString, css::uno::Reference<com::sun::star::ui::XAcceleratorConfiguration>>& acceleratorConfs = SfxApplication::Get()->GetAcceleratorConfs_Impl(); + if (acceleratorConfs.find(key) == acceleratorConfs.end()) + { + // Create a new configuration manager for the module. + + OUString actualLang = officecfg::Setup::L10N::ooLocale::get(); + + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Setup::L10N::ooLocale::set(viewLang, batch); + batch->commit(); + + // We have set the language. Time to create the config manager. + acceleratorConfs[key] = svt::AcceleratorExecute::lok_createNewAcceleratorConfiguration(::comphelper::getProcessComponentContext(), sModule); + + std::shared_ptr<comphelper::ConfigurationChanges> batch2(comphelper::ConfigurationChanges::create()); + officecfg::Setup::L10N::ooLocale::set(actualLang, batch2); + batch2->commit(); + } + + if (setModuleConfig) + pImpl->m_xAccExec->lok_setModuleConfig(acceleratorConfs[key]); + } + + return pImpl->m_xAccExec->execute(aKey.GetKeyCode()); +} + +void SfxViewShell::setLibreOfficeKitViewCallback(SfxLokCallbackInterface* pCallback) +{ + pImpl->m_pLibreOfficeKitViewCallback = pCallback; + + afterCallbackRegistered(); + + if (!pImpl->m_pLibreOfficeKitViewCallback) + return; + + // Ask other views to tell us about their cursors. + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell->GetDocId() == GetDocId()) + pViewShell->NotifyCursor(this); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +SfxLokCallbackInterface* SfxViewShell::getLibreOfficeKitViewCallback() const +{ + return pImpl->m_pLibreOfficeKitViewCallback; +} + +void SfxViewShell::dumpLibreOfficeKitViewState(rtl::OStringBuffer &rState) +{ + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->dumpState(rState); +} + +static bool ignoreLibreOfficeKitViewCallback(int nType, const SfxViewShell_Impl* pImpl) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return true; + + if (comphelper::LibreOfficeKit::isTiledPainting()) + { + switch (nType) + { + case LOK_CALLBACK_FORM_FIELD_BUTTON: + case LOK_CALLBACK_TEXT_SELECTION: + case LOK_CALLBACK_COMMENT: + break; + default: + // Reject e.g. invalidate during paint. + return true; + } + } + + if (pImpl->m_bTiledSearching) + { + switch (nType) + { + case LOK_CALLBACK_TEXT_SELECTION: + case LOK_CALLBACK_TEXT_VIEW_SELECTION: + case LOK_CALLBACK_TEXT_SELECTION_START: + case LOK_CALLBACK_TEXT_SELECTION_END: + case LOK_CALLBACK_GRAPHIC_SELECTION: + case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION: + return true; + } + } + + return false; +} + +void SfxViewShell::libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode) const +{ + if (ignoreLibreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_TILES, pImpl.get())) + return; + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewInvalidateTilesCallback(pRect, nPart, nMode); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewInvalidateTilesCallback no callback set!"); +} + +void SfxViewShell::libreOfficeKitViewCallbackWithViewId(int nType, const OString& pPayload, int nViewId) const +{ + if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get())) + return; + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewCallbackWithViewId(nType, pPayload, nViewId); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewCallbackWithViewId no callback set! Dropped payload of type " + << lokCallbackTypeToString(nType) << ": [" << pPayload << ']'); +} + +void SfxViewShell::libreOfficeKitViewCallback(int nType, const OString& pPayload) const +{ + if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get())) + return; + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewCallback(nType, pPayload); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewCallback no callback set! Dropped payload of type " + << lokCallbackTypeToString(nType) << ": [" << pPayload << ']'); +} + +void SfxViewShell::libreOfficeKitViewUpdatedCallback(int nType) const +{ + if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get())) + return; + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallback(nType); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewUpdatedCallback no callback set! Dropped payload of type " + << lokCallbackTypeToString(nType)); +} + +void SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) const +{ + if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get())) + return; + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallbackPerViewId(nType, nViewId, nSourceViewId); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId no callback set! Dropped payload of type " + << lokCallbackTypeToString(nType)); +} + +void SfxViewShell::libreOfficeKitViewAddPendingInvalidateTiles() +{ + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewAddPendingInvalidateTiles(); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewAddPendingInvalidateTiles no callback set!"); +} + +void SfxViewShell::afterCallbackRegistered() +{ + LOK_INFO("sfx.view", "SfxViewShell::afterCallbackRegistered invoked"); + if (GetLOKAccessibilityState()) + { + LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener(); + rDocFocusListener.notifyFocusedParagraphChanged(); + } +} + +void SfxViewShell::flushPendingLOKInvalidateTiles() +{ + // SfxViewShell itself does not delay any tile invalidations. +} + +std::optional<OString> SfxViewShell::getLOKPayload(int nType, int /*nViewId*/) const +{ + // SfxViewShell itself currently doesn't handle any updated-payload types. + SAL_WARN("sfx.view", "SfxViewShell::getLOKPayload unhandled type " << lokCallbackTypeToString(nType)); + abort(); +} + +vcl::Window* SfxViewShell::GetEditWindowForActiveOLEObj() const +{ + vcl::Window* pEditWin = nullptr; + SfxInPlaceClient* pIPClient = GetIPClient(); + if (pIPClient) + { + pEditWin = pIPClient->GetEditWin(); + } + return pEditWin; +} + +::Color SfxViewShell::GetColorConfigColor(svtools::ColorConfigEntry) const +{ + SAL_WARN("sfx.view", "SfxViewShell::GetColorConfigColor not overridden!"); + return {}; +} + +void SfxViewShell::SetLOKLanguageTag(const OUString& rBcp47LanguageTag) +{ + LanguageTag aTag(rBcp47LanguageTag, true); + + css::uno::Sequence<OUString> inst(officecfg::Setup::Office::InstalledLocales::get()->getElementNames()); + LanguageTag aFallbackTag = LanguageTag(getInstalledLocaleForSystemUILanguage(inst, /* bRequestInstallIfMissing */ false, rBcp47LanguageTag), true).makeFallback(); + + // If we want de-CH, and the de localisation is available, we don't want to use de-DE as then + // the magic in Translate::get() won't turn ess-zet into double s. Possibly other similar cases? + if (comphelper::LibreOfficeKit::isActive() && aTag.getLanguage() == aFallbackTag.getLanguage()) + maLOKLanguageTag = aTag; + else + maLOKLanguageTag = aFallbackTag; +} + +LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener() +{ + if (mpLOKDocumentFocusListener) + return *mpLOKDocumentFocusListener; + + mpLOKDocumentFocusListener = new LOKDocumentFocusListener(this); + return *mpLOKDocumentFocusListener; +} + +const LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener() const +{ + return const_cast<SfxViewShell*>(this)->GetLOKDocumentFocusListener(); +} + +void SfxViewShell::SetLOKAccessibilityState(bool bEnabled) +{ + if (bEnabled == mbLOKAccessibilityEnabled) + return; + mbLOKAccessibilityEnabled = bEnabled; + + LOKDocumentFocusListener& rDocumentFocusListener = GetLOKDocumentFocusListener(); + + if (!pWindow) + return; + + uno::Reference< accessibility::XAccessible > xAccessible = + pWindow->GetAccessible(); + + if (!xAccessible.is()) + return; + + if (mbLOKAccessibilityEnabled) + { + try + { + rDocumentFocusListener.attachRecursive(xAccessible); + } + catch (const uno::Exception&) + { + LOK_WARN("SetLOKAccessibilityState", "Exception caught processing LOKDocumentFocusListener::attachRecursive"); + } + } + else + { + try + { + rDocumentFocusListener.detachRecursive(xAccessible, /*bForce*/ true); + } + catch (const uno::Exception&) + { + LOK_WARN("SetLOKAccessibilityState", "Exception caught processing LOKDocumentFocusListener::detachRecursive"); + } + } +} + +void SfxViewShell::SetLOKLocale(const OUString& rBcp47LanguageTag) +{ + maLOKLocale = LanguageTag(rBcp47LanguageTag, true).makeFallback(); +} + +void SfxViewShell::NotifyCursor(SfxViewShell* /*pViewShell*/) const +{ +} + +void SfxViewShell::setTiledSearching(bool bTiledSearching) +{ + pImpl->m_bTiledSearching = bTiledSearching; +} + +int SfxViewShell::getPart() const +{ + return 0; +} + +int SfxViewShell::getEditMode() const +{ + return 0; +} + +ViewShellId SfxViewShell::GetViewShellId() const +{ + return pImpl->m_nViewShellId; +} + +void SfxViewShell::SetCurrentDocId(ViewShellDocId nId) +{ + mnCurrentDocId = nId; +} + +ViewShellDocId SfxViewShell::GetDocId() const +{ + assert(pImpl->m_nDocId >= ViewShellDocId(0) && "m_nDocId should have been initialized, but it is invalid."); + return pImpl->m_nDocId; +} + +void SfxViewShell::notifyInvalidation(tools::Rectangle const* pRect) const +{ + SfxLokHelper::notifyInvalidation(this, pRect); +} + +void SfxViewShell::NotifyOtherViews(int nType, const OString& rKey, const OString& rPayload) +{ + SfxLokHelper::notifyOtherViews(this, nType, rKey, rPayload); +} + +void SfxViewShell::NotifyOtherView(OutlinerViewShell* pOther, int nType, const OString& rKey, const OString& rPayload) +{ + auto pOtherShell = dynamic_cast<SfxViewShell*>(pOther); + if (!pOtherShell) + return; + + SfxLokHelper::notifyOtherView(this, pOtherShell, nType, rKey, rPayload); +} + +void SfxViewShell::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxViewShell")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(static_cast<sal_Int32>(GetViewShellId())).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +bool SfxViewShell::KeyInput( const KeyEvent &rKeyEvent ) + +/* [Description] + + This Method executes the KeyEvent 'rKeyEvent' of the Keys (Accelerator) + configured either direct or indirect (for example by the Application) + in the SfxViewShell. + + [Return value] + + bool TRUE + The Key (Accelerator) is configured and the + associated Handler was called + + FALSE + The Key (Accelerator) is not configured and + subsequently no Handler was called + + [Cross-reference] + + <SfxApplication::KeyInput(const KeyEvent&)> +*/ +{ + return ExecKey_Impl(rKeyEvent); +} + +bool SfxViewShell::GlobalKeyInput_Impl( const KeyEvent &rKeyEvent ) +{ + return ExecKey_Impl(rKeyEvent); +} + + +void SfxViewShell::ShowCursor( bool /*bOn*/ ) + +/* [Description] + + Subclasses must override this Method so that SFx can switch the + Cursor on and off, for example while a <SfxProgress> is running. +*/ + +{ +} + + +void SfxViewShell::ResetAllClients_Impl( SfxInPlaceClient const *pIP ) +{ + + std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl(); + if ( rClients.empty() ) + return; + + for (SfxInPlaceClient* pIPClient : rClients) + { + if( pIPClient != pIP ) + pIPClient->ResetObject(); + } +} + + +void SfxViewShell::DisconnectAllClients() +{ + std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl(); + if ( rClients.empty() ) + return; + + for ( size_t n = 0; n < rClients.size(); ) + // clients will remove themselves from the list + delete rClients.at( n ); +} + + +void SfxViewShell::QueryObjAreaPixel( tools::Rectangle& ) const +{ +} + + +void SfxViewShell::VisAreaChanged() +{ + std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl(); + if ( rClients.empty() ) + return; + + for (SfxInPlaceClient* pIPClient : rClients) + { + if ( pIPClient->IsObjectInPlaceActive() ) + // client is active, notify client that the VisArea might have changed + pIPClient->VisAreaChanged(); + } +} + + +void SfxViewShell::CheckIPClient_Impl( + SfxInPlaceClient const *const pIPClient, const tools::Rectangle& rVisArea) +{ + if ( GetObjectShell()->IsInClose() ) + return; + + bool bAlwaysActive = + ( ( pIPClient->GetObjectMiscStatus() & embed::EmbedMisc::EMBED_ACTIVATEIMMEDIATELY ) != 0 ); + bool bActiveWhenVisible = + ( pIPClient->GetObjectMiscStatus() & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE ) != 0; + + // this method is called when a client is created + if (pIPClient->IsObjectInPlaceActive()) + return; + + // object in client is currently not active + // check if the object wants to be activated always or when it becomes at least partially visible + // TODO/LATER: maybe we should use the scaled area instead of the ObjArea?! + if (bAlwaysActive || (bActiveWhenVisible && rVisArea.Overlaps(pIPClient->GetObjArea()))) + { + try + { + pIPClient->GetObject()->changeState( embed::EmbedStates::INPLACE_ACTIVE ); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx.view", "SfxViewShell::CheckIPClient_Impl"); + } + } +} + +SfxObjectShell* SfxViewShell::GetObjectShell() +{ + return rFrame.GetObjectShell(); +} + +Reference< XModel > SfxViewShell::GetCurrentDocument() const +{ + Reference< XModel > xDocument; + + const SfxObjectShell* pDocShell( const_cast< SfxViewShell* >( this )->GetObjectShell() ); + OSL_ENSURE( pDocShell, "SfxViewFrame::GetCurrentDocument: no DocShell!?" ); + if ( pDocShell ) + xDocument = pDocShell->GetModel(); + return xDocument; +} + + +void SfxViewShell::SetCurrentDocument() const +{ + uno::Reference< frame::XModel > xDocument( GetCurrentDocument() ); + if ( xDocument.is() ) + SfxObjectShell::SetCurrentComponent( xDocument ); +} + + +const Size& SfxViewShell::GetMargin() const +{ + return pImpl->aMargin; +} + + +void SfxViewShell::SetMargin( const Size& rSize ) +{ + // the default margin was verified using www.apple.com !! + Size aMargin = rSize; + if ( aMargin.Width() == -1 ) + aMargin.setWidth( DEFAULT_MARGIN_WIDTH ); + if ( aMargin.Height() == -1 ) + aMargin.setHeight( DEFAULT_MARGIN_HEIGHT ); + + if ( aMargin != pImpl->aMargin ) + { + pImpl->aMargin = aMargin; + MarginChanged(); + } +} + +void SfxViewShell::MarginChanged() +{ +} + +void SfxViewShell::JumpToMark( const OUString& rMark ) +{ + SfxStringItem aMarkItem( SID_JUMPTOMARK, rMark ); + GetViewFrame().GetDispatcher()->ExecuteList( + SID_JUMPTOMARK, + SfxCallMode::SYNCHRON|SfxCallMode::RECORD, + { &aMarkItem }); +} + +void SfxViewShell::SetController( SfxBaseController* pController ) +{ + pImpl->m_pController = pController; + + // there should be no old listener, but if there is one, it should be disconnected + if ( pImpl->xClipboardListener.is() ) + pImpl->xClipboardListener->DisconnectViewShell(); + + pImpl->xClipboardListener = new SfxClipboardChangeListener( this, GetClipboardNotifier() ); +} + +Reference < XController > SfxViewShell::GetController() const +{ + return pImpl->m_pController; +} + +SfxBaseController* SfxViewShell::GetBaseController_Impl() const +{ + return pImpl->m_pController.get(); +} + +void SfxViewShell::AddContextMenuInterceptor_Impl( const uno::Reference< ui::XContextMenuInterceptor >& xInterceptor ) +{ + std::unique_lock g(pImpl->aMutex); + pImpl->aInterceptorContainer.addInterface( g, xInterceptor ); +} + +void SfxViewShell::RemoveContextMenuInterceptor_Impl( const uno::Reference< ui::XContextMenuInterceptor >& xInterceptor ) +{ + std::unique_lock g(pImpl->aMutex); + pImpl->aInterceptorContainer.removeInterface( g, xInterceptor ); +} + +bool SfxViewShell::TryContextMenuInterception(const rtl::Reference<VCLXPopupMenu>& rIn, + const OUString& rMenuIdentifier, + rtl::Reference<VCLXPopupMenu>& rOut, + ui::ContextMenuExecuteEvent aEvent) +{ + rOut.clear(); + bool bModified = false; + + // create container from menu + aEvent.ActionTriggerContainer = ::framework::ActionTriggerHelper::CreateActionTriggerContainerFromMenu( + rIn, &rMenuIdentifier); + + // get selection from controller + aEvent.Selection.set( GetController(), uno::UNO_QUERY ); + + // call interceptors + std::unique_lock g(pImpl->aMutex); + std::vector<uno::Reference< ui::XContextMenuInterceptor>> aInterceptors = + pImpl->aInterceptorContainer.getElements(g); + g.unlock(); + for (const auto & rListener : aInterceptors ) + { + try + { + ui::ContextMenuInterceptorAction eAction; + { + SolarMutexReleaser rel; + eAction = rListener->notifyContextMenuExecute( aEvent ); + } + switch ( eAction ) + { + case ui::ContextMenuInterceptorAction_CANCELLED : + // interceptor does not want execution + return false; + case ui::ContextMenuInterceptorAction_EXECUTE_MODIFIED : + // interceptor wants his modified menu to be executed + bModified = true; + break; + case ui::ContextMenuInterceptorAction_CONTINUE_MODIFIED : + // interceptor has modified menu, but allows for calling other interceptors + bModified = true; + continue; + case ui::ContextMenuInterceptorAction_IGNORED : + // interceptor is indifferent + continue; + default: + OSL_FAIL("Wrong return value of ContextMenuInterceptor!"); + continue; + } + } + catch (...) + { + g.lock(); + pImpl->aInterceptorContainer.removeInterface(g, rListener); + g.unlock(); + } + + break; + } + + if (bModified) + { + // container was modified, create a new menu out of it + rOut = new VCLXPopupMenu(); + ::framework::ActionTriggerHelper::CreateMenuFromActionTriggerContainer(rOut, aEvent.ActionTriggerContainer); + } + + return true; +} + +bool SfxViewShell::TryContextMenuInterception(const rtl::Reference<VCLXPopupMenu>& rPopupMenu, + const OUString& rMenuIdentifier, css::ui::ContextMenuExecuteEvent aEvent) +{ + bool bModified = false; + + // create container from menu + aEvent.ActionTriggerContainer = ::framework::ActionTriggerHelper::CreateActionTriggerContainerFromMenu( + rPopupMenu, &rMenuIdentifier); + + // get selection from controller + aEvent.Selection = css::uno::Reference< css::view::XSelectionSupplier >( GetController(), css::uno::UNO_QUERY ); + + // call interceptors + std::unique_lock g(pImpl->aMutex); + std::vector<uno::Reference< ui::XContextMenuInterceptor>> aInterceptors = + pImpl->aInterceptorContainer.getElements(g); + g.unlock(); + for (const auto & rListener : aInterceptors ) + { + try + { + css::ui::ContextMenuInterceptorAction eAction; + { + SolarMutexReleaser rel; + eAction = rListener->notifyContextMenuExecute( aEvent ); + } + switch ( eAction ) + { + case css::ui::ContextMenuInterceptorAction_CANCELLED: + // interceptor does not want execution + return false; + case css::ui::ContextMenuInterceptorAction_EXECUTE_MODIFIED: + // interceptor wants his modified menu to be executed + bModified = true; + break; + case css::ui::ContextMenuInterceptorAction_CONTINUE_MODIFIED: + // interceptor has modified menu, but allows for calling other interceptors + bModified = true; + continue; + case css::ui::ContextMenuInterceptorAction_IGNORED: + // interceptor is indifferent + continue; + default: + SAL_WARN( "sfx.view", "Wrong return value of ContextMenuInterceptor!" ); + continue; + } + } + catch (...) + { + g.lock(); + pImpl->aInterceptorContainer.removeInterface(g, rListener); + g.unlock(); + } + + break; + } + + if ( bModified ) + { + rPopupMenu->clear(); + ::framework::ActionTriggerHelper::CreateMenuFromActionTriggerContainer(rPopupMenu, aEvent.ActionTriggerContainer); + } + + return true; +} + +bool SfxViewShell::HandleNotifyEvent_Impl( NotifyEvent const & rEvent ) +{ + if (pImpl->m_pController.is()) + return pImpl->m_pController->HandleEvent_Impl( rEvent ); + return false; +} + +bool SfxViewShell::HasKeyListeners_Impl() const +{ + return (pImpl->m_pController.is()) + && pImpl->m_pController->HasKeyListeners_Impl(); +} + +bool SfxViewShell::HasMouseClickListeners_Impl() const +{ + return (pImpl->m_pController.is()) + && pImpl->m_pController->HasMouseClickListeners_Impl(); +} + +bool SfxViewShell::Escape() +{ + return GetViewFrame().GetBindings().Execute(SID_TERMINATE_INPLACEACTIVATION); +} + +Reference< view::XRenderable > SfxViewShell::GetRenderable() +{ + Reference< view::XRenderable >xRender; + SfxObjectShell* pObj = GetObjectShell(); + if( pObj ) + { + Reference< frame::XModel > xModel( pObj->GetModel() ); + if( xModel.is() ) + xRender.set( xModel, UNO_QUERY ); + } + return xRender; +} + +void SfxViewShell::notifyWindow(vcl::LOKWindowId nDialogId, const OUString& rAction, const std::vector<vcl::LOKPayloadItem>& rPayload) const +{ + SfxLokHelper::notifyWindow(this, nDialogId, rAction, rPayload); +} + +uno::Reference< datatransfer::clipboard::XClipboardNotifier > SfxViewShell::GetClipboardNotifier() const +{ + uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClipboardNotifier; + xClipboardNotifier.set(GetViewFrame().GetWindow().GetClipboard(), uno::UNO_QUERY); + return xClipboardNotifier; +} + +void SfxViewShell::AddRemoveClipboardListener( const uno::Reference < datatransfer::clipboard::XClipboardListener >& rClp, bool bAdd ) +{ + try + { + uno::Reference< datatransfer::clipboard::XClipboard > xClipboard(GetViewFrame().GetWindow().GetClipboard()); + if( xClipboard.is() ) + { + uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClpbrdNtfr( xClipboard, uno::UNO_QUERY ); + if( xClpbrdNtfr.is() ) + { + if( bAdd ) + xClpbrdNtfr->addClipboardListener( rClp ); + else + xClpbrdNtfr->removeClipboardListener( rClp ); + } + } + } + catch (const uno::Exception&) + { + } +} + +weld::Window* SfxViewShell::GetFrameWeld() const +{ + return pWindow ? pWindow->GetFrameWeld() : nullptr; +} + +void SfxViewShell::setBlockedCommandList(const char* blockedCommandList) +{ + if(!mvLOKBlockedCommandList.empty()) + return; + + OUString BlockedListString(blockedCommandList, strlen(blockedCommandList), RTL_TEXTENCODING_UTF8); + OUString command = BlockedListString.getToken(0, ' '); + for (size_t i = 1; !command.isEmpty(); i++) + { + mvLOKBlockedCommandList.emplace(command); + command = BlockedListString.getToken(i, ' '); + } +} + +bool SfxViewShell::isBlockedCommand(OUString command) +{ + return mvLOKBlockedCommandList.find(command) != mvLOKBlockedCommandList.end(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |