summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gui_gtk.cc164
-rw-r--r--src/gui_htmlattrtext.cc22
-rw-r--r--src/gui_htmlattrtext.hh13
-rw-r--r--src/gui_qt.cc184
-rw-r--r--src/monitor-gui.cc26
5 files changed, 407 insertions, 2 deletions
diff --git a/src/gui_gtk.cc b/src/gui_gtk.cc
index afccd85..6afec5d 100644
--- a/src/gui_gtk.cc
+++ b/src/gui_gtk.cc
@@ -17,6 +17,7 @@
#include "gui_main.hh"
#include "gui_menu.hh"
#include "gui_statusbar.hh"
+#include "gui_textwnd.hh"
#include "looper.hh"
#include "observers.hh"
@@ -1185,6 +1186,162 @@ private:
bool applied_;
};
+class GtkGuiTextWindow : public virtual GuiTextWindow, public GtkGuiWindow {
+public:
+ GtkGuiTextWindow(std::string const& title, uint32_t width, uint32_t height,
+ AttributedText const* text)
+ : title_(title), width_(width), height_(height), text_(text), wnd_(nullptr),
+ view_(nullptr), end_mark_(nullptr) {
+ }
+
+ ~GtkGuiTextWindow() override {
+ disconnect_buffer();
+ if (wnd_) {
+ gtk_widget_destroy(wnd_);
+ }
+ }
+
+ void set_title(std::string const& title) override {
+ title_ = title;
+ GtkGuiWindow::set_title(title);
+ }
+
+ void set_text(AttributedText const* text) override {
+ if (!text) {
+ assert(false);
+ if (text_own_) text_own_->reset();
+ return;
+ }
+ disconnect_buffer();
+ text_ = text;
+ connect_buffer();
+ text_own_.reset();
+ }
+
+ void set_text(std::unique_ptr<AttributedText>&& text) override {
+ if (!text) {
+ assert(false);
+ if (text_own_) text_own_->reset();
+ return;
+ }
+ disconnect_buffer();
+ text_ = text.get();
+ connect_buffer();
+ text_own_.swap(text);
+ }
+
+ AttributedText const* text() const override {
+ return text_;
+ }
+
+ void* impl() const override {
+ return wnd_;
+ }
+
+ void add_listener(GuiTextWindow::Listener* listener) override {
+ observers_.insert(listener);
+ }
+
+ void remove_listener(GuiTextWindow::Listener* listener) override {
+ observers_.erase(listener);
+ }
+
+ void show(GuiWindow* UNUSED(parent)) override {
+ if (wnd_) {
+ assert(false);
+ focus();
+ return;
+ }
+ wnd_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(G_OBJECT(wnd_), "delete-event",
+ G_CALLBACK(delete_event), this);
+ gtk_window_set_default_size(GTK_WINDOW(wnd_), width_, height_);
+ gtk_window_set_title(GTK_WINDOW(wnd_), title_.c_str());
+ view_ = gtk_text_view_new();
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(view_), false);
+ gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view_), false);
+ connect_buffer();
+ auto scroll = gtk_scrolled_window_new(nullptr, nullptr);
+ gtk_container_add(GTK_CONTAINER(scroll), view_);
+ gtk_container_add(GTK_CONTAINER(wnd_), scroll);
+ gtk_widget_show_all(wnd_);
+ }
+
+ void focus() override {
+ if (!wnd_) {
+ assert(false);
+ return;
+ }
+ gtk_window_present(GTK_WINDOW(wnd_));
+ }
+
+protected:
+ bool notify_about_to_close() {
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ if (!it.next()->about_to_close(this)) return false;
+ }
+ return true;
+ }
+
+ void disconnect_buffer() {
+ if (!view_ || !text_) return;
+ auto buf = static_cast<GtkAttributedText const*>(text_)->buffer();
+ g_signal_handler_disconnect(G_OBJECT(buf), changed_handler_);
+ gtk_text_buffer_delete_mark(buf, end_mark_);
+ end_mark_ = nullptr;
+ }
+
+ void scroll_to_bottom() {
+ gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(view_), end_mark_,
+ 0.0, true, 0.0, 1.0);
+ }
+
+ void connect_buffer() {
+ if (!view_ || !text_) return;
+ auto buf = static_cast<GtkAttributedText const*>(text_)->buffer();
+ changed_handler_ = g_signal_connect(G_OBJECT(buf), "changed",
+ G_CALLBACK(buf_changed), this);
+ GtkTextIter end;
+ gtk_text_buffer_get_end_iter(buf, &end);
+ end_mark_ = gtk_text_buffer_create_mark(buf, nullptr, &end, false);
+ gtk_text_view_set_buffer(GTK_TEXT_VIEW(view_), buf);
+ }
+
+ static void buf_changed(GtkTextBuffer* UNUSED(buffer), gpointer user_data) {
+ auto me = reinterpret_cast<GtkGuiTextWindow*>(user_data);
+ me->scroll_to_bottom();
+ }
+
+ static gboolean delete_event(GtkWidget* widget, GdkEvent* UNUSED(event),
+ gpointer user_data) {
+ auto me = reinterpret_cast<GtkGuiTextWindow*>(user_data);
+ assert(me->wnd_ == widget);
+ me->disconnect_buffer();
+ me->wnd_ = nullptr;
+ auto view = me->view_;
+ me->view_ = nullptr;
+ if (me->notify_about_to_close()) {
+ gtk_widget_destroy(widget);
+ return false;
+ }
+ me->wnd_ = widget;
+ me->view_ = view;
+ me->connect_buffer();
+ return true;
+ }
+
+ std::string title_;
+ uint32_t width_;
+ uint32_t height_;
+ AttributedText const* text_;
+ std::unique_ptr<AttributedText> text_own_;
+ Observers<GuiTextWindow::Listener*> observers_;
+ GtkWidget* wnd_;
+ GtkWidget* view_;
+ GtkTextMark* end_mark_;
+ gulong changed_handler_;
+};
struct _MainApp
{
@@ -1667,3 +1824,10 @@ Looper* GuiMain::createLooper() {
AttributedText* AttributedText::create() {
return new GtkAttributedText();
}
+
+// static
+GuiTextWindow* GuiTextWindow::create(std::string const& title,
+ uint32_t width, uint32_t height,
+ AttributedText const* text) {
+ return new GtkGuiTextWindow(title, width, height, text);
+}
diff --git a/src/gui_htmlattrtext.cc b/src/gui_htmlattrtext.cc
index ceaeba8..453d550 100644
--- a/src/gui_htmlattrtext.cc
+++ b/src/gui_htmlattrtext.cc
@@ -8,6 +8,7 @@
#include <vector>
#include "gui_htmlattrtext.hh"
+#include "observers.hh"
namespace {
@@ -73,6 +74,7 @@ public:
auto offset = text_.size();
text_.append(str + start, length);
if (attr != EMPTY) ranges_.emplace_back(attr, offset, offset + length);
+ notify_changed();
}
void add(Attribute const& attr, size_t start, size_t length) override {
@@ -83,9 +85,11 @@ public:
auto it = std::lower_bound(ranges_.begin(), ranges_.end(), r);
if (it != ranges_.end() && it->start_ == r.start_ && it->end_ == r.end_) {
it->attr_.add(r.attr_);
+ notify_changed();
return;
}
ranges_.insert(it, r);
+ notify_changed();
}
void set(Attribute const& attr, size_t start, size_t length) override {
clear(start, length);
@@ -117,6 +121,7 @@ public:
}
++it;
}
+ notify_changed();
}
std::string html() const override {
@@ -163,6 +168,15 @@ public:
void reset() override {
text_.clear();
ranges_.clear();
+ notify_changed();
+ }
+
+ void add_listener(Listener* listener) override {
+ observers_.insert(listener);
+ }
+
+ void remove_listener(Listener* listener) override {
+ observers_.erase(listener);
}
private:
@@ -188,6 +202,13 @@ private:
return start + length;
}
+ void notify_changed() {
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ it.next()->changed(this);
+ }
+ }
+
static void start_tag(std::string* out, Attribute const& attr) {
out->append("<span style=\"");
if (attr.bold()) out->append("font-weight: bold; ");
@@ -213,6 +234,7 @@ private:
std::string text_;
std::vector<Range> ranges_;
+ Observers<Listener*> observers_;
};
} // namespace
diff --git a/src/gui_htmlattrtext.hh b/src/gui_htmlattrtext.hh
index 91e1cc1..96b0447 100644
--- a/src/gui_htmlattrtext.hh
+++ b/src/gui_htmlattrtext.hh
@@ -7,10 +7,23 @@
class HtmlAttributedText : public AttributedText {
public:
+ class Listener {
+ public:
+ virtual ~Listener() {}
+
+ virtual void changed(HtmlAttributedText* text) = 0;
+
+ protected:
+ Listener() {}
+ };
+
static HtmlAttributedText* create();
virtual std::string html() const = 0;
+ virtual void add_listener(Listener* listener) = 0;
+ virtual void remove_listener(Listener* listener) = 0;
+
protected:
HtmlAttributedText() {}
};
diff --git a/src/gui_qt.cc b/src/gui_qt.cc
index 7aa52d3..cc2ad0d 100644
--- a/src/gui_qt.cc
+++ b/src/gui_qt.cc
@@ -5,6 +5,7 @@
#include <QAction>
#include <QApplication>
#include <QClipboard>
+#include <QCloseEvent>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QHeaderView>
@@ -44,6 +45,7 @@
#include "gui_main.hh"
#include "gui_menu.hh"
#include "gui_statusbar.hh"
+#include "gui_textwnd.hh"
#include "looper.hh"
#include "observers.hh"
@@ -1098,6 +1100,181 @@ private:
bool applied_;
};
+class OptionalCloseWidget : public QWidget {
+public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual bool about_to_close() = 0;
+
+ protected:
+ Delegate() {}
+ };
+
+ OptionalCloseWidget(QWidget* parent, Delegate* delegate)
+ : QWidget(parent), delegate_(delegate) {
+ }
+
+protected:
+ void closeEvent(QCloseEvent* event) override {
+ if (!delegate_->about_to_close()) {
+ event->ignore();
+ return;
+ }
+ this->QWidget::closeEvent(event);
+ }
+
+private:
+ Delegate* const delegate_;
+};
+
+class QtGuiTextWindow : public virtual GuiTextWindow, public QtGuiWindow,
+ public virtual OptionalCloseWidget::Delegate,
+ public virtual HtmlAttributedText::Listener {
+public:
+ QtGuiTextWindow(std::string const& title, uint32_t width, uint32_t height,
+ AttributedText const* text)
+ : title_(title), width_(width), height_(height),
+ text_(static_cast<HtmlAttributedText const*>(text)),
+ widget_(nullptr), view_(nullptr) {
+ }
+
+ ~QtGuiTextWindow() override {
+ }
+
+ void set_text(AttributedText const* text) override {
+ if (!text) {
+ assert(false);
+ if (text_own_) text_own_->reset();
+ return;
+ }
+ disconnect_buffer();
+ text_ = static_cast<HtmlAttributedText const*>(text);
+ connect_buffer();
+ text_own_.reset();
+ }
+
+ void set_text(std::unique_ptr<AttributedText>&& text) override {
+ if (!text) {
+ assert(false);
+ if (text_own_) text_own_->reset();
+ return;
+ }
+ disconnect_buffer();
+ text_ = static_cast<HtmlAttributedText const*>(text.get());
+ connect_buffer();
+ text_own_.swap(text);
+ }
+
+ AttributedText const* text() const override {
+ return text_;
+ }
+
+ void add_listener(GuiTextWindow::Listener* listener) override {
+ observers_.insert(listener);
+ }
+
+ void remove_listener(GuiTextWindow::Listener* listener) override {
+ observers_.erase(listener);
+ }
+
+ void set_title(std::string const& title) override {
+ title_ = title;
+ QtGuiWindow::set_title(title);
+ }
+
+ QWidget* widget() const override {
+ return widget_.get();
+ }
+
+ void* impl() const override {
+ return QtGuiWindow::impl();
+ }
+
+ bool showWidget() override {
+ show();
+ return true;
+ }
+
+ void show(GuiWindow* UNUSED(parent)) override {
+ if (widget_) {
+ focus();
+ return;
+ }
+ show();
+ }
+
+ void focus() override {
+ if (!widget_) {
+ assert(false);
+ show();
+ return;
+ }
+ widget_->raise();
+ widget_->activateWindow();
+ }
+
+private:
+ void show() {
+ widget_.reset(new OptionalCloseWidget(nullptr, this));
+ widget_->setWindowTitle(QString::fromStdString(title_));
+ layout_.reset(new SizeHintLayout(widget_.get(), QSize(width_, height_)));
+ view_ = new QTextEdit();
+ view_->setReadOnly(true);
+ connect_buffer();
+ if (text_) view_->setHtml(QString::fromStdString(text_->html()));
+ widget_->layout()->addWidget(view_);
+ widget_->show();
+ }
+
+ bool about_to_close() override {
+ disconnect_buffer();
+ auto view = view_;
+ view_ = nullptr;
+ auto widget = widget_.release();
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ if (!it.next()->about_to_close(this)) {
+ widget_.reset(widget);
+ view_ = view;
+ connect_buffer();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void changed(HtmlAttributedText* text) override {
+ assert(text == text_);
+ if (view_) {
+ view_->setHtml(QString::fromStdString(text->html()));
+ view_->moveCursor(QTextCursor::End);
+ view_->ensureCursorVisible();
+ }
+ }
+
+ void connect_buffer() {
+ if (!text_ || !view_) return;
+ const_cast<HtmlAttributedText*>(text_)->add_listener(this);
+ }
+
+ void disconnect_buffer() {
+ if (!text_ || !view_) return;
+ const_cast<HtmlAttributedText*>(text_)->remove_listener(this);
+ }
+
+ std::string title_;
+ uint32_t width_;
+ uint32_t height_;
+ HtmlAttributedText const* text_;
+ std::unique_ptr<AttributedText> text_own_;
+ Observers<GuiTextWindow::Listener*> observers_;
+ std::unique_ptr<SizeHintLayout> layout_;
+ std::unique_ptr<QWidget> widget_;
+ QTextEdit* view_;
+};
+
bool QtGuiMain::run(int argc, char** argv) {
QApplication app(argc, argv);
app.setStyleSheet("QStatusBar::item { border: 0px }");
@@ -1322,3 +1499,10 @@ Looper* GuiMain::createLooper() {
AttributedText* AttributedText::create() {
return HtmlAttributedText::create();
}
+
+// static
+GuiTextWindow* GuiTextWindow::create(std::string const& title,
+ uint32_t width, uint32_t height,
+ AttributedText const* text) {
+ return new QtGuiTextWindow(title, width, height, text);
+}
diff --git a/src/monitor-gui.cc b/src/monitor-gui.cc
index c88bb8e..fa43169 100644
--- a/src/monitor-gui.cc
+++ b/src/monitor-gui.cc
@@ -19,6 +19,7 @@
#include "gui_menu.hh"
#include "gui_main.hh"
#include "gui_statusbar.hh"
+#include "gui_textwnd.hh"
#include "io.hh"
#include "logger.hh"
#include "looper.hh"
@@ -37,6 +38,7 @@ std::string const ACTION_ABOUT = "about";
std::string const ACTION_COPY_RAW = "copy_raw";
std::string const ACTION_COPY_TEXT = "copy_text";
std::string const ACTION_CLEAR = "clear";
+std::string const ACTION_PROXY_LOG = "proxy_log";
bool valid_hostname(std::string const& host) {
return !host.empty();
@@ -315,7 +317,8 @@ private:
std::unique_ptr<AttributedText> text_;
};
-class MonitorGui : GuiMenu::Listener, GuiMain::Listener, Monitor::Delegate {
+class MonitorGui : GuiMenu::Listener, GuiMain::Listener, Monitor::Delegate,
+ GuiTextWindow::Listener {
private:
class ConnectFormListener : public GuiFormApply::Listener {
public:
@@ -436,7 +439,7 @@ private:
public:
MonitorGui()
: packages_(new PackageList()),
- main_(GuiMain::create("TransparentProxy Monitor", 800, 400)),
+ main_(GuiMain::create("TransparentProxy Monitor", 800, 500)),
menu_(GuiMenu::create()),
statusbar_(GuiStatusBar::create()),
looper_(main_->createLooper()),
@@ -455,6 +458,7 @@ public:
edit->add_item(ACTION_CLEAR, "Clear");
auto help = menu_->add_menu("Help");
help->add_item(ACTION_ABOUT, "About...");
+ help->add_item(ACTION_PROXY_LOG, "Proxy log...");
main_->set_menu(menu_.get());
main_->set_statusbar(statusbar_.get());
main_->set_split(0.7);
@@ -565,6 +569,16 @@ public:
main_->add_to_clipboard(pkg.data, "application/octet-stream");
} else if (id == ACTION_CLEAR) {
packages_->clear();
+ } else if (id == ACTION_PROXY_LOG) {
+ if (!proxy_logwnd_) {
+ if (!proxy_logger_) proxy_logger_.reset(new StringLogger());
+ proxy_logwnd_.reset(GuiTextWindow::create(
+ "Proxy log", 500, 200, proxy_logger_->text()));
+ proxy_logwnd_->add_listener(this);
+ proxy_logwnd_->show(main_.get());
+ } else {
+ proxy_logwnd_->focus();
+ }
} else {
assert(false);
}
@@ -600,6 +614,13 @@ public:
menu_->enable_item(ACTION_COPY_TEXT, false);
}
+ // GuiTextWindow::Listener
+ bool about_to_close(GuiTextWindow* wnd) override {
+ assert(proxy_logwnd_.get() == wnd);
+ proxy_logwnd_.reset();
+ return true;
+ }
+
// Monitor::Delegate
void state(Monitor* monitor, Monitor::State state) override {
assert(monitor == monitor_.get());
@@ -695,6 +716,7 @@ private:
std::unique_ptr<Config> proxy_config_;
std::unique_ptr<StringLogger> proxy_logger_;
std::unique_ptr<Proxy> proxy_;
+ std::unique_ptr<GuiTextWindow> proxy_logwnd_;
bool has_selection_;
size_t selection_;
};