diff options
author | Rodrigo Tobar <rtobar@icrar.org> | 2022-12-14 23:13:13 +0800 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-12-16 10:04:23 +0100 |
commit | 6d0869c14ad2c503277dd7af5c7141db746ebaaa (patch) | |
tree | 57cbf913c6cfc8895f0a72288eb05f81d7bc5619 /Userland | |
parent | c4bc27f27489cdc062468eea53b18f1e6e5db716 (diff) | |
download | serenity-6d0869c14ad2c503277dd7af5c7141db746ebaaa.zip |
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.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Applications/PDFViewer/PDFViewerWidget.cpp | 156 | ||||
-rw-r--r-- | Userland/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 <AK/HashMap.h> +#include <AK/HashTable.h> +#include <AK/Variant.h> #include <LibCore/File.h> #include <LibFileSystemAccessClient/Client.h> #include <LibGUI/Application.h> @@ -15,11 +22,138 @@ #include <LibGUI/Menu.h> #include <LibGUI/Menubar.h> #include <LibGUI/MessageBox.h> +#include <LibGUI/Model.h> +#include <LibGUI/SortingProxyModel.h> #include <LibGUI/Splitter.h> +#include <LibGUI/TableView.h> #include <LibGUI/Toolbar.h> #include <LibGUI/ToolbarContainer.h> +class PagedErrorsModel : public GUI::Model { + + enum Columns { + Page = 0, + Message, + _Count + }; + + using PageErrors = AK::OrderedHashTable<DeprecatedString>; + using PagedErrors = HashMap<u32, PageErrors>; + +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<int>(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<int>(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<u32 const*>(internal_data); + auto page_idx = static_cast<int>(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<u32 const*>(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<u32> 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<GUI::VerticalBoxLayout>(); @@ -27,19 +161,33 @@ PDFViewerWidget::PDFViewerWidget() auto& toolbar_container = add<GUI::ToolbarContainer>(); auto& toolbar = toolbar_container.add<GUI::Toolbar>(); - auto& splitter = add<GUI::HorizontalSplitter>(); - splitter.layout()->set_spacing(4); + auto& h_splitter = add<GUI::HorizontalSplitter>(); + h_splitter.layout()->set_spacing(4); - m_sidebar = splitter.add<SidebarWidget>(); + m_sidebar = h_splitter.add<SidebarWidget>(); m_sidebar->set_preferred_width(200); m_sidebar->set_visible(false); - m_viewer = splitter.add<PDFViewer>(); + auto& v_splitter = h_splitter.add<GUI::VerticalSplitter>(); + v_splitter.layout()->set_spacing(4); + + m_viewer = v_splitter.add<PDFViewer>(); 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<PagedErrorsModel>(m_paged_errors_model.ptr())->add_errors(page, errors); + }; + + m_errors_tree_view = v_splitter.add<GUI::TreeView>(); + 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 <LibGUI/Widget.h> class PDFViewer; +class PagedErrorsModel; class PDFViewerWidget final : public GUI::Widget { C_OBJECT(PDFViewerWidget) @@ -33,6 +35,8 @@ private: RefPtr<PDFViewer> m_viewer; RefPtr<SidebarWidget> m_sidebar; + NonnullRefPtr<PagedErrorsModel> m_paged_errors_model; + RefPtr<GUI::TreeView> m_errors_tree_view; RefPtr<NumericInput> m_page_text_box; RefPtr<GUI::Label> m_total_page_label; RefPtr<GUI::Action> m_go_to_prev_page_action; |