office-gobmx/sw/source/uibase/sidebar/A11yCheckIssuesPanel.cxx
Noel Grandin e081e03342 prevent accessibility check from locking up the UI
there is probably a better approach, and there should be a UI warning
that the list is truncated

Change-Id: I6df11b16fee3ac6da38e47ac11b1cd0b29e2959d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171725
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2024-08-12 08:34:12 +02:00

416 lines
16 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <sal/config.h>
#include <AccessibilityCheck.hxx>
#include <AccessibilityIssue.hxx>
#include <cmdid.h>
#include <doc.hxx>
#include <docsh.hxx>
#include <ndtxt.hxx>
#include <wrtsh.hxx>
#include <officecfg/Office/Common.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewsh.hxx>
#include <sfx2/AccessibilityIssue.hxx>
#include <sfx2/pageids.hxx>
#include <unotools/configmgr.hxx>
#include <vcl/ptrstyle.hxx>
#include <vcl/svapp.hxx>
#include <o3tl/enumrange.hxx>
#include <comphelper/lok.hxx>
#include "A11yCheckIssuesPanel.hxx"
#include <com/sun/star/lang/IllegalArgumentException.hpp>
namespace sw::sidebar
{
AccessibilityCheckEntry::AccessibilityCheckEntry(
weld::Container* pParent, std::shared_ptr<sfx::AccessibilityIssue> const& rAccessibilityIssue)
: m_xBuilder(Application::CreateBuilder(pParent, u"svx/ui/accessibilitycheckentry.ui"_ustr))
, m_xContainer(m_xBuilder->weld_container(u"accessibilityCheckEntryBox"_ustr))
, m_xLabel(m_xBuilder->weld_label(u"accessibilityCheckEntryLabel"_ustr))
, m_xGotoButton(m_xBuilder->weld_link_button(u"accessibilityCheckEntryLinkButton"_ustr))
, m_xFixButton(m_xBuilder->weld_button(u"accessibilityCheckEntryFixButton"_ustr))
, m_pAccessibilityIssue(rAccessibilityIssue)
{
// lock in the height as including the button so all rows are the same height
m_xContainer->set_size_request(-1, m_xContainer->get_preferred_size().Height());
if (m_pAccessibilityIssue->canGotoIssue())
{
m_xGotoButton->set_label(m_pAccessibilityIssue->m_aIssueText);
// tdf#156137 allow LinkButton label to wrap
int nMaxWidth = m_xGotoButton->get_approximate_digit_width() * 10;
m_xGotoButton->set_label_wrap(true);
m_xGotoButton->set_size_request(nMaxWidth, -1);
m_xGotoButton->connect_activate_link(
LINK(this, AccessibilityCheckEntry, GotoButtonClicked));
// add full path of linked graphic as tooltip,
if (m_pAccessibilityIssue->m_eIssueID == sfx::AccessibilityIssueID::LINKED_GRAPHIC)
{
auto pSwIssue = std::static_pointer_cast<sw::AccessibilityIssue>(m_pAccessibilityIssue);
auto aInfo = pSwIssue->getAdditionalInfo();
if (aInfo.size() > 0)
{
m_xGotoButton->set_tooltip_text(aInfo[0]);
}
}
m_xLabel->set_visible(false);
}
else
{
m_xLabel->set_label(m_pAccessibilityIssue->m_aIssueText);
m_xGotoButton->set_visible(false);
}
m_xFixButton->set_visible(m_pAccessibilityIssue->canQuickFixIssue());
m_xFixButton->connect_clicked(LINK(this, AccessibilityCheckEntry, FixButtonClicked));
m_pAccessibilityIssue->setParent(dynamic_cast<weld::Window*>(get_widget()));
}
IMPL_LINK_NOARG(AccessibilityCheckEntry, GotoButtonClicked, weld::LinkButton&, bool)
{
if (m_pAccessibilityIssue)
m_pAccessibilityIssue->gotoIssue();
return true;
}
IMPL_LINK_NOARG(AccessibilityCheckEntry, FixButtonClicked, weld::Button&, void)
{
if (m_pAccessibilityIssue)
m_pAccessibilityIssue->quickFixIssue();
}
std::unique_ptr<PanelLayout> A11yCheckIssuesPanel::Create(weld::Widget* pParent,
SfxBindings* pBindings)
{
if (pParent == nullptr)
throw ::com::sun::star::lang::IllegalArgumentException(
u"no parent window given to A11yCheckIssuesPanel::Create"_ustr, nullptr, 0);
return std::make_unique<A11yCheckIssuesPanel>(pParent, pBindings);
}
A11yCheckIssuesPanel::A11yCheckIssuesPanel(weld::Widget* pParent, SfxBindings* pBindings)
: PanelLayout(pParent, u"A11yCheckIssuesPanel"_ustr,
u"modules/swriter/ui/a11ycheckissuespanel.ui"_ustr)
, m_xOptionsButton(m_xBuilder->weld_button(u"bOptions"_ustr))
, mxAccessibilityBox(m_xBuilder->weld_box(u"accessibilityCheckBox"_ustr))
, mxUpdateBox(m_xBuilder->weld_box(u"updateBox"_ustr))
, mxUpdateLinkButton(m_xBuilder->weld_link_button(u"updateLinkButton"_ustr))
, mpBindings(pBindings)
, mpDoc(nullptr)
, maA11yCheckController(FN_STAT_ACCESSIBILITY_CHECK, *pBindings, *this)
, mnIssueCount(0)
, mbAutomaticCheckEnabled(false)
{
m_xExpanders[0] = m_xBuilder->weld_expander(u"expand_document"_ustr);
m_xExpanders[1] = m_xBuilder->weld_expander(u"expand_styles"_ustr);
m_xExpanders[2] = m_xBuilder->weld_expander(u"expand_linked"_ustr);
m_xExpanders[3] = m_xBuilder->weld_expander(u"expand_no_alt"_ustr);
m_xExpanders[4] = m_xBuilder->weld_expander(u"expand_table"_ustr);
m_xExpanders[5] = m_xBuilder->weld_expander(u"expand_formatting"_ustr);
m_xExpanders[6] = m_xBuilder->weld_expander(u"expand_direct_formatting"_ustr);
m_xExpanders[7] = m_xBuilder->weld_expander(u"expand_hyperlink"_ustr);
m_xExpanders[8] = m_xBuilder->weld_expander(u"expand_fakes"_ustr);
m_xExpanders[9] = m_xBuilder->weld_expander(u"expand_numbering"_ustr);
m_xExpanders[10] = m_xBuilder->weld_expander(u"expand_other"_ustr);
m_xBoxes[0] = m_xBuilder->weld_box(u"box_document"_ustr);
m_xBoxes[1] = m_xBuilder->weld_box(u"box_styles"_ustr);
m_xBoxes[2] = m_xBuilder->weld_box(u"box_linked"_ustr);
m_xBoxes[3] = m_xBuilder->weld_box(u"box_no_alt"_ustr);
m_xBoxes[4] = m_xBuilder->weld_box(u"box_table"_ustr);
m_xBoxes[5] = m_xBuilder->weld_box(u"box_formatting"_ustr);
m_xBoxes[6] = m_xBuilder->weld_box(u"box_direct_formatting"_ustr);
m_xBoxes[7] = m_xBuilder->weld_box(u"box_hyperlink"_ustr);
m_xBoxes[8] = m_xBuilder->weld_box(u"box_fakes"_ustr);
m_xBoxes[9] = m_xBuilder->weld_box(u"box_numbering"_ustr);
m_xBoxes[10] = m_xBuilder->weld_box(u"box_other"_ustr);
mxUpdateLinkButton->connect_activate_link(
LINK(this, A11yCheckIssuesPanel, UpdateLinkButtonClicked));
SwDocShell* pDocSh = dynamic_cast<SwDocShell*>(SfxObjectShell::Current());
if (!pDocSh)
return;
// Automatic a11y checking must be enabled for this panel to work properly
mbAutomaticCheckEnabled
= officecfg::Office::Common::Accessibility::OnlineAccessibilityCheck::get();
if (!mbAutomaticCheckEnabled)
{
std::shared_ptr<comphelper::ConfigurationChanges> batch(
comphelper::ConfigurationChanges::create());
officecfg::Office::Common::Accessibility::OnlineAccessibilityCheck::set(true, batch);
batch->commit();
pBindings->Invalidate(SID_ACCESSIBILITY_CHECK_ONLINE);
}
mpDoc = pDocSh->GetDoc();
m_xOptionsButton->connect_clicked(LINK(this, A11yCheckIssuesPanel, OptionsButtonClicked));
// If LOKit is enabled, then enable the update button and don't run the accessibility check.
// In desktop don't show the update button and schedule to run the accessibility check async
if (comphelper::LibreOfficeKit::isActive())
{
mxAccessibilityBox->hide();
mxUpdateBox->show();
}
else
{
mxAccessibilityBox->show();
mxUpdateBox->hide();
Application::PostUserEvent(LINK(this, A11yCheckIssuesPanel, PopulateIssuesHdl));
}
}
IMPL_LINK_NOARG(A11yCheckIssuesPanel, OptionsButtonClicked, weld::Button&, void)
{
SfxUInt16Item aPageID(SID_OPTIONS_PAGEID, sal_uInt16(RID_SVXPAGE_ACCESSIBILITYCONFIG));
auto pDispatcher = GetBindings()->GetDispatcher();
pDispatcher->ExecuteList(SID_OPTIONS_TREEDIALOG, SfxCallMode::SYNCHRON, { &aPageID });
}
IMPL_LINK_NOARG(A11yCheckIssuesPanel, UpdateLinkButtonClicked, weld::LinkButton&, bool)
{
mxAccessibilityBox->show();
mxUpdateBox->hide();
Application::PostUserEvent(LINK(this, A11yCheckIssuesPanel, PopulateIssuesHdl));
return true;
}
IMPL_LINK_NOARG(A11yCheckIssuesPanel, PopulateIssuesHdl, void*, void) { populateIssues(); }
void A11yCheckIssuesPanel::ImplDestroy()
{
// Restore state when this panel is no longer used
if (!mbAutomaticCheckEnabled)
{
std::shared_ptr<comphelper::ConfigurationChanges> batch(
comphelper::ConfigurationChanges::create());
officecfg::Office::Common::Accessibility::OnlineAccessibilityCheck::set(false, batch);
batch->commit();
mpBindings->Invalidate(SID_ACCESSIBILITY_CHECK_ONLINE);
}
for (auto& xExpander : m_xExpanders)
xExpander.reset();
for (auto& xBox : m_xBoxes)
xBox.reset();
}
A11yCheckIssuesPanel::~A11yCheckIssuesPanel() { suppress_fun_call_w_exception(ImplDestroy()); }
void A11yCheckIssuesPanel::removeAllEntries()
{
for (auto eGroup : o3tl::enumrange<AccessibilityCheckGroups>())
{
auto nGroupIndex = size_t(eGroup);
for (auto const& xEntry : m_aEntries[nGroupIndex])
m_xBoxes[nGroupIndex]->move(xEntry->get_widget(), nullptr);
}
}
void A11yCheckIssuesPanel::addEntryForGroup(AccessibilityCheckGroups eGroup,
std::vector<sal_Int32>& rIndices,
std::shared_ptr<sfx::AccessibilityIssue> const& pIssue)
{
auto nGroupIndex = size_t(eGroup);
// prevent the UI locking up forever, this is effectively an O(n^2) situation, given the way the UI additions work
if (m_aEntries[nGroupIndex].size() > 500)
{
SAL_WARN("sw", "too many a11y issues, not adding to panel");
return;
}
auto xEntry = std::make_unique<AccessibilityCheckEntry>(m_xBoxes[nGroupIndex].get(), pIssue);
m_xBoxes[nGroupIndex]->reorder_child(xEntry->get_widget(), rIndices[nGroupIndex]++);
m_aEntries[nGroupIndex].push_back(std::move(xEntry));
}
void A11yCheckIssuesPanel::populateIssues()
{
if (!mpDoc || !mxAccessibilityBox->is_visible())
return;
SfxViewShell* pViewShell = SfxViewShell::Current();
auto* pWindow = pViewShell ? pViewShell->GetWindow() : nullptr;
if (pWindow)
pWindow->SetPointer(PointerStyle::Wait);
sw::AccessibilityCheck aCheck(mpDoc);
aCheck.check();
m_aIssueCollection = aCheck.getIssueCollection();
removeAllEntries();
std::vector<sal_Int32> nIndices(11, 0);
sal_Int32 nDirectFormats = 0;
for (std::shared_ptr<sfx::AccessibilityIssue> const& pIssue : m_aIssueCollection.getIssues())
{
switch (pIssue->m_eIssueID)
{
case sfx::AccessibilityIssueID::DOCUMENT_TITLE:
case sfx::AccessibilityIssueID::DOCUMENT_LANGUAGE:
case sfx::AccessibilityIssueID::DOCUMENT_BACKGROUND:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Document, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::STYLE_LANGUAGE:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Styles, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::LINKED_GRAPHIC:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Linked, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::NO_ALT_OLE:
case sfx::AccessibilityIssueID::NO_ALT_GRAPHIC:
case sfx::AccessibilityIssueID::NO_ALT_SHAPE:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::NoAlt, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::TABLE_MERGE_SPLIT:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Table, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::TEXT_FORMATTING:
case sfx::AccessibilityIssueID::TABLE_FORMATTING:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Formatting, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::DIRECT_FORMATTING:
{
if (!pIssue->getHidden())
{
addEntryForGroup(AccessibilityCheckGroups::DirectFormatting, nIndices, pIssue);
nDirectFormats++;
}
}
break;
case sfx::AccessibilityIssueID::HYPERLINK_IS_TEXT:
case sfx::AccessibilityIssueID::HYPERLINK_SHORT:
case sfx::AccessibilityIssueID::HYPERLINK_NO_NAME:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Hyperlink, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::FAKE_FOOTNOTE:
case sfx::AccessibilityIssueID::FAKE_CAPTION:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Fakes, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::MANUAL_NUMBERING:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Numbering, nIndices, pIssue);
}
break;
case sfx::AccessibilityIssueID::TEXT_CONTRAST:
case sfx::AccessibilityIssueID::TEXT_BLINKING:
case sfx::AccessibilityIssueID::HEADINGS_NOT_IN_ORDER:
case sfx::AccessibilityIssueID::NON_INTERACTIVE_FORMS:
case sfx::AccessibilityIssueID::FLOATING_TEXT:
case sfx::AccessibilityIssueID::HEADING_IN_TABLE:
case sfx::AccessibilityIssueID::HEADING_START:
case sfx::AccessibilityIssueID::HEADING_ORDER:
case sfx::AccessibilityIssueID::CONTENT_CONTROL:
case sfx::AccessibilityIssueID::AVOID_FOOTNOTES:
case sfx::AccessibilityIssueID::AVOID_ENDNOTES:
case sfx::AccessibilityIssueID::FONTWORKS:
{
if (!pIssue->getHidden())
addEntryForGroup(AccessibilityCheckGroups::Other, nIndices, pIssue);
}
break;
default:
{
SAL_WARN("sw.a11y", "Invalid issue ID.");
continue;
}
}
}
// add DirectFormats (if have) as last element to Formatting AccessibilityCheckGroup
if (nDirectFormats > 0)
{
size_t nGroupFormatIndex = size_t(AccessibilityCheckGroups::Formatting);
nIndices[nGroupFormatIndex]++;
}
size_t nGroupIndex = 0;
for (sal_Int32 nIndex : nIndices)
{
if (nIndex > 0)
m_xExpanders[nGroupIndex]->show();
else
m_xExpanders[nGroupIndex]->hide();
nGroupIndex++;
}
if (pWindow)
pWindow->SetPointer(PointerStyle::Arrow);
}
void A11yCheckIssuesPanel::NotifyItemUpdate(const sal_uInt16 nSid, const SfxItemState /* eState */,
const SfxPoolItem* pState)
{
if (!pState) //disposed
return;
switch (nSid)
{
case FN_STAT_ACCESSIBILITY_CHECK:
{
sal_Int32 nIssueCount = static_cast<const SfxInt32Item*>(pState)->GetValue();
if (nIssueCount != mnIssueCount)
{
mnIssueCount = nIssueCount;
populateIssues();
}
}
break;
default:
break;
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */