/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "A11yCheckIssuesPanel.hxx" #include namespace sw::sidebar { AccessibilityCheckEntry::AccessibilityCheckEntry( weld::Container* pParent, std::shared_ptr 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(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(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 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(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(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 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 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()) { 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& rIndices, std::shared_ptr 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(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 nIndices(11, 0); sal_Int32 nDirectFormats = 0; for (std::shared_ptr 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(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: */