Parcourir la source

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.
Andreas Kling il y a 6 ans
Parent
commit
6a0011dcea

+ 59 - 0
DevTools/VisualBuilder/VBPropertiesWindow.cpp

@@ -1,9 +1,55 @@
 #include "VBPropertiesWindow.h"
+#include "VBWidgetPropertyModel.h"
 #include <LibGUI/GBoxLayout.h>
+#include <LibGUI/GComboBox.h>
+#include <LibGUI/GModelEditingDelegate.h>
 #include <LibGUI/GTableView.h>
 #include <LibGUI/GTextBox.h>
 #include <LibGUI/GWidget.h>
 
+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<const GComboBox*>(widget())->text() == "true"; }
+    virtual void set_value(const GVariant& value) override { static_cast<GComboBox*>(widget())->set_text(value.to_string()); }
+    virtual void will_begin_editing() override
+    {
+        auto& combo = *static_cast<GComboBox*>(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<GModelEditingDelegate> {
+        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<BoolModelEditingDelegate>();
+        default:
+            return make<GStringModelEditingDelegate>();
+        }
+    };
 }
 
 VBPropertiesWindow::~VBPropertiesWindow()

+ 12 - 0
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);
         }

+ 1 - 0
DevTools/VisualBuilder/VBWidgetPropertyModel.h

@@ -10,6 +10,7 @@ public:
     enum Column {
         Name = 0,
         Value,
+        Type,
         __Count
     };
 

+ 11 - 4
LibGUI/GAbstractView.cpp

@@ -1,6 +1,7 @@
 #include <Kernel/KeyCode.h>
 #include <LibGUI/GAbstractView.h>
 #include <LibGUI/GModel.h>
+#include <LibGUI/GModelEditingDelegate.h>
 #include <LibGUI/GPainter.h>
 #include <LibGUI/GScrollBar.h>
 #include <LibGUI/GTextBox.h>
@@ -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();
     };
 }

+ 5 - 2
LibGUI/GAbstractView.h

@@ -4,7 +4,7 @@
 #include <LibGUI/GModel.h>
 #include <LibGUI/GScrollableWidget.h>
 
-class GTextBox;
+class GModelEditingDelegate;
 
 class GAbstractView : public GScrollableWidget {
     friend class GModel;
@@ -35,6 +35,8 @@ public:
     Function<void(const GModelIndex&)> on_selection;
     Function<void(const GModelNotification&)> on_model_notification;
 
+    Function<OwnPtr<GModelEditingDelegate>(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<GModel> m_model;
+    OwnPtr<GModelEditingDelegate> m_editing_delegate;
     bool m_activates_on_selection { false };
 };

+ 17 - 1
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<GModel> 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);
+}

+ 5 - 0
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<GModel>);
 
+    bool only_allow_values_from_model() const { return m_only_allow_values_from_model; }
+    void set_only_allow_values_from_model(bool);
+
     Function<void(const String&)> on_change;
     Function<void()> 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 };
 };

+ 60 - 0
LibGUI/GModelEditingDelegate.h

@@ -0,0 +1,60 @@
+#pragma once
+
+#include <LibGUI/GModel.h>
+#include <LibGUI/GTextBox.h>
+#include <LibGUI/GWidget.h>
+
+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<void()> 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<GModel> m_model;
+    GModelIndex m_index;
+    WeakPtr<GWidget> 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<const GTextBox*>(widget())->text(); }
+    virtual void set_value(const GVariant& value) override { static_cast<GTextBox*>(widget())->set_text(value.to_string()); }
+};