summaryrefslogtreecommitdiff
path: root/src/gui_qt.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2017-06-15 23:20:00 +0200
committerJoel Klinghed <the_jk@yahoo.com>2017-07-22 22:08:54 +0200
commitcb17c3035bbd80bd8ea6718bae4c57cfb2555653 (patch)
treef454181a2f58071f5f2ba4408e7e179838ed3fb4 /src/gui_qt.cc
parentface8e0a7d5f530ee3e5e63ab1e3d6ecd497326b (diff)
Initial monitor GUI
Basic monitor functionality, GTK-3.0 and QT5 backends
Diffstat (limited to 'src/gui_qt.cc')
-rw-r--r--src/gui_qt.cc1277
1 files changed, 1277 insertions, 0 deletions
diff --git a/src/gui_qt.cc b/src/gui_qt.cc
new file mode 100644
index 0000000..dcfd320
--- /dev/null
+++ b/src/gui_qt.cc
@@ -0,0 +1,1277 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+
+#include <QAction>
+#include <QApplication>
+#include <QClipboard>
+#include <QDialogButtonBox>
+#include <QGridLayout>
+#include <QHeaderView>
+#include <QIntValidator>
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QMainWindow>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMessageBox>
+#include <QMimeData>
+#include <QProgressBar>
+#include <QPushButton>
+#include <QSocketNotifier>
+#include <QSplitter>
+#include <QStatusBar>
+#include <QTextCodec>
+#include <QTextEdit>
+#include <QTimer>
+#include <QTreeView>
+#include <functional>
+#include <limits>
+#include <math.h>
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "gui_about.hh"
+#include "gui_form.hh"
+#include "gui_formapply.hh"
+#include "gui_htmlattrtext.hh"
+#include "gui_listmodel.hh"
+#include "gui_main.hh"
+#include "gui_menu.hh"
+#include "gui_statusbar.hh"
+#include "looper.hh"
+#include "observers.hh"
+
+namespace {
+
+class SizeHintLayout : public QLayout {
+public:
+ SizeHintLayout(QWidget* parent, QSize hint)
+ : QLayout(parent), hint_(hint), item_(nullptr) {
+ }
+
+ ~SizeHintLayout() override {
+ while (true) {
+ auto l = takeAt(0);
+ if (!l) break;
+ delete l;
+ }
+ }
+
+ void addItem(QLayoutItem *item) override {
+ assert(!item_);
+ item_ = item;
+ }
+
+ int count() const override {
+ return item_ ? 1 : 0;
+ }
+
+ QLayoutItem *itemAt(int index) const override {
+ if (item_ && index == 0) {
+ return item_;
+ }
+ return nullptr;
+ }
+
+ Qt::Orientations expandingDirections() const override {
+ return item_ ? item_->expandingDirections()
+ : QLayout::expandingDirections();
+ }
+
+ QSize minimumSize() const override {
+ return item_ ? item_->minimumSize() : QLayout::minimumSize();
+ }
+
+ bool hasHeightForWidth() const override {
+ return item_ ? item_->hasHeightForWidth() : QLayout::hasHeightForWidth();
+ }
+
+ void setGeometry(const QRect &rect) override {
+ QLayout::setGeometry(rect);
+ if (item_) {
+ item_->setGeometry(rect);
+ }
+ }
+
+ QSize sizeHint() const override {
+ return hint_;
+ }
+
+ QLayoutItem *takeAt(int index) override {
+ if (item_ && index == 0) {
+ auto ret = item_;
+ item_ = nullptr;
+ return ret;
+ }
+ return nullptr;
+ }
+
+private:
+ QSize const hint_;
+ QLayoutItem* item_;
+};
+
+class QtGuiWindow : public GuiWindow {
+public:
+ void set_title(std::string const& title) override {
+ auto w = widget();
+ if (w) {
+ w->setWindowTitle(QString::fromStdString(title));
+ }
+ }
+
+ virtual QWidget* widget() const {
+ return nullptr;
+ }
+
+ virtual bool showWidget() {
+ return false;
+ }
+
+ void* impl() const override {
+ return const_cast<QtGuiWindow*>(this);
+ }
+};
+
+class QtGuiStatusBar : public GuiStatusBar {
+public:
+ QtGuiStatusBar();
+
+ void set_status(std::string const& str) override;
+ void set_override(std::string const& str) override;
+
+ void setup(QStatusBar* statusbar);
+
+private:
+ std::string status_;
+ std::string override_;
+ QStatusBar* statusbar_;
+ QLabel* label_;
+};
+
+class QtChildGuiMenu;
+
+class QtCommonMenu : public GuiMenu {
+public:
+ QtCommonMenu(QtCommonMenu* parent, std::string const& label)
+ : parent_(parent), label_(label) {
+ }
+
+ QtCommonMenu()
+ : parent_(nullptr) {
+ }
+
+ void add_item(std::string const& id, std::string const& label) override {
+ item_.emplace_back(id, label);
+ }
+
+ void add_separator() override {
+ item_.emplace_back("", "");
+ }
+
+ GuiMenu* add_menu(std::string const& label) override;
+
+ bool enable_item(const std::string& id, bool enable) override {
+ if (parent_) return parent_->enable_item(id, enable) || find(id);
+ if (enable) {
+ disabled_.erase(id);
+ } else {
+ disabled_.insert(id);
+ }
+ return find(id);
+ }
+
+ virtual void add_action(std::string const& id, QAction* action) {
+ parent_->add_action(id, action);
+ }
+
+ std::string const& label() const {
+ return label_;
+ }
+
+protected:
+ bool find(const std::string& id) const {
+ for (auto const& pair : item_) {
+ if (pair.first == id) return true;
+ }
+ return false;
+ }
+
+ QtCommonMenu* const parent_;
+ std::string const label_;
+ std::vector<std::pair<std::string, std::string>> item_;
+ std::unordered_set<std::string> disabled_;
+ std::vector<std::unique_ptr<QtChildGuiMenu>> children_;
+};
+
+class QtGuiMenu : public QtCommonMenu {
+public:
+ QtGuiMenu();
+
+ void add_item(std::string const& id, std::string const& label) override;
+ void add_separator() override;
+ GuiMenu* add_menu(std::string const& label) override;
+ bool enable_item(const std::string& id, bool enable) override;
+
+ void add_listener(Listener* listener) override;
+ void remove_listener(Listener* listener) override;
+
+ void add_action(std::string const& id, QAction* action) override;
+
+ void setup(QMenuBar* menubar);
+
+private:
+ void notify_item_activated(std::string const& id);
+
+ Observers<Listener*> observers_;
+ QMenuBar* menubar_;
+ std::unordered_map<std::string, QAction*> action_;
+};
+
+class QGuiListModel : public QAbstractItemModel, GuiListModel::Listener {
+public:
+ QGuiListModel(GuiListModel* model)
+ : model_(model) {
+ model_->add_listener(this);
+ }
+
+ ~QGuiListModel() override {
+ model_->remove_listener(this);
+ }
+
+ void rows_added(GuiListModel*, size_t first, size_t last) override {
+ beginInsertRows(QModelIndex(), first, last);
+ endInsertRows();
+ }
+
+ void rows_changed(GuiListModel*, size_t first, size_t last) override {
+ emit dataChanged(createIndex(first, 0), createIndex(last, model_->columns()));
+ }
+
+ void rows_removed(GuiListModel*, size_t first, size_t last) override {
+ beginRemoveRows(QModelIndex(), first, last);
+ endRemoveRows();
+ }
+
+ GuiListModel* model() const {
+ return model_;
+ }
+
+ bool hasChildren(const QModelIndex& parent) const override {
+ return !parent.isValid();
+ }
+
+ Qt::ItemFlags flags(const QModelIndex& index) const override {
+ auto flags = QAbstractItemModel::flags(index);
+ if (index.isValid()) flags |= Qt::ItemNeverHasChildren;
+ return flags;
+ }
+
+ QModelIndex index(int row, int column, const QModelIndex& parent) const override {
+ if (!parent.isValid()
+ && row >= 0 && static_cast<size_t>(row) < model_->rows()
+ && column >= 0 && static_cast<size_t>(column) < model_->columns()) {
+ return createIndex(row, column);
+ }
+ return QModelIndex();
+ }
+
+ QModelIndex parent(const QModelIndex& UNUSED(index)) const override {
+ return QModelIndex();
+ }
+
+ int rowCount(const QModelIndex& parent) const override {
+ if (parent.isValid()) return 0;
+ return model_->rows();
+ }
+
+ int columnCount(const QModelIndex& parent = QModelIndex()) const override {
+ if (parent.isValid()) return 0;
+ return model_->columns();
+ }
+
+ QVariant data(const QModelIndex& index, int role) const override {
+ if (index.isValid() && role == Qt::DisplayRole
+ && index.row() >= 0 && static_cast<size_t>(index.row()) < model_->rows()
+ && index.column() >= 0 && static_cast<size_t>(index.column()) < model_->columns()) {
+ return QVariant(QString::fromStdString(model_->data(index.row(), index.column())));
+ }
+ return QVariant();
+ }
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override {
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole
+ && section >= 0 && static_cast<size_t>(section) < model_->columns()) {
+ return QVariant(QString::fromStdString(model_->header(section)));
+ }
+ return QVariant();
+ }
+
+private:
+ GuiListModel* const model_;
+};
+
+class QtGuiMain : public GuiMain, public QtGuiWindow {
+public:
+ QtGuiMain(std::string const& title, uint32_t width, uint32_t height)
+ : title_(title), width_(width), height_(height), split_(0.7),
+ menu_(nullptr), statusbar_(nullptr), splitter_(nullptr),
+ top_(nullptr), bottom_(nullptr) {
+ }
+
+ void set_menu(GuiMenu* menu) override {
+ menu_ = static_cast<QtGuiMenu*>(menu);
+ }
+
+ GuiMenu* menu() const override {
+ return menu_;
+ }
+
+ void set_statusbar(GuiStatusBar* statusbar) override {
+ statusbar_ = static_cast<QtGuiStatusBar*>(statusbar);
+ }
+
+ GuiStatusBar* statusbar() const override {
+ return statusbar_;
+ }
+
+ void set_split(double split) override {
+ split_ = std::max(0.0, std::min(split, 1.0));
+ if (splitter_) {
+ QList<int> sizes;
+ auto total = splitter_->size().height();
+ sizes << round(total * split_);
+ sizes << total - sizes.front();
+ splitter_->setSizes(sizes);
+ }
+ }
+
+ double split() const override {
+ if (splitter_) {
+ auto sizes = splitter_->sizes();
+ if (!sizes.empty()) {
+ split_ = static_cast<double>(splitter_->size().height()) / sizes.front();
+ }
+ }
+ return split_;
+ }
+
+ void set_listmodel(GuiListModel* listmodel) override {
+ if (listmodel) {
+ listmodel_.reset(new QGuiListModel(listmodel));
+ } else {
+ listmodel_.reset();
+ }
+ }
+
+ GuiListModel* listmodel() const override {
+ return listmodel_ ? listmodel_->model() : nullptr;
+ }
+
+ void set_package(std::unique_ptr<AttributedText>&& text) override {
+ if (text) {
+ package_ = "<span style=\"font-family: monospace;\">"
+ + static_cast<HtmlAttributedText*>(text.get())->html() + "</span>";
+ } else {
+ package_.clear();
+ }
+ if (bottom_) {
+ bottom_->setHtml(QString::fromStdString(package_));
+ }
+ }
+
+ void add_listener(GuiMain::Listener* listener) override {
+ observers_.insert(listener);
+ }
+
+ void remove_listener(GuiMain::Listener* listener) override {
+ observers_.erase(listener);
+ }
+
+ void set_title(std::string const& title) override {
+ title_ = title;
+ QtGuiWindow::set_title(title);
+ }
+
+ void* impl() const override {
+ return QtGuiWindow::impl();
+ }
+
+ QWidget* widget() const override {
+ return main_.get();
+ }
+
+ bool run(int argc, char** argv) override;
+
+ void show(GuiWindow* window) override {
+ auto wnd = static_cast<QtGuiWindow*>(window->impl());
+ auto widget = wnd->widget();
+ if (!widget) {
+ if (!wnd->showWidget()) {
+ assert(false);
+ }
+ return;
+ }
+ widget->show();
+ }
+
+ bool exit() override {
+ if (!notify_about_to_exit()) return false;
+ QApplication::exit();
+ return true;
+ }
+
+ void add_to_clipboard(std::string const& data,
+ std::string const& mimetype) override {
+ auto clipboard = QApplication::clipboard();
+ if (mimetype.empty() || valid_utf8(data)) {
+ clipboard->setText(QString::fromStdString(data));
+ return;
+ }
+ auto mimedata = new QMimeData();
+ mimedata->setData(QString::fromStdString(mimetype),
+ QByteArray(data.data(), data.size()));
+ clipboard->setMimeData(mimedata);
+ }
+
+private:
+ static bool valid_utf8(std::string const& data) {
+ QTextCodec::ConverterState state;
+ QTextCodec* codec = QTextCodec::codecForName("UTF-8");
+ const QString text = codec->toUnicode(data.data(), data.size(), &state);
+ return state.invalidChars == 0;
+ }
+
+ bool notify_about_to_exit() {
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ if (!it.next()->about_to_exit(this)) return false;
+ }
+ return true;
+ }
+
+ void notify_selected_row(size_t row) {
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ it.next()->selected_row(this, row);
+ }
+ }
+
+ void notify_lost_selection() {
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ it.next()->lost_selection(this);
+ }
+ }
+
+ std::string title_;
+ uint32_t width_;
+ uint32_t height_;
+ mutable double split_;
+ QtGuiMenu* menu_;
+ QtGuiStatusBar* statusbar_;
+ std::unique_ptr<QGuiListModel> listmodel_;
+ std::string package_;
+ Observers<GuiMain::Listener*> observers_;
+ std::unique_ptr<QMainWindow> main_;
+ std::unique_ptr<SizeHintLayout> layout_;
+ std::unique_ptr<QWidget> center_;
+ QSplitter* splitter_;
+ QWidget* top_;
+ QTextEdit* bottom_;
+};
+
+class QtChildGuiMenu : public QtCommonMenu {
+public:
+ QtChildGuiMenu(QtCommonMenu* parent, std::string const& label)
+ : QtCommonMenu(parent, label), menu_(nullptr) {
+ }
+
+ void add_item(std::string const& id, std::string const& label) override {
+ if (!menu_) {
+ QtCommonMenu::add_item(id, label);
+ return;
+ }
+ auto action = menu_->addAction(QString::fromStdString(label));
+ parent_->add_action(id, action);
+ }
+
+ void add_separator() override {
+ if (!menu_) {
+ QtCommonMenu::add_separator();
+ return;
+ }
+ menu_->addSeparator();
+ }
+
+ GuiMenu* add_menu(std::string const& label) override {
+ auto menu = static_cast<QtChildGuiMenu*>(QtCommonMenu::add_menu(label));
+ if (menu_) {
+ menu->setup(menu_->addMenu(QString::fromStdString(menu->label())));
+ }
+ return menu;
+ }
+
+ void add_listener(Listener* listener) override {
+ parent_->add_listener(listener);
+ }
+
+ void remove_listener(Listener* listener) override {
+ parent_->remove_listener(listener);
+ }
+
+ void setup(QMenu* menu) {
+ assert(!menu_);
+ assert(menu);
+ menu_ = menu;
+
+ for (auto const& pair : item_) {
+ if (pair.first.empty() && pair.second.empty()) {
+ menu_->addSeparator();
+ continue;
+ }
+ auto action = menu_->addAction(QString::fromStdString(pair.second));
+ parent_->add_action(pair.first, action);
+ }
+ item_.clear();
+
+ for (auto& child : children_) {
+ child->setup(menu_->addMenu(QString::fromStdString(child->label())));
+ }
+ }
+
+private:
+ QMenu* menu_;
+};
+
+GuiMenu* QtCommonMenu::add_menu(std::string const& label) {
+ auto child = new QtChildGuiMenu(this, label);
+ children_.emplace_back(child);
+ return child;
+}
+
+QtGuiMenu::QtGuiMenu()
+ : QtCommonMenu(), menubar_(nullptr) {
+}
+
+void QtGuiMenu::add_item(std::string const& id, std::string const& label) {
+ if (!menubar_) {
+ QtCommonMenu::add_item(id, label);
+ return;
+ }
+ add_action(id, menubar_->addAction(QString::fromStdString(label)));
+}
+
+void QtGuiMenu::add_separator() {
+ if (!menubar_) {
+ QtCommonMenu::add_separator();
+ return;
+ }
+ menubar_->addSeparator();
+}
+
+GuiMenu* QtGuiMenu::add_menu(std::string const& label) {
+ auto menu = static_cast<QtChildGuiMenu*>(QtCommonMenu::add_menu(label));
+ if (menubar_) {
+ menu->setup(menubar_->addMenu(QString::fromStdString(label)));
+ }
+ return menu;
+}
+
+bool QtGuiMenu::enable_item(const std::string& id, bool enable) {
+ if (!menubar_) {
+ return QtCommonMenu::enable_item(id, enable);
+ }
+ auto pair = action_.find(id);
+ if (pair == action_.end()) return false;
+ pair->second->setEnabled(enable);
+ return true;
+}
+
+void QtGuiMenu::add_listener(Listener* listener) {
+ observers_.insert(listener);
+}
+
+void QtGuiMenu::remove_listener(Listener* listener) {
+ observers_.erase(listener);
+}
+
+void QtGuiMenu::notify_item_activated(std::string const& id) {
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ it.next()->item_activated(id);
+ }
+}
+
+void QtGuiMenu::add_action(std::string const& id, QAction* action) {
+ action_.emplace(id, action);
+ QObject::connect(action, &QAction::triggered,
+ [=](bool) { notify_item_activated(id); });
+}
+
+void QtGuiMenu::setup(QMenuBar* menubar) {
+ assert(menubar);
+ assert(!menubar_);
+ menubar_ = menubar;
+ for (auto const& pair : item_) {
+ auto action = menubar_->addAction(QString::fromStdString(pair.second));
+ add_action(pair.first, action);
+ }
+ item_.clear();
+
+ for (auto& child : children_) {
+ child->setup(menubar_->addMenu(QString::fromStdString(child->label())));
+ }
+
+ for (auto const& id : disabled_) {
+ auto it = action_.find(id);
+ if (it == action_.end()) {
+ assert(false);
+ continue;
+ }
+ it->second->setEnabled(false);
+ }
+ disabled_.clear();
+}
+
+QtGuiStatusBar::QtGuiStatusBar()
+ : statusbar_(nullptr), label_(nullptr) {
+}
+
+void QtGuiStatusBar::setup(QStatusBar* statusbar) {
+ if (!statusbar) {
+ statusbar_ = nullptr;
+ label_ = nullptr;
+ return;
+ }
+ label_ = new QLabel();
+ label_->setText(QString::fromStdString(status_));
+ statusbar_ = statusbar;
+ statusbar_->addWidget(label_, 1);
+ if (override_.empty()) {
+ statusbar_->clearMessage();
+ } else {
+ statusbar_->showMessage(QString::fromStdString(override_));
+ }
+}
+
+void QtGuiStatusBar::set_status(std::string const& str) {
+ status_ = str;
+ if (label_) label_->setText(QString::fromStdString(str));
+}
+
+void QtGuiStatusBar::set_override(std::string const& str) {
+ override_ = str;
+ if (statusbar_) {
+ if (override_.empty()) {
+ statusbar_->clearMessage();
+ } else {
+ statusbar_->showMessage(QString::fromStdString(override_));
+ }
+ }
+}
+
+class QtGuiAbout : public virtual GuiAbout, public QtGuiWindow {
+public:
+ QtGuiAbout(std::string const& title, std::string const& version,
+ std::vector<std::string> const& authors) {
+ title_ = title + " " + version;
+ auto it = authors.begin();
+ while (it != authors.end()) {
+ text_ += *it++;
+ if (!it->empty()) {
+ text_.push_back(' ');
+ text_ += *it;
+ }
+ ++it;
+ text_.push_back('\n');
+ }
+ }
+
+ void add_listener(GuiAbout::Listener* listener) override {
+ observers_.insert(listener);
+ }
+
+ void remove_listener(GuiAbout::Listener* listener) override {
+ observers_.erase(listener);
+ }
+
+ void set_title(std::string const& title) override {
+ title_ = title;
+ }
+
+ void* impl() const override {
+ return QtGuiWindow::impl();
+ }
+
+ bool showWidget() override {
+ QMessageBox::about(QApplication::activeWindow(),
+ QString::fromStdString(title_),
+ QString::fromStdString(text_));
+ return true;
+ }
+
+private:
+ std::string title_;
+ std::string text_;
+ Observers<GuiAbout::Listener*> observers_;
+};
+
+class OptionalCloseDialog : public QDialog {
+public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual bool about_to_close() = 0;
+
+ protected:
+ Delegate() {}
+ };
+
+ OptionalCloseDialog(QWidget* parent, Delegate* delegate)
+ : QDialog(parent), delegate_(delegate) {
+ }
+
+ void done(int result) override {
+ if (result == QDialog::Accepted) {
+ if (!delegate_->about_to_close()) return;
+ }
+ QDialog::done(result);
+ }
+
+private:
+ Delegate* const delegate_;
+};
+
+class QtGuiForm : public virtual GuiForm, public QtGuiWindow,
+ public virtual OptionalCloseDialog::Delegate {
+public:
+ QtGuiForm(std::string const& title, std::string const& text)
+ : title_(title), text_(text), dialog_(nullptr), error_(nullptr),
+ buttons_(nullptr) {
+ }
+
+ ~QtGuiForm() {
+ assert(!dialog_);
+ }
+
+ void add_listener(GuiForm::Listener* listener) override {
+ observers_.insert(listener);
+ }
+
+ void remove_listener(GuiForm::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 dialog_;
+ }
+
+ void* impl() const override {
+ return QtGuiWindow::impl();
+ }
+
+ bool showWidget() override {
+ show(QApplication::activeWindow());
+ return true;
+ }
+
+ bool show(GuiWindow* parent) override {
+ auto wnd = static_cast<QtGuiWindow*>(parent->impl());
+ return show(wnd->widget());
+ }
+
+ void add_string(std::string const& id, std::string const& label,
+ std::string const& value) override {
+ values_.emplace_back(new StringValue(id, label, value));
+ }
+
+ void add_number(std::string const& id, std::string const& label,
+ uint64_t value) override {
+ values_.emplace_back(new NumberValue(id, label, value));
+ }
+
+ std::string get_string(std::string const& id) const override {
+ for (auto const& value : values_) {
+ if (value->id_ == id && value->type_ == STRING) {
+ if (value->edit_) {
+ return value->edit_->text().toStdString();
+ }
+ return static_cast<StringValue*>(value.get())->value_;
+ }
+ }
+ assert(false);
+ return "";
+ }
+
+ uint64_t get_number(std::string const& id) const override {
+ for (auto const& value : values_) {
+ if (value->id_ == id && value->type_ == NUMBER) {
+ if (value->edit_) {
+ uint64_t tmp;
+ if (get_number(value->edit_, &tmp)) {
+ return tmp;
+ }
+ }
+ return static_cast<NumberValue*>(value.get())->value_;
+ }
+ }
+ assert(false);
+ return 0;
+ }
+
+ void set_error(std::string const& error) override {
+ if (!error_) {
+ assert(false);
+ return;
+ }
+ if (error.empty()) {
+ error_->hide();
+ } else {
+ error_->setText(QString::fromStdString(error));
+ error_->show();
+ }
+ }
+
+ bool about_to_close() override {
+ return notify_about_to_close();
+ }
+
+protected:
+ enum Type {
+ STRING,
+ NUMBER,
+ };
+
+ struct Value {
+ Type const type_;
+ std::string const id_;
+ std::string const label_;
+ QLineEdit* edit_;
+
+ Value(Type type, std::string const& id, std::string const& label)
+ : type_(type), id_(id), label_(label), edit_(nullptr) {
+ }
+ };
+
+ struct StringValue : public Value {
+ std::string value_;
+
+ StringValue(std::string const& id, std::string const& label,
+ std::string const& value)
+ : Value(STRING, id, label), value_(value) {
+ }
+ };
+
+ struct NumberValue : public Value {
+ uint64_t value_;
+
+ NumberValue(std::string const& id, std::string const& label,
+ uint64_t value)
+ : Value(NUMBER, id, label), value_(value) {
+ }
+ };
+
+ static bool get_number(QLineEdit* edit, uint64_t* number) {
+ assert(edit && number);
+ auto text = edit->text().toStdString();
+ errno = 0;
+ char* end = nullptr;
+ auto tmp = strtoull(text.c_str(), &end, 10);
+ if (errno && end && !*end) {
+ if (number) *number = tmp;
+ return true;
+ }
+ return false;
+ }
+
+ bool show(QWidget* parent) {
+ dialog_ = new OptionalCloseDialog(parent, this);
+ dialog_->setWindowTitle(QString::fromStdString(title_));
+ auto layout = new QGridLayout();
+ int row = 0;
+ if (!text_.empty()) {
+ auto text = new QLabel(QString::fromStdString(text_));
+ layout->addWidget(text, row++, 0, 1, 2);
+ }
+ for (auto& value : values_) {
+ auto label = new QLabel(QString::fromStdString(value->label_));
+ auto edit = new QLineEdit();
+ value->edit_ = edit;
+ switch (value->type_) {
+ case STRING:
+ edit->setText(QString::fromStdString(
+ static_cast<StringValue*>(value.get())->value_));
+ break;
+ case NUMBER: {
+ char tmp[20];
+ snprintf(tmp, sizeof(tmp), "%llu", static_cast<unsigned long long>(
+ static_cast<NumberValue*>(value.get())->value_));
+ edit->setText(tmp);
+ edit->setValidator(new QIntValidator(
+ 0, std::numeric_limits<int>::max(), edit));
+ }
+ }
+ layout->addWidget(label, row, 0);
+ layout->addWidget(edit, row, 1);
+ ++row;
+ }
+ error_ = new QLabel();
+ layout->addWidget(error_, row++, 0, 1, 2);
+ error_->hide();
+ buttons_ = new QDialogButtonBox(QDialogButtonBox::Ok |
+ QDialogButtonBox::Cancel);
+ dialog_->connect(buttons_, &QDialogButtonBox::accepted,
+ dialog_, &QDialog::accept);
+ dialog_->connect(buttons_, &QDialogButtonBox::rejected,
+ dialog_, &QDialog::reject);
+ layout->addWidget(buttons_, row++, 0, 1, 2);
+ add_extra_widgets(layout, row);
+ dialog_->setLayout(layout);
+ auto ret = dialog_->exec();
+ for (auto& value : values_) {
+ switch (value->type_) {
+ case STRING:
+ static_cast<StringValue*>(value.get())->value_ =
+ value->edit_->text().toStdString();
+ break;
+ case NUMBER:
+ get_number(value->edit_,
+ &static_cast<NumberValue*>(value.get())->value_);
+ }
+ value->edit_ = nullptr;
+ }
+ delete dialog_;
+ reset_extra_widgets();
+ dialog_ = nullptr;
+ error_ = nullptr;
+ buttons_ = nullptr;
+ return ret == QDialog::Accepted;
+ }
+
+ virtual bool notify_about_to_close() {
+ auto it = observers_.notify();
+ while (it.has_next()) {
+ if (!it.next()->about_to_close(this)) return false;
+ }
+ return true;
+ }
+
+ virtual int add_extra_widgets(QGridLayout*, int row) {
+ return row;
+ }
+
+ virtual void reset_extra_widgets() {
+ }
+
+ std::string title_;
+ std::string text_;
+ Observers<GuiForm::Listener*> observers_;
+ std::vector<std::unique_ptr<Value>> values_;
+ QDialog* dialog_;
+ QLabel* error_;
+ QDialogButtonBox* buttons_;
+};
+
+class QtGuiFormApply : public QtGuiForm, public virtual GuiFormApply {
+public:
+ QtGuiFormApply(std::string const& title, std::string const& text,
+ std::string const& apply_button,
+ GuiFormApply::Delegate* delegate)
+ : QtGuiForm(title, text), apply_button_(apply_button),
+ delegate_(delegate), progress_(nullptr), applied_(false) {
+ }
+
+ void applied(bool success) override {
+ assert(progress_);
+ if (progress_) {
+ progress_->hide();
+ }
+ assert(buttons_);
+ if (buttons_) {
+ auto ok = buttons_->button(QDialogButtonBox::Ok);
+ ok->setEnabled(true);
+ }
+ if (success) {
+ applied_ = true;
+ dialog_->done(QDialog::Accepted);
+ }
+ }
+
+private:
+ int add_extra_widgets(QGridLayout* layout, int row) override {
+ assert(buttons_);
+ if (buttons_) {
+ auto ok = buttons_->button(QDialogButtonBox::Ok);
+ ok->setText(QString::fromStdString(apply_button_));
+ }
+ assert(!progress_);
+ progress_ = new QProgressBar();
+ progress_->setMinimum(0);
+ progress_->setMaximum(0);
+ layout->addWidget(progress_, row++, 0, 1, 2);
+ progress_->hide();
+ return row;
+ }
+
+ void reset_extra_widgets() override {
+ progress_ = nullptr;
+ }
+
+ bool notify_about_to_close() override {
+ if (applied_) {
+ applied_ = false;
+ return true;
+ }
+ if (!QtGuiForm::notify_about_to_close()) return false;
+ assert(progress_);
+ if (progress_) {
+ progress_->show();
+ }
+ assert(buttons_);
+ if (buttons_) {
+ auto ok = buttons_->button(QDialogButtonBox::Ok);
+ ok->setEnabled(false);
+ }
+ delegate_->apply(this);
+ return false;
+ }
+
+ std::string apply_button_;
+ GuiFormApply::Delegate* const delegate_;
+ QProgressBar* progress_;
+ bool applied_;
+};
+
+bool QtGuiMain::run(int argc, char** argv) {
+ QApplication app(argc, argv);
+ app.setStyleSheet("QStatusBar::item { border: 0px }");
+ main_.reset(new QMainWindow());
+ center_.reset(new QWidget());
+ layout_.reset(new SizeHintLayout(center_.get(), QSize(width_, height_)));
+ splitter_ = new QSplitter(Qt::Vertical, main_.get());
+ center_->layout()->addWidget(splitter_);
+ if (listmodel_) {
+ auto tree = new QTreeView();
+ tree->setModel(listmodel_.get());
+ tree->setUniformRowHeights(true);
+ tree->setSelectionMode(QAbstractItemView::SingleSelection);
+ QObject::connect(tree->selectionModel(), &QItemSelectionModel::selectionChanged,
+ [=](QItemSelection const& selected, QItemSelection const& UNUSED(previous)) {
+ if (selected.size() == 1) {
+ notify_selected_row(selected.indexes()[0].row());
+ } else {
+ notify_lost_selection();
+ }
+ });
+ tree->header()->setSectionsMovable(false);
+ auto columns = listmodel_->columnCount();
+ if (columns > 0) {
+ tree->header()->setSectionResizeMode(columns - 1, QHeaderView::Stretch);
+ }
+ top_ = tree;
+ } else {
+ top_ = new QWidget();
+ }
+ splitter_->addWidget(top_);
+ bottom_ = new QTextEdit();
+ bottom_->setReadOnly(true);
+ bottom_->setHtml(QString::fromStdString(package_));
+ splitter_->addWidget(bottom_);
+ main_->setCentralWidget(center_.get());
+ if (menu_) {
+ menu_->setup(main_->menuBar());
+ }
+ if (statusbar_) {
+ statusbar_->setup(main_->statusBar());
+ }
+ main_->show();
+ set_split(split_);
+ auto ret = app.exec() == 0;
+ layout_.reset();
+ top_ = nullptr;
+ bottom_ = nullptr;
+ splitter_ = nullptr;
+ center_.reset();
+ main_.reset(); // Make sure QMainWindow destructor is run before
+ // QApplication's destructor
+ return ret;
+}
+
+class QtLooper : public Looper {
+public:
+ QtLooper() {
+ }
+
+ void add(int fd, uint8_t events, FdCallback const& callback) override {
+ auto pair = fds_.emplace(fd, new Fd(fd, callback));
+ auto& handle = pair.first->second;
+ handle->read_->setEnabled(events & EVENT_READ);
+ handle->write_->setEnabled(events & EVENT_WRITE);
+ }
+
+ void modify(int fd, uint8_t events) override {
+ auto it = fds_.find(fd);
+ if (it == fds_.end()) {
+ assert(false);
+ return;
+ }
+ auto& handle = it->second;
+ handle->read_->setEnabled(events & EVENT_READ);
+ handle->write_->setEnabled(events & EVENT_WRITE);
+ }
+
+ void remove(int fd) override {
+ auto it = fds_.find(fd);
+ if (it == fds_.end()) {
+ assert(false);
+ return;
+ }
+ auto& handle = it->second;
+ if (handle->in_callback_) {
+ handle.release()->delayed_delete_ = true;
+ }
+ fds_.erase(fd);
+ }
+
+ void* schedule(float delay_s, ScheduleCallback const& callback) override {
+ auto timer = new QTimer();
+ timer->setSingleShot(true);
+ timers_.insert(timer);
+ QObject::connect(timer, &QTimer::timeout,
+ [=]() {
+ timers_.erase(timer);
+ callback(timer);
+ timer->deleteLater();
+ });
+ timer->start(delay_s * 1000);
+ return timer;
+ }
+
+ void cancel(void* handle) override {
+ auto timer = reinterpret_cast<QTimer*>(handle);
+ if (timers_.erase(timer)) {
+ delete timer;
+ }
+ }
+
+ bool run() override {
+ assert(false);
+ return false;
+ }
+ void quit() override {
+ assert(false);
+ }
+
+ clock::time_point now() const override {
+ return clock::now();
+ }
+
+private:
+ struct Fd {
+ FdCallback callback_;
+ std::unique_ptr<QSocketNotifier> read_;
+ std::unique_ptr<QSocketNotifier> write_;
+ bool in_callback_;
+ bool delayed_delete_;
+
+ Fd(int fd, FdCallback const& fd_callback)
+ : callback_(fd_callback),
+ read_(new QSocketNotifier(fd, QSocketNotifier::Read)),
+ write_(new QSocketNotifier(fd, QSocketNotifier::Write)),
+ in_callback_(false),
+ delayed_delete_(false) {
+ QObject::connect(read_.get(), &QSocketNotifier::activated,
+ [=](int fd) { callback(fd, EVENT_READ); });
+ QObject::connect(write_.get(), &QSocketNotifier::activated,
+ [=](int fd) { callback(fd, EVENT_WRITE); });
+ }
+
+ void callback(int fd, uint8_t events) {
+ assert(!in_callback_);
+ in_callback_ = true;
+ callback_(fd, events);
+ in_callback_ = false;
+ if (delayed_delete_) {
+ read_.release()->deleteLater();
+ write_.release()->deleteLater();
+ delete this;
+ }
+ }
+ };
+
+ std::unordered_map<int, std::unique_ptr<Fd>> fds_;
+ std::unordered_set<QTimer*> timers_;
+};
+
+} // namespace
+
+// static
+GuiMain* GuiMain::create(std::string const& title, uint32_t width,
+ uint32_t height) {
+ return new QtGuiMain(title, width, height);
+}
+
+// static
+GuiMenu* GuiMenu::create() {
+ return new QtGuiMenu();
+}
+
+// static
+GuiStatusBar* GuiStatusBar::create() {
+ return new QtGuiStatusBar();
+}
+
+// static
+GuiAbout* GuiAbout::create(std::string const& title, std::string const& version,
+ const char* author_name, const char* author_email,
+ ...) {
+ std::vector<std::string> authors;
+ authors.push_back(author_name);
+ authors.push_back(author_email);
+ va_list args;
+ va_start(args, author_email);
+ while (true) {
+ auto name = va_arg(args, char const*);
+ if (!name) break;
+ auto email = va_arg(args, char const*);
+ authors.push_back(name);
+ authors.push_back(email);
+ }
+ va_end(args);
+ return new QtGuiAbout(title, version, authors);
+}
+
+// static
+GuiForm* GuiForm::create(std::string const& title,
+ std::string const& text) {
+ return new QtGuiForm(title, text);
+}
+
+// static
+GuiFormApply* GuiFormApply::create(std::string const& title,
+ std::string const& text,
+ std::string const& apply_button,
+ Delegate* delegate) {
+ return new QtGuiFormApply(title, text, apply_button, delegate);
+}
+
+// static
+Looper* GuiMain::createLooper() {
+ return new QtLooper();
+}
+
+// static
+AttributedText* AttributedText::create() {
+ return HtmlAttributedText::create();
+}