/* -*- 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 .
*/
// Twist the hash number with a RNG twister so we can get very different number even when the string hash // differs only slightly. For example "Heading 1" and "Heading 2" are very close, so we would get a color // that is very similar and with number quantization could result in the same color.
std::mt19937 twister;
twister.seed(nStringHash); // setting the hash for
nStringHash = twister();
namespace
{ // used to disallow the default character style in the styles spotlight character styles color map
std::optional<OUString> sDefaultCharStyleUIName;
}
// 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();
}
/** 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);
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;
}
// 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
std::erase_if(rArr, [](std::unique_ptr<StyleTree_Impl> const& pEntry) { return !pEntry; });
// 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) returnfalse; if (pEntry1->getName() == aUIName) returntrue; // default always first return aSorter.compare(pEntry1->getName(), pEntry2->getName()) < 0;
});
}
// For kit keep the id used for spotlight/number-image for a style stable // regardless of the selection mode of the style panel, so multiple views // on a document all share the same id for a style.
sal_Int32 nSpotlightId; if (comphelper::LibreOfficeKit::isActive())
nSpotlightId = rEntry.getSpotlightId(); else
{
StylesSpotlightColorMap& rColorMap = (eFam == SfxStyleFamily::Para)
? pViewSh->GetStylesSpotlightParaColorMap()
: pViewSh->GetStylesSpotlightCharColorMap();
nSpotlightId = rColorMap.size();
rColorMap[rName] = std::pair(aColor, nSpotlightId);
}
if (eFam == SfxStyleFamily::Char)
{ // don't show a color or number for default character style 'No Character Style' entry if (rName == sDefaultCharStyleUIName.value() /*"No Character Style"*/)
{
rTreeView.set_id(rIter, rName);
rTreeView.set_text(rIter, rName); return;
}
}
// draw the color rectangle and number image const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
Size aImageSize = rStyleSettings.GetListBoxPreviewDefaultPixelSize();
ScopedVclPtrInstance<VirtualDevice> xDevice;
xDevice->SetOutputSize(aImageSize);
xDevice->SetFillColor(aColor); const tools::Rectangle aRect(Point(0, 0), aImageSize);
xDevice->DrawRect(aRect); // In kit mode, unused styles are -1, so we can just skip the number image for those if (nSpotlightId != -1)
{
xDevice->SetTextColor(COL_BLACK);
xDevice->DrawText(aRect, OUString::number(nSpotlightId),
DrawTextFlags::Center | DrawTextFlags::VCenter);
}
// 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_aStyleFamilies.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();
}
IMPL_LINK_NOARG(StyleList, UpdateStyleDependents, void*, void)
{ // Trigger Help PI. Only when the watercan is on if (m_nActFamily != 0xffff && m_pParentDialog->IsCheckedItem(u"watercan"_ustr) && // only if that region is allowed
nullptr != m_pFamilyState[m_nActFamily - 1] && IsSafeForWaterCan(nullptr))
{
m_pParentDialog->Execute_Impl(SID_STYLE_WATERCAN, u""_ustr, u""_ustr, 0, *this);
m_pParentDialog->Execute_Impl(SID_STYLE_WATERCAN, GetSelectedEntry(), u""_ustr, 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_aStyleFamilies.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();
}
// 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(aStyles.begin(), aStyles.end(),
[](const StyleTree_Impl& rLHS, const StyleTree_Impl& rRHS) { return rLHS.getName() < rRHS.getName();
});
std::sort(aStyles.begin(), aStyles.end(),
[&aSorter, &aUIName](const StyleTree_Impl& rLHSS, const StyleTree_Impl& rRHSS) { const OUString& rLHS = rLHSS.getName(); const OUString& rRHS = rRHSS.getName(); if (rRHS == aUIName) returnfalse; if (rLHS == aUIName) returntrue; // default always first return aSorter.compare(rLHS, rRHS) < 0;
});
// Fill the display box
m_xFmtLb->freeze();
m_xFmtLb->clear();
// make view update if (pViewShell && pSpotlightColorMap && (!pSpotlightColorMap->empty() || bOrigMapHasEntries)) static_cast<SfxListener*>(pViewShell)
->Notify(*m_pStyleSheetPool, SfxHint(SfxHintId::StylesSpotlightModified));
// 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);
}
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));
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;
}
returnfalse;
});
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 (autoconst& 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;
// 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
&& (nId == SfxHintId::SfxStyleSheetPool || dynamic_cast<const SfxStyleSheetHint*>(&rHint)
|| nId == SfxHintId::StyleSheetModifiedExtended)) // ie. SfxStyleSheetModifiedHint
{ if (!pIdle)
{
pIdle.reset(new Idle("SfxCommonTemplate"));
pIdle->SetPriority(TaskPriority::LOWEST);
pIdle->SetInvokeHandler(LINK(this, StyleList, TimeOut));
}
pIdle->Start();
}
}
// 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;
}
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)) + "...";
}
if (pStyleManager)
{ if (const SfxStyleFamilyItem* pItem = GetFamilyItem())
{
SfxStyleSheetBase* pStyleSheet = pStyleManager->Search(rId, pItem->GetFamily());
if (pStyleSheet)
{
rRenderContext.Push(vcl::PushFlags::ALL); // tdf#119919 - show "hidden" styles as disabled to not move children onto root node if (pStyleSheet->IsHidden() && m_bHierarchical)
rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.