summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorRodrigo Tobar <rtobar@icrar.org>2022-12-14 23:13:13 +0800
committerAndreas Kling <kling@serenityos.org>2022-12-16 10:04:23 +0100
commit6d0869c14ad2c503277dd7af5c7141db746ebaaa (patch)
tree57cbf913c6cfc8895f0a72288eb05f81d7bc5619 /Userland
parentc4bc27f27489cdc062468eea53b18f1e6e5db716 (diff)
downloadserenity-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.cpp156
-rw-r--r--Userland/Applications/PDFViewer/PDFViewerWidget.h4
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;