mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 22:07:34 +00:00
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.
This commit is contained in:
parent
1d0ada32cc
commit
6a0011dcea
8 changed files with 170 additions and 7 deletions
|
@ -1,9 +1,55 @@
|
||||||
#include "VBPropertiesWindow.h"
|
#include "VBPropertiesWindow.h"
|
||||||
|
#include "VBWidgetPropertyModel.h"
|
||||||
#include <LibGUI/GBoxLayout.h>
|
#include <LibGUI/GBoxLayout.h>
|
||||||
|
#include <LibGUI/GComboBox.h>
|
||||||
|
#include <LibGUI/GModelEditingDelegate.h>
|
||||||
#include <LibGUI/GTableView.h>
|
#include <LibGUI/GTableView.h>
|
||||||
#include <LibGUI/GTextBox.h>
|
#include <LibGUI/GTextBox.h>
|
||||||
#include <LibGUI/GWidget.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()
|
VBPropertiesWindow::VBPropertiesWindow()
|
||||||
{
|
{
|
||||||
set_title("Properties");
|
set_title("Properties");
|
||||||
|
@ -18,6 +64,19 @@ VBPropertiesWindow::VBPropertiesWindow()
|
||||||
m_table_view = new GTableView(widget);
|
m_table_view = new GTableView(widget);
|
||||||
m_table_view->set_headers_visible(false);
|
m_table_view->set_headers_visible(false);
|
||||||
m_table_view->set_editable(true);
|
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()
|
VBPropertiesWindow::~VBPropertiesWindow()
|
||||||
|
|
|
@ -24,6 +24,8 @@ String VBWidgetPropertyModel::column_name(int column) const
|
||||||
return "Name";
|
return "Name";
|
||||||
case Column::Value:
|
case Column::Value:
|
||||||
return "Value";
|
return "Value";
|
||||||
|
case Column::Type:
|
||||||
|
return "Type";
|
||||||
default:
|
default:
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
@ -39,6 +41,12 @@ GModel::ColumnMetadata VBWidgetPropertyModel::column_metadata(int column) const
|
||||||
|
|
||||||
GVariant VBWidgetPropertyModel::data(const GModelIndex& index, Role role) 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) {
|
if (role == Role::Display) {
|
||||||
auto& property = *m_widget.m_properties[index.row()];
|
auto& property = *m_widget.m_properties[index.row()];
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
|
@ -46,6 +54,8 @@ GVariant VBWidgetPropertyModel::data(const GModelIndex& index, Role role) const
|
||||||
return property.name();
|
return property.name();
|
||||||
case Column::Value:
|
case Column::Value:
|
||||||
return property.value();
|
return property.value();
|
||||||
|
case Column::Type:
|
||||||
|
return to_string(property.value().type());
|
||||||
}
|
}
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
@ -54,6 +64,8 @@ GVariant VBWidgetPropertyModel::data(const GModelIndex& index, Role role) const
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case Column::Name:
|
case Column::Name:
|
||||||
return Color::Black;
|
return Color::Black;
|
||||||
|
case Column::Type:
|
||||||
|
return Color::Blue;
|
||||||
case Column::Value:
|
case Column::Value:
|
||||||
return property.is_readonly() ? Color(Color::MidGray) : Color(Color::Black);
|
return property.is_readonly() ? Color(Color::MidGray) : Color(Color::Black);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ public:
|
||||||
enum Column {
|
enum Column {
|
||||||
Name = 0,
|
Name = 0,
|
||||||
Value,
|
Value,
|
||||||
|
Type,
|
||||||
__Count
|
__Count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include <Kernel/KeyCode.h>
|
#include <Kernel/KeyCode.h>
|
||||||
#include <LibGUI/GAbstractView.h>
|
#include <LibGUI/GAbstractView.h>
|
||||||
#include <LibGUI/GModel.h>
|
#include <LibGUI/GModel.h>
|
||||||
|
#include <LibGUI/GModelEditingDelegate.h>
|
||||||
#include <LibGUI/GPainter.h>
|
#include <LibGUI/GPainter.h>
|
||||||
#include <LibGUI/GScrollBar.h>
|
#include <LibGUI/GScrollBar.h>
|
||||||
#include <LibGUI/GTextBox.h>
|
#include <LibGUI/GTextBox.h>
|
||||||
|
@ -71,15 +72,21 @@ void GAbstractView::begin_editing(const GModelIndex& index)
|
||||||
if (m_edit_widget)
|
if (m_edit_widget)
|
||||||
delete m_edit_widget;
|
delete m_edit_widget;
|
||||||
m_edit_index = index;
|
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->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());
|
m_edit_widget_content_rect = content_rect(index).translated(frame_thickness(), frame_thickness());
|
||||||
update_edit_widget_position();
|
update_edit_widget_position();
|
||||||
m_edit_widget->set_focus(true);
|
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());
|
ASSERT(model());
|
||||||
model()->set_data(m_edit_index, m_edit_widget->text());
|
model()->set_data(m_edit_index, m_editing_delegate->value());
|
||||||
stop_editing();
|
stop_editing();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <LibGUI/GModel.h>
|
#include <LibGUI/GModel.h>
|
||||||
#include <LibGUI/GScrollableWidget.h>
|
#include <LibGUI/GScrollableWidget.h>
|
||||||
|
|
||||||
class GTextBox;
|
class GModelEditingDelegate;
|
||||||
|
|
||||||
class GAbstractView : public GScrollableWidget {
|
class GAbstractView : public GScrollableWidget {
|
||||||
friend class GModel;
|
friend class GModel;
|
||||||
|
@ -35,6 +35,8 @@ public:
|
||||||
Function<void(const GModelIndex&)> on_selection;
|
Function<void(const GModelIndex&)> on_selection;
|
||||||
Function<void(const GModelNotification&)> on_model_notification;
|
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"; }
|
virtual const char* class_name() const override { return "GAbstractView"; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -45,10 +47,11 @@ protected:
|
||||||
|
|
||||||
bool m_editable { false };
|
bool m_editable { false };
|
||||||
GModelIndex m_edit_index;
|
GModelIndex m_edit_index;
|
||||||
GTextBox* m_edit_widget { nullptr };
|
GWidget* m_edit_widget { nullptr };
|
||||||
Rect m_edit_widget_content_rect;
|
Rect m_edit_widget_content_rect;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RefPtr<GModel> m_model;
|
RefPtr<GModel> m_model;
|
||||||
|
OwnPtr<GModelEditingDelegate> m_editing_delegate;
|
||||||
bool m_activates_on_selection { false };
|
bool m_activates_on_selection { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,11 @@ GComboBox::GComboBox(GWidget* parent)
|
||||||
auto new_value = model()->data(index).to_string();
|
auto new_value = model()->data(index).to_string();
|
||||||
m_editor->set_text(new_value);
|
m_editor->set_text(new_value);
|
||||||
m_editor->select_all();
|
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));
|
m_list_view->set_model(move(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GComboBox::select_all()
|
||||||
|
{
|
||||||
|
m_editor->select_all();
|
||||||
|
}
|
||||||
|
|
||||||
void GComboBox::open()
|
void GComboBox::open()
|
||||||
{
|
{
|
||||||
if (!model())
|
if (!model())
|
||||||
|
@ -100,3 +109,10 @@ void GComboBox::set_text(const String& text)
|
||||||
{
|
{
|
||||||
m_editor->set_text(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);
|
||||||
|
}
|
||||||
|
|
|
@ -16,11 +16,15 @@ public:
|
||||||
|
|
||||||
void open();
|
void open();
|
||||||
void close();
|
void close();
|
||||||
|
void select_all();
|
||||||
|
|
||||||
GModel* model() { return m_list_view->model(); }
|
GModel* model() { return m_list_view->model(); }
|
||||||
const GModel* model() const { return m_list_view->model(); }
|
const GModel* model() const { return m_list_view->model(); }
|
||||||
void set_model(NonnullRefPtr<GModel>);
|
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(const String&)> on_change;
|
||||||
Function<void()> on_return_pressed;
|
Function<void()> on_return_pressed;
|
||||||
|
|
||||||
|
@ -34,4 +38,5 @@ private:
|
||||||
GButton* m_open_button { nullptr };
|
GButton* m_open_button { nullptr };
|
||||||
GWindow* m_list_window { nullptr };
|
GWindow* m_list_window { nullptr };
|
||||||
GListView* m_list_view { nullptr };
|
GListView* m_list_view { nullptr };
|
||||||
|
bool m_only_allow_values_from_model { false };
|
||||||
};
|
};
|
||||||
|
|
60
LibGUI/GModelEditingDelegate.h
Normal file
60
LibGUI/GModelEditingDelegate.h
Normal file
|
@ -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()); }
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue