
You can now pass a file:/// URL to HtmlView and it will take care of the loading logic for you. Each Document remembers the URL it was loaded from, which allows us to also have reload(). This patch also adds a very simple function for resolving relative URL's into absolute ones.
178 lines
4.9 KiB
C++
178 lines
4.9 KiB
C++
#include <LibCore/CFile.h>
|
|
#include <LibGUI/GApplication.h>
|
|
#include <LibGUI/GPainter.h>
|
|
#include <LibGUI/GScrollBar.h>
|
|
#include <LibHTML/DOM/Element.h>
|
|
#include <LibHTML/DOM/HTMLAnchorElement.h>
|
|
#include <LibHTML/Dump.h>
|
|
#include <LibHTML/Frame.h>
|
|
#include <LibHTML/HtmlView.h>
|
|
#include <LibHTML/Layout/LayoutNode.h>
|
|
#include <LibHTML/Parser/HTMLParser.h>
|
|
#include <LibHTML/RenderingContext.h>
|
|
#include <stdio.h>
|
|
|
|
HtmlView::HtmlView(GWidget* parent)
|
|
: GScrollableWidget(parent)
|
|
, m_main_frame(Frame::create())
|
|
{
|
|
set_frame_shape(FrameShape::Container);
|
|
set_frame_shadow(FrameShadow::Sunken);
|
|
set_frame_thickness(2);
|
|
set_should_hide_unnecessary_scrollbars(true);
|
|
set_background_color(Color::White);
|
|
}
|
|
|
|
HtmlView::~HtmlView()
|
|
{
|
|
}
|
|
|
|
void HtmlView::set_document(Document* document)
|
|
{
|
|
if (document == m_document)
|
|
return;
|
|
m_document = document;
|
|
|
|
main_frame().set_document(document);
|
|
|
|
if (document == nullptr)
|
|
m_layout_root = nullptr;
|
|
else
|
|
m_layout_root = document->create_layout_tree(document->style_resolver(), nullptr);
|
|
|
|
#ifdef HTML_DEBUG
|
|
if (document != nullptr) {
|
|
dbgprintf("\033[33;1mLayout tree before layout:\033[0m\n");
|
|
::dump_tree(*m_layout_root);
|
|
}
|
|
#endif
|
|
|
|
layout_and_sync_size();
|
|
update();
|
|
}
|
|
|
|
void HtmlView::layout_and_sync_size()
|
|
{
|
|
if (!m_layout_root)
|
|
return;
|
|
|
|
main_frame().set_size(available_size());
|
|
m_layout_root->layout();
|
|
set_content_size(m_layout_root->rect().size());
|
|
|
|
#ifdef HTML_DEBUG
|
|
dbgprintf("\033[33;1mLayout tree after layout:\033[0m\n");
|
|
::dump_tree(*m_layout_root);
|
|
#endif
|
|
}
|
|
|
|
void HtmlView::resize_event(GResizeEvent& event)
|
|
{
|
|
GScrollableWidget::resize_event(event);
|
|
layout_and_sync_size();
|
|
}
|
|
|
|
void HtmlView::paint_event(GPaintEvent& event)
|
|
{
|
|
GFrame::paint_event(event);
|
|
|
|
GPainter painter(*this);
|
|
painter.add_clip_rect(widget_inner_rect());
|
|
painter.add_clip_rect(event.rect());
|
|
|
|
if (!m_layout_root) {
|
|
painter.fill_rect(event.rect(), background_color());
|
|
return;
|
|
}
|
|
|
|
painter.translate(frame_thickness(), frame_thickness());
|
|
painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
|
|
|
|
painter.fill_rect(rect(), m_document->background_color());
|
|
|
|
RenderingContext context { painter };
|
|
m_layout_root->render(context);
|
|
}
|
|
|
|
void HtmlView::mousemove_event(GMouseEvent& event)
|
|
{
|
|
if (!m_layout_root)
|
|
return GScrollableWidget::mousemove_event(event);
|
|
|
|
bool hovered_node_changed = false;
|
|
auto result = m_layout_root->hit_test(to_content_position(event.position()));
|
|
if (result.layout_node) {
|
|
auto* node = result.layout_node->node();
|
|
hovered_node_changed = node != m_document->hovered_node();
|
|
m_document->set_hovered_node(const_cast<Node*>(node));
|
|
#ifdef HTML_DEBUG
|
|
if (node) {
|
|
if (auto* link = node->enclosing_link_element()) {
|
|
dbg() << "HtmlView: hovering over a link to " << link->href();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
if (hovered_node_changed) {
|
|
update();
|
|
auto* hovered_html_element = m_document->hovered_node() ? m_document->hovered_node()->enclosing_html_element() : nullptr;
|
|
if (hovered_html_element && !hovered_html_element->title().is_null()) {
|
|
auto screen_position = screen_relative_rect().location().translated(event.position());
|
|
GApplication::the().show_tooltip(hovered_html_element->title(), screen_position.translated(4, 4));
|
|
} else {
|
|
GApplication::the().hide_tooltip();
|
|
}
|
|
}
|
|
event.accept();
|
|
}
|
|
|
|
void HtmlView::mousedown_event(GMouseEvent& event)
|
|
{
|
|
if (!m_layout_root)
|
|
return GScrollableWidget::mousemove_event(event);
|
|
|
|
bool hovered_node_changed = false;
|
|
auto result = m_layout_root->hit_test(to_content_position(event.position()));
|
|
if (result.layout_node) {
|
|
auto* node = result.layout_node->node();
|
|
hovered_node_changed = node != m_document->hovered_node();
|
|
m_document->set_hovered_node(const_cast<Node*>(node));
|
|
if (node) {
|
|
if (auto* link = node->enclosing_link_element()) {
|
|
dbg() << "HtmlView: clicking on a link to " << link->href();
|
|
if (on_link_click)
|
|
on_link_click(link->href());
|
|
}
|
|
}
|
|
}
|
|
if (hovered_node_changed)
|
|
update();
|
|
event.accept();
|
|
}
|
|
|
|
void HtmlView::reload()
|
|
{
|
|
load(main_frame().document()->url());
|
|
}
|
|
|
|
void HtmlView::load(const URL& url)
|
|
{
|
|
dbg() << "HtmlView::load: " << url;
|
|
|
|
if (on_load_start)
|
|
on_load_start(url);
|
|
|
|
auto f = CFile::construct();
|
|
f->set_filename(url.path());
|
|
if (!f->open(CIODevice::OpenMode::ReadOnly)) {
|
|
dbg() << "HtmlView::load: Error: " << f->error_string();
|
|
return;
|
|
}
|
|
|
|
String html = String::copy(f->read_all());
|
|
auto document = parse_html(html);
|
|
document->set_url(url);
|
|
document->normalize();
|
|
|
|
set_document(document);
|
|
}
|