guck

Vim-like QtWebEngine-powered browser, written in C++
git clone git://git.deurzen.net/guck
Log | Files | Refs | LICENSE

commit dcf52d29fb7ff6aaf4675e37f949ca7cbaa883b3
Author: deurzen <max@deurzen.net>
Date:   Wed, 25 May 2022 05:51:13 +0200

initial commit

Diffstat:
ALICENSE | 27+++++++++++++++++++++++++++
AMakefile | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/common.hh | 12++++++++++++
Asrc/defaults.hh | 7+++++++
Asrc/guck.cc | 34++++++++++++++++++++++++++++++++++
Asrc/guck.hh | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main.cc | 13+++++++++++++
Asrc/parse.cc | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/parse.hh | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/statusbar/backforward.cc | 0
Asrc/ui/statusbar/backforward.hh | 24++++++++++++++++++++++++
Asrc/ui/statusbar/command.cc | 0
Asrc/ui/statusbar/command.hh | 22++++++++++++++++++++++
Asrc/ui/statusbar/elidedlabel.cc | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/statusbar/elidedlabel.hh | 40++++++++++++++++++++++++++++++++++++++++
Asrc/ui/statusbar/position.cc | 0
Asrc/ui/statusbar/position.hh | 24++++++++++++++++++++++++
Asrc/ui/statusbar/progress.cc | 24++++++++++++++++++++++++
Asrc/ui/statusbar/progress.hh | 37+++++++++++++++++++++++++++++++++++++
Asrc/ui/statusbar/status.cc | 30++++++++++++++++++++++++++++++
Asrc/ui/statusbar/status.hh | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/statusbar/tabindex.cc | 0
Asrc/ui/statusbar/tabindex.hh | 24++++++++++++++++++++++++
Asrc/ui/statusbar/text.cc | 20++++++++++++++++++++
Asrc/ui/statusbar/text.hh | 31+++++++++++++++++++++++++++++++
Asrc/ui/statusbar/textbase.cc | 0
Asrc/ui/statusbar/textbase.hh | 24++++++++++++++++++++++++
Asrc/ui/statusbar/url.cc | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/statusbar/url.hh | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/tabbar/tab.cc | 28++++++++++++++++++++++++++++
Asrc/ui/tabbar/tab.hh | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/window.cc | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/window.hh | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util.cc | 40++++++++++++++++++++++++++++++++++++++++
Asrc/util.hh | 14++++++++++++++
36 files changed, 1168 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2022, Max van Deurzen <max@deurzen.net> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile @@ -0,0 +1,53 @@ +include config.mk + +all: quick_build + +quick_build: + $(MAKE) -j39 build + +install: + install $(BIN) $(INSTALL)$(PROJECT) + +bin: + @[ -d bin ] || mkdir bin + +obj: + @[ -d obj ] || mkdir obj + +build: bin obj ${OBJ_FILES} tags deps + ${CC} ${CXXFLAGS} ${OBJ_FILES} ${LDFLAGS} -o ${TARGET} + +-include $(DEPS) + +obj/%.o: obj +obj/%.o: src/%.cc + ${CC} ${CXXFLAGS} -MMD -MP -c $< -o $@ + +obj/%.o: src/ui/%.cc + ${CC} ${CXXFLAGS} -MMD -MP -c $< -o $@ + +obj/%.o: src/ui/statusbar/%.cc + ${CC} ${CXXFLAGS} -MMD -MP -c $< -o $@ + +obj/%.o: src/ui/tabbar/%.cc + ${CC} ${CXXFLAGS} -MMD -MP -c $< -o $@ + +%.moc.cc: %.hh + moc $< -o $@ + +run: + @echo -n running + @./${BIN} + +.PHONY: tags +tags: + @ctags -R --exclude=.git --c++-kinds=+p --fields=+iaS --extras=+q . + +.PHONY: deps +deps: + @echo ${CXXFLAGS} | tr " " "\n" >| .ccls + +.PHONY: clean +clean: + @echo cleaning + @rm -rf ./bin ./release ./obj ./src/ui{,/statusbar,/tabbar}/*.moc.cc diff --git a/config.mk b/config.mk @@ -0,0 +1,43 @@ +PROJECT = guck + +OBJDIR = obj +SRCDIR = src + +MOC_H_FILES := $(shell ag -l Q_OBJECT src | tr "\n" " ") +MOC_BASENAMES := $(shell echo ${MOC_H_FILES} | xargs -n 1 basename) +MOC_SRC_FILES := $(MOC_BASENAMES:.hh=.moc.cc) +MOC_OBJ_FILES := $(patsubst %.moc.cc,obj/%.moc.o,${MOC_SRC_FILES}) + +BROWSER_SRC_FILES := $(wildcard src/ui/*.cc) +BROWSER_OBJ_FILES := $(patsubst src/ui/%.cc,obj/%.o,${BROWSER_SRC_FILES}) + +STATUSBAR_SRC_FILES := $(shell find src/ui/statusbar -name "*.cc") +STATUSBAR_OBJ_FILES := $(patsubst src/ui/statusbar/%.cc,obj/%.o,${STATUSBAR_SRC_FILES}) + +TABBAR_SRC_FILES := $(shell find src/ui/tabbar -name "*.cc") +TABBAR_OBJ_FILES := $(patsubst src/ui/tabbar/%.cc,obj/%.o,${TABBAR_SRC_FILES}) + +BASE_SRC_FILES := $(wildcard src/*.cc) +BASE_OBJ_FILES := $(patsubst src/%.cc,obj/%.o,${BASE_SRC_FILES}) + +H_FILES := $(shell find $(SRCDIR) -name '*.hh') +SRC_FILES := $(shell find $(SRCDIR) -name '*.cc') +OBJ_FILES := ${STATUSBAR_OBJ_FILES} ${TABBAR_OBJ_FILES} ${BROWSER_OBJ_FILES} ${BASE_OBJ_FILES} ${MOC_OBJ_FILES} +DEPS = $(OBJ_FILES:%.o=%.d) + +BIN = bin/$(PROJECT) +INSTALL = /usr/local/bin/ +TARGET ?= $(BIN) + +PKGDEPS = Qt5{Widgets,WebEngineWidgets,Core,WebEngine} +SANFLAGS ?= -fsanitize=undefined -fsanitize=address -fsanitize-address-use-after-scope + +CXXFLAGS ?= -std=c++17 -fPIC ${SANFLAGS} +CXXFLAGS += `pkg-config --cflags ${PKGDEPS}` + +LDFLAGS ?= ${SANFLAGS} +LDFLAGS += `pkg-config --libs ${PKGDEPS}` + +DEBUG_FLAGS = -Wall -DDEBUG + +CC = g++ diff --git a/src/common.hh b/src/common.hh @@ -0,0 +1,12 @@ +#ifndef __GUCK__COMMON__GUARD__ +#define __GUCK__COMMON__GUARD__ + +#include <string> + + +const std::string BROWSER_NAME = "guck"; +const std::string BROWSER_VERSION = "v0.1"; + +const std::string DEFAULT_HOMEPAGE = "https://youtube.com"; + +#endif//__GUCK__COMMON__GUARD__ diff --git a/src/defaults.hh b/src/defaults.hh @@ -0,0 +1,7 @@ +#ifndef __GUCK__DEFAULTS__GUARD__ +#define __GUCK__DEFAULTS__GUARD__ + +const unsigned TAB_HEIGHT = 16; +const unsigned STATUSBAR_HEIGHT = 16; + +#endif//__GUCK__DEFAULTS__GUARD__ diff --git a/src/guck.cc b/src/guck.cc @@ -0,0 +1,34 @@ +#include "guck.hh" + +#include "common.hh" +#include "util.hh" + +#include <QtWebEngine> +#include <QWebEngineSettings> + + +std::unique_ptr<guck_t> +guck_t::init(int argc, char** argv) +{ + QCoreApplication::setOrganizationName(BROWSER_NAME.c_str()); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + return std::make_unique<guck_t>(argc, argv); +} + +void +guck_t::setup() +{ + m_app->setApplicationName(BROWSER_NAME.c_str()); + m_app->setDesktopFileName(("org." + BROWSER_NAME + "." + BROWSER_NAME).c_str()); + m_app->setApplicationVersion(BROWSER_VERSION.c_str()); + m_app->setQuitOnLastWindowClosed(true); + QtWebEngine::initialize(); +} + +void +guck_t::run() +{ + m_app->exec(); +} diff --git a/src/guck.hh b/src/guck.hh @@ -0,0 +1,52 @@ +#ifndef __GUCK__GUCK__GUARD__ +#define __GUCK__GUCK__GUARD__ + +#include "common.hh" +#include "parse.hh" + +#include "ui/window.hh" + +#include <QApplication> + +#include <memory> +#include <string> +#include <vector> + + +class guck_t +{ +public: + guck_t(int argc, char** argv) + : m_parser(argc, argv) + { + m_parser.parse(); + auto&&[m_argc, m_args] = m_parser.getargs(); + m_app = new QApplication(m_argc, m_args.data()); + m_instances.push_back(new window_t()); + } + + ~guck_t() + { + for (size_t i = 0; i < m_instances.size(); ++i) + delete m_instances[i]; + + delete m_app; + } + + void setup(); + void run(); + + static std::unique_ptr<guck_t> init(int, char**); + +private: + QApplication* m_app; + parser_t m_parser; + + int m_argc; + std::vector<char*> m_args; + + std::vector<window_ptr_t> m_instances; + +}; + +#endif//__GUCK__GUCK__GUARD__ diff --git a/src/main.cc b/src/main.cc @@ -0,0 +1,13 @@ +#include "guck.hh" + + +int +main(int argc, char **argv) +{ + auto guck = guck_t::init(argc, argv); + + guck->setup(); + guck->run(); + + return 0; +} diff --git a/src/parse.cc b/src/parse.cc @@ -0,0 +1,44 @@ +#include "parse.hh" +#include "util.hh" + + +void +parser_t::parse() +{ + for (size_t i = 0; i < m_opts.size(); ++i) { + if (m_opts[i].rfind("--", 0) != std::string::npos) { + std::string optflag = m_opts[i].substr(2); + if (!m_optmap.count(optflag)) continue; + option_t opt = m_optmap[optflag]; + if (opt.has_value && i+1 < m_opts.size()) { + setopt(optflag, m_opts[i+1]); + ++i; + } else if (!opt.flag.empty() && !opt.has_value) + setopt(optflag); + } else if (m_opts[i].rfind("-", 0) != std::string::npos) { + std::string optflags = m_opts[i].substr(1); + for (auto& optflag : optflags) + setopt(std::string(1, optflag)); + } + } +} + +void +parser_t::setopt(std::string flag, std::string value) +{ + for (auto& opt : m_optlist) + if (!opt.flag.compare(flag)) { + opt.set = true; + opt.value = value; + } +} + +std::pair<int, std::vector<char*>> +parser_t::getargs() +{ + std::vector<char*> raw; + for (auto& s : m_args) + raw.push_back(convert_new(s)); + + return {raw.size(), raw}; +} diff --git a/src/parse.hh b/src/parse.hh @@ -0,0 +1,79 @@ +#ifndef __GUCK__PARSE__GUARD__ +#define __GUCK__PARSE__GUARD__ + +#include <string> +#include <vector> +#include <unordered_map> +#include <algorithm> +#include <iostream> + +enum option_type_t { longopt, shortopt }; + +struct option_t +{ + option_t() = default; + + option_t(option_type_t _type) + : type(_type), + has_value(false), + set(false) + {} + + option_t(option_type_t _type, std::string _flag, + std::string _desc, bool _has_value = false) + : flag(_flag), + desc(_desc), + has_value(_has_value), + set(false) + {} + + option_type_t type = shortopt; + std::string flag; + std::string desc; + std::string value; + bool has_value, set; +}; + + +class parser_t +{ +public: + parser_t(int argc, char** argv) + : m_opts(argv, argv + argc), + m_optlist({ + // short options flag description + { option_type_t::shortopt, "h", "print help message" }, + { option_type_t::shortopt, "v", "print version information and quit" }, + + // long options flag description has value + { option_type_t::longopt, "help", "print help message", false }, + { option_type_t::longopt, "version", "print version information and quit", false }, + { option_type_t::longopt, "no-config", "do not load configuration file", false }, + { option_type_t::longopt, "config-path", "path to configuration file", true }, + }) + { + for (auto& opt : m_optlist) + m_optmap[opt.flag] = opt; + + std::vector<std::string>::iterator it; + if ((it = std::find(m_opts.begin(), m_opts.end(), "--")) != m_opts.end()) { + std::move(it + 1, m_opts.end(), std::back_inserter(m_args)); + m_opts.erase(it, m_opts.end()); + } + } + + void parse(); + void setopt(std::string, std::string = ""); + + std::pair<int, std::vector<char*>> getargs(); + +private: + std::vector<std::string> m_opts; + std::vector<std::string> m_args; + + std::vector<option_t> m_optlist; + std::unordered_map<std::string, option_t> m_optmap; + +}; + +#endif//__GUCK__PARSE__GUARD__ diff --git a/src/ui/statusbar/backforward.cc b/src/ui/statusbar/backforward.cc diff --git a/src/ui/statusbar/backforward.hh b/src/ui/statusbar/backforward.hh @@ -0,0 +1,24 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__BACKFORWARD__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__BACKFORWARD__GUARD__ + +#include "textbase.hh" + + +class backforward_t : public textbase_t +{ + Q_OBJECT + +public: + backforward_t(QWidget& parent) + : textbase_t(parent), + m_parent(parent) + { + textbase_t::setText("[<>]"); + } + +private: + QWidget& m_parent; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__BACKFORWARD__GUARD__ diff --git a/src/ui/statusbar/command.cc b/src/ui/statusbar/command.cc diff --git a/src/ui/statusbar/command.hh b/src/ui/statusbar/command.hh @@ -0,0 +1,22 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__COMMAND__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__COMMAND__GUARD__ + +#include <QWidget> + + +class command_t : public QWidget +{ + Q_OBJECT + +public: + command_t(QWidget& parent) + : QWidget(&parent), + m_parent(parent) + {} + +private: + QWidget& m_parent; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__COMMAND__GUARD__ diff --git a/src/ui/statusbar/elidedlabel.cc b/src/ui/statusbar/elidedlabel.cc @@ -0,0 +1,54 @@ +#include "elidedlabel.hh" + +#include <QPainter> +#include <QTextLayout> + + +void +elidedlabel_t::setText(const QString& text) +{ + m_content = text; + update(); +} + +void +elidedlabel_t::paintEvent(QPaintEvent* event) +{ + QFrame::paintEvent(event); + + QPainter painter(this); + QFontMetrics metrics = painter.fontMetrics(); + + bool has_elided = false; + int linespacing = metrics.lineSpacing(); + int y = 0; + + QTextLayout textLayout(m_content, painter.font()); + textLayout.beginLayout(); + forever { + QTextLine line = textLayout.createLine(); + + if (!line.isValid()) + break; + + line.setLineWidth(width()); + int nextline_y = y + linespacing; + + if (height() >= nextline_y + linespacing) { + line.draw(&painter, QPoint(0, y)); + y = nextline_y; + } else { + QString lastline = m_content.mid(line.textStart()); + QString lastline_elided = metrics.elidedText(lastline, Qt::ElideRight, width()); + painter.drawText(QPoint(0, y + metrics.ascent()), lastline_elided); + line = textLayout.createLine(); + has_elided = line.isValid(); + break; + } + } + textLayout.endLayout(); + if (has_elided != m_elided) { + m_elided = has_elided; + emit elisionChanged(has_elided); + } +} diff --git a/src/ui/statusbar/elidedlabel.hh b/src/ui/statusbar/elidedlabel.hh @@ -0,0 +1,40 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__ELIDEDLABEL__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__ELIDEDLABEL__GUARD__ + +#include <QFrame> + + +// https://doc.qt.io/qt-5/qtwidgets-widgets-elidedlabel-example.html +class elidedlabel_t : public QFrame +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(bool isElided READ isElided) + +public: + explicit elidedlabel_t(QWidget& parent, const QString& text = "") + : QFrame(&parent), + m_content(text), + m_elided(false) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + } + + void setText(const QString&); + + inline const QString& text() const { return m_content; } + inline bool isElided() const { return m_elided; } + +protected: + void paintEvent(QPaintEvent*) override; + +signals: + void elisionChanged(bool); + +private: + QString m_content; + bool m_elided; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__ELIDEDLABEL__GUARD__ diff --git a/src/ui/statusbar/position.cc b/src/ui/statusbar/position.cc diff --git a/src/ui/statusbar/position.hh b/src/ui/statusbar/position.hh @@ -0,0 +1,24 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__POSITION__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__POSITION__GUARD__ + +#include "textbase.hh" + + +class position_t : public textbase_t +{ + Q_OBJECT + +public: + position_t(QWidget& parent) + : textbase_t(parent), + m_parent(parent) + { + textbase_t::setText("[top]"); + } + +private: + QWidget& m_parent; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__POSITION__GUARD__ diff --git a/src/ui/statusbar/progress.cc b/src/ui/statusbar/progress.cc @@ -0,0 +1,24 @@ +#include "progress.hh" + + +void +progress_t::on_tab_change() +{ + +} + +void +progress_t::on_load() +{ + QProgressBar::setValue(0); + QProgressBar::setVisible(m_enabled); +} + +void +progress_t::on_progress(int value) +{ + QProgressBar::setValue(value); + + if (value == 100) + QProgressBar::hide(); +} diff --git a/src/ui/statusbar/progress.hh b/src/ui/statusbar/progress.hh @@ -0,0 +1,37 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__PROGRESS__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__PROGRESS__GUARD__ + +#include <QProgressBar> + + +class progress_t : public QProgressBar +{ + Q_OBJECT + +public: + progress_t(QWidget& parent) + : QProgressBar(&parent), + m_parent(parent), + m_enabled(false), + m_value(0) + { + QProgressBar::setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + QProgressBar::setTextVisible(false); + QProgressBar::hide(); + } + + void on_tab_change(); + +protected slots: + void on_load(); + void on_progress(int); + +private: + QWidget& m_parent; + + bool m_enabled; + int m_value; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__PROGRESS__GUARD__ diff --git a/src/ui/statusbar/status.cc b/src/ui/statusbar/status.cc @@ -0,0 +1,30 @@ +#include "status.hh" + + +void +status_t::on_tab_change() +{ + +} + +void +status_t::on_set_text(const QString& text) +{ + m_text.setText(text); +} + +void +status_t::resize() +{ + auto size = m_parent.size(); + if (size.height() > TAB_HEIGHT) { + QWidget::move(0, size.height() - TAB_HEIGHT); + QWidget::resize(size.width(), STATUSBAR_HEIGHT); + } +} + +void +status_t::draw() +{ + +} diff --git a/src/ui/statusbar/status.hh b/src/ui/statusbar/status.hh @@ -0,0 +1,75 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__STATUS__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__STATUS__GUARD__ + +#include "../../defaults.hh" + +#include "command.hh" +#include "text.hh" +#include "url.hh" +#include "position.hh" +#include "backforward.hh" +#include "tabindex.hh" +#include "progress.hh" + +#include <QLineEdit> +#include <QHBoxLayout> +#include <QStackedLayout> + + +typedef class status_t : public QWidget +{ + Q_OBJECT + +public: + status_t(QWidget& parent) + : QWidget(&parent), + m_parent(parent), + m_layout(), + m_stack(), + m_command(*this), + m_text(*this), + m_url(*this), + m_position(*this), + m_backforward(*this), + m_tabindex(*this), + m_progress(*this) + { + QWidget::setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + m_layout.setContentsMargins(0, 0, 0, 0); + m_layout.setSpacing(5); + m_layout.addLayout(&m_stack); + m_stack.setContentsMargins(0, 0, 0, 0); + m_stack.addWidget(&m_command); + m_stack.addWidget(&m_text); + m_layout.addWidget(&m_url); + m_layout.addWidget(&m_position); + m_layout.addWidget(&m_backforward); + m_layout.addWidget(&m_tabindex); + m_layout.addWidget(&m_progress); + QWidget::setLayout(&m_layout); + } + + void resize(); + void draw(); + +protected slots: + void on_tab_change(); + void on_set_text(const QString&); + +private: + QWidget& m_parent; + + QHBoxLayout m_layout; + QStackedLayout m_stack; + + command_t m_command; + text_t m_text; + url_t m_url; + position_t m_position; + backforward_t m_backforward; + tabindex_t m_tabindex; + progress_t m_progress; + +}* status_ptr_t; + +#endif//__GUCK__BROWSER__STATUSBAR__STATUS__GUARD__ diff --git a/src/ui/statusbar/tabindex.cc b/src/ui/statusbar/tabindex.cc diff --git a/src/ui/statusbar/tabindex.hh b/src/ui/statusbar/tabindex.hh @@ -0,0 +1,24 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__TABINDEX__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__TABINDEX__GUARD__ + +#include "textbase.hh" + + +class tabindex_t : public textbase_t +{ + Q_OBJECT + +public: + tabindex_t(QWidget& parent) + : textbase_t(parent), + m_parent(parent) + { + textbase_t::setText("[1/1]"); + } + +private: + QWidget& m_parent; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__TABINDEX__GUARD__ diff --git a/src/ui/statusbar/text.cc b/src/ui/statusbar/text.cc @@ -0,0 +1,20 @@ +#include "text.hh" + +void +text_t::setText(const QString& text, bool temp) +{ + (temp ? m_temp : m_perm) = text; + if (!m_temp.isEmpty()) + textbase_t::setText(m_temp); + else if (!m_perm.isEmpty()) + textbase_t::setText(m_perm); + else + textbase_t::setText(""); +} + +void +text_t::check_reset(const QString& text) +{ + if (!m_perm.compare(text)) + m_perm.clear(); +} diff --git a/src/ui/statusbar/text.hh b/src/ui/statusbar/text.hh @@ -0,0 +1,31 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__TEXT__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__TEXT__GUARD__ + +#include "textbase.hh" + + +class text_t : public textbase_t +{ + Q_OBJECT + +public: + text_t(QWidget& parent) + : textbase_t(parent), + m_parent(parent), + m_perm(""), + m_temp("") + {} + + void setText(const QString&, bool = false); + +protected slots: + void check_reset(const QString&); + +private: + QWidget& m_parent; + QString m_perm; + QString m_temp; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__TEXT__GUARD__ diff --git a/src/ui/statusbar/textbase.cc b/src/ui/statusbar/textbase.cc diff --git a/src/ui/statusbar/textbase.hh b/src/ui/statusbar/textbase.hh @@ -0,0 +1,24 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__TEXTBASE__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__TEXTBASE__GUARD__ + +#include <QWidget> +#include <QLabel> + +class textbase_t : public QLabel +{ + Q_OBJECT + +public: + textbase_t(QWidget& parent) + : QLabel(&parent), + m_parent(parent) + { + QLabel::setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + } + +private: + QWidget& m_parent; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__TEXTBASE__GUARD__ diff --git a/src/ui/statusbar/url.cc b/src/ui/statusbar/url.cc @@ -0,0 +1,61 @@ +#include "url.hh" + +#include "../../util.hh" + + +void +url_t::update() +{ + if (!m_hover.isEmpty()) { + textbase_t::setText(m_hover.toString()); + m_type = urltype_t::hover; + } else if (!m_loaded.isEmpty()) { + textbase_t::setText(m_loaded.toString()); + m_type = m_status; + } else { + textbase_t::setText(""); + m_type = urltype_t::normal; + } +} + +void +url_t::on_tab_change(QUrl url) // tab changed +{ + +} + +void +url_t::on_status_change() // add loadstatus +{ + +} + +void +url_t::on_set_loaded(QUrl url) +{ + if (url.isEmpty()) + m_loaded.clear(); + else if (!url.isValid()) + m_loaded = "invalid"; + else + m_loaded = safe_displaystring(url); + + m_status = urltype_t::normal; + update(); +} + +void +url_t::on_set_hover(std::string& link) +{ + if (link.empty()) { + m_hover.clear(); + } else { + QUrl url = QUrl(link.c_str()); + if (url.isValid()) + m_hover = safe_displaystring(url); + else + m_hover = ("(invalid) " + link).c_str(); + } + + update(); +} diff --git a/src/ui/statusbar/url.hh b/src/ui/statusbar/url.hh @@ -0,0 +1,55 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__URL__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__URL__GUARD__ + +#include "textbase.hh" + +#include <QWidget> +#include <QUrl> + + +enum class urltype_t +{ + success, + success_https, + error, + warn, + hover, + normal +}; + +class url_t : public textbase_t +{ + Q_OBJECT + +public: + url_t(QWidget& parent) + : textbase_t(parent), + m_parent(parent), + m_hover(""), + m_loaded("https://deurzen.net"), + m_type(urltype_t::normal), + m_status(urltype_t::normal) + { + update(); + } + + void update(); + void on_tab_change(QUrl); + +protected slots: + void on_status_change(); + void on_set_loaded(QUrl); + void on_set_hover(std::string&); + +private: + QWidget& m_parent; + + QUrl m_hover; + QUrl m_loaded; + + urltype_t m_type; + urltype_t m_status; + +}; + +#endif//__GUCK__BROWSER__STATUSBAR__URL__GUARD__ diff --git a/src/ui/tabbar/tab.cc b/src/ui/tabbar/tab.cc @@ -0,0 +1,28 @@ +#include "tab.hh" + +#include <QString> + + +void +tab_t::resize() +{ + auto size = m_parent.size(); + if (size.height() > TAB_HEIGHT + STATUSBAR_HEIGHT) + QWebEngineView::resize(size.width(), + size.height() - TAB_HEIGHT - STATUSBAR_HEIGHT); +} + +void +tab_t::open(const std::string&& url) +{ + open(QUrl(url.c_str())); +} + +void +tab_t::open(QUrl url) +{ + if (!url.isValid()) + return; + + QWebEngineView::load(url); +} diff --git a/src/ui/tabbar/tab.hh b/src/ui/tabbar/tab.hh @@ -0,0 +1,60 @@ +#ifndef __GUCK__BROWSER__STATUSBAR__TAB__GUARD__ +#define __GUCK__BROWSER__STATUSBAR__TAB__GUARD__ + +#include "../../defaults.hh" + +#include <QKeyEvent> +#include <QUrl> +#include <QWebEngineView> +#include <QWidget> + +#include <iostream> + +class key_filter_t : public QObject +{ + Q_OBJECT + +protected: + bool eventFilter(QObject* obj, QEvent* event) + { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* key = static_cast<QKeyEvent*>(event); + + std::cout << "QWebEngineView " << key->key() << std::endl; + + return QObject::eventFilter(obj, event); + } else { + return QObject::eventFilter(obj, event); + } + return false; + } +}; + +typedef class tab_t : public QWebEngineView +{ + Q_OBJECT + +public: + tab_t(QWidget& parent) + : QWebEngineView(&parent), + m_parent(parent) + { + installEventFilter(new key_filter_t()); + + QWebEngineView::move(0, TAB_HEIGHT); + resize(); + } + + virtual ~tab_t() {} + + void resize(); + + void open(const std::string&&); + void open(QUrl); + +private: + QWidget& m_parent; + +}* tab_ptr_t; + +#endif//__GUCK__BROWSER__STATUSBAR__TAB__GUARD__ diff --git a/src/ui/window.cc b/src/ui/window.cc @@ -0,0 +1,46 @@ +#include "window.hh" + +#include <QResizeEvent> + +void +window_t::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + m_current_tab->resize(); + m_statusbar->resize(); +} + +void +window_t::on_url_send() +{ +} + +void +window_t::on_toggle_released() +{ + +} + +void +window_t::on_gen_released() +{ + +} + +void +window_t::on_selection_changed() +{ + +} + +void +window_t::on_back() +{ + +} + +void +window_t::on_forward() +{ + +} diff --git a/src/ui/window.hh b/src/ui/window.hh @@ -0,0 +1,71 @@ +#ifndef __GUCK__BROWSER__WINDOW__GUARD__ +#define __GUCK__BROWSER__WINDOW__GUARD__ + +#include "../common.hh" + +#include "tabbar/tab.hh" +#include "statusbar/status.hh" + +#include <QWidget> +#include <QKeyEvent> + +#include <vector> +#include <iostream> + + +typedef class window_t : public QWidget +{ + Q_OBJECT + +public: + window_t() + : m_statusbar(new status_t(*this)) + { + QWidget::resize(800, 600); + QWidget::setWindowTitle(BROWSER_NAME.c_str()); + + m_statusbar->resize(); + + m_tabs.push_back((m_current_tab = new tab_t{*this})); + m_tabs.front()->open(DEFAULT_HOMEPAGE.c_str()); + + show(); + } + + virtual ~window_t() + { + for (size_t i = 0; i < m_tabs.size(); ++i) + delete m_tabs[i]; + } + +protected: + void resizeEvent(QResizeEvent*) override; + + void keyPressEvent(QKeyEvent *event) override + { + std::cout << "press " << event->key() << std::endl; + QWidget::keyPressEvent(event); + } + + void keyReleaseEvent(QKeyEvent *event) override + { + std::cout << "release " << event->key() << std::endl; + QWidget::keyReleaseEvent(event); + } + +private slots: + void on_url_send(); + void on_toggle_released(); + void on_gen_released(); + void on_selection_changed(); + void on_back(); + void on_forward(); + +private: + tab_ptr_t m_current_tab; + std::vector<tab_ptr_t> m_tabs; + status_ptr_t m_statusbar; + +}* window_ptr_t; + +#endif//__GUCK__BROWSER__WINDOW__GUARD__ diff --git a/src/util.cc b/src/util.cc @@ -0,0 +1,40 @@ +#include "util.hh" + +#include <QString> +#include <QUrl> + +#include <vector> +#include <string> + +char* +convert_new(const std::string& s) +{ + char* raw = new char[s.size() + 1]; + std::strcpy(raw, s.c_str()); + return raw; +} + +const char* +convert(const std::string& s) +{ + return s.c_str(); +} + +QString +safe_displaystring(QUrl url) +{ + auto host = url.host(QUrl::FullyEncoded); + std::string host_str = host.toStdString(); + + auto pos = 0u; + auto end = host_str.find("."); + + while (end != std::string::npos) { + if (!host_str.substr(pos, end - pos).rfind("xn--", pos) && host.compare(url.host(QUrl::FullyDecoded))) + return "(" + host + ") " + url.toDisplayString(); + pos = end + 1; + end = host_str.find("."); + } + + return url.toDisplayString(); +} diff --git a/src/util.hh b/src/util.hh @@ -0,0 +1,14 @@ +#ifndef __GUCK__UTIL__GUARD__ +#define __GUCK__UTIL__GUARD__ + +#include <cstring> +#include <string> + +char* convert_new(const std::string&); +const char* convert(const std::string&); + +class QString; +class QUrl; +QString safe_displaystring(QUrl); + +#endif//__GUCK__UTIL__GUARD__