commit dcf52d29fb7ff6aaf4675e37f949ca7cbaa883b3
Author: deurzen <max@deurzen.net>
Date: Wed, 25 May 2022 05:51:13 +0200
initial commit
Diffstat:
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__