You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3084 lines
90 KiB
C++
3084 lines
90 KiB
C++
// Modifications copyright Amazon.com, Inc. or its affiliates.
|
|
|
|
/**
|
|
* wWidgets - Lightweight UI Toolkit.
|
|
* Copyright (C) 2009-2011 Evgeny Andreeshchev <eugene.andreeshchev@gmail.com>
|
|
* Alexander Kotliar <alexander.kotliar@gmail.com>
|
|
*
|
|
* This code is distributed under the MIT License:
|
|
* http://www.opensource.org/licenses/MIT
|
|
*/
|
|
|
|
|
|
#include "EditorCommon_precompiled.h"
|
|
#include "QPropertyTree.h"
|
|
#include "PropertyDrawContext.h"
|
|
#include "Serialization.h"
|
|
#include "Serialization/Decorators/Range.h"
|
|
using Serialization::Range;
|
|
#include "PropertyTreeModel.h"
|
|
#include "QPropertyTreeStyle.h"
|
|
|
|
#include "PropertyOArchive.h"
|
|
#include "PropertyIArchive.h"
|
|
#include "Unicode.h"
|
|
|
|
#include <QRect>
|
|
#include <QTimer>
|
|
#include <QMimeData>
|
|
#include <QMenu>
|
|
#include <QMouseEvent>
|
|
#include <QScrollBar>
|
|
#include <QLineEdit>
|
|
#include <QPainter>
|
|
#include <QElapsedTimer>
|
|
#include "PropertyTreeMenuHandler.h"
|
|
|
|
#include "MathUtils.h"
|
|
|
|
#include <QToolTip>
|
|
// only for clipboard:
|
|
#include <QClipboard>
|
|
#include <QApplication>
|
|
#include "PropertyRowPointer.h"
|
|
#include "PropertyRowContainer.h"
|
|
// ^^^
|
|
#include "PropertyRowObject.h"
|
|
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
|
|
|
|
using Serialization::SStructs;
|
|
|
|
void PropertyTreeMenuHandler::onMenuFilter()
|
|
{
|
|
tree->startFilter("");
|
|
}
|
|
|
|
void PropertyTreeMenuHandler::onMenuFilterByName()
|
|
{
|
|
tree->startFilter(filterName.c_str());
|
|
}
|
|
|
|
void PropertyTreeMenuHandler::onMenuFilterByValue()
|
|
{
|
|
tree->startFilter(filterValue.c_str());
|
|
}
|
|
|
|
void PropertyTreeMenuHandler::onMenuFilterByType()
|
|
{
|
|
tree->startFilter(filterType.c_str());
|
|
}
|
|
|
|
void PropertyTreeMenuHandler::onMenuUndo()
|
|
{
|
|
tree->model()->undo();
|
|
}
|
|
|
|
void PropertyTreeMenuHandler::onMenuRedo()
|
|
{
|
|
tree->model()->redo();
|
|
}
|
|
|
|
static PropertyRow* findFirstLeafPulledRow(PropertyRow* row)
|
|
{
|
|
if (row->isLeaf() &&
|
|
row->widgetPlacement() != PropertyRow::WIDGET_ICON &&
|
|
row->widgetPlacement() != PropertyRow::WIDGET_NONE)
|
|
return row;
|
|
|
|
for (int i = 0; i < row->count(); ++i)
|
|
{
|
|
PropertyRow* child = row->childByIndex(i);
|
|
if (!child)
|
|
continue;
|
|
if (!child->pulledUp())
|
|
continue;
|
|
PropertyRow* leaf = findFirstLeafPulledRow(child);
|
|
if (leaf)
|
|
return leaf;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static QMimeData* propertyRowToMimeData(PropertyRow* row, ConstStringList* constStrings)
|
|
{
|
|
PropertyRow::setConstStrings(constStrings);
|
|
SharedPtr<PropertyRow> clonedRow(row->clone(constStrings));
|
|
Serialization::BinOArchive oa;
|
|
PropertyRow::setConstStrings(constStrings);
|
|
if (!oa(clonedRow, "row", "Row")) {
|
|
PropertyRow::setConstStrings(0);
|
|
return 0;
|
|
}
|
|
PropertyRow::setConstStrings(0);
|
|
|
|
QByteArray byteArray(oa.buffer(), (int)oa.length());
|
|
QMimeData* mime = new QMimeData;
|
|
mime->setData("binary/crypropertytree", byteArray);
|
|
if (clonedRow)
|
|
{
|
|
PropertyRow* textRow = findFirstLeafPulledRow(row);
|
|
if (textRow)
|
|
mime->setText(QString::fromWCharArray(textRow->valueAsWString().c_str()));
|
|
}
|
|
return mime;
|
|
}
|
|
|
|
static bool smartPaste(PropertyRow* dest, SharedPtr<PropertyRow>& source, PropertyTreeModel* model, bool onlyCheck)
|
|
{
|
|
bool result = false;
|
|
// content of the pulled container has a priority over the node itself
|
|
PropertyRowContainer* destPulledContainer = static_cast<PropertyRowContainer*>(dest->pulledContainer());
|
|
if ((destPulledContainer && strcmp(destPulledContainer->elementTypeName(), source->typeName()) == 0)) {
|
|
PropertyRow* elementRow = model->defaultType(destPulledContainer->elementTypeName());
|
|
YASLI_ESCAPE(elementRow, return false);
|
|
if (strcmp(elementRow->typeName(), source->typeName()) == 0){
|
|
result = true;
|
|
if (!onlyCheck){
|
|
PropertyRow* dest = elementRow;
|
|
if (dest->isPointer() && !source->isPointer()){
|
|
PropertyRowPointer* d = static_cast<PropertyRowPointer*>(dest);
|
|
SharedPtr<PropertyRowPointer> newSourceRoot = static_cast<PropertyRowPointer*>(d->clone(model->constStrings()).get());
|
|
source->swapChildren(newSourceRoot, model);
|
|
source = newSourceRoot;
|
|
}
|
|
destPulledContainer->add(source.get());
|
|
}
|
|
}
|
|
}
|
|
else if ((source->isContainer() && dest->isContainer() &&
|
|
strcmp(static_cast<PropertyRowContainer*>(source.get())->elementTypeName(),
|
|
static_cast<PropertyRowContainer*>(dest)->elementTypeName()) == 0) ||
|
|
(!source->isContainer() && !dest->isContainer() && strcmp(source->typeName(), dest->typeName()) == 0)){
|
|
result = true;
|
|
if (!onlyCheck){
|
|
if (dest->isPointer() && !source->isPointer()){
|
|
PropertyRowPointer* d = static_cast<PropertyRowPointer*>(dest);
|
|
SharedPtr<PropertyRowPointer> newSourceRoot = static_cast<PropertyRowPointer*>(d->clone(model->constStrings()).get());
|
|
source->swapChildren(newSourceRoot, model);
|
|
source = newSourceRoot;
|
|
}
|
|
const char* name = dest->name();
|
|
const char* nameAlt = dest->label();
|
|
source->setName(name);
|
|
source->setLabel(nameAlt);
|
|
if (dest->parent())
|
|
dest->parent()->replaceAndPreserveState(dest, source, model);
|
|
else{
|
|
dest->swapChildren(source, model);
|
|
source->clear();
|
|
}
|
|
source->setLabelChanged();
|
|
}
|
|
}
|
|
else if (dest->isContainer()){
|
|
if (model){
|
|
PropertyRowContainer* container = static_cast<PropertyRowContainer*>(dest);
|
|
PropertyRow* elementRow = model->defaultType(container->elementTypeName());
|
|
YASLI_ESCAPE(elementRow, return false);
|
|
if (strcmp(elementRow->typeName(), source->typeName()) == 0){
|
|
result = true;
|
|
if (!onlyCheck){
|
|
PropertyRow* dest = elementRow;
|
|
if (dest->isPointer() && !source->isPointer()){
|
|
PropertyRowPointer* d = static_cast<PropertyRowPointer*>(dest);
|
|
SharedPtr<PropertyRowPointer> newSourceRoot = static_cast<PropertyRowPointer*>(d->clone(model->constStrings()).get());
|
|
source->swapChildren(newSourceRoot, model);
|
|
source = newSourceRoot;
|
|
}
|
|
|
|
container->add(source.get());
|
|
}
|
|
}
|
|
container->setLabelChanged();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool propertyRowFromMimeData(SharedPtr<PropertyRow>& row, const QMimeData* mimeData, ConstStringList* constStrings)
|
|
{
|
|
PropertyRow::setConstStrings(constStrings);
|
|
QStringList formats = mimeData->formats();
|
|
QByteArray array = mimeData->data("binary/crypropertytree");
|
|
if (array.isEmpty())
|
|
return 0;
|
|
Serialization::BinIArchive ia;
|
|
if (!ia.open(array.data(), array.size()))
|
|
return 0;
|
|
|
|
if (!ia(row, "row", "Row"))
|
|
return false;
|
|
|
|
PropertyRow::setConstStrings(0);
|
|
return true;
|
|
|
|
}
|
|
|
|
bool propertyRowFromClipboard(SharedPtr<PropertyRow>& row, ConstStringList* constStrings)
|
|
{
|
|
const QMimeData* mime = QApplication::clipboard()->mimeData();
|
|
if (!mime)
|
|
return false;
|
|
return propertyRowFromMimeData(row, mime, constStrings);
|
|
}
|
|
|
|
void PropertyTreeMenuHandler::onMenuCopy()
|
|
{
|
|
QMimeData* mime = propertyRowToMimeData(row, tree->model()->constStrings());
|
|
if (mime)
|
|
QApplication::clipboard()->setMimeData(mime);
|
|
}
|
|
|
|
void PropertyTreeMenuHandler::onMenuPaste()
|
|
{
|
|
if (!tree->canBePasted(row))
|
|
return;
|
|
PropertyRow* parent = row->parent();
|
|
|
|
tree->model()->rowAboutToBeChanged(row);
|
|
|
|
SharedPtr<PropertyRow> source;
|
|
if (!propertyRowFromClipboard(source, tree->model()->constStrings()))
|
|
return;
|
|
|
|
if (!smartPaste(row, source, tree->model(), false))
|
|
return;
|
|
|
|
tree->model()->rowChanged(parent ? parent : tree->model()->root());
|
|
}
|
|
|
|
class FilterEntry : public QLineEdit
|
|
{
|
|
public:
|
|
FilterEntry(QPropertyTree* tree)
|
|
: QLineEdit(tree)
|
|
, tree_(tree)
|
|
{
|
|
}
|
|
protected:
|
|
|
|
void keyPressEvent(QKeyEvent * ev)
|
|
{
|
|
if (ev->key() == Qt::Key_Escape || ev->key() == Qt::Key_Return)
|
|
{
|
|
ev->accept();
|
|
tree_->setFocus();
|
|
tree_->keyPressEvent(ev);
|
|
}
|
|
|
|
if (ev->key() == Qt::Key_Backspace && text().isEmpty())
|
|
{
|
|
tree_->setFilterMode(false);
|
|
}
|
|
QLineEdit::keyPressEvent(ev);
|
|
}
|
|
private:
|
|
QPropertyTree* tree_;
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
DragWindow::DragWindow(QPropertyTree* tree)
|
|
: tree_(tree)
|
|
, offset_(0, 0)
|
|
{
|
|
QWidget::setWindowFlags(Qt::ToolTip);
|
|
QWidget::setWindowOpacity(192.0f / 256.0f);
|
|
}
|
|
|
|
void DragWindow::set(QPropertyTree* tree, PropertyRow* row, const QRect& rowRect)
|
|
{
|
|
QRect rect = tree->rect();
|
|
rect.setTopLeft(tree->mapToGlobal(rect.topLeft()));
|
|
|
|
offset_ = rect.topLeft();
|
|
|
|
row_ = row;
|
|
rect_ = rowRect;
|
|
}
|
|
|
|
void DragWindow::setWindowPos([[maybe_unused]] bool visible)
|
|
{
|
|
QWidget::move(rect_.left() + offset_.x() - 3, rect_.top() + offset_.y() - 3 + tree_->area_.top());
|
|
QWidget::resize(rect_.width() + 5, rect_.height() + 5);
|
|
}
|
|
|
|
void DragWindow::show()
|
|
{
|
|
setWindowPos(true);
|
|
QWidget::show();
|
|
}
|
|
|
|
void DragWindow::move(int deltaX, int deltaY)
|
|
{
|
|
offset_ += QPoint(deltaX, deltaY);
|
|
setWindowPos(isVisible());
|
|
}
|
|
|
|
void DragWindow::hide()
|
|
{
|
|
setWindowPos(false);
|
|
QWidget::hide();
|
|
}
|
|
|
|
struct DrawRowVisitor
|
|
{
|
|
DrawRowVisitor(QPainter& painter) : painter_(painter) {}
|
|
|
|
ScanResult operator()(PropertyRow* row, QPropertyTree* tree, int index)
|
|
{
|
|
if (row->pulledUp() && row->visible(tree)) {
|
|
row->drawRow(painter_, tree, index, true);
|
|
row->drawRow(painter_, tree, index, false);
|
|
}
|
|
|
|
return SCAN_CHILDREN_SIBLINGS;
|
|
}
|
|
|
|
protected:
|
|
QPainter& painter_;
|
|
};
|
|
|
|
void DragWindow::drawRow(QPainter& p)
|
|
{
|
|
QRect entireRowRect(0, 0, rect_.width() + 4, rect_.height() + 4);
|
|
|
|
p.setBrush(tree_->palette().button());
|
|
p.setPen(QPen(tree_->palette().color(QPalette::WindowText)));
|
|
p.drawRect(entireRowRect);
|
|
|
|
QPoint leftTop = row_->rect().topLeft();
|
|
int offsetX = aznumeric_cast<int>(-leftTop.x() - tree_->treeStyle().firstLevelIndent * tree_->_defaultRowHeight() + 3);
|
|
int offsetY = -leftTop.y() + 3;
|
|
p.translate(offsetX, offsetY);
|
|
int rowIndex = 0;
|
|
if (row_->parent())
|
|
rowIndex = row_->parent()->childIndex(row_);
|
|
row_->drawRow(p, tree_, 0, true);
|
|
row_->drawRow(p, tree_, 0, false);
|
|
DrawRowVisitor visitor(p);
|
|
row_->scanChildren(visitor, tree_);
|
|
p.translate(-offsetX, -offsetY);
|
|
}
|
|
|
|
void DragWindow::paintEvent([[maybe_unused]] QPaintEvent* ev)
|
|
{
|
|
QPainter p(this);
|
|
|
|
drawRow(p);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class QPropertyTree::DragController
|
|
{
|
|
public:
|
|
DragController(QPropertyTree* tree)
|
|
: tree_(tree)
|
|
, captured_(false)
|
|
, dragging_(false)
|
|
, before_(false)
|
|
, row_(0)
|
|
, clickedRow_(0)
|
|
, window_(tree)
|
|
, hoveredRow_(0)
|
|
, destinationRow_(0)
|
|
{
|
|
}
|
|
|
|
void beginDrag(PropertyRow* clickedRow, PropertyRow* draggedRow, QPoint pt)
|
|
{
|
|
row_ = draggedRow;
|
|
clickedRow_ = clickedRow;
|
|
startPoint_ = pt;
|
|
lastPoint_ = pt;
|
|
captured_ = true;
|
|
dragging_ = false;
|
|
}
|
|
|
|
bool dragOn(QPoint screenPoint)
|
|
{
|
|
if (dragging_)
|
|
window_.move(screenPoint.x() - lastPoint_.x(), screenPoint.y() - lastPoint_.y());
|
|
|
|
bool needCapture = false;
|
|
if (!dragging_ && (startPoint_ - screenPoint).manhattanLength() >= 5)
|
|
if (row_->canBeDragged()){
|
|
needCapture = true;
|
|
QRect rect = row_->rect();
|
|
rect = QRect(rect.topLeft() - tree_->offset_ + QPoint(aznumeric_cast<int>(tree_->treeStyle().firstLevelIndent * tree_->_defaultRowHeight()), 0),
|
|
rect.bottomRight() - tree_->offset_);
|
|
|
|
window_.set(tree_, row_, rect);
|
|
window_.move(screenPoint.x() - startPoint_.x(), screenPoint.y() - startPoint_.y());
|
|
window_.show();
|
|
dragging_ = true;
|
|
}
|
|
|
|
if (dragging_){
|
|
QPoint point = tree_->mapFromGlobal(screenPoint);
|
|
trackRow(point);
|
|
}
|
|
lastPoint_ = screenPoint;
|
|
return needCapture;
|
|
}
|
|
|
|
void interrupt()
|
|
{
|
|
captured_ = false;
|
|
dragging_ = false;
|
|
row_ = 0;
|
|
window_.hide();
|
|
}
|
|
|
|
void trackRow(QPoint pt)
|
|
{
|
|
hoveredRow_ = 0;
|
|
destinationRow_ = 0;
|
|
|
|
QPoint point = pt;
|
|
PropertyRow* row = tree_->rowByPoint(point);
|
|
if (!row || !row_)
|
|
return;
|
|
|
|
row = row->nonPulledParent();
|
|
if (!row->parent() || row->isChildOf(row_) || row == row_)
|
|
return;
|
|
|
|
float pos = (point.y() - row->rect().top()) / float(row->rect().height());
|
|
if (row_->canBeDroppedOn(row->parent(), row, tree_)){
|
|
if (pos < 0.25f){
|
|
destinationRow_ = row->parent();
|
|
hoveredRow_ = row;
|
|
before_ = true;
|
|
return;
|
|
}
|
|
if (pos > 0.75f){
|
|
destinationRow_ = row->parent();
|
|
hoveredRow_ = row;
|
|
before_ = false;
|
|
return;
|
|
}
|
|
}
|
|
if (row_->canBeDroppedOn(row, 0, tree_))
|
|
hoveredRow_ = destinationRow_ = row;
|
|
}
|
|
|
|
void drawUnder(QPainter& painter)
|
|
{
|
|
if (dragging_ && destinationRow_ == hoveredRow_ && hoveredRow_){
|
|
QRect rowRect = hoveredRow_->rect();
|
|
rowRect.setLeft(aznumeric_cast<int>(rowRect.left() + tree_->treeStyle().firstLevelIndent * tree_->_defaultRowHeight()));
|
|
QBrush brush(true ? tree_->palette().highlight() : tree_->palette().shadow());
|
|
QColor brushColor = brush.color();
|
|
QColor borderColor(brushColor.alpha() / 4, brushColor.red(), brushColor.green(), brushColor.blue());
|
|
fillRoundRectangle(painter, brush, rowRect, borderColor, 6);
|
|
}
|
|
}
|
|
|
|
void drawOver(QPainter& painter)
|
|
{
|
|
if (!dragging_)
|
|
return;
|
|
|
|
QRect rowRect = row_->rect();
|
|
|
|
if (destinationRow_ != hoveredRow_ && hoveredRow_){
|
|
const int tickSize = 4;
|
|
QRect hoveredRect = hoveredRow_->rect();
|
|
hoveredRect.setLeft(aznumeric_cast<int>(hoveredRect.left() + tree_->treeStyle().firstLevelIndent * tree_->_defaultRowHeight()));
|
|
|
|
if (!before_){ // previous
|
|
QRect rect(hoveredRect.left() - 1, hoveredRect.bottom() - 1, hoveredRect.width(), 2);
|
|
QRect rectLeft(hoveredRect.left() - 1, hoveredRect.bottom() - tickSize, 2, tickSize * 2);
|
|
QRect rectRight(hoveredRect.right() - 1, hoveredRect.bottom() - tickSize, 2, tickSize * 2);
|
|
painter.fillRect(rect, tree_->palette().highlight());
|
|
painter.fillRect(rectLeft, tree_->palette().highlight());
|
|
painter.fillRect(rectRight, tree_->palette().highlight());
|
|
}
|
|
else{ // next
|
|
QRect rect(hoveredRect.left() - 1, hoveredRect.top() - 1, hoveredRect.width(), 2);
|
|
QRect rectLeft(hoveredRect.left() - 1, hoveredRect.top() - tickSize, 2, tickSize * 2);
|
|
QRect rectRight(hoveredRect.right() - 1, hoveredRect.top() - tickSize, 2, tickSize * 2);
|
|
painter.fillRect(rect, tree_->palette().highlight());
|
|
painter.fillRect(rectLeft, tree_->palette().highlight());
|
|
painter.fillRect(rectRight, tree_->palette().highlight());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool drop([[maybe_unused]] QPoint screenPoint)
|
|
{
|
|
bool rowLayoutChanged = false;
|
|
if (row_ && hoveredRow_){
|
|
YASLI_ASSERT(destinationRow_);
|
|
clickedRow_->setSelected(false);
|
|
row_->dropInto(destinationRow_, destinationRow_ == hoveredRow_ ? 0 : hoveredRow_, tree_, before_);
|
|
rowLayoutChanged = true;
|
|
}
|
|
|
|
captured_ = false;
|
|
dragging_ = false;
|
|
row_ = 0;
|
|
window_.hide();
|
|
hoveredRow_ = 0;
|
|
destinationRow_ = 0;
|
|
return rowLayoutChanged;
|
|
}
|
|
|
|
bool captured() const{ return captured_; }
|
|
bool dragging() const{ return dragging_; }
|
|
PropertyRow* draggedRow() { return row_; }
|
|
protected:
|
|
DragWindow window_;
|
|
QPropertyTree* tree_;
|
|
PropertyRow* row_;
|
|
PropertyRow* clickedRow_;
|
|
PropertyRow* hoveredRow_;
|
|
PropertyRow* destinationRow_;
|
|
QPoint startPoint_;
|
|
QPoint lastPoint_;
|
|
bool captured_;
|
|
bool dragging_;
|
|
bool before_;
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
AZ_PUSH_DISABLE_WARNING(4335, "-Wunknown-warning-option")
|
|
QPropertyTree::QPropertyTree(QWidget* parent)
|
|
: QWidget(parent)
|
|
, sizeHint_(180, 180)
|
|
, model_(0)
|
|
, cursorX_(0)
|
|
, attachedPropertyTree_(0)
|
|
, autoHideAttachedPropertyTree_(false)
|
|
, autoRevert_(true)
|
|
, dragController_(new DragController(this))
|
|
, leftBorder_(0)
|
|
, rightBorder_(0)
|
|
, filterMode_(false)
|
|
|
|
, applyTime_(0)
|
|
, revertTime_(0)
|
|
, updateHeightsTime_(0)
|
|
, paintTime_(0)
|
|
, pressPoint_(-1, -1)
|
|
, pressDelta_(0, 0)
|
|
, pointerMovedSincePress_(false)
|
|
, lastStillPosition_(-1, -1)
|
|
, pressedRow_(0)
|
|
, capturedRow_(0)
|
|
, iconCache_(new IconXPMCache())
|
|
, dragCheckMode_(false)
|
|
, dragCheckValue_(false)
|
|
, archiveContext_(0)
|
|
, outlineMode_(false)
|
|
, sizeToContent_(false)
|
|
, hideSelection_(false)
|
|
, zoomLevel_(10)
|
|
, validatorBlock_(new ValidatorBlock)
|
|
, style_(new QPropertyTreeStyle())
|
|
|
|
, aggregateMouseEvents_(false)
|
|
, aggregatedMouseEventCount_(0)
|
|
{
|
|
setFocusPolicy(Qt::WheelFocus);
|
|
setMouseTracking(true); // need to receive mouseMoveEvent to update mouse cursor and tooltip
|
|
|
|
scrollBar_ = new QScrollBar(Qt::Vertical, this);
|
|
connect(scrollBar_, SIGNAL(valueChanged(int)), this, SLOT(onScroll(int)));
|
|
|
|
model_.reset(new PropertyTreeModel());
|
|
model_->setExpandLevels(config_.expandLevels);
|
|
model_->setUndoEnabled(config_.undoEnabled);
|
|
model_->setFullUndo(config_.fullUndo);
|
|
|
|
connect(model_.data(), SIGNAL(signalUpdated(const PropertyRows&, bool)), this, SLOT(onModelUpdated(const PropertyRows&, bool)));
|
|
connect(model_.data(), SIGNAL(signalPushUndo(PropertyTreeOperator*, bool*)), this, SLOT(onModelPushUndo(PropertyTreeOperator*, bool*)));
|
|
connect(model_.data(), SIGNAL(signalPushRedo(PropertyTreeOperator*, bool*)), this, SLOT(onModelPushRedo(PropertyTreeOperator*, bool*)));
|
|
//model_->signalPushUndo().connect(this, &QPropertyTree::onModelPushUndo);
|
|
|
|
filterEntry_.reset(new FilterEntry(this));
|
|
QObject::connect(filterEntry_.data(), SIGNAL(textChanged(const QString&)), this, SLOT(onFilterChanged(const QString&)));
|
|
filterEntry_->hide();
|
|
|
|
mouseStillTimer_ = new QTimer(this);
|
|
mouseStillTimer_->setSingleShot(true);
|
|
connect(mouseStillTimer_, SIGNAL(timeout()), this, SLOT(onMouseStillTimeout()));
|
|
|
|
boldFont_.setBold(true);
|
|
backgroundColor_ = palette().color(QPalette::Window);
|
|
}
|
|
AZ_POP_DISABLE_WARNING
|
|
|
|
QPropertyTree::~QPropertyTree()
|
|
{
|
|
clearMenuHandlers();
|
|
}
|
|
|
|
bool QPropertyTree::onRowKeyDown(PropertyRow* row, const QKeyEvent* ev)
|
|
{
|
|
PropertyTreeMenuHandler handler;
|
|
handler.row = row;
|
|
handler.tree = this;
|
|
|
|
if (row->onKeyDown(this, ev))
|
|
return true;
|
|
if (row->pulledContainer() && static_cast<PropertyRowContainer*>(row->pulledContainer())->onKeyDownContainer(this, ev))
|
|
return true;
|
|
|
|
// NOTE: If you add a new key here, you also have to check for it in rowProcessesKey
|
|
|
|
switch (ev->key()){
|
|
case Qt::Key_C:
|
|
if (!row->userNonCopyable() && ev->modifiers() == Qt::CTRL)
|
|
handler.onMenuCopy();
|
|
return true;
|
|
case Qt::Key_V:
|
|
if (!row->userNonCopyable() && ev->modifiers() == Qt::CTRL)
|
|
handler.onMenuPaste();
|
|
return true;
|
|
case Qt::Key_Z:
|
|
if (config_.undoEnabled)
|
|
{
|
|
if (ev->modifiers() == (Qt::SHIFT | Qt::CTRL))
|
|
{
|
|
if (model()->canRedo())
|
|
{
|
|
handler.onMenuRedo();
|
|
}
|
|
return true;
|
|
}
|
|
else if (ev->modifiers() == Qt::CTRL)
|
|
{
|
|
if (model()->canUndo())
|
|
{
|
|
handler.onMenuUndo();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ev->modifiers() == (Qt::SHIFT | Qt::CTRL))
|
|
{
|
|
emit signalRedo();
|
|
}
|
|
else if (ev->modifiers() == Qt::CTRL)
|
|
{
|
|
emit signalUndo();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
break;
|
|
case Qt::Key_Y:
|
|
if (!config_.undoEnabled)
|
|
{
|
|
if (model()->canRedo())
|
|
{
|
|
handler.onMenuRedo();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ev->modifiers() == Qt::CTRL)
|
|
{
|
|
emit signalRedo();
|
|
}
|
|
}
|
|
return true;
|
|
break;
|
|
|
|
case Qt::Key_F2:
|
|
if (ev->modifiers() == Qt::NoModifier) {
|
|
if (selectedRow()) {
|
|
PropertyActivationEvent act;
|
|
act.tree = this;
|
|
act.force = true;
|
|
act.reason = PropertyActivationEvent::REASON_KEYBOARD;
|
|
selectedRow()->onActivate(act);
|
|
}
|
|
}
|
|
break;
|
|
case Qt::Key_Menu:
|
|
{
|
|
if (ev->modifiers() == Qt::NoModifier) {
|
|
QMenu menu(this);
|
|
|
|
if (onContextMenu(row, menu)){
|
|
QRect rect(row->rect());
|
|
QPoint pt = _toScreen(QPoint(rect.left() + rect.height(), rect.bottom()));
|
|
menu.exec(pt);
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
PropertyRow* focusedRow = model()->focusedRow();
|
|
if (!focusedRow)
|
|
return false;
|
|
PropertyRow* parentRow = focusedRow->nonPulledParent();
|
|
int x = parentRow->horizontalIndex(this, focusedRow);
|
|
int y = model()->root()->verticalIndex(this, parentRow);
|
|
PropertyRow* selectedRow = 0;
|
|
switch (ev->key()){
|
|
case Qt::Key_Up:
|
|
if (filterMode_ && y == 0) {
|
|
setFilterMode(true);
|
|
}
|
|
else {
|
|
selectedRow = model()->root()->rowByVerticalIndex(this, --y);
|
|
if (selectedRow)
|
|
selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
|
|
}
|
|
break;
|
|
case Qt::Key_Down:
|
|
if (filterMode_ && filterEntry_->hasFocus()) {
|
|
setFocus();
|
|
}
|
|
else {
|
|
selectedRow = model()->root()->rowByVerticalIndex(this, ++y);
|
|
if (selectedRow)
|
|
selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
|
|
}
|
|
break;
|
|
case Qt::Key_Left:
|
|
selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = --x);
|
|
if (selectedRow == focusedRow && parentRow->canBeToggled(this) && parentRow->expanded()){
|
|
expandRow(parentRow, false);
|
|
selectedRow = model()->focusedRow();
|
|
}
|
|
break;
|
|
case Qt::Key_Right:
|
|
selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = ++x);
|
|
if (selectedRow == focusedRow && parentRow->canBeToggled(this) && !parentRow->expanded()){
|
|
expandRow(parentRow, true);
|
|
selectedRow = model()->focusedRow();
|
|
}
|
|
break;
|
|
case Qt::Key_Home:
|
|
if (ev->modifiers() == Qt::CTRL) {
|
|
selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = INT_MIN);
|
|
}
|
|
else {
|
|
selectedRow = model()->root()->rowByVerticalIndex(this, 0);
|
|
if (selectedRow)
|
|
selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
|
|
}
|
|
break;
|
|
case Qt::Key_End:
|
|
if (ev->modifiers() == Qt::CTRL) {
|
|
selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = INT_MAX);
|
|
}
|
|
else {
|
|
selectedRow = model()->root()->rowByVerticalIndex(this, INT_MAX);
|
|
if (selectedRow)
|
|
selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
|
|
}
|
|
break;
|
|
case Qt::Key_Space:
|
|
if (config_.filterWhenType)
|
|
break;
|
|
case Qt::Key_Return:
|
|
if (focusedRow->canBeToggled(this))
|
|
expandRow(focusedRow, !focusedRow->expanded());
|
|
else {
|
|
PropertyActivationEvent e;
|
|
e.tree = this;
|
|
e.reason = e.REASON_KEYBOARD;
|
|
e.force = false;
|
|
focusedRow->onActivate(e);
|
|
}
|
|
break;
|
|
}
|
|
if (selectedRow){
|
|
onRowSelected(std::vector<PropertyRow*>(1, selectedRow), false, false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QPropertyTree::rowProcessesKey(PropertyRow* row, const QKeyEvent* ev)
|
|
{
|
|
if (row->processesKey(this, ev))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (row->pulledContainer() && static_cast<PropertyRowContainer*>(row->pulledContainer())->processesKeyContainer(this, ev))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int modifiedKey = ev->key() | ev->modifiers();
|
|
|
|
switch (modifiedKey)
|
|
{
|
|
case (Qt::CTRL | Qt::Key_Z):
|
|
case (Qt::CTRL | Qt::SHIFT | Qt::Key_Z) :
|
|
case Qt::Key_Y:
|
|
case (Qt::CTRL | Qt::Key_V):
|
|
case (Qt::CTRL | Qt::Key_C):
|
|
case (Qt::CTRL | Qt::Key_F):
|
|
case Qt::Key_Menu:
|
|
case Qt::Key_F2:
|
|
return true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (ev->key())
|
|
{
|
|
case Qt::Key_Up:
|
|
case Qt::Key_Down:
|
|
case Qt::Key_Left:
|
|
case Qt::Key_Right:
|
|
case Qt::Key_Home:
|
|
case Qt::Key_End:
|
|
case Qt::Key_Return:
|
|
return true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct FirstIssueVisitor
|
|
{
|
|
ValidatorEntryType entryType_;
|
|
PropertyRow* startRow_;
|
|
PropertyRow* result;
|
|
|
|
FirstIssueVisitor(ValidatorEntryType type, PropertyRow* startRow)
|
|
: entryType_(type)
|
|
, startRow_(startRow)
|
|
, result()
|
|
{
|
|
}
|
|
|
|
ScanResult operator()(PropertyRow* row, QPropertyTree* tree, int)
|
|
{
|
|
if ((row->pulledUp() || row->pulledBefore()) && row->nonPulledParent() == startRow_)
|
|
return SCAN_SIBLINGS;
|
|
if (row->validatorCount()) {
|
|
if (const ValidatorEntry* validatorEntries = tree->_validatorBlock()->GetEntry(row->validatorIndex(), row->validatorCount())) {
|
|
for (int i = 0; i < row->validatorCount(); ++i) {
|
|
const ValidatorEntry* validatorEntry = validatorEntries + i;
|
|
if (validatorEntry->type == entryType_) {
|
|
result = row;
|
|
return SCAN_FINISHED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return SCAN_CHILDREN_SIBLINGS;
|
|
}
|
|
};
|
|
|
|
void QPropertyTree::jumpToNextHiddenValidatorIssue(bool isError, PropertyRow* start)
|
|
{
|
|
FirstIssueVisitor op(isError ? VALIDATOR_ENTRY_ERROR : VALIDATOR_ENTRY_WARNING, start);
|
|
start->scanChildren(op, this);
|
|
|
|
PropertyRow* row = op.result;
|
|
|
|
vector<PropertyRow*> parents;
|
|
while (row && row->parent()) {
|
|
parents.push_back(row);
|
|
row = row->parent();
|
|
}
|
|
for (int i = (int)parents.size() - 1; i >= 0; --i) {
|
|
if (!parents[i]->visible(this))
|
|
break;
|
|
row = parents[i];
|
|
}
|
|
if (row)
|
|
setSelectedRow(row);
|
|
|
|
updateValidatorIcons();
|
|
updateHeights();
|
|
}
|
|
|
|
static void rowsInBetween(vector<PropertyRow*>* rows, PropertyRow* a, PropertyRow* b)
|
|
{
|
|
if (!a)
|
|
return;
|
|
if (!b)
|
|
return;
|
|
vector<PropertyRow*> pathA;
|
|
PropertyRow* rootA = a;
|
|
while (rootA->parent()) {
|
|
pathA.push_back(rootA);
|
|
rootA = rootA->parent();
|
|
}
|
|
|
|
vector<PropertyRow*> pathB;
|
|
PropertyRow* rootB = b;
|
|
while (rootB->parent()) {
|
|
pathB.push_back(rootB);
|
|
rootB = rootB->parent();
|
|
}
|
|
|
|
if (rootA != rootB)
|
|
return;
|
|
|
|
const PropertyRow* commonParent = rootA;
|
|
int maxDepth = min((int)pathA.size(), (int)pathB.size());
|
|
for (int i = 0; i < maxDepth; ++i) {
|
|
PropertyRow* parentA = pathA[(int)pathA.size() - 1 - i];
|
|
PropertyRow* parentB = pathB[(int)pathB.size() - 1 - i];
|
|
if (parentA != parentB) {
|
|
int indexA = commonParent->childIndex(parentA);
|
|
int indexB = commonParent->childIndex(parentB);
|
|
int minIndex = min(indexA, indexB);
|
|
int maxIndex = max(indexA, indexB);
|
|
for (int j = minIndex; j <= maxIndex; ++j)
|
|
rows->push_back((PropertyRow*)commonParent->childByIndex(j));
|
|
return;
|
|
}
|
|
commonParent = parentA;
|
|
}
|
|
}
|
|
|
|
bool QPropertyTree::onRowLMBDown(PropertyRow* row, [[maybe_unused]] const QRect& rowRect, QPoint point, bool controlPressed, bool shiftPressed)
|
|
{
|
|
pressPoint_ = point;
|
|
pressDelta_ = QPoint(0, 0);
|
|
pointerMovedSincePress_ = false;
|
|
row = model()->root()->hit(this, point);
|
|
if (row){
|
|
if (!row->isRoot()) {
|
|
if (row->plusRect(this).contains(point) && toggleRow(row))
|
|
return true;
|
|
if (row->validatorWarningIconRect(this).contains(point)) {
|
|
jumpToNextHiddenValidatorIssue(false, row);
|
|
return true;
|
|
}
|
|
if (row->validatorErrorIconRect(this).contains(point)) {
|
|
jumpToNextHiddenValidatorIssue(true, row);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
PropertyRow* rowToSelect = row;
|
|
while (rowToSelect && !rowToSelect->isSelectable())
|
|
rowToSelect = rowToSelect->parent();
|
|
|
|
if (rowToSelect) {
|
|
if (!shiftPressed || !multiSelectable()) {
|
|
onRowSelected(std::vector<PropertyRow*>(1, rowToSelect), multiSelectable() && controlPressed, true);
|
|
lastSelectedRow_ = rowToSelect;
|
|
}
|
|
else {
|
|
vector<PropertyRow*> rowsToSelect;
|
|
|
|
rowsInBetween(&rowsToSelect, lastSelectedRow_, rowToSelect);
|
|
onRowSelected(rowsToSelect, false, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
PropertyTreeModel::UpdateLock lock = model()->lockUpdate();
|
|
row = model()->root()->hit(this, point);
|
|
if (row && !row->isRoot()){
|
|
bool changed = false;
|
|
if (row->widgetRect(this).contains(point)) {
|
|
DragCheckBegin dragCheck = row->onMouseDragCheckBegin();
|
|
if (dragCheck != DRAG_CHECK_IGNORE) {
|
|
dragCheckValue_ = dragCheck == DRAG_CHECK_SET;
|
|
dragCheckMode_ = true;
|
|
changed = row->onMouseDragCheck(this, dragCheckValue_);
|
|
}
|
|
}
|
|
|
|
if (!dragCheckMode_) {
|
|
bool capture = row->onMouseDown(this, point, changed);
|
|
if (!changed){
|
|
if (capture)
|
|
return true;
|
|
else if (row->widgetRect(this).contains(point)){
|
|
if (row->widgetPlacement() != PropertyRow::WIDGET_ICON)
|
|
interruptDrag();
|
|
PropertyActivationEvent e;
|
|
e.force = false;
|
|
e.tree = this;
|
|
e.clickPoint = point;
|
|
row->onActivate(e);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QPropertyTree::onRowLMBUp(PropertyRow* row, [[maybe_unused]] const QRect& rowRect, QPoint point)
|
|
{
|
|
onMouseStill(point);
|
|
row->onMouseUp(this, point);
|
|
|
|
if (!pointerMovedSincePress_ && (pressPoint_ - point).manhattanLength() < 1 && row->widgetRect(this).contains(point)) {
|
|
PropertyActivationEvent e;
|
|
e.tree = this;
|
|
e.clickPoint = point;
|
|
e.reason = e.REASON_RELEASE;
|
|
row->onActivate(e);
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::onRowRMBDown(PropertyRow* row, [[maybe_unused]] const QRect& rowRect, QPoint point)
|
|
{
|
|
SharedPtr<PropertyRow> handle = row;
|
|
PropertyRow* menuRow = 0;
|
|
|
|
if (row->isSelectable()){
|
|
menuRow = row;
|
|
}
|
|
else{
|
|
if (row->parent() && row->parent()->isSelectable())
|
|
menuRow = row->parent();
|
|
}
|
|
|
|
if (menuRow) {
|
|
onRowSelected(std::vector<PropertyRow*>(1, menuRow), false, true);
|
|
QMenu menu(this);
|
|
clearMenuHandlers();
|
|
if (onContextMenu(menuRow, menu))
|
|
menu.exec(point);
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::expandParents(PropertyRow* row)
|
|
{
|
|
bool hasChanges = false;
|
|
typedef std::vector<PropertyRow*> Parents;
|
|
Parents parents;
|
|
PropertyRow* p = row->nonPulledParent()->parent();
|
|
while (p){
|
|
parents.push_back(p);
|
|
p = p->parent();
|
|
}
|
|
Parents::iterator it;
|
|
for (it = parents.begin(); it != parents.end(); ++it) {
|
|
PropertyRow* row = *it;
|
|
row->_setExpanded(true);
|
|
hasChanges = true;
|
|
}
|
|
if (hasChanges) {
|
|
updateValidatorIcons();
|
|
updateHeights();
|
|
}
|
|
}
|
|
|
|
|
|
void QPropertyTree::expandAll(PropertyRow* root)
|
|
{
|
|
if (!root){
|
|
root = model()->root();
|
|
for (PropertyRows::iterator it = root->begin(); it != root->end(); ++it){
|
|
PropertyRow* row = *it;
|
|
row->setExpandedRecursive(this, true);
|
|
}
|
|
root->setLayoutChanged();
|
|
}
|
|
else
|
|
root->setExpandedRecursive(this, true);
|
|
|
|
for (PropertyRow* r = root; r != 0; r = r->parent())
|
|
r->setLayoutChanged();
|
|
|
|
updateHeights();
|
|
}
|
|
|
|
void QPropertyTree::collapseAll(PropertyRow* root)
|
|
{
|
|
if (!root){
|
|
root = model()->root();
|
|
|
|
for (PropertyRows::iterator it = root->begin(); it != root->end(); ++it){
|
|
PropertyRow* row = *it;
|
|
row->setExpandedRecursive(this, false);
|
|
}
|
|
}
|
|
else{
|
|
root->setExpandedRecursive(this, false);
|
|
PropertyRow* row = model()->focusedRow();
|
|
while (row){
|
|
if (root == row){
|
|
model()->selectRow(row, true);
|
|
break;
|
|
}
|
|
row = row->parent();
|
|
}
|
|
}
|
|
|
|
for (PropertyRow* r = root; r != 0; r = r->parent())
|
|
r->setLayoutChanged();
|
|
|
|
updateHeights();
|
|
}
|
|
|
|
|
|
void QPropertyTree::expandRow(PropertyRow* row, bool expanded, bool updateHeights)
|
|
{
|
|
bool hasChanges = false;
|
|
if (row->expanded() != expanded) {
|
|
row->_setExpanded(expanded);
|
|
hasChanges = true;
|
|
}
|
|
|
|
for (PropertyRow* r = row; r != 0; r = r->parent())
|
|
r->setLayoutChanged();
|
|
|
|
if (!row->expanded()){
|
|
PropertyRow* f = model()->focusedRow();
|
|
while (f){
|
|
if (row == f){
|
|
model()->selectRow(row, true);
|
|
break;
|
|
}
|
|
f = f->parent();
|
|
}
|
|
}
|
|
|
|
if (hasChanges)
|
|
updateValidatorIcons();
|
|
if (hasChanges && updateHeights)
|
|
this->updateHeights();
|
|
}
|
|
|
|
void QPropertyTree::interruptDrag()
|
|
{
|
|
dragController_->interrupt();
|
|
}
|
|
|
|
void QPropertyTree::updateHeights(bool recalculateTextSize)
|
|
{
|
|
QFontMetrics fm(font());
|
|
defaultRowHeight_ = max(16, int(fm.lineSpacing() * 1.666f)); // to fit at least 16x16 icons
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
model()->root()->updateLabel(this, 0, false);
|
|
|
|
QRect widgetRect = this->rect();
|
|
|
|
int scrollBarW = 16;
|
|
int lb = 1;
|
|
int rb = widgetRect.right() - lb - scrollBarW - 2;
|
|
int availableWidth = widgetRect.width() - 4 - scrollBarW;
|
|
bool force = recalculateTextSize || lb != leftBorder_ || rb != rightBorder_;
|
|
leftBorder_ = lb;
|
|
rightBorder_ = rb;
|
|
model()->root()->calculateMinimalSize(this, leftBorder_, availableWidth, force, 0, 0, 0);
|
|
|
|
updateValidatorIcons();
|
|
|
|
int totalHeight = 0;
|
|
model()->root()->adjustVerticalPosition(this, totalHeight);
|
|
totalHeight += 4;
|
|
QPoint oldSize = size_;
|
|
size_.setY(totalHeight);
|
|
|
|
updateScrollBar();
|
|
|
|
area_.setLeft(widgetRect.left() + 2);
|
|
area_.setRight(widgetRect.right() - 2 - scrollBarW);
|
|
area_.setTop(widgetRect.top() + 2);
|
|
area_.setBottom(widgetRect.bottom() - 2);
|
|
size_.setX(area_.width());
|
|
|
|
int filterAreaHeight = 0;
|
|
if (filterMode_)
|
|
{
|
|
filterAreaHeight = filterEntry_ ? filterEntry_->height() : 0;
|
|
area_.setTop(area_.top() + filterAreaHeight + 2 + 2);
|
|
}
|
|
|
|
_arrangeChildren();
|
|
|
|
int contentHeight = totalHeight + filterAreaHeight + 4;
|
|
if (sizeToContent_)
|
|
{
|
|
setMaximumHeight(contentHeight);
|
|
setMinimumHeight(contentHeight);
|
|
}
|
|
else
|
|
{
|
|
setMaximumHeight(QWIDGETSIZE_MAX);
|
|
setMinimumHeight(0);
|
|
}
|
|
|
|
update();
|
|
updateHeightsTime_ = aznumeric_cast<int>(timer.elapsed());
|
|
|
|
QSize contentSize = QSize(area_.width(), contentHeight);
|
|
if (contentSize_.height() != contentSize.height())
|
|
{
|
|
contentSize_ = contentSize;
|
|
signalSizeChanged();
|
|
}
|
|
else
|
|
{
|
|
contentSize_ = contentSize;
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::setSizeToContent(bool sizeToContent)
|
|
{
|
|
if (sizeToContent != sizeToContent_)
|
|
{
|
|
sizeToContent_ = sizeToContent;
|
|
updateHeights();
|
|
}
|
|
}
|
|
|
|
|
|
bool QPropertyTree::updateScrollBar()
|
|
{
|
|
int pageSize = rect().height();
|
|
offset_.setX(max(0, min(offset_.x(), max(0, size_.x() - area_.right() - 1))));
|
|
offset_.setY(max(0, min(offset_.y(), max(0, size_.y() - pageSize))));
|
|
|
|
if (pageSize < size_.y())
|
|
{
|
|
scrollBar_->setRange(0, size_.y() - pageSize);
|
|
scrollBar_->setSliderPosition(offset_.y());
|
|
scrollBar_->setPageStep(pageSize);
|
|
scrollBar_->show();
|
|
scrollBar_->move(rect().right() - scrollBar_->width(), 0);
|
|
scrollBar_->resize(scrollBar_->width(), height());
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
scrollBar_->hide();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QPoint QPropertyTree::treeSize() const
|
|
{
|
|
return size_ + (compact() ? QPoint(0, 0) : QPoint(8, 8));
|
|
}
|
|
|
|
void QPropertyTree::onScroll([[maybe_unused]] int pos)
|
|
{
|
|
offset_.setY(scrollBar_->sliderPosition());
|
|
_arrangeChildren();
|
|
repaint();
|
|
}
|
|
|
|
void QPropertyTree::Serialize(IArchive& ar)
|
|
{
|
|
model()->Serialize(ar, this);
|
|
|
|
if (ar.IsInput()){
|
|
ensureVisible(model()->focusedRow());
|
|
updateAttachedPropertyTree(false);
|
|
updateHeights();
|
|
signalSelected();
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::ensureVisible(PropertyRow* row, bool update, bool considerChildren)
|
|
{
|
|
if (row == 0)
|
|
return;
|
|
if (row->isRoot())
|
|
return;
|
|
|
|
expandParents(row);
|
|
|
|
QRect rect = considerChildren ? row->rectIncludingChildren(this) : row->rect();
|
|
if (rect.bottom() > area_.bottom() + offset_.y()){
|
|
offset_.setY(max(0, rect.bottom() - area_.bottom()));
|
|
}
|
|
if (rect.top() < area_.top() + offset_.y()){
|
|
offset_.setY(max(0, rect.top() - area_.top()));
|
|
}
|
|
updateScrollBar();
|
|
if (update)
|
|
this->update();
|
|
}
|
|
|
|
void QPropertyTree::onRowSelected(const std::vector<PropertyRow*>& rows, bool addSelection, bool adjustCursorPos)
|
|
{
|
|
for (size_t i = 0; i < rows.size(); ++i) {
|
|
PropertyRow* row = rows[i];
|
|
if (!row->isRoot()) {
|
|
bool addRowToSelection = !(addSelection && row->selected() && model()->selection().size() > 1) || i > 0;
|
|
bool exclusiveSelection = !addSelection && i == 0;
|
|
model()->selectRow(row, addRowToSelection, exclusiveSelection);
|
|
}
|
|
}
|
|
if (!rows.empty()) {
|
|
ensureVisible(rows.back(), true, false);
|
|
if (adjustCursorPos)
|
|
cursorX_ = rows.back()->nonPulledParent()->horizontalIndex(this, rows.back());
|
|
}
|
|
updateAttachedPropertyTree(false);
|
|
signalSelected();
|
|
}
|
|
|
|
bool QPropertyTree::attach(const Serialization::SStructs& serializers)
|
|
{
|
|
bool changed = false;
|
|
if (attached_.size() != serializers.size())
|
|
changed = true;
|
|
else {
|
|
for (size_t i = 0; i < serializers.size(); ++i) {
|
|
if (attached_[i].serializer() != serializers[i]) {
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We can't perform plain copying here, as it was before:
|
|
// attached_ = serializers;
|
|
// ...as move forwarder calls copying constructor with non-const argument
|
|
// which invokes second templated constructor of Serializer, which is not what we need.
|
|
if (changed) {
|
|
attached_.assign(serializers.begin(), serializers.end());
|
|
model_->clearUndo();
|
|
}
|
|
|
|
revertNoninterrupting();
|
|
|
|
return changed;
|
|
}
|
|
|
|
void QPropertyTree::attach(const Serialization::SStruct& serializer)
|
|
{
|
|
if (attached_.size() != 1 || attached_[0].serializer() != serializer) {
|
|
attached_.clear();
|
|
attached_.push_back(Serialization::Object(serializer));
|
|
model_->clearUndo();
|
|
}
|
|
revert();
|
|
}
|
|
|
|
void QPropertyTree::attach(const Serialization::Object& object)
|
|
{
|
|
attached_.clear();
|
|
attached_.push_back(object);
|
|
|
|
revert();
|
|
}
|
|
|
|
void QPropertyTree::detach()
|
|
{
|
|
if (widget_)
|
|
widget_.reset();
|
|
attached_.clear();
|
|
model()->root()->clear();
|
|
update();
|
|
}
|
|
|
|
int QPropertyTree::revertObjects(vector<void*> objectAddresses)
|
|
{
|
|
int result = 0;
|
|
for (size_t i = 0; i < objectAddresses.size(); ++i) {
|
|
if (revertObject(objectAddresses[i]))
|
|
++result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool QPropertyTree::revertObject(void* objectAddress)
|
|
{
|
|
PropertyRow* row = model()->root()->findByAddress(objectAddress);
|
|
if (row && row->isObject()) {
|
|
// TODO:
|
|
// revertObjectRow(row);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void QPropertyTree::revert()
|
|
{
|
|
interruptDrag();
|
|
widget_.reset();
|
|
capturedRow_ = 0;
|
|
|
|
if (!attached_.empty()) {
|
|
validatorBlock_->Clear();
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
PropertyOArchive oa(model_.data(), model_->root(), validatorBlock_.data());
|
|
oa.SetOutlineMode(outlineMode_);
|
|
if (archiveContext_)
|
|
oa.SetInnerContext(archiveContext_);
|
|
oa.SetFilter(config_.filter);
|
|
|
|
Objects::iterator it = attached_.begin();
|
|
signalAboutToSerialize(oa);
|
|
(*it)(oa);
|
|
signalSerialized(oa);
|
|
|
|
PropertyTreeModel model2;
|
|
if (it != attached_.end()) {
|
|
while (++it != attached_.end()){
|
|
PropertyOArchive oa2(&model2, model2.root(), validatorBlock_.data());
|
|
oa2.SetOutlineMode(outlineMode_);
|
|
Serialization::SContext<QPropertyTree> treeContext(oa2, this);
|
|
if (archiveContext_)
|
|
oa2.SetInnerContext(archiveContext_);
|
|
oa2.SetFilter(config_.filter);
|
|
signalAboutToSerialize(oa2);
|
|
(*it)(oa2);
|
|
signalSerialized(oa2);
|
|
model_->root()->intersect(model2.root());
|
|
}
|
|
}
|
|
revertTime_ = int(timer.elapsed());
|
|
|
|
if (attached_.size() != 1)
|
|
validatorBlock_->Clear();
|
|
applyValidation();
|
|
}
|
|
else
|
|
model_->clear();
|
|
|
|
if (filterMode_) {
|
|
if (model_->root())
|
|
model_->root()->updateLabel(this, 0, false);
|
|
onFilterChanged(QString());
|
|
}
|
|
else {
|
|
updateHeights();
|
|
}
|
|
|
|
update();
|
|
updateAttachedPropertyTree(true);
|
|
|
|
signalReverted();
|
|
}
|
|
|
|
struct ValidatorVisitor
|
|
{
|
|
ValidatorVisitor(ValidatorBlock* validator)
|
|
: validator_(validator)
|
|
{
|
|
}
|
|
|
|
ScanResult operator()(PropertyRow* row, [[maybe_unused]] QPropertyTree* tree, int)
|
|
{
|
|
const void* rowHandle = row->searchHandle();
|
|
int index = 0;
|
|
int count = 0;
|
|
Serialization::TypeID typeID = row->typeId();
|
|
if (validator_->FindHandleEntries(&index, &count, rowHandle, typeID))
|
|
{
|
|
validator_->MarkAsUsed(index, count);
|
|
if (row->setValidatorEntry(index, count))
|
|
row->setLabelChanged();
|
|
}
|
|
else
|
|
{
|
|
if (row->setValidatorEntry(0, 0))
|
|
row->setLabelChanged();
|
|
}
|
|
|
|
return SCAN_CHILDREN_SIBLINGS;
|
|
}
|
|
|
|
protected:
|
|
ValidatorBlock* validator_;
|
|
};
|
|
|
|
void QPropertyTree::applyValidation()
|
|
{
|
|
if (!validatorBlock_->IsEnabled())
|
|
return;
|
|
|
|
ValidatorVisitor visitor(validatorBlock_.data());
|
|
model()->root()->scanChildren(visitor, this);
|
|
|
|
int rootFirst = 0;
|
|
int rootCount = 0;
|
|
Serialization::TypeID typeID = model()->root()->typeId();
|
|
// Gather all the items with unknown handle/type pair at root level.
|
|
validatorBlock_->MergeUnusedItemsWithRootItems(&rootFirst, &rootCount, model()->root()->searchHandle(), typeID);
|
|
model()->root()->setValidatorEntry(rootFirst, rootCount);
|
|
model()->root()->setLabelChanged();
|
|
}
|
|
|
|
void QPropertyTree::revertNoninterrupting()
|
|
{
|
|
if (!capturedRow_)
|
|
revert();
|
|
}
|
|
|
|
void QPropertyTree::apply(bool continuousUpdate)
|
|
{
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
if (!attached_.empty()) {
|
|
Objects::iterator it;
|
|
for (it = attached_.begin(); it != attached_.end(); ++it) {
|
|
PropertyIArchive ia(model_.data(), model_->root());
|
|
Serialization::SContext<QPropertyTree> treeContext(ia, this);
|
|
ia.SetFilter(config_.filter);
|
|
if (archiveContext_)
|
|
ia.SetInnerContext(archiveContext_);
|
|
signalAboutToSerialize(ia);
|
|
(*it)(ia);
|
|
signalSerialized(ia);
|
|
}
|
|
}
|
|
|
|
if (!continuousUpdate)
|
|
signalChanged();
|
|
else
|
|
signalContinuousChange();
|
|
applyTime_ = aznumeric_cast<int>(timer.elapsed());
|
|
}
|
|
|
|
void QPropertyTree::applyInplaceEditor()
|
|
{
|
|
if (widget_)
|
|
widget_->commit();
|
|
}
|
|
|
|
bool QPropertyTree::spawnWidget(PropertyRow* row, bool ignoreReadOnly)
|
|
{
|
|
if (!widget_ || widget_->row() != row || !widget_->actualWidget()->isVisible()){
|
|
interruptDrag();
|
|
setWidget(0);
|
|
PropertyRowWidget* newWidget = 0;
|
|
if ((ignoreReadOnly && row->userReadOnlyRecurse()) || !row->userReadOnly())
|
|
newWidget = row->createWidget(this);
|
|
setWidget(newWidget);
|
|
return newWidget != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QPropertyTree::addMenuHandler(PropertyRowMenuHandler* handler)
|
|
{
|
|
menuHandlers_.push_back(handler);
|
|
}
|
|
|
|
void QPropertyTree::clearMenuHandlers()
|
|
{
|
|
for (size_t i = 0; i < menuHandlers_.size(); ++i)
|
|
{
|
|
PropertyRowMenuHandler* handler = menuHandlers_[i];
|
|
delete handler;
|
|
}
|
|
menuHandlers_.clear();
|
|
}
|
|
|
|
static string quoteIfNeeded(const char* str)
|
|
{
|
|
if (!str)
|
|
return string();
|
|
if (strchr(str, ' ') != 0) {
|
|
string result;
|
|
result = "\"";
|
|
result += str;
|
|
result += "\"";
|
|
return result;
|
|
}
|
|
else {
|
|
return string(str);
|
|
}
|
|
}
|
|
|
|
bool QPropertyTree::onContextMenu(PropertyRow* r, QMenu& menu)
|
|
{
|
|
SharedPtr<PropertyRow> row(r);
|
|
PropertyTreeMenuHandler* handler = new PropertyTreeMenuHandler();
|
|
addMenuHandler(handler);
|
|
handler->tree = this;
|
|
handler->row = row;
|
|
|
|
PropertyRow::iterator it;
|
|
for (it = row->begin(); it != row->end(); ++it){
|
|
PropertyRow* child = *it;
|
|
if (child->isContainer() && child->pulledUp())
|
|
child->onContextMenu(menu, this);
|
|
}
|
|
row->onContextMenu(menu, this);
|
|
if (config_.undoEnabled){
|
|
if (!menu.isEmpty())
|
|
menu.addSeparator();
|
|
QAction* undo = menu.addAction("Undo", handler, SLOT(onMenuUndo()));
|
|
undo->setEnabled(model()->canUndo());
|
|
undo->setShortcut(QKeySequence("Ctrl+Z"));
|
|
|
|
QAction* redo = menu.addAction("Redo", handler, SLOT(onMenuRedo()));
|
|
redo->setEnabled(model()->canRedo());
|
|
redo->setShortcut(QKeySequence("Ctrl+Shift+Z"));
|
|
}
|
|
if (!menu.isEmpty())
|
|
menu.addSeparator();
|
|
|
|
if (!row->userNonCopyable()){
|
|
menu.addAction("Copy", handler, SLOT(onMenuCopy()), QKeySequence("Ctrl+C"));
|
|
|
|
if(!row->userReadOnly()){
|
|
QAction* paste = menu.addAction("Paste", handler, SLOT(onMenuPaste()), QKeySequence("Ctrl+V"));
|
|
paste->setEnabled(canBePasted(row));
|
|
}
|
|
|
|
menu.addSeparator();
|
|
}
|
|
|
|
menu.addAction("Filter...", handler, SLOT(onMenuFilter()), QKeySequence("Ctrl+F"));
|
|
QMenu* filter = menu.addMenu("Filter by");
|
|
{
|
|
string nameFilter = "#";
|
|
nameFilter += quoteIfNeeded(row->labelUndecorated());
|
|
handler->filterName = nameFilter;
|
|
filter->addAction((string("Name:\t") + nameFilter).c_str(), handler, SLOT(onMenuFilterByName()));
|
|
|
|
string valueFilter = "=";
|
|
valueFilter += quoteIfNeeded(row->valueAsString().c_str());
|
|
handler->filterValue = valueFilter;
|
|
filter->addAction((string("Value:\t") + valueFilter).c_str(), handler, SLOT(onMenuFilterByValue()));
|
|
|
|
string typeFilter = ":";
|
|
typeFilter += quoteIfNeeded(row->typeNameForFilter(this));
|
|
handler->filterType = typeFilter;
|
|
filter->addAction((string("Type:\t") + typeFilter).c_str(), handler, SLOT(onMenuFilterByType()));
|
|
}
|
|
|
|
#if 0
|
|
menu.addSeparator();
|
|
menu.addAction(TRANSLATE("Decompose"), row).connect(this, &QPropertyTree::onRowMenuDecompose);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void QPropertyTree::onRowMouseMove(PropertyRow* row, [[maybe_unused]] const QRect& rowRect, QPoint point)
|
|
{
|
|
PropertyDragEvent e;
|
|
e.tree = this;
|
|
e.pos = point;
|
|
e.start = pressPoint_;
|
|
e.totalDelta = pressDelta_;
|
|
row->onMouseDrag(e);
|
|
update();
|
|
}
|
|
|
|
|
|
bool QPropertyTree::canBePasted(PropertyRow* destination)
|
|
{
|
|
SharedPtr<PropertyRow> source;
|
|
if (!propertyRowFromClipboard(source, model_->constStrings()))
|
|
return false;
|
|
|
|
if (!smartPaste(destination, source, model(), true))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool QPropertyTree::canBePasted(const char* destinationType)
|
|
{
|
|
SharedPtr<PropertyRow> source;
|
|
if (!propertyRowFromClipboard(source, model()->constStrings()))
|
|
return false;
|
|
|
|
bool result = strcmp(source->typeName(), destinationType) == 0;
|
|
return result;
|
|
}
|
|
|
|
struct DecomposeProxy
|
|
{
|
|
DecomposeProxy(SharedPtr<PropertyRow>& row) : row(row) {}
|
|
|
|
void Serialize(IArchive& ar)
|
|
{
|
|
ar(row, "row", "Row");
|
|
}
|
|
|
|
SharedPtr<PropertyRow>& row;
|
|
};
|
|
|
|
void QPropertyTree::onRowMenuDecompose([[maybe_unused]] PropertyRow* row)
|
|
{
|
|
// SharedPtr<PropertyRow> clonedRow = row->clone();
|
|
// DecomposeProxy proxy(clonedRow);
|
|
// edit(SStruct(proxy), 0, IMMEDIATE_UPDATE, this);
|
|
}
|
|
|
|
void QPropertyTree::onModelUpdated([[maybe_unused]] const PropertyRows& rows, bool needApply)
|
|
{
|
|
if (widget_)
|
|
widget_.reset();
|
|
|
|
if (config_.immediateUpdate){
|
|
if (needApply)
|
|
apply(false);
|
|
|
|
if (autoRevert_)
|
|
revert();
|
|
else {
|
|
updateHeights();
|
|
updateAttachedPropertyTree(true);
|
|
if (!config_.immediateUpdate)
|
|
onSignalChanged();
|
|
}
|
|
}
|
|
else {
|
|
update();
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::onModelPushUndo([[maybe_unused]] PropertyTreeOperator* op, [[maybe_unused]] bool* handled)
|
|
{
|
|
signalPushUndo();
|
|
}
|
|
|
|
void QPropertyTree::onModelPushRedo([[maybe_unused]] PropertyTreeOperator* op, [[maybe_unused]] bool* handled)
|
|
{
|
|
signalPushRedo();
|
|
}
|
|
|
|
void QPropertyTree::setWidget(PropertyRowWidget* widget)
|
|
{
|
|
if (widget_){
|
|
widget_->setParent(0);
|
|
}
|
|
widget_.reset();
|
|
model()->dismissUpdate();
|
|
|
|
if (widget)
|
|
{
|
|
QWidget* actualWidget = widget->actualWidget();
|
|
if (actualWidget)
|
|
{
|
|
actualWidget->setParent(this);
|
|
actualWidget->setFocus();
|
|
}
|
|
|
|
widget_.reset(widget);
|
|
_arrangeChildren();
|
|
|
|
if (widget_)
|
|
{
|
|
widget_->showPopup();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool QPropertyTree::hasFocusOrInplaceHasFocus() const
|
|
{
|
|
if (hasFocus())
|
|
return true;
|
|
|
|
if (widget_ && widget_->actualWidget() && widget_->actualWidget()->hasFocus())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void QPropertyTree::setFilterMode(bool inFilterMode)
|
|
{
|
|
bool changed = filterMode_ != inFilterMode;
|
|
filterMode_ = inFilterMode;
|
|
|
|
if (filterMode_)
|
|
{
|
|
filterEntry_->show();
|
|
filterEntry_->setFocus();
|
|
filterEntry_->selectAll();
|
|
}
|
|
else
|
|
filterEntry_->hide();
|
|
|
|
if (changed)
|
|
{
|
|
onFilterChanged(QString());
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::startFilter(const char* filter)
|
|
{
|
|
setFilterMode(true);
|
|
filterEntry_->setText(filter);
|
|
onFilterChanged(filter);
|
|
}
|
|
|
|
void QPropertyTree::_arrangeChildren()
|
|
{
|
|
if (widget_){
|
|
PropertyRow* row = widget_->row();
|
|
if (row->visible(this)){
|
|
QWidget* w = widget_->actualWidget();
|
|
YASLI_ASSERT(w);
|
|
if (w){
|
|
QRect rect = row->widgetRect(this);
|
|
rect = QRect(rect.topLeft() - offset_ + area_.topLeft(),
|
|
rect.bottomRight() - offset_ + area_.topLeft());
|
|
w->move(rect.topLeft());
|
|
w->resize(rect.size());
|
|
if (!w->isVisible()){
|
|
w->show();
|
|
w->setFocus();
|
|
}
|
|
}
|
|
else{
|
|
//YASLI_ASSERT(w);
|
|
}
|
|
}
|
|
else{
|
|
widget_.reset();
|
|
}
|
|
}
|
|
|
|
if (filterEntry_) {
|
|
QSize size = rect().size();
|
|
const int padding = 2;
|
|
QRect pos(padding, padding, size.width() - padding * 2, filterEntry_->height());
|
|
filterEntry_->move(pos.topLeft());
|
|
filterEntry_->resize(pos.size() - QSize(scrollBar_ ? scrollBar_->width() : 0, 0));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void QPropertyTree::setExpandLevels(int levels)
|
|
{
|
|
config_.expandLevels = levels;
|
|
model()->setExpandLevels(levels);
|
|
}
|
|
|
|
PropertyRow* QPropertyTree::selectedRow()
|
|
{
|
|
const PropertyTreeModel::Selection &sel = model()->selection();
|
|
if (sel.empty())
|
|
return 0;
|
|
return model()->rowFromPath(sel.front());
|
|
}
|
|
|
|
int QPropertyTree::selectedRowCount() const
|
|
{
|
|
return (int)model()->selection().size();
|
|
}
|
|
|
|
PropertyRow* QPropertyTree::selectedRowByIndex(int index)
|
|
{
|
|
std::vector<PropertyRow*> result;
|
|
const PropertyTreeModel::Selection &sel = model()->selection();
|
|
if (size_t(index) >= sel.size())
|
|
return 0;
|
|
|
|
return model()->rowFromPath(sel[index]);
|
|
}
|
|
|
|
bool QPropertyTree::getSelectedObject(Serialization::Object* object)
|
|
{
|
|
const PropertyTreeModel::Selection &sel = model()->selection();
|
|
if (sel.empty())
|
|
return 0;
|
|
PropertyRow* row = model()->rowFromPath(sel.front());
|
|
while (row && !row->isObject())
|
|
row = row->parent();
|
|
if (!row)
|
|
return false;
|
|
|
|
if (row->isObject()) {
|
|
PropertyRowObject* obj = static_cast<PropertyRowObject*>(row);
|
|
*object = obj->object();
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QPoint QPropertyTree::_toScreen(QPoint point) const
|
|
{
|
|
QPoint pt(point.x() - offset_.x() + area_.left(),
|
|
point.y() - offset_.y() + area_.top());
|
|
|
|
return mapToGlobal(pt);
|
|
}
|
|
|
|
bool QPropertyTree::setSelectedRow(PropertyRow* row)
|
|
{
|
|
TreeSelection sel;
|
|
if (row)
|
|
sel.push_back(model()->pathFromRow(row));
|
|
if (model()->selection() != sel) {
|
|
model()->setSelection(sel);
|
|
if (row)
|
|
ensureVisible(row);
|
|
updateAttachedPropertyTree(false);
|
|
repaint();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QPropertyTree::selectByAddress(const void* addr, bool keepSelectionIfChildSelected)
|
|
{
|
|
if (model()->root()) {
|
|
PropertyRow* row = model()->root()->findByAddress(addr);
|
|
|
|
bool keepSelection = false;
|
|
if (keepSelectionIfChildSelected && row && !model()->selection().empty()) {
|
|
keepSelection = true;
|
|
TreeSelection::const_iterator it;
|
|
for (it = model()->selection().begin(); it != model()->selection().end(); ++it){
|
|
PropertyRow* selectedRow = model()->rowFromPath(*it);
|
|
if (!selectedRow)
|
|
continue;
|
|
if (!selectedRow->isChildOf(row)){
|
|
keepSelection = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!keepSelection)
|
|
return setSelectedRow(row);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool QPropertyTree::selectByAddresses(const void* const* addresses, size_t addressCount, bool keepSelectionIfChildSelected)
|
|
{
|
|
bool result = false;
|
|
if (model()->root()) {
|
|
bool keepSelection = false;
|
|
vector<PropertyRow*> rows;
|
|
for (size_t i = 0; i < addressCount; ++i) {
|
|
const void* addr = addresses[i];
|
|
PropertyRow* row = model()->root()->findByAddress(addr);
|
|
|
|
if (keepSelectionIfChildSelected && row && !model()->selection().empty()) {
|
|
keepSelection = true;
|
|
TreeSelection::const_iterator it;
|
|
for (it = model()->selection().begin(); it != model()->selection().end(); ++it){
|
|
PropertyRow* selectedRow = model()->rowFromPath(*it);
|
|
if (!selectedRow)
|
|
continue;
|
|
if (!selectedRow->isChildOf(row)){
|
|
keepSelection = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (row)
|
|
rows.push_back(row);
|
|
}
|
|
|
|
if (!keepSelection) {
|
|
TreeSelection sel;
|
|
for (size_t j = 0; j < rows.size(); ++j) {
|
|
PropertyRow* row = rows[j];
|
|
if (row)
|
|
sel.push_back(model()->pathFromRow(row));
|
|
}
|
|
if (model()->selection() != sel) {
|
|
model()->setSelection(sel);
|
|
if (!rows.empty())
|
|
ensureVisible(rows.back());
|
|
update();
|
|
result = true;
|
|
if (attachedPropertyTree_)
|
|
updateAttachedPropertyTree(false);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void QPropertyTree::setUndoEnabled(bool enabled, bool full)
|
|
{
|
|
config_.undoEnabled = enabled;
|
|
config_.fullUndo = full;
|
|
model()->setUndoEnabled(enabled);
|
|
model()->setFullUndo(full);
|
|
}
|
|
|
|
void QPropertyTree::attachPropertyTree(QPropertyTree* propertyTree)
|
|
{
|
|
if (attachedPropertyTree_)
|
|
disconnect(attachedPropertyTree_, SIGNAL(signalChanged()), this, SLOT(onAttachedTreeChanged()));
|
|
attachedPropertyTree_ = propertyTree;
|
|
if (attachedPropertyTree_)
|
|
connect(attachedPropertyTree_, SIGNAL(signalChanged()), this, SLOT(onAttachedTreeChanged()));
|
|
updateAttachedPropertyTree(true);
|
|
}
|
|
|
|
void QPropertyTree::detachPropertyTree()
|
|
{
|
|
attachPropertyTree(0);
|
|
}
|
|
|
|
void QPropertyTree::setAutoHideAttachedPropertyTree(bool autoHide)
|
|
{
|
|
autoHideAttachedPropertyTree_ = autoHide;
|
|
}
|
|
|
|
void QPropertyTree::getSelectionSerializers(Serialization::SStructs* serializers)
|
|
{
|
|
TreeSelection::const_iterator i;
|
|
for (i = model()->selection().begin(); i != model()->selection().end(); ++i){
|
|
PropertyRow* row = model()->rowFromPath(*i);
|
|
if (!row)
|
|
continue;
|
|
|
|
|
|
while (row && ((row->pulledUp() || row->pulledBefore()) || row->isLeaf())) {
|
|
row = row->parent();
|
|
}
|
|
if (outlineMode_) {
|
|
PropertyRow* topmostContainerElement = 0;
|
|
PropertyRow* r = row;
|
|
while (r && r->parent()) {
|
|
if (r->parent()->isContainer())
|
|
topmostContainerElement = r;
|
|
r = r->parent();
|
|
}
|
|
if (topmostContainerElement != 0)
|
|
row = topmostContainerElement;
|
|
}
|
|
Serialization::SStruct ser = row->serializer();
|
|
|
|
if (ser)
|
|
serializers->push_back(ser);
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::updateAttachedPropertyTree(bool revert)
|
|
{
|
|
if (attachedPropertyTree_) {
|
|
Serialization::SStructs serializers;
|
|
getSelectionSerializers(&serializers);
|
|
if (!attachedPropertyTree_->attach(serializers) && revert)
|
|
attachedPropertyTree_->revertNoninterrupting();
|
|
if (autoHideAttachedPropertyTree_)
|
|
attachedPropertyTree_->setVisible(!serializers.empty());
|
|
}
|
|
}
|
|
|
|
struct FilterVisitor
|
|
{
|
|
const QPropertyTree::RowFilter& filter_;
|
|
|
|
FilterVisitor(const QPropertyTree::RowFilter& filter)
|
|
: filter_(filter)
|
|
{
|
|
}
|
|
|
|
static void markChildrenAsBelonging(PropertyRow* row, bool belongs)
|
|
{
|
|
int count = int(row->count());
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
PropertyRow* child = row->childByIndex(i);
|
|
child->setBelongsToFilteredRow(belongs);
|
|
|
|
markChildrenAsBelonging(child, belongs);
|
|
}
|
|
}
|
|
|
|
static bool hasMatchingChildren(PropertyRow* row)
|
|
{
|
|
int numChildren = (int)row->count();
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
PropertyRow* child = row->childByIndex(i);
|
|
if (!child)
|
|
continue;
|
|
if (child->matchFilter())
|
|
return true;
|
|
if (hasMatchingChildren(child))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ScanResult operator()(PropertyRow* row, QPropertyTree* tree)
|
|
{
|
|
const char* label = row->labelUndecorated();
|
|
Serialization::string value = row->valueAsString();
|
|
|
|
bool matchFilter = filter_.match(label, filter_.NAME_VALUE, 0, 0) || filter_.match(value.c_str(), filter_.NAME_VALUE, 0, 0);
|
|
if (matchFilter && filter_.typeRelevant(filter_.NAME))
|
|
filter_.match(label, filter_.NAME, 0, 0);
|
|
if (matchFilter && filter_.typeRelevant(filter_.VALUE))
|
|
matchFilter = filter_.match(value.c_str(), filter_.VALUE, 0, 0);
|
|
if (matchFilter && filter_.typeRelevant(filter_.TYPE))
|
|
matchFilter = filter_.match(row->typeNameForFilter(tree), filter_.TYPE, 0, 0);
|
|
|
|
int numChildren = int(row->count());
|
|
if (matchFilter) {
|
|
if (row->pulledBefore() || row->pulledUp()) {
|
|
// treat pulled rows as part of parent
|
|
PropertyRow* parent = row->parent();
|
|
parent->setMatchFilter(true);
|
|
markChildrenAsBelonging(parent, true);
|
|
parent->setBelongsToFilteredRow(false);
|
|
}
|
|
else {
|
|
markChildrenAsBelonging(row, true);
|
|
row->setBelongsToFilteredRow(false);
|
|
row->setLayoutChanged();
|
|
row->setLabelChanged();
|
|
}
|
|
}
|
|
else {
|
|
bool belongs = hasMatchingChildren(row);
|
|
row->setBelongsToFilteredRow(belongs);
|
|
if (belongs) {
|
|
tree->expandRow(row, true, false);
|
|
for (int i = 0; i < numChildren; ++i) {
|
|
PropertyRow* child = row->childByIndex(i);
|
|
if (child->pulledUp())
|
|
child->setBelongsToFilteredRow(true);
|
|
}
|
|
}
|
|
else {
|
|
row->_setExpanded(false);
|
|
row->setLayoutChanged();
|
|
}
|
|
}
|
|
|
|
row->setMatchFilter(matchFilter);
|
|
return SCAN_CHILDREN_SIBLINGS;
|
|
}
|
|
|
|
protected:
|
|
string labelStart_;
|
|
};
|
|
|
|
|
|
|
|
void QPropertyTree::RowFilter::parse(const char* filter)
|
|
{
|
|
for (int i = 0; i < NUM_TYPES; ++i) {
|
|
start[i].clear();
|
|
substrings[i].clear();
|
|
tillEnd[i] = false;
|
|
}
|
|
|
|
YASLI_ESCAPE(filter != 0, return);
|
|
|
|
vector<char> filterBuf(filter, filter + strlen(filter) + 1);
|
|
for (size_t i = 0; i < filterBuf.size(); ++i)
|
|
filterBuf[i] = tolower(filterBuf[i]);
|
|
|
|
const char* str = &filterBuf[0];
|
|
|
|
Type type = NAME_VALUE;
|
|
while (true)
|
|
{
|
|
bool fromStart = false;
|
|
while (*str == '^') {
|
|
fromStart = true;
|
|
++str;
|
|
}
|
|
|
|
const char* tokenStart = str;
|
|
|
|
if (*str == '\"')
|
|
{
|
|
++str;
|
|
while (*str != '\0' && *str != '\"')
|
|
++str;
|
|
}
|
|
else
|
|
{
|
|
while (*str != '\0' && *str != ' ' && *str != '=' && *str != ':' && *str != '#')
|
|
++str;
|
|
}
|
|
if (str != tokenStart) {
|
|
if (*tokenStart == '\"' && *str == '\"') {
|
|
start[type].assign(tokenStart + 1, str);
|
|
tillEnd[type] = true;
|
|
++str;
|
|
}
|
|
else
|
|
{
|
|
if (fromStart)
|
|
start[type].assign(tokenStart, str);
|
|
else
|
|
substrings[type].push_back(string(tokenStart, str));
|
|
}
|
|
}
|
|
while (*str == ' ')
|
|
++str;
|
|
if (*str == '#') {
|
|
type = NAME;
|
|
++str;
|
|
}
|
|
else if (*str == '=') {
|
|
type = VALUE;
|
|
++str;
|
|
}
|
|
else if (*str == ':') {
|
|
type = TYPE;
|
|
++str;
|
|
}
|
|
else if (*str == '\0')
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool QPropertyTree::RowFilter::match(const char* textOriginal, Type type, size_t* matchStart, size_t* matchEnd) const
|
|
{
|
|
YASLI_ESCAPE(textOriginal, return false);
|
|
|
|
char* text;
|
|
{
|
|
size_t textLen = strlen(textOriginal);
|
|
text = (char*)alloca((textLen + 1));
|
|
memcpy(text, textOriginal, (textLen + 1));
|
|
for (char* p = text; *p; ++p)
|
|
*p = tolower(*p);
|
|
}
|
|
|
|
const string &startForType = this->start[type];
|
|
if (tillEnd[type]){
|
|
if (startForType == text) {
|
|
if (matchStart)
|
|
*matchStart = 0;
|
|
if (matchEnd)
|
|
*matchEnd = startForType.size();
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
const vector<string> &substringsForType = this->substrings[type];
|
|
|
|
const char* startPos = text;
|
|
|
|
if (matchStart)
|
|
*matchStart = 0;
|
|
if (matchEnd)
|
|
*matchEnd = 0;
|
|
if (!startForType.empty()) {
|
|
if (strncmp(text, startForType.c_str(), startForType.size()) != 0){
|
|
//_freea(text);
|
|
return false;
|
|
}
|
|
if (matchEnd)
|
|
*matchEnd = startForType.size();
|
|
startPos += startForType.size();
|
|
}
|
|
|
|
size_t numSubstrings = substringsForType.size();
|
|
for (size_t i = 0; i < numSubstrings; ++i) {
|
|
const char* substr = strstr(startPos, substringsForType[i].c_str());
|
|
if (!substr){
|
|
return false;
|
|
}
|
|
startPos += substringsForType[i].size();
|
|
if (matchStart && i == 0 && startForType.empty()) {
|
|
*matchStart = substr - text;
|
|
}
|
|
if (matchEnd)
|
|
*matchEnd = substr - text + substringsForType[i].size();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QPropertyTree::onFilterChanged([[maybe_unused]] const QString& text)
|
|
{
|
|
QByteArray arr = filterEntry_->text().toLocal8Bit();
|
|
const char* filterStr = filterMode_ ? arr.data() : "";
|
|
rowFilter_.parse(filterStr);
|
|
FilterVisitor visitor(rowFilter_);
|
|
model()->root()->scanChildrenBottomUp(visitor, this);
|
|
updateHeights();
|
|
}
|
|
|
|
void QPropertyTree::drawFilteredString(QPainter& p, const wchar_t* text, RowFilter::Type type, const QFont* font, const QRect& rect, const QColor& textColor, bool pathEllipsis, bool center) const
|
|
{
|
|
int textLen = (int)wcslen(text);
|
|
|
|
if (textLen == 0)
|
|
return;
|
|
|
|
string textStr(fromWideChar(text));
|
|
QString str(textStr.c_str());
|
|
QFontMetrics fm(*font);
|
|
QRect textRect = rect;
|
|
int alignment;
|
|
if (center)
|
|
alignment = Qt::AlignHCenter | Qt::AlignVCenter;
|
|
else {
|
|
if (pathEllipsis && textRect.width() < fm.horizontalAdvance(str))
|
|
alignment = Qt::AlignRight | Qt::AlignVCenter;
|
|
else
|
|
alignment = Qt::AlignLeft | Qt::AlignVCenter;
|
|
}
|
|
|
|
if (filterMode_) {
|
|
size_t hiStart = 0;
|
|
size_t hiEnd = 0;
|
|
bool matched = rowFilter_.match(textStr.c_str(), type, &hiStart, &hiEnd) && hiStart != hiEnd;
|
|
if (!matched && (type == RowFilter::NAME || type == RowFilter::VALUE))
|
|
matched = rowFilter_.match(textStr.c_str(), RowFilter::NAME_VALUE, &hiStart, &hiEnd);
|
|
if (matched && hiStart != hiEnd) {
|
|
QRectF boxFull;
|
|
QRectF boxStart;
|
|
QRectF boxEnd;
|
|
|
|
boxFull = fm.boundingRect(textRect, alignment, str);
|
|
|
|
if (hiStart > 0)
|
|
boxStart = fm.boundingRect(textRect, alignment, str.left(hiStart));
|
|
else {
|
|
boxStart = fm.boundingRect(textRect, alignment, str);
|
|
boxStart.setWidth(0.0f);
|
|
}
|
|
boxEnd = fm.boundingRect(textRect, alignment, str.left(hiEnd));
|
|
|
|
QColor highlightColor, highlightBorderColor;
|
|
{
|
|
highlightColor = palette().color(QPalette::Highlight);
|
|
int h, s, v;
|
|
highlightColor.getHsv(&h, &s, &v);
|
|
h -= 175;
|
|
if (h < 0)
|
|
h += 360;
|
|
highlightColor.setHsv(h, min(255, int(s * 1.33f)), v, 255);
|
|
highlightBorderColor.setHsv(h, aznumeric_cast<int>(s * 0.5f), v, 255);
|
|
}
|
|
|
|
int left = int(boxFull.left() + boxStart.width()) - 1;
|
|
int top = int(boxFull.top());
|
|
int right = int(boxFull.left() + boxEnd.width());
|
|
int bottom = int(boxFull.top() + boxEnd.height());
|
|
QRect highlightRect(left, top, right - left, bottom - top);
|
|
QBrush br(highlightColor);
|
|
p.setBrush(br);
|
|
p.setPen(highlightBorderColor);
|
|
bool oldAntialiasing = p.renderHints().testFlag(QPainter::Antialiasing);
|
|
p.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
QRect intersectedHighlightRect = rect.intersected(highlightRect);
|
|
p.drawRoundedRect(intersectedHighlightRect, 4.0, 4.0);
|
|
p.setRenderHint(QPainter::Antialiasing, oldAntialiasing);
|
|
}
|
|
}
|
|
|
|
QBrush textBrush(textColor);
|
|
p.setBrush(textBrush);
|
|
p.setPen(textColor);
|
|
QFont previousFont = p.font();
|
|
p.setFont(*font);
|
|
p.drawText(textRect, alignment, str, 0);
|
|
p.setFont(previousFont);
|
|
}
|
|
|
|
void QPropertyTree::_drawRowLabel(QPainter& p, const wchar_t* text, const QFont* font, const QRect& rect, const QColor& textColor) const
|
|
{
|
|
drawFilteredString(p, text, RowFilter::NAME, font, rect, textColor, false, false);
|
|
}
|
|
|
|
void QPropertyTree::_drawRowValue(QPainter& p, const wchar_t* text, const QFont* font, const QRect& rect, const QColor& textColor, bool pathEllipsis, bool center) const
|
|
{
|
|
drawFilteredString(p, text, RowFilter::VALUE, font, rect, textColor, pathEllipsis, center);
|
|
}
|
|
|
|
struct DrawVisitor
|
|
{
|
|
DrawVisitor(QPainter& painter, const QRect& area, int scrollOffset, bool selectionPass)
|
|
: area_(area)
|
|
, painter_(painter)
|
|
, offset_(0)
|
|
, scrollOffset_(scrollOffset)
|
|
, lastParent_(0)
|
|
, selectionPass_(selectionPass)
|
|
{}
|
|
|
|
ScanResult operator()(PropertyRow* row, QPropertyTree* tree, int index)
|
|
{
|
|
if (row->visible(tree) && ((!row->parent() || (row->parent()->expanded() && !lastParent_)) || row->pulledUp())){
|
|
QRect rect = row->rect();
|
|
if (rect.top() > scrollOffset_ + area_.height())
|
|
lastParent_ = row->parent();
|
|
|
|
int height = row->heightIncludingChildren();
|
|
if ((height == USHRT_MAX || rect.top() + height > scrollOffset_) && rect.width() > 0)
|
|
row->drawRow(painter_, tree, index, selectionPass_);
|
|
|
|
return SCAN_CHILDREN_SIBLINGS;
|
|
}
|
|
else
|
|
return SCAN_SIBLINGS;
|
|
}
|
|
|
|
protected:
|
|
QPainter& painter_;
|
|
QRect area_;
|
|
int offset_;
|
|
int scrollOffset_;
|
|
PropertyRow* lastParent_;
|
|
bool selectionPass_;
|
|
};
|
|
|
|
QSize QPropertyTree::sizeHint() const
|
|
{
|
|
if (sizeToContent_)
|
|
return minimumSize();
|
|
else
|
|
return sizeHint_;
|
|
}
|
|
|
|
bool QPropertyTree::event(QEvent* ev)
|
|
{
|
|
if (ev->type() == QEvent::ShortcutOverride)
|
|
{
|
|
if (!widget_)
|
|
{
|
|
PropertyRow* row = model()->focusedRow();
|
|
if (row)
|
|
{
|
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
|
|
bool keyWillBeProcessed = false;
|
|
|
|
int modifiedKey = keyEvent->key() | keyEvent->modifiers();
|
|
switch (modifiedKey)
|
|
{
|
|
case (Qt::Key_F | Qt::CTRL):
|
|
case Qt::Key_Escape:
|
|
keyWillBeProcessed = true;
|
|
break;
|
|
|
|
default:
|
|
keyWillBeProcessed = rowProcessesKey(row, keyEvent);
|
|
break;
|
|
}
|
|
|
|
if (keyWillBeProcessed)
|
|
{
|
|
ev->accept();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return QWidget::event(ev);
|
|
}
|
|
|
|
void QPropertyTree::paintEvent([[maybe_unused]] QPaintEvent* ev)
|
|
{
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
QPainter painter(this);
|
|
QRect clientRect = this->rect();
|
|
|
|
int clientHeight = clientRect.height();
|
|
backgroundColor_ = palette().color(QPalette::Window);
|
|
painter.fillRect(clientRect, QBrush(backgroundColor_));
|
|
|
|
painter.translate(-offset_.x(), -offset_.y());
|
|
|
|
if (dragController_->captured())
|
|
dragController_->drawUnder(painter);
|
|
|
|
painter.translate(area_.left(), area_.top());
|
|
|
|
if (model()->root()) {
|
|
DrawVisitor selectionOp(painter, area_, offset_.y(), true);
|
|
model()->root()->scanChildren(selectionOp, this);
|
|
|
|
DrawVisitor op(painter, area_, offset_.y(), false);
|
|
op(model()->root(), this, 0);
|
|
model()->root()->scanChildren(op, this);
|
|
}
|
|
|
|
painter.translate(-area_.left(), -area_.top());
|
|
painter.translate(offset_.x(), offset_.y());
|
|
|
|
//painter.setClipRect(rect());
|
|
|
|
if (size_.y() > clientHeight)
|
|
{
|
|
const int shadowHeight = int(_defaultRowHeight() * 0.3f);
|
|
QColor color1(0, 0, 0, 0);
|
|
QColor color2(0, 0, 0, 96);
|
|
|
|
int visibleAreaWidth = area_.width() + 5;
|
|
|
|
QRect upperRect(rect().left() + 1, rect().top(), visibleAreaWidth - 2, shadowHeight);
|
|
QLinearGradient upperGradient(upperRect.left(), upperRect.top(), upperRect.left(), upperRect.bottom());
|
|
upperGradient.setColorAt(0.0f, color2);
|
|
upperGradient.setColorAt(1.0f, color1);
|
|
painter.fillRect(upperRect, QBrush(upperGradient));
|
|
|
|
QLinearGradient upperEdgeGradient(upperRect.left(), upperRect.top(), upperRect.left(), upperRect.bottom() + shadowHeight);
|
|
upperEdgeGradient.setColorAt(0.0f, color2);
|
|
upperEdgeGradient.setColorAt(1.0f, color1);
|
|
painter.fillRect(QRect(rect().left(), rect().top(), 1, shadowHeight * 2 + 1), QBrush(upperEdgeGradient));
|
|
painter.fillRect(QRect(visibleAreaWidth - 1, rect().top(), 1, shadowHeight * 2 + 1), QBrush(upperEdgeGradient));
|
|
|
|
QRect lowerRect(rect().left() + 1, rect().bottom() - shadowHeight / 2, visibleAreaWidth - 2, shadowHeight / 2 + 1);
|
|
QLinearGradient lowerGradient(lowerRect.left(), lowerRect.top(), lowerRect.left(), lowerRect.bottom());
|
|
lowerGradient.setColorAt(0.0f, color1);
|
|
lowerGradient.setColorAt(1.0f, color2);
|
|
QBrush lowerBrush(lowerGradient);
|
|
painter.fillRect(lowerRect, lowerGradient);
|
|
|
|
QLinearGradient lowerEdgeGradient(lowerRect.left(), lowerRect.top() - shadowHeight, lowerRect.left(), lowerRect.bottom());
|
|
lowerEdgeGradient.setColorAt(0.0f, color1);
|
|
lowerEdgeGradient.setColorAt(1.0f, color2);
|
|
painter.fillRect(QRect(rect().left(), rect().bottom() - shadowHeight * 2, 1, shadowHeight * 2 + 1), QBrush(lowerEdgeGradient));
|
|
painter.fillRect(QRect(visibleAreaWidth - 1, rect().bottom() - shadowHeight * 2, 1, shadowHeight * 2 + 1), QBrush(lowerEdgeGradient));
|
|
}
|
|
|
|
if (dragController_->captured()) {
|
|
painter.translate(-offset_);
|
|
dragController_->drawOver(painter);
|
|
painter.translate(offset_);
|
|
}
|
|
else{
|
|
// if(model()->focusedRow() != 0 && model()->focusedRow()->isRoot() && tree_->hasFocus()){
|
|
// clientRect.left += 2; clientRect.top += 2;
|
|
// clientRect.right -= 2; clientRect.bottom -= 2;
|
|
// DrawFocusRect(dc, &clientRect);
|
|
// }
|
|
}
|
|
paintTime_ = aznumeric_cast<int>(timer.elapsed());
|
|
}
|
|
|
|
QPropertyTree::HitTest QPropertyTree::hitTest(PropertyRow* row, const QPoint& pointInWindowSpace, const QRect& rowRect)
|
|
{
|
|
QPoint point = pointToRootSpace(pointInWindowSpace);
|
|
|
|
if (!row->hasVisibleChildren(this) && row->plusRect(this).contains(point))
|
|
return TREE_HIT_PLUS;
|
|
|
|
if (row->textRect(this).contains(point))
|
|
return TREE_HIT_TEXT;
|
|
|
|
if (rowRect.contains(point))
|
|
return TREE_HIT_ROW;
|
|
|
|
return TREE_HIT_NONE;
|
|
|
|
}
|
|
|
|
PropertyRow* QPropertyTree::rowByPoint(const QPoint& pt)
|
|
{
|
|
if (!model_->root())
|
|
return 0;
|
|
if (!area_.contains(pt))
|
|
return 0;
|
|
return model_->root()->hit(this, pointToRootSpace(pt));
|
|
}
|
|
|
|
QPoint QPropertyTree::pointToRootSpace(const QPoint& point) const
|
|
{
|
|
return QPoint(point.x() + offset_.x() - area_.left(), point.y() + offset_.y() - area_.top());
|
|
}
|
|
|
|
QPoint QPropertyTree::pointFromRootSpace(const QPoint& point) const
|
|
{
|
|
return QPoint(point.x() - offset_.x() + area_.left(), point.y() - offset_.y() + area_.top());
|
|
}
|
|
|
|
|
|
void QPropertyTree::moveEvent(QMoveEvent* ev)
|
|
{
|
|
QWidget::moveEvent(ev);
|
|
}
|
|
|
|
void QPropertyTree::resizeEvent(QResizeEvent* ev)
|
|
{
|
|
QWidget::resizeEvent(ev);
|
|
|
|
updateHeights();
|
|
}
|
|
|
|
void QPropertyTree::mousePressEvent(QMouseEvent* ev)
|
|
{
|
|
//QWidget::mousePressEvent(ev);
|
|
setFocus(Qt::MouseFocusReason);
|
|
|
|
if (ev->button() == Qt::LeftButton)
|
|
{
|
|
PropertyRow* row = rowByPoint(ev->pos());
|
|
if (row && !row->isSelectable())
|
|
row = row->parent();
|
|
if (row){
|
|
if (onRowLMBDown(row, row->rect(), pointToRootSpace(ev->pos()), ev->modifiers().testFlag(Qt::ControlModifier), ev->modifiers().testFlag(Qt::ShiftModifier))) {
|
|
capturedRow_ = row;
|
|
lastStillPosition_ = pointToRootSpace(ev->pos());
|
|
}
|
|
else if (!dragCheckMode_){
|
|
row = rowByPoint(ev->pos());
|
|
PropertyRow* draggedRow = row;
|
|
while (draggedRow && (!draggedRow->isSelectable() || draggedRow->pulledUp() || draggedRow->pulledBefore()))
|
|
draggedRow = draggedRow->parent();
|
|
if (draggedRow && !draggedRow->userReadOnly() && !widget_){
|
|
dragController_->beginDrag(row, draggedRow, ev->globalPos());
|
|
}
|
|
}
|
|
}
|
|
update();
|
|
}
|
|
else if (ev->button() == Qt::RightButton)
|
|
{
|
|
QPoint point = ev->pos();
|
|
PropertyRow* row = rowByPoint(point);
|
|
if (row){
|
|
model()->setFocusedRow(row);
|
|
update();
|
|
|
|
onRowRMBDown(row, row->rect(), _toScreen(pointToRootSpace(point)));
|
|
}
|
|
else{
|
|
QRect rect = this->rect();
|
|
onRowRMBDown(model()->root(), rect, _toScreen(pointToRootSpace(point)));
|
|
}
|
|
}
|
|
else if (ev->button() == Qt::MiddleButton)
|
|
{
|
|
QPoint point = ev->pos();
|
|
PropertyRow* row = rowByPoint(point);
|
|
if (row){
|
|
switch (hitTest(row, point, row->rect())){
|
|
case TREE_HIT_PLUS:
|
|
break;
|
|
case TREE_HIT_NONE:
|
|
default:
|
|
model()->setFocusedRow(row);
|
|
update();
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::mouseReleaseEvent(QMouseEvent* ev)
|
|
{
|
|
QWidget::mouseReleaseEvent(ev);
|
|
|
|
if (ev->button() == Qt::LeftButton)
|
|
{
|
|
if (dragController_->captured()){
|
|
if (dragController_->drop(QCursor::pos()))
|
|
updateHeights();
|
|
else
|
|
update();
|
|
}
|
|
if (dragCheckMode_) {
|
|
dragCheckMode_ = false;
|
|
}
|
|
else {
|
|
QPoint point = ev->pos();
|
|
if (capturedRow_){
|
|
QRect rowRect = capturedRow_->rect();
|
|
onRowLMBUp(capturedRow_, rowRect, pointToRootSpace(ev->pos()));
|
|
mouseStillTimer_->stop();
|
|
capturedRow_ = 0;
|
|
update();
|
|
}
|
|
}
|
|
}
|
|
else if (ev->button() == Qt::RightButton)
|
|
{
|
|
|
|
}
|
|
|
|
unsetCursor();
|
|
}
|
|
|
|
void QPropertyTree::focusInEvent(QFocusEvent* ev)
|
|
{
|
|
QWidget::focusInEvent(ev);
|
|
widget_.reset();
|
|
}
|
|
|
|
void QPropertyTree::keyPressEvent(QKeyEvent* ev)
|
|
{
|
|
// NOTE: if you add any new key processing in here, make sure you update QPropertyTree::event() to handle
|
|
// it in the ShortcutOverride event. Otherwise, MainWindow/other shortcuts might take priority and eat it before
|
|
// it reaches this function.
|
|
|
|
if (ev->key() == Qt::Key_F && ev->modifiers() == Qt::CTRL) {
|
|
setFilterMode(true);
|
|
}
|
|
|
|
if (filterMode_) {
|
|
if (ev->key() == Qt::Key_Escape && ev->modifiers() == Qt::NoModifier) {
|
|
setFilterMode(false);
|
|
}
|
|
}
|
|
|
|
bool result = false;
|
|
if (!widget_) {
|
|
PropertyRow* row = model()->focusedRow();
|
|
if (row)
|
|
onRowKeyDown(row, ev);
|
|
}
|
|
update();
|
|
if (!result)
|
|
QWidget::keyPressEvent(ev);
|
|
}
|
|
|
|
|
|
void QPropertyTree::mouseDoubleClickEvent(QMouseEvent* ev)
|
|
{
|
|
QWidget::mouseDoubleClickEvent(ev);
|
|
|
|
QPoint point = ev->pos();
|
|
PropertyRow* row = rowByPoint(point);
|
|
if (row){
|
|
PropertyActivationEvent e;
|
|
e.tree = this;
|
|
e.force = true;
|
|
e.reason = e.REASON_DOUBLECLICK;
|
|
PropertyRow* nonPulledParent = row;
|
|
while (nonPulledParent && nonPulledParent->pulledUp())
|
|
nonPulledParent = nonPulledParent->parent();
|
|
|
|
if (row->widgetRect(this).contains(pointToRootSpace(point))){
|
|
if (!row->onActivate(e))
|
|
toggleRow(nonPulledParent);
|
|
}
|
|
else if (!toggleRow(row)) {
|
|
if (!row->onActivate(e))
|
|
if (!toggleRow(nonPulledParent)) {
|
|
// activate first visible inline row
|
|
for (size_t i = 0; i < row->count(); ++i) {
|
|
PropertyRow* child = row->childByIndex(i);
|
|
if (child && child->pulledUp() && child->visible(this)) {
|
|
child->onActivate(e);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::onMouseStillTimeout()
|
|
{
|
|
onMouseStill(mapFromGlobal(QCursor::pos()));
|
|
}
|
|
|
|
void QPropertyTree::onMouseStill(QPoint point)
|
|
{
|
|
if (capturedRow_) {
|
|
PropertyDragEvent e;
|
|
e.tree = this;
|
|
e.pos = point;
|
|
e.start = pressPoint_;
|
|
|
|
capturedRow_->onMouseStill(e);
|
|
lastStillPosition_ = e.pos;
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::flushAggregatedMouseEvents()
|
|
{
|
|
if (aggregatedMouseEventCount_ > 0) {
|
|
bool gotPendingEvent = aggregatedMouseEventCount_ > 1;
|
|
aggregatedMouseEventCount_ = 0;
|
|
if (gotPendingEvent && lastMouseMoveEvent_.data())
|
|
mouseMoveEvent(lastMouseMoveEvent_.data());
|
|
}
|
|
}
|
|
|
|
void QPropertyTree::mouseMoveEvent(QMouseEvent* ev)
|
|
{
|
|
if (ev->type() == QEvent::MouseMove && aggregateMouseEvents_) {
|
|
lastMouseMoveEvent_.reset(new QMouseEvent(QEvent::MouseMove, ev->localPos(), ev->windowPos(), ev->screenPos(), ev->button(), ev->buttons(), ev->modifiers()));
|
|
ev = lastMouseMoveEvent_.data();
|
|
++aggregatedMouseEventCount_;
|
|
if (aggregatedMouseEventCount_ > 1)
|
|
return;
|
|
}
|
|
|
|
QCursor newCursor = QCursor(Qt::ArrowCursor);
|
|
QString newToolTip;
|
|
if (dragController_->captured() && !ev->buttons().testFlag(Qt::LeftButton))
|
|
dragController_->interrupt();
|
|
if (dragController_->captured()){
|
|
QPoint pos = QCursor::pos();
|
|
if (dragController_->dragOn(pos)) {
|
|
// SetCapture
|
|
}
|
|
update();
|
|
}
|
|
else{
|
|
QPoint point = ev->pos();
|
|
PropertyRow* row = rowByPoint(point);
|
|
if (row && dragCheckMode_ && row->widgetRect(this).contains(pointToRootSpace(point))) {
|
|
row->onMouseDragCheck(this, dragCheckValue_);
|
|
}
|
|
else if (capturedRow_){
|
|
onRowMouseMove(capturedRow_, QRect(), pointToRootSpace(point));
|
|
if (config_.sliderUpdateDelay >= 0 && !mouseStillTimer_->isActive())
|
|
mouseStillTimer_->start(config_.sliderUpdateDelay);
|
|
|
|
if (cursor().shape() == Qt::BlankCursor)
|
|
{
|
|
pressDelta_ += pointToRootSpace(ev->pos()) - pressPoint_;
|
|
pointerMovedSincePress_ = true;
|
|
AzQtComponents::SetCursorPos(mapToGlobal(pointFromRootSpace(pressPoint_)));
|
|
}
|
|
else
|
|
{
|
|
pressDelta_ = pointToRootSpace(ev->pos()) - pressPoint_;
|
|
}
|
|
}
|
|
|
|
PropertyRow* hoverRow = row;
|
|
if (capturedRow_)
|
|
hoverRow = capturedRow_;
|
|
PropertyHoverInfo hover;
|
|
if (hoverRow) {
|
|
QPoint pointInRootSpace = pointToRootSpace(point);
|
|
if (hoverRow->getHoverInfo(&hover, pointInRootSpace, this)) {
|
|
newCursor = hover.cursor;
|
|
newToolTip = hover.toolTip;
|
|
|
|
PropertyRow* tooltipRow = hoverRow;
|
|
while (newToolTip.isEmpty() && tooltipRow->parent() && (tooltipRow->pulledUp() || tooltipRow->pulledBefore())) {
|
|
// check if parent of inlined property has a tooltip instead
|
|
tooltipRow = tooltipRow->parent();
|
|
if (tooltipRow->getHoverInfo(&hover, pointInRootSpace, this))
|
|
newToolTip = hover.toolTip;
|
|
}
|
|
}
|
|
|
|
if (hoverRow->validatorWarningIconRect(this).contains(pointToRootSpace(point))) {
|
|
newCursor = QCursor(Qt::PointingHandCursor);
|
|
newToolTip = "Jump to next warning";
|
|
}
|
|
if (hoverRow->validatorErrorIconRect(this).contains(pointToRootSpace(point))) {
|
|
newCursor = QCursor(Qt::PointingHandCursor);
|
|
newToolTip = "Jump to next error";
|
|
}
|
|
}
|
|
}
|
|
setCursor(newCursor);
|
|
if (toolTip() != newToolTip)
|
|
setToolTip(newToolTip);
|
|
if (newToolTip.isEmpty())
|
|
QToolTip::hideText();
|
|
}
|
|
|
|
void QPropertyTree::wheelEvent(QWheelEvent* ev)
|
|
{
|
|
QWidget::wheelEvent(ev);
|
|
|
|
float delta = ev->angleDelta().ry() / 360.0f;
|
|
if (ev->modifiers() & Qt::CTRL) {
|
|
if (delta > 0)
|
|
zoomLevel_ += 1;
|
|
else
|
|
zoomLevel_ -= 1;
|
|
if (zoomLevel_ < 8)
|
|
zoomLevel_ = 8;
|
|
if (zoomLevel_ > 30)
|
|
zoomLevel_ = 30;
|
|
float scale = zoomLevel_ * 0.1f;
|
|
QFont font;
|
|
font.setPointSizeF(font.pointSizeF() * scale);
|
|
setFont(font);
|
|
font.setBold(true);
|
|
boldFont_ = font;
|
|
|
|
updateHeights(true);
|
|
}
|
|
else {
|
|
if (scrollBar_->isVisible() && scrollBar_->isEnabled())
|
|
scrollBar_->setValue(scrollBar_->value() + -ev->angleDelta().y());
|
|
}
|
|
}
|
|
|
|
bool QPropertyTree::toggleRow(PropertyRow* row)
|
|
{
|
|
if (!row->canBeToggled(this))
|
|
return false;
|
|
expandRow(row, !row->expanded());
|
|
updateHeights();
|
|
return true;
|
|
}
|
|
|
|
bool QPropertyTree::_isDragged(const PropertyRow* row) const
|
|
{
|
|
if (!dragController_->dragging())
|
|
return false;
|
|
if (dragController_->draggedRow() == row)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool QPropertyTree::_isCapturedRow(const PropertyRow* row) const
|
|
{
|
|
return capturedRow_ == row;
|
|
}
|
|
|
|
void QPropertyTree::setValueColumnWidth(float valueColumnWidth)
|
|
{
|
|
if (style_->valueColumnWidth != valueColumnWidth)
|
|
{
|
|
style_->valueColumnWidth = valueColumnWidth;
|
|
updateHeights();
|
|
update();
|
|
}
|
|
}
|
|
|
|
QPropertyTree::QPropertyTree(const QPropertyTree&)
|
|
{
|
|
}
|
|
|
|
|
|
QPropertyTree& QPropertyTree::operator=(const QPropertyTree&)
|
|
{
|
|
return *this;
|
|
}
|
|
|
|
void QPropertyTree::onAttachedTreeChanged()
|
|
{
|
|
revert();
|
|
}
|
|
|
|
struct ValidatorIconVisitor
|
|
{
|
|
ScanResult operator()(PropertyRow* row, QPropertyTree* tree, int)
|
|
{
|
|
row->resetValidatorIcons();
|
|
if (row->validatorCount()) {
|
|
bool hasErrors = false;
|
|
bool hasWarnings = false;
|
|
if (const ValidatorEntry* validatorEntries = tree->_validatorBlock()->GetEntry(row->validatorIndex(), row->validatorCount())) {
|
|
for (int i = 0; i < row->validatorCount(); ++i) {
|
|
const ValidatorEntry* validatorEntry = validatorEntries + i;
|
|
if (validatorEntry->type == VALIDATOR_ENTRY_ERROR)
|
|
hasErrors = true;
|
|
else if (validatorEntry->type == VALIDATOR_ENTRY_WARNING)
|
|
hasWarnings = true;
|
|
}
|
|
}
|
|
|
|
if (hasErrors || hasWarnings)
|
|
{
|
|
PropertyRow* lastClosedParent = 0;
|
|
PropertyRow* current = row->parent();
|
|
bool lastWasPulled = row->pulledUp() || row->pulledBefore();
|
|
while (current && current->parent()) {
|
|
if (!current->expanded() && !lastWasPulled && current->visible(tree))
|
|
lastClosedParent = current;
|
|
lastWasPulled = current->pulledUp() || current->pulledBefore();
|
|
current = current->parent();
|
|
}
|
|
if (lastClosedParent)
|
|
lastClosedParent->addValidatorIcons(hasWarnings, hasErrors);
|
|
}
|
|
}
|
|
return SCAN_CHILDREN_SIBLINGS;
|
|
}
|
|
};
|
|
|
|
void QPropertyTree::updateValidatorIcons()
|
|
{
|
|
if (!validatorBlock_->IsEnabled())
|
|
return;
|
|
ValidatorIconVisitor op;
|
|
model()->root()->scanChildren(op, this);
|
|
model()->root()->setLabelChangedToChildren();
|
|
}
|
|
|
|
void QPropertyTree::setTreeStyle(const QPropertyTreeStyle& style)
|
|
{
|
|
*style_ = style;
|
|
updateHeights(true);
|
|
}
|
|
|
|
void QPropertyTree::setPackCheckboxes(bool pack)
|
|
{
|
|
style_->packCheckboxes = pack;
|
|
updateHeights(true);
|
|
}
|
|
|
|
bool QPropertyTree::packCheckboxes() const
|
|
{
|
|
return style_->packCheckboxes;
|
|
}
|
|
|
|
void QPropertyTree::setCompact(bool compact)
|
|
{
|
|
style_->compact = compact;
|
|
update();
|
|
}
|
|
|
|
bool QPropertyTree::compact() const
|
|
{
|
|
return style_->compact;
|
|
}
|
|
|
|
void QPropertyTree::setRowSpacing(float rowSpacing)
|
|
{
|
|
style_->rowSpacing = rowSpacing;
|
|
}
|
|
|
|
float QPropertyTree::rowSpacing() const
|
|
{
|
|
return style_->rowSpacing;
|
|
}
|
|
|
|
float QPropertyTree::valueColumnWidth() const
|
|
{
|
|
return style_->valueColumnWidth;
|
|
}
|
|
|
|
void QPropertyTree::setFullRowMode(bool fullRowMode)
|
|
{
|
|
style_->fullRowMode = fullRowMode;
|
|
update();
|
|
}
|
|
|
|
bool QPropertyTree::fullRowMode() const
|
|
{
|
|
return style_->fullRowMode;
|
|
}
|
|
|
|
bool QPropertyTree::containsErrors() const
|
|
{
|
|
return validatorBlock_->ContainsErrors();
|
|
}
|
|
|
|
void QPropertyTree::focusFirstError()
|
|
{
|
|
jumpToNextHiddenValidatorIssue(true, model()->root());
|
|
}
|
|
|
|
void QPropertyTree::setBackgroundColor(const QColor& backgroundColor)
|
|
{
|
|
backgroundColor_ = backgroundColor;
|
|
}
|
|
|
|
#include <QPropertyTree/moc_QPropertyTree.cpp>
|
|
#include <QPropertyTree/moc_PropertyTreeMenuHandler.cpp>
|
|
|
|
// vim:ts=4 sw=4:
|