Serenity Operating System
1/*
2 * Copyright (c) 2021, Matthew Olsson <mattco@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "OutlineModel.h"
8#include <AK/Assertions.h>
9#include <LibGUI/ModelRole.h>
10#include <LibGfx/Font/FontDatabase.h>
11#include <LibGfx/TextAlignment.h>
12#include <LibPDF/Document.h>
13
14enum Columns {
15 Page,
16 Title,
17 _Count
18};
19
20ErrorOr<NonnullRefPtr<OutlineModel>> OutlineModel::create(NonnullRefPtr<PDF::OutlineDict> const& outline)
21{
22 auto outline_model = adopt_ref(*new OutlineModel(outline));
23 outline_model->m_closed_item_icon.set_bitmap_for_size(16, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/book.png"sv)));
24 outline_model->m_open_item_icon.set_bitmap_for_size(16, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/book-open.png"sv)));
25 return outline_model;
26}
27
28OutlineModel::OutlineModel(NonnullRefPtr<PDF::OutlineDict> const& outline)
29 : m_outline(outline)
30{
31}
32
33void OutlineModel::set_index_open_state(const GUI::ModelIndex& index, bool is_open)
34{
35 VERIFY(index.is_valid());
36 auto* outline_item = static_cast<PDF::OutlineItem*>(index.internal_data());
37
38 if (is_open) {
39 m_open_outline_items.set(outline_item);
40 } else {
41 m_open_outline_items.remove(outline_item);
42 }
43}
44
45int OutlineModel::row_count(const GUI::ModelIndex& index) const
46{
47 if (!index.is_valid())
48 return m_outline->children.size();
49 auto outline_item = static_cast<PDF::OutlineItem*>(index.internal_data());
50 return static_cast<int>(outline_item->children.size());
51}
52
53int OutlineModel::tree_column() const
54{
55 return Columns::Title;
56}
57
58int OutlineModel::column_count(const GUI::ModelIndex&) const
59{
60 return Columns::_Count;
61}
62
63PDF::Destination const& OutlineModel::get_destination(GUI::ModelIndex const& index)
64{
65 auto* outline_item = static_cast<PDF::OutlineItem*>(index.internal_data());
66 return outline_item->dest;
67}
68
69GUI::Variant OutlineModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
70{
71 VERIFY(index.is_valid());
72 auto outline_item = static_cast<PDF::OutlineItem*>(index.internal_data());
73
74 switch (role) {
75 case GUI::ModelRole::Display:
76 switch (index.column()) {
77 case Columns::Title:
78 return outline_item->title;
79 case Columns::Page: {
80 auto maybe_page_number = outline_item->dest.page;
81 if (maybe_page_number.has_value()) {
82 return maybe_page_number.release_value();
83 }
84 return {};
85 }
86 default:
87 VERIFY_NOT_REACHED();
88 }
89
90 case GUI::ModelRole::Icon:
91 if (m_open_outline_items.contains(outline_item))
92 return m_open_item_icon;
93 return m_closed_item_icon;
94 default:
95 return {};
96
97 case GUI::ModelRole::TextAlignment:
98 switch (index.column()) {
99 case Columns::Title:
100 return Gfx::TextAlignment::CenterLeft;
101 case Columns::Page:
102 return Gfx::TextAlignment::CenterRight;
103 default:
104 VERIFY_NOT_REACHED();
105 }
106 }
107}
108
109GUI::ModelIndex OutlineModel::parent_index(const GUI::ModelIndex& index) const
110{
111 if (!index.is_valid())
112 return {};
113
114 auto* outline_item = static_cast<PDF::OutlineItem*>(index.internal_data());
115 auto& parent = outline_item->parent;
116
117 if (!parent)
118 return {};
119
120 Vector<NonnullRefPtr<PDF::OutlineItem>> parent_siblings = (parent->parent ? parent->parent->children : m_outline->children);
121 for (size_t i = 0; i < parent_siblings.size(); i++) {
122 auto* parent_sibling = parent_siblings[i].ptr();
123 if (parent_sibling == parent.ptr())
124 return create_index(static_cast<int>(i), index.column(), parent.ptr());
125 }
126
127 VERIFY_NOT_REACHED();
128}
129
130GUI::ModelIndex OutlineModel::index(int row, int column, const GUI::ModelIndex& parent) const
131{
132 if (!parent.is_valid())
133 return create_index(row, column, &m_outline->children[row]);
134
135 auto parent_outline_item = static_cast<PDF::OutlineItem*>(parent.internal_data());
136 return create_index(row, column, &parent_outline_item->children[row]);
137}