From cb17c3035bbd80bd8ea6718bae4c57cfb2555653 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Thu, 15 Jun 2017 23:20:00 +0200 Subject: Initial monitor GUI Basic monitor functionality, GTK-3.0 and QT5 backends --- src/gui_qt.cc | 1277 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1277 insertions(+) create mode 100644 src/gui_qt.cc (limited to 'src/gui_qt.cc') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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> item_; + std::unordered_set disabled_; + std::vector> 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 observers_; + QMenuBar* menubar_; + std::unordered_map 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(row) < model_->rows() + && column >= 0 && static_cast(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(index.row()) < model_->rows() + && index.column() >= 0 && static_cast(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(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(menu); + } + + GuiMenu* menu() const override { + return menu_; + } + + void set_statusbar(GuiStatusBar* statusbar) override { + statusbar_ = static_cast(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 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(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&& text) override { + if (text) { + package_ = "" + + static_cast(text.get())->html() + ""; + } 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(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 listmodel_; + std::string package_; + Observers observers_; + std::unique_ptr main_; + std::unique_ptr layout_; + std::unique_ptr 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(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(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 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 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(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(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(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(value.get())->value_)); + break; + case NUMBER: { + char tmp[20]; + snprintf(tmp, sizeof(tmp), "%llu", static_cast( + static_cast(value.get())->value_)); + edit->setText(tmp); + edit->setValidator(new QIntValidator( + 0, std::numeric_limits::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(value.get())->value_ = + value->edit_->text().toStdString(); + break; + case NUMBER: + get_number(value->edit_, + &static_cast(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 observers_; + std::vector> 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(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 read_; + std::unique_ptr 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> fds_; + std::unordered_set 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 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(); +} -- cgit v1.2.3-70-g09d2