Explorar o código

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.
Matthew Olsson %!s(int64=4) %!d(string=hai) anos
pai
achega
cea7dbce42

+ 2 - 0
Userland/Applications/PDFViewer/CMakeLists.txt

@@ -1,6 +1,8 @@
 set(SOURCES
+    OutlineModel.cpp
     PDFViewer.cpp
     PDFViewerWidget.cpp
+    SidebarWidget.cpp
     main.cpp
     )
 

+ 105 - 0
Userland/Applications/PDFViewer/OutlineModel.cpp

@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2021, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "OutlineModel.h"
+#include <LibGfx/FontDatabase.h>
+
+NonnullRefPtr<OutlineModel> OutlineModel::create(const NonnullRefPtr<PDF::OutlineDict>& outline)
+{
+    return adopt_ref(*new OutlineModel(outline));
+}
+
+OutlineModel::OutlineModel(const NonnullRefPtr<PDF::OutlineDict>& 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<PDF::OutlineItem*>(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<PDF::OutlineItem*>(index.internal_data());
+    return static_cast<int>(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<PDF::OutlineItem*>(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<PDF::OutlineItem*>(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<int>(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<int>(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<PDF::OutlineItem*>(parent.internal_data());
+    return create_index(row, column, &parent_outline_item->children[row]);
+}

+ 33 - 0
Userland/Applications/PDFViewer/OutlineModel.h

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2021, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+#include <LibGUI/TreeView.h>
+#include <LibPDF/Document.h>
+
+class OutlineModel final : public GUI::Model {
+public:
+    static NonnullRefPtr<OutlineModel> create(const NonnullRefPtr<PDF::OutlineDict>& 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<PDF::OutlineDict>& outline);
+
+    GUI::Icon m_closed_item_icon;
+    GUI::Icon m_open_item_icon;
+    NonnullRefPtr<PDF::OutlineDict> m_outline;
+    HashTable<PDF::OutlineItem*> m_open_outline_items;
+};

+ 36 - 6
Userland/Applications/PDFViewer/PDFViewerWidget.cpp

@@ -6,23 +6,24 @@
 
 #include "PDFViewerWidget.h"
 #include <LibCore/File.h>
-#include <LibGUI/Action.h>
 #include <LibGUI/Application.h>
 #include <LibGUI/BoxLayout.h>
 #include <LibGUI/FilePicker.h>
 #include <LibGUI/Menu.h>
 #include <LibGUI/Menubar.h>
+#include <LibGUI/Splitter.h>
 
 PDFViewerWidget::PDFViewerWidget()
 {
     set_fill_with_background_color(true);
     set_layout<GUI::VerticalBoxLayout>();
 
-    m_viewer = add<PDFViewer>();
-}
+    auto& splitter = add<GUI::HorizontalSplitter>();
 
-PDFViewerWidget::~PDFViewerWidget()
-{
+    m_sidebar = splitter.add<SidebarWidget>();
+    m_sidebar->set_fixed_width(0);
+
+    m_viewer = splitter.add<PDFViewer>();
 }
 
 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);
+    }
 }

+ 8 - 1
Userland/Applications/PDFViewer/PDFViewerWidget.h

@@ -7,21 +7,28 @@
 #pragma once
 
 #include "PDFViewer.h"
+#include "SidebarWidget.h"
+#include <LibGUI/Action.h>
 #include <LibGUI/Widget.h>
 
 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<GUI::Action> m_open_outline_action;
     RefPtr<PDFViewer> m_viewer;
+    RefPtr<SidebarWidget> m_sidebar;
+    bool m_sidebar_open { false };
     ByteBuffer m_buffer;
     RefPtr<GUI::Action> m_open_action;
 };

+ 31 - 0
Userland/Applications/PDFViewer/SidebarWidget.cpp

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "SidebarWidget.h"
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/TabWidget.h>
+
+SidebarWidget::SidebarWidget()
+{
+    set_fill_with_background_color(true);
+    set_layout<GUI::VerticalBoxLayout>();
+    set_enabled(false);
+
+    auto& tab_bar = add<GUI::TabWidget>();
+
+    auto& outline_container = tab_bar.add_tab<GUI::Widget>("Outline");
+    outline_container.set_layout<GUI::VerticalBoxLayout>();
+    outline_container.layout()->set_margins({ 4, 4, 4, 4 });
+
+    m_outline_tree_view = outline_container.add<GUI::TreeView>();
+    m_outline_tree_view->set_activates_on_selection(true);
+
+    auto& thumbnails_container = tab_bar.add_tab<GUI::Widget>("Thumbnails");
+    thumbnails_container.set_layout<GUI::VerticalBoxLayout>();
+    thumbnails_container.layout()->set_margins({ 4, 4, 4, 4 });
+
+    // FIXME: Add thumbnail previews
+}

+ 35 - 0
Userland/Applications/PDFViewer/SidebarWidget.h

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "OutlineModel.h"
+#include <LibGUI/TreeView.h>
+#include <LibGUI/Widget.h>
+
+class SidebarWidget final : public GUI::Widget {
+    C_OBJECT(SidebarWidget)
+
+public:
+    ~SidebarWidget() override = default;
+
+    void set_outline(RefPtr<PDF::OutlineDict> outline)
+    {
+        if (outline) {
+            m_model = OutlineModel::create(outline.release_nonnull());
+            m_outline_tree_view->set_model(m_model);
+        } else {
+            m_model = RefPtr<OutlineModel> {};
+            m_outline_tree_view->set_model({});
+        }
+    }
+
+private:
+    SidebarWidget();
+
+    RefPtr<OutlineModel> m_model;
+    RefPtr<GUI::TreeView> m_outline_tree_view;
+};