diff options
| -rw-r--r-- | src/gui_gtk.cc | 164 | ||||
| -rw-r--r-- | src/gui_htmlattrtext.cc | 22 | ||||
| -rw-r--r-- | src/gui_htmlattrtext.hh | 13 | ||||
| -rw-r--r-- | src/gui_qt.cc | 184 | ||||
| -rw-r--r-- | src/monitor-gui.cc | 26 |
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_; }; |
