diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sfx2/source/dialog | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream/4%7.4.7.tar.xz libreoffice-upstream/4%7.4.7.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
40 files changed, 20549 insertions, 0 deletions
diff --git a/sfx2/source/dialog/StyleList.cxx b/sfx2/source/dialog/StyleList.cxx new file mode 100644 index 000000000..ded3822b1 --- /dev/null +++ b/sfx2/source/dialog/StyleList.cxx @@ -0,0 +1,1778 @@ +/* -*- 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 <unordered_map> + +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <vcl/commandevent.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weldutils.hxx> +#include <vcl/window.hxx> +#include <svl/intitem.hxx> +#include <svl/style.hxx> +#include <comphelper/processfactory.hxx> +#include <officecfg/Office/Common.hxx> + +#include <osl/diagnose.h> +#include <sfx2/dispatch.hxx> +#include <sfx2/bindings.hxx> +#include <templdgi.hxx> +#include <tplcitem.hxx> +#include <sfx2/styfitem.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/newstyle.hxx> +#include <sfx2/tplpitem.hxx> +#include <sfx2/sfxresid.hxx> + +#include <sfx2/sfxsids.hrc> +#include <sfx2/strings.hrc> +#include <sfx2/docfac.hxx> +#include <sfx2/module.hxx> +#include <helpids.h> +#include <sfx2/viewfrm.hxx> + +#include <comphelper/string.hxx> + +#include <sfx2/StyleManager.hxx> +#include <sfx2/StylePreviewRenderer.hxx> + +#include <StyleList.hxx> + +using namespace css; +using namespace css::beans; +using namespace css::frame; +using namespace css::uno; + +// Constructor + +StyleList::StyleList(weld::Builder* pBuilder, SfxBindings* pBindings, + SfxCommonTemplateDialog_Impl* Parent, weld::Container* pC, + OString treeviewname, OString flatviewname) + : m_bHierarchical(false) + , m_bAllowReParentDrop(false) + , m_bNewByExampleDisabled(false) + , m_bDontUpdate(false) + , m_bTreeDrag(true) + , m_bCanEdit(false) + , m_bCanHide(true) + , m_bCanShow(false) + , m_bCanNew(true) + , m_bUpdateFamily(false) + , m_bCanDel(false) + , m_bBindingUpdate(true) + , m_pStyleSheetPool(nullptr) + , m_nActFilter(0) + , m_xFmtLb(pBuilder->weld_tree_view(flatviewname)) + , m_xTreeBox(pBuilder->weld_tree_view(treeviewname)) + , m_pCurObjShell(nullptr) + , m_nActFamily(0xffff) + , m_nAppFilter(SfxStyleSearchBits::Auto) + , m_pParentDialog(Parent) + , m_pBindings(pBindings) + , m_Module(nullptr) + , m_nModifier(0) + , m_pContainer(pC) +{ + m_xFmtLb->set_help_id(HID_TEMPLATE_FMT); +} + +// Destructor + +StyleList::~StyleList() {} + +// Called in the destructor of Dialog +// Cleans up the StyleList individual components while closing the application +IMPL_LINK_NOARG(StyleList, Cleanup, void*, void) +{ + if (m_pStyleSheetPool) + EndListening(*m_pStyleSheetPool); + m_pStyleSheetPool = nullptr; + m_xTreeView1DropTargetHelper.reset(); + m_xTreeView2DropTargetHelper.reset(); + m_xTreeBox.reset(); + m_xFmtLb.reset(); + pIdle.reset(); +} + +void StyleList::CreateContextMenu() +{ + if (m_bBindingUpdate) + { + m_pBindings->Invalidate(SID_STYLE_NEW, true); + m_pBindings->Update(SID_STYLE_NEW); + m_bBindingUpdate = false; + } + mxMenu.reset(); + mxMenuBuilder = Application::CreateBuilder(nullptr, "sfx/ui/stylecontextmenu.ui"); + mxMenu = mxMenuBuilder->weld_menu("menu"); + mxMenu->set_sensitive("edit", m_bCanEdit); + mxMenu->set_sensitive("delete", m_bCanDel); + mxMenu->set_sensitive("new", m_bCanNew); + mxMenu->set_sensitive("hide", m_bCanHide); + mxMenu->set_sensitive("show", m_bCanShow); + + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + if (pItem && pItem->GetFamily() == SfxStyleFamily::Table) //tdf#101648, no ui for this yet + { + mxMenu->set_sensitive("edit", false); + mxMenu->set_sensitive("new", false); + } + if (pItem && pItem->GetFamily() == SfxStyleFamily::Pseudo) + { + const OUString aTemplName(GetSelectedEntry()); + if (aTemplName == "No List") + { + mxMenu->set_sensitive("edit", false); + mxMenu->set_sensitive("new", false); + mxMenu->set_sensitive("hide", false); + } + } +} + +IMPL_LINK_NOARG(StyleList, ReadResource, void*, size_t) +{ + // Read global user resource + for (auto& i : m_pFamilyState) + i.reset(); + + SfxViewFrame* pViewFrame = m_pBindings->GetDispatcher_Impl()->GetFrame(); + m_pCurObjShell = pViewFrame->GetObjectShell(); + m_Module = m_pCurObjShell ? m_pCurObjShell->GetModule() : nullptr; + if (m_Module) + m_xStyleFamilies = m_Module->CreateStyleFamilies(); + if (!m_xStyleFamilies) + m_xStyleFamilies.emplace(); + + m_nActFilter = 0xffff; + + if (m_pCurObjShell) + { + m_nActFilter = static_cast<sal_uInt16>(m_aLoadFactoryStyleFilter.Call(m_pCurObjShell)); + if (0xffff == m_nActFilter) + { + m_nActFilter = m_pCurObjShell->GetAutoStyleFilterIndex(); + } + } + size_t nCount = m_xStyleFamilies->size(); + m_pBindings->ENTERREGISTRATIONS(); + + size_t i; + for (i = 0; i < nCount; ++i) + { + sal_uInt16 nSlot = 0; + switch (m_xStyleFamilies->at(i).GetFamily()) + { + case SfxStyleFamily::Char: + nSlot = SID_STYLE_FAMILY1; + break; + case SfxStyleFamily::Para: + nSlot = SID_STYLE_FAMILY2; + break; + case SfxStyleFamily::Frame: + nSlot = SID_STYLE_FAMILY3; + break; + case SfxStyleFamily::Page: + nSlot = SID_STYLE_FAMILY4; + break; + case SfxStyleFamily::Pseudo: + nSlot = SID_STYLE_FAMILY5; + break; + case SfxStyleFamily::Table: + nSlot = SID_STYLE_FAMILY6; + break; + default: + OSL_FAIL("unknown StyleFamily"); + break; + } + pBoundItems[i].reset(new SfxTemplateControllerItem(nSlot, *m_pParentDialog, *m_pBindings)); + } + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_WATERCAN, *m_pParentDialog, *m_pBindings)); + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_NEW_BY_EXAMPLE, *m_pParentDialog, *m_pBindings)); + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_UPDATE_BY_EXAMPLE, *m_pParentDialog, *m_pBindings)); + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_NEW, *m_pParentDialog, *m_pBindings)); + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_DRAGHIERARCHIE, *m_pParentDialog, *m_pBindings)); + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_EDIT, *m_pParentDialog, *m_pBindings)); + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_DELETE, *m_pParentDialog, *m_pBindings)); + pBoundItems[i++].reset( + new SfxTemplateControllerItem(SID_STYLE_FAMILY, *m_pParentDialog, *m_pBindings)); + m_pBindings->LEAVEREGISTRATIONS(); + + for (; i < COUNT_BOUND_FUNC; ++i) + pBoundItems[i] = nullptr; + + StartListening(*m_pBindings); + + for (i = SID_STYLE_FAMILY1; i <= SID_STYLE_FAMILY4; i++) + m_pBindings->Update(i); + + return nCount; +} + +void StyleList::EnableNewByExample(bool newByExampleDisabled) +{ + m_bNewByExampleDisabled = newByExampleDisabled; +} + +class TreeViewDropTarget final : public DropTargetHelper +{ +private: + StyleList& m_rParent; + +public: + TreeViewDropTarget(StyleList& rStyleList, weld::TreeView& rTreeView) + : DropTargetHelper(rTreeView.get_drop_target()) + , m_rParent(rStyleList) + { + } + + virtual sal_Int8 AcceptDrop(const AcceptDropEvent& rEvt) override + { + return m_rParent.AcceptDrop(rEvt, *this); + } + + virtual sal_Int8 ExecuteDrop(const ExecuteDropEvent& rEvt) override + { + return m_rParent.ExecuteDrop(rEvt); + } +}; + +void StyleList::FilterSelect(sal_uInt16 nActFilter, bool bsetFilter) +{ + m_nActFilter = nActFilter; + if (bsetFilter) + { + SfxObjectShell* const pDocShell = m_aSaveSelection.Call(*this); + SfxStyleSheetBasePool* pOldStyleSheetPool = m_pStyleSheetPool; + m_pStyleSheetPool = pDocShell ? pDocShell->GetStyleSheetPool() : nullptr; + if (pOldStyleSheetPool != m_pStyleSheetPool) + { + if (pOldStyleSheetPool) + EndListening(*pOldStyleSheetPool); + if (m_pStyleSheetPool) + StartListening(*m_pStyleSheetPool); + } + } + UpdateStyles(StyleFlags::UpdateFamilyList); +} + +IMPL_LINK(StyleList, SetFamily, sal_uInt16, nId, void) +{ + if (m_nActFamily != 0xFFFF) + m_pParentDialog->CheckItem(OString::number(m_nActFamily), false); + m_nActFamily = nId; + if (nId != 0xFFFF) + { + m_bUpdateFamily = true; + } +} + +void StyleList::InvalidateBindings() +{ + m_pBindings->Invalidate(SID_STYLE_NEW_BY_EXAMPLE, true); + m_pBindings->Update(SID_STYLE_NEW_BY_EXAMPLE); + m_pBindings->Invalidate(SID_STYLE_UPDATE_BY_EXAMPLE, true); + m_pBindings->Update(SID_STYLE_UPDATE_BY_EXAMPLE); + m_pBindings->Invalidate(SID_STYLE_WATERCAN, true); + m_pBindings->Update(SID_STYLE_WATERCAN); + m_pBindings->Invalidate(SID_STYLE_NEW, true); + m_pBindings->Update(SID_STYLE_NEW); + m_pBindings->Invalidate(SID_STYLE_DRAGHIERARCHIE, true); + m_pBindings->Update(SID_STYLE_DRAGHIERARCHIE); +} + +void StyleList::Initialize() +{ + m_pBindings->Invalidate(SID_STYLE_FAMILY); + m_pBindings->Update(SID_STYLE_FAMILY); + + m_xFmtLb->connect_row_activated(LINK(this, StyleList, TreeListApplyHdl)); + m_xFmtLb->connect_mouse_press(LINK(this, StyleList, MousePressHdl)); + m_xFmtLb->connect_query_tooltip(LINK(this, StyleList, QueryTooltipHdl)); + m_xFmtLb->connect_changed(LINK(this, StyleList, FmtSelectHdl)); + m_xFmtLb->connect_popup_menu(LINK(this, StyleList, PopupFlatMenuHdl)); + m_xFmtLb->connect_key_press(LINK(this, StyleList, KeyInputHdl)); + m_xFmtLb->set_selection_mode(SelectionMode::Multiple); + m_xTreeBox->connect_changed(LINK(this, StyleList, FmtSelectHdl)); + m_xTreeBox->connect_row_activated(LINK(this, StyleList, TreeListApplyHdl)); + m_xTreeBox->connect_mouse_press(LINK(this, StyleList, MousePressHdl)); + m_xTreeBox->connect_query_tooltip(LINK(this, StyleList, QueryTooltipHdl)); + m_xTreeBox->connect_popup_menu(LINK(this, StyleList, PopupTreeMenuHdl)); + m_xTreeBox->connect_key_press(LINK(this, StyleList, KeyInputHdl)); + m_xTreeBox->connect_drag_begin(LINK(this, StyleList, DragBeginHdl)); + m_xTreeView1DropTargetHelper.reset(new TreeViewDropTarget(*this, *m_xFmtLb)); + m_xTreeView2DropTargetHelper.reset(new TreeViewDropTarget(*this, *m_xTreeBox)); + + m_pParentDialog->connect_stylelist_read_resource(LINK(this, StyleList, ReadResource)); + m_pParentDialog->connect_stylelist_clear(LINK(this, StyleList, Clear)); + m_pParentDialog->connect_stylelist_cleanup(LINK(this, StyleList, Cleanup)); + m_pParentDialog->connect_stylelist_execute_drop(LINK(this, StyleList, ExecuteDrop)); + m_pParentDialog->connect_stylelist_execute_new_menu( + LINK(this, StyleList, NewMenuExecuteAction)); + m_pParentDialog->connect_stylelist_for_watercan(LINK(this, StyleList, IsSafeForWaterCan)); + m_pParentDialog->connect_stylelist_has_selected_style(LINK(this, StyleList, HasSelectedStyle)); + m_pParentDialog->connect_stylelist_update_style_dependents( + LINK(this, StyleList, UpdateStyleDependents)); + m_pParentDialog->connect_stylelist_enable_tree_drag(LINK(this, StyleList, EnableTreeDrag)); + m_pParentDialog->connect_stylelist_enable_delete(LINK(this, StyleList, EnableDelete)); + m_pParentDialog->connect_stylelist_set_water_can_state(LINK(this, StyleList, SetWaterCanState)); + m_pParentDialog->connect_set_family(LINK(this, StyleList, SetFamily)); + + int nTreeHeight = m_xFmtLb->get_height_rows(8); + m_xFmtLb->set_size_request(-1, nTreeHeight); + m_xTreeBox->set_size_request(-1, nTreeHeight); + + m_xFmtLb->connect_custom_get_size(LINK(this, StyleList, CustomGetSizeHdl)); + m_xFmtLb->connect_custom_render(LINK(this, StyleList, CustomRenderHdl)); + m_xTreeBox->connect_custom_get_size(LINK(this, StyleList, CustomGetSizeHdl)); + m_xTreeBox->connect_custom_render(LINK(this, StyleList, CustomRenderHdl)); + bool bCustomPreview = officecfg::Office::Common::StylesAndFormatting::Preview::get(); + m_xFmtLb->set_column_custom_renderer(0, bCustomPreview); + m_xTreeBox->set_column_custom_renderer(0, bCustomPreview); + + m_xFmtLb->set_visible(!m_bHierarchical); + m_xTreeBox->set_visible(m_bHierarchical); + Update(); +} + +void StyleList::UpdateFamily() +{ + m_bUpdateFamily = false; + + SfxDispatcher* pDispat = m_pBindings->GetDispatcher_Impl(); + SfxViewFrame* pViewFrame = pDispat->GetFrame(); + SfxObjectShell* pDocShell = pViewFrame->GetObjectShell(); + + SfxStyleSheetBasePool* pOldStyleSheetPool = m_pStyleSheetPool; + m_pStyleSheetPool = pDocShell ? pDocShell->GetStyleSheetPool() : nullptr; + if (pOldStyleSheetPool != m_pStyleSheetPool) + { + if (pOldStyleSheetPool) + EndListening(*pOldStyleSheetPool); + if (m_pStyleSheetPool) + StartListening(*m_pStyleSheetPool); + } + + m_bTreeDrag = true; + m_bCanNew = m_xTreeBox->get_visible() || m_xFmtLb->count_selected_rows() <= 1; + m_pParentDialog->EnableNew(m_bCanNew, this); + m_bTreeDrag = true; + if (m_pStyleSheetPool) + { + if (!m_xTreeBox->get_visible()) + UpdateStyles(StyleFlags::UpdateFamily | StyleFlags::UpdateFamilyList); + else + { + UpdateStyles(StyleFlags::UpdateFamily); + FillTreeBox(GetActualFamily()); + } + } + + InvalidateBindings(); +} + +bool StyleList::EnableExecute() +{ + return m_xTreeBox->get_visible() || m_xFmtLb->count_selected_rows() <= 1; +} + +void StyleList::connect_LoadFactoryStyleFilter(const Link<SfxObjectShell const*, sal_Int32>& rLink) +{ + m_aLoadFactoryStyleFilter = rLink; +} + +void StyleList::connect_SaveSelection(const Link<StyleList&, SfxObjectShell*> rLink) +{ + m_aSaveSelection = rLink; +} + +/** Drop is enabled as long as it is allowed to create a new style by example, i.e. to + create a style out of the current selection. +*/ +sal_Int8 StyleList::AcceptDrop(const AcceptDropEvent& rEvt, const DropTargetHelper& rHelper) +{ + if (rHelper.IsDropFormatSupported(SotClipboardFormatId::OBJECTDESCRIPTOR)) + { + // special case: page styles are allowed to create new styles by example + // but not allowed to be created by drag and drop + if (GetActualFamily() == SfxStyleFamily::Page || m_bNewByExampleDisabled) + return DND_ACTION_NONE; + else + return DND_ACTION_COPY; + } + // to enable the autoscroll when we're close to the edges + weld::TreeView* pTreeView = m_xTreeBox->get_visible() ? m_xTreeBox.get() : m_xFmtLb.get(); + pTreeView->get_dest_row_at_pos(rEvt.maPosPixel, nullptr, true); + return DND_ACTION_MOVE; +} + +// handles drop of content in treeview when creating a new style +IMPL_LINK(StyleList, ExecuteDrop, const ExecuteDropEvent&, rEvt, sal_Int8) +{ + SfxObjectShell* pDocShell = m_pCurObjShell; + if (pDocShell) + { + TransferableDataHelper aHelper(rEvt.maDropEvent.Transferable); + sal_uInt32 nFormatCount = aHelper.GetFormatCount(); + + sal_Int8 nRet = DND_ACTION_NONE; + + bool bFormatFound = false; + + for (sal_uInt32 i = 0; i < nFormatCount; ++i) + { + SotClipboardFormatId nId = aHelper.GetFormat(i); + TransferableObjectDescriptor aDesc; + + if (aHelper.GetTransferableObjectDescriptor(nId, aDesc)) + { + if (aDesc.maClassName == pDocShell->GetFactory().GetClassId()) + { + Application::PostUserEvent( + LINK(m_pParentDialog, SfxCommonTemplateDialog_Impl, OnAsyncExecuteDrop), + this); + + bFormatFound = true; + nRet = rEvt.mnAction; + break; + } + } + } + + if (bFormatFound) + return nRet; + } + + if (!m_xTreeBox->get_visible()) + return DND_ACTION_NONE; + + if (!m_bAllowReParentDrop) + return DND_ACTION_NONE; + + // otherwise if we're dragging with the treeview to set a new parent of the dragged style + weld::TreeView* pSource = m_xTreeBox->get_drag_source(); + // only dragging within the same widget allowed + if (!pSource || pSource != m_xTreeBox.get()) + return DND_ACTION_NONE; + + std::unique_ptr<weld::TreeIter> xSource(m_xTreeBox->make_iterator()); + if (!m_xTreeBox->get_selected(xSource.get())) + return DND_ACTION_NONE; + + std::unique_ptr<weld::TreeIter> xTarget(m_xTreeBox->make_iterator()); + if (!m_xTreeBox->get_dest_row_at_pos(rEvt.maPosPixel, xTarget.get(), true)) + { + // if nothing under the mouse, use the last row + int nChildren = m_xTreeBox->n_children(); + if (!nChildren) + return DND_ACTION_NONE; + if (!m_xTreeBox->get_iter_first(*xTarget) + || !m_xTreeBox->iter_nth_sibling(*xTarget, nChildren - 1)) + return DND_ACTION_NONE; + while (m_xTreeBox->get_row_expanded(*xTarget)) + { + nChildren = m_xTreeBox->iter_n_children(*xTarget); + if (!m_xTreeBox->iter_children(*xTarget) + || !m_xTreeBox->iter_nth_sibling(*xTarget, nChildren - 1)) + return DND_ACTION_NONE; + } + } + OUString aTargetStyle = m_xTreeBox->get_text(*xTarget); + DropHdl(m_xTreeBox->get_text(*xSource), aTargetStyle); + m_xTreeBox->unset_drag_dest_row(); + FillTreeBox(GetActualFamily()); + m_pParentDialog->SelectStyle(aTargetStyle, false, *this); + return DND_ACTION_NONE; +} + +IMPL_LINK_NOARG(StyleList, NewMenuExecuteAction, void*, void) +{ + if (!m_pStyleSheetPool || m_nActFamily == 0xffff) + return; + + const SfxStyleFamily eFam = GetFamilyItem()->GetFamily(); + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + SfxStyleSearchBits nFilter(SfxStyleSearchBits::Auto); + if (pItem && m_nActFilter != 0xffff) + nFilter = pItem->GetFilterList()[m_nActFilter].nFlags; + if (nFilter == SfxStyleSearchBits::Auto) // automatic + nFilter = m_nAppFilter; + + // why? : FloatingWindow must not be parent of a modal dialog + SfxNewStyleDlg aDlg(m_pContainer, *m_pStyleSheetPool, eFam); + auto nResult = aDlg.run(); + if (nResult == RET_OK) + { + const OUString aTemplName(aDlg.GetName()); + m_pParentDialog->Execute_Impl(SID_STYLE_NEW_BY_EXAMPLE, aTemplName, "", + static_cast<sal_uInt16>(GetFamilyItem()->GetFamily()), *this, + nFilter); + UpdateFamily(); + m_aUpdateFamily.Call(*this); + } +} + +void StyleList::DropHdl(const OUString& rStyle, const OUString& rParent) +{ + m_bDontUpdate = true; + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + const SfxStyleFamily eFam = pItem->GetFamily(); + m_pStyleSheetPool->SetParent(eFam, rStyle, rParent); + m_bDontUpdate = false; +} + +void StyleList::PrepareMenu(const Point& rPos) +{ + weld::TreeView* pTreeView = m_xTreeBox->get_visible() ? m_xTreeBox.get() : m_xFmtLb.get(); + std::unique_ptr<weld::TreeIter> xIter(pTreeView->make_iterator()); + if (pTreeView->get_dest_row_at_pos(rPos, xIter.get(), false) && !pTreeView->is_selected(*xIter)) + { + pTreeView->unselect_all(); + pTreeView->set_cursor(*xIter); + pTreeView->select(*xIter); + } + FmtSelectHdl(*pTreeView); +} + +/** Internal structure for the establishment of the hierarchical view */ +namespace +{ +class StyleTree_Impl; +} + +typedef std::vector<std::unique_ptr<StyleTree_Impl>> StyleTreeArr_Impl; + +namespace +{ +class StyleTree_Impl +{ +private: + OUString aName; + OUString aParent; + StyleTreeArr_Impl pChildren; + +public: + bool HasParent() const { return !aParent.isEmpty(); } + + StyleTree_Impl(const OUString& rName, const OUString& rParent) + : aName(rName) + , aParent(rParent) + , pChildren(0) + { + } + + const OUString& getName() const { return aName; } + const OUString& getParent() const { return aParent; } + StyleTreeArr_Impl& getChildren() { return pChildren; } +}; +} + +static void MakeTree_Impl(StyleTreeArr_Impl& rArr, const OUString& aUIName) +{ + const comphelper::string::NaturalStringSorter aSorter( + ::comphelper::getProcessComponentContext(), + Application::GetSettings().GetLanguageTag().getLocale()); + + std::unordered_map<OUString, StyleTree_Impl*> styleFinder; + styleFinder.reserve(rArr.size()); + for (const auto& pEntry : rArr) + { + styleFinder.emplace(pEntry->getName(), pEntry.get()); + } + + // Arrange all under their Parents + for (auto& pEntry : rArr) + { + if (!pEntry->HasParent()) + continue; + auto it = styleFinder.find(pEntry->getParent()); + if (it != styleFinder.end()) + { + StyleTree_Impl* pCmp = it->second; + // Insert child entries sorted + auto iPos = std::lower_bound( + pCmp->getChildren().begin(), pCmp->getChildren().end(), pEntry, + [&aSorter](std::unique_ptr<StyleTree_Impl> const& pEntry1, + std::unique_ptr<StyleTree_Impl> const& pEntry2) { + return aSorter.compare(pEntry1->getName(), pEntry2->getName()) < 0; + }); + pCmp->getChildren().insert(iPos, std::move(pEntry)); + } + } + + // Only keep tree roots in rArr, child elements can be accessed through the hierarchy + rArr.erase( + std::remove_if(rArr.begin(), rArr.end(), + [](std::unique_ptr<StyleTree_Impl> const& pEntry) { return !pEntry; }), + rArr.end()); + + // tdf#91106 sort top level styles + std::sort(rArr.begin(), rArr.end()); + std::sort(rArr.begin(), rArr.end(), + [&aSorter, &aUIName](std::unique_ptr<StyleTree_Impl> const& pEntry1, + std::unique_ptr<StyleTree_Impl> const& pEntry2) { + if (pEntry2->getName() == aUIName) + return false; + if (pEntry1->getName() == aUIName) + return true; // default always first + return aSorter.compare(pEntry1->getName(), pEntry2->getName()) < 0; + }); +} + +static bool IsExpanded_Impl(const std::vector<OUString>& rEntries, std::u16string_view rStr) +{ + for (const auto& rEntry : rEntries) + { + if (rEntry == rStr) + return true; + } + return false; +} + +static void FillBox_Impl(weld::TreeView& rBox, StyleTree_Impl* pEntry, + const std::vector<OUString>& rEntries, SfxStyleFamily eStyleFamily, + const weld::TreeIter* pParent) +{ + std::unique_ptr<weld::TreeIter> xResult = rBox.make_iterator(); + const OUString& rName = pEntry->getName(); + rBox.insert(pParent, -1, &rName, &rName, nullptr, nullptr, false, xResult.get()); + + for (size_t i = 0; i < pEntry->getChildren().size(); ++i) + FillBox_Impl(rBox, pEntry->getChildren()[i].get(), rEntries, eStyleFamily, xResult.get()); +} + +namespace SfxTemplate +{ +// converts from SFX_STYLE_FAMILY Ids to 1-6 +static sal_uInt16 SfxFamilyIdToNId(SfxStyleFamily nFamily) +{ + switch (nFamily) + { + case SfxStyleFamily::Char: + return 1; + case SfxStyleFamily::Para: + return 2; + case SfxStyleFamily::Frame: + return 3; + case SfxStyleFamily::Page: + return 4; + case SfxStyleFamily::Pseudo: + return 5; + case SfxStyleFamily::Table: + return 6; + default: + return 0xffff; + } +} +// converts from 1-6 to SFX_STYLE_FAMILY Ids +static SfxStyleFamily NIdToSfxFamilyId(sal_uInt16 nId) +{ + switch (nId) + { + case 1: + return SfxStyleFamily::Char; + case 2: + return SfxStyleFamily::Para; + case 3: + return SfxStyleFamily::Frame; + case 4: + return SfxStyleFamily::Page; + case 5: + return SfxStyleFamily::Pseudo; + case 6: + return SfxStyleFamily::Table; + default: + return SfxStyleFamily::All; + } +} +} + +sal_uInt16 StyleList::StyleNrToInfoOffset(sal_uInt16 nId) +{ + const SfxStyleFamilyItem& rItem = m_xStyleFamilies->at(nId); + return SfxTemplate::SfxFamilyIdToNId(rItem.GetFamily()) - 1; +} + +// Helper function: Access to the current family item +const SfxStyleFamilyItem* StyleList::GetFamilyItem() const +{ + const size_t nCount = m_xStyleFamilies->size(); + for (size_t i = 0; i < nCount; ++i) + { + const SfxStyleFamilyItem& rItem = m_xStyleFamilies->at(i); + sal_uInt16 nId = SfxTemplate::SfxFamilyIdToNId(rItem.GetFamily()); + if (nId == m_nActFamily) + return &rItem; + } + return nullptr; +} + +void StyleList::GetSelectedStyle() const +{ + const OUString aTemplName(GetSelectedEntry()); + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + m_pStyleSheetPool->Find(aTemplName, pItem->GetFamily()); +} + +// Used to get the current selected entry in visible treeview +OUString StyleList::GetSelectedEntry() const +{ + OUString aRet; + if (m_xTreeBox->get_visible()) + aRet = m_xTreeBox->get_selected_text(); + else + aRet = m_xFmtLb->get_selected_text(); + return aRet; +} + +/** + * Is it safe to show the water-can / fill icon. If we've a + * hierarchical widget - we have only single select, otherwise + * we need to check if we have a multi-selection. We either have + * a m_xTreeBox showing or an m_xFmtLb (which we hide when not shown) + */ +IMPL_LINK_NOARG(StyleList, IsSafeForWaterCan, void*, bool) +{ + if (m_xTreeBox->get_visible()) + return m_xTreeBox->get_selected_index() != -1; + else + return m_xFmtLb->count_selected_rows() == 1; +} + +IMPL_LINK(StyleList, SetWaterCanState, const SfxBoolItem*, pItem, void) +{ + size_t nCount = m_xStyleFamilies->size(); + m_pBindings->EnterRegistrations(); + for (size_t n = 0; n < nCount; n++) + { + SfxControllerItem* pCItem = pBoundItems[n].get(); + bool bChecked = pItem && pItem->GetValue(); + if (pCItem->IsBound() == bChecked) + { + if (!bChecked) + pCItem->ReBind(); + else + pCItem->UnBind(); + } + } + m_pBindings->LeaveRegistrations(); +} + +void StyleList::FamilySelect(sal_uInt16 nEntry) +{ + m_nActFamily = nEntry; + SfxDispatcher* pDispat = m_pBindings->GetDispatcher_Impl(); + SfxUInt16Item const aItem(SID_STYLE_FAMILY, + static_cast<sal_uInt16>(SfxTemplate::NIdToSfxFamilyId(nEntry))); + pDispat->ExecuteList(SID_STYLE_FAMILY, SfxCallMode::SYNCHRON, { &aItem }); + m_pBindings->Invalidate(SID_STYLE_FAMILY); + m_pBindings->Update(SID_STYLE_FAMILY); + UpdateFamily(); + m_aUpdateFamily.Call(*this); +} + +// It selects the style in treeview +// bIsCallBack is true for the selected style. For eg. if "Addressee" is selected in +// styles, bIsCallBack will be true for it. +void StyleList::SelectStyle(const OUString& rStr, bool bIsCallback) +{ + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + if (!pItem) + return; + const SfxStyleFamily eFam = pItem->GetFamily(); + SfxStyleSheetBase* pStyle = m_pStyleSheetPool->Find(rStr, eFam); + if (pStyle) + { + bool bReadWrite = !(pStyle->GetMask() & SfxStyleSearchBits::ReadOnly); + m_pParentDialog->EnableEdit(bReadWrite, this); + m_pParentDialog->EnableHide(bReadWrite && !pStyle->IsHidden() && !pStyle->IsUsed(), this); + m_pParentDialog->EnableShow(bReadWrite && pStyle->IsHidden(), this); + } + else + { + m_pParentDialog->EnableEdit(false, this); + m_pParentDialog->EnableHide(false, this); + m_pParentDialog->EnableShow(false, this); + } + + if (bIsCallback) + return; + + if (m_xTreeBox->get_visible()) + { + if (!rStr.isEmpty()) + { + std::unique_ptr<weld::TreeIter> xEntry = m_xTreeBox->make_iterator(); + bool bEntry = m_xTreeBox->get_iter_first(*xEntry); + while (bEntry) + { + if (m_xTreeBox->get_text(*xEntry) == rStr) + { + m_xTreeBox->scroll_to_row(*xEntry); + m_xTreeBox->select(*xEntry); + break; + } + bEntry = m_xTreeBox->iter_next(*xEntry); + } + } + else if (eFam == SfxStyleFamily::Pseudo) + { + std::unique_ptr<weld::TreeIter> xEntry = m_xTreeBox->make_iterator(); + if (m_xTreeBox->get_iter_first(*xEntry)) + { + m_xTreeBox->scroll_to_row(*xEntry); + m_xTreeBox->select(*xEntry); + } + } + else + m_xTreeBox->unselect_all(); + } + else + { + bool bSelect = !rStr.isEmpty(); + if (bSelect) + { + std::unique_ptr<weld::TreeIter> xEntry = m_xFmtLb->make_iterator(); + bool bEntry = m_xFmtLb->get_iter_first(*xEntry); + while (bEntry && m_xFmtLb->get_text(*xEntry) != rStr) + bEntry = m_xFmtLb->iter_next(*xEntry); + if (!bEntry) + bSelect = false; + else + { + if (!m_xFmtLb->is_selected(*xEntry)) + { + m_xFmtLb->unselect_all(); + m_xFmtLb->scroll_to_row(*xEntry); + m_xFmtLb->select(*xEntry); + } + } + } + + if (!bSelect) + { + m_xFmtLb->unselect_all(); + m_pParentDialog->EnableEdit(false, this); + m_pParentDialog->EnableHide(false, this); + m_pParentDialog->EnableShow(false, this); + } + } +} + +static void MakeExpanded_Impl(const weld::TreeView& rBox, std::vector<OUString>& rEntries) +{ + std::unique_ptr<weld::TreeIter> xEntry = rBox.make_iterator(); + if (rBox.get_iter_first(*xEntry)) + { + do + { + if (rBox.get_row_expanded(*xEntry)) + rEntries.push_back(rBox.get_text(*xEntry)); + } while (rBox.iter_next(*xEntry)); + } +} + +IMPL_LINK(StyleList, EnableTreeDrag, bool, m_bEnable, void) +{ + if (m_pStyleSheetPool) + { + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + SfxStyleSheetBase* pStyle = pItem ? m_pStyleSheetPool->First(pItem->GetFamily()) : nullptr; + m_bAllowReParentDrop = pStyle && pStyle->HasParentSupport() && m_bEnable; + } + m_bTreeDrag = m_bEnable; +} + +// Fill the treeview + +void StyleList::FillTreeBox(SfxStyleFamily eFam) +{ + assert(m_xTreeBox && "FillTreeBox() without treebox"); + if (!m_pStyleSheetPool || m_nActFamily == 0xffff) + return; + + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + if (!pItem) + return; + + StyleTreeArr_Impl aArr; + SfxStyleSheetBase* pStyle = m_pStyleSheetPool->First(eFam, SfxStyleSearchBits::AllVisible); + + m_bAllowReParentDrop = pStyle && pStyle->HasParentSupport() && m_bTreeDrag; + + while (pStyle) + { + StyleTree_Impl* pNew = new StyleTree_Impl(pStyle->GetName(), pStyle->GetParent()); + aArr.emplace_back(pNew); + pStyle = m_pStyleSheetPool->Next(); + } + OUString aUIName = getDefaultStyleName(eFam); + MakeTree_Impl(aArr, aUIName); + std::vector<OUString> aEntries; + MakeExpanded_Impl(*m_xTreeBox, aEntries); + m_xTreeBox->freeze(); + m_xTreeBox->clear(); + const sal_uInt16 nCount = aArr.size(); + + for (sal_uInt16 i = 0; i < nCount; ++i) + { + FillBox_Impl(*m_xTreeBox, aArr[i].get(), aEntries, eFam, nullptr); + aArr[i].reset(); + } + + m_pParentDialog->EnableItem("watercan", false); + + SfxTemplateItem* pState = m_pFamilyState[m_nActFamily - 1].get(); + + m_xTreeBox->thaw(); + + std::unique_ptr<weld::TreeIter> xEntry = m_xTreeBox->make_iterator(); + bool bEntry = m_xTreeBox->get_iter_first(*xEntry); + if (bEntry && nCount) + m_xTreeBox->expand_row(*xEntry); + + while (bEntry) + { + if (IsExpanded_Impl(aEntries, m_xTreeBox->get_text(*xEntry))) + m_xTreeBox->expand_row(*xEntry); + bEntry = m_xTreeBox->iter_next(*xEntry); + } + + OUString aStyle; + if (pState) // Select current entry + aStyle = pState->GetStyleName(); + m_pParentDialog->SelectStyle(aStyle, false, *this); + EnableDelete(nullptr); +} + +static OUString lcl_GetStyleFamilyName(SfxStyleFamily nFamily) +{ + if (nFamily == SfxStyleFamily::Char) + return "CharacterStyles"; + if (nFamily == SfxStyleFamily::Para) + return "ParagraphStyles"; + if (nFamily == SfxStyleFamily::Page) + return "PageStyles"; + if (nFamily == SfxStyleFamily::Table) + return "TableStyles"; + if (nFamily == SfxStyleFamily::Pseudo) + return "NumberingStyles"; + return OUString(); +} + +OUString StyleList::getDefaultStyleName(const SfxStyleFamily eFam) +{ + OUString sDefaultStyle; + OUString aFamilyName = lcl_GetStyleFamilyName(eFam); + if (aFamilyName == "TableStyles") + sDefaultStyle = "Default Style"; + else if (aFamilyName == "NumberingStyles") + sDefaultStyle = "No List"; + else + sDefaultStyle = "Standard"; + uno::Reference<style::XStyleFamiliesSupplier> xModel(m_pCurObjShell->GetModel(), + uno::UNO_QUERY); + OUString aUIName; + try + { + uno::Reference<container::XNameAccess> xStyles; + uno::Reference<container::XNameAccess> xCont = xModel->getStyleFamilies(); + xCont->getByName(aFamilyName) >>= xStyles; + uno::Reference<beans::XPropertySet> xInfo; + xStyles->getByName(sDefaultStyle) >>= xInfo; + xInfo->getPropertyValue("DisplayName") >>= aUIName; + } + catch (const uno::Exception&) + { + } + return aUIName; +} + +SfxStyleFamily StyleList::GetActualFamily() const +{ + const SfxStyleFamilyItem* pFamilyItem = GetFamilyItem(); + if (!pFamilyItem || m_nActFamily == 0xffff) + return SfxStyleFamily::Para; + else + return pFamilyItem->GetFamily(); +} + +IMPL_LINK_NOARG(StyleList, HasSelectedStyle, void*, bool) +{ + return m_xTreeBox->get_visible() ? m_xTreeBox->get_selected_index() != -1 + : m_xFmtLb->count_selected_rows() != 0; +} + +IMPL_LINK_NOARG(StyleList, UpdateStyleDependents, void*, void) +{ + // Trigger Help PI. Only when the watercan is on + if (m_nActFamily != 0xffff && m_pParentDialog->IsCheckedItem("watercan") && + // only if that region is allowed + nullptr != m_pFamilyState[m_nActFamily - 1] && IsSafeForWaterCan(nullptr)) + { + m_pParentDialog->Execute_Impl(SID_STYLE_WATERCAN, "", "", 0, *this); + m_pParentDialog->Execute_Impl(SID_STYLE_WATERCAN, GetSelectedEntry(), "", + static_cast<sal_uInt16>(GetFamilyItem()->GetFamily()), *this); + } +} + +// Comes into action when the current style is changed +void StyleList::UpdateStyles(StyleFlags nFlags) +{ + OSL_ENSURE(nFlags != StyleFlags::NONE, "nothing to do"); + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + if (!pItem) + { + // Is the case for the template catalog + const size_t nFamilyCount = m_xStyleFamilies->size(); + size_t n; + for (n = 0; n < nFamilyCount; n++) + if (m_pFamilyState[StyleNrToInfoOffset(n)]) + break; + if (n == nFamilyCount) + // It happens sometimes, God knows why + return; + m_nAppFilter = m_pFamilyState[StyleNrToInfoOffset(n)]->GetValue(); + m_pParentDialog->FamilySelect(StyleNrToInfoOffset(n) + 1, *this); + pItem = GetFamilyItem(); + } + + const SfxStyleFamily eFam = pItem->GetFamily(); + + SfxStyleSearchBits nFilter(m_nActFilter < pItem->GetFilterList().size() + ? pItem->GetFilterList()[m_nActFilter].nFlags + : SfxStyleSearchBits::Auto); + if (nFilter == SfxStyleSearchBits::Auto) // automatic + nFilter = m_nAppFilter; + + OSL_ENSURE(m_pStyleSheetPool, "no StyleSheetPool"); + if (!m_pStyleSheetPool) + return; + + pItem = GetFamilyItem(); + + m_aUpdateStyles.Call(nFlags); + + SfxStyleSheetBase* pStyle = m_pStyleSheetPool->First(eFam, nFilter); + + std::unique_ptr<weld::TreeIter> xEntry = m_xFmtLb->make_iterator(); + bool bEntry = m_xFmtLb->get_iter_first(*xEntry); + std::vector<OUString> aStrings; + + comphelper::string::NaturalStringSorter aSorter( + ::comphelper::getProcessComponentContext(), + Application::GetSettings().GetLanguageTag().getLocale()); + + while (pStyle) + { + aStrings.push_back(pStyle->GetName()); + pStyle = m_pStyleSheetPool->Next(); + } + OUString aUIName = getDefaultStyleName(eFam); + + // Paradoxically, with a list and non-Latin style names, + // sorting twice is faster than sorting once. + // The first sort has a cheap comparator, and gets the list into mostly-sorted order. + // Then the second sort needs to call its (much more expensive) comparator less often. + std::sort(aStrings.begin(), aStrings.end()); + std::sort(aStrings.begin(), aStrings.end(), + [&aSorter, &aUIName](const OUString& rLHS, const OUString& rRHS) { + if (rRHS == aUIName) + return false; + if (rLHS == aUIName) + return true; // default always first + return aSorter.compare(rLHS, rRHS) < 0; + }); + + size_t nCount = aStrings.size(); + size_t nPos = 0; + while (nPos < nCount && bEntry && aStrings[nPos] == m_xFmtLb->get_text(*xEntry)) + { + ++nPos; + bEntry = m_xFmtLb->iter_next(*xEntry); + } + + if (nPos < nCount || bEntry) + { + // Fills the display box + m_xFmtLb->freeze(); + m_xFmtLb->clear(); + + for (nPos = 0; nPos < nCount; ++nPos) + m_xFmtLb->append(aStrings[nPos], aStrings[nPos]); + + m_xFmtLb->thaw(); + } + // Selects the current style if any + SfxTemplateItem* pState = m_pFamilyState[m_nActFamily - 1].get(); + OUString aStyle; + if (pState) + aStyle = pState->GetStyleName(); + m_pParentDialog->SelectStyle(aStyle, false, *this); + EnableDelete(nullptr); +} + +void StyleList::SetFamilyState(sal_uInt16 nSlotId, const SfxTemplateItem* pItem) +{ + sal_uInt16 nIdx = nSlotId - SID_STYLE_FAMILY_START; + m_pFamilyState[nIdx].reset(); + if (pItem) + m_pFamilyState[nIdx].reset(new SfxTemplateItem(*pItem)); + m_bUpdateFamily = true; +} + +void StyleList::SetHierarchical() +{ + m_bHierarchical = true; + const OUString aSelectEntry(GetSelectedEntry()); + m_xFmtLb->hide(); + FillTreeBox(GetActualFamily()); + m_pParentDialog->SelectStyle(aSelectEntry, false, *this); + m_xTreeBox->show(); +} + +void StyleList::SetFilterControlsHandle() +{ + m_xTreeBox->hide(); + m_xFmtLb->show(); + m_bHierarchical = false; +} + +// Handler for the New-Buttons +void StyleList::NewHdl() +{ + if (m_nActFamily == 0xffff + || !(m_xTreeBox->get_visible() || m_xFmtLb->count_selected_rows() <= 1)) + return; + + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + const SfxStyleFamily eFam = pItem->GetFamily(); + SfxStyleSearchBits nMask(SfxStyleSearchBits::Auto); + if (m_nActFilter != 0xffff) + nMask = pItem->GetFilterList()[m_nActFilter].nFlags; + if (nMask == SfxStyleSearchBits::Auto) // automatic + nMask = m_nAppFilter; + + m_pParentDialog->Execute_Impl(SID_STYLE_NEW, "", GetSelectedEntry(), + static_cast<sal_uInt16>(eFam), *this, nMask); +} + +// Handler for the edit-Buttons +void StyleList::EditHdl() +{ + if (m_nActFamily != 0xffff && HasSelectedStyle(nullptr)) + { + sal_uInt16 nFilter = m_nActFilter; + OUString aTemplName(GetSelectedEntry()); + GetSelectedStyle(); // -Wall required?? + m_pParentDialog->Execute_Impl(SID_STYLE_EDIT, aTemplName, OUString(), + static_cast<sal_uInt16>(GetFamilyItem()->GetFamily()), *this, + SfxStyleSearchBits::Auto, &nFilter); + } +} + +// Handler for the Delete-Buttons +void StyleList::DeleteHdl() +{ + if (m_nActFamily == 0xffff || !HasSelectedStyle(nullptr)) + return; + + bool bUsedStyle = false; // one of the selected styles are used in the document? + + std::vector<std::unique_ptr<weld::TreeIter>> aList; + weld::TreeView* pTreeView = m_xTreeBox->get_visible() ? m_xTreeBox.get() : m_xFmtLb.get(); + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + + OUStringBuffer aMsg; + aMsg.append(SfxResId(STR_DELETE_STYLE_USED) + SfxResId(STR_DELETE_STYLE)); + + pTreeView->selected_foreach( + [this, pTreeView, pItem, &aList, &bUsedStyle, &aMsg](weld::TreeIter& rEntry) { + aList.emplace_back(pTreeView->make_iterator(&rEntry)); + // check the style is used or not + const OUString aTemplName(pTreeView->get_text(rEntry)); + + SfxStyleSheetBase* pStyle = m_pStyleSheetPool->Find(aTemplName, pItem->GetFamily()); + + if (pStyle->IsUsed()) // pStyle is in use in the document? + { + if (bUsedStyle) // add a separator for the second and later styles + aMsg.append(", "); + aMsg.append(aTemplName); + bUsedStyle = true; + } + + return false; + }); + + bool aApproved = false; + + // we only want to show the dialog once and if we want to delete a style in use (UX-advice) + if (bUsedStyle) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog( + pTreeView, VclMessageType::Question, VclButtonsType::YesNo, aMsg.makeStringAndClear())); + aApproved = xBox->run() == RET_YES; + } + + // if there are no used styles selected or the user approved the changes + if (bUsedStyle && !aApproved) + return; + + for (auto const& elem : aList) + { + const OUString aTemplName(pTreeView->get_text(*elem)); + m_bDontUpdate = true; // To prevent the Treelistbox to shut down while deleting + m_pParentDialog->Execute_Impl(SID_STYLE_DELETE, aTemplName, OUString(), + static_cast<sal_uInt16>(GetFamilyItem()->GetFamily()), *this); + + if (m_xTreeBox->get_visible()) + { + weld::RemoveParentKeepChildren(*m_xTreeBox, *elem); + m_bDontUpdate = false; + } + } + m_bDontUpdate = false; // if everything is deleted set m_bDontUpdate back to false + UpdateStyles(StyleFlags::UpdateFamilyList); // and force-update the list +} + +void StyleList::HideHdl() +{ + if (m_nActFamily == 0xffff || !HasSelectedStyle(nullptr)) + return; + + weld::TreeView* pTreeView = m_xTreeBox->get_visible() ? m_xTreeBox.get() : m_xFmtLb.get(); + pTreeView->selected_foreach([this, pTreeView](weld::TreeIter& rEntry) { + OUString aTemplName = pTreeView->get_text(rEntry); + + m_pParentDialog->Execute_Impl(SID_STYLE_HIDE, aTemplName, OUString(), + static_cast<sal_uInt16>(GetFamilyItem()->GetFamily()), *this); + + return false; + }); +} + +void StyleList::ShowHdl() +{ + if (m_nActFamily == 0xffff || !HasSelectedStyle(nullptr)) + return; + + weld::TreeView* pTreeView = m_xTreeBox->get_visible() ? m_xTreeBox.get() : m_xFmtLb.get(); + pTreeView->selected_foreach([this, pTreeView](weld::TreeIter& rEntry) { + OUString aTemplName = pTreeView->get_text(rEntry); + + m_pParentDialog->Execute_Impl(SID_STYLE_SHOW, aTemplName, OUString(), + static_cast<sal_uInt16>(GetFamilyItem()->GetFamily()), *this); + + return false; + }); +} + +IMPL_LINK_NOARG(StyleList, EnableDelete, void*, void) +{ + bool bEnableDelete(false); + if (m_nActFamily != 0xffff && HasSelectedStyle(nullptr)) + { + OSL_ENSURE(m_pStyleSheetPool, "No StyleSheetPool"); + const OUString aTemplName(GetSelectedEntry()); + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + const SfxStyleFamily eFam = pItem->GetFamily(); + SfxStyleSearchBits nFilter = SfxStyleSearchBits::Auto; + if (pItem->GetFilterList().size() > m_nActFilter) + nFilter = pItem->GetFilterList()[m_nActFilter].nFlags; + if (nFilter == SfxStyleSearchBits::Auto) // automatic + nFilter = m_nAppFilter; + const SfxStyleSheetBase* pStyle = m_pStyleSheetPool->Find( + aTemplName, eFam, m_xTreeBox->get_visible() ? SfxStyleSearchBits::All : nFilter); + + OSL_ENSURE(pStyle, "Style not found"); + if (pStyle && pStyle->IsUserDefined()) + { + if (pStyle->HasClearParentSupport() || !pStyle->IsUsed()) + { + bEnableDelete = true; + } + } + } + m_pParentDialog->EnableDel(bEnableDelete, this); +} + +IMPL_LINK_NOARG(StyleList, Clear, void*, void) +{ + m_xStyleFamilies.reset(); + for (auto& i : m_pFamilyState) + i.reset(); + m_pCurObjShell = nullptr; + for (auto& i : pBoundItems) + i.reset(); +} + +void StyleList::ShowMenu(const CommandEvent& rCEvt) +{ + CreateContextMenu(); + weld::TreeView* pTreeView = m_xTreeBox->get_visible() ? m_xTreeBox.get() : m_xFmtLb.get(); + OString sCommand( + mxMenu->popup_at_rect(pTreeView, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1)))); + MenuSelect(sCommand); +} + +void StyleList::MenuSelect(const OString& rIdent) +{ + sLastItemIdent = rIdent; + if (sLastItemIdent.isEmpty()) + return; + Application::PostUserEvent(LINK(this, StyleList, MenuSelectAsyncHdl)); /***check this****/ +} + +void StyleList::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint) +{ + const SfxHintId nId = rHint.GetId(); + + switch (nId) + { + case SfxHintId::UpdateDone: + { + SfxViewFrame* pViewFrame = m_pBindings->GetDispatcher_Impl()->GetFrame(); + SfxObjectShell* pDocShell = pViewFrame->GetObjectShell(); + if (m_pParentDialog->GetNotifyUpdate() + && (!m_pParentDialog->IsCheckedItem("watercan") + || (pDocShell && pDocShell->GetStyleSheetPool() != m_pStyleSheetPool))) + { + m_pParentDialog->SetNotifyupdate(false); + Update(); + } + else if (m_bUpdateFamily) + { + UpdateFamily(); + m_aUpdateFamily.Call(*this); + } + + if (m_pStyleSheetPool) + { + OUString aStr = GetSelectedEntry(); + if (!aStr.isEmpty()) + { + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + if (!pItem) + break; + const SfxStyleFamily eFam = pItem->GetFamily(); + SfxStyleSheetBase* pStyle = m_pStyleSheetPool->Find(aStr, eFam); + if (pStyle) + { + bool bReadWrite = !(pStyle->GetMask() & SfxStyleSearchBits::ReadOnly); + m_pParentDialog->EnableEdit(bReadWrite, this); + m_pParentDialog->EnableHide( + bReadWrite && !pStyle->IsUsed() && !pStyle->IsHidden(), this); + m_pParentDialog->EnableShow(bReadWrite && pStyle->IsHidden(), this); + } + else + { + m_pParentDialog->EnableEdit(false, this); + m_pParentDialog->EnableHide(false, this); + m_pParentDialog->EnableShow(false, this); + } + } + } + break; + } + + // Necessary if switching between documents and in both documents + // the same template is used. Do not immediately call Update_Impl, + // for the case that one of the documents is an internal InPlaceObject! + case SfxHintId::DocChanged: + m_pParentDialog->SetNotifyupdate(true); + break; + case SfxHintId::Dying: + { + EndListening(*m_pStyleSheetPool); + m_pStyleSheetPool = nullptr; + break; + } + default: + break; + } + + // Do not set timer when the stylesheet pool is in the box, because it is + // possible that a new one is registered after the timer is up - + // works bad in UpdateStyles_Impl ()! + + if (!m_bDontUpdate && nId != SfxHintId::Dying + && (dynamic_cast<const SfxStyleSheetPoolHint*>(&rHint) + || dynamic_cast<const SfxStyleSheetHint*>(&rHint) + || dynamic_cast<const SfxStyleSheetModifiedHint*>(&rHint) + || nId == SfxHintId::StyleSheetModified)) + { + if (!pIdle) + { + pIdle.reset(new Idle("SfxCommonTemplate")); + pIdle->SetPriority(TaskPriority::LOWEST); + pIdle->SetInvokeHandler(LINK(this, StyleList, TimeOut)); + } + pIdle->Start(); + } +} + +IMPL_LINK_NOARG(StyleList, TimeOut, Timer*, void) +{ + if (!m_bDontUpdate) + { + m_bDontUpdate = true; + if (!m_xTreeBox->get_visible()) + UpdateStyles(StyleFlags::UpdateFamilyList); + else + { + FillTreeBox(GetActualFamily()); + SfxTemplateItem* pState = m_pFamilyState[m_nActFamily - 1].get(); + if (pState) + { + m_pParentDialog->SelectStyle(pState->GetStyleName(), false, *this); + EnableDelete(nullptr); + } + } + m_bDontUpdate = false; + pIdle.reset(); + } + else + pIdle->Start(); +} + +IMPL_LINK_NOARG(StyleList, MenuSelectAsyncHdl, void*, void) +{ + if (sLastItemIdent == "new") + NewHdl(); + else if (sLastItemIdent == "edit") + EditHdl(); + else if (sLastItemIdent == "delete") + DeleteHdl(); + else if (sLastItemIdent == "hide") + HideHdl(); + else if (sLastItemIdent == "show") + ShowHdl(); +} + +// Double-click on a style sheet in the ListBox is applied. +IMPL_LINK(StyleList, DragBeginHdl, bool&, rUnsetDragIcon, bool) +{ + rUnsetDragIcon = false; + // Allow normal processing. only if bAllowReParentDrop is true + return !m_bAllowReParentDrop; +} + +IMPL_LINK(StyleList, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + bool bRet = false; + const vcl::KeyCode& rKeyCode = rKeyEvent.GetKeyCode(); + if (m_bCanDel && !rKeyCode.GetModifier() && rKeyCode.GetCode() == KEY_DELETE) + { + DeleteHdl(); + bRet = true; + } + return bRet; +} + +IMPL_LINK(StyleList, QueryTooltipHdl, const weld::TreeIter&, rEntry, OUString) +{ + weld::TreeView* pTreeView = m_xTreeBox->get_visible() ? m_xTreeBox.get() : m_xFmtLb.get(); + const OUString aTemplName(pTreeView->get_text(rEntry)); + OUString sQuickHelpText(aTemplName); + + const SfxStyleFamilyItem* pItem = GetFamilyItem(); + if (!pItem) + return sQuickHelpText; + SfxStyleSheetBase* pStyle = m_pStyleSheetPool->Find(aTemplName, pItem->GetFamily()); + + if (pStyle && pStyle->IsUsed()) // pStyle is in use in the document? + { + OUString sUsedBy; + if (pStyle->GetFamily() == SfxStyleFamily::Pseudo) + sUsedBy = pStyle->GetUsedBy(); + + if (!sUsedBy.isEmpty()) + { + const sal_Int32 nMaxLen = 80; + if (sUsedBy.getLength() > nMaxLen) + { + sUsedBy = OUString::Concat(sUsedBy.subView(0, nMaxLen)) + "..."; + } + + OUString aMessage = SfxResId(STR_STYLEUSEDBY); + aMessage = aMessage.replaceFirst("%STYLELIST", sUsedBy); + sQuickHelpText = aTemplName + " " + aMessage; + } + } + + return sQuickHelpText; +} + +IMPL_LINK(StyleList, CustomRenderHdl, weld::TreeView::render_args, aPayload, void) +{ + vcl::RenderContext& rRenderContext = std::get<0>(aPayload); + const ::tools::Rectangle& rRect = std::get<1>(aPayload); + ::tools::Rectangle aRect( + rRect.TopLeft(), + Size(rRenderContext.GetOutputSize().Width() - rRect.Left(), rRect.GetHeight())); + bool bSelected = std::get<2>(aPayload); + const OUString& rId = std::get<3>(aPayload); + + rRenderContext.Push(vcl::PushFlags::TEXTCOLOR); + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + if (bSelected) + rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor()); + else + rRenderContext.SetTextColor(rStyleSettings.GetDialogTextColor()); + + bool bSuccess = false; + + SfxObjectShell* pShell = SfxObjectShell::Current(); + sfx2::StyleManager* pStyleManager = pShell ? pShell->GetStyleManager() : nullptr; + + if (pStyleManager) + { + if (const SfxStyleFamilyItem* pItem = GetFamilyItem()) + { + SfxStyleSheetBase* pStyleSheet = pStyleManager->Search(rId, pItem->GetFamily()); + + if (pStyleSheet) + { + rRenderContext.Push(vcl::PushFlags::ALL); + sal_Int32 nSize = aRect.GetHeight(); + std::unique_ptr<sfx2::StylePreviewRenderer> pStylePreviewRenderer( + pStyleManager->CreateStylePreviewRenderer(rRenderContext, pStyleSheet, nSize)); + bSuccess + = pStylePreviewRenderer->recalculate() && pStylePreviewRenderer->render(aRect); + rRenderContext.Pop(); + } + } + } + + if (!bSuccess) + rRenderContext.DrawText(aRect, rId, DrawTextFlags::Left | DrawTextFlags::VCenter); + + rRenderContext.Pop(); +} + +// Selection of a template during the Watercan-Status +IMPL_LINK(StyleList, FmtSelectHdl, weld::TreeView&, rListBox, void) +{ + std::unique_ptr<weld::TreeIter> xHdlEntry = rListBox.make_iterator(); + if (!rListBox.get_cursor(xHdlEntry.get())) + return; + + m_pParentDialog->SelectStyle(rListBox.get_text(*xHdlEntry), true, *this); +} + +IMPL_LINK_NOARG(StyleList, TreeListApplyHdl, weld::TreeView&, bool) +{ + // only if that region is allowed + if (m_nActFamily != 0xffff && nullptr != m_pFamilyState[m_nActFamily - 1] + && !GetSelectedEntry().isEmpty()) + { + m_pParentDialog->Execute_Impl(SID_STYLE_APPLY, GetSelectedEntry(), OUString(), + static_cast<sal_uInt16>(GetFamilyItem()->GetFamily()), *this, + SfxStyleSearchBits::Auto, nullptr, &m_nModifier); + } + // After selecting a focused item if possible again on the app window + if (dynamic_cast<const SfxTemplateDialog_Impl*>(m_pParentDialog) != nullptr) + { + SfxViewFrame* pViewFrame = m_pBindings->GetDispatcher_Impl()->GetFrame(); + SfxViewShell* pVu = pViewFrame->GetViewShell(); + vcl::Window* pAppWin = pVu ? pVu->GetWindow() : nullptr; + if (pAppWin) + pAppWin->GrabFocus(); + } + + return true; +} + +IMPL_LINK(StyleList, MousePressHdl, const MouseEvent&, rMEvt, bool) +{ + m_nModifier = rMEvt.GetModifier(); + return false; +} + +// Notice from SfxBindings that the update is completed. Pushes out the update +// of the display. +void StyleList::Update() +{ + bool bDocChanged = false; + SfxStyleSheetBasePool* pNewPool = nullptr; + SfxViewFrame* pViewFrame = m_pBindings->GetDispatcher_Impl()->GetFrame(); + SfxObjectShell* pDocShell = pViewFrame->GetObjectShell(); + if (pDocShell) + pNewPool = pDocShell->GetStyleSheetPool(); + + if (pNewPool != m_pStyleSheetPool && pDocShell) + { + SfxModule* pNewModule = pDocShell->GetModule(); + if (pNewModule && pNewModule != m_Module) + { + m_aClearResource.Call(nullptr); + m_aReadResource.Call(*this); + } + if (m_pStyleSheetPool) + { + EndListening(*m_pStyleSheetPool); + m_pStyleSheetPool = nullptr; + } + + if (pNewPool) + { + StartListening(*pNewPool); + m_pStyleSheetPool = pNewPool; + bDocChanged = true; + } + } + + if (m_bUpdateFamily) + { + UpdateFamily(); + m_aUpdateFamily.Call(*this); + } + + sal_uInt16 i; + for (i = 0; i < MAX_FAMILIES; ++i) + if (m_pFamilyState[i]) + break; + if (i == MAX_FAMILIES || !pNewPool) + // nothing is allowed + return; + + SfxTemplateItem* pItem = nullptr; + // current region not within the allowed region or default + if (m_nActFamily == 0xffff || nullptr == (pItem = m_pFamilyState[m_nActFamily - 1].get())) + { + m_pParentDialog->CheckItem(OString::number(m_nActFamily), false); + const size_t nFamilyCount = m_xStyleFamilies->size(); + size_t n; + for (n = 0; n < nFamilyCount; n++) + if (m_pFamilyState[StyleNrToInfoOffset(n)]) + break; + + std::unique_ptr<SfxTemplateItem>& pNewItem = m_pFamilyState[StyleNrToInfoOffset(n)]; + m_nAppFilter = pNewItem->GetValue(); + m_pParentDialog->FamilySelect(StyleNrToInfoOffset(n) + 1, *this); + pItem = pNewItem.get(); + } + else if (bDocChanged) + { + // other DocShell -> all new + m_pParentDialog->CheckItem(OString::number(m_nActFamily)); + m_nActFilter = static_cast<sal_uInt16>(m_aLoadFactoryStyleFilter.Call(pDocShell)); + m_pParentDialog->IsUpdate(*this); + if (0xffff == m_nActFilter) + { + m_nActFilter = pDocShell->GetAutoStyleFilterIndex(); + } + + m_nAppFilter = pItem->GetValue(); + if (!m_xTreeBox->get_visible()) + { + UpdateStyles(StyleFlags::UpdateFamilyList); + } + else + FillTreeBox(GetActualFamily()); + } + else + { + // other filters for automatic + m_pParentDialog->CheckItem(OString::number(m_nActFamily)); + const SfxStyleFamilyItem* pStyleItem = GetFamilyItem(); + if (pStyleItem + && SfxStyleSearchBits::Auto == pStyleItem->GetFilterList()[m_nActFilter].nFlags + && m_nAppFilter != pItem->GetValue()) + { + m_nAppFilter = pItem->GetValue(); + if (!m_xTreeBox->get_visible()) + UpdateStyles(StyleFlags::UpdateFamilyList); + else + FillTreeBox(GetActualFamily()); + } + else + { + m_nAppFilter = pItem->GetValue(); + } + } + const OUString aStyle(pItem->GetStyleName()); + m_pParentDialog->SelectStyle(aStyle, false, *this); + EnableDelete(nullptr); + m_pParentDialog->EnableNew(m_bCanNew, this); +} + +void StyleList::EnablePreview(bool bCustomPreview) +{ + m_xFmtLb->clear(); + m_xFmtLb->set_column_custom_renderer(0, bCustomPreview); + m_xTreeBox->clear(); + m_xTreeBox->set_column_custom_renderer(0, bCustomPreview); +} + +const SfxStyleFamilyItem& StyleList::GetFamilyItemByIndex(size_t i) const +{ + return m_xStyleFamilies->at(i); +} + +IMPL_STATIC_LINK(StyleList, CustomGetSizeHdl, weld::TreeView::get_size_args, aPayload, Size) +{ + vcl::RenderContext& rRenderContext = aPayload.first; + return Size(42, 32 * rRenderContext.GetDPIScaleFactor()); +} + +IMPL_LINK(StyleList, PopupFlatMenuHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + PrepareMenu(rCEvt.GetMousePosPixel()); + + if (m_xFmtLb->count_selected_rows() <= 0) + { + m_pParentDialog->EnableEdit(false, this); + m_pParentDialog->EnableDel(false, this); + } + + ShowMenu(rCEvt); + + return true; +} + +IMPL_LINK(StyleList, PopupTreeMenuHdl, const CommandEvent&, rCEvt, bool) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + + PrepareMenu(rCEvt.GetMousePosPixel()); + + ShowMenu(rCEvt); + + return true; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/alienwarn.cxx b/sfx2/source/dialog/alienwarn.cxx new file mode 100644 index 000000000..15fe92ccd --- /dev/null +++ b/sfx2/source/dialog/alienwarn.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <alienwarn.hxx> +#include <officecfg/Office/Common.hxx> + +SfxAlienWarningDialog::SfxAlienWarningDialog(weld::Window* pParent, + std::u16string_view _rFormatName, + const OUString& _rDefaultExtension, + bool rDefaultIsAlien) + : MessageDialogController(pParent, "sfx/ui/alienwarndialog.ui", "AlienWarnDialog", "ask") + , m_xKeepCurrentBtn(m_xBuilder->weld_button("save")) + , m_xUseDefaultFormatBtn(m_xBuilder->weld_button("cancel")) + , m_xWarningOnBox(m_xBuilder->weld_check_button("ask")) +{ + OUString aExtension = "ODF"; + + // replace formatname (text) + OUString sInfoText = m_xDialog->get_primary_text(); + sInfoText = sInfoText.replaceAll("%FORMATNAME", _rFormatName); + m_xDialog->set_primary_text(sInfoText); + + // replace formatname (button) + sInfoText = m_xKeepCurrentBtn->get_label(); + sInfoText = sInfoText.replaceAll("%FORMATNAME", _rFormatName); + m_xKeepCurrentBtn->set_label(sInfoText); + + // hide ODF explanation if default format is alien + // and set the proper extension in the button + if (rDefaultIsAlien) + { + m_xDialog->set_secondary_text(OUString()); + aExtension = _rDefaultExtension.toAsciiUpperCase(); + } + + // replace defaultextension (button) + sInfoText = m_xUseDefaultFormatBtn->get_label(); + sInfoText = sInfoText.replaceAll("%DEFAULTEXTENSION", aExtension); + m_xUseDefaultFormatBtn->set_label(sInfoText); + + // load value of "warning on" checkbox from save options + m_xWarningOnBox->set_active(officecfg::Office::Common::Save::Document::WarnAlienFormat::get()); +} + +SfxAlienWarningDialog::~SfxAlienWarningDialog() +{ + try + { + // save value of "warning off" checkbox, if necessary + bool bChecked = m_xWarningOnBox->get_active(); + if (officecfg::Office::Common::Save::Document::WarnAlienFormat::get() != bChecked) + { + auto xChanges = comphelper::ConfigurationChanges::create(); + officecfg::Office::Common::Save::Document::WarnAlienFormat::set(bChecked, xChanges); + xChanges->commit(); + } + } + catch (...) + { + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/backingcomp.cxx b/sfx2/source/dialog/backingcomp.cxx new file mode 100644 index 000000000..845435ddc --- /dev/null +++ b/sfx2/source/dialog/backingcomp.cxx @@ -0,0 +1,736 @@ +/* -*- 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 "backingwindow.hxx" + +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/KeyEvent.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XKeyListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/weak.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syswin.hxx> + +#include <sfx2/notebookbar/SfxNotebookBar.hxx> + +namespace { + +/** + implements the backing component. + + This component is a special one, which doesn't provide a controller + nor a model. It supports the following features: + - Drag & Drop + - Key Accelerators + - Simple Menu + - Progress Bar + - Background + */ +class BackingComp : public css::lang::XTypeProvider + , public css::lang::XServiceInfo + , public css::lang::XInitialization + , public css::frame::XController // => XComponent + , public css::awt::XKeyListener // => XEventListener + , public css::frame::XDispatchProvider + , public css::frame::XDispatch + , public ::cppu::OWeakObject +{ +private: + /** reference to the component window. */ + css::uno::Reference< css::awt::XWindow > m_xWindow; + + /** the owner frame of this component. */ + css::uno::Reference< css::frame::XFrame > m_xFrame; + + Size m_aInitialWindowMinSize; + +public: + + explicit BackingComp(); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override; + virtual void SAL_CALL acquire ( ) noexcept override; + virtual void SAL_CALL release ( ) noexcept override; + + // XTypeProvide + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes () override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName ( ) override; + virtual sal_Bool SAL_CALL supportsService ( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& lArgs ) override; + + // XController + virtual void SAL_CALL attachFrame( const css::uno::Reference< css::frame::XFrame >& xFrame ) override; + virtual sal_Bool SAL_CALL attachModel( const css::uno::Reference< css::frame::XModel >& xModel ) override; + virtual sal_Bool SAL_CALL suspend( sal_Bool bSuspend ) override; + virtual css::uno::Any SAL_CALL getViewData() override; + virtual void SAL_CALL restoreViewData( const css::uno::Any& aData ) override; + virtual css::uno::Reference< css::frame::XModel > SAL_CALL getModel() override; + virtual css::uno::Reference< css::frame::XFrame > SAL_CALL getFrame() override; + + // XKeyListener + virtual void SAL_CALL keyPressed ( const css::awt::KeyEvent& aEvent ) override; + virtual void SAL_CALL keyReleased( const css::awt::KeyEvent& aEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override; + + // XComponent + virtual void SAL_CALL dispose ( ) override; + virtual void SAL_CALL addEventListener ( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + + // XDispatchProvider + virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch( const css::util::URL& aURL, const OUString& sTargetFrameName , sal_Int32 nSearchFlags ) override; + virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptions ) override; + + // XDispatch + virtual void SAL_CALL dispatch( const css::util::URL& aURL, const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) override; + virtual void SAL_CALL addStatusListener( const css::uno::Reference< css::frame::XStatusListener >& xListener, const css::util::URL& aURL ) override; + virtual void SAL_CALL removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >& xListener, const css::util::URL& aURL ) override; +}; + +BackingComp::BackingComp() +{ +} + +/** return information about supported interfaces. + + Some interfaces are supported by his class directly, but some other ones are + used by aggregation. An instance of this class must provide some window interfaces. + But it must represent a VCL window behind such interfaces too! So we use an internal + saved window member to ask it for its interfaces and return it. But we must be aware then, + that it can be destroyed from outside too ... + + @param aType + describe the required interface type + + @return An Any holding the instance, which provides the queried interface. + Note: There exist two possible results ... this instance itself and her window member! + */ + +css::uno::Any SAL_CALL BackingComp::queryInterface( /*IN*/ const css::uno::Type& aType ) +{ + // first look for own supported interfaces + css::uno::Any aResult = ::cppu::queryInterface( + aType, + static_cast< css::lang::XTypeProvider* >(this), + static_cast< css::lang::XServiceInfo* >(this), + static_cast< css::lang::XInitialization* >(this), + static_cast< css::frame::XController* >(this), + static_cast< css::lang::XComponent* >(this), + static_cast< css::lang::XEventListener* >(this), + static_cast< css::awt::XKeyListener* >(static_cast< css::lang::XEventListener* >(this)), + static_cast< css::frame::XDispatchProvider* >(this), + static_cast< css::frame::XDispatch* >(this) ); + + // then look for supported window interfaces + // Note: They exist only, if this instance was initialized + // with a valid window reference. It's aggregation on demand ... + if (!aResult.hasValue()) + { + /* SAFE { */ + SolarMutexGuard aGuard; + if (m_xWindow.is()) + aResult = m_xWindow->queryInterface(aType); + /* } SAFE */ + } + + // look for XWeak and XInterface + if (!aResult.hasValue()) + aResult = OWeakObject::queryInterface(aType); + + return aResult; +} + + +/** increase ref count of this instance. + */ + +void SAL_CALL BackingComp::acquire() + noexcept +{ + OWeakObject::acquire(); +} + + +/** decrease ref count of this instance. + */ + +void SAL_CALL BackingComp::release() + noexcept +{ + OWeakObject::release(); +} + + +/** return collection about all supported interfaces. + + Optimize this method ! + We initialize a static variable only one time. + And we don't must use a mutex at every call! + For the first call; pTypeCollection is NULL - + for the second call pTypeCollection is different from NULL! + + @return A list of all supported interface types. +*/ + +css::uno::Sequence< css::uno::Type > SAL_CALL BackingComp::getTypes() +{ + static cppu::OTypeCollection aTypeCollection = [this]() { + SolarMutexGuard aGuard; + css::uno::Reference<css::lang::XTypeProvider> xProvider(m_xWindow, css::uno::UNO_QUERY); + + css::uno::Sequence<css::uno::Type> lWindowTypes; + if (xProvider.is()) + lWindowTypes = xProvider->getTypes(); + + return cppu::OTypeCollection( + cppu::UnoType<css::lang::XInitialization>::get(), + cppu::UnoType<css::lang::XTypeProvider>::get(), + cppu::UnoType<css::lang::XServiceInfo>::get(), + cppu::UnoType<css::frame::XController>::get(), + cppu::UnoType<css::lang::XComponent>::get(), + cppu::UnoType<css::frame::XDispatchProvider>::get(), + cppu::UnoType<css::frame::XDispatch>::get(), lWindowTypes); + }(); + + return aTypeCollection.getTypes(); +} + + +/** create one unique Id for all instances of this class. + + Optimize this method + We initialize a static variable only one time. And we don't must use a mutex at every call! + For the first call; pID is NULL - for the second call pID is different from NULL! + + @return A byte array, which represent the unique id. +*/ + +css::uno::Sequence< sal_Int8 > SAL_CALL BackingComp::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +OUString SAL_CALL BackingComp::getImplementationName() +{ + return "com.sun.star.comp.sfx2.BackingComp"; +} + +sal_Bool SAL_CALL BackingComp::supportsService( /*IN*/ const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL BackingComp::getSupportedServiceNames() +{ + return { "com.sun.star.frame.StartModule", "com.sun.star.frame.ProtocolHandler" }; +} + + +/** + attach this component to a target frame. + + We have to use the container window of this frame as parent window of our own component window. + But it's not allowed to work with it really. May another component used it too. + Currently we need it only to create our child component window and support it's + interfaces inside our queryInterface() method. The user of us must have e.g. the + XWindow interface of it to be able to call setComponent(xWindow,xController) at the + frame! + + May he will do the following things: + + <listing> + XController xBackingComp = (XController)UnoRuntime.queryInterface( + XController.class, + xSMGR.createInstance(SERVICENAME_STARTMODULE)); + + // at this time XWindow isn't present at this instance! + XWindow xBackingComp = (XWindow)UnoRuntime.queryInterface( + XWindow.class, + xBackingComp); + + // attach controller to the frame + // We will use its container window, to create + // the component window. From now we offer the window interfaces! + xBackingComp.attachFrame(xFrame); + + XWindow xBackingComp = (XWindow)UnoRuntime.queryInterface( + XWindow.class, + xBackingComp); + + // Our user can set us at the frame as new component + xFrame.setComponent(xBackingWin, xBackingComp); + + // But that had no effect to our view state. + // We must be started to create our UI elements like e.g. menu, title, background ... + XInitialization xBackingInit = (XInitialization)UnoRuntime.queryInterface( + XInitialization.class, + xBackingComp); + + xBackingInit.initialize(lArgs); + </listing> + + @param xFrame + reference to our new target frame + + @throw css::uno::RuntimeException + if the given frame reference is wrong or component window couldn't be created + successfully. + We throw it too, if we already attached to a frame. Because we don't support + reparenting of our component window on demand! +*/ + +void SAL_CALL BackingComp::attachFrame( /*IN*/ const css::uno::Reference< css::frame::XFrame >& xFrame ) +{ + /* SAFE */ + SolarMutexGuard aGuard; + + // check some required states + if (m_xFrame.is()) + throw css::uno::RuntimeException( + "already attached", + static_cast< ::cppu::OWeakObject* >(this)); + + if (!xFrame.is()) + throw css::uno::RuntimeException( + "invalid frame reference", + static_cast< ::cppu::OWeakObject* >(this)); + + if (!m_xWindow.is()) + return; // disposed + + // safe the frame reference + m_xFrame = xFrame; + + // initialize the component and its parent window + css::uno::Reference< css::awt::XWindow > xParentWindow = xFrame->getContainerWindow(); + VclPtr< WorkWindow > pParent = static_cast<WorkWindow*>(VCLUnoHelper::GetWindow(xParentWindow)); + VclPtr< vcl::Window > pWindow = VCLUnoHelper::GetWindow(m_xWindow); + + // disable full screen mode of the frame! + if (pParent && pParent->IsFullScreenMode()) + { + pParent->ShowFullScreenMode(false); + pParent->SetMenuBarMode(MenuBarMode::Normal); + } + + // create the menu bar for the backing component + css::uno::Reference< css::beans::XPropertySet > xPropSet(m_xFrame, css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + xPropSet->getPropertyValue("LayoutManager") >>= xLayoutManager; + if (xLayoutManager.is()) + { + xLayoutManager->lock(); + xLayoutManager->createElement("private:resource/menubar/menubar"); + xLayoutManager->unlock(); + } + + if (pWindow) + { + // set help ID for our canvas + pWindow->SetHelpId("FWK_HID_BACKINGWINDOW"); + } + + // inform BackingWindow about frame + BackingWindow* pBack = dynamic_cast<BackingWindow*>(pWindow.get()); + if( pBack ) + pBack->setOwningFrame( m_xFrame ); + + // Set a minimum size for Start Center + if( !pParent || !pBack ) + return; + + tools::Long nMenuHeight = 0; + vcl::Window* pMenu = pParent->GetWindow(GetWindowType::Next); + if( pMenu ) + nMenuHeight = pMenu->GetSizePixel().Height(); + + m_aInitialWindowMinSize = pParent->GetMinOutputSizePixel(); + if (!m_aInitialWindowMinSize.Width()) + m_aInitialWindowMinSize.AdjustWidth(1); + if (!m_aInitialWindowMinSize.Height()) + m_aInitialWindowMinSize.AdjustHeight(1); + + pParent->SetMinOutputSizePixel( + Size( + pBack->get_width_request(), + pBack->get_height_request() + nMenuHeight)); + + /* } SAFE */ +} + + +/** not supported. + + This component does not know any model. It will be represented by a window and + its controller only. + + return <FALSE/> every time. + */ + +sal_Bool SAL_CALL BackingComp::attachModel( /*IN*/ const css::uno::Reference< css::frame::XModel >& ) +{ + return false; +} + + +/** not supported. + + This component does not know any model. It will be represented by a window and + its controller only. + + return An empty reference every time. + */ + +css::uno::Reference< css::frame::XModel > SAL_CALL BackingComp::getModel() +{ + return css::uno::Reference< css::frame::XModel >(); +} + + +/** not supported. + + return An empty value. + */ + +css::uno::Any SAL_CALL BackingComp::getViewData() +{ + return css::uno::Any(); +} + + +/** not supported. + + @param aData + not used. + */ + +void SAL_CALL BackingComp::restoreViewData( /*IN*/ const css::uno::Any& ) +{ +} + + +/** returns the attached frame for this component. + + @see attachFrame() + + @return The internally saved frame reference. + Can be null, if attachFrame() was not called before. + */ + +css::uno::Reference< css::frame::XFrame > SAL_CALL BackingComp::getFrame() +{ + /* SAFE { */ + SolarMutexGuard aGuard; + return m_xFrame; + /* } SAFE */ +} + + +/** ask controller for its current working state. + + If someone wishes to close this component, it must suspend the controller before. + That will be a chance for it to disagree with that AND show any UI for a possible + UI user. + + @param bSuspend + If it's set to sal_True this controller should be suspended. + sal_False will resuspend it. + + @return sal_True if the request could be finished successfully; sal_False otherwise. + */ + +sal_Bool SAL_CALL BackingComp::suspend( /*IN*/ sal_Bool ) +{ + /* FIXME ... implemented by using default :-( */ + return true; +} + + +/** callback from our window member. + + Our internal saved window wish to die. It will be disposed from outside (may be the frame) + and inform us. We must release its reference only here. Of course we check the given reference + here and reject callback from unknown sources. + + Note: deregistration as listener isn't necessary here. The broadcaster do it automatically. + + @param aEvent + describe the broadcaster of this callback + + @throw css::uno::RuntimeException + if the broadcaster doesn't represent the expected window reference. +*/ + +void SAL_CALL BackingComp::disposing( /*IN*/ const css::lang::EventObject& aEvent ) +{ + // Attention: don't free m_pAccExec here! see comments inside dtor and + // keyPressed() for further details. + + /* SAFE { */ + SolarMutexGuard aGuard; + + if (!aEvent.Source.is() || aEvent.Source!=m_xWindow || !m_xWindow.is()) + throw css::uno::RuntimeException( + "unexpected source or called twice", + static_cast< ::cppu::OWeakObject* >(this)); + + m_xWindow.clear(); + + /* } SAFE */ +} + + +/** kill this instance. + + It can be called from our owner frame only. But there is no possibility to check the caller. + We have to release all our internal used resources and die. From this point we can throw + DisposedExceptions for every further interface request... but current implementation doesn't do so... + +*/ + +void SAL_CALL BackingComp::dispose() +{ + /* SAFE { */ + SolarMutexGuard aGuard; + + if (m_xFrame.is()) + { + css::uno::Reference< css::awt::XWindow > xParentWindow = m_xFrame->getContainerWindow(); + VclPtr< WorkWindow > pParent = static_cast<WorkWindow*>(VCLUnoHelper::GetWindow(xParentWindow)); + if (pParent) + { + pParent->SetMinOutputSizePixel(m_aInitialWindowMinSize); + // hide NotebookBar + sfx2::SfxNotebookBar::CloseMethod(static_cast<SystemWindow*>(pParent)); + } + } + + // stop listening at the window + if (m_xWindow.is()) + { + m_xWindow->removeEventListener(this); + m_xWindow->removeKeyListener(this); + m_xWindow.clear(); + } + + // forget all other used references + m_xFrame.clear(); + + /* } SAFE */ +} + + +/** not supported. + + @param xListener + not used. + + @throw css::uno::RuntimeException + because the listener expect to be holded alive by this container. + We must inform it about this unsupported feature. + */ + +void SAL_CALL BackingComp::addEventListener( /*IN*/ const css::uno::Reference< css::lang::XEventListener >& ) +{ + throw css::uno::RuntimeException( + "not supported", + static_cast< ::cppu::OWeakObject* >(this)); +} + + +/** not supported. + + Because registration is not supported too, we must do nothing here. Nobody can call this method really. + + @param xListener + not used. + */ + +void SAL_CALL BackingComp::removeEventListener( /*IN*/ const css::uno::Reference< css::lang::XEventListener >& ) +{ +} + + +/** + force initialization for this component. + + Inside attachFrame() we created our component window. But it was not allowed there, to + initialize it. E.g. the menu must be set at the container window of the frame, which + is our parent window. But may at that time another component used it. + That's why our creator has to inform us, when it's time to initialize us really. + Currently only calling of this method must be done. But further implementations + can use special in parameter to configure this initialization... + + @param lArgs + currently not used + + @throw css::uno::RuntimeException + if some resources are missing + Means if may be attachedFrame() wasn't called before. + */ + +void SAL_CALL BackingComp::initialize( /*IN*/ const css::uno::Sequence< css::uno::Any >& lArgs ) +{ + /* SAFE { */ + SolarMutexGuard aGuard; + + if (m_xWindow.is()) + throw css::uno::Exception( + "already initialized", + static_cast< ::cppu::OWeakObject* >(this)); + + css::uno::Reference< css::awt::XWindow > xParentWindow; + if ( + (lArgs.getLength()!=1 ) || + (!(lArgs[0] >>= xParentWindow)) || + (!xParentWindow.is() ) + ) + { + throw css::uno::Exception( + "wrong or corrupt argument list", + static_cast< ::cppu::OWeakObject* >(this)); + } + + // create the component window + VclPtr<vcl::Window> pParent = VCLUnoHelper::GetWindow(xParentWindow); + VclPtr<vcl::Window> pWindow = VclPtr<BackingWindow>::Create(pParent); + m_xWindow = VCLUnoHelper::GetInterface(pWindow); + + if (!m_xWindow.is()) + throw css::uno::RuntimeException( + "couldn't create component window", + static_cast< ::cppu::OWeakObject* >(this)); + + // start listening for window disposing + // It's set at our owner frame as component window later too. So it will may be disposed there ... + m_xWindow->addEventListener(static_cast< css::lang::XEventListener* >(this)); + + m_xWindow->setVisible(true); + + /* } SAFE */ +} + + +void SAL_CALL BackingComp::keyPressed( /*IN*/ const css::awt::KeyEvent& ) +{ +} + + +void SAL_CALL BackingComp::keyReleased( /*IN*/ const css::awt::KeyEvent& ) +{ + /* Attention + Please use keyPressed() instead of this method. Otherwise it would be possible, that + - a key input may be first switch to the backing mode + - and this component register itself as key listener too + - and it's first event will be a keyReleased() for the already well known event, which switched to the backing mode! + So it will be handled twice! document => backing mode => exit app... + */ +} + +// XDispatchProvider +css::uno::Reference< css::frame::XDispatch > SAL_CALL BackingComp::queryDispatch( const css::util::URL& aURL, const OUString& /*sTargetFrameName*/, sal_Int32 /*nSearchFlags*/ ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatch; + if ( aURL.Protocol == "vnd.org.libreoffice.recentdocs:" ) + xDispatch = this; + + return xDispatch; +} + +css::uno::Sequence < css::uno::Reference< css::frame::XDispatch > > SAL_CALL BackingComp::queryDispatches( const css::uno::Sequence < css::frame::DispatchDescriptor >& seqDescripts ) +{ + sal_Int32 nCount = seqDescripts.getLength(); + css::uno::Sequence < css::uno::Reference < XDispatch > > lDispatcher( nCount ); + + std::transform(seqDescripts.begin(), seqDescripts.end(), lDispatcher.getArray(), + [this](const css::frame::DispatchDescriptor& rDesc) -> css::uno::Reference<XDispatch> { + return queryDispatch(rDesc.FeatureURL, rDesc.FrameName, rDesc.SearchFlags); }); + + return lDispatcher; +} + +// XDispatch +void SAL_CALL BackingComp::dispatch( const css::util::URL& aURL, const css::uno::Sequence < css::beans::PropertyValue >& /*lArgs*/ ) +{ + // vnd.org.libreoffice.recentdocs:ClearRecentFileList - clear recent files + if ( aURL.Path != "ClearRecentFileList" ) + return; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(m_xWindow); + BackingWindow* pBack = dynamic_cast<BackingWindow*>(pWindow.get()); + if( !pBack ) + return; + + pBack->clearRecentFileList(); + + // Recalculate minimum width + css::uno::Reference< css::awt::XWindow > xParentWindow = m_xFrame->getContainerWindow(); + VclPtr< WorkWindow > pParent = static_cast<WorkWindow*>(VCLUnoHelper::GetWindow(xParentWindow)); + if( pParent ) + { + pParent->SetMinOutputSizePixel( Size( + pBack->get_width_request(), + pParent->GetMinOutputSizePixel().Height()) ); + } +} + +void SAL_CALL BackingComp::addStatusListener( const css::uno::Reference< css::frame::XStatusListener >& /*xControl*/, const css::util::URL& /*aURL*/ ) +{ +} + +void SAL_CALL BackingComp::removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >& /*xControl*/, const css::util::URL& /*aURL*/ ) +{ +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_sfx2_BackingComp_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new BackingComp); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/backingwindow.cxx b/sfx2/source/dialog/backingwindow.cxx new file mode 100644 index 000000000..6d8c2bbbc --- /dev/null +++ b/sfx2/source/dialog/backingwindow.cxx @@ -0,0 +1,780 @@ +/* -*- 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 "backingwindow.hxx" +#include <vcl/event.hxx> +#include <vcl/help.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syswin.hxx> + +#include <unotools/historyoptions.hxx> +#include <unotools/moduleoptions.hxx> +#include <svtools/openfiledroptargetlistener.hxx> +#include <svtools/colorcfg.hxx> +#include <svtools/langhelp.hxx> +#include <templateviewitem.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <sfx2/app.hxx> +#include <officecfg/Office/Common.hxx> + +#include <tools/diagnose_ex.h> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/document/MacroExecMode.hpp> +#include <com/sun/star/document/UpdateDocMode.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::document; + +constexpr OUStringLiteral SERVICENAME_CFGREADACCESS = u"com.sun.star.configuration.ConfigurationAccess"; + +class BrandImage final : public weld::CustomWidgetController +{ +private: + BitmapEx maBrandImage; + bool mbIsDark = false; + Size m_BmpSize; + +public: + Size getSize() { return m_BmpSize; } + + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override + { + weld::CustomWidgetController::SetDrawingArea(pDrawingArea); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + rDevice.SetBackground(Wallpaper(rStyleSettings.GetWindowColor())); + + SetPointer(PointerStyle::RefHand); + } + + virtual void Resize() override + { + auto nWidth = GetOutputSizePixel().Width(); + if (maBrandImage.GetSizePixel().Width() != nWidth) + LoadImageForWidth(nWidth); + weld::CustomWidgetController::Resize(); + } + + void LoadImageForWidth(int nWidth) + { + mbIsDark = Application::GetSettings().GetStyleSettings().GetDialogColor().IsDark(); + SfxApplication::loadBrandSvg(mbIsDark ? "shell/logo-sc_inverted" : "shell/logo-sc", + maBrandImage, nWidth); + } + + void ConfigureForWidth(int nWidth) + { + if (maBrandImage.GetSizePixel().Width() == nWidth) + return; + LoadImageForWidth(nWidth); + m_BmpSize = maBrandImage.GetSizePixel(); + set_size_request(m_BmpSize.Width(), m_BmpSize.Height()); + } + + virtual void StyleUpdated() override + { + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + + // tdf#141857 update background to current theme + OutputDevice& rDevice = GetDrawingArea()->get_ref_device(); + rDevice.SetBackground(Wallpaper(rStyleSettings.GetWindowColor())); + + const bool bIsDark = rStyleSettings.GetDialogColor().IsDark(); + if (bIsDark != mbIsDark) + LoadImageForWidth(GetOutputSizePixel().Width()); + weld::CustomWidgetController::StyleUpdated(); + } + + virtual bool MouseButtonUp(const MouseEvent& rMEvt) override + { + if (rMEvt.IsLeft()) + { + OUString sURL = officecfg::Office::Common::Menus::VolunteerURL::get(); + localizeWebserviceURI(sURL); + + Reference<css::system::XSystemShellExecute> const xSystemShellExecute( + css::system::SystemShellExecute::create( + ::comphelper::getProcessComponentContext())); + xSystemShellExecute->execute(sURL, OUString(), + css::system::SystemShellExecuteFlags::URIS_ONLY); + } + return true; + } + + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) override + { + rRenderContext.DrawBitmapEx(Point(0, 0), maBrandImage); + } +}; + +// increase size of the text in the buttons on the left fMultiplier-times +float const g_fMultiplier = 1.2f; + +BackingWindow::BackingWindow(vcl::Window* i_pParent) + : InterimItemWindow(i_pParent, "sfx/ui/startcenter.ui", "StartCenter", false) + , mxOpenButton(m_xBuilder->weld_button("open_all")) + , mxRecentButton(m_xBuilder->weld_menu_toggle_button("open_recent")) + , mxRemoteButton(m_xBuilder->weld_button("open_remote")) + , mxTemplateButton(m_xBuilder->weld_menu_toggle_button("templates_all")) + , mxCreateLabel(m_xBuilder->weld_label("create_label")) + , mxAltHelpLabel(m_xBuilder->weld_label("althelplabel")) + , mxWriterAllButton(m_xBuilder->weld_button("writer_all")) + , mxCalcAllButton(m_xBuilder->weld_button("calc_all")) + , mxImpressAllButton(m_xBuilder->weld_button("impress_all")) + , mxDrawAllButton(m_xBuilder->weld_button("draw_all")) + , mxDBAllButton(m_xBuilder->weld_button("database_all")) + , mxMathAllButton(m_xBuilder->weld_button("math_all")) + , mxBrandImage(new BrandImage) + , mxBrandImageWeld(new weld::CustomWeld(*m_xBuilder, "daBrand", *mxBrandImage)) + , mxHelpButton(m_xBuilder->weld_button("help")) + , mxExtensionsButton(m_xBuilder->weld_button("extensions")) + , mxAllButtonsBox(m_xBuilder->weld_container("all_buttons_box")) + , mxButtonsBox(m_xBuilder->weld_container("buttons_box")) + , mxSmallButtonsBox(m_xBuilder->weld_container("small_buttons_box")) + , mxAllRecentThumbnails(new sfx2::RecentDocsView(m_xBuilder->weld_scrolled_window("scrollrecent", true), + m_xBuilder->weld_menu("recentmenu"))) + , mxAllRecentThumbnailsWin(new weld::CustomWeld(*m_xBuilder, "all_recent", *mxAllRecentThumbnails)) + , mxLocalView(new TemplateDefaultView(m_xBuilder->weld_scrolled_window("scrolllocal", true), + m_xBuilder->weld_menu("localmenu"))) + , mxLocalViewWin(new weld::CustomWeld(*m_xBuilder, "local_view", *mxLocalView)) + , mbLocalViewInitialized(false) + , mbInitControls(false) +{ + // init background + SetPaintTransparent(false); + SetBackground(svtools::ColorConfig().GetColorValue(::svtools::APPBACKGROUND).nColor); + + //set an alternative help label that doesn't hotkey the H of the Help menu + mxHelpButton->set_label(mxAltHelpLabel->get_label()); + mxHelpButton->connect_clicked(LINK(this, BackingWindow, ClickHelpHdl)); + + mxDropTarget = mxAllRecentThumbnails->GetDropTarget(); + + try + { + mxContext.set( ::comphelper::getProcessComponentContext(), uno::UNO_SET_THROW ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "fwk", "BackingWindow" ); + } + + SetStyle( GetStyle() | WB_DIALOGCONTROL ); + + // get dispatch provider + Reference<XDesktop2> xDesktop = Desktop::create( comphelper::getProcessComponentContext() ); + mxDesktopDispatchProvider = xDesktop; + +} + +IMPL_LINK(BackingWindow, ClickHelpHdl, weld::Button&, rButton, void) +{ + if (Help* pHelp = Application::GetHelp()) + pHelp->Start(OUString::fromUtf8(m_xContainer->get_help_id()), &rButton); +} + +BackingWindow::~BackingWindow() +{ + disposeOnce(); +} + +void BackingWindow::dispose() +{ + // deregister drag&drop helper + if (mxDropTargetListener.is()) + { + if (mxDropTarget.is()) + { + mxDropTarget->removeDropTargetListener(mxDropTargetListener); + mxDropTarget->setActive(false); + } + mxDropTargetListener.clear(); + } + mxDropTarget.clear(); + mxOpenButton.reset(); + mxRemoteButton.reset(); + mxRecentButton.reset(); + mxTemplateButton.reset(); + mxCreateLabel.reset(); + mxAltHelpLabel.reset(); + mxWriterAllButton.reset(); + mxCalcAllButton.reset(); + mxImpressAllButton.reset(); + mxDrawAllButton.reset(); + mxDBAllButton.reset(); + mxMathAllButton.reset(); + mxBrandImageWeld.reset(); + mxBrandImage.reset(); + mxHelpButton.reset(); + mxExtensionsButton.reset(); + mxAllButtonsBox.reset(); + mxButtonsBox.reset(); + mxSmallButtonsBox.reset(); + mxAllRecentThumbnailsWin.reset(); + mxAllRecentThumbnails.reset(); + mxLocalViewWin.reset(); + mxLocalView.reset(); + InterimItemWindow::dispose(); +} + +void BackingWindow::initControls() +{ + if( mbInitControls ) + return; + + mbInitControls = true; + + // collect the URLs of the entries in the File/New menu + SvtModuleOptions aModuleOptions; + + if (aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::WRITER)) + mxAllRecentThumbnails->mnFileTypes |= sfx2::ApplicationType::TYPE_WRITER; + + if (aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::CALC)) + mxAllRecentThumbnails->mnFileTypes |= sfx2::ApplicationType::TYPE_CALC; + + if (aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::IMPRESS)) + mxAllRecentThumbnails->mnFileTypes |= sfx2::ApplicationType::TYPE_IMPRESS; + + if (aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::DRAW)) + mxAllRecentThumbnails->mnFileTypes |= sfx2::ApplicationType::TYPE_DRAW; + + if (aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::DATABASE)) + mxAllRecentThumbnails->mnFileTypes |= sfx2::ApplicationType::TYPE_DATABASE; + + if (aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::MATH)) + mxAllRecentThumbnails->mnFileTypes |= sfx2::ApplicationType::TYPE_MATH; + + mxAllRecentThumbnails->mnFileTypes |= sfx2::ApplicationType::TYPE_OTHER; + mxAllRecentThumbnails->Reload(); + mxAllRecentThumbnails->ShowTooltips( true ); + mxRecentButton->set_active(true); + mxRecentButton->grab_focus(); + + //initialize Template view + mxLocalView->Hide(); + + //set handlers + mxLocalView->setCreateContextMenuHdl(LINK(this, BackingWindow, CreateContextMenuHdl)); + mxLocalView->setOpenTemplateHdl(LINK(this, BackingWindow, OpenTemplateHdl)); + mxLocalView->setEditTemplateHdl(LINK(this, BackingWindow, EditTemplateHdl)); + mxLocalView->ShowTooltips( true ); + + checkInstalledModules(); + + mxExtensionsButton->connect_clicked(LINK(this, BackingWindow, ExtLinkClickHdl)); + + mxOpenButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxRemoteButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxRecentButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxTemplateButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxWriterAllButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxDrawAllButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxCalcAllButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxDBAllButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxImpressAllButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + mxMathAllButton->connect_clicked(LINK(this, BackingWindow, ClickHdl)); + + mxRecentButton->connect_selected(LINK(this, BackingWindow, MenuSelectHdl)); + mxTemplateButton->connect_selected(LINK(this, BackingWindow, MenuSelectHdl)); + + ApplyStyleSettings(); +} + +void BackingWindow::DataChanged(const DataChangedEvent& rDCEvt) +{ + if ((rDCEvt.GetType() != DataChangedEventType::SETTINGS) + || !(rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) + { + InterimItemWindow::DataChanged(rDCEvt); + return; + } + + ApplyStyleSettings(); + Invalidate(); +} + +template <typename WidgetClass> +void BackingWindow::setLargerFont(WidgetClass& pWidget, const vcl::Font& rFont) +{ + vcl::Font aFont(rFont); + aFont.SetFontSize(Size(0, aFont.GetFontSize().Height() * g_fMultiplier)); + pWidget->set_font(aFont); +} + +void BackingWindow::ApplyStyleSettings() +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + const Color aButtonsBackground(rStyleSettings.GetWindowColor()); + const vcl::Font& aButtonFont(rStyleSettings.GetPushButtonFont()); + const vcl::Font& aLabelFont(rStyleSettings.GetLabelFont()); + + // setup larger fonts + setLargerFont(mxOpenButton, aButtonFont); + setLargerFont(mxOpenButton, aButtonFont); + setLargerFont(mxRemoteButton, aButtonFont); + setLargerFont(mxRecentButton, aButtonFont); + setLargerFont(mxTemplateButton, aButtonFont); + setLargerFont(mxWriterAllButton, aButtonFont); + setLargerFont(mxDrawAllButton, aButtonFont); + setLargerFont(mxCalcAllButton, aButtonFont); + setLargerFont(mxDBAllButton, aButtonFont); + setLargerFont(mxImpressAllButton, aButtonFont); + setLargerFont(mxMathAllButton, aButtonFont); + setLargerFont(mxCreateLabel, aLabelFont); + + mxAllButtonsBox->set_background(aButtonsBackground); + mxSmallButtonsBox->set_background(aButtonsBackground); + + // compute the menubar height + sal_Int32 nMenuHeight = 0; + if (SystemWindow* pSystemWindow = GetSystemWindow()) + nMenuHeight = pSystemWindow->GetMenuBarHeight(); + + // fdo#34392: we do the layout dynamically, the layout depends on the font, + // so we should handle data changed events (font changing) of the last child + // control, at this point all the controls have updated settings (i.e. font). + Size aPrefSize(mxAllButtonsBox->get_preferred_size()); + set_width_request(aPrefSize.Width()); + + // Now set a brand image wide enough to fill this width + weld::DrawingArea* pDrawingArea = mxBrandImage->GetDrawingArea(); + mxBrandImage->ConfigureForWidth(aPrefSize.Width() - + (pDrawingArea->get_margin_start() + pDrawingArea->get_margin_end())); + // Refetch because the brand image height to match this width is now set + aPrefSize = mxAllButtonsBox->get_preferred_size(); + + set_height_request(nMenuHeight + aPrefSize.Height() + mxBrandImage->getSize().getHeight()); +} + +void BackingWindow::initializeLocalView() +{ + if (!mbLocalViewInitialized) + { + mbLocalViewInitialized = true; + mxLocalView->Populate(); + mxLocalView->filterItems(ViewFilter_Application(FILTER_APPLICATION::NONE)); + mxLocalView->showAllTemplates(); + } +} + +void BackingWindow::checkInstalledModules() +{ + SvtModuleOptions aModuleOpt; + + mxWriterAllButton->set_sensitive( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER )); + mxCalcAllButton->set_sensitive( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) ); + mxImpressAllButton->set_sensitive( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) ); + mxDrawAllButton->set_sensitive( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) ); + mxMathAllButton->set_sensitive(aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::MATH )); + mxDBAllButton->set_sensitive(aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE )); +} + +bool BackingWindow::PreNotify(NotifyEvent& rNEvt) +{ + if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + const KeyEvent* pEvt = rNEvt.GetKeyEvent(); + const vcl::KeyCode& rKeyCode(pEvt->GetKeyCode()); + + bool bThumbnailHasFocus = mxAllRecentThumbnails->HasFocus() || mxLocalView->HasFocus(); + + // Subwindows of BackingWindow: Sidebar and Thumbnail view + if( rKeyCode.GetCode() == KEY_F6 ) + { + if( rKeyCode.IsShift() ) // Shift + F6 + { + if (bThumbnailHasFocus) + { + mxOpenButton->grab_focus(); + return true; + } + } + else if ( rKeyCode.IsMod1() ) // Ctrl + F6 + { + if(mxAllRecentThumbnails->IsVisible()) + { + mxAllRecentThumbnails->GrabFocus(); + return true; + } + else if(mxLocalView->IsVisible()) + { + mxLocalView->GrabFocus(); + return true; + } + } + else // F6 + { + if (!bThumbnailHasFocus) + { + if(mxAllRecentThumbnails->IsVisible()) + { + mxAllRecentThumbnails->GrabFocus(); + return true; + } + else if(mxLocalView->IsVisible()) + { + mxLocalView->GrabFocus(); + return true; + } + } + } + } + + // try the 'normal' accelerators (so that eg. Ctrl+Q works) + if (!mpAccExec) + { + mpAccExec = svt::AcceleratorExecute::createAcceleratorHelper(); + mpAccExec->init( comphelper::getProcessComponentContext(), mxFrame); + } + + const OUString aCommand = mpAccExec->findCommand(svt::AcceleratorExecute::st_VCLKey2AWTKey(rKeyCode)); + if ((aCommand != "vnd.sun.star.findbar:FocusToFindbar") && pEvt && mpAccExec->execute(rKeyCode)) + return true; + } + return InterimItemWindow::PreNotify( rNEvt ); +} + +void BackingWindow::GetFocus() +{ + GetFocusFlags nFlags = GetParent()->GetGetFocusFlags(); + if( nFlags & GetFocusFlags::F6 ) + { + if( nFlags & GetFocusFlags::Forward ) // F6 + { + mxOpenButton->grab_focus(); + return; + } + else // Shift + F6 or Ctrl + F6 + { + if(mxAllRecentThumbnails->IsVisible()) + mxAllRecentThumbnails->GrabFocus(); + else if(mxLocalView->IsVisible()) + mxLocalView->GrabFocus(); + return; + } + } + InterimItemWindow::GetFocus(); +} + +void BackingWindow::setOwningFrame( const css::uno::Reference< css::frame::XFrame >& xFrame ) +{ + mxFrame = xFrame; + if( ! mbInitControls ) + initControls(); + + // establish drag&drop mode + mxDropTargetListener.set(new OpenFileDropTargetListener(mxContext, mxFrame)); + + if (mxDropTarget.is()) + { + mxDropTarget->addDropTargetListener(mxDropTargetListener); + mxDropTarget->setActive(true); + } + + css::uno::Reference<XFramesSupplier> xFramesSupplier(mxDesktopDispatchProvider, UNO_QUERY); + if (xFramesSupplier) + xFramesSupplier->setActiveFrame(mxFrame); +} + +IMPL_LINK(BackingWindow, ExtLinkClickHdl, weld::Button&, rButton, void) +{ + OUString aNode; + + if (&rButton == mxExtensionsButton.get()) + aNode = "AddFeatureURL"; + + if (aNode.isEmpty()) + return; + + try + { + uno::Sequence<uno::Any> args(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.Common/Help/StartCenter"))} + })); + + Reference<lang::XMultiServiceFactory> xConfig = configuration::theDefaultProvider::get( comphelper::getProcessComponentContext() ); + Reference<container::XNameAccess> xNameAccess(xConfig->createInstanceWithArguments(SERVICENAME_CFGREADACCESS, args), UNO_QUERY); + if (xNameAccess.is()) + { + OUString sURL; + Any value(xNameAccess->getByName(aNode)); + + sURL = value.get<OUString>(); + localizeWebserviceURI(sURL); + + Reference<css::system::XSystemShellExecute> const + xSystemShellExecute( + css::system::SystemShellExecute::create( + ::comphelper::getProcessComponentContext())); + xSystemShellExecute->execute(sURL, OUString(), + css::system::SystemShellExecuteFlags::URIS_ONLY); + } + } + catch (const Exception&) + { + } +} + +IMPL_LINK( BackingWindow, ClickHdl, weld::Button&, rButton, void ) +{ + // dispatch the appropriate URL and end the dialog + if( &rButton == mxWriterAllButton.get() ) + dispatchURL( "private:factory/swriter" ); + else if( &rButton == mxCalcAllButton.get() ) + dispatchURL( "private:factory/scalc" ); + else if( &rButton == mxImpressAllButton.get() ) + dispatchURL( "private:factory/simpress?slot=6686" ); + else if( &rButton == mxDrawAllButton.get() ) + dispatchURL( "private:factory/sdraw" ); + else if( &rButton == mxDBAllButton.get() ) + dispatchURL( "private:factory/sdatabase?Interactive" ); + else if( &rButton == mxMathAllButton.get() ) + dispatchURL( "private:factory/smath" ); + else if( &rButton == mxOpenButton.get() ) + { + Reference< XDispatchProvider > xFrame( mxFrame, UNO_QUERY ); + + dispatchURL( ".uno:Open", OUString(), xFrame, { comphelper::makePropertyValue("Referer", OUString("private:user")) } ); + } + else if( &rButton == mxRemoteButton.get() ) + { + Reference< XDispatchProvider > xFrame( mxFrame, UNO_QUERY ); + + dispatchURL( ".uno:OpenRemote", OUString(), xFrame, {} ); + } + else if( &rButton == mxRecentButton.get() ) + { + mxLocalView->Hide(); + mxAllRecentThumbnails->Show(); + mxAllRecentThumbnails->GrabFocus(); + mxRecentButton->set_active(true); + mxTemplateButton->set_active(false); + } + else if( &rButton == mxTemplateButton.get() ) + { + mxAllRecentThumbnails->Hide(); + initializeLocalView(); + mxLocalView->filterItems(ViewFilter_Application(FILTER_APPLICATION::NONE)); + mxLocalView->Show(); + mxLocalView->reload(); + mxLocalView->GrabFocus(); + mxRecentButton->set_active(false); + mxTemplateButton->set_active(true); + } +} + +IMPL_LINK (BackingWindow, MenuSelectHdl, const OString&, rId, void) +{ + if (rId == "clear_all") + { + SvtHistoryOptions::Clear(EHistoryType::PickList); + mxAllRecentThumbnails->Reload(); + return; + } + else if (!rId.isEmpty()) + { + initializeLocalView(); + + if( rId == "filter_writer" ) + { + mxLocalView->filterItems(ViewFilter_Application(FILTER_APPLICATION::WRITER)); + } + else if( rId == "filter_calc" ) + { + mxLocalView->filterItems(ViewFilter_Application(FILTER_APPLICATION::CALC)); + } + else if( rId == "filter_impress" ) + { + mxLocalView->filterItems(ViewFilter_Application(FILTER_APPLICATION::IMPRESS)); + } + else if( rId == "filter_draw" ) + { + mxLocalView->filterItems(ViewFilter_Application(FILTER_APPLICATION::DRAW)); + } + else if( rId == "manage" ) + { + Reference< XDispatchProvider > xFrame( mxFrame, UNO_QUERY ); + + dispatchURL( ".uno:NewDoc", OUString(), xFrame, { comphelper::makePropertyValue("Referer", OUString("private:user")) } ); + return; + } + + mxAllRecentThumbnails->Hide(); + mxLocalView->Show(); + mxLocalView->reload(); + mxLocalView->GrabFocus(); + mxRecentButton->set_active(false); + mxTemplateButton->set_active(true); + } +} + +IMPL_LINK(BackingWindow, CreateContextMenuHdl, ThumbnailViewItem*, pItem, void) +{ + const TemplateViewItem *pViewItem = dynamic_cast<TemplateViewItem*>(pItem); + + if (pViewItem) + mxLocalView->createContextMenu(); +} + +IMPL_LINK(BackingWindow, OpenTemplateHdl, ThumbnailViewItem*, pItem, void) +{ + uno::Sequence< PropertyValue > aArgs{ + comphelper::makePropertyValue("AsTemplate", true), + comphelper::makePropertyValue("MacroExecutionMode", MacroExecMode::USE_CONFIG), + comphelper::makePropertyValue("UpdateDocMode", UpdateDocMode::ACCORDING_TO_CONFIG), + comphelper::makePropertyValue("InteractionHandler", task::InteractionHandler::createWithParent( ::comphelper::getProcessComponentContext(), nullptr )) + }; + + TemplateViewItem *pTemplateItem = static_cast<TemplateViewItem*>(pItem); + + Reference< XDispatchProvider > xFrame( mxFrame, UNO_QUERY ); + + try + { + dispatchURL( pTemplateItem->getPath(), "_default", xFrame, aArgs ); + } + catch( const uno::Exception& ) + { + } +} + +IMPL_LINK(BackingWindow, EditTemplateHdl, ThumbnailViewItem*, pItem, void) +{ + uno::Sequence< PropertyValue > aArgs{ + comphelper::makePropertyValue("AsTemplate", false), + comphelper::makePropertyValue("MacroExecutionMode", MacroExecMode::USE_CONFIG), + comphelper::makePropertyValue("UpdateDocMode", UpdateDocMode::ACCORDING_TO_CONFIG), + }; + + TemplateViewItem *pViewItem = static_cast<TemplateViewItem*>(pItem); + + Reference< XDispatchProvider > xFrame( mxFrame, UNO_QUERY ); + + try + { + dispatchURL( pViewItem->getPath(), "_default", xFrame, aArgs ); + } + catch( const uno::Exception& ) + { + } +} + +namespace { + +struct ImplDelayedDispatch +{ + Reference< XDispatch > xDispatch; + css::util::URL aDispatchURL; + Sequence< PropertyValue > aArgs; + + ImplDelayedDispatch( const Reference< XDispatch >& i_xDispatch, + const css::util::URL& i_rURL, + const Sequence< PropertyValue >& i_rArgs ) + : xDispatch( i_xDispatch ), + aDispatchURL( i_rURL ), + aArgs( i_rArgs ) + { + } +}; + +} + +static void implDispatchDelayed( void*, void* pArg ) +{ + struct ImplDelayedDispatch* pDispatch = static_cast<ImplDelayedDispatch*>(pArg); + try + { + pDispatch->xDispatch->dispatch( pDispatch->aDispatchURL, pDispatch->aArgs ); + } + catch (const Exception&) + { + } + + // clean up + delete pDispatch; +} + +void BackingWindow::dispatchURL( const OUString& i_rURL, + const OUString& rTarget, + const Reference< XDispatchProvider >& i_xProv, + const Sequence< PropertyValue >& i_rArgs ) +{ + // if no special dispatch provider is given, get the desktop + Reference< XDispatchProvider > xProvider( i_xProv.is() ? i_xProv : mxDesktopDispatchProvider ); + + // check for dispatch provider + if( !xProvider.is()) + return; + + // get a URL transformer to clean up the URL + css::util::URL aDispatchURL; + aDispatchURL.Complete = i_rURL; + + Reference < css::util::XURLTransformer > xURLTransformer( + css::util::URLTransformer::create( comphelper::getProcessComponentContext() ) ); + try + { + // clean up the URL + xURLTransformer->parseStrict( aDispatchURL ); + // get a Dispatch for the URL and target + Reference< XDispatch > xDispatch( + xProvider->queryDispatch( aDispatchURL, rTarget, 0 ) + ); + // dispatch the URL + if ( xDispatch.is() ) + { + std::unique_ptr<ImplDelayedDispatch> pDisp(new ImplDelayedDispatch( xDispatch, aDispatchURL, i_rArgs )); + if( Application::PostUserEvent( Link<void*,void>( nullptr, implDispatchDelayed ), pDisp.get() ) ) + pDisp.release(); + } + } + catch (const css::uno::RuntimeException&) + { + throw; + } + catch (const css::uno::Exception&) + { + } +} + +void BackingWindow::clearRecentFileList() +{ + mxAllRecentThumbnails->Clear(); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab:*/ diff --git a/sfx2/source/dialog/backingwindow.hxx b/sfx2/source/dialog/backingwindow.hxx new file mode 100644 index 000000000..358055c66 --- /dev/null +++ b/sfx2/source/dialog/backingwindow.hxx @@ -0,0 +1,125 @@ +/* -*- 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_DIALOG_BACKINGWINDOW_HXX +#define INCLUDED_SFX2_SOURCE_DIALOG_BACKINGWINDOW_HXX + +#include <rtl/ustring.hxx> + +#include <vcl/InterimItemWindow.hxx> + +#include <recentdocsview.hxx> +#include <templatedefaultview.hxx> + +#include <svtools/acceleratorexecute.hxx> + +#include <com/sun/star/datatransfer/dnd/XDropTargetListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> + +#include <memory> + +class BrandImage; + +class BackingWindow : public InterimItemWindow +{ + css::uno::Reference<css::uno::XComponentContext> mxContext; + css::uno::Reference<css::frame::XDispatchProvider> mxDesktopDispatchProvider; + css::uno::Reference<css::frame::XFrame> mxFrame; + + /** helper for drag&drop. */ + css::uno::Reference<css::datatransfer::dnd::XDropTargetListener> mxDropTargetListener; + + std::unique_ptr<weld::Button> mxOpenButton; + std::unique_ptr<weld::MenuToggleButton> mxRecentButton; + std::unique_ptr<weld::Button> mxRemoteButton; + std::unique_ptr<weld::MenuToggleButton> mxTemplateButton; + + std::unique_ptr<weld::Label> mxCreateLabel; + std::unique_ptr<weld::Label> mxAltHelpLabel; + + std::unique_ptr<weld::Button> mxWriterAllButton; + std::unique_ptr<weld::Button> mxCalcAllButton; + std::unique_ptr<weld::Button> mxImpressAllButton; + std::unique_ptr<weld::Button> mxDrawAllButton; + std::unique_ptr<weld::Button> mxDBAllButton; + std::unique_ptr<weld::Button> mxMathAllButton; + std::unique_ptr<BrandImage> mxBrandImage; + std::unique_ptr<weld::CustomWeld> mxBrandImageWeld; + + std::unique_ptr<weld::Button> mxHelpButton; + std::unique_ptr<weld::Button> mxExtensionsButton; + + std::unique_ptr<weld::Container> mxAllButtonsBox; + std::unique_ptr<weld::Container> mxButtonsBox; + std::unique_ptr<weld::Container> mxSmallButtonsBox; + + std::unique_ptr<sfx2::RecentDocsView> mxAllRecentThumbnails; + std::unique_ptr<weld::CustomWeld> mxAllRecentThumbnailsWin; + std::unique_ptr<TemplateDefaultView> mxLocalView; + std::unique_ptr<weld::CustomWeld> mxLocalViewWin; + bool mbLocalViewInitialized; + + css::uno::Reference<css::datatransfer::dnd::XDropTarget> mxDropTarget; + + bool mbInitControls; + std::unique_ptr<svt::AcceleratorExecute> mpAccExec; + + void dispatchURL(const OUString& i_rURL, const OUString& i_rTarget = OUString("_default"), + const css::uno::Reference<css::frame::XDispatchProvider>& i_xProv + = css::uno::Reference<css::frame::XDispatchProvider>(), + const css::uno::Sequence<css::beans::PropertyValue>& = css::uno::Sequence< + css::beans::PropertyValue>()); + + DECL_LINK(ClickHdl, weld::Button&, void); + DECL_LINK(ClickHelpHdl, weld::Button&, void); + DECL_LINK(MenuSelectHdl, const OString&, void); + DECL_LINK(ExtLinkClickHdl, weld::Button&, void); + DECL_LINK(CreateContextMenuHdl, ThumbnailViewItem*, void); + DECL_LINK(OpenTemplateHdl, ThumbnailViewItem*, void); + DECL_LINK(EditTemplateHdl, ThumbnailViewItem*, void); + + void initControls(); + + void initializeLocalView(); + + void checkInstalledModules(); + + void DataChanged(const DataChangedEvent&) override; + + template <typename WidgetClass> void setLargerFont(WidgetClass&, const vcl::Font&); + void ApplyStyleSettings(); + +public: + explicit BackingWindow(vcl::Window* pParent); + virtual ~BackingWindow() override; + virtual void dispose() override; + + virtual bool PreNotify(NotifyEvent& rNEvt) override; + virtual void GetFocus() override; + + void setOwningFrame(const css::uno::Reference<css::frame::XFrame>& xFrame); + + void clearRecentFileList(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/basedlgs.cxx b/sfx2/source/dialog/basedlgs.cxx new file mode 100644 index 000000000..0604f035b --- /dev/null +++ b/sfx2/source/dialog/basedlgs.cxx @@ -0,0 +1,329 @@ +/* -*- 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 <vcl/help.hxx> +#include <svl/eitem.hxx> +#include <unotools/viewoptions.hxx> +#include <vcl/idle.hxx> + +#include <sfx2/basedlgs.hxx> +#include <sfx2/tabdlg.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/viewsh.hxx> +#include <workwin.hxx> +#include <comphelper/lok.hxx> + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral USERITEM_NAME = u"UserItem"; + +class SfxModelessDialog_Impl : public SfxListener +{ +public: + OString aWinState; + SfxChildWindow* pMgr; + bool bClosing; + void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + + Idle aMoveIdle { "SfxModelessDialog_Impl aMoveIdle" }; +}; + +void SfxModelessDialog_Impl::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + if (pMgr && rHint.GetId() == SfxHintId::Dying) { + pMgr->Destroy(); + } +} + +void SfxModelessDialogController::Initialize(SfxChildWinInfo const *pInfo) + +/* [Description] + + Initialization of the class SfxModelessDialog via a SfxChildWinInfo. + The initialization is done only in a 2nd step after the constructor, this + constructor should be called from the derived class or from the + SfxChildWindows. +*/ + +{ + if (!pInfo) + return; + m_xImpl->aWinState = pInfo->aWinState; + if (m_xImpl->aWinState.isEmpty()) + return; + m_xDialog->set_window_state(m_xImpl->aWinState); +} + +SfxModelessDialogController::SfxModelessDialogController(SfxBindings* pBindinx, + SfxChildWindow *pCW, weld::Window *pParent, const OUString& rUIXMLDescription, + const OString& rID) + : SfxDialogController(pParent, rUIXMLDescription, rID) +{ + Init(pBindinx, pCW); +} + +/* [Description] + + Fills a SfxChildWinInfo with specific data from SfxModelessDialog, + so that it can be written in the INI file. It is assumed that rinfo + receives all other possible relevant data in the ChildWindow class. + ModelessDialogs have no specific information, so that the base + implementation does nothing and therefore must not be called. +*/ +void SfxModelessDialogController::FillInfo(SfxChildWinInfo& rInfo) const +{ + rInfo.aSize = m_xDialog->get_size(); +} + +void SfxModelessDialogController::Init(SfxBindings *pBindinx, SfxChildWindow *pCW) +{ + m_pBindings = pBindinx; + m_xImpl.reset(new SfxModelessDialog_Impl); + m_xImpl->pMgr = pCW; + m_xImpl->bClosing = false; + if (pBindinx) + m_xImpl->StartListening( *pBindinx ); +} + +/* [Description] + + If a ModelessDialog is enabled its ViewFrame will be activated. + This is necessary by PluginInFrames. +*/ +IMPL_LINK_NOARG(SfxDialogController, FocusChangeHdl, weld::Container&, void) +{ + if (m_xDialog->has_toplevel_focus()) + Activate(); + else + Deactivate(); +} + +void SfxModelessDialogController::Activate() +{ + if (!m_xImpl || !m_xImpl->pMgr) + return; + m_pBindings->SetActiveFrame(m_xImpl->pMgr->GetFrame()); + m_xImpl->pMgr->Activate_Impl(); +} + +void SfxModelessDialogController::Deactivate() +{ + if (!m_xImpl) + return; + m_pBindings->SetActiveFrame(css::uno::Reference< css::frame::XFrame>()); +} + +SfxModelessDialogController::~SfxModelessDialogController() +{ + if (!m_xImpl->pMgr) + return; + auto xFrame = m_xImpl->pMgr->GetFrame(); + if (!xFrame) + return; + if (xFrame == m_pBindings->GetActiveFrame()) + m_pBindings->SetActiveFrame(nullptr); +} + +void SfxDialogController::EndDialog(int nResponse) +{ + if (!m_xDialog->get_visible()) + return; + response(nResponse); +} + +bool SfxModelessDialogController::IsClosing() const +{ + return m_xImpl->bClosing; +} + +void SfxModelessDialogController::EndDialog(int nResponse) +{ + if (m_xImpl->bClosing) + return; + // In the case of async dialogs, the call to SfxDialogController::EndDialog + // may delete this object, so keep myself alive for the duration of this + // stack frame. + auto aHoldSelf = shared_from_this(); + m_xImpl->bClosing = true; + SfxDialogController::EndDialog(nResponse); + if (!m_xImpl) + return; + m_xImpl->bClosing = false; +} + +void SfxModelessDialogController::ChildWinDispose() +{ + if (m_xImpl->pMgr) + { + WindowStateMask nMask = WindowStateMask::Pos | WindowStateMask::State; + if (m_xDialog->get_resizable()) + nMask |= WindowStateMask::Size; + m_xImpl->aWinState = m_xDialog->get_window_state(nMask); + GetBindings().GetWorkWindow_Impl()->ConfigChild_Impl( SfxChildIdentifier::DOCKINGWINDOW, SfxDockingConfig::ALIGNDOCKINGWINDOW, m_xImpl->pMgr->GetType() ); + } + + m_xImpl->pMgr = nullptr; +} + +/* [Description] + + The window is closed when the ChildWindow is destroyed by running the + ChildWindow-slots. +*/ +void SfxModelessDialogController::Close() +{ + if (m_xImpl->bClosing) + return; + // Execute with Parameters, since Toggle is ignored by some ChildWindows. + SfxBoolItem aValue(m_xImpl->pMgr->GetType(), false); + m_pBindings->GetDispatcher_Impl()->ExecuteList( + m_xImpl->pMgr->GetType(), + SfxCallMode::RECORD|SfxCallMode::SYNCHRON, { &aValue } ); + SfxDialogController::Close(); +} + +SfxDialogController::SfxDialogController(weld::Widget* pParent, const OUString& rUIFile, + const OString& rDialogId) + : GenericDialogController(pParent, rUIFile, rDialogId, + comphelper::LibreOfficeKit::isActive() + && SfxViewShell::Current() + && SfxViewShell::Current()->isLOKMobilePhone()) +{ + m_xDialog->SetInstallLOKNotifierHdl(LINK(this, SfxDialogController, InstallLOKNotifierHdl)); + m_xDialog->connect_container_focus_changed(LINK(this, SfxDialogController, FocusChangeHdl)); +} + +void SfxDialogController::Close() +{ + // tdf3146571 ignore focus changes after we've closed + m_xDialog->connect_container_focus_changed(Link<weld::Container&, void>()); +} + +IMPL_STATIC_LINK_NOARG(SfxDialogController, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*) +{ + return SfxViewShell::Current(); +} + +SfxSingleTabDialogController::SfxSingleTabDialogController(weld::Widget *pParent, const SfxItemSet* pSet, + const OUString& rUIXMLDescription, const OString& rID) + : SfxOkDialogController(pParent, rUIXMLDescription, rID) + , m_pInputSet(pSet) + , m_xContainer(m_xDialog->weld_content_area()) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + , m_xHelpBtn(m_xBuilder->weld_button("help")) +{ + m_xOKBtn->connect_clicked(LINK(this, SfxSingleTabDialogController, OKHdl_Impl)); +} + +SfxSingleTabDialogController::~SfxSingleTabDialogController() +{ +} + +/* [Description] + + Insert a (new) TabPage; an existing page is deleted. + The passed on page is initialized with the initially given Itemset + through calling Reset(). +*/ +void SfxSingleTabDialogController::SetTabPage(std::unique_ptr<SfxTabPage> xTabPage) +{ + m_xSfxPage = std::move(xTabPage); + if (!m_xSfxPage) + return; + + // First obtain the user data, only then Reset() + OUString sConfigId = OStringToOUString(m_xSfxPage->GetConfigId(), RTL_TEXTENCODING_UTF8); + SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId); + Any aUserItem = aPageOpt.GetUserItem( USERITEM_NAME ); + OUString sUserData; + aUserItem >>= sUserData; + m_xSfxPage->SetUserData(sUserData); + m_xSfxPage->Reset(GetInputItemSet()); + + m_xHelpBtn->set_visible(Help::IsContextHelpEnabled()); + + // Set TabPage text in the Dialog if there is any + OUString sTitle(m_xSfxPage->GetPageTitle()); + if (!sTitle.isEmpty()) + m_xDialog->set_title(sTitle); + + // Dialog receives the HelpId of TabPage if there is any + OString sHelpId(m_xSfxPage->GetHelpId()); + if (!sHelpId.isEmpty()) + m_xDialog->set_help_id(sHelpId); +} + +/* [Description] + + Ok_Handler; FillItemSet() is called for setting of Page. +*/ +IMPL_LINK_NOARG(SfxSingleTabDialogController, OKHdl_Impl, weld::Button&, void) +{ + const SfxItemSet* pInputSet = GetInputItemSet(); + if (!pInputSet) + { + // TabPage without ItemSet + m_xDialog->response(RET_OK); + return; + } + + if (!GetOutputItemSet()) + { + CreateOutputItemSet(*pInputSet); + } + + bool bModified = false; + + if (m_xSfxPage->HasExchangeSupport()) + { + DeactivateRC nRet = m_xSfxPage->DeactivatePage(m_xOutputSet.get()); + if (nRet != DeactivateRC::LeavePage) + return; + else + bModified = m_xOutputSet->Count() > 0; + } + else + bModified = m_xSfxPage->FillItemSet(m_xOutputSet.get()); + + if (bModified) + { + // Save user data in IniManager. + m_xSfxPage->FillUserData(); + OUString sData(m_xSfxPage->GetUserData()); + + OUString sConfigId = OStringToOUString(m_xSfxPage->GetConfigId(), + RTL_TEXTENCODING_UTF8); + SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId); + aPageOpt.SetUserItem( USERITEM_NAME, Any( sData ) ); + m_xDialog->response(RET_OK); + } + else + m_xDialog->response(RET_CANCEL); +} + +void SfxSingleTabDialogController::CreateOutputItemSet(const SfxItemSet& rSet) +{ + assert(!m_xOutputSet && "Double creation of OutputSet!"); + m_xOutputSet.reset(new SfxItemSet(rSet)); + m_xOutputSet->ClearItem(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/bluthsnd.cxx b/sfx2/source/dialog/bluthsnd.cxx new file mode 100644 index 000000000..0083f0749 --- /dev/null +++ b/sfx2/source/dialog/bluthsnd.cxx @@ -0,0 +1,50 @@ +/* -*- 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 <com/sun/star/frame/XFrame.hpp> + +#include <stdio.h> + +#include <bluthsndapi.hxx> + +SfxBluetoothModel::SendMailResult SfxBluetoothModel::SaveAndSend( const css::uno::Reference< css::frame::XFrame >& xFrame ) +{ + SaveResult eSaveResult; + SendMailResult eResult = SEND_MAIL_ERROR; + OUString aFileName; + + eSaveResult = SaveDocumentAsFormat( OUString(), xFrame, OUString(), aFileName ); + if( eSaveResult == SAVE_SUCCESSFUL ) + { + maAttachedDocuments.push_back( aFileName ); + return Send(); + } + else if( eSaveResult == SAVE_CANCELLED ) + eResult = SEND_MAIL_CANCELLED; + + return eResult; +} + +SfxBluetoothModel::SendMailResult SfxBluetoothModel::Send() +{ +#ifndef LINUX + (void) this; // avoid loplugin:staticmethods + return SEND_MAIL_ERROR; +#else + char bthsend[300]; + SendMailResult eResult = SEND_MAIL_OK; + OUString aFileName = maAttachedDocuments[0]; + snprintf(bthsend,300,"bluetooth-sendto %s",OUStringToOString( aFileName, RTL_TEXTENCODING_UTF8).getStr() ); + if( !system( bthsend ) ) + eResult = SEND_MAIL_ERROR; + return eResult; +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/charmappopup.cxx b/sfx2/source/dialog/charmappopup.cxx new file mode 100644 index 000000000..93cfa1adb --- /dev/null +++ b/sfx2/source/dialog/charmappopup.cxx @@ -0,0 +1,73 @@ +/* -*- 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 <charmappopup.hxx> +#include <charmapcontrol.hxx> +#include <vcl/toolbox.hxx> + +CharmapPopup::CharmapPopup(const css::uno::Reference<css::uno::XComponentContext>& rContext) + : PopupWindowController(rContext, nullptr, OUString()) +{ +} + +CharmapPopup::~CharmapPopup() {} + +void CharmapPopup::initialize(const css::uno::Sequence<css::uno::Any>& rArguments) +{ + PopupWindowController::initialize(rArguments); + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if (getToolboxId(nId, &pToolBox)) + pToolBox->SetItemBits(nId, ToolBoxItemBits::DROPDOWNONLY | pToolBox->GetItemBits(nId)); +} + +std::unique_ptr<WeldToolbarPopup> CharmapPopup::weldPopupWindow() +{ + return std::make_unique<SfxCharmapCtrl>(this, m_pToolbar); +} + +VclPtr<vcl::Window> CharmapPopup::createVclPopupWindow(vcl::Window* pParent) +{ + mxInterimPopover = VclPtr<InterimToolbarPopup>::Create( + getFrameInterface(), pParent, + std::make_unique<SfxCharmapCtrl>(this, pParent->GetFrameWeld())); + + mxInterimPopover->Show(); + + return mxInterimPopover; +} + +OUString CharmapPopup::getImplementationName() +{ + return "com.sun.star.comp.sfx2.InsertSymbolToolBoxControl"; +} + +css::uno::Sequence<OUString> CharmapPopup::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ToolbarController" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_sfx2_InsertSymbolToolBoxControl_get_implementation( + css::uno::XComponentContext* rContext, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new CharmapPopup(rContext)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/checkin.cxx b/sfx2/source/dialog/checkin.cxx new file mode 100644 index 000000000..6d90be8af --- /dev/null +++ b/sfx2/source/dialog/checkin.cxx @@ -0,0 +1,40 @@ +/* -*- 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 <checkin.hxx> + +SfxCheckinDialog::SfxCheckinDialog(weld::Window* pParent) + : GenericDialogController( pParent, "sfx/ui/checkin.ui", "CheckinDialog") + , m_xCommentED(m_xBuilder->weld_text_view("VersionComment")) + , m_xMajorCB(m_xBuilder->weld_check_button("MajorVersion")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) +{ + m_xOKBtn->connect_clicked(LINK(this, SfxCheckinDialog, OKHdl)); +} + +SfxCheckinDialog::~SfxCheckinDialog() +{ +} + +OUString SfxCheckinDialog::GetComment( ) const +{ + return m_xCommentED->get_text(); +} + +bool SfxCheckinDialog::IsMajor( ) const +{ + return m_xMajorCB->get_active(); +} + +IMPL_LINK_NOARG(SfxCheckinDialog, OKHdl, weld::Button&, void ) +{ + m_xDialog->response(RET_OK); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/dialoghelper.cxx b/sfx2/source/dialog/dialoghelper.cxx new file mode 100644 index 000000000..9585c8baa --- /dev/null +++ b/sfx2/source/dialog/dialoghelper.cxx @@ -0,0 +1,48 @@ +/* -*- 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 <unotools/localedatawrapper.hxx> +#include <sfx2/dialoghelper.hxx> +#include <tools/datetime.hxx> +#include <vcl/outdev.hxx> + +Size getParagraphPreviewOptimalSize(const OutputDevice& rReference) +{ + return rReference.LogicToPixel(Size(68, 112), MapMode(MapUnit::MapAppFont)); +} + +Size getDrawPreviewOptimalSize(const OutputDevice& rReference) +{ + return rReference.LogicToPixel(Size(88, 42), MapMode(MapUnit::MapAppFont)); +} + +Size getPreviewStripSize(const OutputDevice& rReference) +{ + return rReference.LogicToPixel(Size(70, 40), MapMode(MapUnit::MapAppFont)); +} + +Size getPreviewOptionsSize(const OutputDevice& rReference) +{ + return rReference.LogicToPixel(Size(70, 27), MapMode(MapUnit::MapAppFont)); +} + +OUString getWidestDateTime(const LocaleDataWrapper& rWrapper, bool bWithSec) +{ + Date aDate(22, 12, 2000); + tools::Time aTime(22, 59, 59); + DateTime aDateTime(aDate, aTime); + return formatDateTime(aDateTime, rWrapper, bWithSec); +} + +OUString formatDateTime(const DateTime& rDateTime, const LocaleDataWrapper& rWrapper, bool bWithSec) +{ + return rWrapper.getDate(rDateTime) + " " + rWrapper.getTime(rDateTime, bWithSec); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/dinfdlg.cxx b/sfx2/source/dialog/dinfdlg.cxx new file mode 100644 index 000000000..ddc272907 --- /dev/null +++ b/sfx2/source/dialog/dinfdlg.cxx @@ -0,0 +1,2446 @@ +/* -*- 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 <svl/eitem.hxx> +#include <tools/datetime.hxx> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> +#include <unotools/datetime.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/cmdoptions.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/stl_types.hxx> +#include <comphelper/xmlsechelper.hxx> +#include <unotools/useroptions.hxx> +#include <svtools/ctrlbox.hxx> +#include <svtools/imagemgr.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <osl/file.hxx> + +#include <memory> + +#include <comphelper/sequence.hxx> +#include <comphelper/string.hxx> +#include <com/sun/star/security/DocumentSignatureInformation.hpp> +#include <com/sun/star/security/DocumentDigitalSignatures.hpp> +#include <unotools/syslocale.hxx> +#include <rtl/math.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertyContainer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/DateTimeWithTimezone.hpp> +#include <com/sun/star/util/DateWithTimezone.hpp> +#include <com/sun/star/util/Duration.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/document/CmisProperty.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include <vcl/timer.hxx> +#include <vcl/settings.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/dinfdlg.hxx> +#include <sfx2/sfxsids.hrc> +#include <helper.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docfile.hxx> + +#include <documentfontsdialog.hxx> +#include <dinfdlg.hrc> +#include <sfx2/strings.hrc> +#include <strings.hxx> +#include <tools/diagnose_ex.h> +#include "securitypage.hxx" + +#include <algorithm> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +struct CustomProperty +{ + OUString m_sName; + css::uno::Any m_aValue; + + CustomProperty( const OUString& sName, const css::uno::Any& rValue ) : + m_sName( sName ), m_aValue( rValue ) {} + + bool operator==(const CustomProperty& rProp) const + { + return m_sName == rProp.m_sName && m_aValue == rProp.m_aValue; + } +}; + +SfxPoolItem* SfxDocumentInfoItem::CreateDefault() { return new SfxDocumentInfoItem; } + +namespace { + +OUString CreateSizeText( sal_Int64 nSize ) +{ + OUString aUnitStr = " " + SfxResId(STR_BYTES); + sal_Int64 nSize1 = nSize; + sal_Int64 nSize2 = nSize1; + sal_Int64 nMega = 1024 * 1024; + sal_Int64 nGiga = nMega * 1024; + double fSize = nSize; + int nDec = 0; + + if ( nSize1 >= 10000 && nSize1 < nMega ) + { + nSize1 /= 1024; + aUnitStr = " " + SfxResId(STR_KB); + fSize /= 1024; + nDec = 0; + } + else if ( nSize1 >= nMega && nSize1 < nGiga ) + { + nSize1 /= nMega; + aUnitStr = " " + SfxResId(STR_MB); + fSize /= nMega; + nDec = 2; + } + else if ( nSize1 >= nGiga ) + { + nSize1 /= nGiga; + aUnitStr = " " + SfxResId(STR_GB); + fSize /= nGiga; + nDec = 3; + } + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleWrapper = aSysLocale.GetLocaleData(); + OUString aSizeStr = rLocaleWrapper.getNum( nSize1, 0 ) + aUnitStr; + if ( nSize1 < nSize2 ) + { + aSizeStr = ::rtl::math::doubleToUString( fSize, + rtl_math_StringFormat_F, nDec, + rLocaleWrapper.getNumDecimalSep()[0] ) + + aUnitStr + + " (" + + rLocaleWrapper.getNum( nSize2, 0 ) + + " " + + SfxResId(STR_BYTES) + + ")"; + } + return aSizeStr; +} + +OUString ConvertDateTime_Impl( std::u16string_view rName, + const util::DateTime& uDT, const LocaleDataWrapper& rWrapper ) +{ + Date aD(uDT); + tools::Time aT(uDT); + static const OUStringLiteral aDelim( u", " ); + OUString aStr = rWrapper.getDate( aD ) + + aDelim + + rWrapper.getTime( aT ); + std::u16string_view aAuthor = comphelper::string::stripStart(rName, ' '); + if (!aAuthor.empty()) + { + aStr += aDelim + aAuthor; + } + return aStr; +} + +} + + +SfxDocumentInfoItem::SfxDocumentInfoItem() + : m_AutoloadDelay(0) + , m_isAutoloadEnabled(false) + , m_EditingCycles(0) + , m_EditingDuration(0) + , m_bHasTemplate( true ) + , m_bDeleteUserData( false ) + , m_bUseUserData( true ) + , m_bUseThumbnailSave( true ) +{ +} + +SfxDocumentInfoItem::SfxDocumentInfoItem( const OUString& rFile, + const uno::Reference<document::XDocumentProperties>& i_xDocProps, + const uno::Sequence<document::CmisProperty>& i_cmisProps, + bool bIs, bool _bIs ) + : SfxStringItem( SID_DOCINFO, rFile ) + , m_AutoloadDelay( i_xDocProps->getAutoloadSecs() ) + , m_AutoloadURL( i_xDocProps->getAutoloadURL() ) + , m_isAutoloadEnabled( (m_AutoloadDelay > 0) || !m_AutoloadURL.isEmpty() ) + , m_DefaultTarget( i_xDocProps->getDefaultTarget() ) + , m_TemplateName( i_xDocProps->getTemplateName() ) + , m_Author( i_xDocProps->getAuthor() ) + , m_CreationDate( i_xDocProps->getCreationDate() ) + , m_ModifiedBy( i_xDocProps->getModifiedBy() ) + , m_ModificationDate( i_xDocProps->getModificationDate() ) + , m_PrintedBy( i_xDocProps->getPrintedBy() ) + , m_PrintDate( i_xDocProps->getPrintDate() ) + , m_EditingCycles( i_xDocProps->getEditingCycles() ) + , m_EditingDuration( i_xDocProps->getEditingDuration() ) + , m_Description( i_xDocProps->getDescription() ) + , m_Keywords( ::comphelper::string::convertCommaSeparated( + i_xDocProps->getKeywords()) ) + , m_Subject( i_xDocProps->getSubject() ) + , m_Title( i_xDocProps->getTitle() ) + , m_bHasTemplate( true ) + , m_bDeleteUserData( false ) + , m_bUseUserData( bIs ) + , m_bUseThumbnailSave( _bIs ) +{ + try + { + Reference< beans::XPropertyContainer > xContainer = i_xDocProps->getUserDefinedProperties(); + if ( xContainer.is() ) + { + Reference < beans::XPropertySet > xSet( xContainer, UNO_QUERY ); + const Sequence< beans::Property > lProps = xSet->getPropertySetInfo()->getProperties(); + for ( const beans::Property& rProp : lProps ) + { + // "fix" property? => not a custom property => ignore it! + if (!(rProp.Attributes & css::beans::PropertyAttribute::REMOVABLE)) + { + SAL_WARN( "sfx.dialog", "non-removable user-defined property?"); + continue; + } + + uno::Any aValue = xSet->getPropertyValue(rProp.Name); + AddCustomProperty( rProp.Name, aValue ); + } + } + + // get CMIS properties + m_aCmisProperties = i_cmisProps; + } + catch ( Exception& ) {} +} + + +SfxDocumentInfoItem::SfxDocumentInfoItem( const SfxDocumentInfoItem& rItem ) + : SfxStringItem( rItem ) + , m_AutoloadDelay( rItem.getAutoloadDelay() ) + , m_AutoloadURL( rItem.getAutoloadURL() ) + , m_isAutoloadEnabled( rItem.isAutoloadEnabled() ) + , m_DefaultTarget( rItem.getDefaultTarget() ) + , m_TemplateName( rItem.getTemplateName() ) + , m_Author( rItem.getAuthor() ) + , m_CreationDate( rItem.getCreationDate() ) + , m_ModifiedBy( rItem.getModifiedBy() ) + , m_ModificationDate( rItem.getModificationDate() ) + , m_PrintedBy( rItem.getPrintedBy() ) + , m_PrintDate( rItem.getPrintDate() ) + , m_EditingCycles( rItem.getEditingCycles() ) + , m_EditingDuration( rItem.getEditingDuration() ) + , m_Description( rItem.getDescription() ) + , m_Keywords( rItem.getKeywords() ) + , m_Subject( rItem.getSubject() ) + , m_Title( rItem.getTitle() ) + , m_bHasTemplate( rItem.m_bHasTemplate ) + , m_bDeleteUserData( rItem.m_bDeleteUserData ) + , m_bUseUserData( rItem.m_bUseUserData ) + , m_bUseThumbnailSave( rItem.m_bUseThumbnailSave ) +{ + for (auto const & pOtherProp : rItem.m_aCustomProperties) + { + AddCustomProperty( pOtherProp->m_sName, pOtherProp->m_aValue ); + } + + m_aCmisProperties = rItem.m_aCmisProperties; +} + +SfxDocumentInfoItem::~SfxDocumentInfoItem() +{ + ClearCustomProperties(); +} + +SfxDocumentInfoItem* SfxDocumentInfoItem::Clone( SfxItemPool * ) const +{ + return new SfxDocumentInfoItem( *this ); +} + +bool SfxDocumentInfoItem::operator==( const SfxPoolItem& rItem) const +{ + if (!SfxStringItem::operator==(rItem)) + return false; + const SfxDocumentInfoItem& rInfoItem(static_cast<const SfxDocumentInfoItem&>(rItem)); + + return + m_AutoloadDelay == rInfoItem.m_AutoloadDelay && + m_AutoloadURL == rInfoItem.m_AutoloadURL && + m_isAutoloadEnabled == rInfoItem.m_isAutoloadEnabled && + m_DefaultTarget == rInfoItem.m_DefaultTarget && + m_Author == rInfoItem.m_Author && + m_CreationDate == rInfoItem.m_CreationDate && + m_ModifiedBy == rInfoItem.m_ModifiedBy && + m_ModificationDate == rInfoItem.m_ModificationDate && + m_PrintedBy == rInfoItem.m_PrintedBy && + m_PrintDate == rInfoItem.m_PrintDate && + m_EditingCycles == rInfoItem.m_EditingCycles && + m_EditingDuration == rInfoItem.m_EditingDuration && + m_Description == rInfoItem.m_Description && + m_Keywords == rInfoItem.m_Keywords && + m_Subject == rInfoItem.m_Subject && + m_Title == rInfoItem.m_Title && + comphelper::ContainerUniquePtrEquals(m_aCustomProperties, rInfoItem.m_aCustomProperties) && + m_aCmisProperties.getLength() == rInfoItem.m_aCmisProperties.getLength(); +} + + +void SfxDocumentInfoItem::resetUserData(const OUString & i_rAuthor) +{ + m_Author = i_rAuthor; + DateTime now( DateTime::SYSTEM ); + m_CreationDate = now.GetUNODateTime(); + m_ModifiedBy = OUString(); + m_PrintedBy = OUString(); + m_ModificationDate = util::DateTime(); + m_PrintDate = util::DateTime(); + m_EditingDuration = 0; + m_EditingCycles = 1; +} + + +void SfxDocumentInfoItem::UpdateDocumentInfo( + const uno::Reference<document::XDocumentProperties>& i_xDocProps, + bool i_bDoNotUpdateUserDefined) const +{ + if (isAutoloadEnabled()) { + i_xDocProps->setAutoloadSecs(getAutoloadDelay()); + i_xDocProps->setAutoloadURL(getAutoloadURL()); + } else { + i_xDocProps->setAutoloadSecs(0); + i_xDocProps->setAutoloadURL(OUString()); + } + i_xDocProps->setDefaultTarget(getDefaultTarget()); + i_xDocProps->setAuthor(getAuthor()); + i_xDocProps->setCreationDate(getCreationDate()); + i_xDocProps->setModifiedBy(getModifiedBy()); + i_xDocProps->setModificationDate(getModificationDate()); + i_xDocProps->setPrintedBy(getPrintedBy()); + i_xDocProps->setPrintDate(getPrintDate()); + i_xDocProps->setEditingCycles(getEditingCycles()); + i_xDocProps->setEditingDuration(getEditingDuration()); + i_xDocProps->setDescription(getDescription()); + i_xDocProps->setKeywords( + ::comphelper::string::convertCommaSeparated(getKeywords())); + i_xDocProps->setSubject(getSubject()); + i_xDocProps->setTitle(getTitle()); + + // this is necessary in case of replaying a recorded macro: + // in this case, the macro may contain the 4 old user-defined DocumentInfo + // fields, but not any of the DocumentInfo properties; + // as a consequence, most of the UserDefined properties of the + // DocumentProperties would be summarily deleted here, which does not + // seem like a good idea. + if (i_bDoNotUpdateUserDefined) + return; + + try + { + Reference< beans::XPropertyContainer > xContainer = i_xDocProps->getUserDefinedProperties(); + Reference < beans::XPropertySet > xSet( xContainer, UNO_QUERY ); + Reference< beans::XPropertySetInfo > xSetInfo = xSet->getPropertySetInfo(); + const Sequence< beans::Property > lProps = xSetInfo->getProperties(); + for ( const beans::Property& rProp : lProps ) + { + if (rProp.Attributes & css::beans::PropertyAttribute::REMOVABLE) + { + xContainer->removeProperty( rProp.Name ); + } + } + + for (auto const & pProp : m_aCustomProperties) + { + try + { + xContainer->addProperty( pProp->m_sName, + beans::PropertyAttribute::REMOVABLE, pProp->m_aValue ); + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "SfxDocumentInfoItem::updateDocumentInfo(): exception while adding custom properties" ); + } + } + } + catch ( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "SfxDocumentInfoItem::updateDocumentInfo(): exception while removing custom properties" ); + } +} + + +void SfxDocumentInfoItem::SetDeleteUserData( bool bSet ) +{ + m_bDeleteUserData = bSet; +} + + +void SfxDocumentInfoItem::SetUseUserData( bool bSet ) +{ + m_bUseUserData = bSet; +} + +void SfxDocumentInfoItem::SetUseThumbnailSave( bool bSet ) +{ + m_bUseThumbnailSave = bSet; +} + +std::vector< std::unique_ptr<CustomProperty> > SfxDocumentInfoItem::GetCustomProperties() const +{ + std::vector< std::unique_ptr<CustomProperty> > aRet; + for (auto const & pOtherProp : m_aCustomProperties) + { + std::unique_ptr<CustomProperty> pProp(new CustomProperty( pOtherProp->m_sName, + pOtherProp->m_aValue )); + aRet.push_back( std::move(pProp) ); + } + + return aRet; +} + +void SfxDocumentInfoItem::ClearCustomProperties() +{ + m_aCustomProperties.clear(); +} + +void SfxDocumentInfoItem::AddCustomProperty( const OUString& sName, const Any& rValue ) +{ + std::unique_ptr<CustomProperty> pProp(new CustomProperty( sName, rValue )); + m_aCustomProperties.push_back( std::move(pProp) ); +} + + +void SfxDocumentInfoItem::SetCmisProperties( const Sequence< document::CmisProperty >& cmisProps) +{ + m_aCmisProperties = cmisProps; +} + +bool SfxDocumentInfoItem::QueryValue( Any& rVal, sal_uInt8 nMemberId ) const +{ + OUString aValue; + sal_Int32 nValue = 0; + bool bValue = false; + bool bIsInt = false; + bool bIsString = false; + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_DOCINFO_USEUSERDATA: + bValue = IsUseUserData(); + break; + case MID_DOCINFO_USETHUMBNAILSAVE: + bValue = IsUseThumbnailSave(); + break; + case MID_DOCINFO_DELETEUSERDATA: + bValue = m_bDeleteUserData; + break; + case MID_DOCINFO_AUTOLOADENABLED: + bValue = isAutoloadEnabled(); + break; + case MID_DOCINFO_AUTOLOADSECS: + bIsInt = true; + nValue = getAutoloadDelay(); + break; + case MID_DOCINFO_AUTOLOADURL: + bIsString = true; + aValue = getAutoloadURL(); + break; + case MID_DOCINFO_DEFAULTTARGET: + bIsString = true; + aValue = getDefaultTarget(); + break; + case MID_DOCINFO_DESCRIPTION: + bIsString = true; + aValue = getDescription(); + break; + case MID_DOCINFO_KEYWORDS: + bIsString = true; + aValue = getKeywords(); + break; + case MID_DOCINFO_SUBJECT: + bIsString = true; + aValue = getSubject(); + break; + case MID_DOCINFO_TITLE: + bIsString = true; + aValue = getTitle(); + break; + default: + OSL_FAIL("Wrong MemberId!"); + return false; + } + + if ( bIsString ) + rVal <<= aValue; + else if ( bIsInt ) + rVal <<= nValue; + else + rVal <<= bValue; + return true; +} + +bool SfxDocumentInfoItem::PutValue( const Any& rVal, sal_uInt8 nMemberId ) +{ + OUString aValue; + sal_Int32 nValue=0; + bool bValue = false; + bool bRet = false; + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_DOCINFO_USEUSERDATA: + bRet = (rVal >>= bValue); + if ( bRet ) + SetUseUserData( bValue ); + break; + case MID_DOCINFO_USETHUMBNAILSAVE: + bRet = (rVal >>=bValue); + if ( bRet ) + SetUseThumbnailSave( bValue ); + break; + case MID_DOCINFO_DELETEUSERDATA: + // QUESTION: deleting user data was done here; seems to be superfluous! + bRet = (rVal >>= bValue); + if ( bRet ) + SetDeleteUserData( bValue ); + break; + case MID_DOCINFO_AUTOLOADENABLED: + bRet = (rVal >>= bValue); + if ( bRet ) + m_isAutoloadEnabled = bValue; + break; + case MID_DOCINFO_AUTOLOADSECS: + bRet = (rVal >>= nValue); + if ( bRet ) + m_AutoloadDelay = nValue; + break; + case MID_DOCINFO_AUTOLOADURL: + bRet = (rVal >>= aValue); + if ( bRet ) + m_AutoloadURL = aValue; + break; + case MID_DOCINFO_DEFAULTTARGET: + bRet = (rVal >>= aValue); + if ( bRet ) + m_DefaultTarget = aValue; + break; + case MID_DOCINFO_DESCRIPTION: + bRet = (rVal >>= aValue); + if ( bRet ) + setDescription(aValue); + break; + case MID_DOCINFO_KEYWORDS: + bRet = (rVal >>= aValue); + if ( bRet ) + setKeywords(aValue); + break; + case MID_DOCINFO_SUBJECT: + bRet = (rVal >>= aValue); + if ( bRet ) + setSubject(aValue); + break; + case MID_DOCINFO_TITLE: + bRet = (rVal >>= aValue); + if ( bRet ) + setTitle(aValue); + break; + default: + OSL_FAIL("Wrong MemberId!"); + return false; + } + + return bRet; +} + +SfxDocumentDescPage::SfxDocumentDescPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rItemSet) + : SfxTabPage(pPage, pController, "sfx/ui/descriptioninfopage.ui", "DescriptionInfoPage", &rItemSet) + , m_pInfoItem(nullptr) + , m_xTitleEd(m_xBuilder->weld_entry("title")) + , m_xThemaEd(m_xBuilder->weld_entry("subject")) + , m_xKeywordsEd(m_xBuilder->weld_entry("keywords")) + , m_xCommentEd(m_xBuilder->weld_text_view("comments")) +{ + m_xCommentEd->set_size_request(m_xKeywordsEd->get_preferred_size().Width(), + m_xCommentEd->get_height_rows(16)); +} + +SfxDocumentDescPage::~SfxDocumentDescPage() +{ +} + +std::unique_ptr<SfxTabPage> SfxDocumentDescPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet *rItemSet) +{ + return std::make_unique<SfxDocumentDescPage>(pPage, pController, *rItemSet); +} + +bool SfxDocumentDescPage::FillItemSet(SfxItemSet *rSet) +{ + // Test whether a change is present + const bool bTitleMod = m_xTitleEd->get_value_changed_from_saved(); + const bool bThemeMod = m_xThemaEd->get_value_changed_from_saved(); + const bool bKeywordsMod = m_xKeywordsEd->get_value_changed_from_saved(); + const bool bCommentMod = m_xCommentEd->get_value_changed_from_saved(); + if ( !( bTitleMod || bThemeMod || bKeywordsMod || bCommentMod ) ) + { + return false; + } + + // Generating the output data + const SfxDocumentInfoItem* pItem = nullptr; + SfxDocumentInfoItem* pInfo = nullptr; + const SfxItemSet* pExSet = GetDialogExampleSet(); + + if ( pExSet && !(pItem = pExSet->GetItemIfSet( SID_DOCINFO )) ) + pInfo = m_pInfoItem; + else if ( pItem ) + pInfo = new SfxDocumentInfoItem( *pItem ); + + if ( !pInfo ) + { + SAL_WARN( "sfx.dialog", "SfxDocumentDescPage::FillItemSet(): no item found" ); + return false; + } + + if ( bTitleMod ) + { + pInfo->setTitle( m_xTitleEd->get_text() ); + } + if ( bThemeMod ) + { + pInfo->setSubject( m_xThemaEd->get_text() ); + } + if ( bKeywordsMod ) + { + pInfo->setKeywords( m_xKeywordsEd->get_text() ); + } + if ( bCommentMod ) + { + pInfo->setDescription( m_xCommentEd->get_text() ); + } + rSet->Put( *pInfo ); + if ( pInfo != m_pInfoItem ) + { + delete pInfo; + } + + return true; +} + +void SfxDocumentDescPage::Reset(const SfxItemSet *rSet) +{ + m_pInfoItem = const_cast<SfxDocumentInfoItem*>(&rSet->Get(SID_DOCINFO)); + + m_xTitleEd->set_text(m_pInfoItem->getTitle()); + m_xThemaEd->set_text(m_pInfoItem->getSubject()); + m_xKeywordsEd->set_text(m_pInfoItem->getKeywords()); + m_xCommentEd->set_text(m_pInfoItem->getDescription()); + + m_xTitleEd->save_value(); + m_xThemaEd->save_value(); + m_xKeywordsEd->save_value(); + m_xCommentEd->save_value(); + + const SfxBoolItem* pROItem = SfxItemSet::GetItem<SfxBoolItem>(rSet, SID_DOC_READONLY, false); + if (pROItem && pROItem->GetValue()) + { + m_xTitleEd->set_editable(false); + m_xThemaEd->set_editable(false); + m_xKeywordsEd->set_editable(false); + m_xCommentEd->set_editable(false); + } +} + +SfxDocumentPage::SfxDocumentPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rItemSet) + : SfxTabPage(pPage, pController, "sfx/ui/documentinfopage.ui", "DocumentInfoPage", &rItemSet) + , bEnableUseUserData( false ) + , bHandleDelete( false ) + , m_xBmp(m_xBuilder->weld_image("icon")) + , m_xNameED(m_xBuilder->weld_label("nameed")) + , m_xChangePassBtn(m_xBuilder->weld_button("changepass")) + , m_xShowTypeFT(m_xBuilder->weld_label("showtype")) + , m_xFileValEd(m_xBuilder->weld_link_button("showlocation")) + , m_xShowSizeFT(m_xBuilder->weld_label("showsize")) + , m_xCreateValFt(m_xBuilder->weld_label("showcreate")) + , m_xChangeValFt(m_xBuilder->weld_label("showmodify")) + , m_xSignedValFt(m_xBuilder->weld_label("showsigned")) + , m_xSignatureBtn(m_xBuilder->weld_button("signature")) + , m_xPrintValFt(m_xBuilder->weld_label("showprint")) + , m_xTimeLogValFt(m_xBuilder->weld_label("showedittime")) + , m_xDocNoValFt(m_xBuilder->weld_label("showrevision")) + , m_xUseUserDataCB(m_xBuilder->weld_check_button("userdatacb")) + , m_xDeleteBtn(m_xBuilder->weld_button("reset")) + , m_xUseThumbnailSaveCB(m_xBuilder->weld_check_button("thumbnailsavecb")) + , m_xTemplFt(m_xBuilder->weld_label("templateft")) + , m_xTemplValFt(m_xBuilder->weld_label("showtemplate")) + , m_xImagePreferredDpiCheckButton(m_xBuilder->weld_check_button("image-preferred-dpi-checkbutton")) + , m_xImagePreferredDpiComboBox(m_xBuilder->weld_combo_box("image-preferred-dpi-combobox")) +{ + m_xUseUserDataCB->set_accessible_description(SfxResId(STR_A11Y_DESC_USERDATA)); + + m_aUnknownSize = m_xShowSizeFT->get_label(); + m_xShowSizeFT->set_label(OUString()); + + m_aMultiSignedStr = m_xSignedValFt->get_label(); + m_xSignedValFt->set_label(OUString()); + + ImplUpdateSignatures(); + ImplCheckPasswordState(); + m_xChangePassBtn->connect_clicked( LINK( this, SfxDocumentPage, ChangePassHdl ) ); + m_xSignatureBtn->connect_clicked( LINK( this, SfxDocumentPage, SignatureHdl ) ); + m_xDeleteBtn->connect_clicked( LINK( this, SfxDocumentPage, DeleteHdl ) ); + m_xImagePreferredDpiCheckButton->connect_toggled(LINK(this, SfxDocumentPage, ImagePreferredDPICheckBoxClicked)); + + // [i96288] Check if the document signature command is enabled + // on the main list enable/disable the pushbutton accordingly + SvtCommandOptions aCmdOptions; + if ( aCmdOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, "Signature" ) ) + m_xSignatureBtn->set_sensitive(false); +} + +SfxDocumentPage::~SfxDocumentPage() +{ +} + +IMPL_LINK_NOARG(SfxDocumentPage, DeleteHdl, weld::Button&, void) +{ + OUString aName; + if (bEnableUseUserData && m_xUseUserDataCB->get_active()) + aName = SvtUserOptions().GetFullName(); + const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetLocaleDataWrapper() ); + DateTime now( DateTime::SYSTEM ); + util::DateTime uDT( now.GetUNODateTime() ); + m_xCreateValFt->set_label( ConvertDateTime_Impl( aName, uDT, rLocaleWrapper ) ); + m_xChangeValFt->set_label( "" ); + m_xPrintValFt->set_label( "" ); + const tools::Time aTime( 0 ); + m_xTimeLogValFt->set_label( rLocaleWrapper.getDuration( aTime ) ); + m_xDocNoValFt->set_label(OUString('1')); + bHandleDelete = true; +} + +IMPL_LINK_NOARG(SfxDocumentPage, SignatureHdl, weld::Button&, void) +{ + SfxObjectShell* pDoc = SfxObjectShell::Current(); + if( pDoc ) + { + pDoc->SignDocumentContent(GetFrameWeld()); + + ImplUpdateSignatures(); + } +} + +IMPL_LINK_NOARG(SfxDocumentPage, ImagePreferredDPICheckBoxClicked, weld::Toggleable&, void) +{ + bool bEnabled = m_xImagePreferredDpiCheckButton->get_state() == TRISTATE_TRUE; + m_xImagePreferredDpiComboBox->set_sensitive(bEnabled); +} + +IMPL_LINK_NOARG(SfxDocumentPage, ChangePassHdl, weld::Button&, void) +{ + SfxObjectShell* pShell = SfxObjectShell::Current(); + do + { + if (!pShell) + break; + SfxItemSet* pMedSet = pShell->GetMedium()->GetItemSet(); + if (!pMedSet) + break; + std::shared_ptr<const SfxFilter> pFilter = pShell->GetMedium()->GetFilter(); + if (!pFilter) + break; + + sfx2::RequestPassword(pFilter, OUString(), pMedSet, GetFrameWeld()->GetXWindow()); + pShell->SetModified(); + } + while (false); +} + +void SfxDocumentPage::ImplUpdateSignatures() +{ + SfxObjectShell* pDoc = SfxObjectShell::Current(); + if ( !pDoc ) + return; + + SfxMedium* pMedium = pDoc->GetMedium(); + if ( !pMedium || pMedium->GetName().isEmpty() || !pMedium->GetStorage().is() ) + return; + + Reference< security::XDocumentDigitalSignatures > xD; + try + { + xD = security::DocumentDigitalSignatures::createDefault(comphelper::getProcessComponentContext()); + xD->setParentWindow(GetDialogController()->getDialog()->GetXWindow()); + } + catch ( const css::uno::DeploymentException& ) + { + } + OUString s; + Sequence< security::DocumentSignatureInformation > aInfos; + + if ( xD.is() ) + aInfos = xD->verifyDocumentContentSignatures( pMedium->GetZipStorageToSign_Impl(), + uno::Reference< io::XInputStream >() ); + if ( aInfos.getLength() > 1 ) + s = m_aMultiSignedStr; + else if ( aInfos.getLength() == 1 ) + { + const security::DocumentSignatureInformation& rInfo = aInfos[ 0 ]; + s = utl::GetDateTimeString( rInfo.SignatureDate, rInfo.SignatureTime ) + ", " + + comphelper::xmlsec::GetContentPart(rInfo.Signer->getSubjectName(), rInfo.Signer->getCertificateKind()); + } + m_xSignedValFt->set_label(s); +} + +void SfxDocumentPage::ImplCheckPasswordState() +{ + SfxObjectShell* pShell = SfxObjectShell::Current(); + do + { + if (!pShell) + break; + SfxItemSet* pMedSet = pShell->GetMedium()->GetItemSet(); + if (!pMedSet) + break; + const SfxUnoAnyItem* pEncryptionDataItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pMedSet, SID_ENCRYPTIONDATA, false); + uno::Sequence< beans::NamedValue > aEncryptionData; + if (pEncryptionDataItem) + pEncryptionDataItem->GetValue() >>= aEncryptionData; + else + break; + + if (!aEncryptionData.hasElements()) + break; + m_xChangePassBtn->set_sensitive(true); + return; + } + while (false); + m_xChangePassBtn->set_sensitive(false); +} + +std::unique_ptr<SfxTabPage> SfxDocumentPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rItemSet) +{ + return std::make_unique<SfxDocumentPage>(pPage, pController, *rItemSet); +} + +void SfxDocumentPage::EnableUseUserData() +{ + bEnableUseUserData = true; + m_xUseUserDataCB->show(); + m_xDeleteBtn->show(); +} + +bool SfxDocumentPage::FillItemSet( SfxItemSet* rSet ) +{ + bool bRet = false; + + if ( !bHandleDelete && bEnableUseUserData && + m_xUseUserDataCB->get_state_changed_from_saved() ) + { + const SfxItemSet* pExpSet = GetDialogExampleSet(); + const SfxDocumentInfoItem* pInfoItem; + + if ( pExpSet && (pInfoItem = pExpSet->GetItemIfSet( SID_DOCINFO ) ) ) + { + bool bUseData = ( TRISTATE_TRUE == m_xUseUserDataCB->get_state() ); + const_cast<SfxDocumentInfoItem*>(pInfoItem)->SetUseUserData( bUseData ); + rSet->Put( *pInfoItem ); + bRet = true; + } + } + + if ( bHandleDelete ) + { + const SfxItemSet* pExpSet = GetDialogExampleSet(); + const SfxDocumentInfoItem* pInfoItem; + if ( pExpSet && (pInfoItem = pExpSet->GetItemIfSet( SID_DOCINFO )) ) + { + bool bUseAuthor = bEnableUseUserData && m_xUseUserDataCB->get_active(); + SfxDocumentInfoItem newItem( *pInfoItem ); + newItem.resetUserData( bUseAuthor + ? SvtUserOptions().GetFullName() + : OUString() ); + const_cast<SfxDocumentInfoItem*>(pInfoItem)->SetUseUserData( TRISTATE_TRUE == m_xUseUserDataCB->get_state() ); + newItem.SetUseUserData( TRISTATE_TRUE == m_xUseUserDataCB->get_state() ); + + newItem.SetDeleteUserData( true ); + rSet->Put( newItem ); + bRet = true; + } + } + + if ( m_xUseThumbnailSaveCB->get_state_changed_from_saved() ) + { + const SfxItemSet* pExpSet = GetDialogExampleSet(); + const SfxDocumentInfoItem* pInfoItem; + + if ( pExpSet && (pInfoItem = pExpSet->GetItemIfSet( SID_DOCINFO )) ) + { + bool bUseThumbnail = ( TRISTATE_TRUE == m_xUseThumbnailSaveCB->get_state() ); + const_cast<SfxDocumentInfoItem*>(pInfoItem)->SetUseThumbnailSave( bUseThumbnail ); + rSet->Put( *pInfoItem ); + bRet = true; + } + } + + SfxObjectShell* pDocSh = SfxObjectShell::Current(); + if (pDocSh) + { + uno::Reference<lang::XMultiServiceFactory> xFac(pDocSh->GetModel(), uno::UNO_QUERY); + if (xFac.is()) + { + uno::Reference<beans::XPropertySet> xProps(xFac->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY); + if (xProps.is()) + { + sal_Int32 nImagePreferredDPI = 0; + if (m_xImagePreferredDpiCheckButton->get_state() == TRISTATE_TRUE) + { + OUString aImagePreferredDPIString = m_xImagePreferredDpiComboBox->get_active_text(); + nImagePreferredDPI = aImagePreferredDPIString.toInt32(); + } + xProps->setPropertyValue("ImagePreferredDPI", uno::Any(nImagePreferredDPI)); + } + } + } + + return bRet; +} + +void SfxDocumentPage::Reset( const SfxItemSet* rSet ) +{ + // Determine the document information + const SfxDocumentInfoItem& rInfoItem = rSet->Get(SID_DOCINFO); + + // template data + if (rInfoItem.HasTemplate()) + { + const OUString& rName = rInfoItem.getTemplateName(); + if (rName.getLength() > SAL_MAX_INT16) // tdf#122780 pick some ~arbitrary max size + m_xTemplValFt->set_label(rName.copy(0, SAL_MAX_INT16)); + else + m_xTemplValFt->set_label(rName); + } + else + { + m_xTemplFt->hide(); + m_xTemplValFt->hide(); + } + + // determine file name + OUString aFile( rInfoItem.GetValue() ); + OUString aFactory( aFile ); + if ( aFile.getLength() > 2 && aFile[0] == '[' ) + { + sal_Int32 nPos = aFile.indexOf( ']' ); + aFactory = aFile.copy( 1, nPos-1 ); + aFile = aFile.copy( nPos+1 ); + } + + // determine name + INetURLObject aURL(aFile); + OUString aName = aURL.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + if ( aName.isEmpty() || aURL.GetProtocol() == INetProtocol::PrivSoffice ) + aName = SfxResId( STR_NONAME ); + m_xNameED->set_label( aName ); + + // determine context symbol + aURL.SetSmartProtocol( INetProtocol::File ); + aURL.SetSmartURL( aFactory); + const OUString& rMainURL = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + OUString aImage = SvFileInformationManager::GetImageId( aURL, true ); + m_xBmp->set_from_icon_name(aImage); + + // determine size and type + OUString aSizeText( m_aUnknownSize ); + if ( aURL.GetProtocol() == INetProtocol::File || + aURL.isAnyKnownWebDAVScheme() ) + aSizeText = CreateSizeText( SfxContentHelper::GetSize( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ); + m_xShowSizeFT->set_label( aSizeText ); + + OUString aDescription = SvFileInformationManager::GetDescription( INetURLObject(rMainURL) ); + if ( aDescription.isEmpty() ) + aDescription = SfxResId( STR_SFX_NEWOFFICEDOC ); + m_xShowTypeFT->set_label( aDescription ); + + // determine location + aURL.SetSmartURL( aFile); + if ( aURL.GetProtocol() == INetProtocol::File ) + { + INetURLObject aPath( aURL ); + aPath.setFinalSlash(); + aPath.removeSegment(); + // we know it's a folder -> don't need the final slash, but it's better for WB_PATHELLIPSIS + aPath.removeFinalSlash(); + OUString aText( aPath.PathToFileName() ); //! (pb) MaxLen? + m_xFileValEd->set_label(aText); + OUString aURLStr; + osl::FileBase::getFileURLFromSystemPath(aText, aURLStr); + m_xFileValEd->set_uri(aURLStr); + } + else if (aURL.GetProtocol() != INetProtocol::PrivSoffice) + { + m_xFileValEd->set_label(aURL.GetPartBeforeLastName()); + m_xFileValEd->set_uri(m_xFileValEd->get_label()); + } + + // handle access data + bool bUseUserData = rInfoItem.IsUseUserData(); + const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetLocaleDataWrapper() ); + m_xCreateValFt->set_label( ConvertDateTime_Impl( rInfoItem.getAuthor(), + rInfoItem.getCreationDate(), rLocaleWrapper ) ); + util::DateTime aTime( rInfoItem.getModificationDate() ); + if ( aTime.Month > 0 ) + m_xChangeValFt->set_label( ConvertDateTime_Impl( + rInfoItem.getModifiedBy(), aTime, rLocaleWrapper ) ); + aTime = rInfoItem.getPrintDate(); + if ( aTime.Month > 0 ) + m_xPrintValFt->set_label( ConvertDateTime_Impl( rInfoItem.getPrintedBy(), + aTime, rLocaleWrapper ) ); + const tools::Long nTime = rInfoItem.getEditingDuration(); + if ( bUseUserData ) + { + const tools::Time aT( nTime/3600, (nTime%3600)/60, nTime%60 ); + m_xTimeLogValFt->set_label( rLocaleWrapper.getDuration( aT ) ); + m_xDocNoValFt->set_label( OUString::number( + rInfoItem.getEditingCycles() ) ); + } + + bool bUseThumbnailSave = rInfoItem.IsUseThumbnailSave(); + + // Check for cmis properties where otherwise unavailable + if ( rInfoItem.isCmisDocument( ) ) + { + const uno::Sequence< document::CmisProperty > aCmisProps = rInfoItem.GetCmisProperties(); + for ( const auto& rCmisProp : aCmisProps ) + { + if ( rCmisProp.Id == "cmis:contentStreamLength" && + aSizeText == m_aUnknownSize ) + { + Sequence< sal_Int64 > seqValue; + rCmisProp.Value >>= seqValue; + SvNumberFormatter aNumberFormatter( ::comphelper::getProcessComponentContext(), + Application::GetSettings().GetLanguageTag().getLanguageType() ); + sal_uInt32 nIndex = aNumberFormatter.GetFormatIndex( NF_NUMBER_SYSTEM ); + if ( seqValue.hasElements() ) + { + OUString sValue; + aNumberFormatter.GetInputLineString( seqValue[0], nIndex, sValue ); + m_xShowSizeFT->set_label( CreateSizeText( sValue.toInt64( ) ) ); + } + } + + util::DateTime uDT; + OUString emptyDate = ConvertDateTime_Impl( u"", uDT, rLocaleWrapper ); + if ( rCmisProp.Id == "cmis:creationDate" && + (m_xCreateValFt->get_label() == emptyDate || + m_xCreateValFt->get_label().isEmpty())) + { + Sequence< util::DateTime > seqValue; + rCmisProp.Value >>= seqValue; + if ( seqValue.hasElements() ) + { + m_xCreateValFt->set_label( ConvertDateTime_Impl( u"", seqValue[0], rLocaleWrapper ) ); + } + } + if ( rCmisProp.Id == "cmis:lastModificationDate" && + (m_xChangeValFt->get_label() == emptyDate || + m_xChangeValFt->get_label().isEmpty())) + { + Sequence< util::DateTime > seqValue; + rCmisProp.Value >>= seqValue; + if ( seqValue.hasElements() ) + { + m_xChangeValFt->set_label( ConvertDateTime_Impl( u"", seqValue[0], rLocaleWrapper ) ); + } + } + } + } + + m_xUseUserDataCB->set_active(bUseUserData); + m_xUseUserDataCB->save_state(); + m_xUseUserDataCB->set_sensitive( bEnableUseUserData ); + bHandleDelete = false; + m_xDeleteBtn->set_sensitive( bEnableUseUserData ); + m_xUseThumbnailSaveCB->set_active(bUseThumbnailSave); + m_xUseThumbnailSaveCB->save_state(); + + SfxObjectShell* pDocSh = SfxObjectShell::Current(); + sal_Int32 nImagePreferredDPI = 0; + if (pDocSh) + { + try + { + uno::Reference< lang::XMultiServiceFactory > xFac( pDocSh->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProps( xFac->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY_THROW ); + + xProps->getPropertyValue("ImagePreferredDPI") >>= nImagePreferredDPI; + } + catch( uno::Exception& ) + { + } + } + if (nImagePreferredDPI > 0) + { + m_xImagePreferredDpiCheckButton->set_state(TRISTATE_TRUE); + m_xImagePreferredDpiComboBox->set_sensitive(true); + m_xImagePreferredDpiComboBox->set_entry_text(OUString::number(nImagePreferredDPI)); + } + else + { + m_xImagePreferredDpiCheckButton->set_state(TRISTATE_FALSE); + m_xImagePreferredDpiComboBox->set_sensitive(false); + m_xImagePreferredDpiComboBox->set_entry_text(""); + } + +} + +SfxDocumentInfoDialog::SfxDocumentInfoDialog(weld::Window* pParent, const SfxItemSet& rItemSet) + : SfxTabDialogController(pParent, "sfx/ui/documentpropertiesdialog.ui", + "DocumentPropertiesDialog", &rItemSet) +{ + const SfxDocumentInfoItem& rInfoItem = rItemSet.Get( SID_DOCINFO ); + +#ifdef DBG_UTIL + const SfxStringItem* pURLItem = rItemSet.GetItem<SfxStringItem>(SID_BASEURL, false); + DBG_ASSERT( pURLItem, "No BaseURL provided for InternetTabPage!" ); +#endif + + // Determine the Titles + OUString aTitle(m_xDialog->get_title()); + const SfxStringItem* pItem = rItemSet.GetItemIfSet( SID_EXPLORER_PROPS_START, false ); + if ( !pItem ) + { + // File name + const OUString& aFile( rInfoItem.GetValue() ); + + INetURLObject aURL; + aURL.SetSmartProtocol( INetProtocol::File ); + aURL.SetSmartURL( aFile); + if ( INetProtocol::PrivSoffice != aURL.GetProtocol() ) + { + OUString aLastName( aURL.GetLastName() ); + if ( !aLastName.isEmpty() ) + aTitle = aTitle.replaceFirst("%1", aLastName); + else + aTitle = aTitle.replaceFirst("%1", aFile); + } + else + aTitle = aTitle.replaceFirst("%1", SfxResId( STR_NONAME )); + } + else + { + aTitle = aTitle.replaceFirst("%1", pItem->GetValue()); + } + m_xDialog->set_title(aTitle); + + // Property Pages + AddTabPage("general", SfxDocumentPage::Create, nullptr); + AddTabPage("description", SfxDocumentDescPage::Create, nullptr); + AddTabPage("customprops", SfxCustomPropertiesPage::Create, nullptr); + if (rInfoItem.isCmisDocument()) + AddTabPage("cmisprops", SfxCmisPropertiesPage::Create, nullptr); + else + RemoveTabPage("cmisprops"); + AddTabPage("security", SfxSecurityPage::Create, nullptr); +} + +void SfxDocumentInfoDialog::PageCreated(const OString& rId, SfxTabPage &rPage) +{ + if (rId == "general") + static_cast<SfxDocumentPage&>(rPage).EnableUseUserData(); +} + +void SfxDocumentInfoDialog::AddFontTabPage() +{ + AddTabPage("font", SfxResId(STR_FONT_TABPAGE), SfxDocumentFontsPage::Create); +} + +// class CustomPropertiesYesNoButton ------------------------------------- + +CustomPropertiesYesNoButton::CustomPropertiesYesNoButton(std::unique_ptr<weld::Widget> xTopLevel, + std::unique_ptr<weld::RadioButton> xYesButton, + std::unique_ptr<weld::RadioButton> xNoButton) + : m_xTopLevel(std::move(xTopLevel)) + , m_xYesButton(std::move(xYesButton)) + , m_xNoButton(std::move(xNoButton)) +{ + CheckNo(); +} + +CustomPropertiesYesNoButton::~CustomPropertiesYesNoButton() +{ +} + +namespace { + +class DurationDialog_Impl : public weld::GenericDialogController +{ + std::unique_ptr<weld::CheckButton> m_xNegativeCB; + std::unique_ptr<weld::SpinButton> m_xYearNF; + std::unique_ptr<weld::SpinButton> m_xMonthNF; + std::unique_ptr<weld::SpinButton> m_xDayNF; + std::unique_ptr<weld::SpinButton> m_xHourNF; + std::unique_ptr<weld::SpinButton> m_xMinuteNF; + std::unique_ptr<weld::SpinButton> m_xSecondNF; + std::unique_ptr<weld::SpinButton> m_xMSecondNF; + +public: + DurationDialog_Impl(weld::Widget* pParent, const util::Duration& rDuration); + util::Duration GetDuration() const; +}; + +} + +DurationDialog_Impl::DurationDialog_Impl(weld::Widget* pParent, const util::Duration& rDuration) + : GenericDialogController(pParent, "sfx/ui/editdurationdialog.ui", "EditDurationDialog") + , m_xNegativeCB(m_xBuilder->weld_check_button("negative")) + , m_xYearNF(m_xBuilder->weld_spin_button("years")) + , m_xMonthNF(m_xBuilder->weld_spin_button("months")) + , m_xDayNF(m_xBuilder->weld_spin_button("days")) + , m_xHourNF(m_xBuilder->weld_spin_button("hours")) + , m_xMinuteNF(m_xBuilder->weld_spin_button("minutes")) + , m_xSecondNF(m_xBuilder->weld_spin_button("seconds")) + , m_xMSecondNF(m_xBuilder->weld_spin_button("milliseconds")) +{ + m_xNegativeCB->set_active(rDuration.Negative); + m_xYearNF->set_value(rDuration.Years); + m_xMonthNF->set_value(rDuration.Months); + m_xDayNF->set_value(rDuration.Days); + m_xHourNF->set_value(rDuration.Hours); + m_xMinuteNF->set_value(rDuration.Minutes); + m_xSecondNF->set_value(rDuration.Seconds); + m_xMSecondNF->set_value(rDuration.NanoSeconds); +} + +util::Duration DurationDialog_Impl::GetDuration() const +{ + util::Duration aRet; + aRet.Negative = m_xNegativeCB->get_active(); + aRet.Years = m_xYearNF->get_value(); + aRet.Months = m_xMonthNF->get_value(); + aRet.Days = m_xDayNF->get_value(); + aRet.Hours = m_xHourNF->get_value(); + aRet.Minutes = m_xMinuteNF->get_value(); + aRet.Seconds = m_xSecondNF->get_value(); + aRet.NanoSeconds = m_xMSecondNF->get_value(); + return aRet; +} + +CustomPropertiesDurationField::CustomPropertiesDurationField(std::unique_ptr<weld::Entry> xEntry, + std::unique_ptr<weld::Button> xEditButton) + : m_xEntry(std::move(xEntry)) + , m_xEditButton(std::move(xEditButton)) +{ + m_xEditButton->connect_clicked(LINK(this, CustomPropertiesDurationField, ClickHdl)); + SetDuration( util::Duration(false, 0, 0, 0, 0, 0, 0, 0) ); +} + +void CustomPropertiesDurationField::set_visible(bool bVisible) +{ + m_xEntry->set_visible(bVisible); + m_xEditButton->set_visible(bVisible); +} + +void CustomPropertiesDurationField::SetDuration( const util::Duration& rDuration ) +{ + m_aDuration = rDuration; + OUString sText = (rDuration.Negative ? OUString('-') : OUString('+')) + + SfxResId(SFX_ST_DURATION_FORMAT); + sText = sText.replaceFirst( "%1", OUString::number( rDuration.Years ) ); + sText = sText.replaceFirst( "%2", OUString::number( rDuration.Months ) ); + sText = sText.replaceFirst( "%3", OUString::number( rDuration.Days ) ); + sText = sText.replaceFirst( "%4", OUString::number( rDuration.Hours ) ); + sText = sText.replaceFirst( "%5", OUString::number( rDuration.Minutes) ); + sText = sText.replaceFirst( "%6", OUString::number( rDuration.Seconds) ); + m_xEntry->set_text(sText); +} + +IMPL_LINK(CustomPropertiesDurationField, ClickHdl, weld::Button&, rButton, void) +{ + DurationDialog_Impl aDurationDlg(&rButton, GetDuration()); + if (aDurationDlg.run() == RET_OK) + SetDuration(aDurationDlg.GetDuration()); +} + +namespace +{ + void fillNameBox(weld::ComboBox& rNameBox) + { + for (size_t i = 0; i < SAL_N_ELEMENTS(SFX_CB_PROPERTY_STRINGARRAY); ++i) + rNameBox.append_text(SfxResId(SFX_CB_PROPERTY_STRINGARRAY[i])); + Size aSize(rNameBox.get_preferred_size()); + rNameBox.set_size_request(aSize.Width(), aSize.Height()); + } + + void fillTypeBox(weld::ComboBox& rTypeBox) + { + for (size_t i = 0; i < SAL_N_ELEMENTS(SFX_LB_PROPERTY_STRINGARRAY); ++i) + { + OUString sId(OUString::number(SFX_LB_PROPERTY_STRINGARRAY[i].second)); + rTypeBox.append(sId, SfxResId(SFX_LB_PROPERTY_STRINGARRAY[i].first)); + } + rTypeBox.set_active(0); + Size aSize(rTypeBox.get_preferred_size()); + rTypeBox.set_size_request(aSize.Width(), aSize.Height()); + } +} + +// struct CustomPropertyLine --------------------------------------------- +CustomPropertyLine::CustomPropertyLine(CustomPropertiesWindow* pParent, weld::Widget* pContainer) + : m_pParent(pParent) + , m_xBuilder(Application::CreateBuilder(pContainer, "sfx/ui/linefragment.ui")) + , m_xLine(m_xBuilder->weld_container("lineentry")) + , m_xNameBox(m_xBuilder->weld_combo_box("namebox")) + , m_xTypeBox(m_xBuilder->weld_combo_box("typebox")) + , m_xValueEdit(m_xBuilder->weld_entry("valueedit")) + , m_xDateTimeBox(m_xBuilder->weld_widget("datetimebox")) + , m_xDateField(new CustomPropertiesDateField(new SvtCalendarBox(m_xBuilder->weld_menu_button("date")))) + , m_xTimeField(new CustomPropertiesTimeField(m_xBuilder->weld_formatted_spin_button("time"))) + , m_xDurationBox(m_xBuilder->weld_widget("durationbox")) + , m_xDurationField(new CustomPropertiesDurationField(m_xBuilder->weld_entry("duration"), + m_xBuilder->weld_button("durationbutton"))) + , m_xYesNoButton(new CustomPropertiesYesNoButton(m_xBuilder->weld_widget("yesno"), + m_xBuilder->weld_radio_button("yes"), + m_xBuilder->weld_radio_button("no"))) + , m_xRemoveButton(m_xBuilder->weld_button("remove")) + , m_bTypeLostFocus( false ) +{ + fillNameBox(*m_xNameBox); + fillTypeBox(*m_xTypeBox); + + m_xTypeBox->connect_changed(LINK(this, CustomPropertyLine, TypeHdl)); + m_xRemoveButton->connect_clicked(LINK(this, CustomPropertyLine, RemoveHdl)); + m_xValueEdit->connect_focus_out(LINK(this, CustomPropertyLine, EditLoseFocusHdl)); + //add lose focus handlers of date/time fields + m_xTypeBox->connect_focus_out(LINK(this, CustomPropertyLine, BoxLoseFocusHdl)); +} + +void CustomPropertyLine::Clear() +{ + m_xNameBox->set_active(-1); + m_xValueEdit->set_text(OUString()); + +} + +void CustomPropertyLine::Hide() +{ + m_xLine->hide(); +} + +CustomPropertiesWindow::CustomPropertiesWindow(weld::Container& rParent, weld::Label& rHeaderAccName, + weld::Label& rHeaderAccType, weld::Label& rHeaderAccValue) + : m_nHeight(0) + , m_nLineHeight(0) + , m_nScrollPos(0) + , m_pCurrentLine(nullptr) + , m_aNumberFormatter(::comphelper::getProcessComponentContext(), + Application::GetSettings().GetLanguageTag().getLanguageType()) + , m_aEditLoseFocusIdle("sfx2 CustomPropertiesWindow loseFocusIdle") + , m_aBoxLoseFocusIdle("sfx2 CustomPropertiesWindow m_aBoxLoseFocusIdle") + , m_rBody(rParent) + , m_rHeaderAccName(rHeaderAccName) + , m_rHeaderAccType(rHeaderAccType) + , m_rHeaderAccValue(rHeaderAccValue) +{ + m_aEditLoseFocusIdle.SetPriority( TaskPriority::LOWEST ); + m_aEditLoseFocusIdle.SetInvokeHandler( LINK( this, CustomPropertiesWindow, EditTimeoutHdl ) ); + m_aBoxLoseFocusIdle.SetPriority( TaskPriority::LOWEST ); + m_aBoxLoseFocusIdle.SetInvokeHandler( LINK( this, CustomPropertiesWindow, BoxTimeoutHdl ) ); +} + +CustomPropertiesWindow::~CustomPropertiesWindow() +{ + m_aEditLoseFocusIdle.Stop(); + m_aBoxLoseFocusIdle.Stop(); + + m_pCurrentLine = nullptr; +} + +void CustomPropertyLine::DoTypeHdl(const weld::ComboBox& rBox) +{ + auto nType = rBox.get_active_id().toInt32(); + m_xValueEdit->set_visible( (Custom_Type_Text == nType) || (Custom_Type_Number == nType) ); + m_xDateTimeBox->set_visible( (Custom_Type_Date == nType) || (Custom_Type_Datetime == nType) ); + m_xDateField->set_visible( (Custom_Type_Date == nType) || (Custom_Type_Datetime == nType) ); + m_xTimeField->set_visible( Custom_Type_Datetime == nType ); + m_xDurationBox->set_visible( Custom_Type_Duration == nType ); + m_xDurationField->set_visible( Custom_Type_Duration == nType ); + m_xYesNoButton->set_visible( Custom_Type_Boolean == nType ); +} + +IMPL_LINK(CustomPropertyLine, TypeHdl, weld::ComboBox&, rBox, void) +{ + DoTypeHdl(rBox); +} + +void CustomPropertiesWindow::Remove(const CustomPropertyLine* pLine) +{ + StoreCustomProperties(); + + auto pFound = std::find_if( m_aCustomPropertiesLines.begin(), m_aCustomPropertiesLines.end(), + [&] (const std::unique_ptr<CustomPropertyLine>& p) { return p.get() == pLine; }); + if ( pFound != m_aCustomPropertiesLines.end() ) + { + sal_uInt32 nLineNumber = pFound - m_aCustomPropertiesLines.begin(); + sal_uInt32 nDataModelIndex = GetCurrentDataModelPosition() + nLineNumber; + m_aCustomProperties.erase(m_aCustomProperties.begin() + nDataModelIndex); + + ReloadLinesContent(); + } + + m_aRemovedHdl.Call(nullptr); +} + +IMPL_LINK_NOARG(CustomPropertyLine, RemoveHdl, weld::Button&, void) +{ + m_pParent->Remove(this); +} + +void CustomPropertiesWindow::EditLoseFocus(CustomPropertyLine* pLine) +{ + m_pCurrentLine = pLine; + m_aEditLoseFocusIdle.Start(); +} + +IMPL_LINK_NOARG(CustomPropertyLine, EditLoseFocusHdl, weld::Widget&, void) +{ + if (!m_bTypeLostFocus) + m_pParent->EditLoseFocus(this); + else + m_bTypeLostFocus = false; +} + +void CustomPropertiesWindow::BoxLoseFocus(CustomPropertyLine* pLine) +{ + m_pCurrentLine = pLine; + m_aBoxLoseFocusIdle.Start(); +} + +IMPL_LINK_NOARG(CustomPropertyLine, BoxLoseFocusHdl, weld::Widget&, void) +{ + m_pParent->BoxLoseFocus(this); +} + +IMPL_LINK_NOARG(CustomPropertiesWindow, EditTimeoutHdl, Timer *, void) +{ + ValidateLine( m_pCurrentLine, false ); +} + +IMPL_LINK_NOARG(CustomPropertiesWindow, BoxTimeoutHdl, Timer *, void) +{ + ValidateLine( m_pCurrentLine, true ); +} + +bool CustomPropertiesWindow::IsLineValid( CustomPropertyLine* pLine ) const +{ + bool bIsValid = true; + pLine->m_bTypeLostFocus = false; + auto nType = pLine->m_xTypeBox->get_active_id().toInt32(); + OUString sValue = pLine->m_xValueEdit->get_text(); + if ( sValue.isEmpty() ) + return true; + + sal_uInt32 nIndex = NUMBERFORMAT_ENTRY_NOT_FOUND; + if ( Custom_Type_Number == nType ) + // tdf#116214 Scientific format allows to use also standard numbers + nIndex = const_cast< SvNumberFormatter& >( + m_aNumberFormatter ).GetFormatIndex( NF_SCIENTIFIC_000E00 ); + else if ( Custom_Type_Date == nType ) + nIndex = const_cast< SvNumberFormatter& >( + m_aNumberFormatter).GetFormatIndex( NF_DATE_SYS_DDMMYYYY ); + + if ( nIndex != NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + sal_uInt32 nTemp = nIndex; + double fDummy = 0.0; + bIsValid = const_cast< SvNumberFormatter& >( + m_aNumberFormatter ).IsNumberFormat( sValue, nIndex, fDummy ); + if ( bIsValid && nTemp != nIndex ) + // sValue is a number but the format doesn't match the index + bIsValid = false; + } + + return bIsValid; +} + +void CustomPropertiesWindow::ValidateLine( CustomPropertyLine* pLine, bool bIsFromTypeBox ) +{ + if (pLine && !IsLineValid(pLine)) + { + if ( bIsFromTypeBox ) // LoseFocus of TypeBox + pLine->m_bTypeLostFocus = true; + std::unique_ptr<weld::MessageDialog> xMessageBox(Application::CreateMessageDialog(&m_rBody, + VclMessageType::Question, VclButtonsType::OkCancel, SfxResId(STR_SFX_QUERY_WRONG_TYPE))); + if (xMessageBox->run() == RET_OK) + pLine->m_xTypeBox->set_active_id(OUString::number(Custom_Type_Text)); + else + pLine->m_xValueEdit->grab_focus(); + } +} + +void CustomPropertiesWindow::SetVisibleLineCount(sal_uInt32 nCount) +{ + while (GetExistingLineCount() < nCount) + { + CreateNewLine(); + } +} + +void CustomPropertiesWindow::AddLine(const OUString& sName, Any const & rAny) +{ + m_aCustomProperties.push_back(std::unique_ptr<CustomProperty>(new CustomProperty(sName, rAny))); + ReloadLinesContent(); +} + +void CustomPropertiesWindow::CreateNewLine() +{ + CustomPropertyLine* pNewLine = new CustomPropertyLine(this, &m_rBody); + pNewLine->m_xNameBox->set_accessible_relation_labeled_by(&m_rHeaderAccName); + pNewLine->m_xNameBox->set_accessible_name(m_rHeaderAccName.get_label()); + pNewLine->m_xTypeBox->set_accessible_relation_labeled_by(&m_rHeaderAccType); + pNewLine->m_xTypeBox->set_accessible_name(m_rHeaderAccType.get_label()); + pNewLine->m_xValueEdit->set_accessible_relation_labeled_by(&m_rHeaderAccValue); + pNewLine->m_xValueEdit->set_accessible_name(m_rHeaderAccValue.get_label()); + + m_aCustomPropertiesLines.emplace_back( pNewLine ); + + // for ui-testing. Distinguish the elements in the lines + sal_uInt16 nSize = m_aCustomPropertiesLines.size(); + pNewLine->m_xNameBox->set_buildable_name( + pNewLine->m_xNameBox->get_buildable_name() + OString::number(nSize)); + pNewLine->m_xTypeBox->set_buildable_name( + pNewLine->m_xTypeBox->get_buildable_name() + OString::number(nSize)); + pNewLine->m_xValueEdit->set_buildable_name( + pNewLine->m_xValueEdit->get_buildable_name() + OString::number(nSize)); + pNewLine->m_xRemoveButton->set_buildable_name( + pNewLine->m_xRemoveButton->get_buildable_name() + OString::number(nSize)); + + pNewLine->DoTypeHdl(*pNewLine->m_xTypeBox); +} + +bool CustomPropertiesWindow::AreAllLinesValid() const +{ + bool bRet = true; + for ( std::unique_ptr<CustomPropertyLine> const & pLine : m_aCustomPropertiesLines ) + { + if ( !IsLineValid( pLine.get() ) ) + { + bRet = false; + break; + } + } + + return bRet; +} + +void CustomPropertiesWindow::ClearAllLines() +{ + for (auto& pLine : m_aCustomPropertiesLines) + { + pLine->Clear(); + } + m_pCurrentLine = nullptr; + m_aCustomProperties.clear(); + m_nScrollPos = 0; +} + +void CustomPropertiesWindow::DoScroll( sal_Int32 nNewPos ) +{ + StoreCustomProperties(); + m_nScrollPos += nNewPos; + ReloadLinesContent(); +} + +Sequence< beans::PropertyValue > CustomPropertiesWindow::GetCustomProperties() +{ + StoreCustomProperties(); + + Sequence< beans::PropertyValue > aPropertiesSeq(GetTotalLineCount()); + std::transform( + m_aCustomProperties.begin(), m_aCustomProperties.end(), aPropertiesSeq.getArray(), + [](const auto& el) { return comphelper::makePropertyValue(el->m_sName, el->m_aValue); }); + + return aPropertiesSeq; +} + +CustomPropertiesTimeField::CustomPropertiesTimeField(std::unique_ptr<weld::FormattedSpinButton> xTimeField) + : m_xTimeField(std::move(xTimeField)) + , m_xFormatter(new weld::TimeFormatter(*m_xTimeField)) + , m_isUTC(false) +{ + m_xFormatter->SetExtFormat(ExtTimeFieldFormat::LongDuration); + m_xFormatter->EnableEmptyField(false); +} + +tools::Time CustomPropertiesTimeField::get_value() const +{ + return m_xFormatter->GetTime(); +} + +void CustomPropertiesTimeField::set_value(const tools::Time& rTime) +{ + m_xFormatter->SetTime(rTime); +} + +CustomPropertiesTimeField::~CustomPropertiesTimeField() +{ +} + +CustomPropertiesDateField::CustomPropertiesDateField(SvtCalendarBox* pDateField) + : m_xDateField(pDateField) +{ + DateTime aDateTime(DateTime::SYSTEM); + m_xDateField->set_date(aDateTime); +} + +void CustomPropertiesDateField::set_visible(bool bVisible) +{ + m_xDateField->set_visible(bVisible); +} + +Date CustomPropertiesDateField::get_date() const +{ + return m_xDateField->get_date(); +} + +void CustomPropertiesDateField::set_date(const Date& rDate) +{ + m_xDateField->set_date(rDate); +} + +CustomPropertiesDateField::~CustomPropertiesDateField() +{ +} + +void CustomPropertiesWindow::StoreCustomProperties() +{ + sal_uInt32 nDataModelPos = GetCurrentDataModelPosition(); + + for (sal_uInt32 i = 0; nDataModelPos + i < GetTotalLineCount() && i < GetExistingLineCount(); i++) + { + CustomPropertyLine* pLine = m_aCustomPropertiesLines[i].get(); + + OUString sPropertyName = pLine->m_xNameBox->get_active_text(); + if (!sPropertyName.isEmpty()) + { + m_aCustomProperties[nDataModelPos + i]->m_sName = sPropertyName; + auto nType = pLine->m_xTypeBox->get_active_id().toInt32(); + if (Custom_Type_Number == nType) + { + double nValue = 0; + sal_uInt32 nIndex = m_aNumberFormatter.GetFormatIndex(NF_NUMBER_SYSTEM); + bool bIsNum = m_aNumberFormatter. + IsNumberFormat(pLine->m_xValueEdit->get_text(), nIndex, nValue); + if (bIsNum) + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= nValue; + } + else if (Custom_Type_Boolean == nType) + { + bool bValue = pLine->m_xYesNoButton->IsYesChecked(); + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= bValue; + } + else if (Custom_Type_Datetime == nType) + { + Date aTmpDate = pLine->m_xDateField->get_date(); + tools::Time aTmpTime = pLine->m_xTimeField->get_value(); + util::DateTime const aDateTime(aTmpTime.GetNanoSec(), + aTmpTime.GetSec(), aTmpTime.GetMin(), aTmpTime.GetHour(), + aTmpDate.GetDay(), aTmpDate.GetMonth(), aTmpDate.GetYear(), + pLine->m_xTimeField->m_isUTC); + if (pLine->m_xDateField->m_TZ) + { + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= util::DateTimeWithTimezone( + aDateTime, *pLine->m_xDateField->m_TZ); + } + else + { + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= aDateTime; + } + } + else if (Custom_Type_Date == nType) + { + Date aTmpDate = pLine->m_xDateField->get_date(); + util::Date const aDate(aTmpDate.GetDay(), aTmpDate.GetMonth(), + aTmpDate.GetYear()); + if (pLine->m_xDateField->m_TZ) + { + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= util::DateWithTimezone( + aDate, *pLine->m_xDateField->m_TZ); + } + else + { + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= aDate; + } + } + else if (Custom_Type_Duration == nType) + { + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= pLine->m_xDurationField->GetDuration(); + } + else + { + OUString sValue(pLine->m_xValueEdit->get_text()); + m_aCustomProperties[nDataModelPos + i]->m_aValue <<= sValue; + } + } + } +} + +void CustomPropertiesWindow::SetCustomProperties(std::vector< std::unique_ptr<CustomProperty> >&& rProperties) +{ + m_aCustomProperties = std::move(rProperties); + ReloadLinesContent(); +} + +void CustomPropertiesWindow::ReloadLinesContent() +{ + double nTmpValue = 0; + bool bTmpValue = false; + OUString sTmpValue; + util::DateTime aTmpDateTime; + util::Date aTmpDate; + util::DateTimeWithTimezone aTmpDateTimeTZ; + util::DateWithTimezone aTmpDateTZ; + util::Duration aTmpDuration; + SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleWrapper = aSysLocale.GetLocaleData(); + CustomProperties nType = Custom_Type_Unknown; + OUString sValue; + + sal_uInt32 nDataModelPos = GetCurrentDataModelPosition(); + sal_uInt32 i = 0; + + for (; nDataModelPos + i < GetTotalLineCount() && i < GetExistingLineCount(); i++) + { + const OUString& rName = m_aCustomProperties[nDataModelPos + i]->m_sName; + const css::uno::Any& rAny = m_aCustomProperties[nDataModelPos + i]->m_aValue; + + CustomPropertyLine* pLine = m_aCustomPropertiesLines[i].get(); + pLine->Clear(); + + pLine->m_xNameBox->set_entry_text(rName); + pLine->m_xLine->show(); + + if (!rAny.hasValue()) + { + pLine->m_xValueEdit->set_text(OUString()); + } + else if (rAny >>= nTmpValue) + { + sal_uInt32 nIndex = m_aNumberFormatter.GetFormatIndex(NF_NUMBER_SYSTEM); + m_aNumberFormatter.GetInputLineString(nTmpValue, nIndex, sValue); + pLine->m_xValueEdit->set_text(sValue); + nType = Custom_Type_Number; + } + else if (rAny >>= bTmpValue) + { + sValue = (bTmpValue ? rLocaleWrapper.getTrueWord() : rLocaleWrapper.getFalseWord()); + nType = Custom_Type_Boolean; + } + else if (rAny >>= sTmpValue) + { + pLine->m_xValueEdit->set_text(sTmpValue); + nType = Custom_Type_Text; + } + else if (rAny >>= aTmpDate) + { + pLine->m_xDateField->set_date(Date(aTmpDate)); + nType = Custom_Type_Date; + } + else if (rAny >>= aTmpDateTime) + { + pLine->m_xDateField->set_date(Date(aTmpDateTime)); + pLine->m_xTimeField->set_value(tools::Time(aTmpDateTime)); + pLine->m_xTimeField->m_isUTC = aTmpDateTime.IsUTC; + nType = Custom_Type_Datetime; + } + else if (rAny >>= aTmpDateTZ) + { + pLine->m_xDateField->set_date(Date(aTmpDateTZ.DateInTZ.Day, + aTmpDateTZ.DateInTZ.Month, aTmpDateTZ.DateInTZ.Year)); + pLine->m_xDateField->m_TZ = aTmpDateTZ.Timezone; + nType = Custom_Type_Date; + } + + else if (rAny >>= aTmpDateTimeTZ) + { + util::DateTime const& rDT(aTmpDateTimeTZ.DateTimeInTZ); + pLine->m_xDateField->set_date(Date(rDT)); + pLine->m_xTimeField->set_value(tools::Time(rDT)); + pLine->m_xTimeField->m_isUTC = rDT.IsUTC; + pLine->m_xDateField->m_TZ = aTmpDateTimeTZ.Timezone; + nType = Custom_Type_Datetime; + } + else if (rAny >>= aTmpDuration) + { + nType = Custom_Type_Duration; + pLine->m_xDurationField->SetDuration(aTmpDuration); + } + + if (nType != Custom_Type_Duration) + { + if (Custom_Type_Boolean == nType) + { + if (bTmpValue) + pLine->m_xYesNoButton->CheckYes(); + else + pLine->m_xYesNoButton->CheckNo(); + } + pLine->m_xTypeBox->set_active_id(OUString::number(nType)); + } + + pLine->DoTypeHdl(*pLine->m_xTypeBox); + } + + // tdf#132667 - grab focus on the last inserted property + if (i > 0 && m_aCustomProperties[nDataModelPos + i - 1]->m_sName.isEmpty()) + { + CustomPropertyLine* pLine = m_aCustomPropertiesLines[i - 1].get(); + pLine->m_xNameBox->grab_focus(); + } + + while (nDataModelPos + i >= GetTotalLineCount() && i < GetExistingLineCount()) + { + CustomPropertyLine* pLine = m_aCustomPropertiesLines[i].get(); + pLine->Hide(); + i++; + } +} + +CustomPropertiesControl::CustomPropertiesControl() + : m_nThumbPos(0) +{ +} + +void CustomPropertiesControl::Init(weld::Builder& rBuilder) +{ + m_xBox = rBuilder.weld_widget("box"); + m_xBody = rBuilder.weld_container("properties"); + + m_xName = rBuilder.weld_label("name"); + m_xType = rBuilder.weld_label("type"); + m_xValue = rBuilder.weld_label("value"); + m_xVertScroll = rBuilder.weld_scrolled_window("scroll", true); + m_xPropertiesWin.reset(new CustomPropertiesWindow(*m_xBody, *m_xName, *m_xType, *m_xValue)); + + m_xBox->set_stack_background(); + m_xVertScroll->show(); + + std::unique_ptr<CustomPropertyLine> xNewLine(new CustomPropertyLine(m_xPropertiesWin.get(), m_xBody.get())); + Size aLineSize(xNewLine->m_xLine->get_preferred_size()); + m_xPropertiesWin->SetLineHeight(aLineSize.Height() + 6); + m_xBody->set_size_request(aLineSize.Width() + 6, -1); + auto nHeight = aLineSize.Height() * 8; + m_xVertScroll->set_size_request(-1, nHeight + 6); + + m_xPropertiesWin->SetHeight(nHeight); + m_xVertScroll->connect_size_allocate(LINK(this, CustomPropertiesControl, ResizeHdl)); + + m_xName->set_size_request(xNewLine->m_xNameBox->get_preferred_size().Width(), -1); + m_xType->set_size_request(xNewLine->m_xTypeBox->get_preferred_size().Width(), -1); + m_xValue->set_size_request(xNewLine->m_xValueEdit->get_preferred_size().Width(), -1); + + m_xBody->move(xNewLine->m_xLine.get(), nullptr); + xNewLine.reset(); + + m_xPropertiesWin->SetRemovedHdl( LINK( this, CustomPropertiesControl, RemovedHdl ) ); + + m_xVertScroll->vadjustment_set_lower(0); + m_xVertScroll->vadjustment_set_upper(0); + m_xVertScroll->vadjustment_set_page_size(0xFFFF); + + Link<weld::ScrolledWindow&,void> aScrollLink = LINK( this, CustomPropertiesControl, ScrollHdl ); + m_xVertScroll->connect_vadjustment_changed(aScrollLink); + + ResizeHdl(Size(-1, nHeight)); +} + +IMPL_LINK(CustomPropertiesControl, ResizeHdl, const Size&, rSize, void) +{ + int nHeight = rSize.Height() - 6; + if (nHeight == m_xPropertiesWin->GetHeight()) + return; + m_xPropertiesWin->SetHeight(nHeight); + sal_Int32 nScrollOffset = m_xPropertiesWin->GetLineHeight(); + sal_Int32 nVisibleEntries = nHeight / nScrollOffset; + m_xPropertiesWin->SetVisibleLineCount( nVisibleEntries ); + m_xVertScroll->vadjustment_set_page_increment( nVisibleEntries - 1 ); + m_xVertScroll->vadjustment_set_page_size( nVisibleEntries ); + m_xPropertiesWin->ReloadLinesContent(); +} + +CustomPropertiesControl::~CustomPropertiesControl() +{ +} + +IMPL_LINK( CustomPropertiesControl, ScrollHdl, weld::ScrolledWindow&, rScrollBar, void ) +{ + sal_Int32 nOffset = m_xPropertiesWin->GetLineHeight(); + int nThumbPos = rScrollBar.vadjustment_get_value(); + nOffset *= ( m_nThumbPos - nThumbPos ); + m_nThumbPos = nThumbPos; + m_xPropertiesWin->DoScroll( nOffset ); +} + +IMPL_LINK_NOARG(CustomPropertiesControl, RemovedHdl, void*, void) +{ + auto nLineCount = m_xPropertiesWin->GetTotalLineCount(); + m_xVertScroll->vadjustment_set_upper(nLineCount + 1); + if (m_xPropertiesWin->GetTotalLineCount() > m_xPropertiesWin->GetExistingLineCount()) + { + m_xVertScroll->vadjustment_set_value(nLineCount - 1); + ScrollHdl(*m_xVertScroll); + } +} + +void CustomPropertiesControl::AddLine( Any const & rAny ) +{ + m_xPropertiesWin->AddLine( OUString(), rAny ); + auto nLineCount = m_xPropertiesWin->GetTotalLineCount(); + m_xVertScroll->vadjustment_set_upper(nLineCount + 1); + if (m_xPropertiesWin->GetHeight() < nLineCount * m_xPropertiesWin->GetLineHeight()) + { + m_xVertScroll->vadjustment_set_value(nLineCount + 1); + ScrollHdl(*m_xVertScroll); + } +} + +void CustomPropertiesControl::SetCustomProperties(std::vector< std::unique_ptr<CustomProperty> >&& rProperties) +{ + m_xPropertiesWin->SetCustomProperties(std::move(rProperties)); + auto nLineCount = m_xPropertiesWin->GetTotalLineCount(); + m_xVertScroll->vadjustment_set_upper(nLineCount + 1); +} + +// class SfxCustomPropertiesPage ----------------------------------------- +SfxCustomPropertiesPage::SfxCustomPropertiesPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rItemSet ) + : SfxTabPage(pPage, pController, "sfx/ui/custominfopage.ui", "CustomInfoPage", &rItemSet) + , m_xPropertiesCtrl(new CustomPropertiesControl) + , m_xAdd(m_xBuilder->weld_button("add")) +{ + m_xPropertiesCtrl->Init(*m_xBuilder); + m_xAdd->connect_clicked(LINK(this, SfxCustomPropertiesPage, AddHdl)); +} + +SfxCustomPropertiesPage::~SfxCustomPropertiesPage() +{ + m_xPropertiesCtrl.reset(); +} + +IMPL_LINK_NOARG(SfxCustomPropertiesPage, AddHdl, weld::Button&, void) +{ + // tdf#115853: reload current lines before adding a brand new one + // indeed the info are deleted by ClearCustomProperties + // each time SfxDocumentInfoItem destructor is called + SfxDocumentInfoItem pInfo; + const Sequence< beans::PropertyValue > aPropertySeq = m_xPropertiesCtrl->GetCustomProperties(); + for ( const auto& rProperty : aPropertySeq ) + { + if ( !rProperty.Name.isEmpty() ) + { + pInfo.AddCustomProperty( rProperty.Name, rProperty.Value ); + } + } + + Any aAny; + m_xPropertiesCtrl->AddLine(aAny); +} + +bool SfxCustomPropertiesPage::FillItemSet( SfxItemSet* rSet ) +{ + const SfxDocumentInfoItem* pItem = nullptr; + SfxDocumentInfoItem* pInfo = nullptr; + bool bMustDelete = false; + + if (const SfxItemSet* pItemSet = GetDialogExampleSet()) + { + pItem = pItemSet->GetItemIfSet(SID_DOCINFO); + if (!pItem) + pInfo = const_cast<SfxDocumentInfoItem*>(&rSet->Get( SID_DOCINFO )); + else + { + bMustDelete = true; + pInfo = new SfxDocumentInfoItem( *pItem ); + } + } + + if ( pInfo ) + { + // If it's a CMIS document, we can't save custom properties + if ( pInfo->isCmisDocument( ) ) + { + if ( bMustDelete ) + delete pInfo; + return false; + } + + pInfo->ClearCustomProperties(); + const Sequence< beans::PropertyValue > aPropertySeq = m_xPropertiesCtrl->GetCustomProperties(); + for ( const auto& rProperty : aPropertySeq ) + { + if ( !rProperty.Name.isEmpty() ) + pInfo->AddCustomProperty( rProperty.Name, rProperty.Value ); + } + } + + if (pInfo) + { + rSet->Put(*pInfo); + if ( bMustDelete ) + delete pInfo; + } + return true; +} + +void SfxCustomPropertiesPage::Reset( const SfxItemSet* rItemSet ) +{ + m_xPropertiesCtrl->ClearAllLines(); + const SfxDocumentInfoItem& rInfoItem = rItemSet->Get(SID_DOCINFO); + std::vector< std::unique_ptr<CustomProperty> > aCustomProps = rInfoItem.GetCustomProperties(); + // tdf#123919 - sort custom document properties + auto const sort = comphelper::string::NaturalStringSorter( + comphelper::getProcessComponentContext(), + Application::GetSettings().GetLanguageTag().getLocale()); + std::sort(aCustomProps.begin(), aCustomProps.end(), + [&sort](const std::unique_ptr<CustomProperty>& rLHS, + const std::unique_ptr<CustomProperty>& rRHS) { + return sort.compare(rLHS->m_sName, rRHS->m_sName) < 0; + }); + m_xPropertiesCtrl->SetCustomProperties(std::move(aCustomProps)); +} + +DeactivateRC SfxCustomPropertiesPage::DeactivatePage( SfxItemSet* /*pSet*/ ) +{ + DeactivateRC nRet = DeactivateRC::LeavePage; + if ( !m_xPropertiesCtrl->AreAllLinesValid() ) + nRet = DeactivateRC::KeepPage; + return nRet; +} + +std::unique_ptr<SfxTabPage> SfxCustomPropertiesPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rItemSet) +{ + return std::make_unique<SfxCustomPropertiesPage>(pPage, pController, *rItemSet); +} + +CmisValue::CmisValue(weld::Widget* pParent, const OUString& aStr) + : m_xBuilder(Application::CreateBuilder(pParent, "sfx/ui/cmisline.ui")) + , m_xFrame(m_xBuilder->weld_frame("CmisFrame")) + , m_xValueEdit(m_xBuilder->weld_entry("value")) +{ + m_xValueEdit->show(); + m_xValueEdit->set_text(aStr); +} + +CmisDateTime::CmisDateTime(weld::Widget* pParent, const util::DateTime& aDateTime) + : m_xBuilder(Application::CreateBuilder(pParent, "sfx/ui/cmisline.ui")) + , m_xFrame(m_xBuilder->weld_frame("CmisFrame")) + , m_xDateField(new SvtCalendarBox(m_xBuilder->weld_menu_button("date"))) + , m_xTimeField(m_xBuilder->weld_formatted_spin_button("time")) + , m_xFormatter(new weld::TimeFormatter(*m_xTimeField)) +{ + m_xFormatter->SetExtFormat(ExtTimeFieldFormat::LongDuration); + m_xFormatter->EnableEmptyField(false); + + m_xDateField->show(); + m_xTimeField->show(); + m_xDateField->set_date(Date(aDateTime)); + m_xFormatter->SetTime(tools::Time(aDateTime)); +} + +CmisYesNo::CmisYesNo(weld::Widget* pParent, bool bValue) + : m_xBuilder(Application::CreateBuilder(pParent, "sfx/ui/cmisline.ui")) + , m_xFrame(m_xBuilder->weld_frame("CmisFrame")) + , m_xYesButton(m_xBuilder->weld_radio_button("yes")) + , m_xNoButton(m_xBuilder->weld_radio_button("no")) +{ + m_xYesButton->show(); + m_xNoButton->show(); + if (bValue) + m_xYesButton->set_active(true); + else + m_xNoButton->set_active(true); +} + +// struct CmisPropertyLine --------------------------------------------- +CmisPropertyLine::CmisPropertyLine(weld::Widget* pParent) + : m_xBuilder(Application::CreateBuilder(pParent, "sfx/ui/cmisline.ui")) + , m_sType(CMIS_TYPE_STRING) + , m_bUpdatable(false) + , m_bRequired(false) + , m_bMultiValued(false) + , m_bOpenChoice(false) + , m_xFrame(m_xBuilder->weld_frame("CmisFrame")) + , m_xName(m_xBuilder->weld_label("name")) + , m_xType(m_xBuilder->weld_label("type")) +{ + m_xFrame->set_sensitive(true); +} + +CmisPropertyLine::~CmisPropertyLine( ) +{ +} + +// class CmisPropertiesWindow ----------------------------------------- + +CmisPropertiesWindow::CmisPropertiesWindow(std::unique_ptr<weld::Container> xParent) + : m_xBox(std::move(xParent)) + , m_aNumberFormatter(::comphelper::getProcessComponentContext(), + Application::GetSettings().GetLanguageTag().getLanguageType()) +{ +} + +CmisPropertiesWindow::~CmisPropertiesWindow() +{ +} + +void CmisPropertiesWindow::ClearAllLines() +{ + m_aCmisPropertiesLines.clear(); +} + +void CmisPropertiesWindow::AddLine( const OUString& sId, const OUString& sName, + const OUString& sType, const bool bUpdatable, + const bool bRequired, const bool bMultiValued, + const bool bOpenChoice, Any& /*aChoices*/, Any const & rAny ) +{ + std::unique_ptr<CmisPropertyLine> pNewLine(new CmisPropertyLine(m_xBox.get())); + + pNewLine->m_sId = sId; + pNewLine->m_sType = sType; + pNewLine->m_bUpdatable = bUpdatable; + pNewLine->m_bRequired = bRequired; + pNewLine->m_bMultiValued = bMultiValued; + pNewLine->m_bOpenChoice = bOpenChoice; + + if ( sType == CMIS_TYPE_INTEGER ) + { + Sequence< sal_Int64 > seqValue; + rAny >>= seqValue; + sal_uInt32 nIndex = m_aNumberFormatter.GetFormatIndex( NF_NUMBER_SYSTEM ); + for ( const auto& rValue : std::as_const(seqValue) ) + { + OUString sValue; + m_aNumberFormatter.GetInputLineString( rValue, nIndex, sValue ); + std::unique_ptr<CmisValue> pValue(new CmisValue(m_xBox.get(), sValue)); + pValue->m_xValueEdit->set_editable(bUpdatable); + pNewLine->m_aValues.push_back( std::move(pValue) ); + } + } + else if ( sType == CMIS_TYPE_DECIMAL ) + { + Sequence< double > seqValue; + rAny >>= seqValue; + sal_uInt32 nIndex = m_aNumberFormatter.GetFormatIndex( NF_NUMBER_SYSTEM ); + for ( const auto& rValue : std::as_const(seqValue) ) + { + OUString sValue; + m_aNumberFormatter.GetInputLineString( rValue, nIndex, sValue ); + std::unique_ptr<CmisValue> pValue(new CmisValue(m_xBox.get(), sValue)); + pValue->m_xValueEdit->set_editable(bUpdatable); + pNewLine->m_aValues.push_back( std::move(pValue) ); + } + + } + else if ( sType == CMIS_TYPE_BOOL ) + { + Sequence<sal_Bool> seqValue; + rAny >>= seqValue; + for ( const auto& rValue : std::as_const(seqValue) ) + { + std::unique_ptr<CmisYesNo> pYesNo(new CmisYesNo(m_xBox.get(), rValue)); + pYesNo->m_xYesButton->set_sensitive( bUpdatable ); + pYesNo->m_xNoButton->set_sensitive( bUpdatable ); + pNewLine->m_aYesNos.push_back( std::move(pYesNo) ); + } + } + else if ( sType == CMIS_TYPE_STRING ) + { + Sequence< OUString > seqValue; + rAny >>= seqValue; + for ( const auto& rValue : std::as_const(seqValue) ) + { + std::unique_ptr<CmisValue> pValue(new CmisValue(m_xBox.get(), rValue)); + pValue->m_xValueEdit->set_editable(bUpdatable); + pNewLine->m_aValues.push_back( std::move(pValue) ); + } + } + else if ( sType == CMIS_TYPE_DATETIME ) + { + Sequence< util::DateTime > seqValue; + rAny >>= seqValue; + for ( const auto& rValue : std::as_const(seqValue) ) + { + std::unique_ptr<CmisDateTime> pDateTime(new CmisDateTime(m_xBox.get(), rValue)); + pDateTime->m_xDateField->set_sensitive(bUpdatable); + pDateTime->m_xTimeField->set_sensitive(bUpdatable); + pNewLine->m_aDateTimes.push_back( std::move(pDateTime) ); + } + } + pNewLine->m_xName->set_label( sName ); + pNewLine->m_xName->show(); + pNewLine->m_xType->set_label( sType ); + pNewLine->m_xType->show(); + + m_aCmisPropertiesLines.push_back( std::move(pNewLine) ); +} + +Sequence< document::CmisProperty > CmisPropertiesWindow::GetCmisProperties() const +{ + Sequence< document::CmisProperty > aPropertiesSeq( m_aCmisPropertiesLines.size() ); + auto aPropertiesSeqRange = asNonConstRange(aPropertiesSeq); + sal_Int32 i = 0; + for ( auto& rxLine : m_aCmisPropertiesLines ) + { + CmisPropertyLine* pLine = rxLine.get(); + + aPropertiesSeqRange[i].Id = pLine->m_sId; + aPropertiesSeqRange[i].Type = pLine->m_sType; + aPropertiesSeqRange[i].Updatable = pLine->m_bUpdatable; + aPropertiesSeqRange[i].Required = pLine->m_bRequired; + aPropertiesSeqRange[i].OpenChoice = pLine->m_bOpenChoice; + aPropertiesSeqRange[i].MultiValued = pLine->m_bMultiValued; + + OUString sPropertyName = pLine->m_xName->get_label(); + if ( !sPropertyName.isEmpty() ) + { + aPropertiesSeqRange[i].Name = sPropertyName; + OUString sType = pLine->m_xType->get_label(); + if ( CMIS_TYPE_DECIMAL == sType ) + { + sal_uInt32 nIndex = const_cast< SvNumberFormatter& >( + m_aNumberFormatter ).GetFormatIndex( NF_NUMBER_SYSTEM ); + Sequence< double > seqValue( pLine->m_aValues.size( ) ); + auto seqValueRange = asNonConstRange(seqValue); + sal_Int32 k = 0; + for ( const auto& rxValue : pLine->m_aValues ) + { + double dValue = 0.0; + OUString sValue( rxValue->m_xValueEdit->get_text() ); + bool bIsNum = const_cast< SvNumberFormatter& >( m_aNumberFormatter ). + IsNumberFormat( sValue, nIndex, dValue ); + if ( bIsNum ) + seqValueRange[k] = dValue; + ++k; + } + aPropertiesSeqRange[i].Value <<= seqValue; + } + else if ( CMIS_TYPE_INTEGER == sType ) + { + sal_uInt32 nIndex = const_cast< SvNumberFormatter& >( + m_aNumberFormatter ).GetFormatIndex( NF_NUMBER_SYSTEM ); + Sequence< sal_Int64 > seqValue( pLine->m_aValues.size( ) ); + auto seqValueRange = asNonConstRange(seqValue); + sal_Int32 k = 0; + for ( const auto& rxValue : pLine->m_aValues ) + { + double dValue = 0; + OUString sValue( rxValue->m_xValueEdit->get_text() ); + bool bIsNum = const_cast< SvNumberFormatter& >( m_aNumberFormatter ). + IsNumberFormat( sValue, nIndex, dValue ); + if ( bIsNum ) + seqValueRange[k] = static_cast<sal_Int64>(dValue); + ++k; + } + aPropertiesSeqRange[i].Value <<= seqValue; + } + else if ( CMIS_TYPE_BOOL == sType ) + { + Sequence<sal_Bool> seqValue( pLine->m_aYesNos.size( ) ); + sal_Bool* pseqValue = seqValue.getArray(); + sal_Int32 k = 0; + for ( const auto& rxYesNo : pLine->m_aYesNos ) + { + bool bValue = rxYesNo->m_xYesButton->get_active(); + pseqValue[k] = bValue; + ++k; + } + aPropertiesSeqRange[i].Value <<= seqValue; + + } + else if ( CMIS_TYPE_DATETIME == sType ) + { + Sequence< util::DateTime > seqValue( pLine->m_aDateTimes.size( ) ); + auto seqValueRange = asNonConstRange(seqValue); + sal_Int32 k = 0; + for ( const auto& rxDateTime : pLine->m_aDateTimes ) + { + Date aTmpDate = rxDateTime->m_xDateField->get_date(); + tools::Time aTmpTime = rxDateTime->m_xFormatter->GetTime(); + util::DateTime aDateTime( aTmpTime.GetNanoSec(), aTmpTime.GetSec(), + aTmpTime.GetMin(), aTmpTime.GetHour(), + aTmpDate.GetDay(), aTmpDate.GetMonth(), + aTmpDate.GetYear(), true ); + seqValueRange[k] = aDateTime; + ++k; + } + aPropertiesSeqRange[i].Value <<= seqValue; + } + else + { + Sequence< OUString > seqValue( pLine->m_aValues.size( ) ); + auto seqValueRange = asNonConstRange(seqValue); + sal_Int32 k = 0; + for ( const auto& rxValue : pLine->m_aValues ) + { + OUString sValue( rxValue->m_xValueEdit->get_text() ); + seqValueRange[k] = sValue; + ++k; + } + aPropertiesSeqRange[i].Value <<= seqValue; + } + } + ++i; + } + + return aPropertiesSeq; +} + +CmisPropertiesControl::CmisPropertiesControl(weld::Builder& rBuilder) + : m_aPropertiesWin(rBuilder.weld_container("CmisWindow")) + , m_xScrolledWindow(rBuilder.weld_scrolled_window("CmisScroll")) +{ + // set height to something small and force it to take the size + // dictated by the other pages + m_xScrolledWindow->set_size_request(-1, 42); +} + +void CmisPropertiesControl::ClearAllLines() +{ + m_aPropertiesWin.ClearAllLines(); +} + +void CmisPropertiesControl::AddLine( const OUString& sId, const OUString& sName, + const OUString& sType, const bool bUpdatable, + const bool bRequired, const bool bMultiValued, + const bool bOpenChoice, Any& aChoices, Any const & rAny + ) +{ + m_aPropertiesWin.AddLine( sId, sName, sType, bUpdatable, bRequired, bMultiValued, + bOpenChoice, aChoices, rAny ); +} + +// class SfxCmisPropertiesPage ----------------------------------------- +SfxCmisPropertiesPage::SfxCmisPropertiesPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rItemSet) + : SfxTabPage(pPage, pController, "sfx/ui/cmisinfopage.ui", "CmisInfoPage", &rItemSet) + , m_xPropertiesCtrl(new CmisPropertiesControl(*m_xBuilder)) +{ +} + +SfxCmisPropertiesPage::~SfxCmisPropertiesPage() +{ + m_xPropertiesCtrl.reset(); +} + +bool SfxCmisPropertiesPage::FillItemSet( SfxItemSet* rSet ) +{ + const SfxDocumentInfoItem* pItem = nullptr; + SfxDocumentInfoItem* pInfo = nullptr; + bool bMustDelete = false; + + if (const SfxItemSet* pItemSet = GetDialogExampleSet()) + { + pItem = pItemSet->GetItemIfSet(SID_DOCINFO); + if (!pItem) + pInfo = const_cast<SfxDocumentInfoItem*>(&rSet->Get( SID_DOCINFO )); + else + { + bMustDelete = true; + pInfo = new SfxDocumentInfoItem( *pItem ); + } + } + + sal_Int32 modifiedNum = 0; + if ( pInfo ) + { + Sequence< document::CmisProperty > aOldProps = pInfo->GetCmisProperties( ); + Sequence< document::CmisProperty > aNewProps = m_xPropertiesCtrl->GetCmisProperties(); + + std::vector< document::CmisProperty > changedProps; + for ( sal_Int32 i = 0; i< aNewProps.getLength( ); ++i ) + { + if ( aOldProps[i].Updatable && !aNewProps[i].Id.isEmpty( ) ) + { + if ( aOldProps[i].Type == CMIS_TYPE_DATETIME ) + { + Sequence< util::DateTime > oldValue; + aOldProps[i].Value >>= oldValue; + // We only edit hours and minutes + // don't compare NanoSeconds and Seconds + for ( auto& rDateTime : asNonConstRange(oldValue) ) + { + rDateTime.NanoSeconds = 0; + rDateTime.Seconds = 0; + } + Sequence< util::DateTime > newValue; + aNewProps[i].Value >>= newValue; + if ( oldValue != newValue ) + { + modifiedNum++; + changedProps.push_back( aNewProps[i] ); + } + } + else if ( aOldProps[i].Value != aNewProps[i].Value ) + { + modifiedNum++; + changedProps.push_back( aNewProps[i] ); + } + } + } + Sequence< document::CmisProperty> aModifiedProps( comphelper::containerToSequence(changedProps) ); + pInfo->SetCmisProperties( aModifiedProps ); + rSet->Put( *pInfo ); + if ( bMustDelete ) + delete pInfo; + } + + return modifiedNum; +} + +void SfxCmisPropertiesPage::Reset( const SfxItemSet* rItemSet ) +{ + m_xPropertiesCtrl->ClearAllLines(); + const SfxDocumentInfoItem& rInfoItem = rItemSet->Get(SID_DOCINFO); + uno::Sequence< document::CmisProperty > aCmisProps = rInfoItem.GetCmisProperties(); + for ( auto& rCmisProp : asNonConstRange(aCmisProps) ) + { + m_xPropertiesCtrl->AddLine(rCmisProp.Id, + rCmisProp.Name, + rCmisProp.Type, + rCmisProp.Updatable, + rCmisProp.Required, + rCmisProp.MultiValued, + rCmisProp.OpenChoice, + rCmisProp.Choices, + rCmisProp.Value); + } +} + +DeactivateRC SfxCmisPropertiesPage::DeactivatePage( SfxItemSet* /*pSet*/ ) +{ + return DeactivateRC::LeavePage; +} + +std::unique_ptr<SfxTabPage> SfxCmisPropertiesPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rItemSet) +{ + return std::make_unique<SfxCmisPropertiesPage>(pPage, pController, *rItemSet); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/dockwin.cxx b/sfx2/source/dialog/dockwin.cxx new file mode 100644 index 000000000..4329b2d90 --- /dev/null +++ b/sfx2/source/dialog/dockwin.cxx @@ -0,0 +1,1543 @@ +/* -*- 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 <svl/eitem.hxx> +#include <svl/solar.hrc> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/debug.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> + +#include <sfx2/dockwin.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <workwin.hxx> +#include <splitwin.hxx> +#include <sfx2/viewsh.hxx> + +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/ui/theWindowStateConfiguration.hpp> +#include <com/sun/star/ui/theWindowContentFactoryManager.hpp> + +#define MAX_TOGGLEAREA_WIDTH 20 +#define MAX_TOGGLEAREA_HEIGHT 20 + +using namespace ::com::sun::star; + +// If you want to change the number you also have to: +// - Add new slot ids to sfxsids.hrc +// - Add new slots to frmslots.sdi +// - Add new slot definitions to sfx.sdi +const int NUM_OF_DOCKINGWINDOWS = 10; + +namespace { + +class SfxTitleDockingWindow : public SfxDockingWindow +{ + VclPtr<vcl::Window> m_pWrappedWindow; + +public: + SfxTitleDockingWindow( + SfxBindings* pBindings , + SfxChildWindow* pChildWin , + vcl::Window* pParent , + WinBits nBits); + virtual ~SfxTitleDockingWindow() override; + virtual void dispose() override; + + vcl::Window* GetWrappedWindow() const { return m_pWrappedWindow; } + void SetWrappedWindow(vcl::Window* const pWindow); + + virtual void StateChanged( StateChangedType nType ) override; + virtual void Resize() override; + virtual void Resizing( Size& rSize ) override; +}; + + struct WindowState + { + OUString sTitle; + }; +} + +static bool lcl_getWindowState( const uno::Reference< container::XNameAccess >& xWindowStateMgr, const OUString& rResourceURL, WindowState& rWindowState ) +{ + bool bResult = false; + + try + { + uno::Any a; + uno::Sequence< beans::PropertyValue > aWindowState; + a = xWindowStateMgr->getByName( rResourceURL ); + if ( a >>= aWindowState ) + { + for ( const auto& rProp : std::as_const(aWindowState) ) + { + if ( rProp.Name == "UIName" ) + { + rProp.Value >>= rWindowState.sTitle; + } + } + } + + bResult = true; + } + catch ( container::NoSuchElementException& ) + { + bResult = false; + } + + return bResult; +} + +SfxDockingWrapper::SfxDockingWrapper( vcl::Window* pParentWnd , + sal_uInt16 nId , + SfxBindings* pBindings , + SfxChildWinInfo* pInfo ) + : SfxChildWindow( pParentWnd , nId ) +{ + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + VclPtr<SfxTitleDockingWindow> pTitleDockWindow = VclPtr<SfxTitleDockingWindow>::Create( pBindings, this, pParentWnd, + WB_STDDOCKWIN | WB_CLIPCHILDREN | WB_SIZEABLE | WB_3DLOOK); + SetWindow( pTitleDockWindow ); + + // Use factory manager to retrieve XWindow factory. That can be used to instantiate + // the real window factory. + uno::Reference< lang::XSingleComponentFactory > xFactoryMgr = ui::theWindowContentFactoryManager::get(xContext); + + SfxDispatcher* pDispatcher = pBindings->GetDispatcher(); + uno::Reference< frame::XFrame > xFrame = pDispatcher->GetFrame()->GetFrame().GetFrameInterface(); + // create a resource URL from the nId provided by the sfx2 + OUString aResourceURL = "private:resource/dockingwindow/" + OUString::number(nId); + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"Frame", uno::Any(xFrame)}, + {"ResourceURL", uno::Any(aResourceURL)}, + })); + + uno::Reference< awt::XWindow > xWindow; + try + { + xWindow.set( + xFactoryMgr->createInstanceWithArgumentsAndContext( aArgs, xContext ), + uno::UNO_QUERY ); + + static uno::WeakReference< frame::XModuleManager2 > s_xModuleManager; + + uno::Reference< frame::XModuleManager2 > xModuleManager( s_xModuleManager ); + if ( !xModuleManager.is() ) + { + xModuleManager = frame::ModuleManager::create(xContext); + s_xModuleManager = xModuleManager; + } + + static uno::WeakReference< container::XNameAccess > s_xWindowStateConfiguration; + + uno::Reference< container::XNameAccess > xWindowStateConfiguration( s_xWindowStateConfiguration ); + if ( !xWindowStateConfiguration.is() ) + { + xWindowStateConfiguration = ui::theWindowStateConfiguration::get( xContext ); + s_xWindowStateConfiguration = xWindowStateConfiguration; + } + + OUString sModuleIdentifier = xModuleManager->identify( xFrame ); + + uno::Reference< container::XNameAccess > xModuleWindowState( + xWindowStateConfiguration->getByName( sModuleIdentifier ), + uno::UNO_QUERY ); + if ( xModuleWindowState.is() ) + { + WindowState aDockWinState; + if ( lcl_getWindowState( xModuleWindowState, aResourceURL, aDockWinState )) + pTitleDockWindow->SetText( aDockWinState.sTitle ); + } + } + catch ( beans::UnknownPropertyException& ) + { + } + catch ( uno::RuntimeException& ) + { + } + catch ( uno::Exception& ) + { + } + + VclPtr<vcl::Window> pContentWindow = VCLUnoHelper::GetWindow(xWindow); + if ( pContentWindow ) + pContentWindow->SetStyle( pContentWindow->GetStyle() | WB_DIALOGCONTROL | WB_CHILDDLGCTRL ); + pTitleDockWindow->SetWrappedWindow(pContentWindow); + + GetWindow()->SetOutputSizePixel( Size( 270, 240 ) ); + + static_cast<SfxDockingWindow*>( GetWindow() )->Initialize( pInfo ); + SetHideNotDelete( true ); +} + +std::unique_ptr<SfxChildWindow> SfxDockingWrapper::CreateImpl(vcl::Window *pParent, sal_uInt16 nId, + SfxBindings *pBindings, SfxChildWinInfo* pInfo) +{ + return std::make_unique<SfxDockingWrapper>(pParent, nId, pBindings, pInfo); +} + +void SfxDockingWrapper::RegisterChildWindow (bool bVis, SfxModule *pMod, SfxChildWindowFlags nFlags) +{ + // pre-register a couple of docking windows + for (int i=0; i < NUM_OF_DOCKINGWINDOWS; i++ ) + { + sal_uInt16 nID = sal_uInt16(SID_DOCKWIN_START+i); + SfxChildWinFactory aFact( SfxDockingWrapper::CreateImpl, nID, 0xffff ); + aFact.aInfo.nFlags |= nFlags; + aFact.aInfo.bVisible = bVis; + SfxChildWindow::RegisterChildWindow(pMod, aFact); + } +} + +SfxChildWinInfo SfxDockingWrapper::GetInfo() const +{ + SfxChildWinInfo aInfo = SfxChildWindow::GetInfo(); + static_cast<SfxDockingWindow*>(GetWindow())->FillInfo( aInfo ); + return aInfo; +}; + +SfxTitleDockingWindow::SfxTitleDockingWindow(SfxBindings* pBind, SfxChildWindow* pChildWin, + vcl::Window* pParent, WinBits nBits) + : SfxDockingWindow(pBind, pChildWin, pParent, nBits) + , m_pWrappedWindow(nullptr) +{ +} + +SfxTitleDockingWindow::~SfxTitleDockingWindow() +{ + disposeOnce(); +} + +void SfxTitleDockingWindow::dispose() +{ + m_pWrappedWindow.disposeAndClear(); + SfxDockingWindow::dispose(); +} + +void SfxTitleDockingWindow::SetWrappedWindow( vcl::Window* const pWindow ) +{ + m_pWrappedWindow = pWindow; + if (m_pWrappedWindow) + { + m_pWrappedWindow->SetParent(this); + m_pWrappedWindow->SetSizePixel( GetOutputSizePixel() ); + m_pWrappedWindow->Show(); + } +} + +void SfxTitleDockingWindow::StateChanged( StateChangedType nType ) +{ + if ( nType == StateChangedType::InitShow ) + { + vcl::Window* pWindow = GetWrappedWindow(); + if ( pWindow ) + { + pWindow->SetSizePixel( GetOutputSizePixel() ); + pWindow->Show(); + } + } + + SfxDockingWindow::StateChanged(nType); +} + +void SfxTitleDockingWindow::Resize() +{ + SfxDockingWindow::Resize(); + if (m_pWrappedWindow) + m_pWrappedWindow->SetSizePixel( GetOutputSizePixel() ); +} + +void SfxTitleDockingWindow::Resizing( Size &rSize ) +{ + SfxDockingWindow::Resizing( rSize ); + if (m_pWrappedWindow) + m_pWrappedWindow->SetSizePixel( GetOutputSizePixel() ); +} + +static bool lcl_checkDockingWindowID( sal_uInt16 nID ) +{ + return nID >= SID_DOCKWIN_START && nID < o3tl::make_unsigned(SID_DOCKWIN_START+NUM_OF_DOCKINGWINDOWS); +} + +static SfxWorkWindow* lcl_getWorkWindowFromXFrame( const uno::Reference< frame::XFrame >& rFrame ) +{ + // We need to find the corresponding SfxFrame of our XFrame + SfxFrame* pFrame = SfxFrame::GetFirst(); + SfxFrame* pXFrame = nullptr; + while ( pFrame ) + { + uno::Reference< frame::XFrame > xViewShellFrame( pFrame->GetFrameInterface() ); + if ( xViewShellFrame == rFrame ) + { + pXFrame = pFrame; + break; + } + else + pFrame = SfxFrame::GetNext( *pFrame ); + } + + // If we have a SfxFrame we can retrieve the work window (Sfx layout manager for docking windows) + if ( pXFrame ) + return pXFrame->GetWorkWindow_Impl(); + else + return nullptr; +} + +/** Factory function used by the framework layout manager to "create" a docking window with a special name. + The string rDockingWindowName MUST BE a valid ID! The ID is pre-defined by a certain slot range located + in sfxsids.hrc (currently SID_DOCKWIN_START = 9800). +*/ +void SfxDockingWindowFactory( const uno::Reference< frame::XFrame >& rFrame, std::u16string_view rDockingWindowName ) +{ + SolarMutexGuard aGuard; + sal_uInt16 nID = sal_uInt16(o3tl::toInt32(rDockingWindowName)); + + // Check the range of the provided ID otherwise nothing will happen + if ( !lcl_checkDockingWindowID( nID )) + return; + + SfxWorkWindow* pWorkWindow = lcl_getWorkWindowFromXFrame( rFrame ); + if ( pWorkWindow ) + { + SfxChildWindow* pChildWindow = pWorkWindow->GetChildWindow_Impl(nID); + if ( !pChildWindow ) + { + // Register window at the workwindow child window list + pWorkWindow->SetChildWindow_Impl( nID, true, false ); + } + } +} + +/** Function used by the framework layout manager to determine the visibility state of a docking window with + a special name. The string rDockingWindowName MUST BE a valid ID! The ID is pre-defined by a certain slot + range located in sfxsids.hrc (currently SID_DOCKWIN_START = 9800). +*/ +bool IsDockingWindowVisible( const uno::Reference< frame::XFrame >& rFrame, std::u16string_view rDockingWindowName ) +{ + SolarMutexGuard aGuard; + + sal_uInt16 nID = sal_uInt16(o3tl::toInt32(rDockingWindowName)); + + // Check the range of the provided ID otherwise nothing will happen + if ( lcl_checkDockingWindowID( nID )) + { + SfxWorkWindow* pWorkWindow = lcl_getWorkWindowFromXFrame( rFrame ); + if ( pWorkWindow ) + { + SfxChildWindow* pChildWindow = pWorkWindow->GetChildWindow_Impl(nID); + if ( pChildWindow ) + return true; + } + } + + return false; +} + +class SfxDockingWindow_Impl +{ +friend class SfxDockingWindow; + + SfxChildAlignment eLastAlignment; + SfxChildAlignment eDockAlignment; + bool bConstructed; + Size aMinSize; + VclPtr<SfxSplitWindow> pSplitWin; + Idle aMoveIdle; + + // The following members are only valid in the time from startDocking to + // EndDocking: + Size aSplitSize; + tools::Long nHorizontalSize; + tools::Long nVerticalSize; + sal_uInt16 nLine; + sal_uInt16 nPos; + sal_uInt16 nDockLine; + sal_uInt16 nDockPos; + bool bNewLine; + bool bDockingPrevented; + OString aWinState; + + explicit SfxDockingWindow_Impl(SfxDockingWindow *pBase); + SfxChildAlignment GetLastAlignment() const + { return eLastAlignment; } + void SetLastAlignment(SfxChildAlignment eAlign) + { eLastAlignment = eAlign; } + SfxChildAlignment GetDockAlignment() const + { return eDockAlignment; } + void SetDockAlignment(SfxChildAlignment eAlign) + { eDockAlignment = eAlign; } +}; + +SfxDockingWindow_Impl::SfxDockingWindow_Impl(SfxDockingWindow* pBase) + :eLastAlignment(SfxChildAlignment::NOALIGNMENT) + ,eDockAlignment(SfxChildAlignment::NOALIGNMENT) + ,bConstructed(false) + ,pSplitWin(nullptr) + ,aMoveIdle( "sfx::SfxDockingWindow_Impl aMoveIdle" ) + ,nHorizontalSize(0) + ,nVerticalSize(0) + ,nLine(0) + ,nPos(0) + ,nDockLine(0) + ,nDockPos(0) + ,bNewLine(false) + ,bDockingPrevented(false) +{ + aMoveIdle.SetPriority(TaskPriority::RESIZE); + aMoveIdle.SetInvokeHandler(LINK(pBase,SfxDockingWindow,TimerHdl)); +} + +/* [Description] + + This virtual method of the class FloatingWindow keeps track of changes in + FloatingSize. If this method is overridden by a derived class, + then the FloatingWindow: Resize() must also be called. +*/ +void SfxDockingWindow::Resize() +{ + ResizableDockingWindow::Resize(); + Invalidate(); + if ( !pImpl || !pImpl->bConstructed || !pMgr ) + return; + + if ( IsFloatingMode() ) + { + // start timer for saving window status information + pImpl->aMoveIdle.Start(); + } + else + { + Size aSize( GetSizePixel() ); + switch ( pImpl->GetDockAlignment() ) + { + case SfxChildAlignment::LEFT: + case SfxChildAlignment::FIRSTLEFT: + case SfxChildAlignment::LASTLEFT: + case SfxChildAlignment::RIGHT: + case SfxChildAlignment::FIRSTRIGHT: + case SfxChildAlignment::LASTRIGHT: + pImpl->nHorizontalSize = aSize.Width(); + pImpl->aSplitSize = aSize; + break; + case SfxChildAlignment::TOP: + case SfxChildAlignment::LOWESTTOP: + case SfxChildAlignment::HIGHESTTOP: + case SfxChildAlignment::BOTTOM: + case SfxChildAlignment::HIGHESTBOTTOM: + case SfxChildAlignment::LOWESTBOTTOM: + pImpl->nVerticalSize = aSize.Height(); + pImpl->aSplitSize = aSize; + break; + default: + break; + } + } +} + +/* [Description] + + This virtual method of the class DockingWindow makes it possible to + intervene in the switching of the floating mode. + If this method is overridden by a derived class, + then the SfxDockingWindow::PrepareToggleFloatingMode() must be called + afterwards, if not FALSE is returned. +*/ +bool SfxDockingWindow::PrepareToggleFloatingMode() +{ + if (!pImpl || !pImpl->bConstructed) + return true; + + if ( (Application::IsInModalMode() && IsFloatingMode()) || !pMgr ) + return false; + + if ( pImpl->bDockingPrevented ) + return false; + + if (!IsFloatingMode()) + { + // Test, if FloatingMode is permitted. + if ( CheckAlignment(GetAlignment(),SfxChildAlignment::NOALIGNMENT) != SfxChildAlignment::NOALIGNMENT ) + return false; + + if ( pImpl->pSplitWin ) + { + // The DockingWindow is inside a SplitWindow and will be teared of. + pImpl->pSplitWin->RemoveWindow(this/*, sal_False*/); + pImpl->pSplitWin = nullptr; + } + } + else if ( pMgr ) + { + pImpl->aWinState = GetFloatingWindow()->GetWindowState(); + + // Test if it is allowed to dock, + if (CheckAlignment(GetAlignment(),pImpl->GetLastAlignment()) == SfxChildAlignment::NOALIGNMENT) + return false; + + // Test, if the Workwindow allows for docking at the moment. + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + if ( !pWorkWin->IsDockingAllowed() || !pWorkWin->IsInternalDockingAllowed() ) + return false; + } + + return true; +} + +/* [Description] + + This virtual method of the DockingWindow class sets the internal data of + the SfxDockingWindow and ensures the correct alignment on the parent window. + Through PrepareToggleFloatMode and Initialize it is ensured that + pImpl-> GetLastAlignment() always delivers an allowed alignment. If this + method is overridden by a derived class, then first the + SfxDockingWindow::ToggleFloatingMode() must be called. +*/ +void SfxDockingWindow::ToggleFloatingMode() +{ + if ( !pImpl || !pImpl->bConstructed || !pMgr ) + return; // No Handler call + + // Remember old alignment and then switch. + // SV has already switched, but the alignment SfxDockingWindow is still + // the old one. What I was before? + SfxChildAlignment eLastAlign = GetAlignment(); + + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + + if (IsFloatingMode()) + { + SetAlignment(SfxChildAlignment::NOALIGNMENT); + if ( !pImpl->aWinState.isEmpty() ) + GetFloatingWindow()->SetWindowState( pImpl->aWinState ); + else + GetFloatingWindow()->SetOutputSizePixel( GetFloatingSize() ); + } + else + { + if (pImpl->GetDockAlignment() == eLastAlign) + { + // If ToggleFloatingMode was called, but the DockAlignment still + // is unchanged, then this means that it must have been a toggling + // through DClick, so use last alignment + SetAlignment (pImpl->GetLastAlignment()); + } + else + { + + // Toggling was triggered by dragging + pImpl->nLine = pImpl->nDockLine; + pImpl->nPos = pImpl->nDockPos; + SetAlignment (pImpl->GetDockAlignment()); + } + + // The DockingWindow is now in a SplitWindow + pImpl->pSplitWin = pWorkWin->GetSplitWindow_Impl(GetAlignment()); + + // The LastAlignment is still the last docked + SfxSplitWindow *pSplit = pWorkWin->GetSplitWindow_Impl(pImpl->GetLastAlignment()); + + DBG_ASSERT( pSplit, "LastAlignment is not correct!" ); + if ( pSplit && pSplit != pImpl->pSplitWin ) + pSplit->ReleaseWindow_Impl(this); + if ( pImpl->GetDockAlignment() == eLastAlign ) + pImpl->pSplitWin->InsertWindow( this, pImpl->aSplitSize ); + else + pImpl->pSplitWin->InsertWindow( this, pImpl->aSplitSize, pImpl->nLine, pImpl->nPos, pImpl->bNewLine ); + if ( !pImpl->pSplitWin->IsFadeIn() ) + pImpl->pSplitWin->FadeIn(); + } + + // Keep the old alignment for the next toggle; set it only now due to the + // unregister SplitWindow! + pImpl->SetLastAlignment(eLastAlign); + + // Reset DockAlignment, if EndDocking is still called + pImpl->SetDockAlignment(GetAlignment()); + + // Dock or undock SfxChildWindow correctly. + pWorkWin->ConfigChild_Impl( SfxChildIdentifier::SPLITWINDOW, SfxDockingConfig::TOGGLEFLOATMODE, pMgr->GetType() ); +} + +/* [Description] + + This virtual method of the DockingWindow class takes the inner and outer + docking rectangle from the parent window. If this method is overridden by + a derived class, then SfxDockingWindow:StartDocking() has to be called at + the end. +*/ +void SfxDockingWindow::StartDocking() +{ + if ( !pImpl || !pImpl->bConstructed || !pMgr ) + return; + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + pWorkWin->ConfigChild_Impl( SfxChildIdentifier::SPLITWINDOW, SfxDockingConfig::SETDOCKINGRECTS, pMgr->GetType() ); + pImpl->SetDockAlignment(GetAlignment()); + + if ( pImpl->pSplitWin ) + { + // Get the current docking data + pImpl->pSplitWin->GetWindowPos(this, pImpl->nLine, pImpl->nPos); + pImpl->nDockLine = pImpl->nLine; + pImpl->nDockPos = pImpl->nPos; + pImpl->bNewLine = false; + } +} + +/* [Description] + + This virtual method of the DockingWindow class calculates the current + tracking rectangle. For this purpose the method CalcAlignment(RPOs, rRect) + is used, the behavior can be influenced by the derived classes (see below). + This method should if possible not be overwritten. +*/ +bool SfxDockingWindow::Docking( const Point& rPos, tools::Rectangle& rRect ) +{ + if ( Application::IsInModalMode() ) + return true; + + if ( !pImpl || !pImpl->bConstructed || !pMgr ) + { + rRect.SetSize( Size() ); + return IsFloatingMode(); + } + + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + if ( pImpl->bDockingPrevented || !pWorkWin->IsInternalDockingAllowed() ) + return false; + + bool bFloatMode = false; + + if ( GetOuterRect().Contains( rPos ) ) + { + // Mouse within OuterRect: calculate Alignment and Rectangle + SfxChildAlignment eAlign = CalcAlignment(rPos, rRect); + if (eAlign == SfxChildAlignment::NOALIGNMENT) + bFloatMode = true; + pImpl->SetDockAlignment(eAlign); + } + else + { + // Mouse is not within OuterRect: must be FloatingWindow + // Is this allowed? + if (CheckAlignment(pImpl->GetDockAlignment(),SfxChildAlignment::NOALIGNMENT) != SfxChildAlignment::NOALIGNMENT) + return false; + bFloatMode = true; + if ( SfxChildAlignment::NOALIGNMENT != pImpl->GetDockAlignment() ) + { + // Due to a bug the rRect may only be changed when the + // alignment is changed! + pImpl->SetDockAlignment(SfxChildAlignment::NOALIGNMENT); + rRect.SetSize(CalcDockingSize(SfxChildAlignment::NOALIGNMENT)); + } + } + + return bFloatMode; +} + +/** Virtual method of the DockingWindow class ensures the correct alignment on + the parent window. If this method is overridden by a derived class, then + SfxDockingWindow::EndDocking() must be called first. +*/ +void SfxDockingWindow::EndDocking( const tools::Rectangle& rRect, bool bFloatMode ) +{ + if ( !pImpl || !pImpl->bConstructed || IsDockingCanceled() || !pMgr ) + return; + + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + + // If the alignment changes and the window is in a docked state in a + // SplitWindow, then it must be re-registered. If it is docked again, + // PrepareToggleFloatingMode() and ToggleFloatingMode() perform the + // re-registered + bool bReArrange = !bFloatMode; + + if ( bReArrange ) + { + if ( GetAlignment() != pImpl->GetDockAlignment() ) + { + // before Show() is called must the reassignment have been made, + // therefore the base class can not be called + if ( IsFloatingMode() ) + Show( false, ShowFlags::NoFocusChange ); + + // Set the size for toggling. + pImpl->aSplitSize = rRect.GetSize(); + if ( IsFloatingMode() ) + { + SetFloatingMode( bFloatMode ); + if ( IsFloatingMode() ) + Show( true, ShowFlags::NoFocusChange ); + } + else + { + pImpl->pSplitWin->RemoveWindow(this,false); + pImpl->nLine = pImpl->nDockLine; + pImpl->nPos = pImpl->nDockPos; + pImpl->pSplitWin->ReleaseWindow_Impl(this); + pImpl->pSplitWin = pWorkWin->GetSplitWindow_Impl(pImpl->GetDockAlignment()); + pImpl->pSplitWin->InsertWindow( this, pImpl->aSplitSize, pImpl->nDockLine, pImpl->nDockPos, pImpl->bNewLine ); + if ( !pImpl->pSplitWin->IsFadeIn() ) + pImpl->pSplitWin->FadeIn(); + } + } + else if ( pImpl->nLine != pImpl->nDockLine || pImpl->nPos != pImpl->nDockPos || pImpl->bNewLine ) + { + // Moved within Splitwindows + if ( pImpl->nLine != pImpl->nDockLine ) + pImpl->aSplitSize = rRect.GetSize(); + pImpl->pSplitWin->MoveWindow( this, pImpl->aSplitSize, pImpl->nDockLine, pImpl->nDockPos, pImpl->bNewLine ); + } + } + else + { + ResizableDockingWindow::EndDocking(rRect, bFloatMode); + } + + SetAlignment( IsFloatingMode() ? SfxChildAlignment::NOALIGNMENT : pImpl->GetDockAlignment() ); +} + +/* [Description] + + Virtual method of the DockingWindow class. Here, the interactive resize in + FloatingMode can be influenced, for example by only allowing for discrete + values for width and / or height. The base implementation prevents that the + output size is smaller than one set with SetMinOutputSizePixel(). +*/ +void SfxDockingWindow::Resizing( Size& /*rSize*/ ) +{ + +} + +/* [Description] + + Constructor for the SfxDockingWindow class. A SfxChildWindow will be + required because the docking is implemented in Sfx through SfxChildWindows. +*/ +SfxDockingWindow::SfxDockingWindow( SfxBindings *pBindinx, SfxChildWindow *pCW, + vcl::Window* pParent, WinBits nWinBits) + : ResizableDockingWindow(pParent, nWinBits) + , pBindings(pBindinx) + , pMgr(pCW) +{ + pImpl.reset(new SfxDockingWindow_Impl(this)); +} + +/** Constructor for the SfxDockingWindow class. A SfxChildWindow will be + required because the docking is implemented in Sfx through SfxChildWindows. +*/ +SfxDockingWindow::SfxDockingWindow( SfxBindings *pBindinx, SfxChildWindow *pCW, + vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription) + : ResizableDockingWindow(pParent) + , pBindings(pBindinx) + , pMgr(pCW) +{ + m_xBuilder = Application::CreateInterimBuilder(m_xBox, rUIXMLDescription, true); + m_xContainer = m_xBuilder->weld_box(rID); + + pImpl.reset(new SfxDockingWindow_Impl(this)); +} + +/** Initialization of the SfxDockingDialog class via a SfxChildWinInfo. + The initialization is done only in a 2nd step after the constructor, this + constructor should be called from the derived class or from the + SfxChildWindows. +*/ +void SfxDockingWindow::Initialize(SfxChildWinInfo *pInfo) +{ + if ( !pMgr ) + { + pImpl->SetDockAlignment( SfxChildAlignment::NOALIGNMENT ); + pImpl->bConstructed = true; + return; + } + + if (pInfo && (pInfo->nFlags & SfxChildWindowFlags::FORCEDOCK)) + pImpl->bDockingPrevented = true; + + pImpl->aSplitSize = GetOutputSizePixel(); + if ( !GetFloatingSize().Width() ) + { + Size aMinSize( GetMinOutputSizePixel() ); + SetFloatingSize( pImpl->aSplitSize ); + if ( pImpl->aSplitSize.Width() < aMinSize.Width() ) + pImpl->aSplitSize.setWidth( aMinSize.Width() ); + if ( pImpl->aSplitSize.Height() < aMinSize.Height() ) + pImpl->aSplitSize.setHeight( aMinSize.Height() ); + } + + bool bVertHorzRead( false ); + if (pInfo && !pInfo->aExtraString.isEmpty()) + { + // get information about alignment, split size and position in SplitWindow + OUString aStr; + sal_Int32 nPos = pInfo->aExtraString.indexOf("AL:"); + if ( nPos != -1 ) + { + // alignment information + sal_Int32 n1 = pInfo->aExtraString.indexOf('(', nPos); + if ( n1 != -1 ) + { + sal_Int32 n2 = pInfo->aExtraString.indexOf(')', n1); + if ( n2 != -1 ) + { + // extract alignment information from extrastring + aStr = pInfo->aExtraString.copy(nPos, n2 - nPos + 1); + pInfo->aExtraString = pInfo->aExtraString.replaceAt(nPos, n2 - nPos + 1, u""); + aStr = aStr.replaceAt(nPos, n1-nPos+1, u""); + } + } + } + + if ( !aStr.isEmpty() ) + { + // accept window state only if alignment is also set + pImpl->aWinState = pInfo->aWinState; + + // check for valid alignment + SfxChildAlignment eLocalAlignment = static_cast<SfxChildAlignment>(static_cast<sal_uInt16>(aStr.toInt32())); + bool bIgnoreFloatConfig = (eLocalAlignment == SfxChildAlignment::NOALIGNMENT && + !StyleSettings::GetDockingFloatsSupported()); + if (pImpl->bDockingPrevented || bIgnoreFloatConfig) + // docking prevented, ignore old configuration and take alignment from default + aStr.clear(); + else + SetAlignment( eLocalAlignment ); + + SfxChildAlignment eAlign = CheckAlignment(GetAlignment(),GetAlignment()); + if ( eAlign != GetAlignment() ) + { + OSL_FAIL("Invalid Alignment!"); + SetAlignment( eAlign ); + aStr.clear(); + } + + // get last alignment (for toggling) + nPos = aStr.indexOf(','); + if ( nPos != -1 ) + { + aStr = aStr.copy(nPos+1); + pImpl->SetLastAlignment( static_cast<SfxChildAlignment>(static_cast<sal_uInt16>(aStr.toInt32())) ); + } + + nPos = aStr.indexOf(','); + if ( nPos != -1 ) + { + // get split size and position in SplitWindow + Point aPos; + aStr = aStr.copy(nPos+1); + if ( GetPosSizeFromString( aStr, aPos, pImpl->aSplitSize ) ) + { + pImpl->nLine = pImpl->nDockLine = static_cast<sal_uInt16>(aPos.X()); + pImpl->nPos = pImpl->nDockPos = static_cast<sal_uInt16>(aPos.Y()); + pImpl->nVerticalSize = pImpl->aSplitSize.Height(); + pImpl->nHorizontalSize = pImpl->aSplitSize.Width(); + if ( GetSplitSizeFromString( aStr, pImpl->aSplitSize )) + bVertHorzRead = true; + } + } + } + else { + OSL_FAIL( "Information is missing!" ); + } + } + + if ( !bVertHorzRead ) + { + pImpl->nVerticalSize = pImpl->aSplitSize.Height(); + pImpl->nHorizontalSize = pImpl->aSplitSize.Width(); + } + + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + if ( GetAlignment() != SfxChildAlignment::NOALIGNMENT ) + { + // check if SfxWorkWindow is able to allow docking at its border + if ( + !pWorkWin->IsDockingAllowed() || + !pWorkWin->IsInternalDockingAllowed() || + ( (GetFloatStyle() & WB_STANDALONE) && Application::IsInModalMode()) ) + { + SetAlignment( SfxChildAlignment::NOALIGNMENT ); + } + } + + // detect floating mode + // toggling mode will not execute code in handlers, because pImpl->bConstructed is not set yet + bool bFloatMode = IsFloatingMode(); + if ( bFloatMode != (GetAlignment() == SfxChildAlignment::NOALIGNMENT) ) + { + bFloatMode = !bFloatMode; + SetFloatingMode( bFloatMode ); + if ( bFloatMode ) + { + if ( !pImpl->aWinState.isEmpty() ) + GetFloatingWindow()->SetWindowState( pImpl->aWinState ); + else + GetFloatingWindow()->SetOutputSizePixel( GetFloatingSize() ); + } + } + + if ( IsFloatingMode() ) + { + // validate last alignment + SfxChildAlignment eLastAlign = pImpl->GetLastAlignment(); + if ( eLastAlign == SfxChildAlignment::NOALIGNMENT) + eLastAlign = CheckAlignment(eLastAlign, SfxChildAlignment::LEFT); + if ( eLastAlign == SfxChildAlignment::NOALIGNMENT) + eLastAlign = CheckAlignment(eLastAlign, SfxChildAlignment::RIGHT); + if ( eLastAlign == SfxChildAlignment::NOALIGNMENT) + eLastAlign = CheckAlignment(eLastAlign, SfxChildAlignment::TOP); + if ( eLastAlign == SfxChildAlignment::NOALIGNMENT) + eLastAlign = CheckAlignment(eLastAlign, SfxChildAlignment::BOTTOM); + pImpl->SetLastAlignment(eLastAlign); + } + else + { + // docked window must have NOALIGNMENT as last alignment + pImpl->SetLastAlignment(SfxChildAlignment::NOALIGNMENT); + + pImpl->pSplitWin = pWorkWin->GetSplitWindow_Impl(GetAlignment()); + pImpl->pSplitWin->InsertWindow(this, pImpl->aSplitSize); + } + + // save alignment + pImpl->SetDockAlignment( GetAlignment() ); +} + +void SfxDockingWindow::Initialize_Impl() +{ + if ( !pMgr ) + { + pImpl->bConstructed = true; + return; + } + + SystemWindow* pFloatWin = GetFloatingWindow(); + bool bSet = false; + if ( pFloatWin ) + { + bSet = !pFloatWin->IsDefaultPos(); + } + else + { + Point aPos = GetFloatingPos(); + if ( aPos != Point() ) + bSet = true; + } + + if ( !bSet) + { + SfxViewFrame *pFrame = pBindings->GetDispatcher_Impl()->GetFrame(); + vcl::Window* pEditWin = pFrame->GetViewShell()->GetWindow(); + Point aPos = pEditWin->OutputToScreenPixel( pEditWin->GetPosPixel() ); + aPos = GetParent()->ScreenToOutputPixel( aPos ); + SetFloatingPos( aPos ); + } + + if ( pFloatWin ) + { + // initialize floating window + if ( pImpl->aWinState.isEmpty() ) + // window state never set before, get if from defaults + pImpl->aWinState = pFloatWin->GetWindowState(); + + // trick: use VCL method SetWindowState to adjust position and size + pFloatWin->SetWindowState( pImpl->aWinState ); + Size aSize(pFloatWin->GetSizePixel()); + + // remember floating size for calculating alignment and tracking rectangle + SetFloatingSize(aSize); + + } + + // allow calling of docking handlers + pImpl->bConstructed = true; +} + +/** Fills a SfxChildWinInfo with specific data from SfxDockingWindow, + so that it can be written in the INI file. It is assumed that rinfo + receives all other possible relevant data in the ChildWindow class. + Insertions are marked with size and the ZoomIn flag. + If this method is overridden, the base implementation must be called first. +*/ +void SfxDockingWindow::FillInfo(SfxChildWinInfo& rInfo) const +{ + if (!pMgr || !pImpl) + return; + + if (GetFloatingWindow() && pImpl->bConstructed) + pImpl->aWinState = GetFloatingWindow()->GetWindowState(); + + rInfo.aWinState = pImpl->aWinState; + rInfo.aExtraString = "AL:("; + rInfo.aExtraString += OUString::number(static_cast<sal_uInt16>(GetAlignment())); + rInfo.aExtraString += ","; + rInfo.aExtraString += OUString::number (static_cast<sal_uInt16>(pImpl->GetLastAlignment())); + + Point aPos(pImpl->nLine, pImpl->nPos); + rInfo.aExtraString += ","; + rInfo.aExtraString += OUString::number( aPos.X() ); + rInfo.aExtraString += "/"; + rInfo.aExtraString += OUString::number( aPos.Y() ); + rInfo.aExtraString += "/"; + rInfo.aExtraString += OUString::number( pImpl->nHorizontalSize ); + rInfo.aExtraString += "/"; + rInfo.aExtraString += OUString::number( pImpl->nVerticalSize ); + rInfo.aExtraString += ","; + rInfo.aExtraString += OUString::number( pImpl->aSplitSize.Width() ); + rInfo.aExtraString += ";"; + rInfo.aExtraString += OUString::number( pImpl->aSplitSize.Height() ); + + rInfo.aExtraString += ")"; +} + +SfxDockingWindow::~SfxDockingWindow() +{ + disposeOnce(); +} + +void SfxDockingWindow::dispose() +{ + ReleaseChildWindow_Impl(); + pImpl.reset(); + m_xContainer.reset(); + m_xBuilder.reset(); + ResizableDockingWindow::dispose(); +} + +void SfxDockingWindow::ReleaseChildWindow_Impl() +{ + if ( pMgr && pMgr->GetFrame() == pBindings->GetActiveFrame() ) + pBindings->SetActiveFrame( nullptr ); + + if ( pMgr && pImpl->pSplitWin && pImpl->pSplitWin->IsItemValid( GetType() ) ) + pImpl->pSplitWin->RemoveWindow(this); + + pMgr=nullptr; +} + +/** This method calculates a resulting alignment for the given mouse position + and tracking rectangle. When changing the alignment it can also be that + the tracking rectangle is changed, so that an altered rectangle is + returned. The user of this class can influence behaviour of this method, + and thus the behavior of his DockinWindow class when docking where the + called virtual method: + + SfxDockingWindow::CalcDockingSize (SfxChildAlignment eAlign) + + is overridden (see below). +*/ +SfxChildAlignment SfxDockingWindow::CalcAlignment(const Point& rPos, tools::Rectangle& rRect) +{ + // calculate hypothetical sizes for different modes + Size aFloatingSize(CalcDockingSize(SfxChildAlignment::NOALIGNMENT)); + + // check if docking is permitted + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + if ( !pWorkWin->IsDockingAllowed() ) + { + rRect.SetSize( aFloatingSize ); + return pImpl->GetDockAlignment(); + } + + // calculate borders to shrink inner area before checking for intersection with tracking rectangle + tools::Long nLRBorder, nTBBorder; + + // take the smaller size of docked and floating mode + Size aBorderTmp = pImpl->aSplitSize; + if ( GetFloatingSize().Height() < aBorderTmp.Height() ) + aBorderTmp.setHeight( GetFloatingSize().Height() ); + if ( GetFloatingSize().Width() < aBorderTmp.Width() ) + aBorderTmp.setWidth( GetFloatingSize().Width() ); + + nLRBorder = aBorderTmp.Width(); + nTBBorder = aBorderTmp.Height(); + + // limit border to predefined constant values + if ( nLRBorder > MAX_TOGGLEAREA_WIDTH ) + nLRBorder = MAX_TOGGLEAREA_WIDTH; + if ( nTBBorder > MAX_TOGGLEAREA_WIDTH ) + nTBBorder = MAX_TOGGLEAREA_WIDTH; + + // shrink area for floating mode if possible + tools::Rectangle aInRect = GetInnerRect(); + if ( aInRect.GetWidth() > nLRBorder ) + aInRect.AdjustLeft(nLRBorder/2 ); + if ( aInRect.GetWidth() > nLRBorder ) + aInRect.AdjustRight( -(nLRBorder/2) ); + if ( aInRect.GetHeight() > nTBBorder ) + aInRect.AdjustTop(nTBBorder/2 ); + if ( aInRect.GetHeight() > nTBBorder ) + aInRect.AdjustBottom( -(nTBBorder/2) ); + + // calculate alignment resulting from docking rectangle + bool bBecomesFloating = false; + SfxChildAlignment eDockAlign = pImpl->GetDockAlignment(); + tools::Rectangle aDockingRect( rRect ); + if ( !IsFloatingMode() ) + { + // don't use tracking rectangle for alignment check, because it will be too large + // to get a floating mode as result - switch to floating size + // so the calculation only depends on the position of the rectangle, not the current + // docking state of the window + aDockingRect.SetSize( GetFloatingSize() ); + + // in this mode docking is never done by keyboard, so it's OK to use the mouse position + aDockingRect.SetPos( pWorkWin->GetWindow()->OutputToScreenPixel( pWorkWin->GetWindow()->GetPointerPosPixel() ) ); + } + + Point aPos = aDockingRect.TopLeft(); + tools::Rectangle aIntersect = GetOuterRect().GetIntersection( aDockingRect ); + if ( aIntersect.IsEmpty() ) + // docking rectangle completely outside docking area -> floating mode + bBecomesFloating = true; + else + { + // create a small test rect around the mouse position and use this one + // instead of the passed rRect to not dock too easily or by accident + tools::Rectangle aSmallDockingRect; + aSmallDockingRect.SetSize( Size( MAX_TOGGLEAREA_WIDTH, MAX_TOGGLEAREA_HEIGHT ) ); + Point aNewPos(rPos); + aNewPos.AdjustX( -(aSmallDockingRect.GetWidth()/2) ); + aNewPos.AdjustY( -(aSmallDockingRect.GetHeight()/2) ); + aSmallDockingRect.SetPos(aNewPos); + tools::Rectangle aIntersectRect = aInRect.GetIntersection( aSmallDockingRect ); + if ( aIntersectRect == aSmallDockingRect ) + // docking rectangle completely inside (shrunk) inner area -> floating mode + bBecomesFloating = true; + } + + if ( bBecomesFloating ) + { + eDockAlign = CheckAlignment(pImpl->GetDockAlignment(),SfxChildAlignment::NOALIGNMENT); + } + else + { + // docking rectangle is in the "sensible area" + Point aInPosTL( aPos.X()-aInRect.Left(), aPos.Y()-aInRect.Top() ); + Point aInPosBR( aPos.X()-aInRect.Left() + aDockingRect.GetWidth(), aPos.Y()-aInRect.Top() + aDockingRect.GetHeight() ); + Size aInSize = aInRect.GetSize(); + bool bNoChange = false; + + // check if alignment is still unchanged + switch ( GetAlignment() ) + { + case SfxChildAlignment::LEFT: + case SfxChildAlignment::FIRSTLEFT: + case SfxChildAlignment::LASTLEFT: + if (aInPosTL.X() <= 0) + { + eDockAlign = GetAlignment(); + bNoChange = true; + } + break; + case SfxChildAlignment::TOP: + case SfxChildAlignment::LOWESTTOP: + case SfxChildAlignment::HIGHESTTOP: + if ( aInPosTL.Y() <= 0) + { + eDockAlign = GetAlignment(); + bNoChange = true; + } + break; + case SfxChildAlignment::RIGHT: + case SfxChildAlignment::FIRSTRIGHT: + case SfxChildAlignment::LASTRIGHT: + if ( aInPosBR.X() >= aInSize.Width()) + { + eDockAlign = GetAlignment(); + bNoChange = true; + } + break; + case SfxChildAlignment::BOTTOM: + case SfxChildAlignment::LOWESTBOTTOM: + case SfxChildAlignment::HIGHESTBOTTOM: + if ( aInPosBR.Y() >= aInSize.Height()) + { + eDockAlign = GetAlignment(); + bNoChange = true; + } + break; + default: + break; + } + + if ( !bNoChange ) + { + // alignment will change, test alignment according to distance of the docking rectangles edges + bool bForbidden = true; + if ( aInPosTL.X() <= 0) + { + eDockAlign = CheckAlignment(pImpl->GetDockAlignment(),SfxChildAlignment::LEFT); + bForbidden = ( eDockAlign != SfxChildAlignment::LEFT && + eDockAlign != SfxChildAlignment::FIRSTLEFT && + eDockAlign != SfxChildAlignment::LASTLEFT ); + } + + if ( bForbidden && aInPosTL.Y() <= 0) + { + eDockAlign = CheckAlignment(pImpl->GetDockAlignment(),SfxChildAlignment::TOP); + bForbidden = ( eDockAlign != SfxChildAlignment::TOP && + eDockAlign != SfxChildAlignment::HIGHESTTOP && + eDockAlign != SfxChildAlignment::LOWESTTOP ); + } + + if ( bForbidden && aInPosBR.X() >= aInSize.Width()) + { + eDockAlign = CheckAlignment(pImpl->GetDockAlignment(),SfxChildAlignment::RIGHT); + bForbidden = ( eDockAlign != SfxChildAlignment::RIGHT && + eDockAlign != SfxChildAlignment::FIRSTRIGHT && + eDockAlign != SfxChildAlignment::LASTRIGHT ); + } + + if ( bForbidden && aInPosBR.Y() >= aInSize.Height()) + { + eDockAlign = CheckAlignment(pImpl->GetDockAlignment(),SfxChildAlignment::BOTTOM); + bForbidden = ( eDockAlign != SfxChildAlignment::BOTTOM && + eDockAlign != SfxChildAlignment::HIGHESTBOTTOM && + eDockAlign != SfxChildAlignment::LOWESTBOTTOM ); + } + + // the calculated alignment was rejected by the window -> take floating mode + if ( bForbidden ) + eDockAlign = CheckAlignment(pImpl->GetDockAlignment(),SfxChildAlignment::NOALIGNMENT); + } + } + + if ( eDockAlign == SfxChildAlignment::NOALIGNMENT ) + { + // In the FloatingMode the tracking rectangle will get the floating + // size. Due to a bug the rRect may only be changed when the + // alignment is changed! + if ( eDockAlign != pImpl->GetDockAlignment() ) + aDockingRect.SetSize( aFloatingSize ); + } + else + { + sal_uInt16 nLine, nPos; + SfxSplitWindow *pSplitWin = pWorkWin->GetSplitWindow_Impl(eDockAlign); + aPos = pSplitWin->ScreenToOutputPixel( aPos ); + if ( pSplitWin->GetWindowPos( aPos, nLine, nPos ) ) + { + // mouse over splitwindow, get line and position + pImpl->nDockLine = nLine; + pImpl->nDockPos = nPos; + pImpl->bNewLine = false; + } + else + { + // mouse touches inner border -> create new line + if ( eDockAlign == GetAlignment() && pImpl->pSplitWin && + pImpl->nLine == pImpl->pSplitWin->GetLineCount()-1 && pImpl->pSplitWin->GetWindowCount(pImpl->nLine) == 1 ) + { + // if this window is the only one in the last line, it can't be docked as new line in the same splitwindow + pImpl->nDockLine = pImpl->nLine; + pImpl->nDockPos = pImpl->nPos; + pImpl->bNewLine = false; + } + else + { + // create new line + pImpl->nDockLine = pSplitWin->GetLineCount(); + pImpl->nDockPos = 0; + pImpl->bNewLine = true; + } + } + + bool bChanged = pImpl->nLine != pImpl->nDockLine || pImpl->nPos != pImpl->nDockPos || eDockAlign != GetAlignment(); + if ( !bChanged && !IsFloatingMode() ) + { + // window only slightly moved, no change of any property + rRect.SetSize( pImpl->aSplitSize ); + rRect.SetPos( aDockingRect.TopLeft() ); + return eDockAlign; + } + + // calculate new size and position + Size aSize; + Point aPoint = aDockingRect.TopLeft(); + Size aInnerSize = GetInnerRect().GetSize(); + if ( eDockAlign == SfxChildAlignment::LEFT || eDockAlign == SfxChildAlignment::RIGHT ) + { + if ( pImpl->bNewLine ) + { + // set height to height of free area + aSize.setHeight( aInnerSize.Height() ); + aSize.setWidth( pImpl->nHorizontalSize ); + if ( eDockAlign == SfxChildAlignment::LEFT ) + { + aPoint = aInnerRect.TopLeft(); + } + else + { + aPoint = aInnerRect.TopRight(); + aPoint.AdjustX( -(aSize.Width()) ); + } + } + else + { + // get width from splitwindow + aSize.setWidth( pSplitWin->GetLineSize(nLine) ); + aSize.setHeight( pImpl->aSplitSize.Height() ); + } + } + else + { + if ( pImpl->bNewLine ) + { + // set width to width of free area + aSize.setWidth( aInnerSize.Width() ); + aSize.setHeight( pImpl->nVerticalSize ); + if ( eDockAlign == SfxChildAlignment::TOP ) + { + aPoint = aInnerRect.TopLeft(); + } + else + { + aPoint = aInnerRect.BottomLeft(); + aPoint.AdjustY( -(aSize.Height()) ); + } + } + else + { + // get height from splitwindow + aSize.setHeight( pSplitWin->GetLineSize(nLine) ); + aSize.setWidth( pImpl->aSplitSize.Width() ); + } + } + + aDockingRect.SetSize( aSize ); + aDockingRect.SetPos( aPoint ); + } + + rRect = aDockingRect; + return eDockAlign; +} + +/** Virtual method of the SfxDockingWindow class. This method determines how + the size of the DockingWindows changes depending on the alignment. The base + implementation uses the floating mode, the size of the marked Floating + Size. For horizontal alignment, the width will be the width of the outer + DockingRectangle, with vertical alignment the height will be the height of + the inner DockingRectangle (resulting from the order in which the SFX child + windows are displayed). The other size is set to the current floating-size, + this could changed by a to intervening derived class. The docking size must + be the same for Left/Right and Top/Bottom. +*/ +Size SfxDockingWindow::CalcDockingSize(SfxChildAlignment eAlign) +{ + // Note: if the resizing is also possible in the docked state, then the + // Floating-size does also have to be adjusted? + + Size aSize = GetFloatingSize(); + switch (eAlign) + { + case SfxChildAlignment::TOP: + case SfxChildAlignment::BOTTOM: + case SfxChildAlignment::LOWESTTOP: + case SfxChildAlignment::HIGHESTTOP: + case SfxChildAlignment::LOWESTBOTTOM: + case SfxChildAlignment::HIGHESTBOTTOM: + aSize.setWidth( aOuterRect.Right() - aOuterRect.Left() ); + break; + case SfxChildAlignment::LEFT: + case SfxChildAlignment::RIGHT: + case SfxChildAlignment::FIRSTLEFT: + case SfxChildAlignment::LASTLEFT: + case SfxChildAlignment::FIRSTRIGHT: + case SfxChildAlignment::LASTRIGHT: + aSize.setHeight( aInnerRect.Bottom() - aInnerRect.Top() ); + break; + case SfxChildAlignment::NOALIGNMENT: + break; + default: + break; + } + + return aSize; +} + +/** Virtual method of the SfxDockingWindow class. Here a derived class can + disallow certain alignments. The base implementation does not + prohibit alignment. +*/ +SfxChildAlignment SfxDockingWindow::CheckAlignment(SfxChildAlignment, + SfxChildAlignment eAlign) +{ + return eAlign; +} + +/** The window is closed when the ChildWindow is destroyed by running the + ChildWindow-slots. If this is method is overridden by a derived class + method, then the SfxDockingDialogWindow: Close() must be called afterwards + if the Close() was not cancelled with "return sal_False". +*/ +bool SfxDockingWindow::Close() +{ + // Execute with Parameters, since Toggle is ignored by some ChildWindows. + if ( !pMgr ) + return true; + + SfxBoolItem aValue( pMgr->GetType(), false); + pBindings->GetDispatcher_Impl()->ExecuteList( + pMgr->GetType(), SfxCallMode::RECORD | SfxCallMode::ASYNCHRON, + { &aValue }); + return true; +} + +void SfxDockingWindow::Paint(vcl::RenderContext&, const tools::Rectangle& /*rRect*/) +{ +} + +/** With this method, a minimal OutputSize be can set, that is queried in + the Resizing()-Handler. +*/ +void SfxDockingWindow::SetMinOutputSizePixel( const Size& rSize ) +{ + pImpl->aMinSize = rSize; + ResizableDockingWindow::SetMinOutputSizePixel( rSize ); +} + +/** Set the minimum size which is returned.*/ +const Size& SfxDockingWindow::GetMinOutputSizePixel() const +{ + return pImpl->aMinSize; +} + +bool SfxDockingWindow::EventNotify( NotifyEvent& rEvt ) +{ + if ( !pImpl ) + return ResizableDockingWindow::EventNotify( rEvt ); + + if ( rEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + { + if (pMgr != nullptr) + pBindings->SetActiveFrame( pMgr->GetFrame() ); + + if ( pImpl->pSplitWin ) + pImpl->pSplitWin->SetActiveWindow_Impl( this ); + else if (pMgr != nullptr) + pMgr->Activate_Impl(); + + // In VCL EventNotify goes first to the window itself, also call the + // base class, otherwise the parent learns nothing + // if ( rEvt.GetWindow() == this ) PB: #i74693# not necessary any longer + ResizableDockingWindow::EventNotify( rEvt ); + return true; + } + else if( rEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + // First, allow KeyInput for Dialog functions + if (!DockingWindow::EventNotify(rEvt) && SfxViewShell::Current()) + { + // then also for valid global accelerators. + return SfxViewShell::Current()->GlobalKeyInput_Impl( *rEvt.GetKeyEvent() ); + } + return true; + } + else if ( rEvt.GetType() == MouseNotifyEvent::LOSEFOCUS && !HasChildPathFocus() ) + { + pBindings->SetActiveFrame( nullptr ); + } + + return ResizableDockingWindow::EventNotify( rEvt ); +} + +void SfxDockingWindow::SetItemSize_Impl( const Size& rSize ) +{ + pImpl->aSplitSize = rSize; + + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + pWorkWin->ConfigChild_Impl( SfxChildIdentifier::SPLITWINDOW, SfxDockingConfig::ALIGNDOCKINGWINDOW, pMgr->GetType() ); +} + +void SfxDockingWindow::Disappear_Impl() +{ + if ( pImpl->pSplitWin && pImpl->pSplitWin->IsItemValid( GetType() ) ) + pImpl->pSplitWin->RemoveWindow(this); +} + +void SfxDockingWindow::Reappear_Impl() +{ + if ( pImpl->pSplitWin && !pImpl->pSplitWin->IsItemValid( GetType() ) ) + { + pImpl->pSplitWin->InsertWindow( this, pImpl->aSplitSize ); + } +} + +bool SfxDockingWindow::IsAutoHide_Impl() const +{ + if ( pImpl->pSplitWin ) + return !pImpl->pSplitWin->IsFadeIn(); + else + return false; +} + +void SfxDockingWindow::AutoShow_Impl() +{ + if ( pImpl->pSplitWin ) + { + pImpl->pSplitWin->FadeIn(); + } +} + +void SfxDockingWindow::StateChanged( StateChangedType nStateChange ) +{ + if ( nStateChange == StateChangedType::InitShow ) + Initialize_Impl(); + + ResizableDockingWindow::StateChanged( nStateChange ); +} + +void SfxDockingWindow::Move() +{ + if ( pImpl ) + pImpl->aMoveIdle.Start(); +} + +IMPL_LINK_NOARG(SfxDockingWindow, TimerHdl, Timer *, void) +{ + pImpl->aMoveIdle.Stop(); + if ( IsReallyVisible() && IsFloatingMode() ) + { + SetFloatingSize( GetOutputSizePixel() ); + pImpl->aWinState = GetFloatingWindow()->GetWindowState(); + SfxWorkWindow *pWorkWin = pBindings->GetWorkWindow_Impl(); + pWorkWin->ConfigChild_Impl( SfxChildIdentifier::SPLITWINDOW, SfxDockingConfig::ALIGNDOCKINGWINDOW, pMgr->GetType() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/documentfontsdialog.cxx b/sfx2/source/dialog/documentfontsdialog.cxx new file mode 100644 index 000000000..e7c0348b5 --- /dev/null +++ b/sfx2/source/dialog/documentfontsdialog.cxx @@ -0,0 +1,113 @@ +/* -*- 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 <documentfontsdialog.hxx> + +#include <sfx2/objsh.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +using namespace ::com::sun::star; + +std::unique_ptr<SfxTabPage> SfxDocumentFontsPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* set) +{ + return std::make_unique<SfxDocumentFontsPage>(pPage, pController, *set); +} + +SfxDocumentFontsPage::SfxDocumentFontsPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& set) + : SfxTabPage(pPage, pController, "sfx/ui/documentfontspage.ui", "DocumentFontsPage", &set) + , embedFontsCheckbox(m_xBuilder->weld_check_button("embedFonts")) + , embedUsedFontsCheckbox(m_xBuilder->weld_check_button("embedUsedFonts")) + , embedLatinScriptFontsCheckbox(m_xBuilder->weld_check_button("embedLatinScriptFonts")) + , embedAsianScriptFontsCheckbox(m_xBuilder->weld_check_button("embedAsianScriptFonts")) + , embedComplexScriptFontsCheckbox(m_xBuilder->weld_check_button("embedComplexScriptFonts")) +{ +} + +SfxDocumentFontsPage::~SfxDocumentFontsPage() +{ +} + +void SfxDocumentFontsPage::Reset( const SfxItemSet* ) +{ + bool bEmbedFonts = false; + bool bEmbedUsedFonts = false; + + bool bEmbedLatinScriptFonts = false; + bool bEmbedAsianScriptFonts = false; + bool bEmbedComplexScriptFonts = false; + + SfxObjectShell* pDocSh = SfxObjectShell::Current(); + if (pDocSh) + { + try + { + uno::Reference< lang::XMultiServiceFactory > xFac( pDocSh->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProps( xFac->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY_THROW ); + + xProps->getPropertyValue("EmbedFonts") >>= bEmbedFonts; + xProps->getPropertyValue("EmbedOnlyUsedFonts") >>= bEmbedUsedFonts; + xProps->getPropertyValue("EmbedLatinScriptFonts") >>= bEmbedLatinScriptFonts; + xProps->getPropertyValue("EmbedAsianScriptFonts") >>= bEmbedAsianScriptFonts; + xProps->getPropertyValue("EmbedComplexScriptFonts") >>= bEmbedComplexScriptFonts; + } + catch( uno::Exception& ) + { + } + } + embedFontsCheckbox->set_active(bEmbedFonts); + embedUsedFontsCheckbox->set_active(bEmbedUsedFonts); + + embedLatinScriptFontsCheckbox->set_active(bEmbedLatinScriptFonts); + embedAsianScriptFontsCheckbox->set_active(bEmbedAsianScriptFonts); + embedComplexScriptFontsCheckbox->set_active(bEmbedComplexScriptFonts); +} + +bool SfxDocumentFontsPage::FillItemSet( SfxItemSet* ) +{ + bool bEmbedFonts = embedFontsCheckbox->get_active(); + bool bEmbedUsedFonts = embedUsedFontsCheckbox->get_active(); + + bool bEmbedLatinScriptFonts = embedLatinScriptFontsCheckbox->get_active(); + bool bEmbedAsianScriptFonts = embedAsianScriptFontsCheckbox->get_active(); + bool bEmbedComplexScriptFonts = embedComplexScriptFontsCheckbox->get_active(); + + SfxObjectShell* pDocSh = SfxObjectShell::Current(); + if ( pDocSh ) + { + try + { + uno::Reference< lang::XMultiServiceFactory > xFac( pDocSh->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProps( xFac->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY_THROW ); + xProps->setPropertyValue("EmbedFonts", uno::Any(bEmbedFonts)); + xProps->setPropertyValue("EmbedOnlyUsedFonts", uno::Any(bEmbedUsedFonts)); + xProps->setPropertyValue("EmbedLatinScriptFonts", uno::Any(bEmbedLatinScriptFonts)); + xProps->setPropertyValue("EmbedAsianScriptFonts", uno::Any(bEmbedAsianScriptFonts)); + xProps->setPropertyValue("EmbedComplexScriptFonts", uno::Any(bEmbedComplexScriptFonts)); + } + catch( uno::Exception& ) + { + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/filedlghelper.cxx b/sfx2/source/dialog/filedlghelper.cxx new file mode 100644 index 000000000..859c5663f --- /dev/null +++ b/sfx2/source/dialog/filedlghelper.cxx @@ -0,0 +1,3000 @@ +/* -*- 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 <optional> +#include <string_view> + +#include <sfx2/filedlghelper.hxx> +#include <sal/types.h> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/ControlActions.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp> +#include <com/sun/star/ui/dialogs/FilePreviewImageFormats.hpp> +#include <com/sun/star/ui/dialogs/FolderPicker.hpp> +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/XControlInformation.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp> +#include <com/sun/star/ui/dialogs/XFilePreview.hpp> +#include <com/sun/star/ui/dialogs/XFilePicker3.hpp> +#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XContainerQuery.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/XInteractionRequest.hpp> +#include <com/sun/star/util/RevisionTag.hpp> +#include <comphelper/fileurl.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/string.hxx> +#include <comphelper/types.hxx> +#include <tools/urlobj.hxx> +#include <vcl/help.hxx> +#include <vcl/weld.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/ucbhelper.hxx> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/svapp.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/saveopt.hxx> +#include <unotools/securityoptions.hxx> +#include <svl/itemset.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/graphicfilter.hxx> +#include <unotools/viewoptions.hxx> +#include <svtools/helpids.h> +#include <comphelper/docpasswordrequest.hxx> +#include <comphelper/docpasswordhelper.hxx> +#include <ucbhelper/content.hxx> +#include <comphelper/storagehelper.hxx> +#include <sfx2/app.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/sfxsids.hrc> +#include "filtergrouping.hxx" +#include "filedlgimpl.hxx" +#include <sfx2/strings.hrc> +#include <sal/log.hxx> +#include <comphelper/sequence.hxx> +#include <tools/diagnose_ex.h> +#include <o3tl/string_view.hxx> +#include <officecfg/Office/Common.hxx> + +#ifdef UNX +#include <errno.h> +#include <sys/stat.h> +#endif + +using namespace ::com::sun::star; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ui::dialogs; +using namespace ::com::sun::star::ui::dialogs::TemplateDescription; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::cppu; + +constexpr OUStringLiteral IODLG_CONFIGNAME = u"FilePicker_Save"; +constexpr OUStringLiteral IMPGRF_CONFIGNAME = u"FilePicker_Graph"; +constexpr OUStringLiteral USERITEM_NAME = u"UserItem"; + +namespace sfx2 +{ + +namespace +{ + bool lclSupportsOOXMLEncryption(std::u16string_view aFilterName) + { + return aFilterName == u"Calc MS Excel 2007 XML" + || aFilterName == u"MS Word 2007 XML" + || aFilterName == u"Impress MS PowerPoint 2007 XML" + || aFilterName == u"Impress MS PowerPoint 2007 XML AutoPlay" + || aFilterName == u"Calc Office Open XML" + || aFilterName == u"Impress Office Open XML" + || aFilterName == u"Impress Office Open XML AutoPlay" + || aFilterName == u"Office Open XML Text"; + } +} + +static std::optional<OUString> GetLastFilterConfigId( FileDialogHelper::Context _eContext ) +{ + static constexpr OUStringLiteral aSD_EXPORT_IDENTIFIER(u"SdExportLastFilter"); + static constexpr OUStringLiteral aSI_EXPORT_IDENTIFIER(u"SiExportLastFilter"); + static constexpr OUStringLiteral aSW_EXPORT_IDENTIFIER(u"SwExportLastFilter"); + + switch( _eContext ) + { + case FileDialogHelper::DrawExport: return aSD_EXPORT_IDENTIFIER; + case FileDialogHelper::ImpressExport: return aSI_EXPORT_IDENTIFIER; + case FileDialogHelper::WriterExport: return aSW_EXPORT_IDENTIFIER; + default: break; + } + + return {}; +} + +static OUString EncodeSpaces_Impl( const OUString& rSource ); +static OUString DecodeSpaces_Impl( const OUString& rSource ); + +// FileDialogHelper_Impl + +// XFilePickerListener Methods +void SAL_CALL FileDialogHelper_Impl::fileSelectionChanged( const FilePickerEvent& ) +{ + SolarMutexGuard aGuard; + mpAntiImpl->FileSelectionChanged(); +} + +void SAL_CALL FileDialogHelper_Impl::directoryChanged( const FilePickerEvent& ) +{ + SolarMutexGuard aGuard; + mpAntiImpl->DirectoryChanged(); +} + +OUString SAL_CALL FileDialogHelper_Impl::helpRequested( const FilePickerEvent& aEvent ) +{ + SolarMutexGuard aGuard; + return sfx2::FileDialogHelper::HelpRequested( aEvent ); +} + +void SAL_CALL FileDialogHelper_Impl::controlStateChanged( const FilePickerEvent& aEvent ) +{ + SolarMutexGuard aGuard; + mpAntiImpl->ControlStateChanged( aEvent ); +} + +void SAL_CALL FileDialogHelper_Impl::dialogSizeChanged() +{ + SolarMutexGuard aGuard; + mpAntiImpl->DialogSizeChanged(); +} + +// XDialogClosedListener Methods +void SAL_CALL FileDialogHelper_Impl::dialogClosed( const DialogClosedEvent& _rEvent ) +{ + SolarMutexGuard aGuard; + mpAntiImpl->DialogClosed( _rEvent ); + postExecute( _rEvent.DialogResult ); +} + +// handle XFilePickerListener events +void FileDialogHelper_Impl::handleFileSelectionChanged() +{ + if ( mbHasVersions ) + updateVersions(); + + if ( mbShowPreview ) + maPreviewIdle.Start(); +} + +void FileDialogHelper_Impl::handleDirectoryChanged() +{ + if ( mbShowPreview ) + TimeOutHdl_Impl( nullptr ); +} + +OUString FileDialogHelper_Impl::handleHelpRequested( const FilePickerEvent& aEvent ) +{ + //!!! todo: cache the help strings (here or TRA) + + OString sHelpId; + // mapping from element id -> help id + switch ( aEvent.ElementId ) + { + case ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION : + sHelpId = HID_FILESAVE_AUTOEXTENSION; + break; + + case ExtendedFilePickerElementIds::CHECKBOX_PASSWORD : + sHelpId = HID_FILESAVE_SAVEWITHPASSWORD; + break; + + case ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS : + sHelpId = HID_FILESAVE_CUSTOMIZEFILTER; + break; + + case ExtendedFilePickerElementIds::CHECKBOX_READONLY : + sHelpId = HID_FILEOPEN_READONLY; + break; + + case ExtendedFilePickerElementIds::CHECKBOX_LINK : + sHelpId = HID_FILEDLG_LINK_CB; + break; + + case ExtendedFilePickerElementIds::CHECKBOX_PREVIEW : + sHelpId = HID_FILEDLG_PREVIEW_CB; + break; + + case ExtendedFilePickerElementIds::PUSHBUTTON_PLAY : + sHelpId = HID_FILESAVE_DOPLAY; + break; + + case ExtendedFilePickerElementIds::LISTBOX_VERSION_LABEL : + case ExtendedFilePickerElementIds::LISTBOX_VERSION : + sHelpId = HID_FILEOPEN_VERSION; + break; + + case ExtendedFilePickerElementIds::LISTBOX_TEMPLATE_LABEL : + case ExtendedFilePickerElementIds::LISTBOX_TEMPLATE : + sHelpId = HID_FILESAVE_TEMPLATE; + break; + + case ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE_LABEL : + case ExtendedFilePickerElementIds::LISTBOX_IMAGE_TEMPLATE : + sHelpId = HID_FILEOPEN_IMAGE_TEMPLATE; + break; + + case ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR_LABEL : + case ExtendedFilePickerElementIds::LISTBOX_IMAGE_ANCHOR : + sHelpId = HID_FILEOPEN_IMAGE_ANCHOR; + break; + + case ExtendedFilePickerElementIds::CHECKBOX_SELECTION : + sHelpId = HID_FILESAVE_SELECTION; + break; + + default: + SAL_WARN( "sfx.dialog", "invalid element id" ); + } + + OUString aHelpText; + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + aHelpText = pHelp->GetHelpText(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), static_cast<weld::Widget*>(nullptr)); + return aHelpText; +} + +void FileDialogHelper_Impl::handleControlStateChanged( const FilePickerEvent& aEvent ) +{ + switch ( aEvent.ElementId ) + { + case CommonFilePickerElementIds::LISTBOX_FILTER: + updateFilterOptionsBox(); + enablePasswordBox( false ); + updateSelectionBox(); + // only use it for export and with our own dialog + if ( mbExport && !mbSystemPicker ) + updateExportButton(); + break; + + case ExtendedFilePickerElementIds::CHECKBOX_PREVIEW: + updatePreviewState(true); + break; + } +} + +void FileDialogHelper_Impl::handleDialogSizeChanged() +{ + if ( mbShowPreview ) + TimeOutHdl_Impl( nullptr ); +} + +// XEventListener Methods +void SAL_CALL FileDialogHelper_Impl::disposing( const EventObject& ) +{ + SolarMutexGuard aGuard; + dispose(); +} + +void FileDialogHelper_Impl::dispose() +{ + if ( mxFileDlg.is() ) + { + // remove the event listener + mxFileDlg->removeFilePickerListener( this ); + + ::comphelper::disposeComponent( mxFileDlg ); + mxFileDlg.clear(); + } +} + +OUString FileDialogHelper_Impl::getCurrentFilterUIName() const +{ + OUString aFilterName; + + if( mxFileDlg.is() ) + { + aFilterName = mxFileDlg->getCurrentFilter(); + + if ( !aFilterName.isEmpty() && isShowFilterExtensionEnabled() ) + aFilterName = getFilterName( aFilterName ); + } + + return aFilterName; +} + +void FileDialogHelper_Impl::LoadLastUsedFilter( const OUString& _rContextIdentifier ) +{ + SvtViewOptions aDlgOpt( EViewType::Dialog, IODLG_CONFIGNAME ); + + if( aDlgOpt.Exists() ) + { + OUString aLastFilter; + if( aDlgOpt.GetUserItem( _rContextIdentifier ) >>= aLastFilter ) + setFilter( aLastFilter ); + } +} + +void FileDialogHelper_Impl::SaveLastUsedFilter() +{ + std::optional<OUString> pConfigId = GetLastFilterConfigId( meContext ); + if( pConfigId ) + SvtViewOptions( EViewType::Dialog, IODLG_CONFIGNAME ).SetUserItem( *pConfigId, + Any( getFilterWithExtension( getFilter() ) ) ); +} + +std::shared_ptr<const SfxFilter> FileDialogHelper_Impl::getCurentSfxFilter() +{ + OUString aFilterName = getCurrentFilterUIName(); + + if ( mpMatcher && !aFilterName.isEmpty() ) + return mpMatcher->GetFilter4UIName( aFilterName, m_nMustFlags, m_nDontFlags ); + + return nullptr; +} + +bool FileDialogHelper_Impl::updateExtendedControl( sal_Int16 _nExtendedControlId, bool _bEnable ) +{ + bool bIsEnabled = false; + + uno::Reference < XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + if ( xCtrlAccess.is() ) + { + try + { + xCtrlAccess->enableControl( _nExtendedControlId, _bEnable ); + bIsEnabled = _bEnable; + } + catch( const IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION( "sfx", "FileDialogHelper_Impl::updateExtendedControl" ); + } + } + return bIsEnabled; +} + +bool FileDialogHelper_Impl::CheckFilterOptionsCapability( const std::shared_ptr<const SfxFilter>& _pFilter ) +{ + bool bResult = false; + + if( mxFilterCFG.is() && _pFilter ) + { + try + { + Sequence < PropertyValue > aProps; + Any aAny = mxFilterCFG->getByName( _pFilter->GetName() ); + if ( aAny >>= aProps ) + { + OUString aServiceName; + for( const auto& rProp : std::as_const(aProps) ) + { + if( rProp.Name == "UIComponent" ) + { + rProp.Value >>= aServiceName; + if( !aServiceName.isEmpty() ) + bResult = true; + } + } + } + } + catch( const Exception& ) + { + } + } + + return bResult; +} + +bool FileDialogHelper_Impl::isInOpenMode() const +{ + bool bRet = false; + + switch ( m_nDialogType ) + { + case FILEOPEN_SIMPLE: + case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE: + case FILEOPEN_PLAY: + case FILEOPEN_LINK_PLAY: + case FILEOPEN_READONLY_VERSION: + case FILEOPEN_LINK_PREVIEW: + case FILEOPEN_PREVIEW: + bRet = true; + } + + return bRet; +} + +void FileDialogHelper_Impl::updateFilterOptionsBox() +{ + if ( !m_bHaveFilterOptions ) + return; + + updateExtendedControl( + ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS, + CheckFilterOptionsCapability( getCurentSfxFilter() ) + ); +} + +void FileDialogHelper_Impl::updateExportButton() +{ + uno::Reference < XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + if ( !xCtrlAccess.is() ) + return; + + OUString sOldLabel( xCtrlAccess->getLabel( CommonFilePickerElementIds::PUSHBUTTON_OK ) ); + + // initialize button label; we need the label with the mnemonic char + if ( maButtonLabel.isEmpty() || maButtonLabel.indexOf( MNEMONIC_CHAR ) == -1 ) + { + // cut the ellipses, if necessary + sal_Int32 nIndex = sOldLabel.indexOf( "..." ); + if ( -1 == nIndex ) + nIndex = sOldLabel.getLength(); + maButtonLabel = sOldLabel.copy( 0, nIndex ); + } + + OUString sLabel = maButtonLabel; + // filter with options -> append ellipses on export button label + if ( CheckFilterOptionsCapability( getCurentSfxFilter() ) ) + sLabel += "..."; + + if ( sOldLabel != sLabel ) + { + try + { + xCtrlAccess->setLabel( CommonFilePickerElementIds::PUSHBUTTON_OK, sLabel ); + } + catch( const IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::updateExportButton" ); + } + } +} + +void FileDialogHelper_Impl::updateSelectionBox() +{ + if ( !mbHasSelectionBox ) + return; + + // Does the selection box exist? + bool bSelectionBoxFound = false; + uno::Reference< XControlInformation > xCtrlInfo( mxFileDlg, UNO_QUERY ); + if ( xCtrlInfo.is() ) + { + Sequence< OUString > aCtrlList = xCtrlInfo->getSupportedControls(); + bSelectionBoxFound = comphelper::findValue(aCtrlList, "SelectionBox") != -1; + } + + if ( bSelectionBoxFound ) + { + std::shared_ptr<const SfxFilter> pFilter = getCurentSfxFilter(); + mbSelectionFltrEnabled = updateExtendedControl( + ExtendedFilePickerElementIds::CHECKBOX_SELECTION, + ( mbSelectionEnabled && pFilter && ( pFilter->GetFilterFlags() & SfxFilterFlags::SUPPORTSSELECTION ) ) ); + uno::Reference< XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + xCtrlAccess->setValue( ExtendedFilePickerElementIds::CHECKBOX_SELECTION, 0, Any( mbSelection ) ); + } +} + +void FileDialogHelper_Impl::enablePasswordBox( bool bInit ) +{ + if ( ! mbHasPassword ) + return; + + bool bWasEnabled = mbIsPwdEnabled; + + std::shared_ptr<const SfxFilter> pCurrentFilter = getCurentSfxFilter(); + mbIsPwdEnabled = updateExtendedControl( + ExtendedFilePickerElementIds::CHECKBOX_PASSWORD, + pCurrentFilter && ( pCurrentFilter->GetFilterFlags() & SfxFilterFlags::ENCRYPTION ) + ); + + if( bInit ) + { + // in case of initialization previous state is not interesting + if( mbIsPwdEnabled ) + { + uno::Reference< XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + if( mbPwdCheckBoxState ) + xCtrlAccess->setValue( ExtendedFilePickerElementIds::CHECKBOX_PASSWORD, 0, Any( true ) ); + } + } + else if( !bWasEnabled && mbIsPwdEnabled ) + { + uno::Reference< XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + if( mbPwdCheckBoxState ) + xCtrlAccess->setValue( ExtendedFilePickerElementIds::CHECKBOX_PASSWORD, 0, Any( true ) ); + } + else if( bWasEnabled && !mbIsPwdEnabled ) + { + // remember user settings until checkbox is enabled + uno::Reference< XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_PASSWORD, 0 ); + bool bPassWord = false; + mbPwdCheckBoxState = ( aValue >>= bPassWord ) && bPassWord; + xCtrlAccess->setValue( ExtendedFilePickerElementIds::CHECKBOX_PASSWORD, 0, Any( false ) ); + } +} + +void FileDialogHelper_Impl::updatePreviewState( bool _bUpdatePreviewWindow ) +{ + if ( !mbHasPreview ) + return; + + uno::Reference< XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + + // check, whether or not we have to display a preview + if ( !xCtrlAccess.is() ) + return; + + try + { + Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_PREVIEW, 0 ); + bool bShowPreview = false; + + if ( aValue >>= bShowPreview ) + { + mbShowPreview = bShowPreview; + + // setShowState has currently no effect for the + // OpenOffice FilePicker (see svtools/source/filepicker/iodlg.cxx) + uno::Reference< XFilePreview > xFilePreview( mxFileDlg, UNO_QUERY ); + if ( xFilePreview.is() ) + xFilePreview->setShowState( mbShowPreview ); + + if ( _bUpdatePreviewWindow ) + TimeOutHdl_Impl( nullptr ); + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::updatePreviewState" ); + } +} + +void FileDialogHelper_Impl::updateVersions() +{ + Sequence < OUString > aEntries; + Sequence < OUString > aPathSeq = mxFileDlg->getFiles(); + + if ( aPathSeq.getLength() == 1 ) + { + INetURLObject aObj( aPathSeq[0] ); + + if ( ( aObj.GetProtocol() == INetProtocol::File ) && + ( utl::UCBContentHelper::IsDocument( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) ) + { + try + { + uno::Reference< embed::XStorage > xStorage = ::comphelper::OStorageHelper::GetStorageFromURL( + aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + embed::ElementModes::READ ); + + DBG_ASSERT( xStorage.is(), "The method must return the storage or throw exception!" ); + if ( !xStorage.is() ) + throw uno::RuntimeException(); + + const uno::Sequence < util::RevisionTag > xVersions = SfxMedium::GetVersionList( xStorage ); + + aEntries.realloc( xVersions.getLength() + 1 ); + aEntries.getArray()[0] = SfxResId( STR_SFX_FILEDLG_ACTUALVERSION ); + + std::transform(xVersions.begin(), xVersions.end(), std::next(aEntries.getArray()), + [](const util::RevisionTag& rVersion) -> OUString { return rVersion.Identifier; }); + } + catch( const uno::Exception& ) + { + } + } + } + + uno::Reference < XFilePickerControlAccess > xDlg( mxFileDlg, UNO_QUERY ); + Any aValue; + + try + { + xDlg->setValue( ExtendedFilePickerElementIds::LISTBOX_VERSION, + ControlActions::DELETE_ITEMS, aValue ); + } + catch( const IllegalArgumentException& ){} + + if ( !aEntries.hasElements() ) + return; + + try + { + aValue <<= aEntries; + xDlg->setValue( ExtendedFilePickerElementIds::LISTBOX_VERSION, + ControlActions::ADD_ITEMS, aValue ); + + Any aPos; + aPos <<= sal_Int32(0); + xDlg->setValue( ExtendedFilePickerElementIds::LISTBOX_VERSION, + ControlActions::SET_SELECT_ITEM, aPos ); + } + catch( const IllegalArgumentException& ){} +} + +IMPL_LINK_NOARG(FileDialogHelper_Impl, TimeOutHdl_Impl, Timer *, void) +{ + if ( !mbHasPreview ) + return; + + maGraphic.Clear(); + + Any aAny; + uno::Reference < XFilePreview > xFilePicker( mxFileDlg, UNO_QUERY ); + + if ( ! xFilePicker.is() ) + return; + + Sequence < OUString > aPathSeq = mxFileDlg->getFiles(); + + if ( mbShowPreview && ( aPathSeq.getLength() == 1 ) ) + { + OUString aURL = aPathSeq[0]; + + if ( ERRCODE_NONE == getGraphic( aURL, maGraphic ) ) + { + // changed the code slightly; + // before: the bitmap was scaled and + // surrounded a white frame + // now: the bitmap will only be scaled + // and the filepicker implementation + // is responsible for placing it at its + // proper position and painting a frame + + BitmapEx aBmp = maGraphic.GetBitmapEx(); + if ( !aBmp.IsEmpty() ) + { + // scale the bitmap to the correct size + sal_Int32 nOutWidth = xFilePicker->getAvailableWidth(); + sal_Int32 nOutHeight = xFilePicker->getAvailableHeight(); + sal_Int32 nBmpWidth = aBmp.GetSizePixel().Width(); + sal_Int32 nBmpHeight = aBmp.GetSizePixel().Height(); + + double nXRatio = static_cast<double>(nOutWidth) / nBmpWidth; + double nYRatio = static_cast<double>(nOutHeight) / nBmpHeight; + + if ( nXRatio < nYRatio ) + aBmp.Scale( nXRatio, nXRatio ); + else + aBmp.Scale( nYRatio, nYRatio ); + + // Convert to true color, to allow CopyPixel + aBmp.Convert( BmpConversion::N24Bit ); + + // and copy it into the Any + SvMemoryStream aData; + + WriteDIB(aBmp, aData, false); + + const Sequence < sal_Int8 > aBuffer( + static_cast< const sal_Int8* >(aData.GetData()), + aData.GetEndOfData() ); + + aAny <<= aBuffer; + } + } + } + + try + { + SolarMutexReleaser aReleaseForCallback; + // clear the preview window + xFilePicker->setImage( FilePreviewImageFormats::BITMAP, aAny ); + } + catch( const IllegalArgumentException& ) + { + } +} + +ErrCode FileDialogHelper_Impl::getGraphic( const OUString& rURL, + Graphic& rGraphic ) const +{ + if ( utl::UCBContentHelper::IsFolder( rURL ) ) + return ERRCODE_IO_NOTAFILE; + + if ( !mpGraphicFilter ) + return ERRCODE_IO_NOTSUPPORTED; + + // select graphic filter from dialog filter selection + OUString aCurFilter( getFilter() ); + + sal_uInt16 nFilter = !aCurFilter.isEmpty() && mpGraphicFilter->GetImportFormatCount() + ? mpGraphicFilter->GetImportFormatNumber( aCurFilter ) + : GRFILTER_FORMAT_DONTKNOW; + + INetURLObject aURLObj( rURL ); + + if ( aURLObj.HasError() || INetProtocol::NotValid == aURLObj.GetProtocol() ) + { + aURLObj.SetSmartProtocol( INetProtocol::File ); + aURLObj.SetSmartURL( rURL ); + } + + ErrCode nRet = ERRCODE_NONE; + + GraphicFilterImportFlags nFilterImportFlags = GraphicFilterImportFlags::SetLogsizeForJpeg; + // non-local? + if ( INetProtocol::File != aURLObj.GetProtocol() ) + { + std::unique_ptr<SvStream> pStream = ::utl::UcbStreamHelper::CreateStream( rURL, StreamMode::READ ); + + if( pStream ) + nRet = mpGraphicFilter->ImportGraphic( rGraphic, rURL, *pStream, nFilter, nullptr, nFilterImportFlags ); + else + nRet = mpGraphicFilter->ImportGraphic( rGraphic, aURLObj, nFilter, nullptr, nFilterImportFlags ); + } + else + { + nRet = mpGraphicFilter->ImportGraphic( rGraphic, aURLObj, nFilter, nullptr, nFilterImportFlags ); + } + + return nRet; +} + +ErrCode FileDialogHelper_Impl::getGraphic( Graphic& rGraphic ) const +{ + ErrCode nRet = ERRCODE_NONE; + + // rhbz#1079672 do not return maGraphic, it needs not to be the selected file + + OUString aPath; + Sequence<OUString> aPathSeq = mxFileDlg->getFiles(); + + if (aPathSeq.getLength() == 1) + { + aPath = aPathSeq[0]; + } + + if (!aPath.isEmpty()) + nRet = getGraphic(aPath, rGraphic); + else + nRet = ERRCODE_IO_GENERAL; + + return nRet; +} + +static bool lcl_isSystemFilePicker( const uno::Reference< XExecutableDialog >& _rxFP ) +{ + try + { + uno::Reference< XServiceInfo > xSI( _rxFP, UNO_QUERY ); + if ( !xSI.is() ) + return true; + return xSI->supportsService( "com.sun.star.ui.dialogs.SystemFilePicker" ); + } + catch( const Exception& ) + { + } + return false; +} + +namespace { + +bool lcl_isAsyncFilePicker( const uno::Reference< XExecutableDialog >& _rxFP ) +{ + try + { + uno::Reference<XAsynchronousExecutableDialog> xSI(_rxFP, UNO_QUERY); + return xSI.is(); + } + catch( const Exception& ) + { + } + return false; +} + +enum open_or_save_t {OPEN, SAVE, UNDEFINED}; + +} + +static open_or_save_t lcl_OpenOrSave(sal_Int16 const nDialogType) +{ + switch (nDialogType) + { + case FILEOPEN_SIMPLE: + case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE: + case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR: + case FILEOPEN_PLAY: + case FILEOPEN_LINK_PLAY: + case FILEOPEN_READONLY_VERSION: + case FILEOPEN_LINK_PREVIEW: + case FILEOPEN_PREVIEW: + return OPEN; + case FILESAVE_SIMPLE: + case FILESAVE_AUTOEXTENSION_PASSWORD: + case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS: + case FILESAVE_AUTOEXTENSION_SELECTION: + case FILESAVE_AUTOEXTENSION_TEMPLATE: + case FILESAVE_AUTOEXTENSION: + return SAVE; + default: + assert(false); // invalid dialog type + } + return UNDEFINED; +} + +// FileDialogHelper_Impl + +css::uno::Reference<css::awt::XWindow> FileDialogHelper_Impl::GetFrameInterface() +{ + if (mpFrameWeld) + return mpFrameWeld->GetXWindow(); + return css::uno::Reference<css::awt::XWindow>(); +} + +FileDialogHelper_Impl::FileDialogHelper_Impl( + FileDialogHelper* _pAntiImpl, + sal_Int16 nDialogType, + FileDialogFlags nFlags, + sal_Int16 nDialog, + weld::Window* pFrameWeld, + const OUString& sStandardDir, + const css::uno::Sequence< OUString >& rDenyList + ) + :maPreviewIdle("sfx2 FileDialogHelper_Impl maPreviewIdle") + ,m_nDialogType ( nDialogType ) + ,meContext ( FileDialogHelper::UnknownContext ) +{ + const char* pServiceName=nullptr; + switch (nDialog) + { + case SFX2_IMPL_DIALOG_SYSTEM: + case SFX2_IMPL_DIALOG_OOO: + pServiceName = "com.sun.star.ui.dialogs.OfficeFilePicker"; + break; + case SFX2_IMPL_DIALOG_REMOTE: + pServiceName = "com.sun.star.ui.dialogs.RemoteFilePicker"; + break; + default: + pServiceName = "com.sun.star.ui.dialogs.FilePicker"; + break; + } + + OUString aService = OUString::createFromAscii( pServiceName ); + + uno::Reference< XMultiServiceFactory > xFactory( ::comphelper::getProcessServiceFactory() ); + + // create the file open dialog + // the flags can be SFXWB_INSERT or SFXWB_MULTISELECTION + + mpFrameWeld = pFrameWeld; + mpAntiImpl = _pAntiImpl; + mbHasAutoExt = false; + mbHasPassword = false; + m_bHaveFilterOptions = false; + mbIsPwdEnabled = true; + mbHasVersions = false; + mbHasPreview = false; + mbShowPreview = false; + mbDeleteMatcher = false; + mbInsert = bool(nFlags & (FileDialogFlags::Insert| + FileDialogFlags::InsertCompare| + FileDialogFlags::InsertMerge)); + mbExport = bool(nFlags & FileDialogFlags::Export); + mbIsSaveDlg = false; + mbPwdCheckBoxState = false; + mbSelection = false; + mbSelectionEnabled = true; + mbHasSelectionBox = false; + mbSelectionFltrEnabled = false; + + // default settings + m_nDontFlags = SFX_FILTER_NOTINSTALLED | SfxFilterFlags::INTERNAL | SfxFilterFlags::NOTINFILEDLG; + if (OPEN == lcl_OpenOrSave(m_nDialogType)) + m_nMustFlags = SfxFilterFlags::IMPORT; + else + m_nMustFlags = SfxFilterFlags::EXPORT; + + + mpMatcher = nullptr; + mpGraphicFilter = nullptr; + mnPostUserEventId = nullptr; + + // create the picker component + mxFileDlg.set(xFactory->createInstance( aService ), css::uno::UNO_QUERY); + mbSystemPicker = lcl_isSystemFilePicker( mxFileDlg ); + mbAsyncPicker = lcl_isAsyncFilePicker(mxFileDlg); + + uno::Reference< XInitialization > xInit( mxFileDlg, UNO_QUERY ); + + if ( ! mxFileDlg.is() ) + { + return; + } + + + if ( xInit.is() ) + { + sal_Int16 nTemplateDescription = TemplateDescription::FILEOPEN_SIMPLE; + + switch ( m_nDialogType ) + { + case FILEOPEN_SIMPLE: + nTemplateDescription = TemplateDescription::FILEOPEN_SIMPLE; + break; + + case FILESAVE_SIMPLE: + nTemplateDescription = TemplateDescription::FILESAVE_SIMPLE; + mbIsSaveDlg = true; + break; + + case FILESAVE_AUTOEXTENSION_PASSWORD: + nTemplateDescription = TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD; + mbHasPassword = true; + mbHasAutoExt = true; + mbIsSaveDlg = true; + break; + + case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS: + nTemplateDescription = TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS; + mbHasPassword = true; + + m_bHaveFilterOptions = true; + if( xFactory.is() ) + { + mxFilterCFG.set( + xFactory->createInstance( "com.sun.star.document.FilterFactory" ), + UNO_QUERY ); + } + + mbHasAutoExt = true; + mbIsSaveDlg = true; + break; + + case FILESAVE_AUTOEXTENSION_SELECTION: + nTemplateDescription = TemplateDescription::FILESAVE_AUTOEXTENSION_SELECTION; + mbHasAutoExt = true; + mbIsSaveDlg = true; + mbHasSelectionBox = true; + if ( mbExport && !mxFilterCFG.is() && xFactory.is() ) + { + mxFilterCFG.set( + xFactory->createInstance( "com.sun.star.document.FilterFactory" ), + UNO_QUERY ); + } + break; + + case FILESAVE_AUTOEXTENSION_TEMPLATE: + nTemplateDescription = TemplateDescription::FILESAVE_AUTOEXTENSION_TEMPLATE; + mbHasAutoExt = true; + mbIsSaveDlg = true; + break; + + case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE: + nTemplateDescription = TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE; + mbHasPreview = true; + break; + + case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR: + nTemplateDescription = TemplateDescription::FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR; + mbHasPreview = true; + break; + + case FILEOPEN_PLAY: + nTemplateDescription = TemplateDescription::FILEOPEN_PLAY; + break; + + case FILEOPEN_LINK_PLAY: + nTemplateDescription = TemplateDescription::FILEOPEN_LINK_PLAY; + break; + + case FILEOPEN_READONLY_VERSION: + nTemplateDescription = TemplateDescription::FILEOPEN_READONLY_VERSION; + mbHasVersions = true; + break; + + case FILEOPEN_LINK_PREVIEW: + nTemplateDescription = TemplateDescription::FILEOPEN_LINK_PREVIEW; + mbHasPreview = true; + break; + + case FILESAVE_AUTOEXTENSION: + nTemplateDescription = TemplateDescription::FILESAVE_AUTOEXTENSION; + mbHasAutoExt = true; + mbIsSaveDlg = true; + break; + + case FILEOPEN_PREVIEW: + nTemplateDescription = TemplateDescription::FILEOPEN_PREVIEW; + mbHasPreview = true; + break; + + default: + SAL_WARN( "sfx.dialog", "FileDialogHelper::ctor with unknown type" ); + break; + } + + if (mbHasPreview) + { + maPreviewIdle.SetPriority( TaskPriority::LOWEST ); + maPreviewIdle.SetInvokeHandler( LINK( this, FileDialogHelper_Impl, TimeOutHdl_Impl ) ); + } + + auto xWindow = GetFrameInterface(); + + Sequence < Any > aInitArguments(!xWindow.is() ? 3 : 4); + auto pInitArguments = aInitArguments.getArray(); + + // This is a hack. We currently know that the internal file picker implementation + // supports the extended arguments as specified below. + // TODO: + // a) adjust the service description so that it includes the TemplateDescription and ParentWindow args + // b) adjust the implementation of the system file picker to that it recognizes it + if ( mbSystemPicker ) + { + pInitArguments[0] <<= nTemplateDescription; + if (xWindow.is()) + pInitArguments[1] <<= xWindow; + } + else + { + pInitArguments[0] <<= NamedValue( + "TemplateDescription", + Any( nTemplateDescription ) + ); + + pInitArguments[1] <<= NamedValue( + "StandardDir", + Any( sStandardDir ) + ); + + pInitArguments[2] <<= NamedValue( + "DenyList", + Any( rDenyList ) + ); + + + if (xWindow.is()) + pInitArguments[3] <<= NamedValue("ParentWindow", Any(xWindow)); + } + + try + { + xInit->initialize( aInitArguments ); + } + catch( const Exception& ) + { + OSL_FAIL( "FileDialogHelper_Impl::FileDialogHelper_Impl: could not initialize the picker!" ); + } + } + + + // set multiselection mode + if ( nFlags & FileDialogFlags::MultiSelection ) + mxFileDlg->setMultiSelectionMode( true ); + + if ( nFlags & FileDialogFlags::Graphic ) // generate graphic filter only on demand + { + addGraphicFilter(); + } + + // Export dialog + if ( mbExport ) + { + mxFileDlg->setTitle( SfxResId( STR_SFX_EXPLORERFILE_EXPORT ) ); + try { + css::uno::Reference < XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY_THROW ); + xCtrlAccess->enableControl( ExtendedFilePickerElementIds::LISTBOX_FILTER_SELECTOR, true ); + } + catch( const Exception & ) { } + } + + // Save a copy dialog + if ( nFlags & FileDialogFlags::SaveACopy ) + { + mxFileDlg->setTitle( SfxResId( STR_PB_SAVEACOPY ) ); + } + + // the "insert file" dialog needs another title + if ( mbInsert ) + { + if ( nFlags & FileDialogFlags::InsertCompare ) + { + mxFileDlg->setTitle( SfxResId( STR_PB_COMPAREDOC ) ); + } + else if ( nFlags & FileDialogFlags::InsertMerge ) + { + mxFileDlg->setTitle( SfxResId( STR_PB_MERGEDOC ) ); + } + else + { + mxFileDlg->setTitle( SfxResId( STR_SFX_EXPLORERFILE_INSERT ) ); + } + uno::Reference < XFilePickerControlAccess > xExtDlg( mxFileDlg, UNO_QUERY ); + if ( xExtDlg.is() ) + { + try + { + xExtDlg->setLabel( CommonFilePickerElementIds::PUSHBUTTON_OK, + SfxResId( STR_SFX_EXPLORERFILE_BUTTONINSERT ) ); + } + catch( const IllegalArgumentException& ){} + } + } + + // add the event listener + mxFileDlg->addFilePickerListener( this ); +} + +css::uno::Reference<css::ui::dialogs::XFolderPicker2> createFolderPicker(const css::uno::Reference<css::uno::XComponentContext>& rContext, weld::Window* pPreferredParent) +{ + auto xRet = css::ui::dialogs::FolderPicker::create(rContext); + + // see FileDialogHelper_Impl::FileDialogHelper_Impl (above) for args to FilePicker + // reuse the same arguments for FolderPicker + if (pPreferredParent && lcl_isSystemFilePicker(xRet)) + { + uno::Reference< XInitialization > xInit(xRet, UNO_QUERY); + if (xInit.is()) + { + Sequence<Any> aInitArguments{ Any(sal_Int32(0)), Any(pPreferredParent->GetXWindow()) }; + + try + { + xInit->initialize(aInitArguments); + } + catch (const Exception&) + { + OSL_FAIL( "createFolderPicker: could not initialize the picker!" ); + } + } + } + + return xRet; +} + +FileDialogHelper_Impl::~FileDialogHelper_Impl() +{ + // Remove user event if we haven't received it yet + if ( mnPostUserEventId ) + Application::RemoveUserEvent( mnPostUserEventId ); + mnPostUserEventId = nullptr; + + mpGraphicFilter.reset(); + + if ( mbDeleteMatcher ) + delete mpMatcher; + + maPreviewIdle.ClearInvokeHandler(); + + ::comphelper::disposeComponent( mxFileDlg ); +} + +void FileDialogHelper_Impl::setControlHelpIds( const sal_Int16* _pControlId, const char** _pHelpId ) +{ + DBG_ASSERT( _pControlId && _pHelpId, "FileDialogHelper_Impl::setControlHelpIds: invalid array pointers!" ); + if ( !_pControlId || !_pHelpId ) + return; + + // forward these ids to the file picker + try + { + const OUString sHelpIdPrefix( INET_HID_SCHEME ); + // the ids for the single controls + uno::Reference< XFilePickerControlAccess > xControlAccess( mxFileDlg, UNO_QUERY ); + if ( xControlAccess.is() ) + { + while ( *_pControlId ) + { + DBG_ASSERT( INetURLObject( OStringToOUString( *_pHelpId, RTL_TEXTENCODING_UTF8 ) ).GetProtocol() == INetProtocol::NotValid, "Wrong HelpId!" ); + OUString sId = sHelpIdPrefix + + OUString( *_pHelpId, strlen( *_pHelpId ), RTL_TEXTENCODING_UTF8 ); + xControlAccess->setValue( *_pControlId, ControlActions::SET_HELP_URL, Any( sId ) ); + + ++_pControlId; ++_pHelpId; + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::setControlHelpIds: caught an exception while setting the help ids!" ); + } +} + +IMPL_LINK_NOARG( FileDialogHelper_Impl, InitControls, void*, void ) +{ + mnPostUserEventId = nullptr; + enablePasswordBox( true ); + updateFilterOptionsBox( ); + updateSelectionBox( ); +} + +void FileDialogHelper_Impl::preExecute() +{ + loadConfig( ); + setDefaultValues( ); + updatePreviewState( false ); + + implInitializeFileName( ); + +#if !(defined(MACOSX) && defined(MACOSX)) && !defined(_WIN32) + // allow for dialog implementations which need to be executed before they return valid values for + // current filter and such + + // On Vista (at least SP1) it's the same as on MacOSX, the modal dialog won't let message pass + // through before it returns from execution + mnPostUserEventId = Application::PostUserEvent( LINK( this, FileDialogHelper_Impl, InitControls ) ); +#else + // However, the macOS implementation's pickers run modally in execute and so the event doesn't + // get through in time... so we call the methods directly + enablePasswordBox( true ); + updateFilterOptionsBox( ); + updateSelectionBox( ); +#endif +} + +void FileDialogHelper_Impl::postExecute( sal_Int16 _nResult ) +{ + if ( ExecutableDialogResults::CANCEL != _nResult ) + saveConfig(); +} + +void FileDialogHelper_Impl::implInitializeFileName( ) +{ + if ( maFileName.isEmpty() ) + return; + + INetURLObject aObj( maPath ); + aObj.Append( maFileName ); + + // in case we're operating as save dialog, and "auto extension" is checked, + // cut the extension from the name + if ( !(mbIsSaveDlg && mbHasAutoExt) ) + return; + + try + { + bool bAutoExtChecked = false; + + uno::Reference < XFilePickerControlAccess > xControlAccess( mxFileDlg, UNO_QUERY ); + if ( xControlAccess.is() + && ( xControlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, 0 ) + >>= bAutoExtChecked + ) + ) + { + if ( bAutoExtChecked ) + { // cut the extension + aObj.removeExtension( ); + mxFileDlg->setDefaultName( + aObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset)); + } + } + } + catch( const Exception& ) + { + OSL_FAIL( "FileDialogHelper_Impl::implInitializeFileName: could not ask for the auto-extension current-value!" ); + } +} + +sal_Int16 FileDialogHelper_Impl::implDoExecute() +{ + preExecute(); + + sal_Int16 nRet = ExecutableDialogResults::CANCEL; + +//On MacOSX the native file picker has to run in the primordial thread because of drawing issues +//On Linux the native gtk file picker, when backed by gnome-vfs2, needs to be run in the same +//primordial thread as the ucb gnome-vfs2 provider was initialized in. + + { + try + { +#ifdef _WIN32 + if ( mbSystemPicker ) + { + SolarMutexReleaser aSolarMutex; + nRet = mxFileDlg->execute(); + } + else +#endif + nRet = mxFileDlg->execute(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::implDoExecute" ); + } + } + + postExecute( nRet ); + + return nRet; +} + +void FileDialogHelper_Impl::implStartExecute() +{ + DBG_ASSERT( mxFileDlg.is(), "invalid file dialog" ); + + assert(mbAsyncPicker); + preExecute(); + + try + { + uno::Reference< XAsynchronousExecutableDialog > xAsyncDlg( mxFileDlg, UNO_QUERY ); + if ( xAsyncDlg.is() ) + xAsyncDlg->startExecuteModal( this ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::implDoExecute" ); + } +} + +void FileDialogHelper_Impl::implGetAndCacheFiles(const uno::Reference< XInterface >& xPicker, std::vector<OUString>& rpURLList) +{ + rpURLList.clear(); + + // a) the new way (optional!) + uno::Reference< XFilePicker3 > xPickNew(xPicker, UNO_QUERY); + if (xPickNew.is()) + { + Sequence< OUString > lFiles = xPickNew->getSelectedFiles(); + comphelper::sequenceToContainer(rpURLList, lFiles); + } + + // b) the olde way ... non optional. + else + { + uno::Reference< XFilePicker3 > xPickOld(xPicker, UNO_QUERY_THROW); + Sequence< OUString > lFiles = xPickOld->getFiles(); + ::sal_Int32 nFiles = lFiles.getLength(); + if ( nFiles == 1 ) + { + rpURLList.push_back(lFiles[0]); + } + else if ( nFiles > 1 ) + { + INetURLObject aPath( lFiles[0] ); + aPath.setFinalSlash(); + + for (::sal_Int32 i = 1; i < nFiles; i++) + { + if (i == 1) + aPath.Append( lFiles[i] ); + else + aPath.setName( lFiles[i] ); + + rpURLList.push_back(aPath.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + } + } + } + + mlLastURLs = rpURLList; +} + +ErrCode FileDialogHelper_Impl::execute( std::vector<OUString>& rpURLList, + std::optional<SfxAllItemSet>& rpSet, + OUString& rFilter ) +{ + // rFilter is a pure output parameter, it shouldn't be used for anything else + // changing this would surely break code + // rpSet is in/out parameter, usually just a media-descriptor that can be changed by dialog + + uno::Reference< XFilePickerControlAccess > xCtrlAccess( mxFileDlg, UNO_QUERY ); + + // retrieves parameters from rpSet + // for now only Password is used + if ( rpSet ) + { + // check password checkbox if the document had password before + if( mbHasPassword ) + { + const SfxBoolItem* pPassItem = SfxItemSet::GetItem<SfxBoolItem>(&*rpSet, SID_PASSWORDINTERACTION, false); + mbPwdCheckBoxState = ( pPassItem != nullptr && pPassItem->GetValue() ); + + // in case the document has password to modify, the dialog should be shown + const SfxUnoAnyItem* pPassToModifyItem = SfxItemSet::GetItem<SfxUnoAnyItem>(&*rpSet, SID_MODIFYPASSWORDINFO, false); + mbPwdCheckBoxState |= ( pPassToModifyItem && pPassToModifyItem->GetValue().hasValue() ); + } + + const SfxBoolItem* pSelectItem = SfxItemSet::GetItem<SfxBoolItem>(&*rpSet, SID_SELECTION, false); + if ( pSelectItem ) + mbSelection = pSelectItem->GetValue(); + else + mbSelectionEnabled = false; + + // the password will be set in case user decide so + rpSet->ClearItem( SID_PASSWORDINTERACTION ); + if (rpSet->HasItem( SID_PASSWORD )) + { + // As the SID_ENCRYPTIONDATA and SID_PASSWORD are using for setting password together, we need to clear them both. + // Note: Do not remove SID_ENCRYPTIONDATA without SID_PASSWORD + rpSet->ClearItem( SID_PASSWORD ); + rpSet->ClearItem( SID_ENCRYPTIONDATA ); + } + rpSet->ClearItem( SID_RECOMMENDREADONLY ); + rpSet->ClearItem( SID_MODIFYPASSWORDINFO ); + + } + + if ( mbHasPassword && !mbPwdCheckBoxState ) + { + mbPwdCheckBoxState = ( + SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::DocWarnRecommendPassword ) ); + } + + rpURLList.clear(); + + if ( ! mxFileDlg.is() ) + return ERRCODE_ABORT; + + if ( ExecutableDialogResults::CANCEL != implDoExecute() ) + { + // create an itemset if there is no + if( !rpSet ) + rpSet.emplace( SfxGetpApp()->GetPool() ); + + // the item should remain only if it was set by the dialog + rpSet->ClearItem( SID_SELECTION ); + + if( mbExport && mbHasSelectionBox ) + { + try + { + Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_SELECTION, 0 ); + bool bSelection = false; + if ( aValue >>= bSelection ) + rpSet->Put( SfxBoolItem( SID_SELECTION, bSelection ) ); + } + catch( const IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::execute: caught an IllegalArgumentException!" ); + } + } + + + // set the read-only flag. When inserting a file, this flag is always set + if ( mbInsert ) + rpSet->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + else + { + if ( ( FILEOPEN_READONLY_VERSION == m_nDialogType ) && xCtrlAccess.is() ) + { + try + { + Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_READONLY, 0 ); + bool bReadOnly = false; + if ( ( aValue >>= bReadOnly ) && bReadOnly ) + rpSet->Put( SfxBoolItem( SID_DOC_READONLY, bReadOnly ) ); + } + catch( const IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::execute: caught an IllegalArgumentException!" ); + } + } + } + if ( mbHasVersions && xCtrlAccess.is() ) + { + try + { + Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::LISTBOX_VERSION, + ControlActions::GET_SELECTED_ITEM_INDEX ); + sal_Int32 nVersion = 0; + if ( ( aValue >>= nVersion ) && nVersion > 0 ) + // open a special version; 0 == current version + rpSet->Put( SfxInt16Item( SID_VERSION, static_cast<short>(nVersion) ) ); + } + catch( const IllegalArgumentException& ){} + } + + // set the filter + getRealFilter( rFilter ); + + std::shared_ptr<const SfxFilter> pCurrentFilter = getCurentSfxFilter(); + + // fill the rpURLList + implGetAndCacheFiles( mxFileDlg, rpURLList ); + if ( rpURLList.empty() ) + return ERRCODE_ABORT; + + // check, whether or not we have to display a password box + if ( pCurrentFilter && mbHasPassword && mbIsPwdEnabled && xCtrlAccess.is() ) + { + try + { + Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_PASSWORD, 0 ); + bool bPassWord = false; + if ( ( aValue >>= bPassWord ) && bPassWord ) + { + // ask for a password + OUString aDocName(rpURLList[0]); + ErrCode errCode = RequestPassword(pCurrentFilter, aDocName, &*rpSet, GetFrameInterface()); + if (errCode != ERRCODE_NONE) + return errCode; + } + } + catch( const IllegalArgumentException& ){} + } + // check, whether or not we have to display a key selection box + if ( pCurrentFilter && mbHasPassword && xCtrlAccess.is() ) + { + try + { + Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_GPGENCRYPTION, 0 ); + bool bGpg = false; + if ( ( aValue >>= bGpg ) && bGpg ) + { + uno::Sequence< beans::NamedValue > aEncryptionData; + while(true) + { + try + { + // ask for keys + aEncryptionData = ::comphelper::OStorageHelper::CreateGpgPackageEncryptionData(); + break; // user cancelled or we've some keys now + } + catch( const IllegalArgumentException& ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(mpFrameWeld, + VclMessageType::Warning, VclButtonsType::Ok, + SfxResId(RID_SVXSTR_GPG_ENCRYPT_FAILURE))); + xBox->run(); + } + } + + if ( aEncryptionData.hasElements() ) + rpSet->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData) ) ); + } + } + catch( const IllegalArgumentException& ){} + } + + SaveLastUsedFilter(); + return ERRCODE_NONE; + } + else + return ERRCODE_ABORT; +} + +ErrCode FileDialogHelper_Impl::execute() +{ + if ( ! mxFileDlg.is() ) + return ERRCODE_ABORT; + + sal_Int16 nRet = implDoExecute(); + + maPath = mxFileDlg->getDisplayDirectory(); + + if ( ExecutableDialogResults::CANCEL == nRet ) + return ERRCODE_ABORT; + else + { + return ERRCODE_NONE; + } +} + +OUString FileDialogHelper_Impl::getPath() const +{ + OUString aPath; + + if ( mxFileDlg.is() ) + aPath = mxFileDlg->getDisplayDirectory(); + + if ( aPath.isEmpty() ) + aPath = maPath; + + return aPath; +} + +OUString FileDialogHelper_Impl::getFilter() const +{ + OUString aFilter = getCurrentFilterUIName(); + + if( aFilter.isEmpty() ) + aFilter = maCurFilter; + + return aFilter; +} + +void FileDialogHelper_Impl::getRealFilter( OUString& _rFilter ) const +{ + _rFilter = getCurrentFilterUIName(); + + if ( _rFilter.isEmpty() ) + _rFilter = maCurFilter; + + if ( !_rFilter.isEmpty() && mpMatcher ) + { + std::shared_ptr<const SfxFilter> pFilter = + mpMatcher->GetFilter4UIName( _rFilter, m_nMustFlags, m_nDontFlags ); + _rFilter = pFilter ? pFilter->GetFilterName() : OUString(); + } +} + +void FileDialogHelper_Impl::verifyPath() +{ +#ifdef UNX + // lp#905355, fdo#43895 + // Check that the file has read only permission and is in /tmp -- this is + // the case if we have opened the file from the web with firefox only. + if (maFileName.isEmpty()) { + return; + } + INetURLObject url(maPath); + if (url.GetProtocol() != INetProtocol::File + || url.getName(0, true, INetURLObject::DecodeMechanism::WithCharset) != "tmp") + { + return; + } + if (maFileName.indexOf('/') != -1) { + SAL_WARN("sfx.dialog", maFileName << " contains /"); + return; + } + url.insertName( + maFileName, false, INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All); + OUString sysPathU; + osl::FileBase::RC e = osl::FileBase::getSystemPathFromFileURL( + url.GetMainURL(INetURLObject::DecodeMechanism::NONE), sysPathU); + if (e != osl::FileBase::E_None) { + SAL_WARN( + "sfx.dialog", + "getSystemPathFromFileURL(" + << url.GetMainURL(INetURLObject::DecodeMechanism::NONE) << ") failed with " + << +e); + return; + } + OString sysPathC; + if (!sysPathU.convertToString( + &sysPathC, osl_getThreadTextEncoding(), + (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR + | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))) + { + SAL_WARN( + "sfx.dialog", + "convertToString(" << sysPathU << ") failed for encoding " + << +osl_getThreadTextEncoding()); + return; + } + struct stat aFileStat; + if (stat(sysPathC.getStr(), &aFileStat) == -1) { + SAL_WARN( "sfx.dialog", "stat(" << sysPathC << ") failed with errno " << errno); + return; + } + if ((aFileStat.st_mode & (S_IRWXO | S_IRWXG | S_IRWXU)) == S_IRUSR) { + maPath = SvtPathOptions().GetWorkPath(); + mxFileDlg->setDisplayDirectory( maPath ); + } +#else + (void) this; +#endif +} + +void FileDialogHelper_Impl::displayFolder( const OUString& _rPath ) +{ + if ( _rPath.isEmpty() ) + // nothing to do + return; + + maPath = _rPath; + if ( mxFileDlg.is() ) + { + try + { + mxFileDlg->setDisplayDirectory( maPath ); + verifyPath(); + } + catch( const IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION( "sfx", "FileDialogHelper_Impl::displayFolder" ); + } + } +} + +void FileDialogHelper_Impl::setFileName( const OUString& _rFile ) +{ + maFileName = _rFile; + if ( mxFileDlg.is() ) + { + try + { + mxFileDlg->setDefaultName( maFileName ); + verifyPath(); + } + catch( const IllegalArgumentException& ) + { + TOOLS_WARN_EXCEPTION( "sfx", "FileDialogHelper_Impl::setFileName" ); + } + } +} + +void FileDialogHelper_Impl::setFilter( const OUString& rFilter ) +{ + DBG_ASSERT( rFilter.indexOf(':') == -1, "Old filter name used!"); + + maCurFilter = rFilter; + + if ( !rFilter.isEmpty() && mpMatcher ) + { + std::shared_ptr<const SfxFilter> pFilter = mpMatcher->GetFilter4FilterName( + rFilter, m_nMustFlags, m_nDontFlags ); + if ( pFilter ) + maCurFilter = pFilter->GetUIName(); + } + + if ( !maCurFilter.isEmpty() && mxFileDlg.is() ) + { + try + { + mxFileDlg->setCurrentFilter( maCurFilter ); + } + catch( const IllegalArgumentException& ){} + } +} + +void FileDialogHelper_Impl::createMatcher( const OUString& rFactory ) +{ + if (mbDeleteMatcher) + delete mpMatcher; + + mpMatcher = new SfxFilterMatcher( SfxObjectShell::GetServiceNameFromFactory(rFactory) ); + mbDeleteMatcher = true; +} + +void FileDialogHelper_Impl::addFilters( const OUString& rFactory, + SfxFilterFlags nMust, + SfxFilterFlags nDont ) +{ + if ( ! mxFileDlg.is() ) + return; + + if (mbDeleteMatcher) + delete mpMatcher; + + // we still need a matcher to convert UI names to filter names + if ( rFactory.isEmpty() ) + { + SfxApplication *pSfxApp = SfxGetpApp(); + mpMatcher = &pSfxApp->GetFilterMatcher(); + mbDeleteMatcher = false; + } + else + { + mpMatcher = new SfxFilterMatcher( rFactory ); + mbDeleteMatcher = true; + } + + uno::Reference< XMultiServiceFactory > xSMGR = ::comphelper::getProcessServiceFactory(); + uno::Reference< XContainerQuery > xFilterCont( + xSMGR->createInstance("com.sun.star.document.FilterFactory"), + UNO_QUERY); + if ( ! xFilterCont.is() ) + return; + + m_nMustFlags |= nMust; + m_nDontFlags |= nDont; + + // create the list of filters + OUString sQuery = + "getSortedFilterList()" + ":module=" + + rFactory + // use long name here ! + ":iflags=" + + OUString::number(static_cast<sal_Int32>(m_nMustFlags)) + + ":eflags=" + + OUString::number(static_cast<sal_Int32>(m_nDontFlags)); + + uno::Reference< XEnumeration > xResult; + try + { + xResult = xFilterCont->createSubSetEnumerationByQuery(sQuery); + } + catch( const uno::Exception& ) + { + SAL_WARN( "sfx.dialog", "Could not get filters from the configuration!" ); + } + + TSortedFilterList aIter (xResult); + + // append the filters + OUString sFirstFilter; + if (OPEN == lcl_OpenOrSave(m_nDialogType)) + ::sfx2::appendFiltersForOpen( aIter, mxFileDlg, sFirstFilter, *this ); + else if ( mbExport ) + ::sfx2::appendExportFilters( aIter, mxFileDlg, sFirstFilter, *this ); + else + ::sfx2::appendFiltersForSave( aIter, mxFileDlg, sFirstFilter, *this, rFactory ); + + // set our initial selected filter (if we do not already have one) + if ( maSelectFilter.isEmpty() ) + maSelectFilter = sFirstFilter; +} + +void FileDialogHelper_Impl::addFilter( const OUString& rFilterName, + const OUString& rExtension ) +{ + if ( ! mxFileDlg.is() ) + return; + + try + { + mxFileDlg->appendFilter( rFilterName, rExtension ); + + if ( maSelectFilter.isEmpty() ) + maSelectFilter = rFilterName; + } + catch( const IllegalArgumentException& ) + { + SAL_WARN( "sfx.dialog", "Could not append Filter" << rFilterName ); + } +} + +void FileDialogHelper_Impl::addGraphicFilter() +{ + if ( ! mxFileDlg.is() ) + return; + + // create the list of filters + mpGraphicFilter.reset( new GraphicFilter ); + sal_uInt16 i, j, nCount = mpGraphicFilter->GetImportFormatCount(); + + // compute the extension string for all known import filters + OUString aExtensions; + + for ( i = 0; i < nCount; i++ ) + { + j = 0; + while( true ) + { + OUString sWildcard = mpGraphicFilter->GetImportWildcard( i, j++ ); + if ( sWildcard.isEmpty() ) + break; + if ( aExtensions.indexOf( sWildcard ) == -1 ) + { + if ( !aExtensions.isEmpty() ) + aExtensions += ";"; + aExtensions += sWildcard; + } + } + } + +#if defined(_WIN32) + if ( aExtensions.getLength() > 240 ) + aExtensions = FILEDIALOG_FILTER_ALL; +#endif + bool bIsInOpenMode = isInOpenMode(); + + try + { + // if the extension is not "All files", insert "All images" + if (aExtensions != FILEDIALOG_FILTER_ALL) + { + OUString aAllFilterName = SfxResId(STR_SFX_IMPORT_ALL_IMAGES); + aAllFilterName = ::sfx2::addExtension( aAllFilterName, aExtensions, bIsInOpenMode, *this ); + mxFileDlg->appendFilter( aAllFilterName, aExtensions ); + maSelectFilter = aAllFilterName; // and make it the default + } + + // rhbz#1715109 always include All files *.* or * + OUString aAllFilesName = SfxResId( STR_SFX_FILTERNAME_ALL ); + aAllFilesName = ::sfx2::addExtension( aAllFilesName, FILEDIALOG_FILTER_ALL, bIsInOpenMode, *this ); + mxFileDlg->appendFilter( aAllFilesName, FILEDIALOG_FILTER_ALL ); + + // if the extension is "All files", make that the default + if (aExtensions == FILEDIALOG_FILTER_ALL) + maSelectFilter = aAllFilesName; + } + catch( const IllegalArgumentException& ) + { + SAL_WARN( "sfx.dialog", "Could not append Filter" ); + } + + // Now add the filter + for ( i = 0; i < nCount; i++ ) + { + OUString aName = mpGraphicFilter->GetImportFormatName( i ); + OUString aExt; + j = 0; + while( true ) + { + OUString sWildcard = mpGraphicFilter->GetImportWildcard( i, j++ ); + if ( sWildcard.isEmpty() ) + break; + if ( aExt.indexOf( sWildcard ) == -1 ) + { + if ( !aExt.isEmpty() ) + aExt += ";"; + aExt += sWildcard; + } + } + aName = ::sfx2::addExtension( aName, aExt, bIsInOpenMode, *this ); + try + { + mxFileDlg->appendFilter( aName, aExt ); + } + catch( const IllegalArgumentException& ) + { + SAL_WARN( "sfx.dialog", "Could not append Filter" ); + } + } +} + +constexpr OUStringLiteral GRF_CONFIG_STR = u" "; +constexpr OUStringLiteral STD_CONFIG_STR = u"1 "; + +static void SetToken( OUString& rOrigStr, sal_Int32 nToken, sal_Unicode cTok, std::u16string_view rStr) +{ + const sal_Unicode* pStr = rOrigStr.getStr(); + sal_Int32 nLen = rOrigStr.getLength(); + sal_Int32 nTok = 0; + sal_Int32 nFirstChar = 0; + sal_Int32 i = nFirstChar; + + // Determine token position and length + pStr += i; + while ( i < nLen ) + { + // Increase token count if match + if ( *pStr == cTok ) + { + ++nTok; + + if ( nTok == nToken ) + nFirstChar = i+1; + else + { + if ( nTok > nToken ) + break; + } + } + + ++pStr; + ++i; + } + + if ( nTok >= nToken ) + rOrigStr = rOrigStr.replaceAt( nFirstChar, i-nFirstChar, rStr ); +} + +namespace +{ +void SaveLastDirectory(OUString const& sContext, OUString const& sDirectory) +{ + if (sContext.isEmpty()) + return; + + std::shared_ptr<comphelper::ConfigurationChanges> batch( + comphelper::ConfigurationChanges::create()); + Reference<container::XNameContainer> set( + officecfg::Office::Common::Misc::FilePickerLastDirectory::get(batch)); + + bool found; + Any v; + try + { + v = set->getByName(sContext); + found = true; + } + catch (container::NoSuchElementException&) + { + found = false; + } + if (found) + { + Reference<XPropertySet> el(v.get<Reference<XPropertySet>>(), UNO_SET_THROW); + el->setPropertyValue("LastPath", Any(sDirectory)); + } + else + { + Reference<XPropertySet> el( + (Reference<lang::XSingleServiceFactory>(set, UNO_QUERY_THROW)->createInstance()), + UNO_QUERY_THROW); + el->setPropertyValue("LastPath", Any(sDirectory)); + Any v2(el); + set->insertByName(sContext, v2); + } + batch->commit(); +} +} + +void FileDialogHelper_Impl::saveConfig() +{ + uno::Reference < XFilePickerControlAccess > xDlg( mxFileDlg, UNO_QUERY ); + Any aValue; + + if ( ! xDlg.is() ) + return; + + if ( mbHasPreview ) + { + SvtViewOptions aDlgOpt( EViewType::Dialog, IMPGRF_CONFIGNAME ); + + try + { + aValue = xDlg->getValue( ExtendedFilePickerElementIds::CHECKBOX_PREVIEW, 0 ); + bool bValue = false; + aValue >>= bValue; + OUString aUserData(GRF_CONFIG_STR); + SetToken( aUserData, 1, ' ', OUString::number( static_cast<sal_Int32>(bValue) ) ); + + INetURLObject aObj( getPath() ); + + if ( aObj.GetProtocol() == INetProtocol::File ) + SetToken( aUserData, 2, ' ', aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + OUString aFilter = getFilter(); + aFilter = EncodeSpaces_Impl( aFilter ); + SetToken( aUserData, 3, ' ', aFilter ); + + aDlgOpt.SetUserItem( USERITEM_NAME, Any( aUserData ) ); + } + catch( const IllegalArgumentException& ){} + } + else + { + bool bWriteConfig = false; + SvtViewOptions aDlgOpt( EViewType::Dialog, IODLG_CONFIGNAME ); + OUString aUserData(STD_CONFIG_STR); + + if ( aDlgOpt.Exists() ) + { + Any aUserItem = aDlgOpt.GetUserItem( USERITEM_NAME ); + OUString aTemp; + if ( aUserItem >>= aTemp ) + aUserData = aTemp; + } + + if ( mbHasAutoExt ) + { + try + { + aValue = xDlg->getValue( ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, 0 ); + bool bAutoExt = true; + aValue >>= bAutoExt; + SetToken( aUserData, 0, ' ', OUString::number( static_cast<sal_Int32>(bAutoExt) ) ); + bWriteConfig = true; + } + catch( const IllegalArgumentException& ){} + } + + if ( ! mbIsSaveDlg ) + { + OUString aPath = getPath(); + if ( comphelper::isFileUrl( aPath ) ) + { + SetToken( aUserData, 1, ' ', aPath ); + bWriteConfig = true; + } + } + + if( mbHasSelectionBox && mbSelectionFltrEnabled ) + { + try + { + aValue = xDlg->getValue( ExtendedFilePickerElementIds::CHECKBOX_SELECTION, 0 ); + bool bSelection = true; + aValue >>= bSelection; + if ( comphelper::string::getTokenCount(aUserData, ' ') < 3 ) + aUserData += " "; + SetToken( aUserData, 2, ' ', OUString::number( static_cast<sal_Int32>(bSelection) ) ); + bWriteConfig = true; + } + catch( const IllegalArgumentException& ){} + } + + if ( bWriteConfig ) + aDlgOpt.SetUserItem( USERITEM_NAME, Any( aUserData ) ); + } + + // Store to config, if explicit context is set. Otherwise store in (global) runtime var. + if (meContext != FileDialogHelper::UnknownContext) + { + SaveLastDirectory(FileDialogHelper::contextToString(meContext), getPath()); + } + else + { + SfxApplication *pSfxApp = SfxGetpApp(); + pSfxApp->SetLastDir_Impl( getPath() ); + } +} + +OUString FileDialogHelper_Impl::getInitPath(std::u16string_view _rFallback, + const sal_Int32 _nFallbackToken) +{ + OUString sPath; + // Load from config, if explicit context is set. Otherwise load from (global) runtime var. + if (meContext != FileDialogHelper::UnknownContext) + { + OUString sContext = FileDialogHelper::contextToString(meContext); + Reference<XNameAccess> set(officecfg::Office::Common::Misc::FilePickerLastDirectory::get()); + Any v; + try + { + v = set->getByName(sContext); + Reference<XPropertySet> el(v.get<Reference<XPropertySet>>(), UNO_SET_THROW); + sPath = el->getPropertyValue("LastPath").get<OUString>(); + } + catch (NoSuchElementException&) + { + } + } + else + { + SfxApplication *pSfxApp = SfxGetpApp(); + sPath = pSfxApp->GetLastDir_Impl(); + } + + if ( sPath.isEmpty() ) + sPath = o3tl::getToken(_rFallback, _nFallbackToken, ' ' ); + + // check if the path points to a valid (accessible) directory + bool bValid = false; + if ( !sPath.isEmpty() ) + { + OUString sPathCheck( sPath ); + if ( sPathCheck[ sPathCheck.getLength() - 1 ] != '/' ) + sPathCheck += "/"; + sPathCheck += "."; + try + { + ::ucbhelper::Content aContent( sPathCheck, + utl::UCBContentHelper::getDefaultCommandEnvironment(), + comphelper::getProcessComponentContext() ); + bValid = aContent.isFolder(); + } + catch( const Exception& ) {} + } + if ( !bValid ) + sPath.clear(); + return sPath; +} + +void FileDialogHelper_Impl::loadConfig() +{ + uno::Reference < XFilePickerControlAccess > xDlg( mxFileDlg, UNO_QUERY ); + Any aValue; + + if ( ! xDlg.is() ) + return; + + if ( mbHasPreview ) + { + SvtViewOptions aViewOpt( EViewType::Dialog, IMPGRF_CONFIGNAME ); + OUString aUserData; + + if ( aViewOpt.Exists() ) + { + Any aUserItem = aViewOpt.GetUserItem( USERITEM_NAME ); + OUString aTemp; + if ( aUserItem >>= aTemp ) + aUserData = aTemp; + } + + if ( !aUserData.isEmpty() ) + { + try + { + // respect the last "insert as link" state + bool bLink = o3tl::toInt32(o3tl::getToken(aUserData, 0, ' ' )); + aValue <<= bLink; + xDlg->setValue( ExtendedFilePickerElementIds::CHECKBOX_LINK, 0, aValue ); + + // respect the last "show preview" state + bool bShowPreview = o3tl::toInt32(o3tl::getToken(aUserData, 1, ' ' )); + aValue <<= bShowPreview; + xDlg->setValue( ExtendedFilePickerElementIds::CHECKBOX_PREVIEW, 0, aValue ); + + if ( maPath.isEmpty() ) + displayFolder( getInitPath( aUserData, 2 ) ); + + if ( maCurFilter.isEmpty() ) + { + OUString aFilter = aUserData.getToken( 3, ' ' ); + aFilter = DecodeSpaces_Impl( aFilter ); + setFilter( aFilter ); + } + + // set the member so we know that we have to show the preview + mbShowPreview = bShowPreview; + } + catch( const IllegalArgumentException& ){} + } + + if ( maPath.isEmpty() ) + displayFolder( SvtPathOptions().GetWorkPath() ); + } + else + { + SvtViewOptions aViewOpt( EViewType::Dialog, IODLG_CONFIGNAME ); + OUString aUserData; + + if ( aViewOpt.Exists() ) + { + Any aUserItem = aViewOpt.GetUserItem( USERITEM_NAME ); + OUString aTemp; + if ( aUserItem >>= aTemp ) + aUserData = aTemp; + } + + if ( aUserData.isEmpty() ) + aUserData = STD_CONFIG_STR; + + if ( maPath.isEmpty() ) + displayFolder( getInitPath( aUserData, 1 ) ); + + if ( mbHasAutoExt ) + { + sal_Int32 nFlag = o3tl::toInt32(o3tl::getToken(aUserData, 0, ' ' )); + aValue <<= static_cast<bool>(nFlag); + try + { + xDlg->setValue( ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION, 0, aValue ); + } + catch( const IllegalArgumentException& ){} + } + + if( mbHasSelectionBox ) + { + sal_Int32 nFlag = o3tl::toInt32(o3tl::getToken(aUserData, 2, ' ' )); + aValue <<= static_cast<bool>(nFlag); + try + { + xDlg->setValue( ExtendedFilePickerElementIds::CHECKBOX_SELECTION, 0, aValue ); + } + catch( const IllegalArgumentException& ){} + } + + if ( maPath.isEmpty() ) + displayFolder( SvtPathOptions().GetWorkPath() ); + } +} + +void FileDialogHelper_Impl::setDefaultValues() +{ + // when no filter is set, we set the currentFilter to <all> + if ( maCurFilter.isEmpty() && !maSelectFilter.isEmpty() ) + { + try + { + mxFileDlg->setCurrentFilter( maSelectFilter ); + } + catch( const IllegalArgumentException& ) + {} + } + + // when no path is set, we use the standard 'work' folder + if ( maPath.isEmpty() ) + { + OUString aWorkFolder = SvtPathOptions().GetWorkPath(); + try + { + mxFileDlg->setDisplayDirectory( aWorkFolder ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.dialog", "FileDialogHelper_Impl::setDefaultValues: caught an exception while setting the display directory!" ); + } + } +} + +bool FileDialogHelper_Impl::isShowFilterExtensionEnabled() const +{ + return !maFilters.empty(); +} + +void FileDialogHelper_Impl::addFilterPair( const OUString& rFilter, + const OUString& rFilterWithExtension ) +{ + maFilters.emplace_back( rFilter, rFilterWithExtension ); + +} + +OUString FileDialogHelper_Impl::getFilterName( std::u16string_view rFilterWithExtension ) const +{ + OUString sRet; + for (auto const& filter : maFilters) + { + if (filter.Second == rFilterWithExtension) + { + sRet = filter.First; + break; + } + } + return sRet; +} + +OUString FileDialogHelper_Impl::getFilterWithExtension( std::u16string_view rFilter ) const +{ + OUString sRet; + for (auto const& filter : maFilters) + { + if ( filter.First == rFilter ) + { + sRet = filter.Second; + break; + } + } + return sRet; +} + +void FileDialogHelper_Impl::SetContext( FileDialogHelper::Context _eNewContext ) +{ + meContext = _eNewContext; + + std::optional<OUString> pConfigId = GetLastFilterConfigId( _eNewContext ); + if( pConfigId ) + LoadLastUsedFilter( *pConfigId ); +} + +// FileDialogHelper + +FileDialogHelper::FileDialogHelper( + sal_Int16 nDialogType, + FileDialogFlags nFlags, + const OUString& rFact, + SfxFilterFlags nMust, + SfxFilterFlags nDont, + weld::Window* pPreferredParent) + : m_nError(0), + mpImpl(new FileDialogHelper_Impl(this, nDialogType, nFlags, SFX2_IMPL_DIALOG_CONFIG, pPreferredParent)) +{ + + // create the list of filters + mpImpl->addFilters( + SfxObjectShell::GetServiceNameFromFactory(rFact), nMust, nDont ); +} + +FileDialogHelper::FileDialogHelper( + sal_Int16 nDialogType, + FileDialogFlags nFlags, + const OUString& rFact, + sal_Int16 nDialog, + SfxFilterFlags nMust, + SfxFilterFlags nDont, + const OUString& rStandardDir, + const css::uno::Sequence< OUString >& rDenyList, + weld::Window* pPreferredParent) + : m_nError(0), + mpImpl( new FileDialogHelper_Impl( this, nDialogType, nFlags, nDialog, pPreferredParent, rStandardDir, rDenyList ) ) +{ + // create the list of filters + mpImpl->addFilters( + SfxObjectShell::GetServiceNameFromFactory(rFact), nMust, nDont ); +} + +FileDialogHelper::FileDialogHelper(sal_Int16 nDialogType, FileDialogFlags nFlags, weld::Window* pPreferredParent) + : m_nError(0), + mpImpl( new FileDialogHelper_Impl( this, nDialogType, nFlags, SFX2_IMPL_DIALOG_CONFIG, pPreferredParent ) ) +{ +} + +FileDialogHelper::FileDialogHelper( + sal_Int16 nDialogType, + FileDialogFlags nFlags, + const OUString& aFilterUIName, + std::u16string_view aExtName, + const OUString& rStandardDir, + const css::uno::Sequence< OUString >& rDenyList, + weld::Window* pPreferredParent ) + : m_nError(0), + mpImpl( new FileDialogHelper_Impl( this, nDialogType, nFlags, SFX2_IMPL_DIALOG_CONFIG, pPreferredParent, rStandardDir, rDenyList ) ) +{ + // the wildcard here is expected in form "*.extension" + OUString aWildcard; + if ( aExtName.find( '*' ) != 0 ) + { + if ( !aExtName.empty() && aExtName.find( '.' ) != 0 ) + aWildcard = "*."; + else + aWildcard = "*"; + } + + aWildcard += aExtName; + + OUString const aUIString = ::sfx2::addExtension( + aFilterUIName, aWildcard, (OPEN == lcl_OpenOrSave(mpImpl->m_nDialogType)), *mpImpl); + AddFilter( aUIString, aWildcard ); +} + +FileDialogHelper::~FileDialogHelper() +{ + mpImpl->dispose(); +} + +void FileDialogHelper::CreateMatcher( const OUString& rFactory ) +{ + mpImpl->createMatcher( SfxObjectShell::GetServiceNameFromFactory(rFactory) ); +} + +void FileDialogHelper::SetControlHelpIds( const sal_Int16* _pControlId, const char** _pHelpId ) +{ + mpImpl->setControlHelpIds( _pControlId, _pHelpId ); +} + +void FileDialogHelper::SetContext( Context _eNewContext ) +{ + mpImpl->SetContext( _eNewContext ); +} + +OUString FileDialogHelper::contextToString(Context context) +{ + // These strings are used in the configuration, to store the last used directory for each context. + // Please don't change them. + switch(context) { + case AcceleratorConfig: + return "AcceleratorConfig"; + case AutoRedact: + return "AutoRedact"; + case BaseDataSource: + return "BaseDataSource"; + case BaseSaveAs: + return "BaseSaveAs"; + case BasicExportDialog: + return "BasicExportDialog"; + case BasicExportPackage: + return "BasicExportPackage"; + case BasicExportSource: + return "BasicExportSource"; + case BasicImportDialog: + return "BasicImportDialog"; + case BasicImportSource: + return "BasicImportSource"; + case BasicInsertLib: + return "BasicInsertLib"; + case BulletsAddImage: + return "BulletsAddImage"; + case CalcDataProvider: + return "CalcDataProvider"; + case CalcDataStream: + return "CalcDataStream"; + case CalcExport: + return "CalcExport"; + case CalcSaveAs: + return "CalcSaveAs"; + case CalcXMLSource: + return "CalcXMLSource"; + case ExportImage: + return "ExportImage"; + case ExtensionManager: + return "ExtensionManager"; + case FormsAddInstance: + return "FormsAddInstance"; + case FormsInsertImage: + return "FormsInsertImage"; + case LinkClientOLE: + return "LinkClientOLE"; + case LinkClientFile: + return "LinkClientFile"; + case DrawImpressInsertFile: + return "DrawImpressInsertFile"; + case DrawImpressOpenSound: + return "DrawImpressOpenSound"; + case DrawExport: + return "DrawExport"; + case DrawSaveAs: + return "DrawSaveAs"; + case IconImport: + return "IconImport"; + case ImpressClickAction: + return "ImpressClickAction"; + case ImpressExport: + return "ImpressExport"; + case ImpressPhotoDialog: + return "ImpressPhotoDialog"; + case ImpressSaveAs: + return "ImpressSaveAs"; + case ImageMap: + return "ImageMap"; + case InsertDoc: + return "InsertDoc"; + case InsertImage: + return "InsertImage"; + case InsertOLE: + return "InsertOLE"; + case InsertMedia: + return "InsertMedia"; + case JavaClassPath: + return "JavaClassPath"; + case ReportInsertImage: + return "ReportInsertImage"; + case ScreenshotAnnotation: + return "ScreenshotAnnotation"; + case SignatureLine: + return "SignatureLine"; + case TemplateImport: + return "TemplateImport"; + case WriterCreateAddressList: + return "WriterCreateAddressList"; + case WriterExport: + return "WriterExport"; + case WriterImportAutotext: + return "WriterImportAutotext"; + case WriterInsertDoc: + return "WriterInsertDoc"; + case WriterInsertHyperlink: + return "WriterInsertHyperlink"; + case WriterInsertImage: + return "WriterInsertImage"; + case WriterInsertScript: + return "WriterInsertScript"; + case WriterLoadTemplate: + return "WriterLoadTemplate"; + case WriterMailMerge: + return "WriterMailMerge"; + case WriterMailMergeSaveAs: + return "WriterMailMergeSaveAs"; + case WriterNewHTMLGlobalDoc: + return "WriterNewHTMLGlobalDoc"; + case WriterRegisterDataSource: + return "WriterRegisterDataSource"; + case WriterSaveAs: + return "WriterSaveAs"; + case WriterSaveHTML: + return "WriterSaveHTML"; + case XMLFilterSettings: + return "XMLFilterSettings"; + case UnknownContext: + default: + return ""; + } +} + +IMPL_LINK_NOARG(FileDialogHelper, ExecuteSystemFilePicker, void*, void) +{ + m_nError = mpImpl->execute(); + m_aDialogClosedLink.Call( this ); +} + +// rDirPath has to be a directory +ErrCode FileDialogHelper::Execute( std::vector<OUString>& rpURLList, + std::optional<SfxAllItemSet>& rpSet, + OUString& rFilter, + const OUString& rDirPath ) +{ + SetDisplayFolder( rDirPath ); + return mpImpl->execute( rpURLList, rpSet, rFilter ); +} + + +ErrCode FileDialogHelper::Execute() +{ + return mpImpl->execute(); +} + +ErrCode FileDialogHelper::Execute( std::optional<SfxAllItemSet>& rpSet, + OUString& rFilter ) +{ + ErrCode nRet; + std::vector<OUString> rURLList; + nRet = mpImpl->execute(rURLList, rpSet, rFilter); + return nRet; +} + +void FileDialogHelper::StartExecuteModal( const Link<FileDialogHelper*,void>& rEndDialogHdl ) +{ + m_aDialogClosedLink = rEndDialogHdl; + m_nError = ERRCODE_NONE; + if (!mpImpl->isAsyncFilePicker()) + Application::PostUserEvent( LINK( this, FileDialogHelper, ExecuteSystemFilePicker ) ); + else + mpImpl->implStartExecute(); +} + +sal_Int16 FileDialogHelper::GetDialogType() const { return mpImpl ? mpImpl->m_nDialogType : 0; } + +bool FileDialogHelper::IsPasswordEnabled() const +{ + return mpImpl && mpImpl->isPasswordEnabled(); +} + +OUString FileDialogHelper::GetRealFilter() const +{ + OUString sFilter; + if (mpImpl) + mpImpl->getRealFilter( sFilter ); + return sFilter; +} + +void FileDialogHelper::SetTitle( const OUString& rNewTitle ) +{ + if ( mpImpl->mxFileDlg.is() ) + mpImpl->mxFileDlg->setTitle( rNewTitle ); +} + +OUString FileDialogHelper::GetPath() const +{ + OUString aPath; + + if ( !mpImpl->mlLastURLs.empty()) + return mpImpl->mlLastURLs[0]; + + if ( mpImpl->mxFileDlg.is() ) + { + Sequence < OUString > aPathSeq = mpImpl->mxFileDlg->getFiles(); + + if ( aPathSeq.getLength() == 1 ) + { + aPath = aPathSeq[0]; + } + } + + return aPath; +} + +Sequence < OUString > FileDialogHelper::GetMPath() const +{ + if ( !mpImpl->mlLastURLs.empty()) + return comphelper::containerToSequence(mpImpl->mlLastURLs); + + if ( mpImpl->mxFileDlg.is() ) + return mpImpl->mxFileDlg->getFiles(); + else + { + Sequence < OUString > aEmpty; + return aEmpty; + } +} + +Sequence< OUString > FileDialogHelper::GetSelectedFiles() const +{ + // a) the new way (optional!) + uno::Sequence< OUString > aResultSeq; + if (mpImpl->mxFileDlg.is()) + { + aResultSeq = mpImpl->mxFileDlg->getSelectedFiles(); + } + // b) the olde way ... non optional. + else + { + uno::Reference< XFilePicker > xPickOld(mpImpl->mxFileDlg, UNO_QUERY_THROW); + Sequence< OUString > lFiles = xPickOld->getFiles(); + ::sal_Int32 nFiles = lFiles.getLength(); + if ( nFiles > 1 ) + { + aResultSeq = Sequence< OUString >( nFiles-1 ); + auto pResultSeq = aResultSeq.getArray(); + + INetURLObject aPath( lFiles[0] ); + aPath.setFinalSlash(); + + for (::sal_Int32 i = 1; i < nFiles; i++) + { + if (i == 1) + aPath.Append( lFiles[i] ); + else + aPath.setName( lFiles[i] ); + + pResultSeq[i-1] = aPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + } + else + aResultSeq = lFiles; + } + + return aResultSeq; +} + +OUString FileDialogHelper::GetDisplayDirectory() const +{ + return mpImpl->getPath(); +} + +OUString FileDialogHelper::GetCurrentFilter() const +{ + return mpImpl->getFilter(); +} + +ErrCode FileDialogHelper::GetGraphic( Graphic& rGraphic ) const +{ + return mpImpl->getGraphic( rGraphic ); +} + +static int impl_isFolder( const OUString& rPath ) +{ + try + { + ::ucbhelper::Content aContent( + rPath, uno::Reference< ucb::XCommandEnvironment > (), + comphelper::getProcessComponentContext() ); + if ( aContent.isFolder() ) + return 1; + + return 0; + } + catch ( const Exception & ) + { + } + + return -1; +} + +void FileDialogHelper::SetDisplayDirectory( const OUString& _rPath ) +{ + if ( _rPath.isEmpty() ) + return; + + // if the given path isn't a folder, we cut off the last part + // and take it as filename and the rest of the path should be + // the folder + + INetURLObject aObj( _rPath ); + + OUString sFileName = aObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + aObj.removeSegment(); + OUString sPath = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + int nIsFolder = impl_isFolder( _rPath ); + if ( nIsFolder == 0 || + ( nIsFolder == -1 && impl_isFolder( sPath ) == 1 ) ) + { + mpImpl->setFileName( sFileName ); + mpImpl->displayFolder( sPath ); + } + else + { + INetURLObject aObjPathName( _rPath ); + OUString sFolder( aObjPathName.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + if ( sFolder.isEmpty() ) + { + // _rPath is not a valid path -> fallback to home directory + osl::Security aSecurity; + aSecurity.getHomeDir( sFolder ); + } + mpImpl->displayFolder( sFolder ); + } +} + +void FileDialogHelper::SetDisplayFolder( const OUString& _rURL ) +{ + mpImpl->displayFolder( _rURL ); +} + +void FileDialogHelper::SetFileName( const OUString& _rFileName ) +{ + mpImpl->setFileName( _rFileName ); +} + +void FileDialogHelper::AddFilter( const OUString& rFilterName, + const OUString& rExtension ) +{ + mpImpl->addFilter( rFilterName, rExtension ); +} + +void FileDialogHelper::SetCurrentFilter( const OUString& rFilter ) +{ + OUString sFilter( rFilter ); + if ( mpImpl->isShowFilterExtensionEnabled() ) + sFilter = mpImpl->getFilterWithExtension( rFilter ); + mpImpl->setFilter( sFilter ); +} + +const uno::Reference < XFilePicker3 >& FileDialogHelper::GetFilePicker() const +{ + return mpImpl->mxFileDlg; +} + +// XFilePickerListener Methods +void FileDialogHelper::FileSelectionChanged() +{ + mpImpl->handleFileSelectionChanged(); +} + +void FileDialogHelper::DirectoryChanged() +{ + mpImpl->handleDirectoryChanged(); +} + +OUString FileDialogHelper::HelpRequested( const FilePickerEvent& aEvent ) +{ + return sfx2::FileDialogHelper_Impl::handleHelpRequested( aEvent ); +} + +void FileDialogHelper::ControlStateChanged( const FilePickerEvent& aEvent ) +{ + mpImpl->handleControlStateChanged( aEvent ); +} + +void FileDialogHelper::DialogSizeChanged() +{ + mpImpl->handleDialogSizeChanged(); +} + +void FileDialogHelper::DialogClosed( const DialogClosedEvent& _rEvent ) +{ + m_nError = ( RET_OK == _rEvent.DialogResult ) ? ERRCODE_NONE : ERRCODE_ABORT; + m_aDialogClosedLink.Call( this ); +} + +ErrCode FileOpenDialog_Impl( weld::Window* pParent, + sal_Int16 nDialogType, + FileDialogFlags nFlags, + std::vector<OUString>& rpURLList, + OUString& rFilter, + std::optional<SfxAllItemSet>& rpSet, + const OUString* pPath, + sal_Int16 nDialog, + const OUString& rStandardDir, + const css::uno::Sequence< OUString >& rDenyList ) +{ + ErrCode nRet; + std::unique_ptr<FileDialogHelper> pDialog; + // Sign existing PDF: only works with PDF files and they are opened + // read-only to discourage editing (which would invalidate existing + // signatures). + if (nFlags & FileDialogFlags::SignPDF) + pDialog.reset(new FileDialogHelper(nDialogType, nFlags, SfxResId(STR_SFX_FILTERNAME_PDF), u"pdf", rStandardDir, rDenyList, pParent)); + else + pDialog.reset(new FileDialogHelper(nDialogType, nFlags, OUString(), nDialog, SfxFilterFlags::NONE, SfxFilterFlags::NONE, rStandardDir, rDenyList, pParent)); + + OUString aPath; + if ( pPath ) + aPath = *pPath; + + nRet = pDialog->Execute(rpURLList, rpSet, rFilter, aPath); + DBG_ASSERT( rFilter.indexOf(": ") == -1, "Old filter name used!"); + + if (rpSet && nFlags & FileDialogFlags::SignPDF) + rpSet->Put(SfxBoolItem(SID_DOC_READONLY, true)); + return nRet; +} + +ErrCode RequestPassword(const std::shared_ptr<const SfxFilter>& pCurrentFilter, OUString const & aURL, SfxItemSet* pSet, const css::uno::Reference<css::awt::XWindow>& rParent) +{ + uno::Reference<task::XInteractionHandler2> xInteractionHandler = task::InteractionHandler::createWithParent(::comphelper::getProcessComponentContext(), rParent); + // TODO: need a save way to distinguish MS filters from other filters + // for now MS-filters are the only alien filters that support encryption + const bool bMSType = !pCurrentFilter->IsOwnFormat(); + // For OOXML we can use the standard password ("unlimited" characters) + // request, otherwise the MS limited password request is needed. + const bool bOOXML = bMSType && lclSupportsOOXMLEncryption( pCurrentFilter->GetFilterName()); + const ::comphelper::DocPasswordRequestType eType = bMSType && !bOOXML ? + ::comphelper::DocPasswordRequestType::MS : + ::comphelper::DocPasswordRequestType::Standard; + + ::rtl::Reference< ::comphelper::DocPasswordRequest > pPasswordRequest( new ::comphelper::DocPasswordRequest( eType, css::task::PasswordRequestMode_PASSWORD_CREATE, aURL, bool( pCurrentFilter->GetFilterFlags() & SfxFilterFlags::PASSWORDTOMODIFY ) ) ); + + uno::Reference< css::task::XInteractionRequest > rRequest( pPasswordRequest ); + do + { + xInteractionHandler->handle( rRequest ); + if (!pPasswordRequest->isPassword() || bMSType) + { + break; + } + OString const utf8Pwd(OUStringToOString(pPasswordRequest->getPassword(), RTL_TEXTENCODING_UTF8)); + OString const utf8Ptm(OUStringToOString(pPasswordRequest->getPasswordToModify(), RTL_TEXTENCODING_UTF8)); + if (!(52 <= utf8Pwd.getLength() && utf8Pwd.getLength() <= 55 + && GetODFSaneDefaultVersion() < SvtSaveOptions::ODFSVER_012) + && (52 > utf8Ptm.getLength() || utf8Ptm.getLength() > 55)) + { + break; + } + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(Application::GetFrameWeld(rParent), VclMessageType::Warning, + VclButtonsType::Ok, SfxResId(STR_PASSWORD_LEN))); + xBox->set_secondary_text(SfxResId(STR_PASSWORD_WARNING)); + xBox->run(); + } + while (true); + if ( pPasswordRequest->isPassword() ) + { + if ( pPasswordRequest->getPassword().getLength() ) + { + css::uno::Sequence< css::beans::NamedValue > aEncryptionData; + + // TODO/LATER: The filters should show the password dialog themself in future + if ( bMSType ) + { + if (bOOXML) + { + ::comphelper::SequenceAsHashMap aHashData; + aHashData[ OUString( "OOXPassword" ) ] <<= pPasswordRequest->getPassword(); + aHashData[ OUString( "CryptoType" ) ] <<= OUString( "Standard" ); + aEncryptionData = aHashData.getAsConstNamedValueList(); + } + else + { + uno::Sequence< sal_Int8 > aUniqueID = ::comphelper::DocPasswordHelper::GenerateRandomByteSequence( 16 ); + uno::Sequence< sal_Int8 > aEncryptionKey = ::comphelper::DocPasswordHelper::GenerateStd97Key( pPasswordRequest->getPassword(), aUniqueID ); + + if ( aEncryptionKey.hasElements() ) + { + ::comphelper::SequenceAsHashMap aHashData; + aHashData[ OUString( "STD97EncryptionKey" ) ] <<= aEncryptionKey; + aHashData[ OUString( "STD97UniqueID" ) ] <<= aUniqueID; + + aEncryptionData = aHashData.getAsConstNamedValueList(); + } + else + { + return ERRCODE_IO_NOTSUPPORTED; + } + } + } + + // tdf#118639: We need ODF encryption data for autorecovery where password will already + // be unavailable, even for non-ODF documents, so append it here unconditionally + pSet->Put(SfxUnoAnyItem( + SID_ENCRYPTIONDATA, + uno::Any(comphelper::concatSequences( + aEncryptionData, comphelper::OStorageHelper::CreatePackageEncryptionData( + pPasswordRequest->getPassword()))))); + } + + if ( pPasswordRequest->getRecommendReadOnly() ) + pSet->Put( SfxBoolItem( SID_RECOMMENDREADONLY, true ) ); + + if ( bMSType ) + { + if (bOOXML) + { + uno::Sequence<beans::PropertyValue> aModifyPasswordInfo + = ::comphelper::DocPasswordHelper::GenerateNewModifyPasswordInfoOOXML( + pPasswordRequest->getPasswordToModify()); + if (aModifyPasswordInfo.hasElements()) + pSet->Put( + SfxUnoAnyItem(SID_MODIFYPASSWORDINFO, uno::Any(aModifyPasswordInfo))); + } + else + { + // the empty password has 0 as Hash + sal_Int32 nHash = SfxMedium::CreatePasswordToModifyHash( + pPasswordRequest->getPasswordToModify(), + pCurrentFilter->GetServiceName() == "com.sun.star.text.TextDocument"); + if (nHash) + pSet->Put(SfxUnoAnyItem(SID_MODIFYPASSWORDINFO, uno::Any(nHash))); + } + } + else + { + uno::Sequence< beans::PropertyValue > aModifyPasswordInfo = ::comphelper::DocPasswordHelper::GenerateNewModifyPasswordInfo( pPasswordRequest->getPasswordToModify() ); + if ( aModifyPasswordInfo.hasElements() ) + pSet->Put( SfxUnoAnyItem( SID_MODIFYPASSWORDINFO, uno::Any( aModifyPasswordInfo ) ) ); + } + } + else + return ERRCODE_ABORT; + return ERRCODE_NONE; +} + +OUString EncodeSpaces_Impl( const OUString& rSource ) +{ + OUString sRet = rSource.replaceAll( " ", "%20" ); + return sRet; +} + +OUString DecodeSpaces_Impl( const OUString& rSource ) +{ + OUString sRet = rSource.replaceAll( "%20", " " ); + return sRet; +} + +} // end of namespace sfx2 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/filedlgimpl.hxx b/sfx2/source/dialog/filedlgimpl.hxx new file mode 100644 index 000000000..e5910790c --- /dev/null +++ b/sfx2/source/dialog/filedlgimpl.hxx @@ -0,0 +1,220 @@ +/* -*- 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_DIALOG_FILEDLGIMPL_HXX +#define INCLUDED_SFX2_SOURCE_DIALOG_FILEDLGIMPL_HXX + +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include <vcl/graph.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/ui/dialogs/XFilePickerListener.hpp> +#include <com/sun/star/ui/dialogs/XDialogClosedListener.hpp> +#include <sfx2/fcontnr.hxx> +#include <sfx2/filedlghelper.hxx> + +class SfxFilterMatcher; +class GraphicFilter; +class FileDialogHelper; +struct ImplSVEvent; + +namespace sfx2 +{ + class FileDialogHelper_Impl : + public ::cppu::WeakImplHelper< + css::ui::dialogs::XFilePickerListener, + css::ui::dialogs::XDialogClosedListener > + { + friend class FileDialogHelper; + + css::uno::Reference < css::ui::dialogs::XFilePicker3 > mxFileDlg; + css::uno::Reference < css::container::XNameAccess > mxFilterCFG; + + std::vector< css::beans::StringPair > maFilters; + + SfxFilterMatcher* mpMatcher; + std::unique_ptr<GraphicFilter> mpGraphicFilter; + FileDialogHelper* mpAntiImpl; + weld::Window* mpFrameWeld; + + ::std::vector< OUString > mlLastURLs; + + OUString maPath; + OUString maFileName; + OUString maCurFilter; + OUString maSelectFilter; + OUString maButtonLabel; + + Idle maPreviewIdle; + Graphic maGraphic; + + const short m_nDialogType; + + SfxFilterFlags m_nMustFlags; + SfxFilterFlags m_nDontFlags; + + ImplSVEvent * mnPostUserEventId; + + FileDialogHelper::Context meContext; + + bool mbHasPassword : 1; + bool mbIsPwdEnabled : 1; + bool m_bHaveFilterOptions : 1; + bool mbHasVersions : 1; + bool mbHasAutoExt : 1; + bool mbHasPreview : 1; + bool mbShowPreview : 1; + bool mbIsSaveDlg : 1; + bool mbExport : 1; + + bool mbDeleteMatcher : 1; + bool mbInsert : 1; + bool mbSystemPicker : 1; + bool mbAsyncPicker : 1; + bool mbPwdCheckBoxState : 1; + bool mbSelection : 1; + bool mbSelectionEnabled : 1; + bool mbHasSelectionBox : 1; + bool mbSelectionFltrEnabled : 1; + + private: + void addFilters( const OUString& rFactory, + SfxFilterFlags nMust, + SfxFilterFlags nDont ); + void addFilter( const OUString& rFilterName, + const OUString& rExtension ); + void addGraphicFilter(); + void enablePasswordBox( bool bInit ); + void updateFilterOptionsBox(); + void updateExportButton(); + void updateSelectionBox(); + void updateVersions(); + void updatePreviewState( bool _bUpdatePreviewWindow ); + void dispose(); + + void loadConfig(); + void saveConfig(); + + std::shared_ptr<const SfxFilter> getCurentSfxFilter(); + bool updateExtendedControl( sal_Int16 _nExtendedControlId, bool _bEnable ); + + ErrCode getGraphic( const OUString& rURL, Graphic& rGraphic ) const; + void setDefaultValues(); + + void preExecute(); + void postExecute( sal_Int16 _nResult ); + sal_Int16 implDoExecute(); + void implStartExecute(); + + void setControlHelpIds( const sal_Int16* _pControlId, const char** _pHelpId ); + + bool CheckFilterOptionsCapability( const std::shared_ptr<const SfxFilter>& _pFilter ); + + bool isInOpenMode() const; + OUString getCurrentFilterUIName() const; + + void LoadLastUsedFilter( const OUString& _rContextIdentifier ); + void SaveLastUsedFilter(); + + void implInitializeFileName( ); + + void verifyPath( ); + + void implGetAndCacheFiles( const css::uno::Reference< XInterface >& xPicker , + std::vector<OUString>& rpURLList ); + + DECL_LINK( TimeOutHdl_Impl, Timer *, void); + DECL_LINK( InitControls, void*, void ); + + public: + // XFilePickerListener methods + virtual void SAL_CALL fileSelectionChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) override; + virtual void SAL_CALL directoryChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) override; + virtual OUString SAL_CALL helpRequested( const css::ui::dialogs::FilePickerEvent& aEvent ) override; + virtual void SAL_CALL controlStateChanged( const css::ui::dialogs::FilePickerEvent& aEvent ) override; + virtual void SAL_CALL dialogSizeChanged() override; + + // XDialogClosedListener methods + virtual void SAL_CALL dialogClosed( const css::ui::dialogs::DialogClosedEvent& _rEvent ) override; + + // XEventListener methods + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // handle XFilePickerListener events + void handleFileSelectionChanged(); + void handleDirectoryChanged(); + static OUString handleHelpRequested( const css::ui::dialogs::FilePickerEvent& aEvent ); + void handleControlStateChanged( const css::ui::dialogs::FilePickerEvent& aEvent ); + void handleDialogSizeChanged(); + + // Own methods + FileDialogHelper_Impl( + FileDialogHelper* _pAntiImpl, + const sal_Int16 nDialogType, + FileDialogFlags nFlags, + sal_Int16 nDialog, + weld::Window* pFrameWeld, + const OUString& sStandardDir = OUString(), + const css::uno::Sequence< OUString >& rDenyList = css::uno::Sequence< OUString >() + ); + virtual ~FileDialogHelper_Impl() override; + + ErrCode execute( std::vector<OUString>& rpURLList, + std::optional<SfxAllItemSet>& rpSet, + OUString& rFilter ); + ErrCode execute(); + + void setFilter( const OUString& rFilter ); + + /** sets the directory which should be browsed + + <p>If the given path does not point to a valid (existent and accessible) folder, the request + is silently dropped</p> + */ + void displayFolder( const OUString& rPath ); + void setFileName( const OUString& _rFile ); + + OUString getPath() const; + OUString getFilter() const; + void getRealFilter( OUString& _rFilter ) const; + + ErrCode getGraphic( Graphic& rGraphic ) const; + void createMatcher( const OUString& rFactory ); + + bool isShowFilterExtensionEnabled() const; + void addFilterPair( const OUString& rFilter, + const OUString& rFilterWithExtension ); + OUString getFilterName( std::u16string_view rFilterWithExtension ) const; + OUString getFilterWithExtension( std::u16string_view rFilter ) const; + + void SetContext( FileDialogHelper::Context _eNewContext ); + OUString getInitPath( std::u16string_view _rFallback, const sal_Int32 _nFallbackToken ); + + bool isAsyncFilePicker() const { return mbAsyncPicker; } + bool isPasswordEnabled() const { return mbIsPwdEnabled; } + + css::uno::Reference<css::awt::XWindow> GetFrameInterface(); + }; + +} // end of namespace sfx2 + +#endif // INCLUDED_SFX2_SOURCE_DIALOG_FILEDLGIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/filtergrouping.cxx b/sfx2/source/dialog/filtergrouping.cxx new file mode 100644 index 000000000..ef1a4fef3 --- /dev/null +++ b/sfx2/source/dialog/filtergrouping.cxx @@ -0,0 +1,1169 @@ +/* -*- 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 "filtergrouping.hxx" +#include <o3tl/safeint.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/docfilt.hxx> +#include <sfx2/sfxresid.hxx> +#include <sal/log.hxx> +#include <com/sun/star/ui/dialogs/XFilterGroupManager.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <unotools/confignode.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/string.hxx> +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> + +#include <list> +#include <vector> +#include <map> +#include <algorithm> + + +namespace sfx2 +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::ui::dialogs; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::beans; + using namespace ::utl; + + + /** + + Some general words about what's going on here... + + <p>In our file open dialog, usually we display every filter we know. That's how it was before: every filter + lead to an own line in the filter list box, e.g. "StarWriter 5.0 Document" or "Microsoft Word 97".</p> + + <p>But then the PM came. And everything changed...</p> + + <p>A basic idea are groups: Why simply listing all the single filters? Couldn't we draw nice separators + between the filters which logically belong together? I.e. all the filters which open a document in StarWriter: + couldn't we separate them from all the filters which open the document in StarCalc?<br/> + So spoke the PM, and engineering obeyed.</p> + + <p>So we have groups. They're just a visual aspect: All the filters of a group are presented together, separated + by a line from other groups.</p> + + <p>Let's be honest: How the concrete implementation of the file picker service separates the different groups + is a matter of this implementation. We only do this grouping and suggest it to the FilePicker service ...</p> + + <p>Now for the second concept:<br/> + Thinking about it (and that's what the PM did), both "StarWriter 5.0 Document" and "Microsoft Word 97" + describe a text document. It's a text. It's of no interest for the user that one of the texts was saved in + MS' format, and one in our own format.<br/> + So in a first step, we want to have a filter entry "Text documents". This would cover both above-mentioned + filters, as well as any other filters for documents which are texts.</p> + + <p>Such an entry as "Text documents" is - within the scope of this file - called "class" or "filter class".</p> + + <p>In the file-open-dialog, such a class looks like an ordinary filter: it's simply a name in the filter + listbox. Selecting means that all the files matching one of the "sub-filters" are displayed (in the example above, + this would be "*.sdw", "*.doc" and so on).</p> + + <p>Now there are two types of filter classes: global ones and local ones. "Text documents" is a global class. As + well as "Spreadsheets". Or "Web pages".<br/> + Let's have a look at a local class: The filters "MS Word 95" and "MS WinWord 6.0" together form the class + "Microsoft Word 6.0 / 95" (don't ask for the reasons. At least not me. Ask the PM). There are a lot of such + local classes ...</p> + + <p>The difference between global and local classes is as follows: Global classes are presented in an own group. + There is one dedicated group at the top of the list, containing all the global groups - no local groups and no + single filters.</p> + + <p>Ehm - it was a lie. Not really at the top. Before this group, there is this single "All files" entry. It forms + its own group. But this is uninteresting here.</p> + + <p>Local classes must consist of filters which - without the classification - would all belong to the same group. + Then, they're combined to one entry (in the example above: "Microsoft Word 6.0 / 95"), and this entry is inserted + into the file picker filter list, instead of the single filters which form the class.</p> + + <p>This is an interesting difference between local and global classes: Filters which are part of a global class + are listed in their own group, too. Filters in local classes aren't listed a second time - neither directly (as + the filter itself) nor indirectly (as part of another local group).</p> + + <p>The only exception are filters which are part of a global class <em>and</em> a local class. This is allowed. + Being contained in two local classes isn't.</p> + + <p>So that's all what you need to know: Understand the concept of "filter classes" (a filter class combines + different filters and acts as if it's a filter itself) and the concept of groups (a group just describes a + logical correlation of filters and usually is represented to the user by drawing group separators in the filter + list).</p> + + <p>If you got it, go try understanding this file :).</p> + + */ + + + typedef StringPair FilterDescriptor; // a single filter or a filter class (display name and filter mask) + typedef ::std::list< FilterDescriptor > FilterGroup; // a list of single filter entries + typedef ::std::list< FilterGroup > GroupedFilterList; // a list of all filters, already grouped + + /// the logical name of a filter + typedef OUString FilterName; + + // a struct which holds references from a logical filter name to a filter group entry + // used for quick lookup of classes (means class entries - entries representing a class) + // which a given filter may belong to + typedef ::std::map< OUString, FilterGroup::iterator > FilterGroupEntryReferrer; + + namespace { + + /// a descriptor for a filter class (which in the final dialog is represented by one filter entry) + struct FilterClass + { + OUString sDisplayName; // the display name + Sequence< FilterName > aSubFilters; // the (logical) names of the filter which belong to the class + }; + + } + + typedef ::std::list< FilterClass > FilterClassList; + typedef ::std::map< OUString, FilterClassList::iterator > FilterClassReferrer; + + +// = reading of configuration data + + + static void lcl_ReadFilterClass( const OConfigurationNode& _rClassesNode, const OUString& _rLogicalClassName, + FilterClass& /* [out] */ _rClass ) + { + // the description node for the current class + OConfigurationNode aClassDesc = _rClassesNode.openNode( _rLogicalClassName ); + + // the values + aClassDesc.getNodeValue( "DisplayName" ) >>= _rClass.sDisplayName; + aClassDesc.getNodeValue( "Filters" ) >>= _rClass.aSubFilters; + } + + namespace { + + struct CreateEmptyClassRememberPos + { + protected: + FilterClassList& m_rClassList; + FilterClassReferrer& m_rClassesReferrer; + + public: + CreateEmptyClassRememberPos( FilterClassList& _rClassList, FilterClassReferrer& _rClassesReferrer ) + :m_rClassList ( _rClassList ) + ,m_rClassesReferrer ( _rClassesReferrer ) + { + } + + // operate on a single class name + void operator() ( const FilterName& _rLogicalFilterName ) + { + // insert a new (empty) class + m_rClassList.emplace_back( ); + // get the position of this new entry + FilterClassList::iterator aInsertPos = m_rClassList.end(); + --aInsertPos; + // remember this position + m_rClassesReferrer.emplace( _rLogicalFilterName, aInsertPos ); + } + }; + + + struct ReadGlobalFilter + { + protected: + OConfigurationNode m_aClassesNode; + FilterClassReferrer& m_aClassReferrer; + + public: + ReadGlobalFilter( const OConfigurationNode& _rClassesNode, FilterClassReferrer& _rClassesReferrer ) + :m_aClassesNode ( _rClassesNode ) + ,m_aClassReferrer ( _rClassesReferrer ) + { + } + + // operate on a single logical name + void operator() ( const FilterName& _rName ) + { + FilterClassReferrer::iterator aClassRef = m_aClassReferrer.find( _rName ); + if ( m_aClassReferrer.end() == aClassRef ) + { + // we do not know this global class + OSL_FAIL( "ReadGlobalFilter::operator(): unknown filter name!" ); + // TODO: perhaps we should be more tolerant - at the moment, the filter is dropped + // We could silently push_back it to the container... + } + else + { + // read the data of this class into the node referred to by aClassRef + lcl_ReadFilterClass( m_aClassesNode, _rName, *aClassRef->second ); + } + } + }; + + } + + static void lcl_ReadGlobalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rGlobalClasses, std::vector<OUString>& _rGlobalClassNames ) + { + _rGlobalClasses.clear(); + _rGlobalClassNames.clear(); + + // get the list describing the order of all global classes + Sequence< OUString > aGlobalClasses; + _rFilterClassification.getNodeValue( "GlobalFilters/Order" ) >>= aGlobalClasses; + + // copy the logical names + comphelper::sequenceToContainer(_rGlobalClassNames, aGlobalClasses); + + // Global classes are presented in an own group, so their order matters (while the order of the + // "local classes" doesn't). + // That's why we can't simply add the global classes to _rGlobalClasses using the order in which they + // are returned from the configuration - it is completely undefined, and we need a _defined_ order. + FilterClassReferrer aClassReferrer; + ::std::for_each( + std::cbegin(aGlobalClasses), + std::cend(aGlobalClasses), + CreateEmptyClassRememberPos( _rGlobalClasses, aClassReferrer ) + ); + // now _rGlobalClasses contains a dummy entry for each global class, + // while aClassReferrer maps from the logical name of the class to the position within _rGlobalClasses where + // it's dummy entry resides + + + // go for all the single class entries + OConfigurationNode aFilterClassesNode = + _rFilterClassification.openNode( "GlobalFilters/Classes" ); + const Sequence< OUString > aFilterClasses = aFilterClassesNode.getNodeNames(); + ::std::for_each( + aFilterClasses.begin(), + aFilterClasses.end(), + ReadGlobalFilter( aFilterClassesNode, aClassReferrer ) + ); + } + + namespace { + + struct ReadLocalFilter + { + protected: + OConfigurationNode m_aClassesNode; + FilterClassList& m_rClasses; + + public: + ReadLocalFilter( const OConfigurationNode& _rClassesNode, FilterClassList& _rClasses ) + :m_aClassesNode ( _rClassesNode ) + ,m_rClasses ( _rClasses ) + { + } + + // operate on a single logical name + void operator() ( const FilterName& _rName ) + { + // read the data for this class + FilterClass aClass; + lcl_ReadFilterClass( m_aClassesNode, _rName, aClass ); + + // insert the class descriptor + m_rClasses.push_back( aClass ); + } + }; + + } + + static void lcl_ReadLocalFilters( const OConfigurationNode& _rFilterClassification, FilterClassList& _rLocalClasses ) + { + _rLocalClasses.clear(); + + // the node for the local classes + OConfigurationNode aFilterClassesNode = + _rFilterClassification.openNode( "LocalFilters/Classes" ); + const Sequence< OUString > aFilterClasses = aFilterClassesNode.getNodeNames(); + + ::std::for_each( + aFilterClasses.begin(), + aFilterClasses.end(), + ReadLocalFilter( aFilterClassesNode, _rLocalClasses ) + ); + } + + + static void lcl_ReadClassification( FilterClassList& _rGlobalClasses, std::vector<OUString>& _rGlobalClassNames, FilterClassList& _rLocalClasses ) + { + + // open our config node + OConfigurationTreeRoot aFilterClassification = OConfigurationTreeRoot::createWithComponentContext( + ::comphelper::getProcessComponentContext(), + "org.openoffice.Office.UI/FilterClassification", + -1, + OConfigurationTreeRoot::CM_READONLY + ); + + + // go for the global classes + lcl_ReadGlobalFilters( aFilterClassification, _rGlobalClasses, _rGlobalClassNames ); + + + // go for the local classes + lcl_ReadLocalFilters( aFilterClassification, _rLocalClasses ); + + } + + +// = grouping and classifying + + namespace { + + // a struct which adds helps remembering a reference to a class entry + struct ReferToFilterEntry + { + protected: + FilterGroupEntryReferrer& m_rEntryReferrer; + FilterGroup::iterator m_aClassPos; + + public: + ReferToFilterEntry( FilterGroupEntryReferrer& _rEntryReferrer, const FilterGroup::iterator& _rClassPos ) + :m_rEntryReferrer( _rEntryReferrer ) + ,m_aClassPos( _rClassPos ) + { + } + + // operate on a single filter name + void operator() ( const FilterName& _rName ) + { + ::std::pair< FilterGroupEntryReferrer::iterator, bool > aInsertRes = + m_rEntryReferrer.emplace( _rName, m_aClassPos ); + SAL_WARN_IF( + !aInsertRes.second, "sfx.dialog", + "already have an element for " << _rName); + } + }; + + + struct FillClassGroup + { + protected: + FilterGroup& m_rClassGroup; + FilterGroupEntryReferrer& m_rClassReferrer; + + public: + FillClassGroup( FilterGroup& _rClassGroup, FilterGroupEntryReferrer& _rClassReferrer ) + :m_rClassGroup ( _rClassGroup ) + ,m_rClassReferrer ( _rClassReferrer ) + { + } + + // operate on a single class + void operator() ( const FilterClass& _rClass ) + { + // create an empty filter descriptor for the class + FilterDescriptor aClassEntry; + // set its name (which is all we know by now) + aClassEntry.First = _rClass.sDisplayName; + + // add it to the group + m_rClassGroup.push_back( aClassEntry ); + // the position of the newly added class + FilterGroup::iterator aClassEntryPos = m_rClassGroup.end(); + --aClassEntryPos; + + // and for all the sub filters of the class, remember the class + // (respectively the position of the class it the group) + ::std::for_each( + _rClass.aSubFilters.begin(), + _rClass.aSubFilters.end(), + ReferToFilterEntry( m_rClassReferrer, aClassEntryPos ) + ); + } + }; + + } + + const sal_Unicode s_cWildcardSeparator( ';' ); + + static OUString getSeparatorString() + { + return ";"; + } + + namespace { + + struct CheckAppendSingleWildcard + { + OUString& _rToBeExtended; + + explicit CheckAppendSingleWildcard( OUString& _rBase ) : _rToBeExtended( _rBase ) { } + + void operator() ( const OUString& _rWC ) + { + // check for double wildcards + sal_Int32 nExistentPos = _rToBeExtended.indexOf( _rWC ); + if ( -1 < nExistentPos ) + { // found this wildcard (already part of _rToBeExtended) + if ( ( 0 == nExistentPos ) + || ( s_cWildcardSeparator == _rToBeExtended[ nExistentPos - 1 ] ) + ) + { // the wildcard really starts at this position (it starts at pos 0 or the previous character is a separator + sal_Int32 nExistentWCEnd = nExistentPos + _rWC.getLength(); + if ( ( _rToBeExtended.getLength() == nExistentWCEnd ) + || ( s_cWildcardSeparator == _rToBeExtended[ nExistentWCEnd ] ) + ) + { // it's really the complete wildcard we found + // (not something like _rWC being "*.t" and _rToBeExtended containing "*.txt") + // -> outta here + return; + } + } + } + + if ( !_rToBeExtended.isEmpty() ) + _rToBeExtended += getSeparatorString(); + _rToBeExtended += _rWC; + } + }; + + + // a helper struct which adds a fixed (Sfx-)filter to a filter group entry given by iterator + struct AppendWildcardToDescriptor + { + protected: + ::std::vector< OUString > aWildCards; + + public: + explicit AppendWildcardToDescriptor( const OUString& _rWildCard ); + + // operate on a single class entry + void operator() ( const FilterGroupEntryReferrer::value_type& _rClassReference ) + { + // simply add our wildcards + ::std::for_each( + aWildCards.begin(), + aWildCards.end(), + CheckAppendSingleWildcard( _rClassReference.second->Second ) + ); + } + }; + + } + + AppendWildcardToDescriptor::AppendWildcardToDescriptor( const OUString& _rWildCard ) + { + DBG_ASSERT( !_rWildCard.isEmpty(), + "AppendWildcardToDescriptor::AppendWildcardToDescriptor: invalid wildcard!" ); + DBG_ASSERT( _rWildCard.isEmpty() || _rWildCard[0] != s_cWildcardSeparator, + "AppendWildcardToDescriptor::AppendWildcardToDescriptor: wildcard already separated!" ); + + aWildCards.reserve( comphelper::string::getTokenCount(_rWildCard, s_cWildcardSeparator) ); + + const sal_Unicode* pTokenLoop = _rWildCard.getStr(); + const sal_Unicode* pTokenLoopEnd = pTokenLoop + _rWildCard.getLength(); + const sal_Unicode* pTokenStart = pTokenLoop; + for ( ; pTokenLoop != pTokenLoopEnd; ++pTokenLoop ) + { + if ( ( s_cWildcardSeparator == *pTokenLoop ) && ( pTokenLoop > pTokenStart ) ) + { // found a new token separator (and a non-empty token) + aWildCards.emplace_back( pTokenStart, pTokenLoop - pTokenStart ); + + // search the start of the next token + while ( ( pTokenStart != pTokenLoopEnd ) && ( *pTokenStart != s_cWildcardSeparator ) ) + ++pTokenStart; + + if ( pTokenStart == pTokenLoopEnd ) + // reached the end + break; + + ++pTokenStart; + pTokenLoop = pTokenStart; + } + } + if ( pTokenLoop > pTokenStart ) + // the last one... + aWildCards.emplace_back( pTokenStart, pTokenLoop - pTokenStart ); + } + + + static void lcl_InitGlobalClasses( GroupedFilterList& _rAllFilters, const FilterClassList& _rGlobalClasses, FilterGroupEntryReferrer& _rGlobalClassesRef ) + { + // we need an extra group in our "all filters" container + _rAllFilters.push_front( FilterGroup() ); + FilterGroup& rGlobalFilters = _rAllFilters.front(); + // it's important to work on the reference: we want to access the members of this filter group + // by an iterator (FilterGroup::const_iterator) + // the referrer for the global classes + + // initialize the group + ::std::for_each( + _rGlobalClasses.begin(), + _rGlobalClasses.end(), + FillClassGroup( rGlobalFilters, _rGlobalClassesRef ) + ); + // now we have: + // in rGlobalFilters: a list of FilterDescriptor's, where each's descriptor's display name is set to the name of a class + // in aGlobalClassesRef: a mapping from logical filter names to positions within rGlobalFilters + // this way, if we encounter an arbitrary filter, we can easily (and efficient) check if it belongs to a global class + // and modify the descriptor for this class accordingly + } + + + typedef ::std::vector< ::std::pair< FilterGroupEntryReferrer::mapped_type, FilterGroup::iterator > > + MapGroupEntry2GroupEntry; + // this is not really a map - it's just called this way because it is used as a map + + namespace { + + struct FindGroupEntry + { + FilterGroupEntryReferrer::mapped_type aLookingFor; + explicit FindGroupEntry( FilterGroupEntryReferrer::mapped_type const & _rLookingFor ) : aLookingFor( _rLookingFor ) { } + + bool operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry ) + { + return _rMapEntry.first == aLookingFor; + } + }; + + struct CopyGroupEntryContent + { + void operator() ( const MapGroupEntry2GroupEntry::value_type& _rMapEntry ) + { + *_rMapEntry.second = *_rMapEntry.first; + } + }; + + + struct CopyNonEmptyFilter + { + FilterGroup& rTarget; + explicit CopyNonEmptyFilter( FilterGroup& _rTarget ) :rTarget( _rTarget ) { } + + void operator() ( const FilterDescriptor& _rFilter ) + { + if ( !_rFilter.Second.isEmpty() ) + rTarget.push_back( _rFilter ); + } + }; + + } + + static void lcl_GroupAndClassify( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rAllFilters ) + { + _rAllFilters.clear(); + + + // read the classification of filters + FilterClassList aGlobalClasses, aLocalClasses; + std::vector<OUString> aGlobalClassNames; + lcl_ReadClassification( aGlobalClasses, aGlobalClassNames, aLocalClasses ); + + + // for the global filter classes + FilterGroupEntryReferrer aGlobalClassesRef; + lcl_InitGlobalClasses( _rAllFilters, aGlobalClasses, aGlobalClassesRef ); + + // insert as much placeholders (FilterGroup's) into _rAllFilter for groups as we have global classes + // (this assumes that both numbers are the same, which, speaking strictly, must not hold - but it does, as we know ...) + sal_Int32 nGlobalClasses = aGlobalClasses.size(); + while ( nGlobalClasses-- ) + _rAllFilters.emplace_back( ); + + + // for the local classes: + // if n filters belong to a local class, they do not appear in their respective group explicitly, instead + // and entry for the class is added to the group and the extensions of the filters are collected under + // this entry + FilterGroupEntryReferrer aLocalClassesRef; + FilterGroup aCollectedLocals; + ::std::for_each( + aLocalClasses.begin(), + aLocalClasses.end(), + FillClassGroup( aCollectedLocals, aLocalClassesRef ) + ); + // to map from the position within aCollectedLocals to positions within the real groups + // (where they finally belong to) + MapGroupEntry2GroupEntry aLocalFinalPositions; + + + // now add the filters + // the group which we currently work with + GroupedFilterList::iterator aCurrentGroup = _rAllFilters.end(); // no current group + // the filter container of the current group - if this changes between two filters, a new group is reached + OUString aCurrentServiceName; + + OUString sFilterWildcard; + OUString sFilterName; + // loop through all the filters + for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() ) + { + sFilterName = pFilter->GetFilterName(); + sFilterWildcard = pFilter->GetWildcard().getGlob(); + AppendWildcardToDescriptor aExtendWildcard( sFilterWildcard ); + + DBG_ASSERT( !sFilterWildcard.isEmpty(), "sfx2::lcl_GroupAndClassify: invalid wildcard of this filter!" ); + + + // check for a change in the group + OUString aServiceName = pFilter->GetServiceName(); + if ( aServiceName != aCurrentServiceName ) + { // we reached a new group + + // look for the place in _rAllFilters where this ne group belongs - this is determined + // by the order of classes in aGlobalClassNames + GroupedFilterList::iterator aGroupPos = _rAllFilters.begin(); + DBG_ASSERT( aGroupPos != _rAllFilters.end(), + "sfx2::lcl_GroupAndClassify: invalid all-filters array here!" ); + // the loop below will work on invalid objects else ... + ++aGroupPos; + auto aGlobalIter = std::find(aGlobalClassNames.begin(), aGlobalClassNames.end(), aServiceName); + auto nGroupPosShift = std::min( + std::distance(aGlobalClassNames.begin(), aGlobalIter), + std::distance(aGroupPos, _rAllFilters.end())); + std::advance(aGroupPos, nGroupPosShift); + if ( aGroupPos != _rAllFilters.end() ) + // we found a global class name which matches the doc service name -> fill the filters of this + // group in the respective prepared group + aCurrentGroup = aGroupPos; + else + // insert a new entry in our overall-list + aCurrentGroup = _rAllFilters.insert( _rAllFilters.end(), FilterGroup() ); + + // remember the container to properly detect the next group + aCurrentServiceName = aServiceName; + } + + assert(aCurrentGroup != _rAllFilters.end()); //invalid current group! + if (aCurrentGroup == _rAllFilters.end()) + aCurrentGroup = _rAllFilters.begin(); + + + // check if the filter is part of a global group + ::std::pair< FilterGroupEntryReferrer::iterator, FilterGroupEntryReferrer::iterator > + aBelongsTo = aGlobalClassesRef.equal_range( sFilterName ); + // add the filter to the entries for these classes + // (if they exist - if not, the range is empty and the for_each is a no-op) + ::std::for_each( + aBelongsTo.first, + aBelongsTo.second, + aExtendWildcard + ); + + + // add the filter to its group + + // for this, check if the filter is part of a local filter + FilterGroupEntryReferrer::iterator aBelongsToLocal = aLocalClassesRef.find( sFilterName ); + if ( aLocalClassesRef.end() != aBelongsToLocal ) + { + // okay, there is a local class which the filter belongs to + // -> append the wildcard + aExtendWildcard( *aBelongsToLocal ); + + if ( std::none_of( aLocalFinalPositions.begin(), aLocalFinalPositions.end(), FindGroupEntry( aBelongsToLocal->second ) ) ) + { // the position within aCollectedLocals has not been mapped to a final position + // within the "real" group (aCollectedLocals is only temporary) + // -> do this now (as we just encountered the first filter belonging to this local class + // add a new entry which is the "real" group entry + aCurrentGroup->push_back( FilterDescriptor( aBelongsToLocal->second->First, OUString() ) ); + // the position where we inserted the entry + FilterGroup::iterator aInsertPos = aCurrentGroup->end(); + --aInsertPos; + // remember this pos + aLocalFinalPositions.emplace_back( aBelongsToLocal->second, aInsertPos ); + } + } + else + aCurrentGroup->push_back( FilterDescriptor( pFilter->GetUIName(), sFilterWildcard ) ); + } + + // now just complete the infos for the local groups: + // During the above loop, they have been collected in aCollectedLocals, but this is only temporary + // They have to be copied into their final positions (which are stored in aLocalFinalPositions) + ::std::for_each( + aLocalFinalPositions.begin(), + aLocalFinalPositions.end(), + CopyGroupEntryContent() + ); + + // and remove local groups which do not apply - e.g. have no entries due to the limited content of the + // current SfxFilterMatcherIter + + FilterGroup& rGlobalFilters = _rAllFilters.front(); + FilterGroup aNonEmptyGlobalFilters; + ::std::for_each( + rGlobalFilters.begin(), + rGlobalFilters.end(), + CopyNonEmptyFilter( aNonEmptyGlobalFilters ) + ); + rGlobalFilters.swap( aNonEmptyGlobalFilters ); + } + + namespace { + + struct AppendFilter + { + protected: + Reference< XFilterManager > m_xFilterManager; + FileDialogHelper_Impl* m_pFileDlgImpl; + bool m_bAddExtension; + + public: + AppendFilter( const Reference< XFilterManager >& _rxFilterManager, + FileDialogHelper_Impl* _pImpl, bool _bAddExtension ) : + + m_xFilterManager( _rxFilterManager ), + m_pFileDlgImpl ( _pImpl ), + m_bAddExtension ( _bAddExtension ) + + { + DBG_ASSERT( m_xFilterManager.is(), "AppendFilter::AppendFilter: invalid filter manager!" ); + DBG_ASSERT( m_pFileDlgImpl, "AppendFilter::AppendFilter: invalid filedlg impl!" ); + } + + // operate on a single filter + void operator() ( const FilterDescriptor& _rFilterEntry ) + { + OUString sDisplayText = m_bAddExtension + ? addExtension( _rFilterEntry.First, _rFilterEntry.Second, true, *m_pFileDlgImpl ) + : _rFilterEntry.First; + m_xFilterManager->appendFilter( sDisplayText, _rFilterEntry.Second ); + } + }; + + } + +// = handling for the "all files" entry + + + static bool lcl_hasAllFilesFilter( TSortedFilterList& _rFilterMatcher, OUString& /* [out] */ _rAllFilterName ) + { + bool bHasAll = false; + _rAllFilterName = SfxResId( STR_SFX_FILTERNAME_ALL ); + + + // check if there's already a filter <ALL> + for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter && !bHasAll; pFilter = _rFilterMatcher.Next() ) + { + if ( pFilter->GetUIName() == _rAllFilterName ) + bHasAll = true; + } + return bHasAll; + } + + + static void lcl_EnsureAllFilesEntry( TSortedFilterList& _rFilterMatcher, GroupedFilterList& _rFilters ) + { + + OUString sAllFilterName; + if ( !lcl_hasAllFilesFilter( _rFilterMatcher, sAllFilterName ) ) + { + // get the first group of filters (by definition, this group contains the global classes) + DBG_ASSERT( !_rFilters.empty(), "lcl_EnsureAllFilesEntry: invalid filter list!" ); + if ( !_rFilters.empty() ) + { + FilterGroup& rGlobalClasses = *_rFilters.begin(); + rGlobalClasses.push_front( FilterDescriptor( sAllFilterName, FILEDIALOG_FILTER_ALL ) ); + } + } + } + + +// = filling an XFilterManager + + namespace { + + struct AppendFilterGroup + { + protected: + Reference< XFilterManager > m_xFilterManager; + Reference< XFilterGroupManager > m_xFilterGroupManager; + FileDialogHelper_Impl* m_pFileDlgImpl; + + public: + AppendFilterGroup( const Reference< XFilterManager >& _rxFilterManager, FileDialogHelper_Impl* _pImpl ) + :m_xFilterManager ( _rxFilterManager ) + ,m_xFilterGroupManager ( _rxFilterManager, UNO_QUERY ) + ,m_pFileDlgImpl ( _pImpl ) + { + DBG_ASSERT( m_xFilterManager.is(), "AppendFilterGroup::AppendFilterGroup: invalid filter manager!" ); + DBG_ASSERT( m_pFileDlgImpl, "AppendFilterGroup::AppendFilterGroup: invalid filedlg impl!" ); + } + + void appendGroup( const FilterGroup& _rGroup, bool _bAddExtension ) + { + try + { + if ( m_xFilterGroupManager.is() ) + { // the file dialog implementation supports visual grouping of filters + // create a representation of the group which is understandable by the XFilterGroupManager + if ( !_rGroup.empty() ) + { + Sequence< StringPair > aFilters( comphelper::containerToSequence(_rGroup) ); + if ( _bAddExtension ) + { + for ( StringPair & filter : asNonConstRange(aFilters) ) + filter.First = addExtension( filter.First, filter.Second, true, *m_pFileDlgImpl ); + } + m_xFilterGroupManager->appendFilterGroup( OUString(), aFilters ); + } + } + else + { + ::std::for_each( + _rGroup.begin(), + _rGroup.end(), + AppendFilter( m_xFilterManager, m_pFileDlgImpl, _bAddExtension ) ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.dialog"); + } + } + + // operate on a single filter group + void operator() ( const FilterGroup& _rGroup ) + { + appendGroup( _rGroup, true ); + } + }; + + } + + TSortedFilterList::TSortedFilterList(const css::uno::Reference< css::container::XEnumeration >& xFilterList) + : m_nIterator(0) + { + if (!xFilterList.is()) + return; + + m_lFilters.clear(); + while(xFilterList->hasMoreElements()) + { + ::comphelper::SequenceAsHashMap lFilterProps (xFilterList->nextElement()); + OUString sFilterName = lFilterProps.getUnpackedValueOrDefault( + "Name", + OUString()); + if (!sFilterName.isEmpty()) + m_lFilters.push_back(sFilterName); + } + } + + + std::shared_ptr<const SfxFilter> TSortedFilterList::First() + { + m_nIterator = 0; + return impl_getFilter(m_nIterator); + } + + + std::shared_ptr<const SfxFilter> TSortedFilterList::Next() + { + ++m_nIterator; + return impl_getFilter(m_nIterator); + } + + + std::shared_ptr<const SfxFilter> TSortedFilterList::impl_getFilter(sal_Int32 nIndex) + { + if (nIndex<0 || o3tl::make_unsigned(nIndex)>=m_lFilters.size()) + return nullptr; + const OUString& sFilterName = m_lFilters[nIndex]; + if (sFilterName.isEmpty()) + return nullptr; + return SfxFilter::GetFilterByName(sFilterName); + } + + + void appendFiltersForSave( TSortedFilterList& _rFilterMatcher, + const Reference< XFilterManager >& _rxFilterManager, + OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl, + std::u16string_view _rFactory ) + { + DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForSave: invalid manager!" ); + if ( !_rxFilterManager.is() ) + return; + + OUString sUIName; + OUString sExtension; + + // retrieve the default filter for this application module. + // It must be set as first of the generated filter list. + std::shared_ptr<const SfxFilter> pDefaultFilter = SfxFilterContainer::GetDefaultFilter_Impl(_rFactory); + // Only use one extension (#i32434#) + // (and always the first if there are more than one) + sExtension = pDefaultFilter->GetWildcard().getGlob().getToken(0, ';'); + sUIName = addExtension( pDefaultFilter->GetUIName(), sExtension, false, _rFileDlgImpl ); + try + { + _rxFilterManager->appendFilter( sUIName, sExtension ); + if ( _rFirstNonEmpty.isEmpty() ) + _rFirstNonEmpty = sUIName; + } + catch( const IllegalArgumentException& ) + { + SAL_WARN( "sfx.dialog", "Could not append DefaultFilter" << sUIName ); + } + + for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() ) + { + if (pFilter->GetName() == pDefaultFilter->GetName()) + continue; + + // Only use one extension (#i32434#) + // (and always the first if there are more than one) + sExtension = pFilter->GetWildcard().getGlob().getToken(0, ';'); + sUIName = addExtension( pFilter->GetUIName(), sExtension, false, _rFileDlgImpl ); + try + { + _rxFilterManager->appendFilter( sUIName, sExtension ); + if ( _rFirstNonEmpty.isEmpty() ) + _rFirstNonEmpty = sUIName; + } + catch( const IllegalArgumentException& ) + { + SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName ); + } + } + } + + namespace { + + struct ExportFilter + { + ExportFilter( const OUString& _aUIName, const OUString& _aWildcard ) : + aUIName( _aUIName ), aWildcard( _aWildcard ) {} + + OUString aUIName; + OUString aWildcard; + }; + + } + + void appendExportFilters( TSortedFilterList& _rFilterMatcher, + const Reference< XFilterManager >& _rxFilterManager, + OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl ) + { + DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendExportFilters: invalid manager!" ); + if ( !_rxFilterManager.is() ) + return; + + sal_Int32 nHTMLIndex = -1; + sal_Int32 nXHTMLIndex = -1; + sal_Int32 nPDFIndex = -1; + OUString sUIName; + OUString sExtensions; + std::vector< ExportFilter > aImportantFilterGroup; + std::vector< ExportFilter > aFilterGroup; + Reference< XFilterGroupManager > xFilterGroupManager( _rxFilterManager, UNO_QUERY ); + OUString sTypeName; + + for ( std::shared_ptr<const SfxFilter> pFilter = _rFilterMatcher.First(); pFilter; pFilter = _rFilterMatcher.Next() ) + { + sTypeName = pFilter->GetTypeName(); + sUIName = pFilter->GetUIName(); + sExtensions = pFilter->GetWildcard().getGlob(); + ExportFilter aExportFilter( sUIName, sExtensions ); + + if ( nHTMLIndex == -1 && + ( sTypeName == "generic_HTML" || sTypeName == "graphic_HTML" ) ) + { + aImportantFilterGroup.insert( aImportantFilterGroup.begin(), aExportFilter ); + nHTMLIndex = 0; + } + else if ( nXHTMLIndex == -1 && sTypeName == "XHTML_File" ) + { + std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin(); + if ( nHTMLIndex == -1 ) + aImportantFilterGroup.insert( aIter, aExportFilter ); + else + aImportantFilterGroup.insert( ++aIter, aExportFilter ); + nXHTMLIndex = 0; + } + else if ( nPDFIndex == -1 && sTypeName == "pdf_Portable_Document_Format" ) + { + std::vector< ExportFilter >::iterator aIter = aImportantFilterGroup.begin(); + if ( nHTMLIndex != -1 ) + ++aIter; + if ( nXHTMLIndex != -1 ) + ++aIter; + aImportantFilterGroup.insert( aIter, aExportFilter ); + nPDFIndex = 0; + } + else + aFilterGroup.push_back( aExportFilter ); + } + + if ( xFilterGroupManager.is() ) + { + // Add both html/pdf filter as a filter group to get a separator between both groups + if ( !aImportantFilterGroup.empty() ) + { + Sequence< StringPair > aFilters( aImportantFilterGroup.size() ); + auto pFilters = aFilters.getArray(); + for ( sal_Int32 i = 0; i < static_cast<sal_Int32>(aImportantFilterGroup.size()); i++ ) + { + pFilters[i].First = addExtension( aImportantFilterGroup[i].aUIName, + aImportantFilterGroup[i].aWildcard, + false, _rFileDlgImpl ); + pFilters[i].Second = aImportantFilterGroup[i].aWildcard; + } + + try + { + xFilterGroupManager->appendFilterGroup( OUString(), aFilters ); + } + catch( const IllegalArgumentException& ) + { + } + } + + if ( !aFilterGroup.empty() ) + { + Sequence< StringPair > aFilters( aFilterGroup.size() ); + auto pFilters = aFilters.getArray(); + for ( sal_Int32 i = 0; i < static_cast<sal_Int32>(aFilterGroup.size()); i++ ) + { + pFilters[i].First = addExtension( aFilterGroup[i].aUIName, + aFilterGroup[i].aWildcard, + false, _rFileDlgImpl ); + pFilters[i].Second = aFilterGroup[i].aWildcard; + } + + try + { + xFilterGroupManager->appendFilterGroup( OUString(), aFilters ); + } + catch( const IllegalArgumentException& ) + { + } + } + } + else + { + // Fallback solution just add both filter groups as single filters + sal_Int32 n; + + for ( n = 0; n < static_cast<sal_Int32>(aImportantFilterGroup.size()); n++ ) + { + try + { + OUString aUIName = addExtension( aImportantFilterGroup[n].aUIName, + aImportantFilterGroup[n].aWildcard, + false, _rFileDlgImpl ); + _rxFilterManager->appendFilter( aUIName, aImportantFilterGroup[n].aWildcard ); + if ( _rFirstNonEmpty.isEmpty() ) + _rFirstNonEmpty = sUIName; + + } + catch( const IllegalArgumentException& ) + { + SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName ); + } + } + + for ( n = 0; n < static_cast<sal_Int32>(aFilterGroup.size()); n++ ) + { + try + { + OUString aUIName = addExtension( aFilterGroup[n].aUIName, + aFilterGroup[n].aWildcard, + false, _rFileDlgImpl ); + _rxFilterManager->appendFilter( aUIName, aFilterGroup[n].aWildcard ); + if ( _rFirstNonEmpty.isEmpty() ) + _rFirstNonEmpty = sUIName; + + } + catch( const IllegalArgumentException& ) + { + SAL_WARN( "sfx.dialog", "Could not append Filter" << sUIName ); + } + } + } + } + + + void appendFiltersForOpen( TSortedFilterList& _rFilterMatcher, + const Reference< XFilterManager >& _rxFilterManager, + OUString& _rFirstNonEmpty, FileDialogHelper_Impl& _rFileDlgImpl ) + { + DBG_ASSERT( _rxFilterManager.is(), "sfx2::appendFiltersForOpen: invalid manager!" ); + if ( !_rxFilterManager.is() ) + return; + + + // group and classify the filters + GroupedFilterList aAllFilters; + lcl_GroupAndClassify( _rFilterMatcher, aAllFilters ); + + + // ensure that we have the one "all files" entry + lcl_EnsureAllFilesEntry( _rFilterMatcher, aAllFilters ); + + + // the first non-empty string - which we assume is the first overall entry + if ( !aAllFilters.empty() ) + { + const FilterGroup& rFirstGroup = *aAllFilters.begin(); // should be the global classes + if ( !rFirstGroup.empty() ) + _rFirstNonEmpty = rFirstGroup.begin()->First; + // append first group, without extension + AppendFilterGroup aGroup( _rxFilterManager, &_rFileDlgImpl ); + aGroup.appendGroup( rFirstGroup, false ); + } + + + // append the filters to the manager + if ( !aAllFilters.empty() ) + { + ::std::list< FilterGroup >::iterator pIter = aAllFilters.begin(); + ++pIter; + ::std::for_each( + pIter, // first filter group was handled separately, see above + aAllFilters.end(), + AppendFilterGroup( _rxFilterManager, &_rFileDlgImpl ) ); + } + } + + OUString addExtension( const OUString& _rDisplayText, + const OUString& _rExtension, + bool _bForOpen, FileDialogHelper_Impl& _rFileDlgImpl ) + { + OUString sRet = _rDisplayText; + + if ( sRet.indexOf( "(*.*)" ) == -1 ) + { + OUString sExt = _rExtension; + if ( !_bForOpen ) + { + // show '*' in extensions only when opening a document + sExt = sExt.replaceAll("*", ""); + } + sRet += " (" + sExt + ")"; + } + _rFileDlgImpl.addFilterPair( _rDisplayText, sRet ); + return sRet; + } + + +} // namespace sfx2 + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/filtergrouping.hxx b/sfx2/source/dialog/filtergrouping.hxx new file mode 100644 index 000000000..1d8a44473 --- /dev/null +++ b/sfx2/source/dialog/filtergrouping.hxx @@ -0,0 +1,96 @@ +/* -*- 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_DIALOG_FILTERGROUPING_HXX +#define INCLUDED_SFX2_SOURCE_DIALOG_FILTERGROUPING_HXX + +#include <com/sun/star/ui/dialogs/XFilterManager.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include "filedlgimpl.hxx" + +#include <memory> + +namespace sfx2 +{ + + + class TSortedFilterList + { + private: + ::std::vector< OUString > m_lFilters; + sal_Int32 m_nIterator; + + public: + explicit TSortedFilterList(const css::uno::Reference< css::container::XEnumeration >& xFilterList); + std::shared_ptr<const SfxFilter> First(); + std::shared_ptr<const SfxFilter> Next(); + + private: + std::shared_ptr<const SfxFilter> impl_getFilter(sal_Int32 nIndex); + }; + + + /** adds the given filters to the filter manager. + <p>To be used when saving generic files.</p> + */ + void appendFiltersForSave( + TSortedFilterList& _rFilterMatcher, + const css::uno::Reference< css::ui::dialogs::XFilterManager >& _rFilterManager, + OUString& /* [out] */ _rFirstNonEmpty, + FileDialogHelper_Impl& _rFileDlgImpl, + std::u16string_view _rFactory + ); + + void appendExportFilters( + TSortedFilterList& _rFilterMatcher, + const css::uno::Reference< css::ui::dialogs::XFilterManager >& _rFilterManager, + OUString& /* [out] */ _rFirstNonEmpty, + FileDialogHelper_Impl& _rFileDlgImpl + ); + + + /** adds the given filters to the filter manager. + <p>To be used when opening generic files.</p> + */ + void appendFiltersForOpen( + TSortedFilterList& _rFilterMatcher, + const css::uno::Reference< css::ui::dialogs::XFilterManager >& _rFilterManager, + OUString& /* [out] */ _rFirstNonEmpty, + FileDialogHelper_Impl& _rFileDlgImpl + ); + + + /** adds the given extension to the display text. + <p>To be used when opening or save generic files.</p> + */ + OUString addExtension( + const OUString& _rDisplayText, + const OUString& _rExtension, + bool _bForOpen, + FileDialogHelper_Impl& _rFileDlgImpl + ); + + +} // namespace sfx2 + + +#endif // INCLUDED_SFX2_SOURCE_DIALOG_FILTERGROUPING_HXX + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/infobar.cxx b/sfx2/source/dialog/infobar.cxx new file mode 100644 index 000000000..eade717ea --- /dev/null +++ b/sfx2/source/dialog/infobar.cxx @@ -0,0 +1,524 @@ +/* -*- 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 <basegfx/polygon/b2dpolygon.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <memory> +#include <officecfg/Office/UI/Infobar.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <vcl/image.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weldutils.hxx> +#include <bitmaps.hlst> + +using namespace drawinglayer::geometry; +using namespace drawinglayer::processor2d; +using namespace drawinglayer::primitive2d; +using namespace drawinglayer::attribute; +using namespace basegfx; +using namespace css::frame; + +namespace +{ +void GetInfoBarColors(InfobarType ibType, BColor& rBackgroundColor, BColor& rForegroundColor, + BColor& rMessageColor) +{ + rMessageColor = basegfx::BColor(0.0, 0.0, 0.0); + + switch (ibType) + { + case InfobarType::INFO: // blue; #004785/0,71,133; #BDE5F8/189,229,248 + rBackgroundColor = basegfx::BColor(0.741, 0.898, 0.973); + rForegroundColor = basegfx::BColor(0.0, 0.278, 0.522); + break; + case InfobarType::SUCCESS: // green; #32550C/50,85,12; #DFF2BF/223,242,191 + rBackgroundColor = basegfx::BColor(0.874, 0.949, 0.749); + rForegroundColor = basegfx::BColor(0.196, 0.333, 0.047); + break; + case InfobarType::WARNING: // orange; #704300/112,67,0; #FEEFB3/254,239,179 + rBackgroundColor = basegfx::BColor(0.996, 0.937, 0.702); + rForegroundColor = basegfx::BColor(0.439, 0.263, 0.0); + break; + case InfobarType::DANGER: // red; #7A0006/122,0,6; #FFBABA/255,186,186 + rBackgroundColor = basegfx::BColor(1.0, 0.729, 0.729); + rForegroundColor = basegfx::BColor(0.478, 0.0, 0.024); + break; + } + + //remove this? + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + if (rSettings.GetHighContrastMode()) + { + rBackgroundColor = rSettings.GetLightColor().getBColor(); + rForegroundColor = rSettings.GetDialogTextColor().getBColor(); + } +} +OUString GetInfoBarIconName(InfobarType ibType) +{ + OUString aRet; + + switch (ibType) + { + case InfobarType::INFO: + aRet = "vcl/res/infobox.png"; + break; + case InfobarType::SUCCESS: + aRet = "vcl/res/successbox.png"; + break; + case InfobarType::WARNING: + aRet = "vcl/res/warningbox.png"; + break; + case InfobarType::DANGER: + aRet = "vcl/res/errorbox.png"; + break; + } + + return aRet; +} + +} // anonymous namespace + +void SfxInfoBarWindow::SetCloseButtonImage() +{ + Size aSize = Image(StockImage::Yes, CLOSEDOC).GetSizePixel(); + aSize = Size(aSize.Width() * 1.5, aSize.Height() * 1.5); + + ScopedVclPtr<VirtualDevice> xDevice(m_xCloseBtn->create_virtual_device()); + xDevice->SetOutputSizePixel(aSize); + + Point aBtnPos(0, 0); + + const ViewInformation2D aNewViewInfos; + const std::unique_ptr<BaseProcessor2D> pProcessor( + createBaseProcessor2DFromOutputDevice(*xDevice, aNewViewInfos)); + + const ::tools::Rectangle aRect(aBtnPos, xDevice->PixelToLogic(aSize)); + + drawinglayer::primitive2d::Primitive2DContainer aSeq(2); + + // background + B2DPolygon aPolygon; + aPolygon.append(B2DPoint(aRect.Left(), aRect.Top())); + aPolygon.append(B2DPoint(aRect.Right(), aRect.Top())); + aPolygon.append(B2DPoint(aRect.Right(), aRect.Bottom())); + aPolygon.append(B2DPoint(aRect.Left(), aRect.Bottom())); + aPolygon.setClosed(true); + + aSeq[0] = new PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), m_aBackgroundColor); + + LineAttribute aLineAttribute(m_aForegroundColor, 2.0); + + // Cross + B2DPolyPolygon aCross; + + B2DPolygon aLine1; + aLine1.append(B2DPoint(aRect.Left(), aRect.Top())); + aLine1.append(B2DPoint(aRect.Right(), aRect.Bottom())); + aCross.append(aLine1); + + B2DPolygon aLine2; + aLine2.append(B2DPoint(aRect.Right(), aRect.Top())); + aLine2.append(B2DPoint(aRect.Left(), aRect.Bottom())); + aCross.append(aLine2); + + aSeq[1] = new PolyPolygonStrokePrimitive2D(aCross, aLineAttribute, StrokeAttribute()); + + pProcessor->process(aSeq); + + m_xCloseBtn->set_item_image("close", xDevice); +} + +class ExtraButton +{ +private: + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<weld::Button> m_xButton; + /** StatusListener. Updates the button as the slot state changes */ + rtl::Reference<weld::WidgetStatusListener> m_xStatusListener; + OUString m_aCommand; + + DECL_LINK(CommandHdl, weld::Button&, void); + +public: + ExtraButton(weld::Container* pContainer, const OUString* pCommand) + : m_xBuilder(Application::CreateBuilder(pContainer, "sfx/ui/extrabutton.ui")) + , m_xContainer(m_xBuilder->weld_container("ExtraButton")) + , m_xButton(m_xBuilder->weld_button("button")) + { + if (pCommand) + { + m_aCommand = *pCommand; + m_xButton->connect_clicked(LINK(this, ExtraButton, CommandHdl)); + m_xStatusListener.set(new weld::WidgetStatusListener(m_xButton.get(), m_aCommand)); + m_xStatusListener->startListening(); + } + } + + ~ExtraButton() + { + if (m_xStatusListener.is()) + m_xStatusListener->dispose(); + } + + weld::Button& get_widget() { return *m_xButton; } +}; + +IMPL_LINK_NOARG(ExtraButton, CommandHdl, weld::Button&, void) +{ + comphelper::dispatchCommand(m_aCommand, css::uno::Sequence<css::beans::PropertyValue>()); +} + +SfxInfoBarWindow::SfxInfoBarWindow(vcl::Window* pParent, const OUString& sId, + const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, InfobarType ibType, + bool bShowCloseButton) + : InterimItemWindow(pParent, "sfx/ui/infobar.ui", "InfoBar") + , m_sId(sId) + , m_eType(ibType) + , m_bLayingOut(false) + , m_xImage(m_xBuilder->weld_image("image")) + , m_xPrimaryMessage(m_xBuilder->weld_label("primary")) + , m_xSecondaryMessage(m_xBuilder->weld_text_view("secondary")) + , m_xButtonBox(m_xBuilder->weld_container("buttonbox")) + , m_xCloseBtn(m_xBuilder->weld_toolbar("closebar")) +{ + SetStyle(GetStyle() | WB_DIALOGCONTROL); + + InitControlBase(m_xCloseBtn.get()); + + m_xImage->set_from_icon_name(GetInfoBarIconName(ibType)); + m_xSecondaryMessage->set_margin_top(m_xImage->get_preferred_size().Height() / 4); + + if (!sPrimaryMessage.isEmpty()) + { + m_xPrimaryMessage->set_label(sPrimaryMessage); + m_xPrimaryMessage->show(); + } + + m_xSecondaryMessage->set_text(sSecondaryMessage); + m_aOrigMessageSize = m_xSecondaryMessage->get_preferred_size(); + m_aMessageSize = m_aOrigMessageSize; + m_xSecondaryMessage->connect_size_allocate(LINK(this, SfxInfoBarWindow, SizeAllocHdl)); + + if (bShowCloseButton) + { + m_xCloseBtn->connect_clicked(LINK(this, SfxInfoBarWindow, CloseHandler)); + m_xCloseBtn->show(); + } + + EnableChildTransparentMode(); + + SetForeAndBackgroundColors(m_eType); + + auto nWidth = pParent->GetSizePixel().getWidth(); + auto nHeight = get_preferred_size().Height(); + SetSizePixel(Size(nWidth, nHeight + 2)); + + Resize(); +} + +IMPL_LINK(SfxInfoBarWindow, SizeAllocHdl, const Size&, rSize, void) +{ + if (m_aMessageSize != rSize) + { + m_aMessageSize = rSize; + static_cast<SfxInfoBarContainerWindow*>(GetParent())->TriggerUpdateLayout(); + } +} + +Size SfxInfoBarWindow::DoLayout() +{ + Size aGivenSize(GetSizePixel()); + + // disconnect SizeAllocHdl because we don't care about the size change + // during layout + m_xSecondaryMessage->connect_size_allocate(Link<const Size&, void>()); + + // blow away size cache in case m_aMessageSize.Width() is already the width request + // and we would get the cached preferred size instead of the recalc we want to force + m_xSecondaryMessage->set_size_request(-1, -1); + // make the width we were detected as set to by SizeAllocHdl as our desired width + m_xSecondaryMessage->set_size_request(m_aMessageSize.Width(), -1); + // get our preferred size with that message width + Size aSizeForWidth(aGivenSize.Width(), m_xContainer->get_preferred_size().Height()); + // restore the message preferred size so we can freely resize, and get a new + // m_aMessageSize and repeat the process if we do + m_xSecondaryMessage->set_size_request(m_aOrigMessageSize.Width(), -1); + + // connect SizeAllocHdl so changes outside of this layout will trigger a new layout + m_xSecondaryMessage->connect_size_allocate(LINK(this, SfxInfoBarWindow, SizeAllocHdl)); + + return aSizeForWidth; +} + +void SfxInfoBarWindow::Layout() +{ + if (m_bLayingOut) + return; + m_bLayingOut = true; + + InterimItemWindow::Layout(); + + m_bLayingOut = false; +} + +weld::Button& SfxInfoBarWindow::addButton(const OUString* pCommand) +{ + m_aActionBtns.emplace_back(std::make_unique<ExtraButton>(m_xButtonBox.get(), pCommand)); + + return m_aActionBtns.back()->get_widget(); +} + +SfxInfoBarWindow::~SfxInfoBarWindow() { disposeOnce(); } + +void SfxInfoBarWindow::SetForeAndBackgroundColors(InfobarType eType) +{ + basegfx::BColor aMessageColor; + GetInfoBarColors(eType, m_aBackgroundColor, m_aForegroundColor, aMessageColor); + + m_xPrimaryMessage->set_font_color(Color(aMessageColor)); + m_xSecondaryMessage->set_font_color(Color(aMessageColor)); + + Color aBackgroundColor(m_aBackgroundColor); + m_xPrimaryMessage->set_background(aBackgroundColor); + m_xSecondaryMessage->set_background(aBackgroundColor); + m_xContainer->set_background(aBackgroundColor); + if (m_xCloseBtn->get_visible()) + { + m_xCloseBtn->set_background(aBackgroundColor); + SetCloseButtonImage(); + } +} + +void SfxInfoBarWindow::dispose() +{ + for (auto& rxBtn : m_aActionBtns) + rxBtn.reset(); + + m_xImage.reset(); + m_xPrimaryMessage.reset(); + m_xSecondaryMessage.reset(); + m_xButtonBox.reset(); + m_xCloseBtn.reset(); + m_aActionBtns.clear(); + InterimItemWindow::dispose(); +} + +void SfxInfoBarWindow::Update(const OUString& sPrimaryMessage, const OUString& sSecondaryMessage, + InfobarType eType) +{ + if (m_eType != eType) + { + m_eType = eType; + SetForeAndBackgroundColors(m_eType); + m_xImage->set_from_icon_name(GetInfoBarIconName(eType)); + } + + m_xPrimaryMessage->set_label(sPrimaryMessage); + m_xSecondaryMessage->set_text(sSecondaryMessage); + Resize(); + Invalidate(); +} + +IMPL_LINK_NOARG(SfxInfoBarWindow, CloseHandler, const OString&, void) +{ + static_cast<SfxInfoBarContainerWindow*>(GetParent())->removeInfoBar(this); +} + +SfxInfoBarContainerWindow::SfxInfoBarContainerWindow(SfxInfoBarContainerChild* pChildWin) + : Window(pChildWin->GetParent(), WB_DIALOGCONTROL) + , m_pChildWin(pChildWin) + , m_aLayoutIdle("SfxInfoBarContainerWindow m_aLayoutIdle") + , m_bResizing(false) +{ + m_aLayoutIdle.SetPriority(TaskPriority::HIGHEST); + m_aLayoutIdle.SetInvokeHandler(LINK(this, SfxInfoBarContainerWindow, DoUpdateLayout)); +} + +IMPL_LINK_NOARG(SfxInfoBarContainerWindow, DoUpdateLayout, Timer*, void) { m_pChildWin->Update(); } + +SfxInfoBarContainerWindow::~SfxInfoBarContainerWindow() { disposeOnce(); } + +void SfxInfoBarContainerWindow::dispose() +{ + for (auto& infoBar : m_pInfoBars) + infoBar.disposeAndClear(); + m_pInfoBars.clear(); + Window::dispose(); +} + +VclPtr<SfxInfoBarWindow> SfxInfoBarContainerWindow::appendInfoBar(const OUString& sId, + const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, + InfobarType ibType, + bool bShowCloseButton) +{ + if (!isInfobarEnabled(sId)) + return nullptr; + + auto pInfoBar = VclPtr<SfxInfoBarWindow>::Create(this, sId, sPrimaryMessage, sSecondaryMessage, + ibType, bShowCloseButton); + + basegfx::BColor aBackgroundColor; + basegfx::BColor aForegroundColor; + basegfx::BColor aMessageColor; + GetInfoBarColors(ibType, aBackgroundColor, aForegroundColor, aMessageColor); + pInfoBar->m_aBackgroundColor = aBackgroundColor; + pInfoBar->m_aForegroundColor = aForegroundColor; + m_pInfoBars.push_back(pInfoBar); + + Resize(); + return pInfoBar; +} + +VclPtr<SfxInfoBarWindow> SfxInfoBarContainerWindow::getInfoBar(std::u16string_view sId) +{ + for (auto const& infoBar : m_pInfoBars) + { + if (infoBar->getId() == sId) + return infoBar; + } + return nullptr; +} + +bool SfxInfoBarContainerWindow::hasInfoBarWithID(std::u16string_view sId) +{ + return (getInfoBar(sId) != nullptr); +} + +void SfxInfoBarContainerWindow::removeInfoBar(VclPtr<SfxInfoBarWindow> const& pInfoBar) +{ + // Remove + auto it = std::find(m_pInfoBars.begin(), m_pInfoBars.end(), pInfoBar); + if (it != m_pInfoBars.end()) + { + it->disposeAndClear(); + m_pInfoBars.erase(it); + } + + m_pChildWin->Update(); +} + +bool SfxInfoBarContainerWindow::isInfobarEnabled(std::u16string_view sId) +{ + if (sId == u"readonly") + return officecfg::Office::UI::Infobar::Enabled::Readonly::get(); + if (sId == u"signature") + return officecfg::Office::UI::Infobar::Enabled::Signature::get(); + if (sId == u"donate") + return officecfg::Office::UI::Infobar::Enabled::Donate::get(); + if (sId == u"getinvolved") + return officecfg::Office::UI::Infobar::Enabled::GetInvolved::get(); + if (sId == u"hyphenationmissing") + return officecfg::Office::UI::Infobar::Enabled::HyphenationMissing::get(); + if (sId == u"whatsnew") + return officecfg::Office::UI::Infobar::Enabled::WhatsNew::get(); + if (sId == u"hiddentrackchanges") + return officecfg::Office::UI::Infobar::Enabled::HiddenTrackChanges::get(); + + return true; +} + +// This triggers the SfxFrame to re-layout its childwindows +void SfxInfoBarContainerWindow::TriggerUpdateLayout() { m_aLayoutIdle.Start(); } + +void SfxInfoBarContainerWindow::Resize() +{ + if (m_bResizing) + return; + m_bResizing = true; + const Size& rOrigSize = GetSizePixel(); + auto nOrigWidth = rOrigSize.getWidth(); + auto nOrigHeight = rOrigSize.getHeight(); + + tools::Long nHeight = 0; + + for (auto& rxInfoBar : m_pInfoBars) + { + Size aOrigSize = rxInfoBar->GetSizePixel(); + Size aSize(nOrigWidth, aOrigSize.Height()); + + Point aPos(0, nHeight); + // stage 1: provisionally size the infobar, + rxInfoBar->SetPosSizePixel(aPos, aSize); + + // stage 2: perhaps allow height to stretch to fit + // the stage 1 width + aSize = rxInfoBar->DoLayout(); + rxInfoBar->SetPosSizePixel(aPos, aSize); + rxInfoBar->Show(); + + // Stretch to fit the infobar(s) + nHeight += aSize.getHeight(); + } + + if (nOrigHeight != nHeight) + { + SetSizePixel(Size(nOrigWidth, nHeight)); + TriggerUpdateLayout(); + } + + m_bResizing = false; +} + +SFX_IMPL_POS_CHILDWINDOW_WITHID(SfxInfoBarContainerChild, SID_INFOBAR, SFX_OBJECTBAR_OBJECT); + +SfxInfoBarContainerChild::SfxInfoBarContainerChild(vcl::Window* _pParent, sal_uInt16 nId, + SfxBindings* pBindings, SfxChildWinInfo*) + : SfxChildWindow(_pParent, nId) + , m_pBindings(pBindings) +{ + SetWindow(VclPtr<SfxInfoBarContainerWindow>::Create(this)); + GetWindow()->SetPosSizePixel(Point(0, 0), Size(_pParent->GetSizePixel().getWidth(), 0)); + GetWindow()->Show(); + + SetAlignment(SfxChildAlignment::LOWESTTOP); +} + +SfxInfoBarContainerChild::~SfxInfoBarContainerChild() {} + +SfxChildWinInfo SfxInfoBarContainerChild::GetInfo() const +{ + SfxChildWinInfo aInfo = SfxChildWindow::GetInfo(); + return aInfo; +} + +void SfxInfoBarContainerChild::Update() +{ + // Layout to current width, this may change the height + if (vcl::Window* pChild = GetWindow()) + { + Size aSize(pChild->GetSizePixel()); + pChild->Resize(); + if (aSize == pChild->GetSizePixel()) + return; + } + + // Refresh the frame to take the infobars container height change into account + const sal_uInt16 nId = GetChildWindowId(); + SfxViewFrame* pVFrame = m_pBindings->GetDispatcher()->GetFrame(); + pVFrame->ShowChildWindow(nId); + + // Give the focus to the document view + pVFrame->GetWindow().GrabFocusToDocument(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/inputdlg.cxx b/sfx2/source/dialog/inputdlg.cxx new file mode 100644 index 000000000..243f2fa09 --- /dev/null +++ b/sfx2/source/dialog/inputdlg.cxx @@ -0,0 +1,67 @@ +/* -*- 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/inputdlg.hxx> + +InputDialog::InputDialog(weld::Widget* pParent, const OUString& rLabelText) + : GenericDialogController(pParent, "sfx/ui/inputdialog.ui", "InputDialog") + , m_xEntry(m_xBuilder->weld_entry("entry")) + , m_xLabel(m_xBuilder->weld_label("label")) + , m_xHelp(m_xBuilder->weld_button("help")) + , m_xOk(m_xBuilder->weld_button("ok")) +{ + m_xLabel->set_label(rLabelText); +} + +void InputDialog::HideHelpBtn() { m_xHelp->hide(); } + +OUString InputDialog::GetEntryText() const { return m_xEntry->get_text(); } + +void InputDialog::SetEntryText(const OUString& rStr) +{ + m_xEntry->set_text(rStr); + m_xEntry->set_position(-1); +} + +void InputDialog::SetEntryMessageType(weld::EntryMessageType aType) +{ + m_xEntry->set_message_type(aType); + if (aType == weld::EntryMessageType::Error) + { + m_xEntry->select_region(0, -1); + m_xEntry->grab_focus(); + m_xOk->set_sensitive(false); + } + else + { + m_xOk->set_sensitive(true); + SetTooltip(""); + } +} + +void InputDialog::SetTooltip(const OUString& rStr) +{ + m_xEntry->set_tooltip_text(rStr); + m_xOk->set_tooltip_text(rStr); +} + +void InputDialog::setCheckEntry(std::function<bool(OUString)> aFunc) +{ + mCheckEntry = aFunc; + m_xEntry->connect_changed(LINK(this, InputDialog, EntryChangedHdl)); +} + +IMPL_LINK_NOARG(InputDialog, EntryChangedHdl, weld::Entry&, void) +{ + if (mCheckEntry(m_xEntry->get_text())) + SetEntryMessageType(weld::EntryMessageType::Normal); + else + SetEntryMessageType(weld::EntryMessageType::Error); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/mailmodel.cxx b/sfx2/source/dialog/mailmodel.cxx new file mode 100644 index 000000000..382a67736 --- /dev/null +++ b/sfx2/source/dialog/mailmodel.cxx @@ -0,0 +1,849 @@ +/* -*- 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/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/container/XContainerQuery.hpp> +#include <com/sun/star/document/XExporter.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/system/SimpleSystemMail.hpp> +#include <com/sun/star/system/SimpleCommandMail.hpp> +#include <com/sun/star/system/XSimpleMailClientSupplier.hpp> +#include <com/sun/star/system/SimpleMailClientFlags.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <vcl/weld.hxx> +#include <osl/diagnose.h> + +#include <sfx2/mailmodelapi.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/strings.hrc> + +#include <unotools/tempfile.hxx> +#include <tools/urlobj.hxx> +#include <unotools/useroptions.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/string.hxx> +#include <vcl/svapp.hxx> +#include <cppuhelper/implbase.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::system; + +namespace { + +// - class PrepareListener_Impl ------------------------------------------ +class PrepareListener_Impl : public ::cppu::WeakImplHelper< css::frame::XStatusListener > +{ + bool m_bState; +public: + PrepareListener_Impl(); + + // css.frame.XStatusListener + virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& aEvent) override; + + // css.lang.XEventListener + virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override; + + bool IsSet() const {return m_bState;} +}; + +} + +PrepareListener_Impl::PrepareListener_Impl() : + m_bState( false ) +{ +} + +void PrepareListener_Impl::statusChanged(const css::frame::FeatureStateEvent& rEvent) +{ + if( rEvent.IsEnabled ) + rEvent.State >>= m_bState; + else + m_bState = false; +} + +void PrepareListener_Impl::disposing(const css::lang::EventObject& /*rEvent*/) +{ +} + +// class SfxMailModel ----------------------------------------------- + +const char16_t PDF_DOCUMENT_TYPE[] = u"pdf_Portable_Document_Format"; + +SfxMailModel::SaveResult SfxMailModel::ShowFilterOptionsDialog( + const uno::Reference< lang::XMultiServiceFactory >& xSMGR, + const uno::Reference< frame::XModel >& xModel, + const OUString& rFilterName, + std::u16string_view rType, + bool bModified, + sal_Int32& rNumArgs, + css::uno::Sequence< css::beans::PropertyValue >& rArgs ) +{ + SaveResult eRet( SAVE_ERROR ); + + try + { + uno::Sequence < beans::PropertyValue > aProps; + css::uno::Reference< css::container::XNameAccess > xFilterCFG( + xSMGR->createInstance( "com.sun.star.document.FilterFactory" ), uno::UNO_QUERY ); + css::uno::Reference< css::util::XModifiable > xModifiable( xModel, css::uno::UNO_QUERY ); + + if ( !xFilterCFG.is() ) + return eRet; + + uno::Any aAny = xFilterCFG->getByName( rFilterName ); + + if ( aAny >>= aProps ) + { + for( const auto& rProp : std::as_const(aProps) ) + { + if( rProp.Name == "UIComponent" ) + { + OUString aServiceName; + rProp.Value >>= aServiceName; + if( !aServiceName.isEmpty() ) + { + uno::Reference< ui::dialogs::XExecutableDialog > xFilterDialog( + xSMGR->createInstance( aServiceName ), uno::UNO_QUERY ); + uno::Reference< beans::XPropertyAccess > xFilterProperties( + xFilterDialog, uno::UNO_QUERY ); + + if( xFilterDialog.is() && xFilterProperties.is() ) + { + uno::Reference< document::XExporter > xExporter( xFilterDialog, uno::UNO_QUERY ); + + if ( rType == PDF_DOCUMENT_TYPE ) + { + //add an internal property, used to tell the dialog we want to set a different + //string for the ok button + //used in filter/source/pdf/impdialog.cxx + uno::Sequence< beans::PropertyValue > aFilterDataValue{ + comphelper::makePropertyValue("_OkButtonString", + SfxResId(STR_PDF_EXPORT_SEND )) + }; + + //add to the filterdata property, the only one the PDF export filter dialog will care for + uno::Sequence< beans::PropertyValue > aPropsForDialog{ + comphelper::makePropertyValue("FilterData", aFilterDataValue) + }; + + //when executing the dialog will merge the persistent FilterData properties + xFilterProperties->setPropertyValues( aPropsForDialog ); + } + + if( xExporter.is() ) + xExporter->setSourceDocument( xModel ); + + if( xFilterDialog->execute() ) + { + //get the filter data + const uno::Sequence< beans::PropertyValue > aPropsFromDialog = xFilterProperties->getPropertyValues(); + + //add them to the args + auto pProp = std::find_if(aPropsFromDialog.begin(), aPropsFromDialog.end(), + [](const beans::PropertyValue& rDialogProp) { return rDialogProp.Name == "FilterData"; }); + if (pProp != aPropsFromDialog.end()) + { + //found the filterdata, add to the storing argument + rArgs.realloc( ++rNumArgs ); + auto pArgs = rArgs.getArray(); + pArgs[rNumArgs-1].Name = pProp->Name; + pArgs[rNumArgs-1].Value = pProp->Value; + } + eRet = SAVE_SUCCESSFUL; + } + else + { + // cancel from dialog, then do not send + // If the model is not modified, it could be modified by the dispatch calls. + // Therefore set back to modified = false. This should not hurt if we call + // on a non-modified model. + if ( !bModified ) + { + try + { + xModifiable->setModified( false ); + } + catch( css::beans::PropertyVetoException& ) + { + } + } + eRet = SAVE_CANCELLED; + } + } + break; + } + } + } + } + } + catch( css::uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + } + + return eRet; +} + +bool SfxMailModel::IsEmpty() const +{ + return maAttachedDocuments.empty(); +} + +SfxMailModel::SaveResult SfxMailModel::SaveDocumentAsFormat( + const OUString& aSaveFileName, + const css::uno::Reference< css::uno::XInterface >& xFrameOrModel, + const OUString& rType, + OUString& rFileNamePath ) +{ + SaveResult eRet( SAVE_ERROR ); + bool bSendAsPDF = ( rType == PDF_DOCUMENT_TYPE ); + + css::uno::Reference< css::lang::XMultiServiceFactory > xSMGR = ::comphelper::getProcessServiceFactory(); + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + if (!xContext.is()) + return eRet; + + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager( css::frame::ModuleManager::create(xContext) ); + + OUString aModule; + try + { + aModule = xModuleManager->identify( xFrameOrModel ); + } + catch ( css::uno::RuntimeException& ) + { + throw; + } + catch ( css::uno::Exception& ) + { + } + + css::uno::Reference< css::frame::XFrame > xFrame( xFrameOrModel, css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XModel > xModel( xFrameOrModel, css::uno::UNO_QUERY ); + 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 + if ( !aModule.isEmpty() && xModel.is() ) + { + bool bModified( false ); + bool bHasLocation( false ); + bool bStoreTo( false ); + + css::uno::Reference< css::util::XModifiable > xModifiable( xModel, css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XStorable > xStorable( xModel, css::uno::UNO_QUERY ); + + if ( xModifiable.is() ) + bModified = xModifiable->isModified(); + if ( xStorable.is() ) + { + OUString aLocation = xStorable->getLocation(); + INetURLObject aFileObj( aLocation ); + + bool bPrivateProtocol = ( aFileObj.GetProtocol() == INetProtocol::PrivSoffice ); + + bHasLocation = !aLocation.isEmpty() && !bPrivateProtocol; + OSL_ASSERT( !bPrivateProtocol ); + } + if ( !rType.isEmpty() ) + bStoreTo = true; + + if ( xStorable.is() ) + { + OUString aFilterName; + OUString aTypeName( rType ); + OUString aFileName; + OUString aExtension; + + css::uno::Reference< css::container::XContainerQuery > xContainerQuery( + xSMGR->createInstance( "com.sun.star.document.FilterFactory" ), + css::uno::UNO_QUERY ); + + if ( bStoreTo ) + { + // Retrieve filter from type + css::uno::Sequence< css::beans::NamedValue > aQuery( bSendAsPDF ? 3 : 2 ); + auto pQuery = aQuery.getArray(); + pQuery[0].Name = "Type"; + pQuery[0].Value <<= aTypeName; + pQuery[1].Name = "DocumentService"; + pQuery[1].Value <<= aModule; + if( bSendAsPDF ) + { + // #i91419# + // FIXME: we want just an export filter. However currently we need + // exact flag value as detailed in the filter configuration to get it + // this seems to be a bug + // without flags we get an import filter here, which is also unwanted + pQuery[2].Name = "Flags"; + pQuery[2].Value <<= sal_Int32(0x80042); // SfxFilterFlags: EXPORT ALIEN 3RDPARTY + } + + css::uno::Reference< css::container::XEnumeration > xEnumeration = + xContainerQuery->createSubSetEnumerationByProperties( aQuery ); + + if ( xEnumeration->hasMoreElements() ) + { + ::comphelper::SequenceAsHashMap aFilterPropsHM( xEnumeration->nextElement() ); + aFilterName = aFilterPropsHM.getUnpackedValueOrDefault( + "Name", + OUString() ); + } + + if ( bHasLocation ) + { + // Retrieve filter from media descriptor + ::comphelper::SequenceAsHashMap aMediaDescrPropsHM( xModel->getArgs() ); + OUString aOrgFilterName = aMediaDescrPropsHM.getUnpackedValueOrDefault( + "FilterName", + OUString() ); + if ( aOrgFilterName == aFilterName ) + { + // We should save the document in the original format. Therefore this + // is not a storeTo operation. To support signing in this case, reset + // bStoreTo flag. + bStoreTo = false; + } + } + } + else + { + if ( bHasLocation ) + { + // Retrieve filter from media descriptor + ::comphelper::SequenceAsHashMap aMediaDescrPropsHM( xModel->getArgs() ); + aFilterName = aMediaDescrPropsHM.getUnpackedValueOrDefault( + "FilterName", + OUString() ); + } + + if ( !bHasLocation || aFilterName.isEmpty()) + { + // Retrieve the user defined default filter + try + { + ::comphelper::SequenceAsHashMap aFilterPropsHM( xModuleManager->getByName( aModule ) ); + aFilterName = aFilterPropsHM.getUnpackedValueOrDefault( + "ooSetupFactoryDefaultFilter", + OUString() ); + css::uno::Reference< css::container::XNameAccess > xNameAccess( + xContainerQuery, css::uno::UNO_QUERY ); + if ( xNameAccess.is() ) + { + ::comphelper::SequenceAsHashMap aFilterPropsHM2( xNameAccess->getByName( aFilterName ) ); + aTypeName = aFilterPropsHM2.getUnpackedValueOrDefault( + "Type", + OUString() ); + } + } + catch ( css::container::NoSuchElementException& ) + { + } + catch ( css::beans::UnknownPropertyException& ) + { + } + } + } + + // No filter found => error + // No type and no location => error + if (( aFilterName.isEmpty() ) || + ( aTypeName.isEmpty() && !bHasLocation )) + return eRet; + + // Determine file name and extension + if ( bHasLocation && !bStoreTo ) + { + INetURLObject aFileObj( xStorable->getLocation() ); + aExtension = aFileObj.getExtension(); + } + else + { + css::uno::Reference< container::XNameAccess > xTypeDetection( + xSMGR->createInstance( "com.sun.star.document.TypeDetection" ), + css::uno::UNO_QUERY ); + + + if ( xTypeDetection.is() ) + { + try + { + ::comphelper::SequenceAsHashMap aTypeNamePropsHM( xTypeDetection->getByName( aTypeName ) ); + uno::Sequence< OUString > aExtensions = aTypeNamePropsHM.getUnpackedValueOrDefault( + "Extensions", + ::uno::Sequence< OUString >() ); + if ( aExtensions.hasElements() ) + aExtension = aExtensions[0]; + } + catch ( css::container::NoSuchElementException& ) + { + } + } + } + + // Use provided save file name. If empty determine file name + aFileName = aSaveFileName; + if ( aFileName.isEmpty() ) + { + if ( !bHasLocation ) + { + // Create a noname file name with the correct extension + aFileName = "noname"; + } + else + { + // Determine file name from model + INetURLObject aFileObj( xStorable->getLocation() ); + aFileName = aFileObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::NONE ); + } + } + + // No file name => error + if ( aFileName.isEmpty() ) + return eRet; + + OSL_ASSERT( !aFilterName.isEmpty() ); + OSL_ASSERT( !aFileName.isEmpty() ); + + // Creates a temporary directory to store a predefined file into it. + // This makes it possible to store the file for "send document as e-mail" + // with the original file name. We cannot use the original file as + // some mail programs need exclusive access. + ::utl::TempFile aTempDir( nullptr, true ); + + INetURLObject aFilePathObj( aTempDir.GetURL() ); + aFilePathObj.insertName( aFileName ); + aFilePathObj.setExtension( aExtension ); + + OUString aFileURL = aFilePathObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + sal_Int32 nNumArgs(1); + static const OUStringLiteral aPasswordPropName( u"Password" ); + css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue( + "FilterName", aFilterName) }; + + ::comphelper::SequenceAsHashMap aMediaDescrPropsHM( xModel->getArgs() ); + OUString aPassword = aMediaDescrPropsHM.getUnpackedValueOrDefault( + aPasswordPropName, + OUString() ); + if ( !aPassword.isEmpty() ) + { + aArgs.realloc( ++nNumArgs ); + auto pArgs = aArgs.getArray(); + pArgs[nNumArgs-1].Name = aPasswordPropName; + pArgs[nNumArgs-1].Value <<= aPassword; + } + + bool bNeedsPreparation = false; + css::util::URL aPrepareURL; + css::uno::Reference< css::frame::XDispatch > xPrepareDispatch; + css::uno::Reference< css::frame::XDispatchProvider > xDispatchProvider( xFrame, css::uno::UNO_QUERY ); + css::uno::Reference< css::util::XURLTransformer > xURLTransformer( css::util::URLTransformer::create( xContext ) ); + if( !bSendAsPDF ) + { + try + { + // check if the document needs to be prepared for sending as mail (embedding of links, removal of invisible content) + + aPrepareURL.Complete = ".uno:PrepareMailExport"; + xURLTransformer->parseStrict( aPrepareURL ); + + if ( xDispatchProvider.is() ) + { + xPrepareDispatch.set( xDispatchProvider->queryDispatch( aPrepareURL, OUString(), 0 )); + if ( xPrepareDispatch.is() ) + { + rtl::Reference<PrepareListener_Impl> pPrepareListener = new PrepareListener_Impl; + xPrepareDispatch->addStatusListener( pPrepareListener, aPrepareURL ); + bNeedsPreparation = pPrepareListener->IsSet(); + xPrepareDispatch->removeStatusListener( pPrepareListener, aPrepareURL ); + } + } + } + catch ( css::uno::RuntimeException& ) + { + throw; + } + catch ( css::uno::Exception& ) + { + } + } + + if ( bModified || !bHasLocation || bStoreTo || bNeedsPreparation ) + { + // Document is modified, is newly created or should be stored in a special format + try + { + if( bNeedsPreparation && xPrepareDispatch.is() ) + { + try + { + css::uno::Sequence< css::beans::PropertyValue > aDispatchArgs; + xPrepareDispatch->dispatch( aPrepareURL, aDispatchArgs ); + } + catch ( css::uno::RuntimeException& ) + { + throw; + } + catch ( css::uno::Exception& ) + { + } + } + + //check if this is the pdf output filter (i#64555) + if( bSendAsPDF ) + { + SaveResult eShowPDFFilterDialog = ShowFilterOptionsDialog( + xSMGR, xModel, aFilterName, rType, bModified, nNumArgs, aArgs ); + + // don't continue on dialog cancel or error + if ( eShowPDFFilterDialog != SAVE_SUCCESSFUL ) + return eShowPDFFilterDialog; + } + + xStorable->storeToURL( aFileURL, aArgs ); + rFileNamePath = aFileURL; + eRet = SAVE_SUCCESSFUL; + + if( !bSendAsPDF ) + { + css::util::URL aURL; + // #i30432# notify that export is finished - the Writer may want to restore removed content + aURL.Complete = ".uno:MailExportFinished"; + xURLTransformer->parseStrict( aURL ); + + if ( xDispatchProvider.is() ) + { + css::uno::Reference< css::frame::XDispatch > xDispatch( + xDispatchProvider->queryDispatch( aURL, OUString(), 0 )); + if ( xDispatch.is() ) + { + try + { + css::uno::Sequence< css::beans::PropertyValue > aDispatchArgs; + xDispatch->dispatch( aURL, aDispatchArgs ); + } + catch ( css::uno::RuntimeException& ) + { + throw; + } + catch ( css::uno::Exception& ) + { + } + } + } + } + // If the model is not modified, it could be modified by the dispatch calls. + // Therefore set back to modified = false. This should not hurt if we call + // on a non-modified model. + if ( !bModified ) + { + try + { + xModifiable->setModified( false ); + } + catch( css::beans::PropertyVetoException& ) + { + } + } + } + catch ( css::io::IOException& ) + { + eRet = SAVE_ERROR; + } + } + else + { + // We need 1:1 copy of the document to preserve an added signature. + aArgs.realloc( ++nNumArgs ); + auto pArgs = aArgs.getArray(); + pArgs[nNumArgs-1].Name = "CopyStreamIfPossible"; + pArgs[nNumArgs-1].Value <<= true; + + try + { + xStorable->storeToURL( aFileURL, aArgs ); + rFileNamePath = aFileURL; + eRet = SAVE_SUCCESSFUL; + } + catch ( css::io::IOException& ) + { + eRet = SAVE_ERROR; + } + } + } + } + + return eRet; +} + +SfxMailModel::SfxMailModel() +{ +} + +SfxMailModel::~SfxMailModel() +{ +} + +void SfxMailModel::AddToAddress( const OUString& rAddress ) +{ + // don't add an empty address + if ( !rAddress.isEmpty() ) + { + if ( !mpToList ) + // create the list + mpToList.reset(new AddressList_Impl); + + // add address to list + mpToList->push_back( rAddress ); + } +} + +SfxMailModel::SendMailResult SfxMailModel::AttachDocument( + const css::uno::Reference< css::uno::XInterface >& xFrameOrModel, + const OUString& sAttachmentTitle ) +{ + OUString sFileName; + + SaveResult eSaveResult = SaveDocumentAsFormat( sAttachmentTitle, xFrameOrModel, OUString()/*sDocumentType*/, sFileName ); + if ( eSaveResult == SAVE_SUCCESSFUL && !sFileName.isEmpty() ) + maAttachedDocuments.push_back(sFileName); + return eSaveResult == SAVE_SUCCESSFUL ? SEND_MAIL_OK : SEND_MAIL_ERROR; +} + +SfxMailModel::SendMailResult SfxMailModel::Send( const css::uno::Reference< css::frame::XFrame >& xFrame ) +{ + OSL_ENSURE(!maAttachedDocuments.empty(),"No document added!"); + SendMailResult eResult = SEND_MAIL_ERROR; + if ( !maAttachedDocuments.empty() ) + { + css::uno::Reference < XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + css::uno::Reference< XSimpleMailClientSupplier > xSimpleMailClientSupplier; + + // Prefer the SimpleSystemMail service if available + try { + xSimpleMailClientSupplier = SimpleSystemMail::create( xContext ); + } + catch ( const uno::Exception & ) + {} + + if ( ! xSimpleMailClientSupplier.is() ) + { + try { + xSimpleMailClientSupplier = SimpleCommandMail::create( xContext ); + } + catch ( const uno::Exception & ) + {} + } + + if ( xSimpleMailClientSupplier.is() ) + { + css::uno::Reference< XSimpleMailClient > xSimpleMailClient = xSimpleMailClientSupplier->querySimpleMailClient(); + + if ( !xSimpleMailClient.is() ) + { + // no mail client support => message box! + return SEND_MAIL_ERROR; + } + + // we have a simple mail client + css::uno::Reference< XSimpleMailMessage > xSimpleMailMessage = xSimpleMailClient->createSimpleMailMessage(); + if ( xSimpleMailMessage.is() ) + { + sal_Int32 nSendFlags = SimpleMailClientFlags::DEFAULTS; + if ( maFromAddress.isEmpty() ) + { + // from address not set, try figure out users e-mail address + CreateFromAddress_Impl( maFromAddress ); + } + xSimpleMailMessage->setOriginator( maFromAddress ); + + size_t nToCount = mpToList ? mpToList->size() : 0; + + // set recipient (only one) for this simple mail server!! + if ( nToCount >= 1 ) + { + xSimpleMailMessage->setRecipient( mpToList->at( 0 ) ); + nSendFlags = SimpleMailClientFlags::NO_USER_INTERFACE; + } + + // all other recipient must be handled with CC recipients! + if ( nToCount > 1 ) + { + Sequence< OUString > aCcRecipientSeq( nToCount - 1 ); + std::copy_n(std::next(mpToList->begin()), aCcRecipientSeq.getLength(), + aCcRecipientSeq.getArray()); + xSimpleMailMessage->setCcRecipient( aCcRecipientSeq ); + } + + Sequence< OUString > aAttachmentSeq(maAttachedDocuments.data(),maAttachedDocuments.size()); + + if ( xSimpleMailMessage->getSubject().isEmpty() ) { + INetURLObject url( + maAttachedDocuments[0], INetURLObject::EncodeMechanism::WasEncoded); + OUString subject( + url.getBase( + INetURLObject::LAST_SEGMENT, false, + INetURLObject::DecodeMechanism::WithCharset)); + if (subject.isEmpty()) { + subject = maAttachedDocuments[0]; + } + if ( maAttachedDocuments.size() > 1 ) + subject += ", ..."; + xSimpleMailMessage->setSubject( subject ); + } + xSimpleMailMessage->setAttachement( aAttachmentSeq ); + + bool bSend( false ); + try + { + xSimpleMailClient->sendSimpleMailMessage( xSimpleMailMessage, nSendFlags ); + bSend = true; + } + catch ( IllegalArgumentException& ) + { + } + catch ( Exception& ) + { + } + + if ( !bSend ) + { + css::uno::Reference< css::awt::XWindow > xParentWindow = xFrame->getContainerWindow(); + + SolarMutexGuard aGuard; + + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(Application::GetFrameWeld(xParentWindow), "sfx/ui/errorfindemaildialog.ui")); + std::unique_ptr<weld::MessageDialog> xBox(xBuilder->weld_message_dialog("ErrorFindEmailDialog")); + xBox->run(); + eResult = SEND_MAIL_CANCELLED; + } + else + eResult = SEND_MAIL_OK; + } + } + } + else + eResult = SEND_MAIL_CANCELLED; + + return eResult; +} + +SfxMailModel::SendMailResult SfxMailModel::SaveAndSend( const css::uno::Reference< css::frame::XFrame >& xFrame, const OUString& rTypeName ) +{ + SaveResult eSaveResult; + SendMailResult eResult = SEND_MAIL_ERROR; + OUString aFileName; + + eSaveResult = SaveDocumentAsFormat( OUString(), xFrame, rTypeName, aFileName ); + + if ( eSaveResult == SAVE_SUCCESSFUL ) + { + maAttachedDocuments.push_back( aFileName ); + return Send( xFrame ); + } + else if ( eSaveResult == SAVE_CANCELLED ) + eResult = SEND_MAIL_CANCELLED; + + return eResult; +} + +// functions ------------------------------------------------------------- + +bool CreateFromAddress_Impl( OUString& rFrom ) + +/* [Description] + + This function tries to create a From-address with the help of IniManagers. + For this the fields 'first name', 'Name' and 'Email' are read from the + application-ini-data. If these fields are not set, FALSE is returned. + + [Return value] + + sal_True: Address could be created. + sal_False: Address could not be created. +*/ + +{ + SvtUserOptions aUserCFG; + OUString aName = aUserCFG.GetLastName (); + OUString aFirstName = aUserCFG.GetFirstName (); + if ( !aFirstName.isEmpty() || !aName.isEmpty() ) + { + if ( !aFirstName.isEmpty() ) + { + rFrom = comphelper::string::strip(aFirstName, ' '); + + if ( !aName.isEmpty() ) + rFrom += " "; + } + rFrom += comphelper::string::strip(aName, ' '); + // remove illegal characters + rFrom = rFrom.replaceAll("<", "").replaceAll(">", "").replaceAll("@", ""); + } + OUString aEmailName = aUserCFG.GetEmail(); + + // remove illegal characters + aEmailName = aEmailName.replaceAll("<", "").replaceAll(">", ""); + + if ( !aEmailName.isEmpty() ) + { + if ( !rFrom.isEmpty() ) + rFrom += " "; + rFrom += "<" + comphelper::string::strip(aEmailName, ' ') + ">"; + } + else + rFrom.clear(); + return !rFrom.isEmpty(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/mgetempl.cxx b/sfx2/source/dialog/mgetempl.cxx new file mode 100644 index 000000000..3b683b743 --- /dev/null +++ b/sfx2/source/dialog/mgetempl.cxx @@ -0,0 +1,653 @@ +/* -*- 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 <comphelper/string.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <svl/style.hxx> +#include <osl/diagnose.h> + +#include <sfx2/styfitem.hxx> +#include <sfx2/styledlg.hxx> +#include <sfx2/tabdlg.hxx> +#include <sfx2/app.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/module.hxx> +#include <sfx2/sfxsids.hrc> + +#include <sfx2/strings.hrc> + +#include <svl/stritem.hxx> +#include <sfx2/dispatch.hxx> + +#include "mgetempl.hxx" + +/* SfxManageStyleSheetPage Constructor + * + * initializes the list box with the templates + */ +SfxManageStyleSheetPage::SfxManageStyleSheetPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rAttrSet) + : SfxTabPage(pPage, pController, "sfx/ui/managestylepage.ui", "ManageStylePage", &rAttrSet) + , pStyle(&static_cast<SfxStyleDialogController*>(pController)->GetStyleSheet()) + , pItem(nullptr) + , bModified(false) + , aName(pStyle->GetName()) + , aFollow(pStyle->GetFollow()) + , aParent(pStyle->GetParent()) + , nFlags(pStyle->GetMask()) + , m_xName(m_xBuilder->weld_entry("name")) + , m_xAutoCB(m_xBuilder->weld_check_button("autoupdate")) + , m_xFollowFt(m_xBuilder->weld_label("nextstyleft")) + , m_xFollowLb(m_xBuilder->weld_combo_box("nextstyle")) + , m_xEditStyleBtn(m_xBuilder->weld_button("editstyle")) + , m_xBaseFt(m_xBuilder->weld_label("linkedwithft")) + , m_xBaseLb(m_xBuilder->weld_combo_box("linkedwith")) + , m_xEditLinkStyleBtn(m_xBuilder->weld_button("editlinkstyle")) + , m_xFilterFt(m_xBuilder->weld_label("categoryft")) + , m_xFilterLb(m_xBuilder->weld_combo_box("category")) + , m_xDescFt(m_xBuilder->weld_label("desc")) + , m_xNameFt(m_xBuilder->weld_label("nameft")) +{ + m_xFollowLb->make_sorted(); + // tdf#120188 like SwCharURLPage limit the width of the style combos + const int nMaxWidth(m_xFollowLb->get_approximate_digit_width() * 50); + m_xFollowLb->set_size_request(nMaxWidth , -1); + m_xBaseLb->make_sorted(); + m_xBaseLb->set_size_request(nMaxWidth , -1); + //note that the code depends on categories not being lexically + //sorted, so if it's changed to sorted, the code needs to + //be adapted to be position unaware + m_xFilterLb->set_size_request(nMaxWidth , -1); + + // this Page needs ExchangeSupport + SetExchangeSupport(); + + if ( aFollow.isEmpty() || aFollow == aName ) + m_xEditStyleBtn->set_sensitive(false); + else + m_xEditStyleBtn->set_sensitive(true); + + int linkSelectPos = m_xBaseLb->get_active(); + if ( linkSelectPos == 0 ) + m_xEditLinkStyleBtn->set_sensitive(false); + else + m_xEditLinkStyleBtn->set_sensitive(true); + + mxFamilies = SfxApplication::GetModule_Impl()->CreateStyleFamilies(); + + SfxStyleSheetBasePool* pPool = nullptr; + SfxObjectShell* pDocShell = SfxObjectShell::Current(); + + if ( pDocShell ) + pPool = pDocShell->GetStyleSheetPool(); + OSL_ENSURE( pPool, "no Pool or no DocShell" ); + + if ( pPool ) + { + pPool->First(pStyle->GetFamily()); // for SW - update internal list + } + + if ( pStyle->GetName().isEmpty() && pPool ) + { + // NullString as Name -> generate Name + OUString aNoName(SfxStyleDialogController::GenerateUnusedName(*pPool, pStyle->GetFamily())); + pStyle->SetName( aNoName ); + aName = aNoName; + aFollow = pStyle->GetFollow(); + aParent = pStyle->GetParent(); + } + m_xName->set_text(pStyle->GetName()); + + // Set the field read-only if it is NOT an user-defined style + // but allow selecting and copying + if (pStyle->IsUserDefined()) + { + m_xName->set_can_focus(true); + m_xName->set_editable(true); + m_xName->set_sensitive(true); + m_xName->grab_focus(); // tdf#142017 default to focus within the page, not in notebook tab + } + else + { + m_xName->set_sensitive(false); + } + + if ( pStyle->HasFollowSupport() && pPool ) + { + SfxStyleSheetBase* pPoolStyle = pPool->First(pStyle->GetFamily()); + + m_xFollowLb->freeze(); + + while ( pPoolStyle ) + { + m_xFollowLb->append_text(pPoolStyle->GetName()); + pPoolStyle = pPool->Next(); + } + + // A new Template is not yet in the Pool + if (m_xFollowLb->find_text(pStyle->GetName()) == -1) + m_xFollowLb->append_text(pStyle->GetName()); + + m_xFollowLb->thaw(); + } + else + { + m_xFollowFt->set_sensitive(false); + m_xFollowFt->hide(); + m_xFollowLb->set_sensitive(false); + m_xFollowLb->hide(); + m_xEditStyleBtn->hide(); + } + + if ( pStyle->HasParentSupport() && pPool ) + { + m_xBaseLb->freeze(); + + if ( pStyle->HasClearParentSupport() ) + // the base template can be set to NULL + m_xBaseLb->append_text(SfxResId(STR_NONE)); + + SfxStyleSheetBase* pPoolStyle = pPool->First(pStyle->GetFamily()); + + while ( pPoolStyle ) + { + const OUString aStr( pPoolStyle->GetName() ); + // own name as base template + if ( aStr != aName ) + m_xBaseLb->append_text(aStr); + pPoolStyle = pPool->Next(); + } + + m_xBaseLb->thaw(); + } + else + { + m_xBaseFt->set_sensitive(false); + m_xBaseLb->set_sensitive(false); + } + + size_t nCount = mxFamilies->size(); + size_t i; + for ( i = 0; i < nCount; ++i ) + { + pItem = &(mxFamilies->at(i)); + + if ( pItem->GetFamily() == pStyle->GetFamily() ) + break; + } + + if ( i < nCount ) + { + sal_uInt16 nStyleFilterIdx = 0xffff; + // Filter flags + const SfxStyleFilter& rList = pItem->GetFilterList(); + nCount = rList.size(); + sal_uInt16 nIdx = 0; + SfxStyleSearchBits nMask = pStyle->GetMask() & ~SfxStyleSearchBits::UserDefined; + + if ( nMask == SfxStyleSearchBits::Auto ) // User Template? + nMask = pStyle->GetMask(); + + for ( i = 0; i < nCount; ++i ) + { + const SfxFilterTuple& rTupel = rList[ i ]; + + if ( rTupel.nFlags != SfxStyleSearchBits::Auto && + rTupel.nFlags != SfxStyleSearchBits::Used && + rTupel.nFlags != SfxStyleSearchBits::AllVisible && + rTupel.nFlags != SfxStyleSearchBits::All ) + { + OUString sId(OUString::number(i)); + m_xFilterLb->insert(nIdx, rTupel.aName, &sId, nullptr, nullptr); + if ( ( rTupel.nFlags & nMask ) == nMask ) + nStyleFilterIdx = nIdx; + ++nIdx; + } + } + + if ( nStyleFilterIdx != 0xFFFF ) + m_xFilterLb->set_active(nStyleFilterIdx); + } + + if ( !m_xFilterLb->get_count() || !pStyle->IsUserDefined() ) + { + pItem = nullptr; + m_xFilterFt->set_sensitive(false); + m_xFilterLb->set_sensitive(false); + } + else + m_xFilterLb->save_value(); + SetDescriptionText_Impl(); + + if (m_xFollowLb->get_sensitive() || m_xBaseLb->get_sensitive()) + { + m_xName->connect_focus_in( + LINK( this, SfxManageStyleSheetPage, GetFocusHdl ) ); + m_xName->connect_focus_out( + LINK( this, SfxManageStyleSheetPage, LoseFocusHdl ) ); + } + // It is a style with auto update? (SW only) + if(SfxItemState::SET == rAttrSet.GetItemState(SID_ATTR_AUTO_STYLE_UPDATE)) + m_xAutoCB->show(); + m_xFollowLb->connect_changed(LINK(this, SfxManageStyleSheetPage, EditStyleSelectHdl_Impl)); + m_xBaseLb->connect_changed(LINK(this, SfxManageStyleSheetPage, EditLinkStyleSelectHdl_Impl)); + m_xEditStyleBtn->connect_clicked(LINK(this, SfxManageStyleSheetPage, EditStyleHdl_Impl)); + m_xEditLinkStyleBtn->connect_clicked(LINK(this, SfxManageStyleSheetPage, EditLinkStyleHdl_Impl)); +} + +SfxManageStyleSheetPage::~SfxManageStyleSheetPage() +{ + mxFamilies.reset(); + pItem = nullptr; + pStyle = nullptr; +} + +void SfxManageStyleSheetPage::UpdateName_Impl( weld::ComboBox* pBox, + const OUString& rNew ) + +/* [Description] + + After the change of a template name update the ListBox pBox + + [Parameter] + + ListBox* pBox ListBox, whose entries are to be updated + const String& rNew the new Name +*/ + +{ + if (pBox->get_sensitive()) + { + // it is the current entry, which name was modified + const bool bSelect = pBox->get_active_text() == aBuf; + int nOldIndex = pBox->find_text(aBuf); + if (nOldIndex != -1) + pBox->remove(nOldIndex); + pBox->append_text(rNew); + + if (bSelect) + pBox->set_active_text(rNew); + } +} + +void SfxManageStyleSheetPage::SetDescriptionText_Impl() + +/* [Description] + + Set attribute description. Get the set metric for this. +*/ + +{ + MapUnit eUnit = MapUnit::MapCM; + FieldUnit eFieldUnit( FieldUnit::CM ); + SfxModule* pModule = SfxModule::GetActiveModule(); + if ( pModule ) + { + const SfxPoolItem* pPoolItem = pModule->GetItem( SID_ATTR_METRIC ); + if ( pPoolItem ) + eFieldUnit = static_cast<FieldUnit>(static_cast<const SfxUInt16Item*>( pPoolItem )->GetValue()); + } + + switch ( eFieldUnit ) + { + case FieldUnit::MM: eUnit = MapUnit::MapMM; break; + case FieldUnit::CM: + case FieldUnit::M: + case FieldUnit::KM: eUnit = MapUnit::MapCM; break; + case FieldUnit::POINT: + case FieldUnit::PICA: eUnit = MapUnit::MapPoint; break; + case FieldUnit::INCH: + case FieldUnit::FOOT: + case FieldUnit::MILE: eUnit = MapUnit::MapInch; break; + + default: + OSL_FAIL( "non supported field unit" ); + } + m_xDescFt->set_label(pStyle->GetDescription(eUnit)); +} + +IMPL_LINK_NOARG(SfxManageStyleSheetPage, EditStyleSelectHdl_Impl, weld::ComboBox&, void) +{ + OUString aTemplName(m_xFollowLb->get_active_text()); + OUString aEditTemplName(m_xName->get_text()); + m_xEditStyleBtn->set_sensitive(aTemplName != aEditTemplName); +} + +IMPL_LINK_NOARG(SfxManageStyleSheetPage, EditStyleHdl_Impl, weld::Button&, void) +{ + OUString aTemplName(m_xFollowLb->get_active_text()); + Execute_Impl(SID_STYLE_EDIT, aTemplName, static_cast<sal_uInt16>(pStyle->GetFamily())); +} + +IMPL_LINK_NOARG(SfxManageStyleSheetPage, EditLinkStyleSelectHdl_Impl, weld::ComboBox&, void) +{ + int linkSelectPos = m_xBaseLb->get_active(); + if ( linkSelectPos == 0 ) + m_xEditLinkStyleBtn->set_sensitive(false); + else + m_xEditLinkStyleBtn->set_sensitive(true); +} + +IMPL_LINK_NOARG(SfxManageStyleSheetPage, EditLinkStyleHdl_Impl, weld::Button&, void) +{ + OUString aTemplName(m_xBaseLb->get_active_text()); + if (aTemplName != SfxResId(STR_NONE)) + Execute_Impl( SID_STYLE_EDIT, aTemplName, static_cast<sal_uInt16>(pStyle->GetFamily()) ); +} + +// Internal: Perform functions through the Dispatcher +bool SfxManageStyleSheetPage::Execute_Impl( + sal_uInt16 nId, const OUString &rStr, sal_uInt16 nFamily) +{ + + SfxDispatcher &rDispatcher = *SfxGetpApp()->GetDispatcher_Impl(); + SfxStringItem aItem(nId, rStr); + SfxUInt16Item aFamily(SID_STYLE_FAMILY, nFamily); + const SfxPoolItem* pItems[ 6 ]; + sal_uInt16 nCount = 0; + if( !rStr.isEmpty() ) + pItems[ nCount++ ] = &aItem; + pItems[ nCount++ ] = &aFamily; + + pItems[ nCount++ ] = nullptr; + + const SfxPoolItem* pItem = rDispatcher.Execute( + nId, SfxCallMode::SYNCHRON | SfxCallMode::RECORD, + pItems ); + + return pItem != nullptr; + +} + +IMPL_LINK(SfxManageStyleSheetPage, GetFocusHdl, weld::Widget&, rControl, void) + +/* [Description] + + StarView Handler; GetFocus-Handler of the Edits with the template name. +*/ + +{ + weld::Entry& rEdit = dynamic_cast<weld::Entry&>(rControl); + aBuf = comphelper::string::stripStart(rEdit.get_text(), ' '); +} + +IMPL_LINK(SfxManageStyleSheetPage, LoseFocusHdl, weld::Widget&, rControl, void) + +/* [Description] + + StarView Handler; lose-focus-handler of the edits of the template name. + This will update the listbox with the subsequent templates. The current + template itself is not returned in the listbox of the base templates. +*/ + +{ + weld::Entry& rEdit = dynamic_cast<weld::Entry&>(rControl); + const OUString aStr(comphelper::string::stripStart(rEdit.get_text(), ' ')); + rEdit.set_text(aStr); + // Update the Listbox of the base template if possible + if ( aStr != aBuf ) + UpdateName_Impl(m_xFollowLb.get(), aStr); +} + +bool SfxManageStyleSheetPage::FillItemSet( SfxItemSet* rSet ) + +/* [Description] + + Handler for setting the (modified) data. I called from the OK of the + SfxTabDialog. + + [Parameter] + + SfxItemSet &rAttrSet The set, which receives the data. + + [Return value] + + sal_Bool sal_True: The data had been changed + sal_False: The data had not been changed + + [Cross-reference] + + <class SfxTabDialog> +*/ + +{ + const int nFilterIdx = m_xFilterLb->get_active(); + + // Set Filter + + if ( nFilterIdx != -1 && + m_xFilterLb->get_value_changed_from_saved() && + m_xFilterLb->get_sensitive() ) + { + bModified = true; + OSL_ENSURE( pItem, "No Item" ); + // is only possibly for user templates + SfxStyleSearchBits nMask = pItem->GetFilterList()[m_xFilterLb->get_id(nFilterIdx).toUInt32()].nFlags | SfxStyleSearchBits::UserDefined; + pStyle->SetMask( nMask ); + } + if (m_xAutoCB->get_visible() && m_xAutoCB->get_state_changed_from_saved()) + { + rSet->Put(SfxBoolItem(SID_ATTR_AUTO_STYLE_UPDATE, m_xAutoCB->get_active())); + } + + return bModified; +} + + +void SfxManageStyleSheetPage::Reset( const SfxItemSet* /*rAttrSet*/ ) + +/* [Description] + + Handler to initialize the page with the initial data. + + [Parameter] + + const SfxItemSet &rAttrSet The data set + + [Cross-reference] + + <class SfxTabDialog> +*/ + +{ + bModified = false; + OUString sCmp( pStyle->GetName() ); + + if ( sCmp != aName ) + pStyle->SetName( aName ); + m_xName->set_text( aName ); + if (m_xName->get_editable()) + m_xName->select_region(0, -1); + + if ( m_xFollowLb->get_sensitive() ) + { + sCmp = pStyle->GetFollow(); + + if ( sCmp != aFollow ) + pStyle->SetFollow( aFollow ); + + if ( aFollow.isEmpty() ) + { + m_xFollowLb->set_active_text( aName ); + m_xEditStyleBtn->set_sensitive( false ); + } + else + m_xFollowLb->set_active_text( aFollow ); + } + + if (m_xBaseLb->get_sensitive()) + { + sCmp = pStyle->GetParent(); + + if ( sCmp != aParent ) + pStyle->SetParent( aParent ); + + if ( aParent.isEmpty() ) + { + m_xBaseLb->set_active_text( SfxResId(STR_NONE) ); + m_xEditLinkStyleBtn->set_sensitive( false ); + } + else + m_xBaseLb->set_active_text( aParent ); + + if ( SfxResId(STR_STANDARD) == aName ) + { + // the default template can not be linked + m_xBaseFt->set_sensitive(false); + m_xBaseLb->set_sensitive(false); + } + } + else + m_xEditLinkStyleBtn->set_sensitive( false ); + + if (m_xFilterLb->get_sensitive()) + { + SfxStyleSearchBits nCmp = pStyle->GetMask(); + + if ( nCmp != nFlags ) + pStyle->SetMask( nFlags ); + m_xFilterLb->set_active_text(m_xFilterLb->get_saved_value()); + } +} + +std::unique_ptr<SfxTabPage> SfxManageStyleSheetPage::Create( weld::Container* pPage, weld::DialogController* pController, + const SfxItemSet *rAttrSet ) +{ + return std::make_unique<SfxManageStyleSheetPage>(pPage, pController, *rAttrSet); +} + +void SfxManageStyleSheetPage::ActivatePage( const SfxItemSet& rSet) + +/* [Description] + + ActivatePage handler of SfxTabDialog, is used for the update of the + descriptive text, since this might have changed through changes of data on + other pages. + + [Parameter] + + const SfxItemSet& the set for the exchange of data; is not used here. + + [Cross-reference] + + <SfxTabDialog::ActivatePage(const SfxItemSet &)> +*/ + +{ + SetDescriptionText_Impl(); + + // It is a style with auto update? (SW only) + const SfxBoolItem* pPoolItem; + + if ( (pPoolItem = rSet.GetItemIfSet( SID_ATTR_AUTO_STYLE_UPDATE, false )) ) + m_xAutoCB->set_active(pPoolItem->GetValue()); + m_xAutoCB->save_state(); + m_xName->save_value(); +} + +DeactivateRC SfxManageStyleSheetPage::DeactivatePage( SfxItemSet* pItemSet ) + +/* [Description] + + DeactivatePage-handler of SfxTabDialog; data is set on the template, so + that the correct inheritance on the other pages of the dialog is made. + If an error occurs, leaving the page is prevented. + [Parameter] + + SfxItemSet* the set for the exchange of data; is not used here. + + [Cross-reference] + + <SfxTabDialog::DeactivatePage(SfxItemSet*)> +*/ + +{ + DeactivateRC nRet = DeactivateRC::LeavePage; + + if (m_xName->get_value_changed_from_saved()) + { + // By pressing <Enter> LoseFocus() is not triggered through StarView + if (m_xName->has_focus()) + LoseFocusHdl( *m_xName ); + + if (!pStyle->SetName(comphelper::string::stripStart(m_xName->get_text(), ' '))) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_TABPAGE_INVALIDNAME))); + xBox->run(); + m_xName->grab_focus(); + m_xName->select_region(0, -1); + return DeactivateRC::KeepPage; + } + bModified = true; + } + + if (pStyle->HasFollowSupport() && m_xFollowLb->get_sensitive()) + { + const OUString aFollowEntry( m_xFollowLb->get_active_text() ); + + if ( pStyle->GetFollow() != aFollowEntry ) + { + if ( !pStyle->SetFollow( aFollowEntry ) ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_TABPAGE_INVALIDSTYLE))); + xBox->run(); + m_xFollowLb->grab_focus(); + return DeactivateRC::KeepPage; + } + bModified = true; + } + } + + if (m_xBaseLb->get_sensitive()) + { + OUString aParentEntry( m_xBaseLb->get_active_text() ); + + if ( SfxResId(STR_NONE) == aParentEntry || aParentEntry == pStyle->GetName() ) + aParentEntry.clear(); + + if ( pStyle->GetParent() != aParentEntry ) + { + if ( !pStyle->SetParent( aParentEntry ) ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_TABPAGE_INVALIDPARENT))); + xBox->run(); + m_xBaseLb->grab_focus(); + return DeactivateRC::KeepPage; + } + bModified = true; + nRet = nRet | DeactivateRC::RefreshSet; + } + } + + if ( pItemSet ) + FillItemSet( pItemSet ); + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/mgetempl.hxx b/sfx2/source/dialog/mgetempl.hxx new file mode 100644 index 000000000..ef0d2fdcd --- /dev/null +++ b/sfx2/source/dialog/mgetempl.hxx @@ -0,0 +1,95 @@ +/* -*- 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_MGETEMPL_HXX +#define INCLUDED_SFX2_MGETEMPL_HXX + +#include <sfx2/styfitem.hxx> +#include <sfx2/tabdlg.hxx> +#include <memory> +#include <optional> + +namespace weld { class Button; } +namespace weld { class CheckButton; } +namespace weld { class ComboBox; } +namespace weld { class Entry; } +namespace weld { class Label; } +namespace weld { class Widget; } + +/* expected: + SID_TEMPLATE_NAME : In: StringItem, Name of Template + SID_TEMPLATE_FAMILY : In: Family of Template +*/ + +class SfxManageStyleSheetPage final : public SfxTabPage +{ + SfxStyleSheetBase *pStyle; + std::optional<SfxStyleFamilies> mxFamilies; + const SfxStyleFamilyItem *pItem; + OUString aBuf; + bool bModified; + + // initial data for the style + OUString aName; + OUString aFollow; + OUString aParent; + SfxStyleSearchBits nFlags; + + std::unique_ptr<weld::Entry> m_xName; + std::unique_ptr<weld::CheckButton> m_xAutoCB; + std::unique_ptr<weld::Label> m_xFollowFt; + std::unique_ptr<weld::ComboBox> m_xFollowLb; + std::unique_ptr<weld::Button> m_xEditStyleBtn; + std::unique_ptr<weld::Label> m_xBaseFt; + std::unique_ptr<weld::ComboBox> m_xBaseLb; + std::unique_ptr<weld::Button> m_xEditLinkStyleBtn; + std::unique_ptr<weld::Label> m_xFilterFt; + std::unique_ptr<weld::ComboBox> m_xFilterLb; + std::unique_ptr<weld::Label> m_xDescFt; + std::unique_ptr<weld::Label> m_xNameFt; + + friend class SfxStyleDialogController; + + DECL_LINK(GetFocusHdl, weld::Widget&, void); + DECL_LINK(LoseFocusHdl, weld::Widget&, void); + DECL_LINK(EditStyleSelectHdl_Impl, weld::ComboBox&, void); + DECL_LINK(EditStyleHdl_Impl, weld::Button&, void); + DECL_LINK(EditLinkStyleSelectHdl_Impl, weld::ComboBox&, void); + DECL_LINK(EditLinkStyleHdl_Impl, weld::Button&, void); + + void UpdateName_Impl(weld::ComboBox*, const OUString &rNew); + void SetDescriptionText_Impl(); + + + static std::unique_ptr<SfxTabPage> Create( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* ); + + virtual bool FillItemSet(SfxItemSet *) override; + virtual void Reset(const SfxItemSet *) override; + + static bool Execute_Impl( sal_uInt16 nId, const OUString& rStr, sal_uInt16 nFamily ); + virtual void ActivatePage(const SfxItemSet &) override; + virtual DeactivateRC DeactivatePage(SfxItemSet *) override; + +public: + SfxManageStyleSheetPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet &rAttrSet); + virtual ~SfxManageStyleSheetPage() override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/navigat.cxx b/sfx2/source/dialog/navigat.cxx new file mode 100644 index 000000000..ff9f8a9f7 --- /dev/null +++ b/sfx2/source/dialog/navigat.cxx @@ -0,0 +1,53 @@ +/* -*- 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 <sfx2/bindings.hxx> +#include <sfx2/navigat.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/sfxresid.hxx> +#include <helpids.h> + +SfxNavigatorWrapper::SfxNavigatorWrapper(vcl::Window* pParentWnd, sal_uInt16 nId) + : SfxChildWindow(pParentWnd , nId) +{ +} + +void SfxNavigatorWrapper::Initialize() +{ + SetHideNotDelete(true); +} + +SfxNavigator::SfxNavigator(SfxBindings* pBind , + SfxChildWindow* pChildWin , + vcl::Window* pParent, + SfxChildWinInfo* pInfo) + : SfxDockingWindow(pBind , + pChildWin , + pParent , + "Navigator", "sfx/ui/navigator.ui") +{ + SetText(SfxResId(STR_SID_NAVIGATOR)); + SetHelpId(HID_NAVIGATOR_WINDOW); + SetOutputSizePixel(Size(270, 240)); + Initialize(pInfo); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/newstyle.cxx b/sfx2/source/dialog/newstyle.cxx new file mode 100644 index 000000000..bc6fc7bab --- /dev/null +++ b/sfx2/source/dialog/newstyle.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <svl/style.hxx> + +#include <sfx2/newstyle.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/sfxresid.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +// Private methods ------------------------------------------------------ + +IMPL_LINK_NOARG(SfxNewStyleDlg, OKClickHdl, weld::Button&, void) +{ + const OUString aName(m_xColBox->get_active_text()); + SfxStyleSheetBase* pStyle = m_rPool.Find(aName, m_eSearchFamily); + if ( pStyle ) + { + if ( !pStyle->IsUserDefined() ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(STR_POOL_STYLE_NAME))); + xBox->run(); + return; + } + + if (RET_YES == m_xQueryOverwriteBox->run()) + m_xDialog->response(RET_OK); + } + else + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(SfxNewStyleDlg, OKHdl, weld::TreeView&, bool) +{ + OKClickHdl(*m_xOKBtn); + return true; +} + +IMPL_LINK(SfxNewStyleDlg, ModifyHdl, weld::ComboBox&, rBox, void) +{ + m_xOKBtn->set_sensitive(!rBox.get_active_text().replaceAll(" ", "").isEmpty()); +} + +SfxNewStyleDlg::SfxNewStyleDlg(weld::Widget* pParent, SfxStyleSheetBasePool& rInPool, SfxStyleFamily eFam) + : GenericDialogController(pParent, "sfx/ui/newstyle.ui", "CreateStyleDialog") + , m_rPool(rInPool) + , m_eSearchFamily(eFam) + , m_xColBox(m_xBuilder->weld_entry_tree_view("stylegrid", "stylename", "styles")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + , m_xQueryOverwriteBox(Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_QUERY_OVERWRITE))) +{ + m_xColBox->set_entry_width_chars(20); + m_xColBox->set_height_request_by_rows(8); + + m_xOKBtn->connect_clicked(LINK(this, SfxNewStyleDlg, OKClickHdl)); + m_xColBox->connect_changed(LINK(this, SfxNewStyleDlg, ModifyHdl)); + m_xColBox->connect_row_activated(LINK(this, SfxNewStyleDlg, OKHdl)); + + auto xIter = m_rPool.CreateIterator(eFam, SfxStyleSearchBits::UserDefined); + SfxStyleSheetBase *pStyle = xIter->First(); + while (pStyle) + { + m_xColBox->append_text(pStyle->GetName()); + pStyle = xIter->Next(); + } +} + +SfxNewStyleDlg::~SfxNewStyleDlg() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/partwnd.cxx b/sfx2/source/dialog/partwnd.cxx new file mode 100644 index 000000000..e387d2c5b --- /dev/null +++ b/sfx2/source/dialog/partwnd.cxx @@ -0,0 +1,174 @@ +/* -*- 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/beans/XPropertySet.hpp> +#include <com/sun/star/frame/Frame.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/uno/Reference.h> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> + +#include <toolkit/helper/vclunohelper.hxx> + +#include <vcl/event.hxx> +#include <sfx2/sfxsids.hrc> +#include <partwnd.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/frame.hxx> + + +// SfxPartChildWnd_Impl + + +SFX_IMPL_DOCKINGWINDOW( SfxPartChildWnd_Impl, SID_BROWSER ); + +SfxPartChildWnd_Impl::SfxPartChildWnd_Impl +( + vcl::Window* pParentWnd, + sal_uInt16 nId, + SfxBindings* pBindings, + SfxChildWinInfo* pInfo +) + : SfxChildWindow( pParentWnd, nId ) +{ + // Create Window + SetWindow(VclPtr<SfxPartDockWnd_Impl>::Create( pBindings, this, pParentWnd, WB_STDDOCKWIN | WB_CLIPCHILDREN | WB_SIZEABLE | WB_3DLOOK )); + SetAlignment(SfxChildAlignment::TOP); + + assert(pInfo); + pInfo->nFlags |= SfxChildWindowFlags::FORCEDOCK; + + static_cast<SfxDockingWindow*>(GetWindow())->SetFloatingSize( Size( 175, 175 ) ); + GetWindow()->SetSizePixel( Size( 175, 175 ) ); + + static_cast<SfxDockingWindow*>(GetWindow())->Initialize( pInfo ); + SetHideNotDelete( true ); +} + +SfxPartChildWnd_Impl::~SfxPartChildWnd_Impl() +{ + css::uno::Reference< css::frame::XFrame > xFrame = GetFrame(); + + // If xFrame=NULL release pMgr! Because this window lives longer then the manager! + // In these case we got a xFrame->dispose() call from outside ... and has release our + // frame reference in our own DisposingListener. + // But don't do it, if xFrame already exist. Then dispose() must come from inside ... + // and we need a valid pMgr for further operations ... + + SfxPartDockWnd_Impl* pWin = static_cast<SfxPartDockWnd_Impl*>(GetWindow()); + + if ( pWin && xFrame == pWin->GetBindings().GetActiveFrame() ) + pWin->GetBindings().SetActiveFrame( nullptr ); +} + +bool SfxPartChildWnd_Impl::QueryClose() +{ + return static_cast<SfxPartDockWnd_Impl*>(GetWindow())->QueryClose(); +} + + +// SfxPartDockWnd_Impl + + +SfxPartDockWnd_Impl::SfxPartDockWnd_Impl +( + SfxBindings* pBind, + SfxChildWindow* pChildWin, + vcl::Window* pParent, + WinBits nBits +) + : SfxDockingWindow( pBind, pChildWin, pParent, nBits ) +{ + css::uno::Reference < css::frame::XFrame2 > xFrame = css::frame::Frame::create( + ::comphelper::getProcessComponentContext() ); + xFrame->initialize( VCLUnoHelper::GetInterface ( this ) ); + + try + { + css::uno::Reference< css::beans::XPropertySet > xLMPropSet( xFrame->getLayoutManager(), css::uno::UNO_QUERY_THROW ); + + xLMPropSet->setPropertyValue( "AutomaticToolbars", css::uno::Any( false )); + } + catch( css::uno::RuntimeException& ) + { + throw; + } + catch( css::uno::Exception& ) + { + } + + pChildWin->SetFrame( css::uno::Reference<css::frame::XFrame>(xFrame,css::uno::UNO_QUERY_THROW) ); + if ( pBind->GetDispatcher() ) + { + css::uno::Reference < css::frame::XFramesSupplier > + xSupp ( pBind->GetDispatcher()->GetFrame()->GetFrame().GetFrameInterface(), css::uno::UNO_QUERY ); + if ( xSupp.is() ) + xSupp->getFrames()->append( css::uno::Reference<css::frame::XFrame>(xFrame, css::uno::UNO_QUERY_THROW) ); + } + else { + OSL_FAIL("Bindings without Dispatcher!"); + } +} + + +bool SfxPartDockWnd_Impl::QueryClose() +{ + bool bClose = true; + SfxChildWindow* pChild = GetChildWindow_Impl(); + if( pChild ) + { + css::uno::Reference< css::frame::XFrame > xFrame = pChild->GetFrame(); + if( xFrame.is() ) + { + css::uno::Reference< css::frame::XController > xCtrl = xFrame->getController(); + if( xCtrl.is() ) + bClose = xCtrl->suspend( true ); + } + } + + return bClose; +} + + +bool SfxPartDockWnd_Impl::EventNotify( NotifyEvent& rEvt ) +{ + if( rEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + { + SfxChildWindow* pChild = GetChildWindow_Impl(); + if( pChild ) + { + css::uno::Reference< css::frame::XFrame > xFrame = pChild->GetFrame(); + if( xFrame.is() ) + xFrame->activate(); + } + } + + return SfxDockingWindow::EventNotify( rEvt ); +} + +void SfxPartDockWnd_Impl::FillInfo( SfxChildWinInfo& rInfo ) const +{ + SfxDockingWindow::FillInfo( rInfo ); + rInfo.bVisible = false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/passwd.cxx b/sfx2/source/dialog/passwd.cxx new file mode 100644 index 000000000..13822c4a9 --- /dev/null +++ b/sfx2/source/dialog/passwd.cxx @@ -0,0 +1,209 @@ +/* -*- 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/passwd.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/strings.hrc> +#include <rtl/ustrbuf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +IMPL_LINK_NOARG(SfxPasswordDialog, EditModifyHdl, weld::Entry&, void) +{ + ModifyHdl(); +} + +void SfxPasswordDialog::ModifyHdl() +{ + bool bEnable = m_xPassword1ED->get_text().getLength() >= mnMinLen; + if (m_xPassword2ED->get_visible()) + bEnable = (bEnable && (m_xPassword2ED->get_text().getLength() >= mnMinLen)); + m_xOKBtn->set_sensitive(bEnable); +} + +IMPL_LINK(SfxPasswordDialog, InsertTextHdl, OUString&, rTest, bool) +{ + if (!mbAsciiOnly) + return true; + + const sal_Unicode* pTest = rTest.getStr(); + sal_Int32 nLen = rTest.getLength(); + OUStringBuffer aFilter(nLen); + bool bReset = false; + for (sal_Int32 i = 0; i < nLen; ++i) + { + if( *pTest > 0x007f ) + bReset = true; + else + aFilter.append(*pTest); + ++pTest; + } + + if (bReset) + { + rTest = aFilter.makeStringAndClear(); + // upgrade from "Normal" to "Warning" if a invalid letter was + // discarded + m_xOnlyAsciiFT->set_label_type(weld::LabelType::Warning); + } + + return true; +} + +IMPL_LINK_NOARG(SfxPasswordDialog, OKHdl, weld::Button&, void) +{ + bool bConfirmFailed = bool( mnExtras & SfxShowExtras::CONFIRM ) && + ( GetConfirm() != GetPassword() ); + if( ( mnExtras & SfxShowExtras::CONFIRM2 ) && ( m_xConfirm2ED->get_text() != GetPassword2() ) ) + bConfirmFailed = true; + if ( bConfirmFailed ) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), + VclMessageType::Warning, VclButtonsType::Ok, + SfxResId(STR_ERROR_WRONG_CONFIRM))); + xBox->run(); + m_xConfirm1ED->set_text(OUString()); + m_xConfirm1ED->grab_focus(); + } + else + m_xDialog->response(RET_OK); +} + +// CTOR / DTOR ----------------------------------------------------------- + +SfxPasswordDialog::SfxPasswordDialog(weld::Widget* pParent, const OUString* pGroupText) + : GenericDialogController(pParent, "sfx/ui/password.ui", "PasswordDialog") + , m_xPassword1Box(m_xBuilder->weld_frame("password1frame")) + , m_xUserFT(m_xBuilder->weld_label("userft")) + , m_xUserED(m_xBuilder->weld_entry("usered")) + , m_xPassword1FT(m_xBuilder->weld_label("pass1ft")) + , m_xPassword1ED(m_xBuilder->weld_entry("pass1ed")) + , m_xConfirm1FT(m_xBuilder->weld_label("confirm1ft")) + , m_xConfirm1ED(m_xBuilder->weld_entry("confirm1ed")) + , m_xPassword2Box(m_xBuilder->weld_frame("password2frame")) + , m_xPassword2FT(m_xBuilder->weld_label("pass2ft")) + , m_xPassword2ED(m_xBuilder->weld_entry("pass2ed")) + , m_xConfirm2FT(m_xBuilder->weld_label("confirm2ft")) + , m_xConfirm2ED(m_xBuilder->weld_entry("confirm2ed")) + , m_xMinLengthFT(m_xBuilder->weld_label("minlenft")) + , m_xOnlyAsciiFT(m_xBuilder->weld_label("onlyascii")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + , maMinLenPwdStr(SfxResId(STR_PASSWD_MIN_LEN)) + , maMinLenPwdStr1(SfxResId(STR_PASSWD_MIN_LEN1)) + , maEmptyPwdStr(SfxResId(STR_PASSWD_EMPTY)) + , mnMinLen(5) + , mnExtras(SfxShowExtras::NONE) + , mbAsciiOnly(false) +{ + Link<weld::Entry&,void> aLink = LINK(this, SfxPasswordDialog, EditModifyHdl); + m_xPassword1ED->connect_changed(aLink); + m_xPassword2ED->connect_changed(aLink); + Link<OUString&,bool> aLink2 = LINK(this, SfxPasswordDialog, InsertTextHdl); + m_xPassword1ED->connect_insert_text(aLink2); + m_xPassword2ED->connect_insert_text(aLink2); + m_xConfirm1ED->connect_insert_text(aLink2); + m_xConfirm2ED->connect_insert_text(aLink2); + m_xOKBtn->connect_clicked(LINK(this, SfxPasswordDialog, OKHdl)); + + if (pGroupText) + m_xPassword1Box->set_label(*pGroupText); + + //set the text to the password length + SetPasswdText(); +} + +void SfxPasswordDialog::SetPasswdText( ) +{ + //set the new string to the minimum password length + if (mnMinLen == 0) + m_xMinLengthFT->set_label(maEmptyPwdStr); + else + { + if( mnMinLen == 1 ) + m_xMinLengthFT->set_label(maMinLenPwdStr1); + else + { + maMainPwdStr = maMinLenPwdStr; + maMainPwdStr = maMainPwdStr.replaceAll( "$(MINLEN)", OUString::number(static_cast<sal_Int32>(mnMinLen) ) ); + m_xMinLengthFT->set_label(maMainPwdStr); + } + } +} + + +void SfxPasswordDialog::SetMinLen( sal_uInt16 nLen ) +{ + mnMinLen = nLen; + SetPasswdText(); + ModifyHdl(); +} + +void SfxPasswordDialog::ShowMinLengthText(bool bShow) +{ + m_xMinLengthFT->set_visible(bShow); +} + +void SfxPasswordDialog::AllowAsciiOnly() +{ + mbAsciiOnly = true; + m_xOnlyAsciiFT->show(); +} + +short SfxPasswordDialog::run() +{ + m_xUserFT->hide(); + m_xUserED->hide(); + m_xConfirm1FT->hide(); + m_xConfirm1ED->hide(); + m_xPassword1FT->hide(); + m_xPassword2Box->hide(); + m_xPassword2FT->hide(); + m_xPassword2ED->hide(); + m_xPassword2FT->hide(); + m_xConfirm2FT->hide(); + m_xConfirm2ED->hide(); + + if (mnExtras != SfxShowExtras::NONE) + m_xPassword1FT->show(); + if (mnExtras & SfxShowExtras::USER) + { + m_xUserFT->show(); + m_xUserED->show(); + } + if (mnExtras & SfxShowExtras::CONFIRM) + { + m_xConfirm1FT->show(); + m_xConfirm1ED->show(); + } + if (mnExtras & SfxShowExtras::PASSWORD2) + { + m_xPassword2Box->show(); + m_xPassword2FT->show(); + m_xPassword2ED->show(); + } + if (mnExtras & SfxShowExtras::CONFIRM2) + { + m_xConfirm2FT->show(); + m_xConfirm2ED->show(); + } + + return GenericDialogController::run(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/printopt.cxx b/sfx2/source/dialog/printopt.cxx new file mode 100644 index 000000000..8d16edfc0 --- /dev/null +++ b/sfx2/source/dialog/printopt.cxx @@ -0,0 +1,290 @@ +/* -*- 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/macros.h> +#include <officecfg/Office/Common.hxx> +#include <svtools/printoptions.hxx> +#include <svtools/restartdialog.hxx> + +#include <comphelper/processfactory.hxx> + +#include <sfx2/printopt.hxx> + +static sal_uInt16 aDPIArray[] = { 72, 96, 150, 200, 300, 600 }; +static bool bOutputForPrinter = true; + +#define DPI_COUNT SAL_N_ELEMENTS(aDPIArray) + +SfxCommonPrintOptionsTabPage::SfxCommonPrintOptionsTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) + : SfxTabPage(pPage, pController, "sfx/ui/optprintpage.ui", "OptPrintPage", &rSet) + , m_xPrinterOutputRB(m_xBuilder->weld_radio_button("printer")) + , m_xPrintFileOutputRB(m_xBuilder->weld_radio_button("file")) + , m_xReduceTransparencyCB(m_xBuilder->weld_check_button("reducetrans")) + , m_xReduceTransparencyAutoRB(m_xBuilder->weld_radio_button("reducetransauto")) + , m_xReduceTransparencyNoneRB(m_xBuilder->weld_radio_button("reducetransnone")) + , m_xReduceGradientsCB(m_xBuilder->weld_check_button("reducegrad")) + , m_xReduceGradientsStripesRB(m_xBuilder->weld_radio_button("reducegradstripes")) + , m_xReduceGradientsColorRB(m_xBuilder->weld_radio_button("reducegradcolor")) + , m_xReduceGradientsStepCountNF(m_xBuilder->weld_spin_button("reducegradstep")) + , m_xReduceBitmapsCB(m_xBuilder->weld_check_button("reducebitmap")) + , m_xReduceBitmapsOptimalRB(m_xBuilder->weld_radio_button("reducebitmapoptimal")) + , m_xReduceBitmapsNormalRB(m_xBuilder->weld_radio_button("reducebitmapnormal")) + , m_xReduceBitmapsResolutionRB(m_xBuilder->weld_radio_button("reducebitmapresol")) + , m_xReduceBitmapsResolutionLB(m_xBuilder->weld_combo_box("reducebitmapdpi")) + , m_xReduceBitmapsTransparencyCB(m_xBuilder->weld_check_button("reducebitmaptrans")) + , m_xConvertToGreyscalesCB(m_xBuilder->weld_check_button("converttogray")) + , m_xPDFCB(m_xBuilder->weld_check_button("pdf")) + , m_xPaperSizeCB(m_xBuilder->weld_check_button("papersize")) + , m_xPaperOrientationCB(m_xBuilder->weld_check_button("paperorient")) + , m_xTransparencyCB(m_xBuilder->weld_check_button("trans")) +{ +#ifndef ENABLE_CUPS + m_xPDFCB->hide(); +#endif + + if( bOutputForPrinter ) + { + m_xPrinterOutputRB->set_active(true); + } + else + { + m_xPrintFileOutputRB->set_active(true); + m_xPDFCB->set_sensitive(false); + } + + m_xPrinterOutputRB->connect_toggled( LINK( this, SfxCommonPrintOptionsTabPage, ToggleOutputPrinterRBHdl ) ); + m_xPrintFileOutputRB->connect_toggled( LINK( this, SfxCommonPrintOptionsTabPage, ToggleOutputPrintFileRBHdl ) ); + + m_xReduceTransparencyCB->connect_toggled( LINK( this, SfxCommonPrintOptionsTabPage, ClickReduceTransparencyCBHdl ) ); + m_xReduceGradientsCB->connect_toggled( LINK( this, SfxCommonPrintOptionsTabPage, ClickReduceGradientsCBHdl ) ); + m_xReduceBitmapsCB->connect_toggled( LINK( this, SfxCommonPrintOptionsTabPage, ClickReduceBitmapsCBHdl ) ); + + m_xReduceGradientsStripesRB->connect_toggled( LINK( this, SfxCommonPrintOptionsTabPage, ToggleReduceGradientsStripesRBHdl ) ); + m_xReduceBitmapsResolutionRB->connect_toggled( LINK( this, SfxCommonPrintOptionsTabPage, ToggleReduceBitmapsResolutionRBHdl ) ); +} + +SfxCommonPrintOptionsTabPage::~SfxCommonPrintOptionsTabPage() +{ +} + +std::unique_ptr<SfxTabPage> SfxCommonPrintOptionsTabPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rAttrSet) +{ + return std::make_unique<SfxCommonPrintOptionsTabPage>(pPage, pController, *rAttrSet); +} + +bool SfxCommonPrintOptionsTabPage::FillItemSet( SfxItemSet* /*rSet*/ ) +{ + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + + if( m_xPaperSizeCB->get_state_changed_from_saved()) + officecfg::Office::Common::Print::Warning::PaperSize::set(m_xPaperSizeCB->get_active(), batch); + if( m_xPaperOrientationCB->get_state_changed_from_saved() ) + officecfg::Office::Common::Print::Warning::PaperOrientation::set(m_xPaperOrientationCB->get_active(), batch); + if( m_xTransparencyCB->get_state_changed_from_saved() ) + officecfg::Office::Common::Print::Warning::Transparency::set(m_xTransparencyCB->get_active(), batch); + + batch->commit(); + + ImplSaveControls( m_xPrinterOutputRB->get_active() ? &maPrinterOptions : &maPrintFileOptions ); + + svtools::SetPrinterOptions(maPrinterOptions, /*bFile*/false); + svtools::SetPrinterOptions(maPrintFileOptions, /*bFile*/true); + + return false; +} + +void SfxCommonPrintOptionsTabPage::Reset( const SfxItemSet* /*rSet*/ ) +{ + m_xPaperSizeCB->set_active(officecfg::Office::Common::Print::Warning::PaperSize::get()); + m_xPaperOrientationCB->set_active(officecfg::Office::Common::Print::Warning::PaperOrientation::get()); + m_xTransparencyCB->set_active(officecfg::Office::Common::Print::Warning::Transparency::get()); + + m_xPaperSizeCB->save_state(); + m_xPaperOrientationCB->save_state(); + m_xTransparencyCB->save_state(); + + svtools::GetPrinterOptions( maPrinterOptions, /*bFile*/false ); + svtools::GetPrinterOptions( maPrintFileOptions, /*bFile*/true ); + if(m_xPrintFileOutputRB->get_active()){ + m_xPrinterOutputRB->set_active(true); + } + + ImplUpdateControls( m_xPrinterOutputRB->get_active() ? &maPrinterOptions : &maPrintFileOptions ); +} + +DeactivateRC SfxCommonPrintOptionsTabPage::DeactivatePage( SfxItemSet* pItemSet ) +{ + if( pItemSet ) + FillItemSet( pItemSet ); + + return DeactivateRC::LeavePage; +} + +void SfxCommonPrintOptionsTabPage::ImplUpdateControls( const vcl::printer::Options* pCurrentOptions ) +{ + m_xReduceTransparencyCB->set_active( pCurrentOptions->IsReduceTransparency() ); + + if( pCurrentOptions->GetReducedTransparencyMode() == vcl::printer::TransparencyMode::Auto ) + m_xReduceTransparencyAutoRB->set_active(true); + else + m_xReduceTransparencyNoneRB->set_active(true); + + m_xReduceGradientsCB->set_active( pCurrentOptions->IsReduceGradients() ); + + if( pCurrentOptions->GetReducedGradientMode() == vcl::printer::GradientMode::Stripes ) + m_xReduceGradientsStripesRB->set_active(true); + else + m_xReduceGradientsColorRB->set_active(true); + + m_xReduceGradientsStepCountNF->set_value(pCurrentOptions->GetReducedGradientStepCount()); + + m_xReduceBitmapsCB->set_active( pCurrentOptions->IsReduceBitmaps() ); + + if( pCurrentOptions->GetReducedBitmapMode() == vcl::printer::BitmapMode::Optimal ) + m_xReduceBitmapsOptimalRB->set_active(true); + else if( pCurrentOptions->GetReducedBitmapMode() == vcl::printer::BitmapMode::Normal ) + m_xReduceBitmapsNormalRB->set_active(true); + else + m_xReduceBitmapsResolutionRB->set_active(true); + + const sal_uInt16 nDPI = pCurrentOptions->GetReducedBitmapResolution(); + + if( nDPI < aDPIArray[ 0 ] ) + m_xReduceBitmapsResolutionLB->set_active(0); + else + { + for( int i = DPI_COUNT - 1; i >= 0; i-- ) + { + if( nDPI >= aDPIArray[ i ] ) + { + m_xReduceBitmapsResolutionLB->set_active(i); + i = -1; + } + } + } + + m_xReduceBitmapsTransparencyCB->set_active( pCurrentOptions->IsReducedBitmapIncludesTransparency() ); + m_xConvertToGreyscalesCB->set_active( pCurrentOptions->IsConvertToGreyscales() ); + m_xPDFCB->set_active( pCurrentOptions->IsPDFAsStandardPrintJobFormat() ); + + ClickReduceTransparencyCBHdl(*m_xReduceTransparencyCB); + ClickReduceGradientsCBHdl(*m_xReduceGradientsCB); + ClickReduceBitmapsCBHdl(*m_xReduceBitmapsCB); +} + +void SfxCommonPrintOptionsTabPage::ImplSaveControls( vcl::printer::Options* pCurrentOptions ) +{ + pCurrentOptions->SetReduceTransparency( m_xReduceTransparencyCB->get_active() ); + pCurrentOptions->SetReducedTransparencyMode( m_xReduceTransparencyAutoRB->get_active() ? vcl::printer::TransparencyMode::Auto : vcl::printer::TransparencyMode::NONE ); + pCurrentOptions->SetReduceGradients( m_xReduceGradientsCB->get_active() ); + pCurrentOptions->SetReducedGradientMode( m_xReduceGradientsStripesRB->get_active() ? vcl::printer::GradientMode::Stripes : vcl::printer::GradientMode::Color ); + pCurrentOptions->SetReducedGradientStepCount(m_xReduceGradientsStepCountNF->get_value()); + pCurrentOptions->SetReduceBitmaps( m_xReduceBitmapsCB->get_active() ); + pCurrentOptions->SetReducedBitmapMode( m_xReduceBitmapsOptimalRB->get_active() ? vcl::printer::BitmapMode::Optimal : + ( m_xReduceBitmapsNormalRB->get_active() ? vcl::printer::BitmapMode::Normal : vcl::printer::BitmapMode::Resolution ) ); + pCurrentOptions->SetReducedBitmapResolution( aDPIArray[ std::min<sal_uInt16>( m_xReduceBitmapsResolutionLB->get_active(), + SAL_N_ELEMENTS(aDPIArray) - 1 ) ] ); + pCurrentOptions->SetReducedBitmapIncludesTransparency( m_xReduceBitmapsTransparencyCB->get_active() ); + pCurrentOptions->SetConvertToGreyscales( m_xConvertToGreyscalesCB->get_active() ); + bool bOrigBackEnd = pCurrentOptions->IsPDFAsStandardPrintJobFormat(); + if (bOrigBackEnd != m_xPDFCB->get_active()) + { + pCurrentOptions->SetPDFAsStandardPrintJobFormat( m_xPDFCB->get_active() ); + svtools::executeRestartDialog( + comphelper::getProcessComponentContext(), nullptr, + svtools::RESTART_REASON_PDF_AS_STANDARD_JOB_FORMAT); + } +} + +IMPL_LINK_NOARG( SfxCommonPrintOptionsTabPage, ClickReduceTransparencyCBHdl, weld::Toggleable&, void ) +{ + const bool bReduceTransparency = m_xReduceTransparencyCB->get_active(); + + m_xReduceTransparencyAutoRB->set_sensitive( bReduceTransparency ); + m_xReduceTransparencyNoneRB->set_sensitive( bReduceTransparency ); + + m_xTransparencyCB->set_sensitive( !bReduceTransparency ); +} + +IMPL_LINK_NOARG( SfxCommonPrintOptionsTabPage, ClickReduceGradientsCBHdl, weld::Toggleable&, void ) +{ + const bool bEnable = m_xReduceGradientsCB->get_active(); + + m_xReduceGradientsStripesRB->set_sensitive( bEnable ); + m_xReduceGradientsColorRB->set_sensitive( bEnable ); + m_xReduceGradientsStepCountNF->set_sensitive( bEnable ); + + ToggleReduceGradientsStripesRBHdl(*m_xReduceGradientsStripesRB); +} + +IMPL_LINK_NOARG( SfxCommonPrintOptionsTabPage, ClickReduceBitmapsCBHdl, weld::Toggleable&, void ) +{ + const bool bEnable = m_xReduceBitmapsCB->get_active(); + + m_xReduceBitmapsOptimalRB->set_sensitive( bEnable ); + m_xReduceBitmapsNormalRB->set_sensitive( bEnable ); + m_xReduceBitmapsResolutionRB->set_sensitive( bEnable ); + m_xReduceBitmapsTransparencyCB->set_sensitive( bEnable ); + m_xReduceBitmapsResolutionLB->set_sensitive( bEnable ); + + ToggleReduceBitmapsResolutionRBHdl(*m_xReduceBitmapsResolutionRB); +} + +IMPL_LINK_NOARG( SfxCommonPrintOptionsTabPage, ToggleReduceGradientsStripesRBHdl, weld::Toggleable&, void ) +{ + const bool bEnable = m_xReduceGradientsCB->get_active() && m_xReduceGradientsStripesRB->get_active(); + + m_xReduceGradientsStepCountNF->set_sensitive(bEnable); +} + +IMPL_LINK_NOARG( SfxCommonPrintOptionsTabPage, ToggleReduceBitmapsResolutionRBHdl, weld::Toggleable&, void ) +{ + const bool bEnable = m_xReduceBitmapsCB->get_active() && m_xReduceBitmapsResolutionRB->get_active(); + + m_xReduceBitmapsResolutionLB->set_sensitive(bEnable); +} + +IMPL_LINK( SfxCommonPrintOptionsTabPage, ToggleOutputPrinterRBHdl, weld::Toggleable&, rButton, void ) +{ + if (rButton.get_active()) + { + ImplUpdateControls( &maPrinterOptions ); + bOutputForPrinter = true; + } + else + ImplSaveControls( &maPrinterOptions ); +} + +IMPL_LINK( SfxCommonPrintOptionsTabPage, ToggleOutputPrintFileRBHdl, weld::Toggleable&, rButton, void ) +{ + if (rButton.get_active()) + { + ImplUpdateControls( &maPrintFileOptions ); + bOutputForPrinter = false; + m_xPDFCB->set_sensitive(false); + } + else + { + ImplSaveControls( &maPrintFileOptions ); + m_xPDFCB->set_sensitive(true); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/recfloat.cxx b/sfx2/source/dialog/recfloat.cxx new file mode 100644 index 000000000..1dcbb2f7c --- /dev/null +++ b/sfx2/source/dialog/recfloat.cxx @@ -0,0 +1,145 @@ +/* -*- 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/awt/XTopWindow.hpp> +#include <com/sun/star/frame/XDispatchRecorder.hpp> + +#include <svl/eitem.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/windowstate.hxx> + +#include <recfloat.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/sfxresid.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <sfx2/viewsh.hxx> + +SFX_IMPL_MODELESSDIALOGCONTOLLER(SfxRecordingFloatWrapper_Impl, SID_RECORDING_FLOATWINDOW); + +SfxRecordingFloatWrapper_Impl::SfxRecordingFloatWrapper_Impl(vcl::Window* pParentWnd, + sal_uInt16 nId, + SfxBindings* pBind, + SfxChildWinInfo const * pInfo) + : SfxChildWindow(pParentWnd, nId) + , pBindings(pBind) +{ + SetController(std::make_shared<SfxRecordingFloat_Impl>(pBindings, this, pParentWnd->GetFrameWeld())); + SetWantsFocus(false); + SfxRecordingFloat_Impl* pFloatDlg = static_cast<SfxRecordingFloat_Impl*>(GetController().get()); + + weld::Dialog* pDlg = pFloatDlg->getDialog(); + + SfxViewFrame *pFrame = pBind->GetDispatcher_Impl()->GetFrame(); + vcl::Window* pEditWin = pFrame->GetViewShell()->GetWindow(); + + Point aPos = pEditWin->OutputToScreenPixel( pEditWin->GetPosPixel() ); + aPos.AdjustX(20); + aPos.AdjustY(10); + + WindowStateData aState; + aState.SetMask(WindowStateMask::Pos); + aState.SetX(aPos.X()); + aState.SetY(aPos.Y()); + pDlg->set_window_state(aState.ToStr()); + + pFloatDlg->Initialize(pInfo); +} + +SfxRecordingFloatWrapper_Impl::~SfxRecordingFloatWrapper_Impl() +{ + SfxBoolItem aItem( FN_PARAM_1, true ); + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder = pBindings->GetRecorder(); + if ( xRecorder.is() ) + pBindings->GetDispatcher()->ExecuteList(SID_STOP_RECORDING, + SfxCallMode::SYNCHRON, { &aItem }); +} + +bool SfxRecordingFloatWrapper_Impl::QueryClose() +{ + // asking for recorded macro should be replaced if index access is available! + bool bRet = true; + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder = pBindings->GetRecorder(); + if ( xRecorder.is() && !xRecorder->getRecordedMacro().isEmpty() ) + { + SfxRecordingFloat_Impl* pFloatDlg = static_cast<SfxRecordingFloat_Impl*>(GetController().get()); + weld::Dialog* pDlg = pFloatDlg->getDialog(); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pDlg, + VclMessageType::Question, VclButtonsType::YesNo, + SfxResId(STR_MACRO_LOSS))); + xQueryBox->set_default_response(RET_NO); + + xQueryBox->set_title(SfxResId(STR_CANCEL_RECORDING)); + bRet = (xQueryBox->run() == RET_YES); + } + + return bRet; +} + +SfxRecordingFloat_Impl::SfxRecordingFloat_Impl(SfxBindings* pBind, SfxChildWindow* pChildWin, + weld::Window* pParent) + : SfxModelessDialogController(pBind, pChildWin, pParent, "sfx/ui/floatingrecord.ui", + "FloatingRecord") + , m_xToolbar(m_xBuilder->weld_toolbar("toolbar")) + , m_xDispatcher(new ToolbarUnoDispatcher(*m_xToolbar, *m_xBuilder, pBind->GetActiveFrame())) + , mnPostUserEventId(nullptr) + , m_bFirstActivate(true) +{ + // start recording + SfxBoolItem aItem( SID_RECORDMACRO, true ); + GetBindings().GetDispatcher()->ExecuteList(SID_RECORDMACRO, + SfxCallMode::SYNCHRON, { &aItem }); +} + +IMPL_LINK_NOARG(SfxRecordingFloat_Impl, PresentParentFrame, void*, void) +{ + mnPostUserEventId = nullptr; + css::uno::Reference<css::awt::XTopWindow> xTopWindow(m_xDispatcher->GetFrame()->getContainerWindow(), css::uno::UNO_QUERY); + if (xTopWindow.is()) + xTopWindow->toFront(); +} + +void SfxRecordingFloat_Impl::Activate() +{ + SfxModelessDialogController::Activate(); + if (!m_bFirstActivate) + return; + // tdf#147782 retain focus in launching frame on the first activate on automatically gaining focus on getting launched + m_bFirstActivate = false; + mnPostUserEventId = Application::PostUserEvent(LINK(this, SfxRecordingFloat_Impl, PresentParentFrame)); +} + +SfxRecordingFloat_Impl::~SfxRecordingFloat_Impl() +{ + if (mnPostUserEventId) + Application::RemoveUserEvent(mnPostUserEventId); + m_xDispatcher->dispose(); +} + +void SfxRecordingFloat_Impl::FillInfo( SfxChildWinInfo& rInfo ) const +{ + SfxModelessDialogController::FillInfo( rInfo ); + rInfo.bVisible = false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/securitypage.cxx b/sfx2/source/dialog/securitypage.cxx new file mode 100644 index 000000000..a07eb4ace --- /dev/null +++ b/sfx2/source/dialog/securitypage.cxx @@ -0,0 +1,451 @@ +/* -*- 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/htmlmode.hxx> + +#include <sfx2/sfxresid.hxx> + +#include <sfx2/sfxsids.hrc> +#include <sfx2/objsh.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/passwd.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svl/eitem.hxx> +#include <svl/poolitem.hxx> +#include <svl/intitem.hxx> +#include <svl/PasswordHelper.hxx> +#include <comphelper/docpasswordhelper.hxx> + +#include <sfx2/strings.hrc> + +#include "securitypage.hxx" + +using namespace ::com::sun::star; + +namespace +{ + enum RedliningMode { RL_NONE, RL_WRITER, RL_CALC }; + + bool QueryState( TypedWhichId<SfxBoolItem> _nSlot, bool& _rValue ) + { + bool bRet = false; + SfxViewShell* pViewSh = SfxViewShell::Current(); + if (pViewSh) + { + const SfxBoolItem* pItem; + SfxDispatcher* pDisp = pViewSh->GetDispatcher(); + SfxItemState nState = pDisp->QueryState( _nSlot, pItem ); + bRet = SfxItemState::DEFAULT <= nState; + if (bRet) + _rValue = pItem->GetValue(); + } + return bRet; + } + + + bool QueryRecordChangesProtectionState( RedliningMode _eMode, bool& _rValue ) + { + bool bRet = false; + if (_eMode != RL_NONE) + { + TypedWhichId<SfxBoolItem> nSlot = _eMode == RL_WRITER ? FN_REDLINE_PROTECT : SID_CHG_PROTECT; + bRet = QueryState( nSlot, _rValue ); + } + return bRet; + } + + + bool QueryRecordChangesState( RedliningMode _eMode, bool& _rValue ) + { + bool bRet = false; + if (_eMode != RL_NONE) + { + TypedWhichId<SfxBoolItem> nSlot = _eMode == RL_WRITER ? FN_REDLINE_ON : FID_CHG_RECORD; + bRet = QueryState( nSlot, _rValue ); + } + return bRet; + } +} + + +static bool lcl_GetPassword( + weld::Window *pParent, + bool bProtect, + /*out*/OUString &rPassword ) +{ + bool bRes = false; + SfxPasswordDialog aPasswdDlg(pParent); + aPasswdDlg.SetMinLen(1); + if (bProtect) + aPasswdDlg.ShowExtras( SfxShowExtras::CONFIRM ); + if (RET_OK == aPasswdDlg.run() && !aPasswdDlg.GetPassword().isEmpty()) + { + rPassword = aPasswdDlg.GetPassword(); + bRes = true; + } + return bRes; +} + + +static bool lcl_IsPasswordCorrect( std::u16string_view rPassword ) +{ + SfxObjectShell* pCurDocShell = SfxObjectShell::Current(); + if (!pCurDocShell) + return false; + + bool bRes = false; + uno::Sequence< sal_Int8 > aPasswordHash; + pCurDocShell->GetProtectionHash( aPasswordHash ); + + // check if supplied password was correct + if (aPasswordHash.getLength() == 1 && aPasswordHash[0] == 1) + { + // dummy RedlinePassword from OOXML import: get real password info + // from the grab-bag to verify the password + const css::uno::Sequence< css::beans::PropertyValue > aDocumentProtection = + pCurDocShell->GetDocumentProtectionFromGrabBag(); + bRes = + // password is ok, if there is no DocumentProtection in the GrabBag, + // i.e. the dummy RedlinePassword imported from an OpenDocument file + !aDocumentProtection.hasElements() || + // verify password with the password info imported from OOXML + ::comphelper::DocPasswordHelper::IsModifyPasswordCorrect( rPassword, + ::comphelper::DocPasswordHelper::ConvertPasswordInfo ( aDocumentProtection ) ); + } + else + { + uno::Sequence< sal_Int8 > aNewPasswd( aPasswordHash ); + SvPasswordHelper::GetHashPassword( aNewPasswd, rPassword ); + bRes = SvPasswordHelper::CompareHashPassword( aPasswordHash, rPassword ); + } + + if ( !bRes ) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + SfxResId(RID_SVXSTR_INCORRECT_PASSWORD))); + xInfoBox->run(); + } + + return bRes; +} + +struct SfxSecurityPage_Impl +{ + SfxSecurityPage & m_rMyTabPage; + + RedliningMode m_eRedlingMode; // for record changes + + bool m_bOrigPasswordIsConfirmed; + bool m_bNewPasswordIsValid; + OUString m_aNewPassword; + + OUString m_aEndRedliningWarning; + bool m_bEndRedliningWarningDone; + + std::unique_ptr<weld::CheckButton> m_xOpenReadonlyCB; + std::unique_ptr<weld::CheckButton> m_xRecordChangesCB; // for record changes + std::unique_ptr<weld::Button> m_xProtectPB; // for record changes + std::unique_ptr<weld::Button> m_xUnProtectPB; // for record changes + + DECL_LINK(RecordChangesCBToggleHdl, weld::Toggleable&, void); + DECL_LINK(ChangeProtectionPBHdl, weld::Button&, void); + + SfxSecurityPage_Impl( SfxSecurityPage &rDlg ); + + bool FillItemSet_Impl(); + void Reset_Impl(); +}; + +SfxSecurityPage_Impl::SfxSecurityPage_Impl(SfxSecurityPage &rTabPage) + : m_rMyTabPage(rTabPage) + , m_eRedlingMode(RL_NONE) + , m_bOrigPasswordIsConfirmed(false) + , m_bNewPasswordIsValid(false) + , m_aEndRedliningWarning(SfxResId(RID_SVXSTR_END_REDLINING_WARNING)) + , m_bEndRedliningWarningDone(false) + , m_xOpenReadonlyCB(rTabPage.GetBuilder().weld_check_button("readonly")) + , m_xRecordChangesCB(rTabPage.GetBuilder().weld_check_button("recordchanges")) + , m_xProtectPB(rTabPage.GetBuilder().weld_button("protect")) + , m_xUnProtectPB(rTabPage.GetBuilder().weld_button("unprotect")) +{ + m_xProtectPB->show(); + m_xUnProtectPB->hide(); + + m_xRecordChangesCB->connect_toggled(LINK(this, SfxSecurityPage_Impl, RecordChangesCBToggleHdl)); + m_xProtectPB->connect_clicked(LINK(this, SfxSecurityPage_Impl, ChangeProtectionPBHdl)); + m_xUnProtectPB->connect_clicked(LINK(this, SfxSecurityPage_Impl, ChangeProtectionPBHdl)); +} + +bool SfxSecurityPage_Impl::FillItemSet_Impl() +{ + bool bModified = false; + + SfxObjectShell* pCurDocShell = SfxObjectShell::Current(); + if (pCurDocShell && !pCurDocShell->IsReadOnly()) + { + if (m_eRedlingMode != RL_NONE ) + { + const bool bDoRecordChanges = m_xRecordChangesCB->get_active(); + const bool bDoChangeProtection = m_xUnProtectPB->get_visible(); + + // sanity checks + DBG_ASSERT( bDoRecordChanges || !bDoChangeProtection, "no change recording should imply no change protection" ); + DBG_ASSERT( bDoChangeProtection || !bDoRecordChanges, "no change protection should imply no change recording" ); + DBG_ASSERT( !bDoChangeProtection || !m_aNewPassword.isEmpty(), "change protection should imply password length is > 0" ); + DBG_ASSERT( bDoChangeProtection || m_aNewPassword.isEmpty(), "no change protection should imply password length is 0" ); + + // change recording + if (bDoRecordChanges != pCurDocShell->IsChangeRecording()) + { + pCurDocShell->SetChangeRecording( bDoRecordChanges ); + bModified = true; + } + + // change record protection + if (m_bNewPasswordIsValid && + bDoChangeProtection != pCurDocShell->HasChangeRecordProtection()) + { + DBG_ASSERT( !bDoChangeProtection || bDoRecordChanges, + "change protection requires record changes to be active!" ); + pCurDocShell->SetProtectionPassword( m_aNewPassword ); + bModified = true; + } + } + + // open read-only? + const bool bDoOpenReadonly = m_xOpenReadonlyCB->get_active(); + if (bDoOpenReadonly != pCurDocShell->IsSecurityOptOpenReadOnly()) + { + pCurDocShell->SetSecurityOptOpenReadOnly( bDoOpenReadonly ); + bModified = true; + } + } + + return bModified; +} + + +void SfxSecurityPage_Impl::Reset_Impl() +{ + SfxObjectShell* pCurDocShell = SfxObjectShell::Current(); + + if (!pCurDocShell) + { + // no doc -> hide document settings + m_xOpenReadonlyCB->set_sensitive(false); + m_xRecordChangesCB->set_sensitive(false); + m_xProtectPB->show(); + m_xProtectPB->set_sensitive(false); + m_xUnProtectPB->hide(); + m_xUnProtectPB->set_sensitive(false); + } + else + { + bool bIsHTMLDoc = false; + bool bProtect = true, bUnProtect = false; + SfxViewShell* pViewSh = SfxViewShell::Current(); + if (pViewSh) + { + const SfxUInt16Item* pItem; + SfxDispatcher* pDisp = pViewSh->GetDispatcher(); + if (SfxItemState::DEFAULT <= pDisp->QueryState( SID_HTML_MODE, pItem )) + { + sal_uInt16 nMode = pItem->GetValue(); + bIsHTMLDoc = ( ( nMode & HTMLMODE_ON ) != 0 ); + } + } + + bool bIsReadonly = pCurDocShell->IsReadOnly(); + if (!bIsHTMLDoc) + { + m_xOpenReadonlyCB->set_active(pCurDocShell->IsSecurityOptOpenReadOnly()); + m_xOpenReadonlyCB->set_sensitive(!bIsReadonly); + } + else + m_xOpenReadonlyCB->set_sensitive(false); + + bool bRecordChanges; + if (QueryRecordChangesState( RL_WRITER, bRecordChanges ) && !bIsHTMLDoc) + m_eRedlingMode = RL_WRITER; + else if (QueryRecordChangesState( RL_CALC, bRecordChanges )) + m_eRedlingMode = RL_CALC; + else + m_eRedlingMode = RL_NONE; + + if (m_eRedlingMode != RL_NONE) + { + bool bProtection(false); + QueryRecordChangesProtectionState( m_eRedlingMode, bProtection ); + + m_xProtectPB->set_sensitive(!bIsReadonly); + m_xUnProtectPB->set_sensitive(!bIsReadonly); + // set the right text + if (bProtection) + { + bProtect = false; + bUnProtect = true; + } + + m_xRecordChangesCB->set_active(bRecordChanges); + m_xRecordChangesCB->set_sensitive(/*!bProtection && */!bIsReadonly); + + m_bOrigPasswordIsConfirmed = true; // default case if no password is set + uno::Sequence< sal_Int8 > aPasswordHash; + // check if password is available + if (pCurDocShell->GetProtectionHash( aPasswordHash ) && + aPasswordHash.hasElements()) + m_bOrigPasswordIsConfirmed = false; // password found, needs to be confirmed later on + } + else + { + // A Calc document that is shared will have 'm_eRedlingMode == RL_NONE' + // In shared documents change recording and protection must be disabled, + // similar to documents that do not support change recording at all. + m_xRecordChangesCB->set_active(false); + m_xRecordChangesCB->set_sensitive(false); + m_xProtectPB->set_sensitive(false); + m_xUnProtectPB->set_sensitive(false); + } + + m_xProtectPB->set_visible(bProtect); + m_xUnProtectPB->set_visible(bUnProtect); + } +} + +IMPL_LINK_NOARG(SfxSecurityPage_Impl, RecordChangesCBToggleHdl, weld::Toggleable&, void) +{ + // when change recording gets disabled protection must be disabled as well + if (m_xRecordChangesCB->get_active()) // the new check state is already present, thus the '!' + return; + + bool bAlreadyDone = false; + if (!m_bEndRedliningWarningDone) + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(m_rMyTabPage.GetFrameWeld(), + VclMessageType::Warning, VclButtonsType::YesNo, + m_aEndRedliningWarning)); + xWarn->set_default_response(RET_NO); + if (xWarn->run() != RET_YES) + bAlreadyDone = true; + else + m_bEndRedliningWarningDone = true; + } + + const bool bNeedPassword = !m_bOrigPasswordIsConfirmed + && m_xUnProtectPB->get_visible(); // tdf#128230 Require password if the Unprotect button is visible + if (!bAlreadyDone && bNeedPassword) + { + OUString aPasswordText; + + // dialog canceled or no password provided + if (!lcl_GetPassword( m_rMyTabPage.GetFrameWeld(), false, aPasswordText )) + bAlreadyDone = true; + + // ask for password and if dialog is canceled or no password provided return + if (lcl_IsPasswordCorrect( aPasswordText )) + m_bOrigPasswordIsConfirmed = true; + else + bAlreadyDone = true; + } + + if (bAlreadyDone) + m_xRecordChangesCB->set_active(true); // restore original state + else + { + // remember required values to change protection and change recording in + // FillItemSet_Impl later on if password was correct. + m_bNewPasswordIsValid = true; + m_aNewPassword.clear(); + m_xProtectPB->show(); + m_xUnProtectPB->hide(); + } +} + +IMPL_LINK_NOARG(SfxSecurityPage_Impl, ChangeProtectionPBHdl, weld::Button&, void) +{ + if (m_eRedlingMode == RL_NONE) + return; + + // the push button text is always the opposite of the current state. Thus: + const bool bCurrentProtection = m_xUnProtectPB->get_visible(); + + // ask user for password (if still necessary) + OUString aPasswordText; + bool bNewProtection = !bCurrentProtection; + const bool bNeedPassword = bNewProtection || !m_bOrigPasswordIsConfirmed; + if (bNeedPassword) + { + // ask for password and if dialog is canceled or no password provided return + if (!lcl_GetPassword(m_rMyTabPage.GetFrameWeld(), bNewProtection, aPasswordText)) + return; + + // provided password still needs to be checked? + if (!bNewProtection && !m_bOrigPasswordIsConfirmed) + { + if (lcl_IsPasswordCorrect( aPasswordText )) + m_bOrigPasswordIsConfirmed = true; + else + return; + } + } + DBG_ASSERT( m_bOrigPasswordIsConfirmed, "ooops... this should not have happened!" ); + + // remember required values to change protection and change recording in + // FillItemSet_Impl later on if password was correct. + m_bNewPasswordIsValid = true; + m_aNewPassword = bNewProtection? aPasswordText : OUString(); + + m_xRecordChangesCB->set_active(bNewProtection); + + m_xUnProtectPB->set_visible(bNewProtection); + m_xProtectPB->set_visible(!bNewProtection); +} + +std::unique_ptr<SfxTabPage> SfxSecurityPage::Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet * rItemSet) +{ + return std::make_unique<SfxSecurityPage>(pPage, pController, *rItemSet); +} + +SfxSecurityPage::SfxSecurityPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rItemSet) + : SfxTabPage(pPage, pController, "sfx/ui/securityinfopage.ui", "SecurityInfoPage", &rItemSet) +{ + m_pImpl.reset(new SfxSecurityPage_Impl( *this )); +} + +bool SfxSecurityPage::FillItemSet( SfxItemSet * /*rItemSet*/ ) +{ + bool bModified = false; + DBG_ASSERT(m_pImpl, "implementation pointer is 0. Still in c-tor?"); + if (m_pImpl != nullptr) + bModified = m_pImpl->FillItemSet_Impl(); + return bModified; +} + +void SfxSecurityPage::Reset( const SfxItemSet * /*rItemSet*/ ) +{ + DBG_ASSERT(m_pImpl, "implementation pointer is 0. Still in c-tor?"); + if (m_pImpl != nullptr) + m_pImpl->Reset_Impl(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/securitypage.hxx b/sfx2/source/dialog/securitypage.hxx new file mode 100644 index 000000000..a598dfeb4 --- /dev/null +++ b/sfx2/source/dialog/securitypage.hxx @@ -0,0 +1,43 @@ +/* -*- 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_SECURITYPAGE_HXX +#define INCLUDED_SFX2_SECURITYPAGE_HXX + +#include <sfx2/tabdlg.hxx> +#include <memory> + +struct SfxSecurityPage_Impl; + +class SfxSecurityPage final : public SfxTabPage +{ + std::unique_ptr<SfxSecurityPage_Impl> m_pImpl; + + virtual bool FillItemSet(SfxItemSet*) override; + virtual void Reset(const SfxItemSet*) override; + +public: + SfxSecurityPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet&); + static std::unique_ptr<SfxTabPage> + Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet*); + weld::Builder& GetBuilder() const { return *m_xBuilder; } +}; + +#endif // INCLUDED_SFX2_SECURITYPAGE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/sfxdlg.cxx b/sfx2/source/dialog/sfxdlg.cxx new file mode 100644 index 000000000..4098dedd9 --- /dev/null +++ b/sfx2/source/dialog/sfxdlg.cxx @@ -0,0 +1,29 @@ +/* -*- 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/sfxdlg.hxx> + +SfxAbstractDialogFactory* SfxAbstractDialogFactory::Create() +{ + return dynamic_cast<SfxAbstractDialogFactory*>(VclAbstractDialogFactory::Create()); +} + +SfxAbstractDialogFactory::~SfxAbstractDialogFactory() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/splitwin.cxx b/sfx2/source/dialog/splitwin.cxx new file mode 100644 index 000000000..2abedce11 --- /dev/null +++ b/sfx2/source/dialog/splitwin.cxx @@ -0,0 +1,1155 @@ +/* -*- 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 . + */ + +#ifdef __sun +#include <ctime> +#endif + +#include <unotools/viewoptions.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> + +#include <vcl/dialoghelper.hxx> +#include <vcl/event.hxx> +#include <vcl/timer.hxx> +#include <vcl/svapp.hxx> + +#include <splitwin.hxx> +#include <workwin.hxx> +#include <sfx2/dockwin.hxx> +#include <o3tl/string_view.hxx> + +#include <memory> +#include <vector> +#include <utility> + +using namespace ::com::sun::star::uno; + +#define VERSION 1 +#define nPixel 30L +constexpr OUStringLiteral USERITEM_NAME = u"UserItem"; + +namespace { + // helper class to deactivate UpdateMode, if needed, for the life time of an instance + class DeactivateUpdateMode + { + public: + explicit DeactivateUpdateMode( SfxSplitWindow& rSplitWindow ) + : mrSplitWindow( rSplitWindow ) + , mbUpdateMode( rSplitWindow.IsUpdateMode() ) + { + if ( mbUpdateMode ) + { + mrSplitWindow.SetUpdateMode( false ); + } + } + + ~DeactivateUpdateMode() + { + if ( mbUpdateMode ) + { + mrSplitWindow.SetUpdateMode( true ); + } + } + + private: + SfxSplitWindow& mrSplitWindow; + const bool mbUpdateMode; + }; +} + +class SfxEmptySplitWin_Impl : public SplitWindow +{ +/* [Description] + + The SfxEmptySplitWin_Impldow is an empty SplitWindow, that replaces the + SfxSplitWindow AutoHide mode. It only serves as a placeholder to receive + mouse moves and if possible blend in the true SplitWindow display. +*/ +friend class SfxSplitWindow; + + VclPtr<SfxSplitWindow> pOwner; + bool bFadeIn; + bool bAutoHide; + bool bSplit; + bool bEndAutoHide; + Timer aTimer; + Point aLastPos; + sal_uInt16 nState; + +public: + explicit SfxEmptySplitWin_Impl( SfxSplitWindow *pParent ) + : SplitWindow( pParent->GetParent(), WinBits( WB_BORDER | WB_3DLOOK ) ) + , pOwner( pParent ) + , bFadeIn( false ) + , bAutoHide( false ) + , bSplit( false ) + , bEndAutoHide( false ) + , aTimer("sfx2 SfxEmptySplitWin_Impl aTimer") + , nState( 1 ) + { + aTimer.SetInvokeHandler( + LINK(pOwner, SfxSplitWindow, TimerHdl ) ); + aTimer.SetTimeout( 200 ); + SetAlign( pOwner->GetAlign() ); + Actualize(); + ShowFadeInHideButton(); + } + + virtual ~SfxEmptySplitWin_Impl() override + { disposeOnce(); } + virtual void dispose() override + { + aTimer.Stop(); + pOwner.clear(); + SplitWindow::dispose(); + } + + virtual void FadeIn() override; + void Actualize(); +}; + +void SfxEmptySplitWin_Impl::Actualize() +{ + Size aSize( pOwner->GetSizePixel() ); + switch ( pOwner->GetAlign() ) + { + case WindowAlign::Left: + case WindowAlign::Right: + aSize.setWidth( GetFadeInSize() ); + break; + case WindowAlign::Top: + case WindowAlign::Bottom: + aSize.setHeight( GetFadeInSize() ); + break; + } + + SetSizePixel( aSize ); +} + +void SfxEmptySplitWin_Impl::FadeIn() +{ + if (!bAutoHide ) + bAutoHide = IsFadeNoButtonMode(); + pOwner->SetFadeIn_Impl( true ); + if ( bAutoHide ) + { + // Set Timer to close; the caller has to ensure themselves that the + // Window is not closed instantly (eg by setting the focus or a modal + // mode. + aLastPos = GetPointerPosPixel(); + aTimer.Start(); + } + else + pOwner->SaveConfig_Impl(); +} + + +void SfxSplitWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( rMEvt.GetClicks() != 2 ) + SplitWindow::MouseButtonDown( rMEvt ); +} + +SfxSplitWindow::SfxSplitWindow( vcl::Window* pParent, SfxChildAlignment eAl, + SfxWorkWindow *pW, bool bWithButtons ) + +/* [Description] + + A SfxSplitWindow brings the recursive structure of the SV-SplitWindows to + the outside by simulating a table-like structure with rows and columns + (maximum recursion depth 2). Furthermore, it ensures the persistence of + the arrangement of the SfxDockingWindows. +*/ + +: SplitWindow ( pParent, WB_BORDER | WB_SIZEABLE | WB_3DLOOK | WB_HIDE ), + eAlign(eAl), + pWorkWin(pW), + bPinned(true), + pEmptyWin(nullptr), + pActive(nullptr) +{ + if (bWithButtons) + { + ShowFadeOutButton(); + } + + // Set SV-Alignment + WindowAlign eTbxAlign; + switch ( eAlign ) + { + case SfxChildAlignment::LEFT: + eTbxAlign = WindowAlign::Left; + break; + case SfxChildAlignment::RIGHT: + eTbxAlign = WindowAlign::Right; + break; + case SfxChildAlignment::TOP: + eTbxAlign = WindowAlign::Top; + break; + case SfxChildAlignment::BOTTOM: + eTbxAlign = WindowAlign::Bottom; + bPinned = true; + break; + default: + eTbxAlign = WindowAlign::Top; // some sort of default... + break; // -Wall lots not handled... + } + + SetAlign (eTbxAlign); + pEmptyWin = VclPtr<SfxEmptySplitWin_Impl>::Create( this ); + if ( bPinned ) + { + pEmptyWin->bFadeIn = true; + pEmptyWin->nState = 2; + } + + if ( bWithButtons ) + { + // Read Configuration + const OUString aWindowId{ "SplitWindow" + OUString::number(static_cast<sal_Int32>(eTbxAlign)) }; + SvtViewOptions aWinOpt( EViewType::Window, aWindowId ); + OUString aWinData; + Any aUserItem = aWinOpt.GetUserItem( USERITEM_NAME ); + OUString aTemp; + if ( aUserItem >>= aTemp ) + aWinData = aTemp; + if ( aWinData.startsWith("V") ) + { + sal_Int32 nIdx{ 0 }; + pEmptyWin->nState = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aWinData, 1, ',', nIdx ))); + if ( pEmptyWin->nState & 2 ) + pEmptyWin->bFadeIn = true; + bPinned = true; // always assume pinned - floating mode not used anymore + + const sal_Int32 nCount{ o3tl::toInt32(o3tl::getToken(aWinData, 0, ',', nIdx)) }; + for ( sal_Int32 n=0; n<nCount; ++n ) + { + std::unique_ptr<SfxDock_Impl> pDock(new SfxDock_Impl); + pDock->pWin = nullptr; + pDock->bNewLine = false; + pDock->bHide = true; + pDock->nType = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aWinData, 0, ',', nIdx))); + if ( !pDock->nType ) + { + // could mean NewLine + pDock->nType = static_cast<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aWinData, 0, ',', nIdx))); + if ( !pDock->nType ) + { + // Read error + break; + } + else + pDock->bNewLine = true; + } + + maDockArr.insert(maDockArr.begin() + n, std::move(pDock)); + } + } + } + else + { + bPinned = true; + pEmptyWin->bFadeIn = true; + pEmptyWin->nState = 2; + } +} + + +SfxSplitWindow::~SfxSplitWindow() +{ + disposeOnce(); +} + +void SfxSplitWindow::dispose() +{ + SaveConfig_Impl(); + + if ( pEmptyWin ) + { + // Set pOwner to NULL, otherwise try to delete pEmptyWin once more. The + // window that is just being docked is always deleted from the outside. + pEmptyWin->pOwner = nullptr; + } + pEmptyWin.disposeAndClear(); + + maDockArr.clear(); + pActive.clear(); + SplitWindow::dispose(); +} + +void SfxSplitWindow::SaveConfig_Impl() +{ + // Save configuration + OUStringBuffer aWinData; + aWinData.append('V'); + aWinData.append(static_cast<sal_Int32>(VERSION)); + aWinData.append(','); + aWinData.append(static_cast<sal_Int32>(pEmptyWin->nState)); + aWinData.append(','); + + sal_uInt16 nCount = 0; + for ( auto const & rDock: maDockArr ) + { + if ( rDock->bHide || rDock->pWin ) + nCount++; + } + + aWinData.append(static_cast<sal_Int32>(nCount)); + + for ( auto const & rDock: maDockArr ) + { + if ( !rDock->bHide && !rDock->pWin ) + continue; + if ( rDock->bNewLine ) + aWinData.append(",0"); + aWinData.append(','); + aWinData.append(static_cast<sal_Int32>(rDock->nType)); + } + + const OUString aWindowId{ "SplitWindow" + OUString::number(static_cast<sal_Int32>(GetAlign())) }; + SvtViewOptions aWinOpt( EViewType::Window, aWindowId ); + aWinOpt.SetUserItem( USERITEM_NAME, Any( aWinData.makeStringAndClear() ) ); +} + + +void SfxSplitWindow::StartSplit() +{ + tools::Long nSize = 0; + Size aSize = GetSizePixel(); + + if ( pEmptyWin ) + { + pEmptyWin->bFadeIn = true; + pEmptyWin->bSplit = true; + } + + tools::Rectangle aRect = pWorkWin->GetFreeArea( !bPinned ); + switch ( GetAlign() ) + { + case WindowAlign::Left: + case WindowAlign::Right: + nSize = aSize.Width() + aRect.GetWidth(); + break; + case WindowAlign::Top: + case WindowAlign::Bottom: + nSize = aSize.Height() + aRect.GetHeight(); + break; + } + + SetMaxSizePixel( nSize ); +} + + +void SfxSplitWindow::SplitResize() +{ + if ( bPinned ) + { + pWorkWin->ArrangeChildren_Impl(); + pWorkWin->ShowChildren_Impl(); + } + else + pWorkWin->ArrangeAutoHideWindows( this ); +} + + +void SfxSplitWindow::Split() +{ + if ( pEmptyWin ) + pEmptyWin->bSplit = false; + + SplitWindow::Split(); + + std::vector< std::pair< sal_uInt16, tools::Long > > aNewOrgSizes; + + sal_uInt16 nCount = maDockArr.size(); + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + const SfxDock_Impl& rD = *maDockArr[n]; + if ( rD.pWin ) + { + const sal_uInt16 nId = rD.nType; + const tools::Long nSize = GetItemSize( nId, SplitWindowItemFlags::Fixed ); + const tools::Long nSetSize = GetItemSize( GetSet( nId ) ); + Size aSize; + + if ( IsHorizontal() ) + { + aSize.setWidth( nSize ); + aSize.setHeight( nSetSize ); + } + else + { + aSize.setWidth( nSetSize ); + aSize.setHeight( nSize ); + } + + rD.pWin->SetItemSize_Impl( aSize ); + + aNewOrgSizes.emplace_back( nId, nSize ); + } + } + + // workaround insufficiency of <SplitWindow> regarding dock layouting: + // apply FIXED item size as 'original' item size to improve layouting of undock-dock-cycle of a window + { + DeactivateUpdateMode aDeactivateUpdateMode( *this ); + for (const std::pair< sal_uInt16, tools::Long > & rNewOrgSize : aNewOrgSizes) + { + SetItemSize( rNewOrgSize.first, rNewOrgSize.second ); + } + } + + SaveConfig_Impl(); +} + + +void SfxSplitWindow::InsertWindow( SfxDockingWindow* pDockWin, const Size& rSize) + +/* + To insert SfxDockingWindows just pass no position. The SfxSplitWindow + searches the last marked one to the passed SfxDockingWindow or appends a + new one at the end. +*/ +{ + short nLine = -1; // so that the first window cab set nline to 0 + sal_uInt16 nL; + sal_uInt16 nPos = 0; + bool bNewLine = true; + bool bSaveConfig = false; + SfxDock_Impl *pFoundDock=nullptr; + sal_uInt16 nCount = maDockArr.size(); + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + SfxDock_Impl& rDock = *maDockArr[n]; + if ( rDock.bNewLine ) + { + // The window opens a new line + if ( pFoundDock ) + // But after the just inserted window + break; + + // New line + nPos = 0; + bNewLine = true; + } + + if ( rDock.pWin ) + { + // Does there exist a window now at this position + if ( bNewLine && !pFoundDock ) + { + // Not known until now in which real line it is located + GetWindowPos( rDock.pWin, nL, nPos ); + nLine = static_cast<short>(nL); + } + + if ( !pFoundDock ) + { + // The window is located before the inserted one + nPos++; + } + + // Line is opened + bNewLine = false; + if ( pFoundDock ) + break; + } + + if ( rDock.nType == pDockWin->GetType() ) + { + DBG_ASSERT( !pFoundDock && !rDock.pWin, "Window already exists!"); + pFoundDock = &rDock; + if ( !bNewLine ) + break; + else + { + // A new line has been created but no window was found there; + // continue searching for a window in this line in-order to set + // bNewLine correctly. While doing so nline or nPos are not + // to be changed! + nLine++; + } + } + } + + if ( !pFoundDock ) + { + // Not found, insert at end + pFoundDock = new SfxDock_Impl; + pFoundDock->bHide = true; + maDockArr.push_back( std::unique_ptr<SfxDock_Impl>(pFoundDock) ); + pFoundDock->nType = pDockWin->GetType(); + nLine++; + nPos = 0; + bNewLine = true; + pFoundDock->bNewLine = bNewLine; + bSaveConfig = true; + } + + pFoundDock->pWin = pDockWin; + pFoundDock->bHide = false; + InsertWindow_Impl( pFoundDock, rSize, nLine, nPos, bNewLine ); + if ( bSaveConfig ) + SaveConfig_Impl(); +} + + +void SfxSplitWindow::ReleaseWindow_Impl(SfxDockingWindow const *pDockWin, bool bSave) +{ +// The docking window is no longer stored in the internal data. + sal_uInt16 nCount = maDockArr.size(); + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + const SfxDock_Impl& rDock = *maDockArr[n]; + if ( rDock.nType == pDockWin->GetType() ) + { + if ( rDock.bNewLine && n<nCount-1 ) + maDockArr[n+1]->bNewLine = true; + + // Window has a position, this we forget + maDockArr.erase(maDockArr.begin() + n); + break; + } + } + + if ( bSave ) + SaveConfig_Impl(); +} + + +void SfxSplitWindow::MoveWindow( SfxDockingWindow* pDockWin, const Size& rSize, + sal_uInt16 nLine, sal_uInt16 nPos, bool bNewLine) + +/* [Description] + + The docking window is moved within the SplitWindows. +*/ + +{ + sal_uInt16 nL, nP; + GetWindowPos( pDockWin, nL, nP ); + + if ( nLine > nL && GetItemCount( GetItemId( nL ) ) == 1 ) + { + // If the last window is removed from its line, then everything slips + // one line to the front! + nLine--; + } + RemoveWindow( pDockWin ); + InsertWindow( pDockWin, rSize, nLine, nPos, bNewLine ); +} + + +void SfxSplitWindow::InsertWindow( SfxDockingWindow* pDockWin, const Size& rSize, + sal_uInt16 nLine, sal_uInt16 nPos, bool bNewLine) + +/* [Description] + + The DockingWindow that is pushed on this SplitWindow and shall hold the + given position and size. +*/ +{ + ReleaseWindow_Impl( pDockWin, false ); + SfxDock_Impl *pDock = new SfxDock_Impl; + pDock->bHide = false; + pDock->nType = pDockWin->GetType(); + pDock->bNewLine = bNewLine; + pDock->pWin = pDockWin; + + DBG_ASSERT( nPos==0 || !bNewLine, "Wrong Parameter!"); + if ( bNewLine ) + nPos = 0; + + // The window must be inserted before the first window so that it has the + // same or a greater position than pDockWin. + sal_uInt16 nCount = maDockArr.size(); + sal_uInt16 nLastWindowIdx(0); + + // If no window is found, a first window is inserted + sal_uInt16 nInsertPos = 0; + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + SfxDock_Impl& rD = *maDockArr[n]; + + if (rD.pWin) + { + // A docked window has been found. If no suitable window behind + // the desired insertion point s found, then insertion is done at + // the end. + nInsertPos = nCount; + nLastWindowIdx = n; + sal_uInt16 nL=0, nP=0; + GetWindowPos( rD.pWin, nL, nP ); + + if ( (nL == nLine && nP == nPos) || nL > nLine ) + { + DBG_ASSERT( nL == nLine || bNewLine || nPos > 0, "Wrong Parameter!" ); + if ( nL == nLine && nPos == 0 && !bNewLine ) + { + DBG_ASSERT(rD.bNewLine, "No new line?"); + + // The position is pushed to nPos==0 + rD.bNewLine = false; + pDock->bNewLine = true; + } + + nInsertPos = n != 0 ? nLastWindowIdx + 1 : 0; // ignore all non-windows after the last window + break; + } + } + } + if (nCount != 0 && nInsertPos == nCount && nLastWindowIdx != nCount - 1) + { + nInsertPos = nLastWindowIdx + 1; // ignore all non-windows after the last window + } + + maDockArr.insert(maDockArr.begin() + nInsertPos, std::unique_ptr<SfxDock_Impl>(pDock)); + InsertWindow_Impl( pDock, rSize, nLine, nPos, bNewLine ); + SaveConfig_Impl(); +} + + +void SfxSplitWindow::InsertWindow_Impl( SfxDock_Impl const * pDock, + const Size& rSize, + sal_uInt16 nLine, sal_uInt16 nPos, bool bNewLine) + +/* [Description] + + Adds a DockingWindow, and causes the recalculation of the size of + the SplitWindows. +*/ + +{ + SfxDockingWindow* pDockWin = pDock->pWin; + + SplitWindowItemFlags nItemBits = SplitWindowItemFlags::NONE; + + tools::Long nWinSize, nSetSize; + if ( IsHorizontal() ) + { + nWinSize = rSize.Width(); + nSetSize = rSize.Height(); + } + else + { + nSetSize = rSize.Width(); + nWinSize = rSize.Height(); + } + + std::unique_ptr<DeactivateUpdateMode> pDeactivateUpdateMode(new DeactivateUpdateMode( *this )); + + if ( bNewLine || nLine == GetItemCount() ) + { + // An existing row should not be inserted, instead a new one + // will be created + + sal_uInt16 nId = 1; + for ( sal_uInt16 n=0; n<GetItemCount(); n++ ) + { + if ( GetItemId(n) >= nId ) + nId = GetItemId(n)+1; + } + + // Create a new nLine:th line + SplitWindowItemFlags nBits = nItemBits; + if ( GetAlign() == WindowAlign::Top || GetAlign() == WindowAlign::Bottom ) + nBits |= SplitWindowItemFlags::ColSet; + InsertItem( nId, nSetSize, nLine, 0, nBits ); + } + + // Insert the window at line with the position nline. ItemWindowSize set to + // "percentage" share since the SV then does the re-sizing as expected, + // "pixel" actually only makes sense if also items with percentage or + // relative sizes are present. + nItemBits |= SplitWindowItemFlags::PercentSize; + sal_uInt16 nSet = GetItemId( nLine ); + InsertItem( pDockWin->GetType(), pDockWin, nWinSize, nPos, nSet, nItemBits ); + + // SplitWindows are once created in SFX and when inserting the first + // DockingWindows is made visible. + if ( GetItemCount() == 1 && GetItemCount( 1 ) == 1 ) + { + // The Rearranging in WorkWindow and a Show() on the SplitWindow is + // caused by SfxDockingwindow (->SfxWorkWindow::ConfigChild_Impl) + if ( !bPinned && !IsFloatingMode() ) + { + bPinned = true; + bool bFadeIn = ( pEmptyWin->nState & 2 ) != 0; + pEmptyWin->bFadeIn = false; + SetPinned_Impl( false ); + pEmptyWin->Actualize(); + SAL_INFO("sfx", "SfxSplitWindow::InsertWindow_Impl - registering empty Splitwindow" ); + pWorkWin->RegisterChild_Impl( *GetSplitWindow(), eAlign )->nVisible = SfxChildVisibility::VISIBLE; + // tdf#113539 FadeIn will call ArrangeChildren_Impl() for us, and avoiding extra calls to that + // can make a different to load times because it avoids extra accessibility calcs + if ( bFadeIn ) + FadeIn(); + else + pWorkWin->ArrangeChildren_Impl(); + } + else + { + bool bFadeIn = ( pEmptyWin->nState & 2 ) != 0; + pEmptyWin->bFadeIn = false; + pEmptyWin->Actualize(); + if ( !bPinned || !pEmptyWin->bFadeIn ) + { + SAL_INFO("sfx", "SfxSplitWindow::InsertWindow_Impl - registering empty Splitwindow" ); + } + else + { + SAL_INFO("sfx", "SfxSplitWindow::InsertWindow_Impl - registering real Splitwindow" ); + } + pWorkWin->RegisterChild_Impl( *GetSplitWindow(), eAlign )->nVisible = SfxChildVisibility::VISIBLE; + // tdf#113539 FadeIn will call ArrangeChildren_Impl() for us, and avoiding extra calls to that + // can make a different to load times because it avoids extra accessibility calcs + if ( bFadeIn ) + FadeIn(); + else + pWorkWin->ArrangeChildren_Impl(); + } + + pWorkWin->ShowChildren_Impl(); + } + + pDeactivateUpdateMode.reset(); + + // workaround insufficiency of <SplitWindow> regarding dock layouting: + // apply FIXED item size as 'original' item size to improve layouting of undock-dock-cycle of a window + { + std::vector< std::pair< sal_uInt16, tools::Long > > aNewOrgSizes; + // get FIXED item sizes + sal_uInt16 nCount = maDockArr.size(); + for ( sal_uInt16 n=0; n<nCount; ++n ) + { + const SfxDock_Impl& rD = *maDockArr[n]; + if ( rD.pWin ) + { + const sal_uInt16 nId = rD.nType; + const tools::Long nSize = GetItemSize( nId, SplitWindowItemFlags::Fixed ); + aNewOrgSizes.emplace_back( nId, nSize ); + } + } + // apply new item sizes + DeactivateUpdateMode aDeactivateUpdateMode( *this ); + for (const std::pair< sal_uInt16, tools::Long > & rNewOrgSize : aNewOrgSizes) + { + SetItemSize( rNewOrgSize.first, rNewOrgSize.second ); + } + } +} + + +void SfxSplitWindow::RemoveWindow( SfxDockingWindow const * pDockWin, bool bHide ) + +/* [Description] + + Removes a DockingWindow. If it was the last one, then the SplitWindow is + being hidden. +*/ +{ + sal_uInt16 nSet = GetSet( pDockWin->GetType() ); + + // SplitWindows are once created in SFX and is made invisible after + // removing the last DockingWindows. + if ( GetItemCount( nSet ) == 1 && GetItemCount() == 1 ) + { + // The Rearranging in WorkWindow is caused by SfxDockingwindow + Hide(); + pEmptyWin->aTimer.Stop(); + sal_uInt16 nRealState = pEmptyWin->nState; + FadeOut_Impl(); + pEmptyWin->Hide(); +#ifdef DBG_UTIL + if ( !bPinned || !pEmptyWin->bFadeIn ) + { + SAL_INFO("sfx", "SfxSplitWindow::RemoveWindow - releasing empty Splitwindow" ); + } + else + { + SAL_INFO("sfx", "SfxSplitWindow::RemoveWindow - releasing real Splitwindow" ); + } +#endif + pWorkWin->ReleaseChild_Impl( *GetSplitWindow() ); + pEmptyWin->nState = nRealState; + pWorkWin->ArrangeAutoHideWindows( this ); + } + + sal_uInt16 nCount = maDockArr.size(); + for ( sal_uInt16 n=0; n<nCount; n++ ) + { + SfxDock_Impl& rDock = *maDockArr[n]; + if ( rDock.nType == pDockWin->GetType() ) + { + rDock.pWin = nullptr; + rDock.bHide = bHide; + break; + } + } + + // Remove Windows, and if it was the last of the line, then also remove + // the line (line = itemset) + DeactivateUpdateMode aDeactivateUpdateMode( *this ); + + RemoveItem( pDockWin->GetType() ); + + if ( nSet && !GetItemCount( nSet ) ) + RemoveItem( nSet ); +}; + + +bool SfxSplitWindow::GetWindowPos( const SfxDockingWindow* pWindow, + sal_uInt16& rLine, sal_uInt16& rPos ) const +/* [Description] + + Returns the ID of the item sets and items for the DockingWindow in + the position passed on the old row / column-name. +*/ + +{ + sal_uInt16 nSet = GetSet ( pWindow->GetType() ); + if ( nSet == SPLITWINDOW_ITEM_NOTFOUND ) + return false; + + rPos = GetItemPos( pWindow->GetType(), nSet ); + rLine = GetItemPos( nSet ); + return true; +} + + +bool SfxSplitWindow::GetWindowPos( const Point& rTestPos, + sal_uInt16& rLine, sal_uInt16& rPos ) const +/* [Description] + + Returns the ID of the item sets and items for the DockingWindow in + the position passed on the old row / column-name. +*/ + +{ + sal_uInt16 nId = GetItemId( rTestPos ); + if ( nId == 0 ) + return false; + + sal_uInt16 nSet = GetSet ( nId ); + rPos = GetItemPos( nId, nSet ); + rLine = GetItemPos( nSet ); + return true; +} + + +sal_uInt16 SfxSplitWindow::GetLineCount() const + +/* [Description] + + Returns the number of rows = number of sub-itemsets in the root set. +*/ +{ + return GetItemCount(); +} + + +tools::Long SfxSplitWindow::GetLineSize( sal_uInt16 nLine ) const + +/* [Description] + + Returns the Row Height of nline itemset. +*/ +{ + sal_uInt16 nId = GetItemId( nLine ); + return GetItemSize( nId ); +} + + +sal_uInt16 SfxSplitWindow::GetWindowCount( sal_uInt16 nLine ) const + +/* [Description] + + Returns the total number of windows +*/ +{ + sal_uInt16 nId = GetItemId( nLine ); + return GetItemCount( nId ); +} + + +sal_uInt16 SfxSplitWindow::GetWindowCount() const + +/* [Description] + + Returns the total number of windows +*/ +{ + return GetItemCount(); +} + + +IMPL_LINK( SfxSplitWindow, TimerHdl, Timer*, pTimer, void) +{ + if ( pTimer ) + pTimer->Stop(); + + if ( CursorIsOverRect() || !pTimer ) + { + // If the cursor is within the window, display the SplitWindow and set + // up the timer for close + pEmptyWin->bAutoHide = true; + if ( !IsVisible() ) + pEmptyWin->FadeIn(); + + pEmptyWin->aLastPos = GetPointerPosPixel(); + pEmptyWin->aTimer.Start(); + } + else if ( pEmptyWin->bAutoHide ) + { + if ( GetPointerPosPixel() != pEmptyWin->aLastPos ) + { + // The mouse has moved within the running time of the timer, thus + // do nothing + pEmptyWin->aLastPos = GetPointerPosPixel(); + pEmptyWin->aTimer.Start(); + return; + } + + // Especially for TF_AUTOSHOW_ON_MOUSEMOVE : + // If the window is not visible, there is nothing to do + // (user has simply moved the mouse over pEmptyWin) + if ( IsVisible() ) + { + pEmptyWin->bEndAutoHide = false; + if ( !Application::IsInModalMode() && + !vcl::IsInPopupMenuExecute() && + !pEmptyWin->bSplit && !HasChildPathFocus( true ) ) + { + // While a modal dialog or a popup menu is open or while the + // Splitting is done, in any case, do not close. Even as long + // as one of the Children has the focus, the window remains + // open. + pEmptyWin->bEndAutoHide = true; + } + + if ( pEmptyWin->bEndAutoHide ) + { + // As far as I am concerned this can be the end of AutoShow + // But maybe some other SfxSplitWindow will remain open, + // then all others remain open too. + if ( !pWorkWin->IsAutoHideMode( this ) ) + { + FadeOut_Impl(); + pWorkWin->ArrangeAutoHideWindows( this ); + } + else + { + pEmptyWin->aLastPos = GetPointerPosPixel(); + pEmptyWin->aTimer.Start(); + } + } + else + { + pEmptyWin->aLastPos = GetPointerPosPixel(); + pEmptyWin->aTimer.Start(); + } + } + } +} + + +bool SfxSplitWindow::CursorIsOverRect() const +{ + bool bVisible = IsVisible(); + + // Also, take the collapsed SplitWindow into account + Point aPos = pEmptyWin->GetParent()->OutputToScreenPixel( pEmptyWin->GetPosPixel() ); + Size aSize = pEmptyWin->GetSizePixel(); + + tools::Rectangle aRect( aPos, aSize ); + + if ( bVisible ) + { + Point aVisPos = GetPosPixel(); + Size aVisSize = GetSizePixel(); + + // Extend with +/- a few pixels, otherwise it is too nervous + aVisPos.AdjustX( -(nPixel) ); + aVisPos.AdjustY( -(nPixel) ); + aVisSize.AdjustWidth(2 * nPixel ); + aVisSize.AdjustHeight(2 * nPixel ); + + tools::Rectangle aVisRect( aVisPos, aVisSize ); + aRect = aRect.GetUnion( aVisRect ); + } + + return aRect.Contains( OutputToScreenPixel( static_cast<vcl::Window*>(const_cast<SfxSplitWindow *>(this))->GetPointerPosPixel() ) ); +} + + +SplitWindow* SfxSplitWindow::GetSplitWindow() +{ + if ( !bPinned || !pEmptyWin->bFadeIn ) + return pEmptyWin; + return this; +} + + +bool SfxSplitWindow::IsFadeIn() const +{ + return pEmptyWin->bFadeIn; +} + +bool SfxSplitWindow::IsAutoHide( bool bSelf ) const +{ + return bSelf ? pEmptyWin->bAutoHide && !pEmptyWin->bEndAutoHide : pEmptyWin->bAutoHide; +} + + +void SfxSplitWindow::SetPinned_Impl( bool bOn ) +{ + if ( bPinned == bOn ) + return; + + bPinned = bOn; + if ( GetItemCount() == 0 ) + return; + + if ( !bOn ) + { + pEmptyWin->nState |= 1; + if ( pEmptyWin->bFadeIn ) + { + // Unregister replacement windows + SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - releasing real Splitwindow" ); + pWorkWin->ReleaseChild_Impl( *this ); + Hide(); + pEmptyWin->Actualize(); + SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - registering empty Splitwindow" ); + pWorkWin->RegisterChild_Impl( *pEmptyWin, eAlign )->nVisible = SfxChildVisibility::VISIBLE; + } + + Point aPos( GetPosPixel() ); + aPos = GetParent()->OutputToScreenPixel( aPos ); + SetFloatingPos( aPos ); + SetFloatingMode( true ); + GetFloatingWindow()->SetOutputSizePixel( GetOutputSizePixel() ); + + if ( pEmptyWin->bFadeIn ) + Show(); + } + else + { + pEmptyWin->nState &= ~1; + SetOutputSizePixel( GetFloatingWindow()->GetOutputSizePixel() ); + SetFloatingMode(false); + + if ( pEmptyWin->bFadeIn ) + { + // Unregister replacement windows + SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - releasing empty Splitwindow" ); + pWorkWin->ReleaseChild_Impl( *pEmptyWin ); + pEmptyWin->Hide(); + SAL_INFO("sfx", "SfxSplitWindow::SetPinned_Impl - registering real Splitwindow" ); + pWorkWin->RegisterChild_Impl( *this, eAlign )->nVisible = SfxChildVisibility::VISIBLE; + } + } +} + +void SfxSplitWindow::SetFadeIn_Impl( bool bOn ) +{ + if ( bOn == pEmptyWin->bFadeIn ) + return; + + if ( GetItemCount() == 0 ) + return; + + pEmptyWin->bFadeIn = bOn; + if ( bOn ) + { + pEmptyWin->nState |= 2; + if ( IsFloatingMode() ) + { + // FloatingWindow is not visible, thus display it + pWorkWin->ArrangeAutoHideWindows( this ); + Show(); + } + else + { + SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - releasing empty Splitwindow" ); + pWorkWin->ReleaseChild_Impl( *pEmptyWin ); + pEmptyWin->Hide(); + SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - registering real Splitwindow" ); + pWorkWin->RegisterChild_Impl( *this, eAlign )->nVisible = SfxChildVisibility::VISIBLE; + pWorkWin->ArrangeChildren_Impl(); + pWorkWin->ShowChildren_Impl(); + } + } + else + { + pEmptyWin->bAutoHide = false; + pEmptyWin->nState &= ~2; + if ( !IsFloatingMode() ) + { + // The window is not "floating", should be hidden + SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - releasing real Splitwindow" ); + pWorkWin->ReleaseChild_Impl( *this ); + Hide(); + pEmptyWin->Actualize(); + SAL_INFO("sfx", "SfxSplitWindow::SetFadeIn_Impl - registering empty Splitwindow" ); + pWorkWin->RegisterChild_Impl( *pEmptyWin, eAlign )->nVisible = SfxChildVisibility::VISIBLE; + pWorkWin->ArrangeChildren_Impl(); + pWorkWin->ShowChildren_Impl(); + pWorkWin->ArrangeAutoHideWindows( this ); + } + else + { + Hide(); + pWorkWin->ArrangeAutoHideWindows( this ); + } + } +} + +void SfxSplitWindow::FadeOut_Impl() +{ + if ( pEmptyWin->aTimer.IsActive() ) + { + pEmptyWin->bAutoHide = false; + pEmptyWin->aTimer.Stop(); + } + + SetFadeIn_Impl( false ); +} + +void SfxSplitWindow::FadeOut() +{ + FadeOut_Impl(); + SaveConfig_Impl(); +} + +void SfxSplitWindow::FadeIn() +{ + SetFadeIn_Impl( true ); +} + +void SfxSplitWindow::SetActiveWindow_Impl( SfxDockingWindow* pWin ) +{ + pActive = pWin; + pWorkWin->SetActiveChild_Impl( this ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/srchdlg.cxx b/sfx2/source/dialog/srchdlg.cxx new file mode 100644 index 000000000..fdd42e7e6 --- /dev/null +++ b/sfx2/source/dialog/srchdlg.cxx @@ -0,0 +1,135 @@ +/* -*- 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 <srchdlg.hxx> +#include <comphelper/string.hxx> + +#include <tools/debug.hxx> +#include <unotools/viewoptions.hxx> +#include <o3tl/string_view.hxx> + +using namespace ::com::sun::star::uno; + + +namespace sfx2 { + +#define MAX_SAVE_COUNT sal_uInt16(10) + + +// SearchDialog + + +SearchDialog::SearchDialog(weld::Window* pWindow, const OUString& rConfigName) + : GenericDialogController(pWindow, "sfx/ui/searchdialog.ui", "SearchDialog") + , m_sConfigName(rConfigName) + , m_xSearchEdit(m_xBuilder->weld_combo_box("searchterm")) + , m_xWholeWordsBox(m_xBuilder->weld_check_button("wholewords")) + , m_xMatchCaseBox(m_xBuilder->weld_check_button("matchcase")) + , m_xWrapAroundBox(m_xBuilder->weld_check_button("wrap")) + , m_xBackwardsBox(m_xBuilder->weld_check_button("backwards")) + , m_xFindBtn(m_xBuilder->weld_button("ok")) +{ + // set handler + m_xFindBtn->connect_clicked(LINK(this, SearchDialog, FindHdl)); + // load config: old search strings and the status of the check boxes + LoadConfig(); + // the search edit should have the focus + m_xSearchEdit->grab_focus(); +} + +SearchDialog::~SearchDialog() +{ + SaveConfig(); +} + +void SearchDialog::LoadConfig() +{ + SvtViewOptions aViewOpt( EViewType::Dialog, m_sConfigName ); + if ( aViewOpt.Exists() ) + { + Any aUserItem = aViewOpt.GetUserItem( "UserItem" ); + OUString sUserData; + if ( aUserItem >>= sUserData ) + { + DBG_ASSERT( comphelper::string::getTokenCount(sUserData, ';') == 5, "invalid config data" ); + sal_Int32 nIdx = 0; + OUString sSearchText = sUserData.getToken( 0, ';', nIdx ); + m_xWholeWordsBox->set_active( o3tl::toInt32(o3tl::getToken(sUserData, 0, ';', nIdx )) == 1 ); + m_xMatchCaseBox->set_active( o3tl::toInt32(o3tl::getToken(sUserData, 0, ';', nIdx )) == 1 ); + m_xWrapAroundBox->set_active( o3tl::toInt32(o3tl::getToken(sUserData, 0, ';', nIdx )) == 1 ); + m_xBackwardsBox->set_active( o3tl::toInt32(o3tl::getToken(sUserData, 0, ';', nIdx )) == 1 ); + + nIdx = 0; + while ( nIdx != -1 ) + m_xSearchEdit->append_text(sSearchText.getToken( 0, '\t', nIdx)); + m_xSearchEdit->set_active(0); + } + } + else + m_xWrapAroundBox->set_active(true); +} + +void SearchDialog::SaveConfig() +{ + SvtViewOptions aViewOpt( EViewType::Dialog, m_sConfigName ); + OUString sUserData; + int i = 0, nCount = std::min(m_xSearchEdit->get_count(), static_cast<int>(MAX_SAVE_COUNT)); + for ( ; i < nCount; ++i ) + { + sUserData += m_xSearchEdit->get_text(i) + "\t"; + } + sUserData = comphelper::string::stripStart(sUserData, '\t') + ";" + + OUString::number( m_xWholeWordsBox->get_active() ? 1 : 0 ) + ";" + + OUString::number( m_xMatchCaseBox->get_active() ? 1 : 0 ) + ";" + + OUString::number( m_xWrapAroundBox->get_active() ? 1 : 0 ) + ";" + + OUString::number( m_xBackwardsBox->get_active() ? 1 : 0 ); + + Any aUserItem( sUserData ); + aViewOpt.SetUserItem( "UserItem", aUserItem ); +} + +IMPL_LINK_NOARG(SearchDialog, FindHdl, weld::Button&, void) +{ + OUString sSrchTxt = m_xSearchEdit->get_active_text(); + auto nPos = m_xSearchEdit->find_text(sSrchTxt); + if (nPos != 0) + { + if (nPos != -1) + m_xSearchEdit->remove(nPos); + m_xSearchEdit->insert_text(0, sSrchTxt); + } + m_aFindHdl.Call( *this ); +} + +void SearchDialog::SetFocusOnEdit() +{ + m_xSearchEdit->select_entry_region(0, -1); + m_xSearchEdit->grab_focus(); +} + +void SearchDialog::runAsync(const std::shared_ptr<SearchDialog>& rController) +{ + weld::DialogController::runAsync(rController, [=](sal_Int32 /*nResult*/){ rController->m_aCloseHdl.Call(nullptr); }); +} + +} // namespace sfx2 + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/styfitem.cxx b/sfx2/source/dialog/styfitem.cxx new file mode 100644 index 000000000..489c4d2df --- /dev/null +++ b/sfx2/source/dialog/styfitem.cxx @@ -0,0 +1,35 @@ +/* -*- 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/styfitem.hxx> +#include <unotools/resmgr.hxx> + +SfxStyleFamilyItem::SfxStyleFamilyItem( + SfxStyleFamily nFamily_, const OUString& rName, const OUString& rImage, + const std::pair<TranslateId, SfxStyleSearchBits>* pStringArray, const std::locale& rResLocale) + : nFamily(nFamily_) + , aText(rName) + , aImage(rImage) +{ + for (const std::pair<TranslateId, SfxStyleSearchBits>* pItem = pStringArray; pItem->first; + ++pItem) + aFilterList.emplace_back(Translate::get(pItem->first, rResLocale), pItem->second); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/styledlg.cxx b/sfx2/source/dialog/styledlg.cxx new file mode 100644 index 000000000..0e2551102 --- /dev/null +++ b/sfx2/source/dialog/styledlg.cxx @@ -0,0 +1,124 @@ +/* -*- 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 <svl/whiter.hxx> +#include <svl/style.hxx> + +#include <sfx2/styledlg.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/strings.hrc> + +#include "mgetempl.hxx" + +/* [Description] + + Constructor: Add Manage TabPage, set ExampleSet from style. +*/ +SfxStyleDialogController::SfxStyleDialogController +( + weld::Window* pParent, // Parent + const OUString& rUIXMLDescription, const OString& rID, + SfxStyleSheetBase& rStyle // stylesheet to be processed +) + : SfxTabDialogController(pParent, rUIXMLDescription, rID, &rStyle.GetItemSet(), true) + , m_rStyle(rStyle) +{ + // without ParentSupport suppress the standardButton + if (!rStyle.HasParentSupport()) + RemoveStandardButton(); + + AddTabPage("organizer", SfxManageStyleSheetPage::Create, nullptr); + + // With new template always set the management page as the current page + if (rStyle.GetName().isEmpty()) + SetCurPageId("organizer"); + else + { + OUString sTxt = m_xDialog->get_title() + ": " + rStyle.GetName(); + m_xDialog->set_title(sTxt); + } + m_xExampleSet.reset(&m_rStyle.GetItemSet()); // in SfxTabDialog::Ctor() already created, reset will delete it + + GetCancelButton().connect_clicked(LINK(this, SfxStyleDialogController, CancelHdl)); +} + +/* [Description] + + Destructor: set ExampleSet to NULL, so that SfxTabDialog does not delete + the Set from Style. +*/ +SfxStyleDialogController::~SfxStyleDialogController() +{ + m_xExampleSet.release(); +} + +/* [Description] + + Override so that always RET_OK is returned. +*/ +short SfxStyleDialogController::Ok() +{ + SfxTabDialogController::Ok(); + return RET_OK; +} + +/* [Description] + + If the dialogue was canceled, then all selected attributes must be reset + again. +*/ +IMPL_LINK_NOARG(SfxStyleDialogController, CancelHdl, weld::Button&, void) +{ + SfxTabPage* pPage = GetTabPage("organizer"); + + const SfxItemSet* pInSet = GetInputSetImpl(); + SfxWhichIter aIter(*pInSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + + while (nWhich) + { + SfxItemState eState = aIter.GetItemState(false); + + if (SfxItemState::DEFAULT == eState) + m_xExampleSet->ClearItem(nWhich); + else + m_xExampleSet->Put(pInSet->Get(nWhich)); + nWhich = aIter.NextWhich(); + } + + if (pPage) + pPage->Reset(GetInputSetImpl()); + + m_xDialog->response(RET_CANCEL); +} + +OUString SfxStyleDialogController::GenerateUnusedName(SfxStyleSheetBasePool &rPool, SfxStyleFamily eFam) +{ + OUString aNo(SfxResId(STR_NONAME)); + sal_uInt16 i = 1; + OUString aNoName = aNo + OUString::number(i); + while (rPool.Find(aNoName, eFam)) + { + ++i; + aNoName = aNo + OUString::number(i); + } + return aNoName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/tabdlg.cxx b/sfx2/source/dialog/tabdlg.cxx new file mode 100644 index 000000000..11a43a498 --- /dev/null +++ b/sfx2/source/dialog/tabdlg.cxx @@ -0,0 +1,1160 @@ +/* -*- 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 <stdlib.h> +#include <algorithm> +#include <string_view> + +#include <sfx2/tabdlg.hxx> +#include <sfx2/app.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/sfxdlg.hxx> +#include <sfx2/viewsh.hxx> +#include <unotools/viewoptions.hxx> +#include <vcl/virdev.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <comphelper/lok.hxx> + +#include <sfx2/strings.hrc> +#include <helpids.h> + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral USERITEM_NAME = u"UserItem"; + + +struct TabPageImpl +{ + bool mbStandard; + SfxOkDialogController* mpSfxDialogController; + css::uno::Reference< css::frame::XFrame > mxFrame; + + TabPageImpl() : mbStandard(false), mpSfxDialogController(nullptr) {} +}; + +namespace { + +struct Data_Impl +{ + OString sId; // The ID + CreateTabPage fnCreatePage; // Pointer to Factory + GetTabPageRanges fnGetRanges; // Pointer to Ranges-Function + std::unique_ptr<SfxTabPage> xTabPage; // The TabPage itself + bool bRefresh; // Flag: Page must be re-initialized + + // Constructor + Data_Impl( const OString& rId, CreateTabPage fnPage, + GetTabPageRanges fnRanges ) : + + sId ( rId ), + fnCreatePage( fnPage ), + fnGetRanges ( fnRanges ), + bRefresh ( false ) + { + } +}; + +} + +SfxTabDialogItem::SfxTabDialogItem( const SfxTabDialogItem& rAttr, SfxItemPool* pItemPool ) + : SfxSetItem( rAttr, pItemPool ) +{ +} + +SfxTabDialogItem::SfxTabDialogItem( sal_uInt16 nId, const SfxItemSet& rItemSet ) + : SfxSetItem( nId, rItemSet ) +{ +} + +SfxTabDialogItem* SfxTabDialogItem::Clone(SfxItemPool* pToPool) const +{ + return new SfxTabDialogItem( *this, pToPool ); +} + +typedef std::vector<Data_Impl*> SfxTabDlgData_Impl; + +struct TabDlg_Impl +{ + bool bHideResetBtn : 1; + bool bStarted : 1; + SfxTabDlgData_Impl aData; + + explicit TabDlg_Impl(sal_uInt8 nCnt) + : bHideResetBtn(false) + , bStarted(false) + { + aData.reserve( nCnt ); + } +}; + +static Data_Impl* Find( const SfxTabDlgData_Impl& rArr, std::string_view rId, sal_uInt16* pPos = nullptr) +{ + const sal_uInt16 nCount = rArr.size(); + + for ( sal_uInt16 i = 0; i < nCount; ++i ) + { + Data_Impl* pObj = rArr[i]; + + if ( pObj->sId == rId ) + { + if ( pPos ) + *pPos = i; + return pObj; + } + } + return nullptr; +} + +void SfxTabPage::SetFrame(const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + if (pImpl) + pImpl->mxFrame = xFrame; +} + +css::uno::Reference< css::frame::XFrame > SfxTabPage::GetFrame() const +{ + if (pImpl) + return pImpl->mxFrame; + return css::uno::Reference< css::frame::XFrame >(); +} + +SfxTabPage::SfxTabPage(weld::Container* pPage, weld::DialogController* pController, const OUString& rUIXMLDescription, const OString& rID, const SfxItemSet *rAttrSet) + : BuilderPage(pPage, pController, rUIXMLDescription, rID, + comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current() + && SfxViewShell::Current()->isLOKMobilePhone()) + , pSet ( rAttrSet ) + , bHasExchangeSupport ( false ) + , pImpl ( new TabPageImpl ) +{ + pImpl->mpSfxDialogController = dynamic_cast<SfxOkDialogController*>(m_pDialogController); +} + +SfxTabPage::~SfxTabPage() +{ + if (m_xContainer) + { + std::unique_ptr<weld::Container> xParent(m_xContainer->weld_parent()); + if (xParent) + xParent->move(m_xContainer.get(), nullptr); + } + m_xContainer.reset(); + pImpl.reset(); + m_xBuilder.reset(); +} + +bool SfxTabPage::FillItemSet( SfxItemSet* ) +{ + return false; +} + +void SfxTabPage::Reset( const SfxItemSet* ) +{ +} + +bool SfxTabPage::DeferResetToFirstActivation() { return false; } + +void SfxTabPage::ActivatePage( const SfxItemSet& ) +/* [Description] + + Default implementation of the virtual ActivatePage method. This method is + called when a page of dialogue supports the exchange of data between pages. + <SfxTabPage::DeactivatePage(SfxItemSet *)> +*/ +{ +} + +DeactivateRC SfxTabPage::DeactivatePage( SfxItemSet* ) + +/* [Description] + + Default implementation of the virtual DeactivatePage method. This method is + called by Sfx when leaving a page; the application can, through the return + value, control whether to leave the page. If the page is displayed through + bHasExchangeSupport which supports data exchange between pages, then a + pointer to the exchange set is passed as parameter. This takes on data for + the exchange, then the set is available as a parameter in + <SfxTabPage::ActivatePage(const SfxItemSet &)>. + + [Return value] + + DeactivateRC::LeavePage; Allow leaving the page +*/ + +{ + return DeactivateRC::LeavePage; +} + + +void SfxTabPage::FillUserData() + +/* [Description] + + Virtual method is called by the base class in the destructor to save + specific information of the TabPage in the ini-file. When overriding a + string must be compiled, which is then flushed with the <SetUserData()>. +*/ + +{ +} + + +bool SfxTabPage::IsReadOnly() const +{ + return false; +} + + +const SfxPoolItem* SfxTabPage::GetItem( const SfxItemSet& rSet, sal_uInt16 nSlot, bool bDeep ) + +/* [Description] + + static Method: hereby are the implementations of the TabPage code + being simplified. +*/ + +{ + const SfxItemPool* pPool = rSet.GetPool(); + sal_uInt16 nWh = pPool->GetWhich( nSlot, bDeep ); + const SfxPoolItem* pItem = nullptr; + rSet.GetItemState( nWh, true, &pItem ); + + if ( !pItem && nWh != nSlot ) + pItem = &pPool->GetDefaultItem( nWh ); + return pItem; +} + + +const SfxPoolItem* SfxTabPage::GetOldItem( const SfxItemSet& rSet, + sal_uInt16 nSlot, bool bDeep ) + +/* [Description] + + This method returns an attribute for comparison of the old value. +*/ + +{ + const SfxItemSet& rOldSet = GetItemSet(); + sal_uInt16 nWh = GetWhich( nSlot, bDeep ); + const SfxPoolItem* pItem = nullptr; + + if ( pImpl->mbStandard && rOldSet.GetParent() ) + pItem = GetItem( *rOldSet.GetParent(), nSlot ); + else if ( rSet.GetParent() && + SfxItemState::DONTCARE == rSet.GetItemState( nWh ) ) + pItem = GetItem( *rSet.GetParent(), nSlot ); + else + pItem = GetItem( rOldSet, nSlot ); + return pItem; +} + +void SfxTabPage::PageCreated( const SfxAllItemSet& /*aSet*/ ) +{ + SAL_WARN( "sfx.dialog", "SfxTabPage::PageCreated should not be called"); +} + +void SfxTabPage::ChangesApplied() +{ +} + +void SfxTabPage::SetDialogController(SfxOkDialogController* pDialog) +{ + pImpl->mpSfxDialogController = pDialog; + m_pDialogController = pImpl->mpSfxDialogController; +} + +SfxOkDialogController* SfxTabPage::GetDialogController() const +{ + return pImpl->mpSfxDialogController; +} + +OString SfxTabPage::GetHelpId() const +{ + if (m_xContainer) + return m_xContainer->get_help_id(); + return OString(); +} + +weld::Window* SfxTabPage::GetFrameWeld() const +{ + if (m_pDialogController) + return m_pDialogController->getDialog(); + return nullptr; +} + +const SfxItemSet* SfxTabPage::GetDialogExampleSet() const +{ + if (pImpl->mpSfxDialogController) + return pImpl->mpSfxDialogController->GetExampleSet(); + return nullptr; +} + +SfxTabDialogController::SfxTabDialogController +( + weld::Widget* pParent, // Parent Window + const OUString& rUIXMLDescription, const OString& rID, // Dialog .ui path, Dialog Name + const SfxItemSet* pItemSet, // Itemset with the data; + // can be NULL, when Pages are onDemand + bool bEditFmt // when yes -> additional Button for standard +) + : SfxOkDialogController(pParent, rUIXMLDescription, rID) + , m_xTabCtrl(m_xBuilder->weld_notebook("tabcontrol")) + , m_xOKBtn(m_xBuilder->weld_button("ok")) + , m_xApplyBtn(m_xBuilder->weld_button("apply")) + , m_xUserBtn(m_xBuilder->weld_button("user")) + , m_xCancelBtn(m_xBuilder->weld_button("cancel")) + , m_xResetBtn(m_xBuilder->weld_button("reset")) + , m_xBaseFmtBtn(m_xBuilder->weld_button("standard")) + , m_pSet(pItemSet ? new SfxItemSet(*pItemSet) : nullptr) + , m_bStandardPushed(false) +{ + m_pImpl.reset(new TabDlg_Impl(m_xTabCtrl->get_n_pages())); + m_pImpl->bHideResetBtn = !m_xResetBtn->get_visible(); + m_xOKBtn->connect_clicked(LINK(this, SfxTabDialogController, OkHdl)); + m_xCancelBtn->connect_clicked(LINK(this, SfxTabDialogController, CancelHdl)); + m_xResetBtn->connect_clicked(LINK(this, SfxTabDialogController, ResetHdl)); + m_xResetBtn->set_label(SfxResId(STR_RESET)); + m_xTabCtrl->connect_enter_page(LINK(this, SfxTabDialogController, ActivatePageHdl)); + m_xTabCtrl->connect_leave_page(LINK(this, SfxTabDialogController, DeactivatePageHdl)); + m_xResetBtn->set_help_id(HID_TABDLG_RESET_BTN); + + if (bEditFmt) + { + m_xBaseFmtBtn->set_label(SfxResId(STR_STANDARD_SHORTCUT)); + m_xBaseFmtBtn->connect_clicked(LINK(this, SfxTabDialogController, BaseFmtHdl)); + m_xBaseFmtBtn->set_help_id(HID_TABDLG_STANDARD_BTN); + m_xBaseFmtBtn->show(); + } + + if (m_xUserBtn) + m_xUserBtn->connect_clicked(LINK(this, SfxTabDialogController, UserHdl)); + + if (m_pSet) + { + m_xExampleSet.reset(new SfxItemSet(*m_pSet)); + m_pOutSet.reset(new SfxItemSet(*m_pSet->GetPool(), m_pSet->GetRanges())); + } + + // The reset functionality seems to be confusing to many; disable in LOK. + if (comphelper::LibreOfficeKit::isActive()) + RemoveResetButton(); +} + +IMPL_LINK_NOARG(SfxTabDialogController, OkHdl, weld::Button&, void) + +/* [Description] + + Handler of the Ok-Buttons + This calls the current page <SfxTabPage::DeactivatePage(SfxItemSet *)>. + Returns <DeactivateRC::LeavePage>, <SfxTabDialog::Ok()> is called + and the Dialog is ended. +*/ + +{ + if (PrepareLeaveCurrentPage()) + m_xDialog->response(Ok()); +} + +IMPL_LINK_NOARG(SfxTabDialogController, UserHdl, weld::Button&, void) + +/* [Description] + + Handler of the User-Buttons + This calls the current page <SfxTabPage::DeactivatePage(SfxItemSet *)>. + returns this <DeactivateRC::LeavePage> and <SfxTabDialog::Ok()> is called. + Then the Dialog is ended with the Return value <SfxTabDialog::Ok()> +*/ + +{ + if (PrepareLeaveCurrentPage()) + { + short nRet = Ok(); + if (RET_OK == nRet) + nRet = RET_USER; + else + nRet = RET_CANCEL; + m_xDialog->response(nRet); + } +} + +IMPL_LINK_NOARG(SfxTabDialogController, CancelHdl, weld::Button&, void) +{ + m_xDialog->response(RET_CANCEL); +} + +IMPL_LINK_NOARG(SfxTabDialogController, ResetHdl, weld::Button&, void) + +/* [Description] + + Handler behind the reset button. + The Current Page is new initialized with their initial data, all the + settings that the user has made on this page are repealed. +*/ + +{ + Data_Impl* pDataObject = Find(m_pImpl->aData, m_xTabCtrl->get_current_page_ident()); + assert(pDataObject && "Id not known"); + + pDataObject->xTabPage->Reset(m_pSet.get()); + // Also reset relevant items of ExampleSet and OutSet to initial state + if (!pDataObject->fnGetRanges) + return; + + if (!m_xExampleSet) + m_xExampleSet.reset(new SfxItemSet(*m_pSet)); + + const SfxItemPool* pPool = m_pSet->GetPool(); + const WhichRangesContainer& pTmpRanges = (pDataObject->fnGetRanges)(); + + for (const auto & rPair : pTmpRanges) + { + // Correct Range with multiple values + sal_uInt16 nTmp = rPair.first, nTmpEnd = rPair.second; + DBG_ASSERT(nTmp <= nTmpEnd, "Range is sorted the wrong way"); + + if (nTmp > nTmpEnd) + { + // If really sorted wrongly, then set new + std::swap(nTmp, nTmpEnd); + } + + while (nTmp && nTmp <= nTmpEnd) + { + // Iterate over the Range and set the Items + sal_uInt16 nWh = pPool->GetWhich(nTmp); + const SfxPoolItem* pItem; + if (SfxItemState::SET == m_pSet->GetItemState(nWh, false, &pItem)) + { + m_xExampleSet->Put(*pItem); + m_pOutSet->Put(*pItem); + } + else + { + m_xExampleSet->ClearItem(nWh); + m_pOutSet->ClearItem(nWh); + } + nTmp++; + } + } +} + +/* [Description] + + Handler behind the Standard-Button. + This button is available when editing style sheets. All the set attributes + in the edited stylesheet are deleted. +*/ +IMPL_LINK_NOARG(SfxTabDialogController, BaseFmtHdl, weld::Button&, void) +{ + m_bStandardPushed = true; + + Data_Impl* pDataObject = Find(m_pImpl->aData, m_xTabCtrl->get_current_page_ident()); + assert(pDataObject && "Id not known"); + + if (!pDataObject->fnGetRanges) + return; + + if (!m_xExampleSet) + m_xExampleSet.reset(new SfxItemSet(*m_pSet)); + + const SfxItemPool* pPool = m_pSet->GetPool(); + const WhichRangesContainer& pTmpRanges = (pDataObject->fnGetRanges)(); + SfxItemSet aTmpSet(*m_xExampleSet); + + for (const auto& rPair : pTmpRanges) + { + // Correct Range with multiple values + sal_uInt16 nTmp = rPair.first, nTmpEnd = rPair.second; + DBG_ASSERT( nTmp <= nTmpEnd, "Range is sorted the wrong way" ); + + if ( nTmp > nTmpEnd ) + { + // If really sorted wrongly, then set new + std::swap(nTmp, nTmpEnd); + } + + while ( nTmp && nTmp <= nTmpEnd ) // guard against overflow + { + // Iterate over the Range and set the Items + sal_uInt16 nWh = pPool->GetWhich(nTmp); + m_xExampleSet->ClearItem(nWh); + aTmpSet.ClearItem(nWh); + // At the Outset of InvalidateItem, + // so that the change takes effect + m_pOutSet->InvalidateItem(nWh); + nTmp++; + } + } + // Set all Items as new -> the call the current Page Reset() + assert(pDataObject->xTabPage && "the Page is gone"); + pDataObject->xTabPage->Reset( &aTmpSet ); + pDataObject->xTabPage->pImpl->mbStandard = true; +} + +IMPL_LINK(SfxTabDialogController, ActivatePageHdl, const OString&, rPage, void) + +/* [Description] + + Handler that is called by StarView for switching to a different page. + If possible the <SfxTabPage::Reset(const SfxItemSet &)> or + <SfxTabPage::ActivatePage(const SfxItemSet &)> is called on the new page +*/ + +{ + assert(!m_pImpl->aData.empty() && "no Pages registered"); + Data_Impl* pDataObject = Find(m_pImpl->aData, rPage); + if (!pDataObject) + { + SAL_WARN("sfx.dialog", "Tab Page ID '" << rPage << "' not known, this is pretty serious and needs investigation"); + return; + } + + SfxTabPage* pTabPage = pDataObject->xTabPage.get(); + if (!pTabPage) + return; + + if (pDataObject->bRefresh) + pTabPage->Reset(m_pSet.get()); + pDataObject->bRefresh = false; + + if (m_xExampleSet) + pTabPage->ActivatePage(*m_xExampleSet); + + if (pTabPage->IsReadOnly() || m_pImpl->bHideResetBtn) + m_xResetBtn->hide(); + else + m_xResetBtn->show(); +} + +IMPL_LINK(SfxTabDialogController, DeactivatePageHdl, const OString&, rPage, bool) + +/* [Description] + + Handler that is called by StarView before leaving a page. + + [Cross-reference] + + <SfxTabPage::DeactivatePage(SfxItemSet *)> +*/ + +{ + assert(!m_pImpl->aData.empty() && "no Pages registered"); + Data_Impl* pDataObject = Find(m_pImpl->aData, rPage); + if (!pDataObject) + { + SAL_WARN("sfx.dialog", "Tab Page ID not known, this is pretty serious and needs investigation"); + return false; + } + + SfxTabPage* pPage = pDataObject->xTabPage.get(); + if (!pPage) + return true; + + DeactivateRC nRet = DeactivateRC::LeavePage; + + if (!m_xExampleSet && pPage->HasExchangeSupport() && m_pSet) + m_xExampleSet.reset(new SfxItemSet(*m_pSet->GetPool(), m_pSet->GetRanges())); + + if (m_pSet) + { + SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() ); + + if (pPage->HasExchangeSupport()) + nRet = pPage->DeactivatePage(&aTmp); + else + nRet = pPage->DeactivatePage(nullptr); + if ( ( DeactivateRC::LeavePage & nRet ) == DeactivateRC::LeavePage && + aTmp.Count() && m_xExampleSet) + { + m_xExampleSet->Put( aTmp ); + m_pOutSet->Put( aTmp ); + } + } + else + { + if ( pPage->HasExchangeSupport() ) //!!! + { + if (!m_xExampleSet) + { + SfxItemPool* pPool = pPage->GetItemSet().GetPool(); + m_xExampleSet.reset(new SfxItemSet(*pPool, GetInputRanges(*pPool))); + } + nRet = pPage->DeactivatePage(m_xExampleSet.get()); + } + else + nRet = pPage->DeactivatePage( nullptr ); + } + + if ( nRet & DeactivateRC::RefreshSet ) + { + RefreshInputSet(); + // Flag all Pages as to be initialized as new + + for (auto const& elem : m_pImpl->aData) + { + elem->bRefresh = ( elem->xTabPage.get() != pPage ); // Do not refresh own Page anymore + } + } + return static_cast<bool>(nRet & DeactivateRC::LeavePage); +} + +bool SfxTabDialogController::PrepareLeaveCurrentPage() +{ + const OString sId = m_xTabCtrl->get_current_page_ident(); + Data_Impl* pDataObject = Find(m_pImpl->aData, sId); + DBG_ASSERT( pDataObject, "Id not known" ); + SfxTabPage* pPage = pDataObject ? pDataObject->xTabPage.get() : nullptr; + + bool bEnd = !pPage; + + if ( pPage ) + { + DeactivateRC nRet = DeactivateRC::LeavePage; + if ( m_pSet ) + { + SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() ); + + if ( pPage->HasExchangeSupport() ) + nRet = pPage->DeactivatePage( &aTmp ); + else + nRet = pPage->DeactivatePage( nullptr ); + + if ( ( DeactivateRC::LeavePage & nRet ) == DeactivateRC::LeavePage + && aTmp.Count() ) + { + m_xExampleSet->Put( aTmp ); + m_pOutSet->Put( aTmp ); + } + } + else + nRet = pPage->DeactivatePage( nullptr ); + bEnd = nRet != DeactivateRC::KeepPage; + } + + return bEnd; +} + +const WhichRangesContainer & SfxTabDialogController::GetInputRanges(const SfxItemPool& rPool) + +/* [Description] + + Makes the set over the range of all pages of the dialogue. Pages have the + static method for querying their range in AddTabPage, ie deliver their + sets onDemand. + + [Return value] + + Pointer to a null-terminated array of sal_uInt16. This array belongs to the + dialog and is deleted when the dialogue is destroy. + + [Cross-reference] + + <SfxTabDialog::AddTabPage(sal_uInt16, CreateTabPage, GetTabPageRanges, bool)> + <SfxTabDialog::AddTabPage(sal_uInt16, const String &, CreateTabPage, GetTabPageRanges, bool, sal_uInt16)> + <SfxTabDialog::AddTabPage(sal_uInt16, const Bitmap &, CreateTabPage, GetTabPageRanges, bool, sal_uInt16)> +*/ + +{ + if ( m_pSet ) + { + SAL_WARN( "sfx.dialog", "Set already exists!" ); + return m_pSet->GetRanges(); + } + + if ( !m_pRanges.empty() ) + return m_pRanges; + SfxItemSet aUS(const_cast<SfxItemPool&>(rPool)); + + for (auto const& elem : m_pImpl->aData) + { + + if ( elem->fnGetRanges ) + { + const WhichRangesContainer& pTmpRanges = (elem->fnGetRanges)(); + + for (const auto & rPair : pTmpRanges) + { + sal_uInt16 nWidFrom = rPool.GetWhich(rPair.first); + sal_uInt16 nWidTo = rPool.GetWhich(rPair.second); + aUS.MergeRange(nWidFrom, nWidTo); // Keep it valid + } + } + } + + m_pRanges = aUS.GetRanges(); + return m_pRanges; +} + +SfxTabDialogController::~SfxTabDialogController() +{ + SavePosAndId(); + + for (auto & elem : m_pImpl->aData) + { + if ( elem->xTabPage ) + { + // save settings of all pages (user data) + elem->xTabPage->FillUserData(); + OUString aPageData( elem->xTabPage->GetUserData() ); + if ( !aPageData.isEmpty() ) + { + // save settings of all pages (user data) + OUString sConfigId = OStringToOUString(elem->xTabPage->GetConfigId(), + RTL_TEXTENCODING_UTF8); + SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId); + aPageOpt.SetUserItem( USERITEM_NAME, Any( aPageData ) ); + } + + elem->xTabPage.reset(); + } + delete elem; + elem = nullptr; + } +} + +short SfxTabDialogController::Ok() + +/* [Description] + + Ok handler for the Dialogue. + + Dialog's current location and current page are saved for the next time + the dialog is shown. + + The OutputSet is created and for each page this or the special OutputSet + is set by calling the method <SfxTabPage::FillItemSet(SfxItemSet &)>, to + insert the entered data by the user into the set. + + [Return value] + + RET_OK: if at least one page has returned from FillItemSet, + otherwise RET_CANCEL. +*/ +{ + SavePosAndId(); //See fdo#38828 "Apply" resetting window position + + if ( !m_pOutSet ) + { + if ( m_xExampleSet ) + m_pOutSet.reset(new SfxItemSet( *m_xExampleSet )); + else if ( m_pSet ) + m_pOutSet = m_pSet->Clone( false ); // without Items + } + bool bModified = false; + + for (auto const& elem : m_pImpl->aData) + { + SfxTabPage* pTabPage = elem->xTabPage.get(); + + if ( pTabPage ) + { + if ( m_pSet && !pTabPage->HasExchangeSupport() ) + { + SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() ); + + if ( pTabPage->FillItemSet( &aTmp ) ) + { + bModified = true; + if (m_xExampleSet) + m_xExampleSet->Put( aTmp ); + m_pOutSet->Put( aTmp ); + } + } + } + } + + if (m_pOutSet && m_pOutSet->Count() > 0) + bModified = true; + + if (m_bStandardPushed) + bModified = true; + + return bModified ? RET_OK : RET_CANCEL; +} + +void SfxTabDialogController::RefreshInputSet() + +/* [Description] + + Default implementation of the virtual Method. + This is called, when <SfxTabPage::DeactivatePage(SfxItemSet *)> + returns <DeactivateRC::RefreshSet>. +*/ + +{ + SAL_INFO ( "sfx.dialog", "RefreshInputSet not implemented" ); +} + +void SfxTabDialogController::PageCreated + +/* [Description] + + Default implementation of the virtual method. This is called immediately + after creating a page. Here the dialogue can call the TabPage Method + directly. +*/ + +( + const OString&, // Id of the created page + SfxTabPage& // Reference to the created page +) +{ +} + +void SfxTabDialogController::SavePosAndId() +{ + // save settings (screen position and current page) + SvtViewOptions aDlgOpt(EViewType::TabDialog, OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8)); + aDlgOpt.SetPageID(m_xTabCtrl->get_current_page_ident()); +} + +/* + Adds a page to the dialog. The Name must correspond to an entry in the + TabControl in the dialog .ui +*/ +void SfxTabDialogController::AddTabPage(const OString &rName /* Page ID */, + CreateTabPage pCreateFunc /* Pointer to the Factory Method */, + GetTabPageRanges pRangesFunc /* Pointer to the Method for querying Ranges onDemand */) +{ + m_pImpl->aData.push_back(new Data_Impl(rName, pCreateFunc, pRangesFunc)); +} + +void SfxTabDialogController::AddTabPage(const OString &rName /* Page ID */, + sal_uInt16 nPageCreateId /* Identifier of the Factory Method to create the page */) +{ + SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create(); + CreateTabPage pCreateFunc = pFact->GetTabPageCreatorFunc(nPageCreateId); + GetTabPageRanges pRangesFunc = pFact->GetTabPageRangesFunc(nPageCreateId); + AddTabPage(rName, pCreateFunc, pRangesFunc); +} + +/* [Description] + + Add a page to the dialog. The Rider text is passed on, the page has no + counterpart in the TabControl in the resource of the dialogue. +*/ + +void SfxTabDialogController::AddTabPage(const OString &rName, /* Page ID */ + const OUString& rRiderText, + CreateTabPage pCreateFunc /* Pointer to the Factory Method */) +{ + assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage"); + m_xTabCtrl->append_page(rName, rRiderText); + AddTabPage(rName, pCreateFunc, nullptr); +} + +void SfxTabDialogController::AddTabPage(const OString &rName, const OUString& rRiderText, + sal_uInt16 nPageCreateId /* Identifier of the Factory Method to create the page */) +{ + assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage"); + m_xTabCtrl->append_page(rName, rRiderText); + AddTabPage(rName, nPageCreateId); +} + +/* [Description] + + Default implementation of the virtual Method. + This is called when pages create their sets onDemand. +*/ +SfxItemSet* SfxTabDialogController::CreateInputItemSet(const OString&) +{ + SAL_WARN( "sfx.dialog", "CreateInputItemSet not implemented" ); + m_xItemSet = std::make_unique<SfxAllItemSet>(SfxGetpApp()->GetPool()); + return m_xItemSet.get(); +} + +void SfxTabDialogController::CreatePages() +{ + for (auto pDataObject : m_pImpl->aData) + { + if (pDataObject->xTabPage) + continue; + weld::Container* pPage = m_xTabCtrl->get_page(pDataObject->sId); + if (m_pSet) + pDataObject->xTabPage = (pDataObject->fnCreatePage)(pPage, this, m_pSet.get()); + else + pDataObject->xTabPage = (pDataObject->fnCreatePage)(pPage, this, CreateInputItemSet(pDataObject->sId)); + pDataObject->xTabPage->SetDialogController(this); + OUString sConfigId = OStringToOUString(pDataObject->xTabPage->GetConfigId(), RTL_TEXTENCODING_UTF8); + SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId); + OUString sUserData; + Any aUserItem = aPageOpt.GetUserItem(USERITEM_NAME); + OUString aTemp; + if ( aUserItem >>= aTemp ) + sUserData = aTemp; + pDataObject->xTabPage->SetUserData(sUserData); + + PageCreated(pDataObject->sId, *pDataObject->xTabPage); + if (pDataObject->xTabPage->DeferResetToFirstActivation()) + pDataObject->bRefresh = true; // Reset will be called in ActivatePageHdl + else + pDataObject->xTabPage->Reset(m_pSet.get()); + } +} + +void SfxTabDialogController::setPreviewsToSamePlace() +{ + //where tab pages have the same basic layout with a preview on the right, + //get both of their non-preview areas to request the same size so that the + //preview appears in the same place in each one so flipping between tabs + //isn't distracting as it jumps around + std::vector<std::unique_ptr<weld::Widget>> aGrids; + for (auto pDataObject : m_pImpl->aData) + { + if (!pDataObject->xTabPage) + continue; + if (!pDataObject->xTabPage->m_xBuilder) + continue; + std::unique_ptr<weld::Widget> pGrid = pDataObject->xTabPage->m_xBuilder->weld_widget("maingrid"); + if (!pGrid) + continue; + aGrids.emplace_back(std::move(pGrid)); + } + + m_xSizeGroup.reset(); + + if (aGrids.size() <= 1) + return; + + m_xSizeGroup = m_xBuilder->create_size_group(); + m_xSizeGroup->set_mode(VclSizeGroupMode::Both); + for (auto& rGrid : aGrids) + m_xSizeGroup->add_widget(rGrid.get()); +} + +void SfxTabDialogController::RemoveTabPage(const OString& rId) + +/* [Description] + + Delete the TabPage with ID nId +*/ + +{ + sal_uInt16 nPos = 0; + m_xTabCtrl->remove_page(rId); + Data_Impl* pDataObject = Find( m_pImpl->aData, rId, &nPos ); + + if ( pDataObject ) + { + if ( pDataObject->xTabPage ) + { + pDataObject->xTabPage->FillUserData(); + OUString aPageData( pDataObject->xTabPage->GetUserData() ); + if ( !aPageData.isEmpty() ) + { + // save settings of this page (user data) + OUString sConfigId = OStringToOUString(pDataObject->xTabPage->GetConfigId(), + RTL_TEXTENCODING_UTF8); + SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId); + aPageOpt.SetUserItem( USERITEM_NAME, Any( aPageData ) ); + } + + pDataObject->xTabPage.reset(); + } + + delete pDataObject; + m_pImpl->aData.erase( m_pImpl->aData.begin() + nPos ); + } + else + { + SAL_INFO( "sfx.dialog", "TabPage-Id not known" ); + } +} + +void SfxTabDialogController::Start_Impl() +{ + CreatePages(); + + setPreviewsToSamePlace(); + + assert(m_pImpl->aData.size() == static_cast<size_t>(m_xTabCtrl->get_n_pages()) + && "not all pages registered"); + + // load old settings, when exists, setting SetCurPageId will override the settings, + // something that the sort dialog in calc depends on + if (m_sAppPageId.isEmpty()) + { + SvtViewOptions aDlgOpt(EViewType::TabDialog, OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8)); + if (aDlgOpt.Exists()) + m_xTabCtrl->set_current_page(aDlgOpt.GetPageID()); + } + + ActivatePageHdl(m_xTabCtrl->get_current_page_ident()); + + m_pImpl->bStarted = true; +} + +void SfxTabDialogController::SetCurPageId(const OString& rIdent) +{ + m_sAppPageId = rIdent; + m_xTabCtrl->set_current_page(m_sAppPageId); +} + +/* [Description] + + The TabPage is activated with the specified Id. +*/ +void SfxTabDialogController::ShowPage(const OString& rIdent) +{ + SetCurPageId(rIdent); + ActivatePageHdl(rIdent); +} + +OString SfxTabDialogController::GetCurPageId() const +{ + return m_xTabCtrl->get_current_page_ident(); +} + +short SfxTabDialogController::run() +{ + Start_Impl(); + return SfxDialogController::run(); +} + +bool SfxTabDialogController::runAsync(const std::shared_ptr<SfxTabDialogController>& rController, + const std::function<void(sal_Int32)>& rFunc) +{ + rController->Start_Impl(); + return weld::DialogController::runAsync(rController, rFunc); +} + +void SfxTabDialogController::SetInputSet( const SfxItemSet* pInSet ) + +/* [Description] + + With this method the Input-Set can subsequently be set initially or re-set. +*/ + +{ + bool bSet = ( m_pSet != nullptr ); + m_pSet.reset(pInSet ? new SfxItemSet(*pInSet) : nullptr); + + if (!bSet && !m_xExampleSet && !m_pOutSet && m_pSet) + { + m_xExampleSet.reset(new SfxItemSet(*m_pSet)); + m_pOutSet.reset(new SfxItemSet( *m_pSet->GetPool(), m_pSet->GetRanges() )); + } +} + +SfxItemSet* SfxTabDialogController::GetInputSetImpl() + +/* [Description] + + Derived classes may create new storage for the InputSet. This has to be + released in the Destructor. To do this, this method must be called. +*/ + +{ + return m_pSet.get(); +} + +void SfxTabDialogController::RemoveResetButton() +{ + m_xResetBtn->hide(); + m_pImpl->bHideResetBtn = true; +} + +void SfxTabDialogController::RemoveStandardButton() +{ + m_xBaseFmtBtn->hide(); +} + +SfxTabPage* SfxTabDialogController::GetTabPage(std::string_view rPageId) const + +/* [Description] + + Return TabPage with the specified Id. +*/ + +{ + Data_Impl* pDataObject = Find(m_pImpl->aData, rPageId); + if (pDataObject) + return pDataObject->xTabPage.get(); + return nullptr; +} + +void SfxTabDialogController::SetApplyHandler(const Link<weld::Button&, void>& _rHdl) +{ + DBG_ASSERT( m_xApplyBtn, "SfxTabDialog::GetApplyHandler: no apply button enabled!" ); + if (m_xApplyBtn) + m_xApplyBtn->connect_clicked(_rHdl); +} + +bool SfxTabDialogController::Apply() +{ + bool bApplied = false; + if (PrepareLeaveCurrentPage()) + { + bApplied = (Ok() == RET_OK); + //let the pages update their saved values + GetInputSetImpl()->Put(*GetOutputItemSet()); + for (auto pDataObject : m_pImpl->aData) + { + if (!pDataObject->xTabPage) + continue; + pDataObject->xTabPage->ChangesApplied(); + } + } + return bApplied; +} + +std::vector<OString> SfxTabDialogController::getAllPageUIXMLDescriptions() const +{ + int nPages = m_xTabCtrl->get_n_pages(); + std::vector<OString> aRet; + aRet.reserve(nPages); + for (int i = 0; i < nPages; ++i) + aRet.push_back(m_xTabCtrl->get_page_ident(i)); + return aRet; +} + +bool SfxTabDialogController::selectPageByUIXMLDescription(const OString& rUIXMLDescription) +{ + ShowPage(rUIXMLDescription); + return m_xTabCtrl->get_current_page_ident() == rUIXMLDescription; +} + +BitmapEx SfxTabDialogController::createScreenshot() const +{ + // if we haven't run Start_Impl yet, do so now to create the initial pages + if (!m_pImpl->bStarted) + { + const_cast<SfxTabDialogController*>(this)->Start_Impl(); + } + + VclPtr<VirtualDevice> xDialogSurface(m_xDialog->screenshot()); + return xDialogSurface->GetBitmapEx(Point(), xDialogSurface->GetOutputSizePixel()); +} + +OString SfxTabDialogController::GetScreenshotId() const +{ + const OString sId = m_xTabCtrl->get_current_page_ident(); + Data_Impl* pDataObject = Find(m_pImpl->aData, sId); + SfxTabPage* pPage = pDataObject ? pDataObject->xTabPage.get() : nullptr; + if (pPage) + { + OString sHelpId(pPage->GetHelpId()); + if (!sHelpId.isEmpty()) + return sHelpId; + } + return m_xDialog->get_help_id(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/templdlg.cxx b/sfx2/source/dialog/templdlg.cxx new file mode 100644 index 000000000..e1db058db --- /dev/null +++ b/sfx2/source/dialog/templdlg.cxx @@ -0,0 +1,909 @@ +/* -*- 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 <vcl/commandinfoprovider.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <svl/style.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/UnknownModuleException.hpp> +#include <officecfg/Office/Common.hxx> + +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> +#include <sfx2/app.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/templdlg.hxx> +#include <templdgi.hxx> +#include <sfx2/styfitem.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/tplpitem.hxx> +#include <sfx2/sfxresid.hxx> + +#include <sfx2/sfxsids.hrc> +#include <sfx2/strings.hrc> +#include <helpids.h> +#include <sfx2/viewfrm.hxx> + +using namespace css; +using namespace css::beans; +using namespace css::frame; +using namespace css::uno; + +class SfxCommonTemplateDialog_Impl::DeletionWatcher +{ + typedef void (DeletionWatcher::* bool_type)(); + +public: + explicit DeletionWatcher(SfxCommonTemplateDialog_Impl& rDialog) + : m_pDialog(&rDialog) + , m_pPrevious(m_pDialog->impl_setDeletionWatcher(this)) + { + } + + ~DeletionWatcher() + { + if (m_pDialog) + m_pDialog->impl_setDeletionWatcher(m_pPrevious); + } + + DeletionWatcher(const DeletionWatcher&) = delete; + DeletionWatcher& operator=(const DeletionWatcher&) = delete; + + // Signal that the dialog was deleted + void signal() + { + m_pDialog = nullptr; + if (m_pPrevious) + m_pPrevious->signal(); + } + + // Return true if the dialog was deleted + operator bool_type() const + { + return m_pDialog ? nullptr : &DeletionWatcher::signal; + } + +private: + SfxCommonTemplateDialog_Impl* m_pDialog; + DeletionWatcher *const m_pPrevious; /// let's add more epicycles! +}; + +sal_Int8 SfxCommonTemplateDialog_Impl::ExecuteDrop(const ExecuteDropEvent& rEvt) +{ + // handle drop of content into the treeview to create a new style + m_aStyleListExecuteDrop.Call(rEvt); + return DND_ACTION_NONE; +} + +IMPL_LINK(SfxCommonTemplateDialog_Impl, OnAsyncExecuteDrop, void*, pStyleList, void) +{ + StyleList* pStyle = static_cast<StyleList*>(pStyleList); + if (pStyle == &m_aStyleList) + ActionSelect("new", m_aStyleList); +} + +SfxTemplatePanelControl::SfxTemplatePanelControl(SfxBindings* pBindings, weld::Widget* pParent) + : PanelLayout(pParent, "TemplatePanel", "sfx/ui/templatepanel.ui") + , pImpl(new SfxTemplateDialog_Impl(pBindings, this)) +{ + OSL_ASSERT(pBindings!=nullptr); +} + +SfxTemplatePanelControl::~SfxTemplatePanelControl() +{ +} + +namespace SfxTemplate +{ + // converts from SFX_STYLE_FAMILY Ids to 1-6 + static sal_uInt16 SfxFamilyIdToNId(SfxStyleFamily nFamily) + { + switch ( nFamily ) + { + case SfxStyleFamily::Char: return 1; + case SfxStyleFamily::Para: return 2; + case SfxStyleFamily::Frame: return 3; + case SfxStyleFamily::Page: return 4; + case SfxStyleFamily::Pseudo: return 5; + case SfxStyleFamily::Table: return 6; + default: return 0xffff; + } + } +} + +void SfxCommonTemplateDialog_Impl::connect_stylelist_execute_drop( + const Link<const ExecuteDropEvent&, sal_Int8>& rLink) +{ + m_aStyleListExecuteDrop = rLink; +} + +void SfxCommonTemplateDialog_Impl::connect_stylelist_has_selected_style(const Link<void*, bool>& rLink) +{ + m_aStyleListHasSelectedStyle = rLink; +} + +void SfxCommonTemplateDialog_Impl::connect_stylelist_update_style_dependents(const Link<void*, void>& rLink) +{ + m_aStyleListUpdateStyleDependents = rLink; +} + +void SfxCommonTemplateDialog_Impl::connect_stylelist_enable_tree_drag(const Link<bool, void> rLink) +{ + m_aStyleListEnableTreeDrag = rLink; +} + +void SfxCommonTemplateDialog_Impl::connect_stylelist_enable_delete(const Link<void*, void> rLink) +{ + m_aStyleListEnableDelete = rLink; +} + +void SfxCommonTemplateDialog_Impl::connect_stylelist_set_water_can_state( + const Link<const SfxBoolItem*, void> rLink) +{ + m_aStyleListSetWaterCanState = rLink; +} + +// Constructor + +SfxCommonTemplateDialog_Impl::SfxCommonTemplateDialog_Impl(SfxBindings* pB, weld::Container* pC, weld::Builder* pBuilder) + : pBindings(pB) + , mpContainer(pC) + , xModuleManager(frame::ModuleManager::create(::comphelper::getProcessComponentContext())) + , m_pDeletionWatcher(nullptr) + , m_aStyleList(pBuilder, pB, this, pC, "treeview", "flatview") + , mxPreviewCheckbox(pBuilder->weld_check_button("showpreview")) + , mxFilterLb(pBuilder->weld_combo_box("filter")) + , nActFamily(0xffff) + , nActFilter(0) + , bIsWater(false) + , bUpdate(false) + , bWaterDisabled(false) + , bNewByExampleDisabled(false) + , bUpdateByExampleDisabled(false) + , m_bWantHierarchical(false) +{ + mxFilterLb->set_help_id(HID_TEMPLATE_FILTER); + mxPreviewCheckbox->set_active(officecfg::Office::Common::StylesAndFormatting::Preview::get()); +} + +void SfxTemplateDialog_Impl::EnableEdit(bool bEnable, StyleList* rStyleList) +{ + if(rStyleList == &m_aStyleList || rStyleList == nullptr) + SfxCommonTemplateDialog_Impl::EnableEdit( bEnable, &m_aStyleList ); + if( !bEnable || !bUpdateByExampleDisabled ) + EnableItem("update", bEnable); +} + +IMPL_LINK(SfxCommonTemplateDialog_Impl, ReadResource_Hdl, StyleList&, rStyleList, void) +{ + nActFilter = 0xffff; + + SfxViewFrame* pViewFrame = pBindings->GetDispatcher_Impl()->GetFrame(); + SfxObjectShell* pCurObjShell = pViewFrame->GetObjectShell(); + if (pCurObjShell) + { + nActFilter = static_cast<sal_uInt16>(LoadFactoryStyleFilter_Hdl(pCurObjShell)); + if (0xffff == nActFilter) + { + nActFilter = pCurObjShell->GetAutoStyleFilterIndex(); + } + } + + size_t nCount = m_aStyleListReadResource.Call(nullptr); + +// Insert in the reverse order of occurrence in the Style Families. This is for +// the toolbar of the designer. The list box of the catalog respects the +// correct order by itself. + +// Sequences: the order of Resource = the order of Toolbar for example list box. +// Order of ascending SIDs: Low SIDs are displayed first when templates of +// several families are active. + + // in the Writer the UpdateStyleByExample Toolbox button is removed and + // the NewStyle button gets a PopupMenu + if(nCount > 4) + ReplaceUpdateButtonByMenu(); + + for( ; nCount--; ) + { + const SfxStyleFamilyItem &rItem = rStyleList.GetFamilyItemByIndex( nCount ); + sal_uInt16 nId = SfxTemplate::SfxFamilyIdToNId( rItem.GetFamily() ); + InsertFamilyItem(nId, rItem); + } +} + +IMPL_LINK_NOARG(SfxCommonTemplateDialog_Impl, ClearResource_Hdl, void*, void) +{ + ClearFamilyList(); + m_aStyleListClear.Call(nullptr); +} + +SfxCommonTemplateDialog_Impl::DeletionWatcher * +SfxCommonTemplateDialog_Impl::impl_setDeletionWatcher( + DeletionWatcher *const pNewWatcher) +{ + DeletionWatcher *const pRet(m_pDeletionWatcher); + m_pDeletionWatcher = pNewWatcher; + return pRet; +} + +void SfxCommonTemplateDialog_Impl::Initialize() +{ + m_aStyleList.connect_ReadResource(LINK(this, SfxCommonTemplateDialog_Impl, ReadResource_Hdl)); + m_aStyleList.connect_ClearResource(LINK(this, SfxCommonTemplateDialog_Impl, ClearResource_Hdl)); + m_aStyleList.connect_LoadFactoryStyleFilter(LINK(this, SfxCommonTemplateDialog_Impl, LoadFactoryStyleFilter_Hdl)); + m_aStyleList.connect_SaveSelection(LINK(this, SfxCommonTemplateDialog_Impl, SaveSelection_Hdl)); + m_aStyleList.connect_UpdateFamily(LINK(this, SfxCommonTemplateDialog_Impl, UpdateFamily_Hdl)); + m_aStyleList.connect_UpdateStyles(LINK(this, SfxCommonTemplateDialog_Impl, UpdateStyles_Hdl)); + + mxFilterLb->connect_changed(LINK(this, SfxCommonTemplateDialog_Impl, FilterSelectHdl)); + mxPreviewCheckbox->connect_toggled(LINK(this, SfxCommonTemplateDialog_Impl, PreviewHdl)); + m_aStyleList.Initialize(); +} + +IMPL_LINK(SfxCommonTemplateDialog_Impl, UpdateStyles_Hdl, StyleFlags, nFlags, void) +{ + const SfxStyleFamilyItem* pItem = m_aStyleList.GetFamilyItem(); + + if (nFlags & StyleFlags::UpdateFamily) // Update view type list (Hierarchical, All, etc. + { + CheckItem(OString::number(nActFamily)); // check Button in Toolbox + + mxFilterLb->freeze(); + mxFilterLb->clear(); + + //insert hierarchical at the beginning + mxFilterLb->append(OUString::number(static_cast<int>(SfxStyleSearchBits::All)), + SfxResId(STR_STYLE_FILTER_HIERARCHICAL)); + const SfxStyleFilter& rFilter = pItem->GetFilterList(); + for (const SfxFilterTuple& i : rFilter) + mxFilterLb->append(OUString::number(static_cast<int>(i.nFlags)), i.aName); + mxFilterLb->thaw(); + + if (nActFilter < mxFilterLb->get_count() - 1) + mxFilterLb->set_active(nActFilter + 1); + else + { + nActFilter = 0; + m_aStyleList.FilterSelect(nActFilter, false); + mxFilterLb->set_active(1); + } + + // if the tree view again, select family hierarchy + if (m_aStyleList.IsTreeView() || m_bWantHierarchical) + { + mxFilterLb->set_active_text(SfxResId(STR_STYLE_FILTER_HIERARCHICAL)); + EnableHierarchical(true, m_aStyleList); + } + } + else + { + if (nActFilter < mxFilterLb->get_count() - 1) + mxFilterLb->set_active(nActFilter + 1); + else + { + nActFilter = 0; + m_aStyleList.FilterSelect(nActFilter, false); + mxFilterLb->set_active(1); + } + } + + if (!(nFlags & StyleFlags::UpdateFamilyList)) + return; + + EnableItem("watercan", false); +} + +SfxCommonTemplateDialog_Impl::~SfxCommonTemplateDialog_Impl() +{ + if ( bIsWater ) + Execute_Impl(SID_STYLE_WATERCAN, "", "", 0, m_aStyleList); + m_aStyleListClear.Call(nullptr); + m_aStyleListCleanup.Call(nullptr); + if ( m_pDeletionWatcher ) + m_pDeletionWatcher->signal(); + mxPreviewCheckbox.reset(); + mxFilterLb.reset(); +} + +/** + * Is it safe to show the water-can / fill icon. If we've a + * hierarchical widget - we have only single select, otherwise + * we need to check if we have a multi-selection. We either have + * a mxTreeBox showing or an mxFmtLb (which we hide when not shown) + */ +bool SfxCommonTemplateDialog_Impl::IsSafeForWaterCan() const +{ + return m_aStyleListWaterCan.Call(nullptr); +} + +void SfxCommonTemplateDialog_Impl::SelectStyle(const OUString &rStr, bool bIsCallback, StyleList& rStyleList) +{ + rStyleList.SelectStyle(rStr, bIsCallback); + + bWaterDisabled = !IsSafeForWaterCan(); + + // tdf#134598 call UpdateStyleDependents to update watercan + UpdateStyleDependents_Hdl(nullptr); +} + +void SfxCommonTemplateDialog_Impl::EnableTreeDrag(bool bEnable) +{ + m_aStyleListEnableTreeDrag.Call(bEnable); +} + +// Updated display: Watering the house +void SfxCommonTemplateDialog_Impl::SetWaterCanState(const SfxBoolItem *pItem) +{ + bWaterDisabled = (pItem == nullptr); + + if(!bWaterDisabled) + //make sure the watercan is only activated when there is (only) one selection + bWaterDisabled = !IsSafeForWaterCan(); + + if(pItem && !bWaterDisabled) + { + CheckItem("watercan", pItem->GetValue()); + EnableItem("watercan"); + } + else + { + if(!bWaterDisabled) + EnableItem("watercan"); + else + EnableItem("watercan", false); + } + +// Ignore while in watercan mode statusupdates + + m_aStyleListSetWaterCanState.Call(pItem); +} + +// Item with the status of a Family is copied and noted +// (is updated when all states have also been updated.) +// See also: <SfxBindings::AddDoneHdl(const Link &)> +void SfxCommonTemplateDialog_Impl::SetFamilyState( sal_uInt16 nSlotId, const SfxTemplateItem* pItem ) +{ + m_aStyleList.SetFamilyState(nSlotId, pItem); + bUpdate = true; +} + +// Internal: Perform functions through the Dispatcher +bool SfxCommonTemplateDialog_Impl::Execute_Impl( + sal_uInt16 nId, const OUString &rStr, const OUString& rRefStr, sal_uInt16 nFamily, StyleList& rStyleList, + SfxStyleSearchBits nMask, sal_uInt16 *pIdx, const sal_uInt16* pModifier) +{ + SfxDispatcher &rDispatcher = *SfxGetpApp()->GetDispatcher_Impl(); + SfxStringItem aItem(nId, rStr); + SfxUInt16Item aFamily(SID_STYLE_FAMILY, nFamily); + SfxUInt16Item aMask( SID_STYLE_MASK, static_cast<sal_uInt16>(nMask) ); + SfxStringItem aUpdName(SID_STYLE_UPD_BY_EX_NAME, rStr); + SfxStringItem aRefName( SID_STYLE_REFERENCE, rRefStr ); + const SfxPoolItem* pItems[ 6 ]; + sal_uInt16 nCount = 0; + if( !rStr.isEmpty() ) + pItems[ nCount++ ] = &aItem; + pItems[ nCount++ ] = &aFamily; + if( nMask != SfxStyleSearchBits::Auto ) + pItems[ nCount++ ] = &aMask; + if(SID_STYLE_UPDATE_BY_EXAMPLE == nId) + { + // Special solution for Numbering update in Writer + const OUString aTemplName(rStyleList.GetSelectedEntry()); + aUpdName.SetValue(aTemplName); + pItems[ nCount++ ] = &aUpdName; + } + + if ( !rRefStr.isEmpty() ) + pItems[ nCount++ ] = &aRefName; + + pItems[ nCount++ ] = nullptr; + + DeletionWatcher aDeleted(*this); + sal_uInt16 nModi = pModifier ? *pModifier : 0; + const SfxPoolItem* pItem = rDispatcher.Execute( + nId, SfxCallMode::SYNCHRON | SfxCallMode::RECORD, + pItems, nModi ); + + // Dialog can be destroyed while in Execute() because started + // subdialogs are not modal to it (#i97888#). + if ( !pItem || aDeleted ) + return false; + + if ((nId == SID_STYLE_NEW || SID_STYLE_EDIT == nId) + && rStyleList.EnableExecute()) + { + const SfxUInt16Item *pFilterItem = dynamic_cast< const SfxUInt16Item* >(pItem); + assert(pFilterItem); + SfxStyleSearchBits nFilterFlags = static_cast<SfxStyleSearchBits>(pFilterItem->GetValue()) & ~SfxStyleSearchBits::UserDefined; + if(nFilterFlags == SfxStyleSearchBits::Auto) // User Template? + nFilterFlags = static_cast<SfxStyleSearchBits>(pFilterItem->GetValue()); + const SfxStyleFamilyItem *pFamilyItem = rStyleList.GetFamilyItem(); + const size_t nFilterCount = pFamilyItem->GetFilterList().size(); + + for ( size_t i = 0; i < nFilterCount; ++i ) + { + const SfxFilterTuple &rTupel = pFamilyItem->GetFilterList()[ i ]; + + if ( ( rTupel.nFlags & nFilterFlags ) == nFilterFlags && pIdx ) + *pIdx = i; + } + } + + return true; +} + +// Handler Listbox of Filter +void SfxCommonTemplateDialog_Impl::EnableHierarchical(bool const bEnable, StyleList& rStyleList) +{ + if (bEnable) + { + if (!rStyleList.IsHierarchical()) + { + // Turn on treeView + m_bWantHierarchical = true; + SaveSelection_Hdl(rStyleList); // fdo#61429 store "hierarchical" + m_aStyleList.SetHierarchical(); + } + } + else + { + m_aStyleList.SetFilterControlsHandle(); + // If bHierarchical, then the family can have changed + // minus one since hierarchical is inserted at the start + m_bWantHierarchical = false; // before FilterSelect + FilterSelect(mxFilterLb->get_active() - 1, rStyleList.IsHierarchical() ); + } +} + +// Other filters; can be switched by the users or as a result of new or +// editing, if the current document has been assigned a different filter. +void SfxCommonTemplateDialog_Impl::FilterSelect( + sal_uInt16 nEntry, // Idx of the new Filters + bool bForce) // Force update, even if the new filter is equal to the current +{ + if (nEntry == nActFilter && !bForce) + return; + + nActFilter = nEntry; + m_aStyleList.FilterSelect(nActFilter, true); +} + +void SfxCommonTemplateDialog_Impl::IsUpdate(StyleList&) +{ + SfxViewFrame* pViewFrame = pBindings->GetDispatcher_Impl()->GetFrame(); + SfxObjectShell* pDocShell = pViewFrame->GetObjectShell(); + nActFilter = static_cast<sal_uInt16>(LoadFactoryStyleFilter_Hdl(pDocShell)); + if (0xffff == nActFilter) + { + nActFilter = pDocShell->GetAutoStyleFilterIndex(); + } +} + +IMPL_LINK(SfxCommonTemplateDialog_Impl, FilterSelectHdl, weld::ComboBox&, rBox, void) +{ + if (SfxResId(STR_STYLE_FILTER_HIERARCHICAL) == rBox.get_active_text()) + { + EnableHierarchical(true, m_aStyleList); + } + else + { + EnableHierarchical(false, m_aStyleList); + } +} + +// Select-Handler for the Toolbox +void SfxCommonTemplateDialog_Impl::FamilySelect(sal_uInt16 nEntry, StyleList&, bool bPreviewRefresh) +{ + assert((0 < nEntry && nEntry <= MAX_FAMILIES) || 0xffff == nEntry); + if( nEntry != nActFamily || bPreviewRefresh ) + { + CheckItem(OString::number(nActFamily), false); + nActFamily = nEntry; + m_aStyleList.FamilySelect(nEntry); + } +} + +void SfxCommonTemplateDialog_Impl::ActionSelect(const OString& rEntry, StyleList& rStyleList) +{ + if (rEntry == "watercan") + { + const bool bOldState = !IsCheckedItem(rEntry); + bool bCheck; + SfxBoolItem aBool; + // when a template is chosen. + if (!bOldState && m_aStyleListHasSelectedStyle.Call(nullptr)) + { + const OUString aTemplName(rStyleList.GetSelectedEntry()); + Execute_Impl(SID_STYLE_WATERCAN, aTemplName, "", + static_cast<sal_uInt16>(m_aStyleList.GetFamilyItem()->GetFamily()), rStyleList); + bCheck = true; + } + else + { + Execute_Impl(SID_STYLE_WATERCAN, "", "", 0, rStyleList); + bCheck = false; + } + CheckItem(rEntry, bCheck); + aBool.SetValue(bCheck); + SetWaterCanState(&aBool); + } + else if (rEntry == "new" || rEntry == "newmenu") + { + m_aStyleListNewMenu.Call(nullptr); + } + else if (rEntry == "update") + { + Execute_Impl(SID_STYLE_UPDATE_BY_EXAMPLE, + "", "", + static_cast<sal_uInt16>(m_aStyleList.GetFamilyItem()->GetFamily()), rStyleList); + } + else if (rEntry == "load") + SfxGetpApp()->GetDispatcher_Impl()->Execute(SID_TEMPLATE_LOAD); + else + SAL_WARN("sfx", "not implemented: " << rEntry); +} + +static OUString getModuleIdentifier( const Reference< XModuleManager2 >& i_xModMgr, SfxObjectShell const * i_pObjSh ) +{ + OSL_ENSURE( i_xModMgr.is(), "getModuleIdentifier(): no XModuleManager" ); + OSL_ENSURE( i_pObjSh, "getModuleIdentifier(): no ObjectShell" ); + + OUString sIdentifier; + + try + { + sIdentifier = i_xModMgr->identify( i_pObjSh->GetModel() ); + } + catch ( css::frame::UnknownModuleException& ) + { + SAL_WARN("sfx", "getModuleIdentifier(): unknown module" ); + } + catch ( Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx", "getModuleIdentifier(): exception of XModuleManager::identify()" ); + } + + return sIdentifier; +} + +IMPL_LINK(SfxCommonTemplateDialog_Impl, LoadFactoryStyleFilter_Hdl, SfxObjectShell const*, i_pObjSh, sal_Int32) +{ + OSL_ENSURE( i_pObjSh, "SfxCommonTemplateDialog_Impl::LoadFactoryStyleFilter(): no ObjectShell" ); + + ::comphelper::SequenceAsHashMap aFactoryProps( + xModuleManager->getByName( getModuleIdentifier( xModuleManager, i_pObjSh ) ) ); + sal_Int32 nFilter = aFactoryProps.getUnpackedValueOrDefault( "ooSetupFactoryStyleFilter", sal_Int32(-1) ); + + m_bWantHierarchical = (nFilter & 0x1000) != 0; + nFilter &= ~0x1000; // clear it + + return nFilter; +} + +void SfxCommonTemplateDialog_Impl::SaveFactoryStyleFilter( SfxObjectShell const * i_pObjSh, sal_Int32 i_nFilter ) +{ + OSL_ENSURE( i_pObjSh, "SfxCommonTemplateDialog_Impl::LoadFactoryStyleFilter(): no ObjectShell" ); + Sequence< PropertyValue > lProps{ comphelper::makePropertyValue( + "ooSetupFactoryStyleFilter", i_nFilter | (m_bWantHierarchical ? 0x1000 : 0)) }; + xModuleManager->replaceByName( getModuleIdentifier( xModuleManager, i_pObjSh ), Any( lProps ) ); +} + +IMPL_LINK_NOARG(SfxCommonTemplateDialog_Impl, SaveSelection_Hdl, StyleList&, SfxObjectShell*) +{ + SfxViewFrame *const pViewFrame(pBindings->GetDispatcher_Impl()->GetFrame()); + SfxObjectShell *const pDocShell(pViewFrame->GetObjectShell()); + if (pDocShell) + { + pDocShell->SetAutoStyleFilterIndex(nActFilter); + SaveFactoryStyleFilter( pDocShell, nActFilter ); + } + return pDocShell; +} + +IMPL_LINK_NOARG(SfxCommonTemplateDialog_Impl, PreviewHdl, weld::Toggleable&, void) +{ + std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() ); + bool bCustomPreview = mxPreviewCheckbox->get_active(); + officecfg::Office::Common::StylesAndFormatting::Preview::set(bCustomPreview, batch ); + batch->commit(); + + m_aStyleList.EnablePreview(bCustomPreview); + + FamilySelect(nActFamily, m_aStyleList, true); +} + +IMPL_LINK_NOARG(SfxCommonTemplateDialog_Impl, UpdateStyleDependents_Hdl, void*, void) +{ + m_aStyleListUpdateStyleDependents.Call(nullptr); + EnableItem("watercan", !bWaterDisabled); + m_aStyleListEnableDelete.Call(nullptr); +} + +void SfxCommonTemplateDialog_Impl::EnableExample_Impl(sal_uInt16 nId, bool bEnable) +{ + bool bDisable = !bEnable || !IsSafeForWaterCan(); + if (nId == SID_STYLE_NEW_BY_EXAMPLE) + { + bNewByExampleDisabled = bDisable; + m_aStyleList.EnableNewByExample(bNewByExampleDisabled); + EnableItem("new", bEnable); + EnableItem("newmenu", bEnable); + } + else if( nId == SID_STYLE_UPDATE_BY_EXAMPLE ) + { + bUpdateByExampleDisabled = bDisable; + EnableItem("update", bEnable); + } +} + +SfxTemplateDialog_Impl::SfxTemplateDialog_Impl(SfxBindings* pB, SfxTemplatePanelControl* pDlgWindow) + : SfxCommonTemplateDialog_Impl(pB, pDlgWindow->get_container(), pDlgWindow->get_builder()) + , m_xActionTbL(pDlgWindow->get_builder()->weld_toolbar("left")) + , m_xActionTbR(pDlgWindow->get_builder()->weld_toolbar("right")) + , m_xToolMenu(pDlgWindow->get_builder()->weld_menu("toolmenu")) + , m_nActionTbLVisible(0) +{ + m_xActionTbR->set_item_help_id("watercan", HID_TEMPLDLG_WATERCAN); + // shown/hidden in SfxTemplateDialog_Impl::ReplaceUpdateButtonByMenu() + m_xActionTbR->set_item_help_id("new", HID_TEMPLDLG_NEWBYEXAMPLE); + m_xActionTbR->set_item_help_id("newmenu", HID_TEMPLDLG_NEWBYEXAMPLE); + m_xActionTbR->set_item_menu("newmenu", m_xToolMenu.get()); + m_xToolMenu->connect_activate(LINK(this, SfxTemplateDialog_Impl, ToolMenuSelectHdl)); + m_xActionTbR->set_item_help_id("update", HID_TEMPLDLG_UPDATEBYEXAMPLE); + + Initialize(); +} + +class ToolbarDropTarget final : public DropTargetHelper +{ +private: + SfxTemplateDialog_Impl& m_rParent; + +public: + ToolbarDropTarget(SfxTemplateDialog_Impl& rDialog, weld::Toolbar& rToolbar) + : DropTargetHelper(rToolbar.get_drop_target()) + , m_rParent(rDialog) + { + } + + virtual sal_Int8 AcceptDrop(const AcceptDropEvent& rEvt) override + { + return m_rParent.AcceptToolbarDrop(rEvt, *this); + } + + virtual sal_Int8 ExecuteDrop(const ExecuteDropEvent& rEvt) override + { + return m_rParent.ExecuteDrop(rEvt); + } +}; + +void SfxTemplateDialog_Impl::Initialize() +{ + SfxCommonTemplateDialog_Impl::Initialize(); + + m_xActionTbL->connect_clicked(LINK(this, SfxTemplateDialog_Impl, ToolBoxLSelect)); + m_xActionTbR->connect_clicked(LINK(this, SfxTemplateDialog_Impl, ToolBoxRSelect)); + m_xActionTbL->set_help_id(HID_TEMPLDLG_TOOLBOX_LEFT); + + m_xToolbarDropTargetHelper.reset(new ToolbarDropTarget(*this, *m_xActionTbL)); +} + +void SfxTemplateDialog_Impl::EnableFamilyItem(sal_uInt16 nId, bool bEnable) +{ + m_xActionTbL->set_item_sensitive(OString::number(nId), bEnable); +} + +// Insert element into dropdown filter "Frame Styles", "List Styles", etc. +void SfxTemplateDialog_Impl::InsertFamilyItem(sal_uInt16 nId, const SfxStyleFamilyItem &rItem) +{ + OString sHelpId; + switch( rItem.GetFamily() ) + { + case SfxStyleFamily::Char: sHelpId = ".uno:CharStyle"; break; + case SfxStyleFamily::Para: sHelpId = ".uno:ParaStyle"; break; + case SfxStyleFamily::Frame: sHelpId = ".uno:FrameStyle"; break; + case SfxStyleFamily::Page: sHelpId = ".uno:PageStyle"; break; + case SfxStyleFamily::Pseudo: sHelpId = ".uno:ListStyle"; break; + case SfxStyleFamily::Table: sHelpId = ".uno:TableStyle"; break; + default: OSL_FAIL("unknown StyleFamily"); break; + } + + OString sId(OString::number(nId)); + m_xActionTbL->set_item_visible(sId, true); + m_xActionTbL->set_item_icon_name(sId, rItem.GetImage()); + m_xActionTbL->set_item_tooltip_text(sId, rItem.GetText()); + m_xActionTbL->set_item_help_id(sId, sHelpId); + ++m_nActionTbLVisible; +} + +void SfxTemplateDialog_Impl::ReplaceUpdateButtonByMenu() +{ + m_xActionTbR->set_item_visible("update", false); + m_xActionTbR->set_item_visible("new", false); + m_xActionTbR->set_item_visible("newmenu", true); + FillToolMenu(); +} + +void SfxTemplateDialog_Impl::ClearFamilyList() +{ + for (int i = 0, nCount = m_xActionTbL->get_n_items(); i < nCount; ++i) + m_xActionTbL->set_item_visible(m_xActionTbL->get_item_ident(i), false); + +} + +SfxTemplateDialog_Impl::~SfxTemplateDialog_Impl() +{ + m_xToolbarDropTargetHelper.reset(); + m_xActionTbL.reset(); + m_xActionTbR.reset(); +} + +void SfxTemplateDialog_Impl::EnableItem(const OString& rMesId, bool bCheck) +{ + if (rMesId == "watercan" && !bCheck && IsCheckedItem("watercan")) + Execute_Impl(SID_STYLE_WATERCAN, "", "", 0, m_aStyleList); + m_xActionTbR->set_item_sensitive(rMesId, bCheck); +} + +void SfxTemplateDialog_Impl::CheckItem(const OString &rMesId, bool bCheck) +{ + if (rMesId == "watercan") + { + bIsWater=bCheck; + m_xActionTbR->set_item_active("watercan", bCheck); + } + else + m_xActionTbL->set_item_active(rMesId, bCheck); +} + +bool SfxTemplateDialog_Impl::IsCheckedItem(const OString& rMesId) +{ + if (rMesId == "watercan") + return m_xActionTbR->get_item_active("watercan"); + return m_xActionTbL->get_item_active(rMesId); +} + +IMPL_LINK( SfxTemplateDialog_Impl, ToolBoxLSelect, const OString&, rEntry, void) +{ + FamilySelect(rEntry.toUInt32(), m_aStyleList); +} + +IMPL_LINK(SfxTemplateDialog_Impl, ToolBoxRSelect, const OString&, rEntry, void) +{ + if (rEntry == "newmenu") + m_xActionTbR->set_menu_item_active(rEntry, !m_xActionTbR->get_menu_item_active(rEntry)); + else + ActionSelect(rEntry, m_aStyleList); +} + +void SfxTemplateDialog_Impl::FillToolMenu() +{ + //create a popup menu in Writer + OUString sTextDoc("com.sun.star.text.TextDocument"); + + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(".uno:StyleNewByExample", sTextDoc); + OUString sLabel = vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties); + m_xToolMenu->append("new", sLabel); + aProperties = vcl::CommandInfoProvider::GetCommandProperties(".uno:StyleUpdateByExample", sTextDoc); + sLabel = vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties); + m_xToolMenu->append("update", sLabel); + m_xToolMenu->append_separator("separator"); + + aProperties = vcl::CommandInfoProvider::GetCommandProperties(".uno:LoadStyles", sTextDoc); + sLabel = vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties); + m_xToolMenu->append("load", sLabel); +} + +IMPL_LINK(SfxTemplateDialog_Impl, ToolMenuSelectHdl, const OString&, rMenuId, void) +{ + if (rMenuId.isEmpty()) + return; + ActionSelect(rMenuId, m_aStyleList); +} + +void SfxCommonTemplateDialog_Impl::SetFamily(SfxStyleFamily const nFamily) +{ + sal_uInt16 const nId(SfxTemplate::SfxFamilyIdToNId(nFamily)); + assert((0 < nId && nId <= MAX_FAMILIES) || 0xffff == nId); + if ( nId != nActFamily ) + { + m_aStyleListSetFamily.Call(nId); + nActFamily = nId; + } +} + +IMPL_LINK(SfxCommonTemplateDialog_Impl, UpdateFamily_Hdl, StyleList&, rStyleList, void) +{ + bWaterDisabled = false; + bUpdateByExampleDisabled = false; + + if (IsCheckedItem("watercan") && + // only if that area is allowed + rStyleList.CurrentFamilyHasState()) + { + Execute_Impl(SID_STYLE_APPLY, rStyleList.GetSelectedEntry(), OUString(), + static_cast<sal_uInt16>(rStyleList.GetFamilyItem()->GetFamily()), rStyleList); + } +} + +void SfxCommonTemplateDialog_Impl::ReplaceUpdateButtonByMenu() +{ + //does nothing +} + +sal_Int8 SfxTemplateDialog_Impl::AcceptToolbarDrop(const AcceptDropEvent& rEvt, const DropTargetHelper& rHelper) +{ + sal_Int8 nReturn = DND_ACTION_NONE; + + // auto flip to the category under the mouse + int nIndex = m_xActionTbL->get_drop_index(rEvt.maPosPixel); + if (nIndex >= m_nActionTbLVisible) + nIndex = m_nActionTbLVisible - 1; + + OString sIdent = m_xActionTbL->get_item_ident(nIndex); + if (!sIdent.isEmpty() && !m_xActionTbL->get_item_active(sIdent)) + ToolBoxLSelect(sIdent); + + // special case: page styles are allowed to create new styles by example + // but not allowed to be created by drag and drop + if (sIdent.toUInt32() != SfxTemplate::SfxFamilyIdToNId(SfxStyleFamily::Page) && + rHelper.IsDropFormatSupported(SotClipboardFormatId::OBJECTDESCRIPTOR) && + !bNewByExampleDisabled) + { + nReturn = DND_ACTION_COPY; + } + return nReturn; +} + +void SfxCommonTemplateDialog_Impl::EnableEdit(bool b, StyleList* rStyleList) +{ + if (rStyleList == &m_aStyleList || rStyleList == nullptr) + m_aStyleList.Enableedit(b); +} +void SfxCommonTemplateDialog_Impl::EnableDel(bool b, const StyleList* rStyleList) +{ + if (rStyleList == &m_aStyleList || rStyleList == nullptr) + m_aStyleList.Enabledel(b); +} +void SfxCommonTemplateDialog_Impl::EnableNew(bool b, const StyleList* rStyleList) +{ + if (rStyleList == &m_aStyleList || rStyleList == nullptr) + m_aStyleList.Enablenew(b); +} +void SfxCommonTemplateDialog_Impl::EnableHide(bool b, const StyleList* rStyleList) +{ + if (rStyleList == &m_aStyleList || rStyleList == nullptr) + m_aStyleList.Enablehide(b); +} +void SfxCommonTemplateDialog_Impl::EnableShow(bool b, const StyleList* rStyleList) +{ + if (rStyleList == &m_aStyleList || rStyleList == nullptr) + m_aStyleList.Enableshow(b); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/tplcitem.cxx b/sfx2/source/dialog/tplcitem.cxx new file mode 100644 index 000000000..c86aabcd7 --- /dev/null +++ b/sfx2/source/dialog/tplcitem.cxx @@ -0,0 +1,169 @@ +/* -*- 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 <svl/intitem.hxx> +#include <vcl/svapp.hxx> +#include <osl/diagnose.h> + +#include <sfx2/bindings.hxx> +#include <sfx2/tplpitem.hxx> +#include <sfx2/sfxsids.hrc> +#include <tplcitem.hxx> +#include <templdgi.hxx> + +// Constructor + +SfxTemplateControllerItem::SfxTemplateControllerItem( + sal_uInt16 nSlotId, // ID + SfxCommonTemplateDialog_Impl &rDlg, // Controller-Instance, + // which is assigned to this item. + SfxBindings &rBindings): + SfxControllerItem(nSlotId, rBindings), + rTemplateDlg(rDlg), + nWaterCanState(0xff), + nUserEventId(nullptr) +{ +} + +SfxTemplateControllerItem::~SfxTemplateControllerItem() +{ + if(nUserEventId) + Application::RemoveUserEvent(nUserEventId); +} + + +// Notice about change of status, is propagated through the Controller +// passed on by the constructor + +void SfxTemplateControllerItem::StateChangedAtToolBoxControl( sal_uInt16 nSID, SfxItemState eState, + const SfxPoolItem* pItem ) +{ + switch(nSID) + { + case SID_STYLE_FAMILY1: + case SID_STYLE_FAMILY2: + case SID_STYLE_FAMILY3: + case SID_STYLE_FAMILY4: + case SID_STYLE_FAMILY5: + case SID_STYLE_FAMILY6: + { + bool bAvailable = SfxItemState::DEFAULT == eState; + if ( !bAvailable ) + rTemplateDlg.SetFamilyState(GetId(), nullptr); + else { + const SfxTemplateItem *pStateItem = dynamic_cast< const SfxTemplateItem* >(pItem); + DBG_ASSERT(pStateItem != nullptr, "SfxTemplateItem expected"); + rTemplateDlg.SetFamilyState( GetId(), pStateItem ); + } + bool bDisable = eState == SfxItemState::DISABLED; + // Disable Family + sal_uInt16 nFamily = 0; + switch( GetId()) + { + case SID_STYLE_FAMILY1: + nFamily = 1; break; + case SID_STYLE_FAMILY2: + nFamily = 2; break; + case SID_STYLE_FAMILY3: + nFamily = 3; break; + case SID_STYLE_FAMILY4: + nFamily = 4; break; + case SID_STYLE_FAMILY5: + nFamily = 5; break; + case SID_STYLE_FAMILY6: + nFamily = 6; break; + + default: OSL_FAIL("unknown StyleFamily"); break; + } + rTemplateDlg.EnableFamilyItem( nFamily, !bDisable ); + break; + } + case SID_STYLE_WATERCAN: + { + if ( eState == SfxItemState::DISABLED ) + nWaterCanState = 0xff; + else if( eState == SfxItemState::DEFAULT ) + { + const SfxBoolItem& rStateItem = dynamic_cast<const SfxBoolItem&>(*pItem); + nWaterCanState = rStateItem.GetValue() ? 1 : 0; + } + //not necessary if the last event is still on the way + if(!nUserEventId) + nUserEventId = Application::PostUserEvent( LINK( + this, SfxTemplateControllerItem, SetWaterCanStateHdl_Impl ) ); + break; + } + case SID_STYLE_EDIT: + rTemplateDlg.EnableEdit( SfxItemState::DISABLED != eState, nullptr ); + break; + case SID_STYLE_DELETE: + rTemplateDlg.EnableDel( SfxItemState::DISABLED != eState, nullptr ); + break; + case SID_STYLE_HIDE: + rTemplateDlg.EnableHide( SfxItemState::DISABLED != eState, nullptr ); + break; + case SID_STYLE_SHOW: + rTemplateDlg.EnableShow( SfxItemState::DISABLED != eState, nullptr ); + break; + case SID_STYLE_NEW_BY_EXAMPLE: + + rTemplateDlg.EnableExample_Impl(nSID, SfxItemState::DISABLED != eState); + break; + case SID_STYLE_UPDATE_BY_EXAMPLE: + { + rTemplateDlg.EnableExample_Impl(nSID, eState != SfxItemState::DISABLED); + break; + } + case SID_STYLE_NEW: + { + rTemplateDlg.EnableNew( SfxItemState::DISABLED != eState, nullptr ); + break; + } + case SID_STYLE_DRAGHIERARCHIE: + { + rTemplateDlg.EnableTreeDrag( SfxItemState::DISABLED != eState ); + break; + } + case SID_STYLE_FAMILY : + { + const SfxUInt16Item *pStateItem = dynamic_cast< const SfxUInt16Item* >(pItem); + if (pStateItem) + { + rTemplateDlg.SetFamily(static_cast<SfxStyleFamily>(pStateItem->GetValue())); + } + break; + } + } +} + +IMPL_LINK_NOARG(SfxTemplateControllerItem, SetWaterCanStateHdl_Impl, void*, void) +{ + nUserEventId = nullptr; + std::unique_ptr<SfxBoolItem> pState; + switch(nWaterCanState) + { + case 0 : + case 1 : + pState.reset(new SfxBoolItem(SID_STYLE_WATERCAN, nWaterCanState != 0)); + break; + } + rTemplateDlg.SetWaterCanState(pState.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/tplpitem.cxx b/sfx2/source/dialog/tplpitem.cxx new file mode 100644 index 000000000..166263444 --- /dev/null +++ b/sfx2/source/dialog/tplpitem.cxx @@ -0,0 +1,90 @@ +/* -*- 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/tplpitem.hxx> +#include <com/sun/star/frame/status/Template.hpp> + +SfxPoolItem* SfxTemplateItem::CreateDefault() { return new SfxTemplateItem; } + + +SfxTemplateItem::SfxTemplateItem() +{ +} + +SfxTemplateItem::SfxTemplateItem +( + sal_uInt16 nWhichId, // Slot-ID + const OUString& rStyle, // Name of the current Styles + const OUString& rStyleIdentifier // Prog Name of current Style +) : SfxFlagItem( nWhichId, static_cast<sal_uInt16>(SfxStyleSearchBits::All) ), + aStyle( rStyle ), + aStyleIdentifier( rStyleIdentifier ) +{ +} + +// op == + +bool SfxTemplateItem::operator==( const SfxPoolItem& rCmp ) const +{ + return ( SfxFlagItem::operator==( rCmp ) && + aStyle == static_cast<const SfxTemplateItem&>(rCmp).aStyle && + aStyleIdentifier == static_cast<const SfxTemplateItem&>(rCmp).aStyleIdentifier ); +} + +SfxTemplateItem* SfxTemplateItem::Clone( SfxItemPool *) const +{ + return new SfxTemplateItem(*this); +} + +bool SfxTemplateItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + css::frame::status::Template aTemplate; + + aTemplate.Value = static_cast<sal_uInt16>(GetValue()); + aTemplate.StyleName = aStyle; + aTemplate.StyleNameIdentifier = aStyleIdentifier; + rVal <<= aTemplate; + + return true; +} + + +bool SfxTemplateItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + css::frame::status::Template aTemplate; + + if ( rVal >>= aTemplate ) + { + SetValue( static_cast<SfxStyleSearchBits>(aTemplate.Value) ); + aStyle = aTemplate.StyleName; + aStyleIdentifier = aTemplate.StyleNameIdentifier; + return true; + } + + return false; +} + + +sal_uInt8 SfxTemplateItem::GetFlagCount() const +{ + return sizeof(sal_uInt16) * 8; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sfx2/source/dialog/versdlg.cxx b/sfx2/source/dialog/versdlg.cxx new file mode 100644 index 000000000..47f33b634 --- /dev/null +++ b/sfx2/source/dialog/versdlg.cxx @@ -0,0 +1,473 @@ +/* -*- 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/document/XCmisDocument.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/util/RevisionTag.hpp> +#include <com/sun/star/beans/NamedValue.hpp> + +#include <officecfg/Office/Common.hxx> +#include <unotools/localedatawrapper.hxx> +#include <svl/intitem.hxx> +#include <svl/stritem.hxx> +#include <svl/itemset.hxx> +#include <unotools/useroptions.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <tools/datetime.hxx> + +#include <versdlg.hxx> +#include <sfx2/strings.hrc> +#include <sfx2/dialoghelper.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/sfxresid.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/dispatch.hxx> + +#include <sfx2/sfxuno.hxx> +#include <memory> +#include <vector> + +using namespace com::sun::star; + +struct SfxVersionInfo +{ + OUString aName; + OUString aComment; + OUString aAuthor; + DateTime aCreationDate; + + SfxVersionInfo(); +}; + +class SfxVersionTableDtor +{ +private: + std::vector<std::unique_ptr<SfxVersionInfo>> aTableList; +public: + explicit SfxVersionTableDtor( const uno::Sequence < util::RevisionTag >& rInfo ); + explicit SfxVersionTableDtor( const uno::Sequence < document::CmisVersion > & rInfo ); + SfxVersionTableDtor(const SfxVersionTableDtor&) = delete; + SfxVersionTableDtor& operator=(const SfxVersionTableDtor&) = delete; + + size_t size() const + { return aTableList.size(); } + + SfxVersionInfo* at( size_t i ) const + { return aTableList[ i ].get(); } +}; + +SfxVersionTableDtor::SfxVersionTableDtor( const uno::Sequence < util::RevisionTag >& rInfo ) +{ + for ( const auto& rItem : rInfo ) + { + std::unique_ptr<SfxVersionInfo> pInfo(new SfxVersionInfo); + pInfo->aName = rItem.Identifier; + pInfo->aComment = rItem.Comment; + pInfo->aAuthor = rItem.Author; + + pInfo->aCreationDate = DateTime( rItem.TimeStamp ); + aTableList.push_back( std::move(pInfo) ); + } +} + +SfxVersionTableDtor::SfxVersionTableDtor( const uno::Sequence < document::CmisVersion >& rInfo ) +{ + for ( const auto& rItem : rInfo ) + { + std::unique_ptr<SfxVersionInfo> pInfo(new SfxVersionInfo); + pInfo->aName = rItem.Id; + pInfo->aComment = rItem.Comment; + pInfo->aAuthor = rItem.Author; + + pInfo->aCreationDate = DateTime( rItem.TimeStamp ); + aTableList.push_back( std::move(pInfo) ); + } +} + +SfxVersionInfo::SfxVersionInfo() + : aCreationDate( DateTime::EMPTY ) +{ +} + +namespace +{ + void setColSizes(weld::TreeView& rVersionBox) + { + // recalculate the datetime column width + int nWidestTime(rVersionBox.get_pixel_size(getWidestDateTime(Application::GetSettings().GetLocaleDataWrapper(), false)).Width()); + int nW1 = rVersionBox.get_pixel_size(rVersionBox.get_column_title(1)).Width(); + + int nMax = std::max(nWidestTime, nW1) + 12; // max width + a little offset + const int nRest = rVersionBox.get_preferred_size().Width() - nMax; + + std::set<OUString> aAuthors; + aAuthors.insert(SvtUserOptions().GetFullName()); + + for (int i = 0; i < rVersionBox.n_children(); ++i) + { + aAuthors.insert(weld::fromId<SfxVersionInfo*>(rVersionBox.get_id(i))->aAuthor); + } + + int nMaxAuthorWidth = nRest/4; + for (auto const& author : aAuthors) + { + nMaxAuthorWidth = std::max<int>(nMaxAuthorWidth, rVersionBox.get_pixel_size(author).Width()); + if (nMaxAuthorWidth > nRest/2) + { + nMaxAuthorWidth = nRest/2; + break; + } + } + + rVersionBox.set_column_fixed_widths({ nMax, nMaxAuthorWidth }); + } +} + +SfxVersionDialog::SfxVersionDialog(weld::Window* pParent, SfxViewFrame* pVwFrame, bool bIsSaveVersionOnClose) + : SfxDialogController(pParent, "sfx/ui/versionsofdialog.ui", "VersionsOfDialog") + , m_pViewFrame(pVwFrame) + , m_bIsSaveVersionOnClose(bIsSaveVersionOnClose) + , m_xSaveButton(m_xBuilder->weld_button("save")) + , m_xSaveCheckBox(m_xBuilder->weld_check_button("always")) + , m_xOpenButton(m_xBuilder->weld_button("open")) + , m_xViewButton(m_xBuilder->weld_button("show")) + , m_xDeleteButton(m_xBuilder->weld_button("delete")) + , m_xCompareButton(m_xBuilder->weld_button("compare")) + , m_xCmisButton(m_xBuilder->weld_button("cmis")) + , m_xVersionBox(m_xBuilder->weld_tree_view("versions")) +{ + m_xVersionBox->set_size_request(m_xVersionBox->get_approximate_digit_width() * 90, + m_xVersionBox->get_height_rows(15)); + setColSizes(*m_xVersionBox); + + Link<weld::Button&,void> aClickLink = LINK( this, SfxVersionDialog, ButtonHdl_Impl ); + m_xViewButton->connect_clicked( aClickLink ); + m_xSaveButton->connect_clicked( aClickLink ); + m_xDeleteButton->connect_clicked( aClickLink ); + m_xCompareButton->connect_clicked( aClickLink ); + m_xOpenButton->connect_clicked( aClickLink ); + m_xSaveCheckBox->connect_toggled(LINK(this, SfxVersionDialog, ToggleHdl_Impl)); + m_xCmisButton->connect_clicked( aClickLink ); + + m_xVersionBox->connect_changed( LINK( this, SfxVersionDialog, SelectHdl_Impl ) ); + m_xVersionBox->connect_row_activated( LINK( this, SfxVersionDialog, DClickHdl_Impl ) ); + + m_xVersionBox->grab_focus(); + + // set dialog title (filename or docinfo title) + OUString sText = m_xDialog->get_title() + + " " + m_pViewFrame->GetObjectShell()->GetTitle(); + m_xDialog->set_title(sText); + + Init_Impl(); +} + +static OUString ConvertWhiteSpaces_Impl( const OUString& rText ) +{ + // converted linebreaks and tabs to blanks; it's necessary for the display + OUStringBuffer sConverted; + const sal_Unicode* pChars = rText.getStr(); + while ( *pChars ) + { + switch ( *pChars ) + { + case '\n' : + case '\t' : + sConverted.append(' '); + break; + + default: + sConverted.append(*pChars); + } + + ++pChars; + } + + return sConverted.makeStringAndClear(); +} + +void SfxVersionDialog::Init_Impl() +{ + SfxObjectShell *pObjShell = m_pViewFrame->GetObjectShell(); + SfxMedium* pMedium = pObjShell->GetMedium(); + uno::Sequence < util::RevisionTag > aVersions = pMedium->GetVersionList( true ); + m_pTable.reset(new SfxVersionTableDtor( aVersions )); + m_xVersionBox->freeze(); + for (size_t n = 0; n < m_pTable->size(); ++n) + { + SfxVersionInfo *pInfo = m_pTable->at( n ); + OUString aEntry = formatDateTime(pInfo->aCreationDate, Application::GetSettings().GetLocaleDataWrapper(), false); + m_xVersionBox->append(weld::toId(pInfo), aEntry); + auto nLastRow = m_xVersionBox->n_children() - 1; + m_xVersionBox->set_text(nLastRow, pInfo->aAuthor, 1); + m_xVersionBox->set_text(nLastRow, ConvertWhiteSpaces_Impl(pInfo->aComment), 2); + } + m_xVersionBox->thaw(); + + if (auto nCount = m_pTable->size()) + m_xVersionBox->select(nCount - 1); + + m_xSaveCheckBox->set_active(m_bIsSaveVersionOnClose); + + bool bEnable = !pObjShell->IsReadOnly(); + m_xSaveButton->set_sensitive( bEnable ); + m_xSaveCheckBox->set_sensitive( bEnable ); + + m_xOpenButton->set_sensitive(false); + m_xViewButton->set_sensitive(false); + m_xDeleteButton->set_sensitive(false); + m_xCompareButton->set_sensitive(false); + + if ( !officecfg::Office::Common::Misc::ExperimentalMode::get() ) + m_xCmisButton->hide( ); + uno::Reference<document::XCmisDocument> xCmisDoc(pObjShell->GetModel(), uno::UNO_QUERY); + if (xCmisDoc && xCmisDoc->isVersionable()) + m_xCmisButton->set_sensitive(true); + else + m_xCmisButton->set_sensitive(false); + + SelectHdl_Impl(*m_xVersionBox); +} + +SfxVersionDialog::~SfxVersionDialog() +{ +} + +void SfxVersionDialog::Open_Impl() +{ + SfxObjectShell *pObjShell = m_pViewFrame->GetObjectShell(); + + auto nPos = m_xVersionBox->get_selected_index(); + SfxInt16Item aItem( SID_VERSION, nPos + 1); + SfxStringItem aTarget( SID_TARGETNAME, "_blank" ); + SfxStringItem aReferer( SID_REFERER, "private:user" ); + SfxStringItem aFile( SID_FILE_NAME, pObjShell->GetMedium()->GetName() ); + + uno::Sequence< beans::NamedValue > aEncryptionData; + if ( GetEncryptionData_Impl( pObjShell->GetMedium()->GetItemSet(), aEncryptionData ) ) + { + // there is a password, it should be used during the opening + SfxUnoAnyItem aEncryptionDataItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData ) ); + m_pViewFrame->GetDispatcher()->ExecuteList( + SID_OPENDOC, SfxCallMode::ASYNCHRON, + { &aFile, &aItem, &aTarget, &aReferer, &aEncryptionDataItem }); + } + else + { + m_pViewFrame->GetDispatcher()->ExecuteList( + SID_OPENDOC, SfxCallMode::ASYNCHRON, + { &aFile, &aItem, &aTarget, &aReferer }); + } + + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(SfxVersionDialog, DClickHdl_Impl, weld::TreeView&, bool) +{ + Open_Impl(); + return true; +} + +IMPL_LINK_NOARG(SfxVersionDialog, SelectHdl_Impl, weld::TreeView&, void) +{ + bool bEnable = m_xVersionBox->get_selected_index() != -1; + SfxObjectShell* pObjShell = m_pViewFrame->GetObjectShell(); + m_xDeleteButton->set_sensitive(bEnable && !pObjShell->IsReadOnly()); + m_xOpenButton->set_sensitive(bEnable); + m_xViewButton->set_sensitive(bEnable); + + const SfxPoolItem *pDummy=nullptr; + m_pViewFrame->GetDispatcher()->QueryState( SID_DOCUMENT_MERGE, pDummy ); + SfxItemState eState = m_pViewFrame->GetDispatcher()->QueryState( SID_DOCUMENT_COMPARE, pDummy ); + m_xCompareButton->set_sensitive(bEnable && eState >= SfxItemState::DEFAULT); +} + +IMPL_LINK(SfxVersionDialog, ButtonHdl_Impl, weld::Button&, rButton, void) +{ + SfxObjectShell *pObjShell = m_pViewFrame->GetObjectShell(); + + int nEntry = m_xVersionBox->get_selected_index(); + + if (&rButton == m_xSaveButton.get()) + { + SfxVersionInfo aInfo; + aInfo.aAuthor = SvtUserOptions().GetFullName(); + SfxViewVersionDialog_Impl aDlg(m_xDialog.get(), aInfo, true); + short nRet = aDlg.run(); + if (nRet == RET_OK) + { + SfxStringItem aComment( SID_DOCINFO_COMMENTS, aInfo.aComment ); + pObjShell->SetModified(); + const SfxPoolItem* aItems[2]; + aItems[0] = &aComment; + aItems[1] = nullptr; + m_pViewFrame->GetBindings().ExecuteSynchron( SID_SAVEDOC, aItems ); + m_xVersionBox->freeze(); + m_xVersionBox->clear(); + m_xVersionBox->thaw(); + Init_Impl(); + } + } + else if (&rButton == m_xDeleteButton.get() && nEntry != -1) + { + SfxVersionInfo* pInfo = weld::fromId<SfxVersionInfo*>(m_xVersionBox->get_id(nEntry)); + pObjShell->GetMedium()->RemoveVersion_Impl(pInfo->aName); + pObjShell->SetModified(); + m_xVersionBox->freeze(); + m_xVersionBox->clear(); + m_xVersionBox->thaw(); + Init_Impl(); + } + else if (&rButton == m_xOpenButton.get() && nEntry != -1) + { + Open_Impl(); + } + else if (&rButton == m_xViewButton.get() && nEntry != -1) + { + SfxVersionInfo* pInfo = weld::fromId<SfxVersionInfo*>(m_xVersionBox->get_id(nEntry)); + SfxViewVersionDialog_Impl aDlg(m_xDialog.get(), *pInfo, false); + aDlg.run(); + } + else if (&rButton == m_xCompareButton.get() && nEntry != -1) + { + SfxAllItemSet aSet( pObjShell->GetPool() ); + aSet.Put(SfxInt16Item(SID_VERSION, nEntry + 1)); + aSet.Put(SfxStringItem(SID_FILE_NAME, pObjShell->GetMedium()->GetName())); + + SfxItemSet* pSet = pObjShell->GetMedium()->GetItemSet(); + const SfxStringItem* pFilterItem = SfxItemSet::GetItem<SfxStringItem>(pSet, SID_FILTER_NAME, false); + const SfxStringItem* pFilterOptItem = SfxItemSet::GetItem<SfxStringItem>(pSet, SID_FILE_FILTEROPTIONS, false); + if ( pFilterItem ) + aSet.Put( *pFilterItem ); + if ( pFilterOptItem ) + aSet.Put( *pFilterOptItem ); + + m_pViewFrame->GetDispatcher()->Execute( SID_DOCUMENT_COMPARE, SfxCallMode::ASYNCHRON, aSet ); + m_xDialog->response(RET_CLOSE); + } + else if (&rButton == m_xCmisButton.get()) + { + SfxCmisVersionsDialog aDlg(m_xDialog.get(), m_pViewFrame); + aDlg.run(); + } +} + +IMPL_LINK(SfxVersionDialog, ToggleHdl_Impl, weld::Toggleable&, rButton, void) +{ + if (&rButton == m_xSaveCheckBox.get()) + { + m_bIsSaveVersionOnClose = m_xSaveCheckBox->get_active(); + } +} + +SfxViewVersionDialog_Impl::SfxViewVersionDialog_Impl(weld::Window *pParent, SfxVersionInfo& rInfo, bool bEdit) + : SfxDialogController(pParent, "sfx/ui/versioncommentdialog.ui", "VersionCommentDialog") + , m_rInfo(rInfo) + , m_xDateTimeText(m_xBuilder->weld_label("timestamp")) + , m_xSavedByText(m_xBuilder->weld_label("author")) + , m_xEdit(m_xBuilder->weld_text_view("textview")) + , m_xOKButton(m_xBuilder->weld_button("ok")) + , m_xCancelButton(m_xBuilder->weld_button("cancel")) + , m_xCloseButton(m_xBuilder->weld_button("close")) +{ + OUString sAuthor = rInfo.aAuthor.isEmpty() ? SfxResId(STR_NO_NAME_SET) : rInfo.aAuthor; + + const LocaleDataWrapper& rLocaleWrapper( Application::GetSettings().GetLocaleDataWrapper() ); + m_xDateTimeText->set_label(m_xDateTimeText->get_label() + formatDateTime(rInfo.aCreationDate, rLocaleWrapper, false)); + m_xSavedByText->set_label(m_xSavedByText->get_label() + sAuthor); + m_xEdit->set_text(rInfo.aComment); + m_xEdit->set_size_request(40 * m_xEdit->get_approximate_digit_width(), + 7 * m_xEdit->get_text_height()); + m_xOKButton->connect_clicked(LINK(this, SfxViewVersionDialog_Impl, ButtonHdl)); + + if (!bEdit) + { + m_xOKButton->hide(); + m_xCancelButton->hide(); + m_xEdit->set_editable(false); + m_xDialog->set_title(SfxResId(STR_VIEWVERSIONCOMMENT)); + m_xCloseButton->grab_focus(); + } + else + { + m_xDateTimeText->hide(); + m_xCloseButton->hide(); + m_xEdit->grab_focus(); + } +} + +IMPL_LINK(SfxViewVersionDialog_Impl, ButtonHdl, weld::Button&, rButton, void) +{ + assert(&rButton == m_xOKButton.get()); + (void)rButton; + m_rInfo.aComment = m_xEdit->get_text(); + m_xDialog->response(RET_OK); +} + +SfxCmisVersionsDialog::SfxCmisVersionsDialog(weld::Window* pParent, SfxViewFrame* pVwFrame) + : SfxDialogController(pParent, "sfx/ui/versionscmis.ui", "VersionsCmisDialog") + , m_pViewFrame(pVwFrame) + , m_xOpenButton(m_xBuilder->weld_button("open")) + , m_xViewButton(m_xBuilder->weld_button("show")) + , m_xDeleteButton(m_xBuilder->weld_button("delete")) + , m_xCompareButton(m_xBuilder->weld_button("compare")) + , m_xVersionBox(m_xBuilder->weld_tree_view("versions")) +{ + m_xVersionBox->set_size_request(m_xVersionBox->get_approximate_digit_width() * 90, + m_xVersionBox->get_height_rows(15)); + setColSizes(*m_xVersionBox); + + m_xVersionBox->grab_focus(); + + OUString sText = m_xDialog->get_title() + + " " + m_pViewFrame->GetObjectShell()->GetTitle(); + m_xDialog->set_title(sText); + + LoadVersions(); +} + +SfxCmisVersionsDialog::~SfxCmisVersionsDialog() +{ +} + +void SfxCmisVersionsDialog::LoadVersions() +{ + SfxObjectShell *pObjShell = m_pViewFrame->GetObjectShell(); + uno::Sequence < document::CmisVersion > aVersions = pObjShell->GetCmisVersions( ); + m_pTable.reset(new SfxVersionTableDtor( aVersions )); + for (size_t n = 0; n < m_pTable->size(); ++n) + { + SfxVersionInfo *pInfo = m_pTable->at( n ); + OUString aEntry = formatDateTime(pInfo->aCreationDate, Application::GetSettings().GetLocaleDataWrapper(), false); + m_xVersionBox->append(weld::toId(pInfo), aEntry); + auto nLastRow = m_xVersionBox->n_children() - 1; + m_xVersionBox->set_text(nLastRow, pInfo->aAuthor, 1); + m_xVersionBox->set_text(nLastRow, ConvertWhiteSpaces_Impl(pInfo->aComment), 2); + } + + if (auto nCount = m_pTable->size()) + m_xVersionBox->select(nCount - 1); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |