!XT (BREAK-16) (Sandbox) Remove double-newlines at the end of files.
[CRYENGINE.git] / Code / Sandbox / Plugins / EditorCommon / Serialization / PropertyTree / PropertyTree.cpp
blob870486a5575e3802f8f14129d58748a8f6464701
1 /**
2 * yasli - Serialization Library.
3 * Copyright (C) 2007-2013 Evgeny Andreeshchev <eugene.andreeshchev@gmail.com>
4 * Alexander Kotliar <alexander.kotliar@gmail.com>
5 *
6 * This code is distributed under the MIT License:
7 * http://www.opensource.org/licenses/MIT
8 */
10 #include "StdAfx.h"
11 #include <CrySerialization/yasli/Pointers.h>
12 #include <CrySerialization/yasli/Archive.h>
13 #include <CrySerialization/yasli/BinArchive.h>
14 #include <CrySerialization/yasli/Pointers.h>
15 #if YASLI_INCLUDE_PROPERTY_TREE_CONFIG_LOCAL
16 #include <CrySerialization/yasli/ConfigLocal.h>
17 #endif
18 #include "PropertyTree.h"
19 #include "IDrawContext.h"
20 #include "Serialization.h"
21 #include "PropertyTreeModel.h"
22 #include "PropertyTreeStyle.h"
23 #include "ValidatorBlock.h"
25 #include <CrySerialization/yasli/ClassFactory.h>
27 #include "PropertyOArchive.h"
28 #include "PropertyIArchive.h"
29 #include "Unicode.h"
31 #include <limits.h>
32 #include "PropertyTreeMenuHandler.h"
33 #include "IUIFacade.h"
34 #include "IMenu.h"
36 #include "MathUtils.h"
38 #include "PropertyRowObject.h"
40 using yasli::Serializers;
41 using std::vector;
43 // ---------------------------------------------------------------------------
45 TreeConfig::TreeConfig()
46 : immediateUpdate(true)
47 , valueColumnWidth(.5f)
48 , filter(YASLI_DEFAULT_FILTER)
49 , fullRowContainers(true)
50 , showContainerIndices(true)
51 , filterWhenType(true)
52 , sliderUpdateDelay(25)
53 , undoEnabled(false)
54 , fullUndo(false)
55 , multiSelection(false)
56 , enableActions(true)
58 defaultRowHeight = 22;
59 tabSize = defaultRowHeight;
62 TreeConfig TreeConfig::defaultConfig;
64 // ---------------------------------------------------------------------------
66 void PropertyTreeMenuHandler::onMenuFilter()
68 tree->startFilter("");
71 void PropertyTreeMenuHandler::onMenuFilterByName()
73 tree->startFilter(filterName.c_str());
76 void PropertyTreeMenuHandler::onMenuFilterByValue()
78 tree->startFilter(filterValue.c_str());
81 void PropertyTreeMenuHandler::onMenuFilterByType()
83 tree->startFilter(filterType.c_str());
86 void PropertyTreeMenuHandler::onMenuUndo()
88 tree->model()->undo();
91 void PropertyTreeMenuHandler::onMenuCopy()
93 tree->copyRow(row);
96 void PropertyTreeMenuHandler::onMenuPaste()
98 tree->pasteRow(row);
100 // ---------------------------------------------------------------------------
102 PropertyTreeStyle PropertyTree::defaultTreeStyle_;
104 PropertyTree::PropertyTree(IUIFacade* uiFacade)
105 : attachedPropertyTree_(0)
106 , ui_(uiFacade)
107 , autoRevert_(true)
108 , leftBorder_(0)
109 , rightBorder_(0)
110 , cursorX_(0)
111 , filterMode_(false)
112 , propertySplitterPos_(150)
113 , splitterDragging_(false)
114 , pressPoint_(-1, -1)
115 , pressDelta_(0, 0)
116 , pointerMovedSincePress_(false)
117 , lastStillPosition_(-1, -1)
118 , mouseOverRow_(0)
119 , pressedRow_(0)
120 , capturedRow_(0)
121 , dragCheckMode_(false)
122 , dragCheckValue_(false)
123 , archiveContext_()
124 , outlineMode_(false)
125 , hideSelection_(false)
126 , zoomLevel_(10)
127 , validatorBlock_(new ValidatorBlock)
128 , style_(new PropertyTreeStyle(defaultTreeStyle_))
129 , defaultRowHeight_(22)
131 model_.reset(new PropertyTreeModel(this));
132 model_->setExpandLevels(config_.expandLevels);
133 model_->setUndoEnabled(config_.undoEnabled);
134 model_->setFullUndo(config_.fullUndo);
137 PropertyTree::~PropertyTree()
139 clearMenuHandlers();
140 delete ui_;
143 bool PropertyTree::onRowKeyDown(PropertyRow* row, const KeyEvent* ev)
145 using namespace property_tree;
146 PropertyTreeMenuHandler handler;
147 handler.row = row;
148 handler.tree = this;
150 if(row->onKeyDown(this, ev))
151 return true;
153 switch(ev->key()){
154 case KEY_C:
155 if (ev->modifiers() == MODIFIER_CONTROL)
156 handler.onMenuCopy();
157 return true;
158 case KEY_V:
159 if (ev->modifiers() == MODIFIER_CONTROL)
160 handler.onMenuPaste();
161 return true;
162 case KEY_Z:
163 if (ev->modifiers() == MODIFIER_CONTROL)
164 if(model()->canUndo()){
165 model()->undo();
166 return true;
168 break;
169 case KEY_F2:
170 if (ev->modifiers() == 0) {
171 if(selectedRow()) {
172 PropertyActivationEvent ev;
173 ev.tree = this;
174 ev.reason = ev.REASON_KEYBOARD;
175 ev.force = true;
176 selectedRow()->onActivate(ev);
179 break;
180 case KEY_MENU:
182 if (ev->modifiers() == 0) {
183 std::unique_ptr<property_tree::IMenu> menu(ui()->createMenu());
185 if(onContextMenu(row, *menu)){
186 Rect rect(row->rect());
187 menu->exec(Point(rect.left() + rect.height(), rect.bottom()));
189 return true;
191 break;
195 PropertyRow* focusedRow = model()->focusedRow();
196 if(!focusedRow)
197 return false;
198 PropertyRow* parentRow = focusedRow->nonPulledParent();
199 int x = parentRow->horizontalIndex(this, focusedRow);
200 int y = model()->root()->verticalIndex(this, parentRow);
201 PropertyRow* selectedRow = 0;
202 switch(ev->key()){
203 case KEY_UP:
204 //if (filterMode_ && y == 0) {
205 // startFilter("");
207 //else
209 selectedRow = model()->root()->rowByVerticalIndex(this, --y);
210 if (selectedRow)
211 selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
213 break;
214 case KEY_DOWN:
215 //if (filterMode_) {
216 // setFocus();
218 //else
220 selectedRow = model()->root()->rowByVerticalIndex(this, ++y);
221 if (selectedRow)
222 selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
224 break;
225 case KEY_LEFT:
226 selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = --x);
227 if(selectedRow == focusedRow && parentRow->canBeToggled(this) && parentRow->expanded()){
228 expandRow(parentRow, false);
229 selectedRow = model()->focusedRow();
231 break;
232 case KEY_RIGHT:
233 selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = ++x);
234 if(selectedRow == focusedRow && parentRow->canBeToggled(this) && !parentRow->expanded()){
235 expandRow(parentRow, true);
236 selectedRow = model()->focusedRow();
238 break;
239 case KEY_HOME:
240 if (ev->modifiers() == MODIFIER_CONTROL) {
241 selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = INT_MIN);
243 else {
244 selectedRow = model()->root()->rowByVerticalIndex(this, 0);
245 if (selectedRow)
246 selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
248 break;
249 case KEY_END:
250 if (ev->modifiers() == MODIFIER_CONTROL) {
251 selectedRow = parentRow->rowByHorizontalIndex(this, cursorX_ = INT_MAX);
253 else {
254 selectedRow = model()->root()->rowByVerticalIndex(this, INT_MAX);
255 if (selectedRow)
256 selectedRow = selectedRow->rowByHorizontalIndex(this, cursorX_);
258 break;
259 case KEY_SPACE:
260 if (config_.filterWhenType)
261 break;
262 case KEY_ENTER:
263 case KEY_RETURN:
264 if(focusedRow->canBeToggled(this))
265 expandRow(focusedRow, !focusedRow->expanded());
266 else {
267 PropertyActivationEvent e;
268 e.tree = this;
269 e.reason = e.REASON_KEYBOARD;
270 e.force = false;
271 focusedRow->onActivate(e);
273 break;
275 if(selectedRow){
276 onRowSelected(std::vector<PropertyRow*>(1, selectedRow), false, false);
277 return true;
279 return false;
282 struct FirstIssueVisitor
284 ValidatorEntryType entryType_;
285 PropertyRow* startRow_;
286 PropertyRow* result;
288 FirstIssueVisitor(ValidatorEntryType type, PropertyRow* startRow)
289 : entryType_(type)
290 , startRow_(startRow)
291 , result()
295 ScanResult operator()(PropertyRow* row, PropertyTree* tree, int)
297 if ((row->pulledUp() || row->pulledBefore()) && row->nonPulledParent() == startRow_)
298 return SCAN_SIBLINGS;
299 if (row->validatorCount()) {
300 if (const ValidatorEntry* validatorEntries = tree->_validatorBlock()->getEntry(row->validatorIndex(), row->validatorCount())) {
301 for (int i = 0; i < row->validatorCount(); ++i) {
302 const ValidatorEntry* validatorEntry = validatorEntries + i;
303 if (validatorEntry->type == entryType_) {
304 result = row;
305 return SCAN_FINISHED;
310 return SCAN_CHILDREN_SIBLINGS;
314 void PropertyTree::jumpToNextHiddenValidatorIssue(bool isError, PropertyRow* start)
316 FirstIssueVisitor op(isError ? VALIDATOR_ENTRY_ERROR : VALIDATOR_ENTRY_WARNING, start);
317 start->scanChildren(op, this);
319 PropertyRow* row = op.result;
321 vector<PropertyRow*> parents;
322 while (row && row->parent()) {
323 parents.push_back(row);
324 row = row->parent();
326 for (int i = (int)parents.size()-1; i >= 0; --i) {
327 if (!parents[i]->visible(this))
328 break;
329 row = parents[i];
331 if (row)
332 setSelectedRow(row);
334 updateValidatorIcons();
335 updateHeights();
338 static void rowsInBetween(vector<PropertyRow*>* rows, PropertyRow* a, PropertyRow* b)
340 if (!a)
341 return;
342 if (!b)
343 return;
344 vector<PropertyRow*> pathA;
345 PropertyRow* rootA = a;
346 while (rootA->parent()) {
347 pathA.push_back(rootA);
348 rootA = rootA->parent();
351 vector<PropertyRow*> pathB;
352 PropertyRow* rootB = b;
353 while (rootB->parent()) {
354 pathB.push_back(rootB);
355 rootB = rootB->parent();
358 if (rootA != rootB)
359 return;
361 const PropertyRow* commonParent = rootA;
362 int maxDepth = min((int)pathA.size(), (int)pathB.size());
363 for (int i = 0; i < maxDepth; ++i) {
364 PropertyRow* parentA = pathA[(int)pathA.size() - 1 - i];
365 PropertyRow* parentB = pathB[(int)pathB.size() - 1 - i];
366 if (parentA != parentB) {
367 int indexA = commonParent->childIndex(parentA);
368 int indexB = commonParent->childIndex(parentB);
369 int minIndex = min(indexA, indexB);
370 int maxIndex = max(indexA, indexB);
371 for (int j = minIndex; j <= maxIndex; ++j)
372 rows->push_back((PropertyRow*)commonParent->childByIndex(j));
373 return;
375 commonParent = parentA;
379 bool PropertyTree::onRowLMBDown(PropertyRow* row, const Rect& rowRect, Point point, bool controlPressed, bool shiftPressed)
381 pressPoint_ = point;
382 pressDelta_ = Point(0, 0);
383 pointerMovedSincePress_ = false;
384 row = model()->root()->hit(this, point);
385 if(row){
386 if (!row->isRoot()) {
387 if(row->plusRect(this).contains(point) && toggleRow(row))
388 return true;
389 if (row->validatorWarningIconRect(this).contains(point)) {
390 jumpToNextHiddenValidatorIssue(false, row);
391 return true;
393 if (row->validatorErrorIconRect(this).contains(point)) {
394 jumpToNextHiddenValidatorIssue(true, row);
395 return true;
399 PropertyRow* rowToSelect = row;
400 while (rowToSelect && !rowToSelect->isSelectable())
401 rowToSelect = rowToSelect->parent();
403 if (rowToSelect) {
404 if (!shiftPressed || !multiSelectable()) {
405 onRowSelected(std::vector<PropertyRow*>(1, rowToSelect), multiSelectable() && controlPressed, true);
406 lastSelectedRow_ = rowToSelect;
408 else {
409 vector<PropertyRow*> rowsToSelect;
411 rowsInBetween(&rowsToSelect, lastSelectedRow_, rowToSelect);
412 onRowSelected(rowsToSelect, false, true);
417 PropertyTreeModel::UpdateLock lock = model()->lockUpdate();
418 row = model()->root()->hit(this, point);
419 if(row && !row->isRoot()){
420 bool changed = false;
421 if (row->widgetRect(this).contains(point)) {
422 DragCheckBegin dragCheck = row->onMouseDragCheckBegin();
423 if (dragCheck != DRAG_CHECK_IGNORE) {
424 dragCheckValue_ = dragCheck == DRAG_CHECK_SET;
425 dragCheckMode_ = true;
426 changed = row->onMouseDragCheck(this, dragCheckValue_);
430 if (!dragCheckMode_) {
431 bool capture = row->onMouseDown(this, point, changed);
432 if(!changed){
433 if(capture)
434 return true;
435 else if(row->widgetRect(this).contains(point)){
436 if(row->widgetPlacement() != PropertyRow::WIDGET_ICON)
437 interruptDrag();
438 PropertyActivationEvent e;
439 e.force = false;
440 e.tree = this;
441 e.clickPoint = point;
442 row->onActivate(e);
443 return false;
448 return false;
451 void PropertyTree::onMouseStill()
453 if (capturedRow_) {
454 PropertyDragEvent e;
455 e.tree = this;
456 e.pos = ui()->cursorPosition();
457 e.start = pressPoint_;
459 capturedRow_->onMouseStill(e);
460 lastStillPosition_ = e.pos;
464 void PropertyTree::onRowLMBUp(PropertyRow* row, const Rect& rowRect, Point point)
466 // Row might be invalidated during onMouseUp() call, so acquire it locally and check reference counter afterwards.
467 // If there is only one reference left (which is the shared pointer here), then the tree was changed
468 // during the onMouseUp() and the row is not needed anymore.
469 const yasli::SharedPtr<PropertyRow> sharedRow(row);
470 row->onMouseUp(this, point);
472 if (row->refCount() > 1) {
473 if (!pointerMovedSincePress_ && (pressPoint_ - point).manhattanLength() < 1 && row->widgetRect(this).contains(point)) {
474 PropertyActivationEvent e;
475 e.tree = this;
476 e.clickPoint = point;
477 e.reason = e.REASON_RELEASE;
478 row->onActivate(e);
483 void PropertyTree::onRowRMBDown(PropertyRow* row, const Rect& rowRect, Point point)
485 SharedPtr<PropertyRow> handle = row;
486 PropertyRow* menuRow = 0;
488 if (row->isSelectable()){
489 menuRow = row;
491 else{
492 if (row->parent() && row->parent()->isSelectable())
493 menuRow = row->parent();
496 if (menuRow) {
497 onRowSelected(std::vector<PropertyRow*>(1, menuRow), false, true);
498 std::unique_ptr<property_tree::IMenu> menu(ui()->createMenu());
499 clearMenuHandlers();
500 if(onContextMenu(menuRow, *menu))
501 menu->exec(point);
505 void PropertyTree::expandParents(PropertyRow* row)
507 bool hasChanges = false;
508 typedef std::vector<PropertyRow*> Parents;
509 Parents parents;
510 PropertyRow* p = row->nonPulledParent()->parent();
511 while(p){
512 parents.push_back(p);
513 p = p->parent();
515 Parents::iterator it;
516 for(it = parents.begin(); it != parents.end(); ++it) {
517 PropertyRow* row = *it;
518 if (!row->expanded() || hasChanges) {
519 row->_setExpanded(true);
520 hasChanges = true;
523 if (hasChanges) {
524 updateValidatorIcons();
525 updateHeights();
529 void PropertyTree::expandAll()
531 expandChildren(0);
535 void PropertyTree::expandChildren(PropertyRow* root)
537 if(!root){
538 root = model()->root();
539 PropertyRow::iterator it;
540 for (PropertyRows::iterator it = root->begin(); it != root->end(); ++it){
541 PropertyRow* row = *it;
542 row->setExpandedRecursive(this, true);
544 root->setLayoutChanged();
546 else
547 root->setExpandedRecursive(this, true);
549 for (PropertyRow* r = root; r != 0; r = r->parent())
550 r->setLayoutChanged();
552 updateHeights();
555 void PropertyTree::collapseAll()
557 collapseChildren(0);
560 void PropertyTree::collapseChildren(PropertyRow* root)
562 if(!root){
563 root = model()->root();
565 PropertyRow::iterator it;
566 for (PropertyRows::iterator it = root->begin(); it != root->end(); ++it){
567 PropertyRow* row = *it;
568 row->setExpandedRecursive(this, false);
571 else{
572 root->setExpandedRecursive(this, false);
573 PropertyRow* row = model()->focusedRow();
574 while(row){
575 if(root == row){
576 model()->selectRow(row, true);
577 break;
579 row = row->parent();
583 for (PropertyRow* r = root; r != 0; r = r->parent())
584 r->setLayoutChanged();
586 updateHeights();
590 void PropertyTree::expandRow(PropertyRow* row, bool expanded, bool updateHeights)
592 bool hasChanges = false;
593 if (row->expanded() != expanded) {
594 row->_setExpanded(expanded);
595 hasChanges = true;
598 for (PropertyRow* r = row; r != 0; r = r->parent())
599 r->setLayoutChanged();
601 if(!row->expanded()){
602 PropertyRow* f = model()->focusedRow();
603 while(f){
604 if(row == f){
605 model()->selectRow(row, true);
606 break;
608 f = f->parent();
612 if (hasChanges)
613 updateValidatorIcons();
614 if (hasChanges && updateHeights)
615 this->updateHeights();
618 Point PropertyTree::treeSize() const
620 return size_ + (compact() ? Point(0,0) : Point(8, 8));
623 void PropertyTree::YASLI_SERIALIZE_METHOD(Archive& ar)
625 model()->YASLI_SERIALIZE_METHOD(ar, this);
627 if(ar.isInput()){
628 updateHeights();
629 ensureVisible(model()->focusedRow());
630 updateAttachedPropertyTree(false);
631 updateHeights();
632 onSelected();
636 void PropertyTree::ensureVisible(PropertyRow* row, bool update, bool considerChildren)
638 if (row == 0)
639 return;
640 if(row->isRoot())
641 return;
643 expandParents(row);
645 Rect rect = row->rect();
646 if(rect.bottom() > area_.bottom() + offset_.y()){
647 offset_.setY(max(0, rect.bottom() - area_.bottom()));
649 if(rect.top() < area_.top() + offset_.y()){
650 offset_.setY(max(0, rect.top() - area_.top()));
652 updateScrollBar();
653 if(update)
654 repaint();
657 void PropertyTree::onRowSelected(const std::vector<PropertyRow*>& rows, bool addSelection, bool adjustCursorPos)
659 for (size_t i = 0; i < rows.size(); ++i) {
660 PropertyRow* row = rows[i];
661 if(!row->isRoot()) {
662 bool addRowToSelection = !(addSelection && row->selected() && model()->selection().size() > 1) || i > 0;
663 bool exclusiveSelection = !addSelection && i == 0;
664 model()->selectRow(row, addRowToSelection, exclusiveSelection);
667 if (!rows.empty()) {
668 ensureVisible(rows.back(), true, false);
669 if(adjustCursorPos)
670 cursorX_ = rows.back()->nonPulledParent()->horizontalIndex(this, rows.back());
672 updateAttachedPropertyTree(false);
673 onSelected();
676 bool PropertyTree::attach(const yasli::Serializers& serializers)
678 bool changed = false;
679 if (attached_.size() != serializers.size())
680 changed = true;
681 else {
682 for (size_t i = 0; i < serializers.size(); ++i) {
683 if (attached_[i].serializer() != serializers[i]) {
684 changed = true;
685 break;
690 // We can't perform plain copying here, as it was before:
691 // attached_ = serializers;
692 // ...as move forwarder calls copying constructor with non-const argument
693 // which invokes second templated constructor of Serializer, which is not what we need.
694 if (changed) {
695 attached_.assign(serializers.begin(), serializers.end());
696 model_->clearUndo();
699 revertNoninterrupting();
701 return changed;
704 void PropertyTree::attach(const yasli::Serializer& serializer)
706 if (attached_.size() != 1 || attached_[0].serializer() != serializer) {
707 attached_.clear();
708 attached_.push_back(yasli::Object(serializer));
709 model_->clearUndo();
711 revert();
714 void PropertyTree::attach(const yasli::Object& object)
716 attached_.clear();
717 attached_.push_back(object);
719 revert();
722 void PropertyTree::detach()
724 capturedRow_ = nullptr;
725 mouseOverRow_ = nullptr;
726 lastSelectedRow_.release();
727 _cancelWidget();
728 attached_.clear();
729 if (model_->root())
730 model()->root()->clear();
731 updateHeights();
732 repaint();
735 void PropertyTree::_cancelWidget()
737 // make sure that widget_ is null the moment widget is destroyed, so we
738 // don't get focus callbacks to act on destroyed widget.
739 std::unique_ptr<property_tree::InplaceWidget> widget(widget_.release());
742 int PropertyTree::revertObjects(vector<void*> objectAddresses)
744 int result = 0;
745 for (size_t i = 0; i < objectAddresses.size(); ++i) {
746 if (revertObject(objectAddresses[i]))
747 ++result;
749 return result;
752 bool PropertyTree::revertObject(void* objectAddress)
754 PropertyRow* row = model()->root()->findByAddress(objectAddress);
755 if (row && row->isObject()) {
756 // TODO:
757 // revertObjectRow(row);
758 return true;
760 return false;
764 void PropertyTree::revert()
766 interruptDrag();
767 _cancelWidget();
768 capturedRow_ = 0;
769 mouseOverRow_ = 0;
770 lastSelectedRow_.release();
772 if (!attached_.empty()) {
773 validatorBlock_->clear();
774 PropertyOArchive oa(model_.get(), model_->root(), validatorBlock_.get());
775 oa.setOutlineMode(outlineMode_);
776 if (archiveContext_)
777 oa.setLastContext(archiveContext_);
778 oa.setFilter(config_.filter);
780 Objects::iterator it = attached_.begin();
781 onAboutToSerialize(oa);
782 (*it)(oa);
783 onSerialized(oa);
785 PropertyTreeModel model2(this);
786 while(++it != attached_.end()){
787 PropertyOArchive oa2(&model2, model2.root(), validatorBlock_.get());
788 oa2.setOutlineMode(outlineMode_);
789 oa2.setLastContext(archiveContext_);
790 yasli::Context treeContext(oa2, this);
791 oa2.setFilter(config_.filter);
792 onAboutToSerialize(oa2);
793 (*it)(oa2);
794 onSerialized(oa2);
795 model_->root()->intersect(model2.root());
797 //revertTime_ = int(timer.elapsed());
799 if (attached_.size() != 1)
800 validatorBlock_->clear();
801 applyValidation();
803 else
804 model_->clear();
806 if (filterMode_) {
807 if (model_->root())
808 model_->root()->updateLabel(this, 0, false);
809 resetFilter();
811 else {
812 updateHeights();
815 repaint();
816 updateAttachedPropertyTree(true);
819 struct ValidatorVisitor
821 ValidatorVisitor(ValidatorBlock* validator)
822 : validator_(validator)
826 ScanResult operator()(PropertyRow* row, PropertyTree* tree, int)
828 const void* rowHandle = row->searchHandle();
829 int index = 0;
830 int count = 0;
831 if (validator_->findHandleEntries(&index, &count, rowHandle, row->typeId()))
833 validator_->markAsUsed(index, count);
834 if (row->setValidatorEntry(index, count))
835 row->setLabelChanged();
837 else
839 if (row->setValidatorEntry(0, 0))
840 row->setLabelChanged();
843 return SCAN_CHILDREN_SIBLINGS;
846 protected:
847 ValidatorBlock* validator_;
850 void PropertyTree::applyValidation()
852 if (!validatorBlock_->isEnabled())
853 return;
855 ValidatorVisitor visitor(validatorBlock_.get());
856 model()->root()->scanChildren(visitor, this);
858 int rootFirst = 0;
859 int rootCount = 0;
860 // Gather all the items with unknown handle/type pair at root level.
861 validatorBlock_->mergeUnusedItemsWithRootItems(&rootFirst, &rootCount, model()->root()->searchHandle(), model()->root()->typeId());
862 model()->root()->setValidatorEntry(rootFirst, rootCount);
863 model()->root()->setLabelChanged();
865 updateValidatorIcons();
868 void PropertyTree::revertNoninterrupting()
870 if (!capturedRow_)
871 revert();
874 void PropertyTree::apply(bool continuous)
876 //QElapsedTimer timer;
877 //timer.start();
879 if (!attached_.empty())
881 Objects::iterator it;
882 PropertyIArchive ia(model_.get(), model_->root());
883 for(it = attached_.begin(); it != attached_.end(); ++it)
885 PropertyRow* row = selectedRow();
886 ia.setModifiedRowName(row ? row->name() : nullptr);
887 ia.setLastContext(archiveContext_);
888 yasli::Context treeContext(ia, this);
889 ia.setFilter(config_.filter);
890 onAboutToSerialize(ia);
891 (*it)(ia);
892 onSerialized(ia);
896 if (continuous)
897 onContinuousChange();
898 else
899 onChanged();
900 //applyTime_ = timer.elapsed();
903 void PropertyTree::applyInplaceEditor()
905 if (widget_.get())
906 widget_->commit();
909 bool PropertyTree::spawnWidget(PropertyRow* row, bool ignoreReadOnly)
911 if(!widget_.get() || widgetRow_ != row){
912 interruptDrag();
913 setWidget(0, 0);
914 property_tree::InplaceWidget* newWidget = 0;
915 if ((ignoreReadOnly && row->userReadOnlyRecurse()) || !row->userReadOnly())
916 newWidget = row->createWidget(this);
917 setWidget(newWidget, row);
918 return newWidget != 0;
920 return false;
923 void PropertyTree::addMenuHandler(PropertyRowMenuHandler* handler)
925 menuHandlers_.push_back(handler);
928 void PropertyTree::clearMenuHandlers()
930 for (size_t i = 0; i < menuHandlers_.size(); ++i)
932 PropertyRowMenuHandler* handler = menuHandlers_[i];
933 delete handler;
935 menuHandlers_.clear();
938 static yasli::string quoteIfNeeded(const char* str)
940 if (!str)
941 return yasli::string();
942 if (strchr(str, ' ') != 0) {
943 yasli::string result;
944 result = "\"";
945 result += str;
946 result += "\"";
947 return result;
949 else {
950 return yasli::string(str);
954 bool PropertyTree::onContextMenu(PropertyRow* r, IMenu& menu)
956 SharedPtr<PropertyRow> row(r);
957 PropertyTreeMenuHandler* handler = new PropertyTreeMenuHandler();
958 addMenuHandler(handler);
959 handler->tree = this;
960 handler->row = row;
962 PropertyRow::iterator it;
963 for(it = row->begin(); it != row->end(); ++it){
964 PropertyRow* child = *it;
965 if(child->isContainer() && child->pulledUp())
966 child->onContextMenu(menu, this);
968 row->onContextMenu(menu, this);
969 if(config_.undoEnabled){
970 if(!menu.isEmpty())
971 menu.addSeparator();
972 menu.addAction("Undo", "Ctrl+Z", model()->canUndo() ? 0 : MENU_DISABLED, handler, &PropertyTreeMenuHandler::onMenuUndo);
974 if(!menu.isEmpty())
975 menu.addSeparator();
977 menu.addAction("Copy", "Ctrl+C", 0, handler, &PropertyTreeMenuHandler::onMenuCopy);
979 if(!row->userReadOnly()){
980 menu.addAction("Paste", "Ctrl+V", canBePasted(row) ? 0 : MENU_DISABLED, handler, &PropertyTreeMenuHandler::onMenuPaste);
983 if (model()->root() && !model()->root()->userReadOnly()) {
984 PropertyTreeMenuHandler* rootHandler = new PropertyTreeMenuHandler();
985 rootHandler->tree = this;
986 rootHandler->row = model()->root();
987 menu.addSeparator();
988 menu.addAction("Copy All", "", 0, rootHandler, &PropertyTreeMenuHandler::onMenuCopy);
989 menu.addAction("Paste All", "", canBePasted(model()->root()) ? 0 : MENU_DISABLED, rootHandler, &PropertyTreeMenuHandler::onMenuPaste);
990 addMenuHandler(rootHandler);
992 menu.addSeparator();
994 menu.addAction("Filter...", "Ctrl+F", 0, handler, &PropertyTreeMenuHandler::onMenuFilter);
995 IMenu* filter = menu.addMenu("Filter by");
997 yasli::string nameFilter = "#";
998 nameFilter += quoteIfNeeded(row->labelUndecorated());
999 handler->filterName = nameFilter;
1000 filter->addAction((yasli::string("Name:\t") + nameFilter).c_str(), 0, handler, &PropertyTreeMenuHandler::onMenuFilterByName);
1002 yasli::string valueFilter = "=";
1003 valueFilter += quoteIfNeeded(row->valueAsString().c_str());
1004 handler->filterValue = valueFilter;
1005 filter->addAction((yasli::string("Value:\t") + valueFilter).c_str(), 0, handler, &PropertyTreeMenuHandler::onMenuFilterByValue);
1007 yasli::string typeFilter = ":";
1008 typeFilter += quoteIfNeeded(row->typeNameForFilter(this));
1009 handler->filterType = typeFilter;
1010 filter->addAction((yasli::string("Type:\t") + typeFilter).c_str(), 0, handler, &PropertyTreeMenuHandler::onMenuFilterByType);
1013 // menu.addSeparator();
1014 // menu.addAction(TRANSLATE("Decompose"), row).connect(this, &PropertyTree::onRowMenuDecompose);
1015 return true;
1018 void PropertyTree::onRowMouseMove(PropertyRow* row, const Rect& rowRect, Point point, Modifier modifiers)
1020 PropertyDragEvent e;
1021 e.tree = this;
1022 e.modifier = modifiers;
1023 e.pos = point;
1024 e.start = pressPoint_;
1025 e.totalDelta = pressDelta_;
1026 row->onMouseDrag(e);
1027 repaint();
1030 struct DecomposeProxy
1032 DecomposeProxy(SharedPtr<PropertyRow>& row) : row(row) {}
1034 void YASLI_SERIALIZE_METHOD(yasli::Archive& ar)
1036 ar(row, "row", "Row");
1039 SharedPtr<PropertyRow>& row;
1042 void PropertyTree::onRowMenuDecompose(PropertyRow* row)
1044 // SharedPtr<PropertyRow> clonedRow = row->clone();
1045 // DecomposeProxy proxy(clonedRow);
1046 // edit(Serializer(proxy), 0, IMMEDIATE_UPDATE, this);
1049 void PropertyTree::onModelUpdated(const PropertyRows& rows, bool needApply)
1051 _cancelWidget();
1053 if(config_.immediateUpdate){
1054 if (needApply)
1055 apply();
1057 if(autoRevert_)
1058 revert();
1059 else {
1060 updateHeights();
1061 updateAttachedPropertyTree(true);
1064 else {
1065 repaint();
1069 void PropertyTree::onModelPushUndo(PropertyTreeOperator* op, bool* handled)
1071 onPushUndo();
1074 void PropertyTree::setWidget(property_tree::InplaceWidget* widget, PropertyRow* widgetRow)
1076 _cancelWidget();
1077 widget_.reset(widget);
1078 widgetRow_ = widgetRow;
1079 model()->dismissUpdate();
1080 if(widget_.get()){
1081 YASLI_ASSERT(widget_->actualWidget());
1082 _arrangeChildren();
1083 YASLI_ASSERT(widget_.get());
1084 if(widget_.get()) // Fix for intermittent crash when editing number fields. It is possible that widget will be destroyed by _arrangeChildren() but the root cause of this issue is not clear.
1086 widget_->showPopup();
1088 repaint();
1092 void PropertyTree::setExpandLevels(int levels)
1094 config_.expandLevels = levels;
1095 model()->setExpandLevels(levels);
1098 PropertyRow* PropertyTree::selectedRow()
1100 const PropertyTreeModel::Selection &sel = model()->selection();
1101 if(sel.empty())
1102 return 0;
1103 return model()->rowFromPath(sel.front());
1106 bool PropertyTree::getSelectedObject(yasli::Object* object)
1108 const PropertyTreeModel::Selection &sel = model()->selection();
1109 if(sel.empty())
1110 return 0;
1111 PropertyRow* row = model()->rowFromPath(sel.front());
1112 while (row && !row->isObject())
1113 row = row->parent();
1114 if (!row)
1115 return false;
1117 if (row->isObject()) {
1118 if (PropertyRowObject* obj = static_cast<PropertyRowObject*>(row)) {
1119 *object = obj->object();
1120 return true;
1123 return false;
1126 bool PropertyTree::setSelectedRow(PropertyRow* row)
1128 TreeSelection sel;
1129 if(row)
1130 sel.push_back(model()->pathFromRow(row));
1131 if (model()->selection() != sel) {
1132 model()->setSelection(sel);
1133 if (row)
1134 ensureVisible(row);
1135 updateAttachedPropertyTree(false);
1136 repaint();
1137 return true;
1139 return false;
1142 int PropertyTree::selectedRowCount() const
1144 return (int)model()->selection().size();
1147 PropertyRow* PropertyTree::selectedRowByIndex(int index)
1149 const PropertyTreeModel::Selection &sel = model()->selection();
1150 if (size_t(index) >= sel.size())
1151 return 0;
1153 return model()->rowFromPath(sel[index]);
1157 bool PropertyTree::selectByAddress(const void* addr, bool keepSelectionIfChildSelected)
1159 return selectByAddresses(vector<const void*>(1, addr), keepSelectionIfChildSelected);
1162 bool PropertyTree::selectByAddresses(const vector<const void*>& addresses, bool keepSelectionIfChildSelected)
1164 return selectByAddresses(&addresses.front(), addresses.size(), keepSelectionIfChildSelected);
1169 bool PropertyTree::selectByAddresses(const void* const* addresses, size_t addressCount, bool keepSelectionIfChildSelected)
1171 bool result = false;
1172 if (model()->root()) {
1173 bool keepSelection = false;
1174 vector<PropertyRow*> rows;
1175 for (size_t i = 0; i < addressCount; ++i) {
1176 const void* addr = addresses[i];
1177 PropertyRow* row = model()->root()->findByAddress(addr);
1179 if (keepSelectionIfChildSelected && row && !model()->selection().empty()) {
1180 keepSelection = true;
1181 TreeSelection::const_iterator it;
1182 for(it = model()->selection().begin(); it != model()->selection().end(); ++it){
1183 PropertyRow* selectedRow = model()->rowFromPath(*it);
1184 if (!selectedRow)
1185 continue;
1186 if (!selectedRow->isChildOf(row)){
1187 keepSelection = false;
1188 break;
1193 if (row)
1194 rows.push_back(row);
1197 if (!keepSelection) {
1198 TreeSelection sel;
1199 for (size_t j = 0; j < rows.size(); ++j) {
1200 PropertyRow* row = rows[j];
1201 if(row)
1202 sel.push_back(model()->pathFromRow(row));
1204 if (model()->selection() != sel) {
1205 model()->setSelection(sel);
1206 if (!rows.empty())
1207 ensureVisible(rows.back());
1208 repaint();
1209 result = true;
1210 if (attachedPropertyTree_)
1211 updateAttachedPropertyTree(false);
1215 return result;
1218 void PropertyTree::setUndoEnabled(bool enabled, bool full)
1220 config_.undoEnabled = enabled;
1221 config_.fullUndo = full;
1222 model()->setUndoEnabled(enabled);
1223 model()->setFullUndo(full);
1226 void PropertyTree::attachPropertyTree(PropertyTree* propertyTree)
1228 // TODO:
1229 // if(attachedPropertyTree_)
1230 // disconnect(attachedPropertyTree_, SIGNAL(signalChanged()), this, SLOT(onAttachedTreeChanged()));
1231 attachedPropertyTree_ = propertyTree;
1232 //if (attachedPropertyTree_)
1233 // connect(attachedPropertyTree_, SIGNAL(signalChanged()), this, SLOT(onAttachedTreeChanged()));
1234 updateAttachedPropertyTree(true);
1237 void PropertyTree::detachPropertyTree()
1239 attachPropertyTree(0);
1242 void PropertyTree::setAutoHideAttachedPropertyTree(bool autoHide)
1244 autoHideAttachedPropertyTree_ = autoHide;
1247 void PropertyTree::getSelectionSerializers(yasli::Serializers* serializers)
1249 TreeSelection::const_iterator i;
1250 for(i = model()->selection().begin(); i != model()->selection().end(); ++i){
1251 PropertyRow* row = model()->rowFromPath(*i);
1252 if (!row)
1253 continue;
1256 while(row && ((row->pulledUp() || row->pulledBefore()) || row->isLeaf())) {
1257 row = row->parent();
1259 if (outlineMode_) {
1260 PropertyRow* topmostContainerElement = 0;
1261 PropertyRow* r = row;
1262 while (r && r->parent()) {
1263 if (r->parent()->isContainer())
1264 topmostContainerElement = r;
1265 r = r->parent();
1267 if (topmostContainerElement != 0)
1268 row = topmostContainerElement;
1270 Serializer ser = row->serializer();
1272 if (ser)
1273 serializers->push_back(ser);
1277 void PropertyTree::updateAttachedPropertyTree(bool revert)
1279 if(attachedPropertyTree_) {
1280 Serializers serializers;
1281 getSelectionSerializers(&serializers);
1282 if (!attachedPropertyTree_->attach(serializers) && revert)
1283 attachedPropertyTree_->revert();
1288 void PropertyTree::RowFilter::parse(const char* filter)
1290 for (int i = 0; i < NUM_TYPES; ++i) {
1291 start[i].clear();
1292 substrings[i].clear();
1293 tillEnd[i] = false;
1296 YASLI_ESCAPE(filter != 0, return);
1298 vector<char> filterBuf(filter, filter + strlen(filter) + 1);
1299 for (size_t i = 0; i < filterBuf.size(); ++i)
1300 filterBuf[i] = tolower(filterBuf[i]);
1302 const char* str = &filterBuf[0];
1304 Type type = NAME_VALUE;
1305 while (true)
1307 bool fromStart = false;
1308 while (*str == '^') {
1309 fromStart = true;
1310 ++str;
1313 const char* tokenStart = str;
1315 if (*str == '\"')
1317 ++str;
1318 while(*str != '\0' && *str != '\"')
1319 ++str;
1321 else
1323 while (*str != '\0' && *str != ' ' && *str != '*' && *str != '=' && *str != ':' && *str != '#')
1324 ++str;
1326 if (str != tokenStart) {
1327 if (*tokenStart == '\"' && *str == '\"') {
1328 start[type].assign(tokenStart + 1, str);
1329 tillEnd[type] = true;
1330 ++str;
1332 else
1334 if (fromStart)
1335 start[type].assign(tokenStart, str);
1336 else
1337 substrings[type].push_back(yasli::string(tokenStart, str));
1340 while (*str == ' ')
1341 ++str;
1342 if (*str == '#') {
1343 type = NAME;
1344 ++str;
1346 else if (*str == '=') {
1347 type = VALUE;
1348 ++str;
1350 else if(*str == ':') {
1351 type = TYPE;
1352 ++str;
1354 else if (*str == '\0')
1355 break;
1359 bool PropertyTree::RowFilter::match(const char* textOriginal, Type type, size_t* matchStart, size_t* matchEnd) const
1361 YASLI_ESCAPE(textOriginal, return false);
1363 char* text;
1365 size_t textLen = strlen(textOriginal);
1366 text = (char*)alloca((textLen + 1));
1367 memcpy(text, textOriginal, (textLen + 1));
1368 for (char* p = text; *p; ++p)
1369 *p = tolower(*p);
1372 const yasli::string &start = this->start[type];
1373 if (tillEnd[type]){
1374 if (start == text) {
1375 if (matchStart)
1376 *matchStart = 0;
1377 if (matchEnd)
1378 *matchEnd = start.size();
1379 return true;
1381 else
1382 return false;
1385 const vector<yasli::string> &substrings = this->substrings[type];
1387 const char* startPos = text;
1389 if (matchStart)
1390 *matchStart = 0;
1391 if (matchEnd)
1392 *matchEnd = 0;
1393 if (!start.empty()) {
1394 if (strncmp(text, start.c_str(), start.size()) != 0){
1395 //_freea(text);
1396 return false;
1398 if (matchEnd)
1399 *matchEnd = start.size();
1400 startPos += start.size();
1403 size_t numSubstrings = substrings.size();
1404 for (size_t i = 0; i < numSubstrings; ++i) {
1405 const char* substr = strstr(startPos, substrings[i].c_str());
1406 if (!substr){
1407 return false;
1409 startPos += substrings[i].size();
1410 if (matchStart && i == 0 && start.empty()) {
1411 *matchStart = substr - text;
1413 if (matchEnd)
1414 *matchEnd = substr - text + substrings[i].size();
1416 return true;
1419 PropertyRow* PropertyTree::rowByPoint(const Point& pt)
1421 if (!model_->root())
1422 return 0;
1423 if (!area_.contains(pt))
1424 return 0;
1425 return model_->root()->hit(this, pointToRootSpace(pt));
1428 Point PropertyTree::pointToRootSpace(const Point& point) const
1430 return Point(point.x() + offset_.x(), point.y() + offset_.y());
1433 Point PropertyTree::pointFromRootSpace(const Point& point) const
1435 return Point(point.x() - offset_.x() + area_.left(), point.y() - offset_.y() + area_.top());
1438 bool PropertyTree::toggleRow(PropertyRow* row)
1440 if(!row->canBeToggled(this))
1441 return false;
1442 expandRow(row, !row->expanded());
1443 updateHeights();
1444 return true;
1447 bool PropertyTree::_isCapturedRow(const PropertyRow* row) const
1449 return capturedRow_ == row;
1452 void PropertyTree::setValueColumnWidth(float valueColumnWidth)
1454 if (style_->valueColumnWidth != valueColumnWidth)
1456 style_->valueColumnWidth = valueColumnWidth;
1457 updateHeights(false);
1458 repaint();
1462 void PropertyTree::setArchiveContext(yasli::Context* lastContext)
1464 archiveContext_ = lastContext;
1467 PropertyTree::PropertyTree(const PropertyTree&)
1472 PropertyTree& PropertyTree::operator=(const PropertyTree&)
1474 return *this;
1477 void PropertyTree::onAttachedTreeChanged()
1479 revert();
1482 Point PropertyTree::_toWidget(Point point) const
1484 return Point(point.x() - offset_.x(), point.y() - offset_.y());
1487 struct ValidatorIconVisitor
1489 ScanResult operator()(PropertyRow* row, PropertyTree* tree, int)
1491 row->resetValidatorIcons();
1492 if (row->validatorCount()) {
1493 bool hasErrors = false;
1494 bool hasWarnings = false;
1495 if (const ValidatorEntry* validatorEntries = tree->_validatorBlock()->getEntry(row->validatorIndex(), row->validatorCount())) {
1496 for (int i = 0; i < row->validatorCount(); ++i) {
1497 const ValidatorEntry* validatorEntry = validatorEntries + i;
1498 if (validatorEntry->type == VALIDATOR_ENTRY_ERROR)
1499 hasErrors = true;
1500 else if (validatorEntry->type == VALIDATOR_ENTRY_WARNING)
1501 hasWarnings = true;
1505 if (hasErrors || hasWarnings)
1507 PropertyRow* lastClosedParent = 0;
1508 PropertyRow* current = row->parent();
1509 bool lastWasPulled = row->pulledUp() || row->pulledBefore();
1510 while (current && current->parent()) {
1511 if (!current->expanded() && !lastWasPulled && current->visible(tree))
1512 lastClosedParent = current;
1513 lastWasPulled = current->pulledUp() || current->pulledBefore();
1514 current = current->parent();
1516 if (lastClosedParent)
1517 lastClosedParent->addValidatorIcons(hasWarnings, hasErrors);
1520 return SCAN_CHILDREN_SIBLINGS;
1524 void PropertyTree::updateValidatorIcons()
1526 if (!validatorBlock_->isEnabled())
1527 return;
1528 ValidatorIconVisitor op;
1529 model()->root()->scanChildren(op, this);
1530 model()->root()->setLabelChangedToChildren();
1533 void PropertyTree::setDefaultTreeStyle(const PropertyTreeStyle& treeStyle)
1535 defaultTreeStyle_ = treeStyle;
1538 void PropertyTree::setTreeStyle(const PropertyTreeStyle& style)
1540 *style_ = style;
1541 updateHeights(true);
1544 void PropertyTree::setPackCheckboxes(bool pack)
1546 style_->packCheckboxes = pack;
1547 updateHeights(true);
1550 bool PropertyTree::packCheckboxes() const
1552 return style_->packCheckboxes;
1555 void PropertyTree::setCompact(bool compact)
1557 style_->compact = compact;
1558 repaint();
1561 bool PropertyTree::compact() const
1563 return style_->compact;
1566 void PropertyTree::setRowSpacing(float rowSpacing)
1568 style_->rowSpacing = rowSpacing;
1571 float PropertyTree::rowSpacing() const
1573 return style_->rowSpacing;
1576 float PropertyTree::valueColumnWidth() const
1578 return style_->valueColumnWidth;
1581 void PropertyTree::setFullRowMode(bool fullRowMode)
1583 style_->fullRowMode = fullRowMode;
1584 repaint();
1587 bool PropertyTree::fullRowMode() const
1589 return style_->fullRowMode;
1592 bool PropertyTree::containsWarnings() const
1594 return validatorBlock_->containsWarnings();
1597 bool PropertyTree::containsErrors() const
1599 return validatorBlock_->containsErrors();
1602 void PropertyTree::focusFirstError()
1604 jumpToNextHiddenValidatorIssue(true, model()->root());
1607 Rect PropertyTree::getPropertySplitterRect() const
1609 int splitterStart = propertySplitterPos_ - treeStyle().propertySplitterHalfWidth;
1610 int splitterWidth = 1 + treeStyle().propertySplitterHalfWidth * 2;
1611 return Rect(splitterStart, area_.top(), splitterWidth, area_.height());
1614 void PropertyTree::setPropertySplitterPos(int pos)
1616 if (pos == propertySplitterPos_)
1617 return;
1619 const int splitterBorder = 2 * defaultRowHeight_;
1620 propertySplitterPos_ = pos;
1621 if (area_.width() > 0)
1622 propertySplitterPos_ = min(area_.right() - splitterBorder, max(area_.left() + splitterBorder, pos));
1623 updateHeights();
1626 // vim:ts=4 sw=4: