You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
netsurf/frontends/qt/window.cpp

608 lines
15 KiB

/*
* Copyright 2023 Vincent Sanders <vince@netsurf-browser.org>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file
* Implementation of netsurf window (widget) for qt.
*/
#include <QGridLayout>
#include <QSplitter>
extern "C" {
#include "utils/errors.h"
#include "utils/log.h"
#include "utils/nsoption.h"
#include "utils/nsurl.h"
#include "utils/messages.h"
#include "netsurf/types.h"
#include "netsurf/mouse.h"
#include "netsurf/window.h"
#include "netsurf/plotters.h"
#include "netsurf/content.h"
#include "netsurf/browser_window.h"
#include "netsurf/mouse.h"
#include "desktop/browser_history.h"
}
#include "qt/window.cls.h"
#include "qt/scaffolding.cls.h"
#include "qt/misc.h"
#include "qt/window.h"
/**
* time (in ms) between throbber animation frame updates
*/
#define THROBBER_FRAME_TIME (100)
#define THROBBER_FRAME_COUNT (8)
struct gui_window {
class NS_Window *window;
};
/**
* netsurf window class constructor
*/
NS_Window::NS_Window(QWidget *parent, struct browser_window *bw)
: QWidget(parent),
m_bw(bw),
m_throbber_frame(0),
m_favicon(new QIcon(":favicon.png"))
{
setFocusPolicy(Qt::StrongFocus);
//int scrlsize = Fl::scrollbar_size();
//int splitx = (nsoption_int(toolbar_status_size) * W) / 10000;
//int urlbarh = scrlsize * 2;
// setup actions
m_actions = new NS_Actions(this, m_bw);
// url bar
m_nsurlbar = new NS_URLBar(this, m_actions, m_bw);
// browser drawing canvas widget
m_nswidget = new NS_Widget(this, bw);
// horizontal scrollbar
m_hscrollbar = new QScrollBar(Qt::Horizontal);
m_hscrollbar->setMinimum(0);
m_hscrollbar->setMaximum(1);
m_hscrollbar->setPageStep(1);
connect(m_hscrollbar, &QScrollBar::valueChanged,
m_nswidget, &NS_Widget::setHorizontalScroll);
// vertical scrollbar
m_vscrollbar = new QScrollBar(Qt::Vertical);
m_vscrollbar->setMinimum(0);
m_vscrollbar->setMaximum(1);
m_vscrollbar->setPageStep(1);
connect(m_vscrollbar, &QScrollBar::valueChanged,
m_nswidget, &NS_Widget::setVerticalScroll);
/* status */
m_status = new QLabel();
QSplitter *splitter = new QSplitter();
splitter->setChildrenCollapsible(false);
splitter->addWidget(m_status);
splitter->addWidget(m_hscrollbar);
int tb_pct = nsoption_int(toolbar_status_size);
//NSLOG(netsurf,WARNING,"%d:%d",tb_pct,10000 - tb_pct);
splitter->setStretchFactor(0,tb_pct);
splitter->setStretchFactor(1,10000 - tb_pct);
// Build browser window grid layout
QGridLayout *layout = new QGridLayout(this);
layout->setContentsMargins(0,0,0,0);
layout->setHorizontalSpacing(0);
layout->setVerticalSpacing(0);
layout->addWidget(m_nsurlbar, 0, 0, 1, 2);
layout->addWidget(m_nswidget,1,0);
layout->addWidget(m_vscrollbar, 1,1);
layout->setRowStretch(1,1);
layout->addWidget(splitter, 2,0);
/* actions */
addAction(m_actions->m_quit);
addAction(m_actions->m_newtab);
addAction(m_actions->m_newwindow);
}
NS_Window::~NS_Window()
{
advance_throbber(false);
delete m_favicon;
delete m_actions;
}
void NS_Window::closeEvent(QCloseEvent *event)
{
browser_window_destroy(m_bw);
}
void NS_Window::wheelEvent(QWheelEvent *event)
{
QPoint pixels = event->pixelDelta();
if (!pixels.isNull()) {
m_hscrollbar->setValue(m_hscrollbar->value() + pixels.x());
m_vscrollbar->setValue(m_vscrollbar->value() - pixels.y());
} else {
QPoint angle = event->angleDelta() / 8;
if (!angle.isNull()) {
if (angle.x() >= 15) {
m_hscrollbar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
} else if (angle.x() <= -15) {
m_hscrollbar->triggerAction(QAbstractSlider::SliderSingleStepSub);
}
if (angle.y() >= 15) {
m_vscrollbar->triggerAction(QAbstractSlider::SliderSingleStepSub);
} else if (angle.y() <= -15) {
m_vscrollbar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
}
}
}
event->accept();
}
void NS_Window::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Left:
m_hscrollbar->triggerAction(QAbstractSlider::SliderSingleStepSub);
break;
case Qt::Key_Right:
m_hscrollbar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
break;
case Qt::Key_Up:
m_vscrollbar->triggerAction(QAbstractSlider::SliderSingleStepSub);
break;
case Qt::Key_Down:
m_vscrollbar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
break;
case Qt::Key_Home:
m_vscrollbar->setValue(m_vscrollbar->minimum());
break;
case Qt::Key_End:
m_vscrollbar->setValue(m_vscrollbar->maximum());
break;
case Qt::Key_PageUp:
m_vscrollbar->triggerAction(QAbstractSlider::SliderPageStepSub);
break;
case Qt::Key_PageDown:
m_vscrollbar->triggerAction(QAbstractSlider::SliderPageStepAdd);
break;
default:
QWidget::keyPressEvent(event);
break;
}
}
/**
* destroy a tab
*/
void NS_Window::destroy(void)
{
browser_window_destroy(m_bw);
}
void NS_Window::set_status(const char *text)
{
m_status->setText(text);
}
nserror NS_Window::set_scroll(const struct rect *rect)
{
m_hscrollbar->setValue(rect->x0);
m_vscrollbar->setValue(rect->y0);
return NSERROR_OK;
}
nserror NS_Window::update_extent()
{
int ew, eh;
nserror res;
res = browser_window_get_extents(m_bw, true, &ew, &eh);
if (res != NSERROR_OK) {
return res;
}
int width = m_nswidget->size().width();
m_hscrollbar->setMaximum(ew - width);
m_hscrollbar->setPageStep(width);
m_hscrollbar->setSingleStep(width / 16);
int height = m_nswidget->size().height();
m_vscrollbar->setMaximum(eh - height);
m_vscrollbar->setPageStep(height);
m_vscrollbar->setSingleStep(height / 16);
return NSERROR_OK;
}
static void next_throbber_frame(void *p)
{
NS_Window *window = (NS_Window *)p;
window->advance_throbber(true);
}
nserror NS_Window::advance_throbber(bool cont)
{
if (cont == false) {
nsqt_schedule(-1, next_throbber_frame, this);
m_throbber_frame = 0;
iconChanged(*m_favicon);
return NSERROR_OK;
}
m_throbber_frame++;
if (m_throbber_frame > THROBBER_FRAME_COUNT) {
m_throbber_frame = 1;
}
QIcon frame(":throbber" + QString().setNum(m_throbber_frame) + ".png");
iconChanged(frame);
nsqt_schedule(THROBBER_FRAME_TIME, next_throbber_frame, this);
return NSERROR_OK;
}
void NS_Window::set_favicon(QIcon *icon)
{
delete m_favicon;
if (icon == nullptr) {
m_favicon = new QIcon(":favicon.png");
} else {
m_favicon = icon;
}
if (m_throbber_frame == 0) {
iconChanged(*m_favicon);
}
}
/* static methods */
/**
* Set the status bar message of a browser window.
*
* \param g gui_window to update
* \param text new status text
*/
void NS_Window::static_set_status(struct gui_window *gw, const char *text)
{
gw->window->set_status(text);
}
/**
* Set the title of a window.
*
* \param gw The gui window to set title of.
* \param title new window title
*/
void NS_Window::static_set_title(struct gui_window *gw, const char *title)
{
gw->window->titleChanged(title);
}
/**
* Set the icon of a window.
*
* \param gw The gui window to set title of.
* \param title new window title
*/
void NS_Window::static_set_icon(struct gui_window *gw,
struct hlcache_handle *icon_handle)
{
QIcon *icon = nullptr;
if (icon_handle != NULL) {
struct bitmap *icon_bitmap = NULL;
icon_bitmap = content_get_bitmap(icon_handle);
if (icon_bitmap != NULL) {
const QImage img = *(QImage *)icon_bitmap;
icon = new QIcon(QPixmap::fromImage(img, Qt::AutoColor));
}
}
gw->window->set_favicon(icon);
}
/**
* Get the scroll position of a browser window.
*
* \param gw The gui window to obtain the scroll position from.
* \param sx receives x ordinate of point at top-left of window
* \param sy receives y ordinate of point at top-left of window
* \return true iff successful
*/
bool NS_Window::static_get_scroll(struct gui_window *gw, int *sx, int *sy)
{
return gw->window->m_nswidget->get_scroll(sx, sy);
}
/**
* Set the scroll position of a browser window.
*
* scrolls the viewport to ensure the specified rectangle of the
* content is shown.
* If the rectangle is of zero size i.e. x0 == x1 and y0 == y1
* the contents will be scrolled so the specified point in the
* content is at the top of the viewport.
* If the size of the rectangle is non zero the frontend may
* add padding or centre the defined area or it may simply
* align as in the zero size rectangle
*
* \param gw The gui window to scroll.
* \param rect The rectangle to ensure is shown.
* \return NSERROR_OK on success or appropriate error code.
*/
nserror
NS_Window::static_set_scroll(struct gui_window *gw, const struct rect *rect)
{
return gw->window->set_scroll(rect);
}
/**
* Set the navigation url.
*
* \param gw window to update.
* \param url The url to use as icon.
*/
nserror NS_Window::static_set_url(struct gui_window *gw, struct nsurl *url)
{
nserror res;
res = gw->window->m_nsurlbar->set_url(url);
gw->window->m_actions->update(NS_Actions::UpdateUnchanged);
return res;
}
/**
* Miscellaneous event occurred for a window
*
* This is used to inform the frontend of window events which
* require no additional parameters.
*
* \param gw The gui window the event occurred for
* \param event Which event has occurred.
* \return NSERROR_OK if the event was processed else error code.
*/
nserror
NS_Window::static_event(struct gui_window *gw, enum gui_window_event event)
{
nserror res;
switch (event) {
case GW_EVENT_UPDATE_EXTENT:
res = gw->window->update_extent();
break;
case GW_EVENT_START_THROBBER:
res = gw->window->advance_throbber(true);
gw->window->m_actions->update(NS_Actions::UpdateActive);
break;
case GW_EVENT_STOP_THROBBER:
res = gw->window->advance_throbber(false);
gw->window->m_actions->update(NS_Actions::UpdateInactive);
break;
case GW_EVENT_PAGE_INFO_CHANGE:
gw->window->m_actions->update(NS_Actions::UpdatePageInfo);
break;
default:
res = NSERROR_OK;
break;
}
return res;
}
/**
* Invalidate an area of a window.
*
* The specified area of the window should now be considered
* out of date. If the area is NULL the entire window must be
* invalidated. It is expected that the windowing system will
* then subsequently cause redraw/expose operations as
* necessary.
*
* \note the frontend should not attempt to actually start the
* redraw operations as a result of this callback because the
* core redraw functions may already be threaded.
*
* \param gw The gui window to invalidate.
* \param rect area to redraw or NULL for the entire window area
* \return NSERROR_OK on success or appropriate error code
*/
nserror
NS_Window::static_invalidate(struct gui_window *gw, const struct rect *rect)
{
return gw->window->m_nswidget->invalidate(rect);
}
/**
* Find the current dimensions of a browser window's content area.
*
* This is used to determine the actual available drawing size
* in pixels. This allows contents that can be dynamically
* reformatted, such as HTML, to better use the available
* space.
*
* \param gw The gui window to measure content area of.
* \param width receives width of window
* \param height receives height of window
* \return NSERROR_OK on success and width and height updated
* else error code.
*/
nserror
NS_Window::static_get_dimensions(struct gui_window *gw, int *width, int *height)
{
return gw->window->m_nswidget->get_dimensions(width, height);
}
/**
* Change mouse pointer shape
*
* \param gw The gui window to set pointer for.
* \param shape The new shape to change to.
*/
void
NS_Window::static_set_pointer(struct gui_window *gw, enum gui_pointer_shape shape)
{
gw->window->m_nswidget->set_pointer(shape);
}
/**
* Create and open a gui window for a browsing context.
*
* The implementing front end must create a context suitable
* for it to display a window referred to as the "gui window".
*
* The frontend will be expected to request the core redraw
* areas of the gui window which have become invalidated
* either from toolkit expose events or as a result of a
* invalidate() call.
*
* Most core operations used by the frontend concerning browser
* windows require passing the browser window context therefor
* the gui window must include a reference to the browser
* window passed here.
*
* If GW_CREATE_CLONE flag is set existing is non-NULL.
*
* \param bw The core browsing context associated with the gui window
* \param existing An existing gui_window, may be NULL.
* \param flags flags to control the gui window creation.
* \return gui window, or NULL on error.
*/
struct gui_window *
NS_Window::static_create(struct browser_window *bw,
struct gui_window *existing,
gui_window_create_flags flags)
{
struct gui_window *gw;
NS_Scaffold *scaffold;
QWidget *existingpage=nullptr;
int tabidx;
gw = (struct gui_window *)calloc(1, sizeof(struct gui_window));
if (gw == NULL) {
return NULL;
}
if (existing != NULL) {
existingpage = existing->window;
}
scaffold = NS_Scaffold::get_scaffold(existingpage,
((flags & GW_CREATE_TAB) != 0));
if (scaffold == NULL) {
free(gw);
return NULL;
}
gw->window = new NS_Window(nullptr, bw);
if (gw->window == NULL) {
free(gw);
return NULL;
}
tabidx = scaffold->addTab(gw->window, messages_get("NewTab"));
connect(gw->window, &NS_Window::titleChanged,
scaffold, &NS_Scaffold::changeTabTitle);
connect(gw->window, &NS_Window::iconChanged,
scaffold, &NS_Scaffold::changeTabIcon);
if (flags & GW_CREATE_FOREGROUND) {
scaffold->setCurrentIndex(tabidx);
}
scaffold->show();
return gw;
}
/**
* Destroy previously created gui window
*
* \param gw The gui window to destroy.
*/
void NS_Window::static_destroy(struct gui_window *gw)
{
gw->window->deleteLater();
free(gw);
}
/**
* window operations table for qt frontend
*/
static struct gui_window_table window_table = {
.create = NS_Window::static_create,
.destroy = NS_Window::static_destroy,
.invalidate = NS_Window::static_invalidate,
.get_scroll = NS_Window::static_get_scroll,
.set_scroll = NS_Window::static_set_scroll,
.get_dimensions = NS_Window::static_get_dimensions,
.event = NS_Window::static_event,
.set_title = NS_Window::static_set_title,
.set_url = NS_Window::static_set_url,
.set_icon = NS_Window::static_set_icon,
.set_status = NS_Window::static_set_status,
.set_pointer = NS_Window::static_set_pointer,
.place_caret = NULL,
.drag_start = NULL,
.save_link = NULL,
.create_form_select_menu = NULL,
.file_gadget_open = NULL,
.drag_save_object = NULL,
.drag_save_selection = NULL,
.console_log = NULL,
};
struct gui_window_table *nsqt_window_table = &window_table;