From 6d0869c14ad2c503277dd7af5c7141db746ebaaa Mon Sep 17 00:00:00 2001 From: Rodrigo Tobar Date: Wed, 14 Dec 2022 23:13:13 +0800 Subject: [PATCH] PDFViewer: Add TreeView to communicate rendering errors Now that the rendering process communicates all errors upstream, and PDFViewer has a way to tap into those errors as they occur, we can visualise them more neatly. This commit adds a TreeView that we populate with the errors stemming from the rendering process. The TreeView has two levels: at the top sit pages where errors can be found, and under each page we can see the errors that have been found on that page. The TreeView sits below the main PDF rendering. --- .../PDFViewer/PDFViewerWidget.cpp | 156 +++++++++++++++++- .../Applications/PDFViewer/PDFViewerWidget.h | 4 + 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.cpp b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp index b273f19dff..8e31b0cea7 100644 --- a/Userland/Applications/PDFViewer/PDFViewerWidget.cpp +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp @@ -6,6 +6,13 @@ */ #include "PDFViewerWidget.h" +#include "AK/Assertions.h" +#include "AK/DeprecatedString.h" +#include "AK/Format.h" +#include "LibGUI/Forward.h" +#include +#include +#include #include #include #include @@ -15,11 +22,138 @@ #include #include #include +#include +#include #include +#include #include #include +class PagedErrorsModel : public GUI::Model { + + enum Columns { + Page = 0, + Message, + _Count + }; + + using PageErrors = AK::OrderedHashTable; + using PagedErrors = HashMap; + +public: + int row_count(GUI::ModelIndex const& index) const override + { + // There are two levels: number of pages and number of errors in page + if (!index.is_valid()) { + return static_cast(m_paged_errors.size()); + } + if (!index.parent().is_valid()) { + auto errors_in_page = m_paged_errors.get(index.row()).release_value().size(); + return static_cast(errors_in_page); + } + return 0; + } + + int column_count(GUI::ModelIndex const&) const override + { + return Columns::_Count; + } + + int tree_column() const override + { + return Columns::Page; + } + + DeprecatedString column_name(int index) const override + { + switch (index) { + case 0: + return "Page"; + case 1: + return "Message"; + default: + VERIFY_NOT_REACHED(); + } + } + + GUI::ModelIndex index(int row, int column, GUI::ModelIndex const& parent) const override + { + if (!parent.is_valid()) { + return create_index(row, column); + } + auto const& page = m_pages_with_errors[parent.row()]; + return create_index(row, column, &page); + } + + GUI::ModelIndex parent_index(GUI::ModelIndex const& index) const override + { + auto* const internal_data = index.internal_data(); + if (internal_data == nullptr) + return {}; + auto page = *static_cast(internal_data); + auto page_idx = static_cast(m_pages_with_errors.find_first_index(page).release_value()); + return create_index(page_idx, index.column()); + } + + virtual GUI::Variant data(GUI::ModelIndex const& index, GUI::ModelRole) const override + { + if (!index.parent().is_valid()) { + switch (index.column()) { + case Columns::Page: + return m_pages_with_errors[index.row()] + 1; + case Columns::Message: + return DeprecatedString::formatted("{} errors", m_paged_errors.get(index.row()).release_value().size()); + default: + VERIFY_NOT_REACHED(); + } + } + + auto page = *static_cast(index.internal_data()); + switch (index.column()) { + case Columns::Page: + return ""; + case Columns::Message: { + auto page_errors = m_paged_errors.get(page).release_value(); + // dbgln("Errors on page {}: {}. Requesting data for index {},{}", page, page_errors.size(), index.row(), index.column()); + auto it = page_errors.begin(); + auto row = index.row(); + for (int i = 0; i < row; ++i, ++it) + ; + return *it; + } + } + VERIFY_NOT_REACHED(); + } + + void add_errors(u32 page, PDF::Errors const& errors) + { + auto old_size = total_error_count(); + if (!m_pages_with_errors.contains_slow(page)) { + m_pages_with_errors.append(page); + } + auto& page_errors = m_paged_errors.ensure(page); + for (auto const& error : errors.errors()) + page_errors.set(error.message()); + auto new_size = total_error_count(); + if (old_size != new_size) + invalidate(); + } + +private: + size_t total_error_count() const + { + size_t count = 0; + for (auto const& entry : m_paged_errors) + count += entry.value.size(); + return count; + } + + Vector m_pages_with_errors; + PagedErrors m_paged_errors; +}; + PDFViewerWidget::PDFViewerWidget() + : m_paged_errors_model(adopt_ref(*new PagedErrorsModel())) { set_fill_with_background_color(true); set_layout(); @@ -27,19 +161,33 @@ PDFViewerWidget::PDFViewerWidget() auto& toolbar_container = add(); auto& toolbar = toolbar_container.add(); - auto& splitter = add(); - splitter.layout()->set_spacing(4); + auto& h_splitter = add(); + h_splitter.layout()->set_spacing(4); - m_sidebar = splitter.add(); + m_sidebar = h_splitter.add(); m_sidebar->set_preferred_width(200); m_sidebar->set_visible(false); - m_viewer = splitter.add(); + auto& v_splitter = h_splitter.add(); + v_splitter.layout()->set_spacing(4); + + m_viewer = v_splitter.add(); m_viewer->on_page_change = [&](auto new_page) { m_page_text_box->set_current_number(new_page + 1, GUI::AllowCallback::No); m_go_to_prev_page_action->set_enabled(new_page > 0); m_go_to_next_page_action->set_enabled(new_page < m_viewer->document()->get_page_count() - 1); }; + m_viewer->on_render_errors = [&](u32 page, PDF::Errors const& errors) { + verify_cast(m_paged_errors_model.ptr())->add_errors(page, errors); + }; + + m_errors_tree_view = v_splitter.add(); + m_errors_tree_view->set_preferred_height(10); + m_errors_tree_view->column_header().set_visible(true); + m_errors_tree_view->set_should_fill_selected_rows(true); + m_errors_tree_view->set_selection_behavior(GUI::AbstractView::SelectionBehavior::SelectRows); + m_errors_tree_view->set_model(MUST(GUI::SortingProxyModel::create(m_paged_errors_model))); + m_errors_tree_view->set_key_column(0); initialize_toolbar(toolbar); } diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.h b/Userland/Applications/PDFViewer/PDFViewerWidget.h index 0d4bd501af..7d6c93dcbe 100644 --- a/Userland/Applications/PDFViewer/PDFViewerWidget.h +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.h @@ -6,6 +6,7 @@ #pragma once +#include "AK/NonnullRefPtr.h" #include "AK/RefPtr.h" #include "NumericInput.h" #include "PDFViewer.h" @@ -16,6 +17,7 @@ #include class PDFViewer; +class PagedErrorsModel; class PDFViewerWidget final : public GUI::Widget { C_OBJECT(PDFViewerWidget) @@ -33,6 +35,8 @@ private: RefPtr m_viewer; RefPtr m_sidebar; + NonnullRefPtr m_paged_errors_model; + RefPtr m_errors_tree_view; RefPtr m_page_text_box; RefPtr m_total_page_label; RefPtr m_go_to_prev_page_action;