From 6a0011dcea26bc687a6c114e62ec1496807d1115 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 23 Jun 2019 08:18:28 +0200 Subject: [PATCH] LibGUI+VisualBuilder: Support custom editing widgets for property values. Implemented this by letting GAbstractViews provide a GModelEditingDelegate for a given index, which then knows how to create and setup a custom widget appropriate for the data type being edited. --- DevTools/VisualBuilder/VBPropertiesWindow.cpp | 59 ++++++++++++++++++ .../VisualBuilder/VBWidgetPropertyModel.cpp | 12 ++++ .../VisualBuilder/VBWidgetPropertyModel.h | 1 + LibGUI/GAbstractView.cpp | 15 +++-- LibGUI/GAbstractView.h | 7 ++- LibGUI/GComboBox.cpp | 18 +++++- LibGUI/GComboBox.h | 5 ++ LibGUI/GModelEditingDelegate.h | 60 +++++++++++++++++++ 8 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 LibGUI/GModelEditingDelegate.h diff --git a/DevTools/VisualBuilder/VBPropertiesWindow.cpp b/DevTools/VisualBuilder/VBPropertiesWindow.cpp index 146389a19e..c61cd56e89 100644 --- a/DevTools/VisualBuilder/VBPropertiesWindow.cpp +++ b/DevTools/VisualBuilder/VBPropertiesWindow.cpp @@ -1,9 +1,55 @@ #include "VBPropertiesWindow.h" +#include "VBWidgetPropertyModel.h" #include +#include +#include #include #include #include +class BoolValuesModel final : public GModel { +public: + virtual int row_count(const GModelIndex&) const override { return 2; } + virtual int column_count(const GModelIndex&) const override { return 1; } + virtual void update() override {} + virtual GVariant data(const GModelIndex& index, Role role) const override + { + if (role != Role::Display) + return {}; + switch (index.row()) { + case 0: + return "false"; + case 1: + return "true"; + } + ASSERT_NOT_REACHED(); + } +}; + +class BoolModelEditingDelegate : public GModelEditingDelegate { +public: + BoolModelEditingDelegate() {} + virtual ~BoolModelEditingDelegate() override {} + + virtual GWidget* create_widget() override + { + auto* combo = new GComboBox(nullptr); + combo->set_only_allow_values_from_model(true); + combo->set_model(adopt(*new BoolValuesModel)); + combo->on_return_pressed = [this] { commit(); }; + combo->on_change = [this](auto&) { commit(); }; + return combo; + } + virtual GVariant value() const override { return static_cast(widget())->text() == "true"; } + virtual void set_value(const GVariant& value) override { static_cast(widget())->set_text(value.to_string()); } + virtual void will_begin_editing() override + { + auto& combo = *static_cast(widget()); + combo.select_all(); + combo.open(); + } +}; + VBPropertiesWindow::VBPropertiesWindow() { set_title("Properties"); @@ -18,6 +64,19 @@ VBPropertiesWindow::VBPropertiesWindow() m_table_view = new GTableView(widget); m_table_view->set_headers_visible(false); m_table_view->set_editable(true); + + m_table_view->aid_create_editing_delegate = [this](auto& index) -> OwnPtr { + if (!m_table_view->model()) + return nullptr; + auto type_index = m_table_view->model()->index(index.row(), VBWidgetPropertyModel::Column::Type); + auto type = m_table_view->model()->data(type_index, GModel::Role::Custom).to_int(); + switch ((GVariant::Type)type) { + case GVariant::Type::Bool: + return make(); + default: + return make(); + } + }; } VBPropertiesWindow::~VBPropertiesWindow() diff --git a/DevTools/VisualBuilder/VBWidgetPropertyModel.cpp b/DevTools/VisualBuilder/VBWidgetPropertyModel.cpp index 5bee6870cb..eaed1184e3 100644 --- a/DevTools/VisualBuilder/VBWidgetPropertyModel.cpp +++ b/DevTools/VisualBuilder/VBWidgetPropertyModel.cpp @@ -24,6 +24,8 @@ String VBWidgetPropertyModel::column_name(int column) const return "Name"; case Column::Value: return "Value"; + case Column::Type: + return "Type"; default: ASSERT_NOT_REACHED(); } @@ -39,6 +41,12 @@ GModel::ColumnMetadata VBWidgetPropertyModel::column_metadata(int column) const GVariant VBWidgetPropertyModel::data(const GModelIndex& index, Role role) const { + if (role == Role::Custom) { + auto& property = *m_widget.m_properties[index.row()]; + if (index.column() == Column::Type) + return (int)property.value().type(); + return {}; + } if (role == Role::Display) { auto& property = *m_widget.m_properties[index.row()]; switch (index.column()) { @@ -46,6 +54,8 @@ GVariant VBWidgetPropertyModel::data(const GModelIndex& index, Role role) const return property.name(); case Column::Value: return property.value(); + case Column::Type: + return to_string(property.value().type()); } ASSERT_NOT_REACHED(); } @@ -54,6 +64,8 @@ GVariant VBWidgetPropertyModel::data(const GModelIndex& index, Role role) const switch (index.column()) { case Column::Name: return Color::Black; + case Column::Type: + return Color::Blue; case Column::Value: return property.is_readonly() ? Color(Color::MidGray) : Color(Color::Black); } diff --git a/DevTools/VisualBuilder/VBWidgetPropertyModel.h b/DevTools/VisualBuilder/VBWidgetPropertyModel.h index 0653e818ae..8d5cf17eae 100644 --- a/DevTools/VisualBuilder/VBWidgetPropertyModel.h +++ b/DevTools/VisualBuilder/VBWidgetPropertyModel.h @@ -10,6 +10,7 @@ public: enum Column { Name = 0, Value, + Type, __Count }; diff --git a/LibGUI/GAbstractView.cpp b/LibGUI/GAbstractView.cpp index c0f72ceccc..a7e560cb0b 100644 --- a/LibGUI/GAbstractView.cpp +++ b/LibGUI/GAbstractView.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -71,15 +72,21 @@ void GAbstractView::begin_editing(const GModelIndex& index) if (m_edit_widget) delete m_edit_widget; m_edit_index = index; - m_edit_widget = new GTextBox(this); + + ASSERT(aid_create_editing_delegate); + m_editing_delegate = aid_create_editing_delegate(index); + m_editing_delegate->bind(*model(), index); + m_editing_delegate->set_value(model()->data(index, GModel::Role::Display)); + m_edit_widget = m_editing_delegate->widget(); + add_child(*m_edit_widget); m_edit_widget->move_to_back(); - m_edit_widget->set_text(model()->data(index, GModel::Role::Display).to_string()); m_edit_widget_content_rect = content_rect(index).translated(frame_thickness(), frame_thickness()); update_edit_widget_position(); m_edit_widget->set_focus(true); - m_edit_widget->on_return_pressed = [this] { + m_editing_delegate->will_begin_editing(); + m_editing_delegate->on_commit = [this] { ASSERT(model()); - model()->set_data(m_edit_index, m_edit_widget->text()); + model()->set_data(m_edit_index, m_editing_delegate->value()); stop_editing(); }; } diff --git a/LibGUI/GAbstractView.h b/LibGUI/GAbstractView.h index f57a65c919..40bd04593a 100644 --- a/LibGUI/GAbstractView.h +++ b/LibGUI/GAbstractView.h @@ -4,7 +4,7 @@ #include #include -class GTextBox; +class GModelEditingDelegate; class GAbstractView : public GScrollableWidget { friend class GModel; @@ -35,6 +35,8 @@ public: Function on_selection; Function on_model_notification; + Function(const GModelIndex&)> aid_create_editing_delegate; + virtual const char* class_name() const override { return "GAbstractView"; } protected: @@ -45,10 +47,11 @@ protected: bool m_editable { false }; GModelIndex m_edit_index; - GTextBox* m_edit_widget { nullptr }; + GWidget* m_edit_widget { nullptr }; Rect m_edit_widget_content_rect; private: RefPtr m_model; + OwnPtr m_editing_delegate; bool m_activates_on_selection { false }; }; diff --git a/LibGUI/GComboBox.cpp b/LibGUI/GComboBox.cpp index 159bfebd40..5bbb07de3f 100644 --- a/LibGUI/GComboBox.cpp +++ b/LibGUI/GComboBox.cpp @@ -41,7 +41,11 @@ GComboBox::GComboBox(GWidget* parent) auto new_value = model()->data(index).to_string(); m_editor->set_text(new_value); m_editor->select_all(); - m_list_window->hide(); + close(); + deferred_invoke([this](auto&) { + if (on_change) + on_change(m_editor->text()); + }); }; } @@ -63,6 +67,11 @@ void GComboBox::set_model(NonnullRefPtr model) m_list_view->set_model(move(model)); } +void GComboBox::select_all() +{ + m_editor->select_all(); +} + void GComboBox::open() { if (!model()) @@ -100,3 +109,10 @@ void GComboBox::set_text(const String& text) { m_editor->set_text(text); } + +void GComboBox::set_only_allow_values_from_model(bool b) +{ + if (m_only_allow_values_from_model == b) + return; + m_editor->set_readonly(m_only_allow_values_from_model); +} diff --git a/LibGUI/GComboBox.h b/LibGUI/GComboBox.h index d892f1cd57..8747c2ec1b 100644 --- a/LibGUI/GComboBox.h +++ b/LibGUI/GComboBox.h @@ -16,11 +16,15 @@ public: void open(); void close(); + void select_all(); GModel* model() { return m_list_view->model(); } const GModel* model() const { return m_list_view->model(); } void set_model(NonnullRefPtr); + bool only_allow_values_from_model() const { return m_only_allow_values_from_model; } + void set_only_allow_values_from_model(bool); + Function on_change; Function on_return_pressed; @@ -34,4 +38,5 @@ private: GButton* m_open_button { nullptr }; GWindow* m_list_window { nullptr }; GListView* m_list_view { nullptr }; + bool m_only_allow_values_from_model { false }; }; diff --git a/LibGUI/GModelEditingDelegate.h b/LibGUI/GModelEditingDelegate.h new file mode 100644 index 0000000000..5d1cb6194c --- /dev/null +++ b/LibGUI/GModelEditingDelegate.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +class GModelEditingDelegate { +public: + GModelEditingDelegate() {} + virtual ~GModelEditingDelegate() {} + + void bind(GModel& model, const GModelIndex& index) + { + if (m_model.ptr() == &model && m_index == index) + return; + m_model = model; + m_index = index; + m_widget = create_widget()->make_weak_ptr(); + } + + GWidget* widget() { return m_widget; } + const GWidget* widget() const { return m_widget; } + + Function on_commit; + + virtual GVariant value() const = 0; + virtual void set_value(const GVariant&) = 0; + + virtual void will_begin_editing() { } + +protected: + virtual GWidget* create_widget() = 0; + void commit() + { + if (on_commit) + on_commit(); + } + +private: + RefPtr m_model; + GModelIndex m_index; + WeakPtr m_widget; +}; + +class GStringModelEditingDelegate : public GModelEditingDelegate { +public: + GStringModelEditingDelegate() {} + virtual ~GStringModelEditingDelegate() override {} + + virtual GWidget* create_widget() override + { + auto* textbox = new GTextBox(nullptr); + textbox->on_return_pressed = [this] { + commit(); + }; + return textbox; + } + virtual GVariant value() const override { return static_cast(widget())->text(); } + virtual void set_value(const GVariant& value) override { static_cast(widget())->set_text(value.to_string()); } +};