4 #include "propertygrid.h" 6 #include "../core/history.h" 7 #include "../core/log.h" 8 #include "../core/serialization.h" 9 #include "../core/workspace.h" 12 static constexpr
int PropertyRole = Qt::UserRole + 1;
13 static Property itemProperty(
const QModelIndex &index) {
14 return index.data(PropertyRole).value<
Property>();
16 static Property itemProperty(
const QTreeWidgetItem *item) {
17 return item->data(1, PropertyRole).value<
Property>();
20 PropertyGridWidget::PropertyGridWidget() : QDockWidget(
"Properties") {
24 QPoint drag_start_pos;
25 double drag_start_value = 0.0;
26 bool aggregate =
false;
30 : property_grid(property_grid) {
31 setParent(property_grid);
36 virtual void mousePressEvent(QMouseEvent *event)
override {
37 QTreeWidget::mousePressEvent(event);
38 if (property_grid->drag_property) {
40 drag_start_pos =
event->pos();
41 auto object = property_grid->object_ptr.lock();
43 LOG_DEBUG(
"object is null");
46 auto &t = property_grid->drag_property->typeId();
47 std::string value_string;
48 property_grid->drag_property->tryToString(value_string);
49 drag_start_value = std::stod(value_string);
53 virtual void mouseMoveEvent(QMouseEvent *event)
override {
54 if (property_grid->drag_property) {
56 auto object = property_grid->object_ptr.lock();
58 LOG_DEBUG(
"object is null");
61 auto &t = property_grid->drag_property->typeId();
63 if (std::isfinite(property_grid->drag_property->attributes()->min) &&
64 std::isfinite(property_grid->drag_property->attributes()->max)) {
65 value = drag_start_value + (
event->x() -
event->y() -
66 drag_start_pos.x() + drag_start_pos.y()) *
70 double step_size = 0.005;
71 if (t !=
typeid(
float) && t !=
typeid(
double)) {
76 property_grid->drag_property->attributes()->step_scale)) {
77 step_size *= property_grid->drag_property->attributes()->step_scale;
79 int delta = (
event->x() -
event->y() - drag_start_pos.x() +
82 value = drag_start_value + delta * step_size;
84 if (property_grid->drag_property->attributes()->wrap) {
85 value -= property_grid->drag_property->attributes()->min;
86 value /= property_grid->drag_property->attributes()->max -
87 property_grid->drag_property->attributes()->min;
88 value -= std::floor(value);
89 value *= property_grid->drag_property->attributes()->max -
90 property_grid->drag_property->attributes()->min;
91 value += property_grid->drag_property->attributes()->min;
93 if (value < property_grid->drag_property->attributes()->min) {
94 value = property_grid->drag_property->attributes()->min;
96 if (value > property_grid->drag_property->attributes()->max) {
97 value = property_grid->drag_property->attributes()->max;
100 LOG_INFO(
"drag " << value <<
" " 101 << property_grid->drag_property->attributes()->min
103 << property_grid->drag_property->attributes()->max);
105 property_grid->drag_property->displayName(),
108 property_grid->drag_property->fromStringOrThrow(std::to_string(value));
111 QTreeWidget::mouseMoveEvent(event);
114 virtual void mouseReleaseEvent(QMouseEvent *event)
override {
118 property_grid->drag_property =
nullptr;
119 property_grid->drag_item =
nullptr;
120 QTreeWidget::mouseReleaseEvent(event);
123 view =
new PropertyTreeWidget(
this);
125 ws->modified.connect(
this, [
this]() { sync(); });
126 view->setColumnCount(2);
127 view->setEditTriggers(QAbstractItemView::DoubleClicked |
128 QAbstractItemView::EditKeyPressed |
129 QAbstractItemView::AnyKeyPressed);
130 view->setSelectionMode(QAbstractItemView::SingleSelection);
131 view->setHeaderLabels({
"Name",
"Value"});
133 view->header()->setSectionResizeMode(1, QHeaderView::Stretch);
134 view->setIndentation(10);
136 class ItemDelegateBase :
public QStyledItemDelegate {
141 : QStyledItemDelegate(parent), _parent(parent) {}
142 void drawLines(QPainter *painter,
const QStyleOptionViewItem &option,
143 const QModelIndex &index)
const {
144 painter->setPen(QPen(QApplication::palette().brush(QPalette::Window), 0));
145 painter->setBrush(QBrush(Qt::transparent));
147 auto rect = option.rect;
148 rect.setX(_parent->view->columnViewportPosition(index.column()));
149 rect.setWidth(_parent->view->columnWidth(index.column()));
150 if (index.column() == 0) {
151 rect.setX(rect.x() - 1);
152 rect.setWidth(rect.width() + 1);
154 painter->drawRect(rect);
156 void paint(QPainter *painter,
const QStyleOptionViewItem &option,
157 const QModelIndex &index)
const {
158 QStyledItemDelegate::paint(painter, option, index);
159 drawLines(painter, option, index);
163 class EditDelegate :
public ItemDelegateBase {
168 : ItemDelegateBase(property_grid), property_grid(property_grid) {}
169 virtual QWidget *createEditor(QWidget *parent,
170 const QStyleOptionViewItem &option,
171 const QModelIndex &index)
const override {
176 view->setItemDelegateForColumn(0,
new EditDelegate(
this));
179 connect(view, &QTreeWidget::currentItemChanged, view, [
this]() {
180 if (view->currentColumn() != 1) {
181 view->setCurrentItem(view->currentItem(), 1);
185 class EditDelegate :
public ItemDelegateBase {
190 : ItemDelegateBase(property_grid), property_grid(property_grid) {}
191 virtual void paint(QPainter *painter,
const QStyleOptionViewItem &option,
192 const QModelIndex &index)
const override {
194 drawLines(painter, option, index);
196 auto rect = option.rect.marginsRemoved(QMargins(1, 1, 0, 0));
199 auto color = ((option.state & QStyle::State_Active) &&
200 (option.state & QStyle::State_Selected))
201 ? option.palette.color(QPalette::Highlight)
202 : option.palette.color(QPalette::Base);
203 painter->fillRect(rect, QBrush(color));
206 if (
auto object = property_grid->object_ptr.lock()) {
207 Property property = itemProperty(index);
208 if (std::isfinite(property.attributes()->min) &&
209 std::isfinite(property.attributes()->max)) {
210 double v = index.data(Qt::DisplayRole).toDouble();
211 rect.setWidth(std::round(
213 ((v -
property.attributes()->min) /
214 (property.attributes()->max -
property.attributes()->min))));
216 auto color = option.palette.color(QPalette::Text);
217 color.setAlphaF(0.1);
218 if ((option.state & QStyle::State_Active) &&
219 (option.state & QStyle::State_Selected)) {
220 color.setAlphaF(1.0 / 3.0);
222 painter->fillRect(rect, QBrush(color));
227 QStyleOptionViewItem opt = option;
228 initStyleOption(&opt, index);
229 opt.backgroundBrush = QBrush(Qt::transparent);
230 opt.state &= ~QStyle::State_Selected;
231 if ((option.state & QStyle::State_Active) &&
232 (option.state & QStyle::State_Selected)) {
233 opt.palette.setBrush(QPalette::Text, opt.palette.highlightedText());
236 (option.widget ? option.widget->style() : QApplication::style());
237 style->drawControl(QStyle::CE_ItemViewItem, &opt, painter,
240 virtual bool editorEvent(QEvent *event, QAbstractItemModel *model,
241 const QStyleOptionViewItem &option,
242 const QModelIndex &index)
override {
245 QStyledItemDelegate::editorEvent(event, model, option, index);
246 if (
auto *item = property_grid->view->itemFromIndex(index)) {
247 if (event->type() == QEvent::MouseButtonPress) {
248 if (
auto *mouse_event = dynamic_cast<QMouseEvent *>(event)) {
249 if (mouse_event->button() == Qt::LeftButton) {
250 auto current_property = itemProperty(index);
251 if (property_grid->isPropertyDraggable(item)) {
252 property_grid->drag_property =
253 std::make_shared<Property>(current_property);
254 property_grid->drag_item = item;
255 property_grid->view->setCursor(Qt::ClosedHandCursor);
257 if (current_property.typeId() ==
typeid(bool)) {
259 bool v = current_property.read().value<
bool>();
261 current_property.assign(
Variant(v));
270 virtual QWidget *createEditor(QWidget *parent,
271 const QStyleOptionViewItem &option,
272 const QModelIndex &index)
const override {
274 auto *item = property_grid->view->itemFromIndex(index);
276 LOG_ERROR(
"tree item not found");
279 auto current_property = itemProperty(index);
280 if (current_property.typeId() ==
typeid(bool)) {
283 bool v = current_property.read().value<
bool>();
285 current_property.assign(
Variant(v));
289 if (current_property.fromStringSupported()) {
290 LOG_DEBUG(
"editable");
291 if (current_property.attributes()->list) {
292 LOG_DEBUG(
"combo box");
293 auto *editor =
new QComboBox(parent);
294 editor->setEditable(
true);
295 editor->setFrame(
false);
296 editor->setStyleSheet(
"border: none;");
298 current_property.attributes()->list(current_property)) {
299 editor->addItem(v.c_str());
303 static_cast<void (QComboBox::*)(
int)
>(&QComboBox::activated),
304 [editor, index,
this](
int i) {
305 setModelData(editor, property_grid->view->model(), index);
309 LOG_DEBUG(
"line edit");
311 auto editor =
new QLineEdit(parent);
312 editor->setFrame(
false);
313 if (
auto complete = current_property.attributes()->complete) {
314 auto *completion_model =
new QStringListModel(editor);
315 auto *completer =
new QCompleter(completion_model, editor);
316 completer->setCaseSensitivity(Qt::CaseInsensitive);
317 completer->setCompletionMode(
318 QCompleter::UnfilteredPopupCompletion);
319 completer->setMaxVisibleItems(20);
320 completer->setWidget(editor);
321 auto update = [current_property, completion_model, complete,
322 editor, completer](
bool show) {
323 QString text = editor->text();
328 complete(current_property, text.toStdString(), completion);
329 for (
auto &s : completion.items) {
330 QString qs = QString(s.c_str());
338 if (s.startsWith(text) && s.size() > text.size()) {
347 completion_model->setStringList(l);
348 completer->complete();
349 if (!completion.completed || show) {
350 LOG_DEBUG(
"show auto completion list");
351 completer->popup()->show();
353 LOG_DEBUG(
"hide auto completion list");
354 completer->popup()->hide();
357 LOG_DEBUG(
"not auto completions, hide list");
358 completer->popup()->hide();
361 connect(editor, &QLineEdit::textEdited, editor,
362 [update, completer](
const QString &) { update(
false); });
364 static_cast<void (QCompleter::*)(
const QString &text)
>(
365 &QCompleter::activated),
366 [editor, update, completer](
const QString &text) {
367 if (text != editor->text()) {
368 editor->setText(text);
372 QTimer::singleShot(0, completer,
373 [completer, update]() { update(
true); });
379 LOG_DEBUG(
"not editable");
383 virtual void setModelData(QWidget *editor, QAbstractItemModel *model,
384 const QModelIndex &index)
const override {
387 auto *item = property_grid->view->itemFromIndex(index);
389 LOG_ERROR(
"tree item not found");
392 std::shared_ptr<Object>
object = property_grid->object_ptr.lock();
393 auto property = itemProperty(index);
395 editor->property(editor->metaObject()->userProperty().name())
400 property.tryToString(v);
402 LOG_DEBUG(
"property unchanged");
408 std::string(
"Change ") +
409 property.displayName()
412 property.fromStringOrThrow(value);
413 LOG_DEBUG(
"property changed");
414 }
catch (std::exception &ex) {
415 LOG_ERROR(
"failed to parse value string");
420 property.tryToString(v);
421 model->setData(index, v.c_str(), 0);
426 view->setItemDelegateForColumn(1,
new EditDelegate(
this));
430 bool PropertyGridWidget::isPropertyDraggable(QTreeWidgetItem *item) {
433 if (
auto object = object_ptr.lock()) {
434 if (!item->data(1, PropertyRole).isNull()) {
435 auto current_property = itemProperty(item);
436 std::type_index t = current_property.typeId();
437 return t ==
typeid(double) || t ==
typeid(
float) ||
438 t ==
typeid(short) || t ==
typeid(
int) || t ==
typeid(long) ||
439 t ==
typeid(
long long) || t ==
typeid(
unsigned short) ||
440 t ==
typeid(
unsigned int) || t ==
typeid(
unsigned long) ||
441 t ==
typeid(
unsigned long long);
448 void PropertyGridWidget::sync(QTreeWidgetItem *parent_item,
449 std::vector<Property> properties) {
452 for (
auto &property : properties) {
453 if (property.attributes()->hidden) {
457 bool ok =
property.tryToString(s);
458 std::vector<Property> children;
460 property.expand(children);
461 if (!children.empty()) {
466 ok =
property.expandList(children);
467 LOG_DEBUG(
"list elements " << children.size());
470 LOG_DEBUG(
"failed to analyze property " << property.name());
474 QTreeWidgetItem *item =
nullptr;
475 bool is_new_item =
false;
477 item = parent_item->child(rows - 1);
479 item =
new QTreeWidgetItem();
480 item->setFlags(item->flags() | Qt::ItemIsEditable);
481 parent_item->addChild(item);
485 item = view->topLevelItem(rows - 1);
487 item =
new QTreeWidgetItem();
488 view->addTopLevelItem(item);
492 item->setData(1, PropertyRole, QVariant::fromValue(property));
494 item->setText(0, property.displayName().c_str());
496 item->setText(1, QString::fromStdString(s));
497 item->setFlags(item->flags() | Qt::ItemIsEditable);
498 if (isPropertyDraggable(item)) {
499 item->setData(1, Qt::DecorationRole, MATERIAL_ICON(
"swap_vert"));
501 if (property.typeId() ==
typeid(bool)) {
502 if (property.get<
bool>()) {
503 item->setData(1, Qt::DecorationRole, MATERIAL_ICON(
"check_box"));
505 item->setData(1, Qt::DecorationRole,
506 MATERIAL_ICON(
"check_box_outline_blank"));
509 if (children.size()) {
510 sync(item, children);
512 item->setExpanded(
true);
517 while (parent_item->childCount() > rows) {
518 delete parent_item->takeChild(rows);
521 while (view->topLevelItemCount() > rows) {
522 delete view->topLevelItem(rows);
527 void PropertyGridWidget::sync() {
528 LOG_DEBUG(
"PropertyGridWidget::sync");
530 auto selected_objects = ws->selection().resolve(ws());
532 (selected_objects.size() == 1 ? selected_objects.front() :
nullptr);
533 if (
object ==
nullptr || object_id != object->id()) {
534 while (view->topLevelItemCount()) {
535 delete view->topLevelItem(0);
539 object_id = (
object ?
object->id() : 0);
541 if (
object !=
nullptr) {
542 auto props =
object->properties();
543 sync(
nullptr, std::vector<Property>(props.begin(), props.end()));