From cea7dbce42efec87d8b8b6a467f6dc0d3b9174b1 Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Sun, 23 May 2021 21:34:06 -0700 Subject: [PATCH] PDFViewer: Add a tab bar with outlines and thumbnails Outlines are in theory implemented (though I'm having trouble finding a simple PDF with outlines to test it on), and thumbnails are not. --- .../Applications/PDFViewer/CMakeLists.txt | 2 + .../Applications/PDFViewer/OutlineModel.cpp | 105 ++++++++++++++++++ .../Applications/PDFViewer/OutlineModel.h | 33 ++++++ .../PDFViewer/PDFViewerWidget.cpp | 42 ++++++- .../Applications/PDFViewer/PDFViewerWidget.h | 9 +- .../Applications/PDFViewer/SidebarWidget.cpp | 31 ++++++ .../Applications/PDFViewer/SidebarWidget.h | 35 ++++++ 7 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 Userland/Applications/PDFViewer/OutlineModel.cpp create mode 100644 Userland/Applications/PDFViewer/OutlineModel.h create mode 100644 Userland/Applications/PDFViewer/SidebarWidget.cpp create mode 100644 Userland/Applications/PDFViewer/SidebarWidget.h diff --git a/Userland/Applications/PDFViewer/CMakeLists.txt b/Userland/Applications/PDFViewer/CMakeLists.txt index c6c437552bc..13eb8b93302 100644 --- a/Userland/Applications/PDFViewer/CMakeLists.txt +++ b/Userland/Applications/PDFViewer/CMakeLists.txt @@ -1,6 +1,8 @@ set(SOURCES + OutlineModel.cpp PDFViewer.cpp PDFViewerWidget.cpp + SidebarWidget.cpp main.cpp ) diff --git a/Userland/Applications/PDFViewer/OutlineModel.cpp b/Userland/Applications/PDFViewer/OutlineModel.cpp new file mode 100644 index 00000000000..b72c9b9c205 --- /dev/null +++ b/Userland/Applications/PDFViewer/OutlineModel.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "OutlineModel.h" +#include + +NonnullRefPtr OutlineModel::create(const NonnullRefPtr& outline) +{ + return adopt_ref(*new OutlineModel(outline)); +} + +OutlineModel::OutlineModel(const NonnullRefPtr& outline) + : m_outline(outline) +{ + m_closed_item_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/book.png")); + m_open_item_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/book-open.png")); +} + +void OutlineModel::set_index_open_state(const GUI::ModelIndex& index, bool is_open) +{ + VERIFY(index.is_valid()); + auto* outline_item = static_cast(index.internal_data()); + + if (is_open) { + m_open_outline_items.set(outline_item); + } else { + m_open_outline_items.remove(outline_item); + } +} + +int OutlineModel::row_count(const GUI::ModelIndex& index) const +{ + if (!index.is_valid()) + return m_outline->children.size(); + auto outline_item = static_cast(index.internal_data()); + return static_cast(outline_item->children.size()); +} + +int OutlineModel::column_count(const GUI::ModelIndex&) const +{ + return 1; +} + +GUI::Variant OutlineModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const +{ + VERIFY(index.is_valid()); + auto outline_item = static_cast(index.internal_data()); + + switch (role) { + case GUI::ModelRole::Display: + return outline_item->title; + case GUI::ModelRole::Icon: + if (m_open_outline_items.contains(outline_item)) + return m_open_item_icon; + return m_closed_item_icon; + default: + return {}; + } +} + +void OutlineModel::update() +{ + did_update(); +} + +GUI::ModelIndex OutlineModel::parent_index(const GUI::ModelIndex& index) const +{ + if (!index.is_valid()) + return {}; + + auto* outline_item = static_cast(index.internal_data()); + auto& parent = outline_item->parent; + + if (!parent) + return {}; + + if (parent->parent) { + auto& grandparent = parent->parent; + for (size_t i = 0; i < grandparent->children.size(); i++) { + auto* sibling = &grandparent->children[i]; + if (sibling == index.internal_data()) + return create_index(static_cast(i), 0, sibling); + } + } else { + for (size_t i = 0; i < m_outline->children.size(); i++) { + auto* sibling = &m_outline->children[i]; + if (sibling == index.internal_data()) + return create_index(static_cast(i), 0, sibling); + } + } + + VERIFY_NOT_REACHED(); +} + +GUI::ModelIndex OutlineModel::index(int row, int column, const GUI::ModelIndex& parent) const +{ + if (!parent.is_valid()) + return create_index(row, column, &m_outline->children[row]); + + auto parent_outline_item = static_cast(parent.internal_data()); + return create_index(row, column, &parent_outline_item->children[row]); +} diff --git a/Userland/Applications/PDFViewer/OutlineModel.h b/Userland/Applications/PDFViewer/OutlineModel.h new file mode 100644 index 00000000000..2885647b7da --- /dev/null +++ b/Userland/Applications/PDFViewer/OutlineModel.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +class OutlineModel final : public GUI::Model { +public: + static NonnullRefPtr create(const NonnullRefPtr& outline); + + void set_index_open_state(const GUI::ModelIndex& index, bool is_open); + + virtual int row_count(const GUI::ModelIndex&) const override; + virtual int column_count(const GUI::ModelIndex&) const override; + virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override; + virtual void update() override; + virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override; + virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex&) const override; + +private: + OutlineModel(const NonnullRefPtr& outline); + + GUI::Icon m_closed_item_icon; + GUI::Icon m_open_item_icon; + NonnullRefPtr m_outline; + HashTable m_open_outline_items; +}; diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.cpp b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp index 6eb0e52b422..9e0fbad9839 100644 --- a/Userland/Applications/PDFViewer/PDFViewerWidget.cpp +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.cpp @@ -6,23 +6,24 @@ #include "PDFViewerWidget.h" #include -#include #include #include #include #include #include +#include PDFViewerWidget::PDFViewerWidget() { set_fill_with_background_color(true); set_layout(); - m_viewer = add(); -} + auto& splitter = add(); -PDFViewerWidget::~PDFViewerWidget() -{ + m_sidebar = splitter.add(); + m_sidebar->set_fixed_width(0); + + m_viewer = splitter.add(); } void PDFViewerWidget::initialize_menubar(GUI::Menubar& menubar) @@ -36,6 +37,21 @@ void PDFViewerWidget::initialize_menubar(GUI::Menubar& menubar) file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) { GUI::Application::the()->quit(); })); + + auto& view_menu = menubar.add_menu("&View"); + + auto open_sidebar_action = GUI::Action::create( + "Open &Sidebar", { Mod_Ctrl, Key_O }, Gfx::Bitmap::load_from_file("/res/icons/16x16/sidebar.png"), [&](auto& action) { + m_sidebar_open = !m_sidebar_open; + m_sidebar->set_fixed_width(m_sidebar_open ? 0 : 200); + action.set_text(m_sidebar_open ? "Open &Sidebar" : "Close &Sidebar"); + }, + nullptr); + open_sidebar_action->set_enabled(false); + + view_menu.add_action(open_sidebar_action); + + m_open_outline_action = open_sidebar_action; } void PDFViewerWidget::open_file(const String& path) @@ -44,5 +60,19 @@ void PDFViewerWidget::open_file(const String& path) auto file_result = Core::File::open(path, Core::OpenMode::ReadOnly); VERIFY(!file_result.is_error()); m_buffer = file_result.value()->read_all(); - m_viewer->set_document(adopt_ref(*new PDF::Document(m_buffer))); + auto document = adopt_ref(*new PDF::Document(m_buffer)); + m_viewer->set_document(document); + + if (document->outline()) { + auto outline = document->outline(); + m_sidebar->set_outline(outline.release_nonnull()); + m_sidebar->set_fixed_width(200); + m_sidebar_open = true; + m_open_outline_action->set_enabled(true); + } else { + m_sidebar->set_outline({}); + m_sidebar->set_fixed_width(0); + m_sidebar_open = false; + m_open_outline_action->set_enabled(false); + } } diff --git a/Userland/Applications/PDFViewer/PDFViewerWidget.h b/Userland/Applications/PDFViewer/PDFViewerWidget.h index 8f897d9720f..dd07d9639ac 100644 --- a/Userland/Applications/PDFViewer/PDFViewerWidget.h +++ b/Userland/Applications/PDFViewer/PDFViewerWidget.h @@ -7,21 +7,28 @@ #pragma once #include "PDFViewer.h" +#include "SidebarWidget.h" +#include #include class PDFViewer; class PDFViewerWidget final : public GUI::Widget { C_OBJECT(PDFViewerWidget) + public: - ~PDFViewerWidget() override; + ~PDFViewerWidget() override = default; + void open_file(const String& path); void initialize_menubar(GUI::Menubar&); private: PDFViewerWidget(); + RefPtr m_open_outline_action; RefPtr m_viewer; + RefPtr m_sidebar; + bool m_sidebar_open { false }; ByteBuffer m_buffer; RefPtr m_open_action; }; diff --git a/Userland/Applications/PDFViewer/SidebarWidget.cpp b/Userland/Applications/PDFViewer/SidebarWidget.cpp new file mode 100644 index 00000000000..93bc4f8c421 --- /dev/null +++ b/Userland/Applications/PDFViewer/SidebarWidget.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "SidebarWidget.h" +#include +#include + +SidebarWidget::SidebarWidget() +{ + set_fill_with_background_color(true); + set_layout(); + set_enabled(false); + + auto& tab_bar = add(); + + auto& outline_container = tab_bar.add_tab("Outline"); + outline_container.set_layout(); + outline_container.layout()->set_margins({ 4, 4, 4, 4 }); + + m_outline_tree_view = outline_container.add(); + m_outline_tree_view->set_activates_on_selection(true); + + auto& thumbnails_container = tab_bar.add_tab("Thumbnails"); + thumbnails_container.set_layout(); + thumbnails_container.layout()->set_margins({ 4, 4, 4, 4 }); + + // FIXME: Add thumbnail previews +} diff --git a/Userland/Applications/PDFViewer/SidebarWidget.h b/Userland/Applications/PDFViewer/SidebarWidget.h new file mode 100644 index 00000000000..5d0657c726d --- /dev/null +++ b/Userland/Applications/PDFViewer/SidebarWidget.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "OutlineModel.h" +#include +#include + +class SidebarWidget final : public GUI::Widget { + C_OBJECT(SidebarWidget) + +public: + ~SidebarWidget() override = default; + + void set_outline(RefPtr outline) + { + if (outline) { + m_model = OutlineModel::create(outline.release_nonnull()); + m_outline_tree_view->set_model(m_model); + } else { + m_model = RefPtr {}; + m_outline_tree_view->set_model({}); + } + } + +private: + SidebarWidget(); + + RefPtr m_model; + RefPtr m_outline_tree_view; +};