e081e03342
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>
416 lines
16 KiB
C++
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: */
|