2 * yasli - Serialization Library.
3 * Copyright (C) 2007-2013 Evgeny Andreeshchev <eugene.andreeshchev@gmail.com>
4 * Alexander Kotliar <alexander.kotliar@gmail.com>
6 * This code is distributed under the MIT License:
7 * http://www.opensource.org/licenses/MIT
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>
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"
32 #include "PropertyTreeMenuHandler.h"
33 #include "IUIFacade.h"
36 #include "MathUtils.h"
38 #include "PropertyRowObject.h"
40 using yasli::Serializers
;
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)
55 , multiSelection(false)
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()
96 void PropertyTreeMenuHandler::onMenuPaste()
100 // ---------------------------------------------------------------------------
102 PropertyTreeStyle
PropertyTree::defaultTreeStyle_
;
104 PropertyTree::PropertyTree(IUIFacade
* uiFacade
)
105 : attachedPropertyTree_(0)
112 , propertySplitterPos_(150)
113 , splitterDragging_(false)
114 , pressPoint_(-1, -1)
116 , pointerMovedSincePress_(false)
117 , lastStillPosition_(-1, -1)
121 , dragCheckMode_(false)
122 , dragCheckValue_(false)
124 , outlineMode_(false)
125 , hideSelection_(false)
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()
143 bool PropertyTree::onRowKeyDown(PropertyRow
* row
, const KeyEvent
* ev
)
145 using namespace property_tree
;
146 PropertyTreeMenuHandler handler
;
150 if(row
->onKeyDown(this, ev
))
155 if (ev
->modifiers() == MODIFIER_CONTROL
)
156 handler
.onMenuCopy();
159 if (ev
->modifiers() == MODIFIER_CONTROL
)
160 handler
.onMenuPaste();
163 if (ev
->modifiers() == MODIFIER_CONTROL
)
164 if(model()->canUndo()){
170 if (ev
->modifiers() == 0) {
172 PropertyActivationEvent ev
;
174 ev
.reason
= ev
.REASON_KEYBOARD
;
176 selectedRow()->onActivate(ev
);
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()));
195 PropertyRow
* focusedRow
= model()->focusedRow();
198 PropertyRow
* parentRow
= focusedRow
->nonPulledParent();
199 int x
= parentRow
->horizontalIndex(this, focusedRow
);
200 int y
= model()->root()->verticalIndex(this, parentRow
);
201 PropertyRow
* selectedRow
= 0;
204 //if (filterMode_ && y == 0) {
209 selectedRow
= model()->root()->rowByVerticalIndex(this, --y
);
211 selectedRow
= selectedRow
->rowByHorizontalIndex(this, cursorX_
);
220 selectedRow
= model()->root()->rowByVerticalIndex(this, ++y
);
222 selectedRow
= selectedRow
->rowByHorizontalIndex(this, cursorX_
);
226 selectedRow
= parentRow
->rowByHorizontalIndex(this, cursorX_
= --x
);
227 if(selectedRow
== focusedRow
&& parentRow
->canBeToggled(this) && parentRow
->expanded()){
228 expandRow(parentRow
, false);
229 selectedRow
= model()->focusedRow();
233 selectedRow
= parentRow
->rowByHorizontalIndex(this, cursorX_
= ++x
);
234 if(selectedRow
== focusedRow
&& parentRow
->canBeToggled(this) && !parentRow
->expanded()){
235 expandRow(parentRow
, true);
236 selectedRow
= model()->focusedRow();
240 if (ev
->modifiers() == MODIFIER_CONTROL
) {
241 selectedRow
= parentRow
->rowByHorizontalIndex(this, cursorX_
= INT_MIN
);
244 selectedRow
= model()->root()->rowByVerticalIndex(this, 0);
246 selectedRow
= selectedRow
->rowByHorizontalIndex(this, cursorX_
);
250 if (ev
->modifiers() == MODIFIER_CONTROL
) {
251 selectedRow
= parentRow
->rowByHorizontalIndex(this, cursorX_
= INT_MAX
);
254 selectedRow
= model()->root()->rowByVerticalIndex(this, INT_MAX
);
256 selectedRow
= selectedRow
->rowByHorizontalIndex(this, cursorX_
);
260 if (config_
.filterWhenType
)
264 if(focusedRow
->canBeToggled(this))
265 expandRow(focusedRow
, !focusedRow
->expanded());
267 PropertyActivationEvent e
;
269 e
.reason
= e
.REASON_KEYBOARD
;
271 focusedRow
->onActivate(e
);
276 onRowSelected(std::vector
<PropertyRow
*>(1, selectedRow
), false, false);
282 struct FirstIssueVisitor
284 ValidatorEntryType entryType_
;
285 PropertyRow
* startRow_
;
288 FirstIssueVisitor(ValidatorEntryType type
, PropertyRow
* startRow
)
290 , startRow_(startRow
)
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_
) {
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
);
326 for (int i
= (int)parents
.size()-1; i
>= 0; --i
) {
327 if (!parents
[i
]->visible(this))
334 updateValidatorIcons();
338 static void rowsInBetween(vector
<PropertyRow
*>* rows
, PropertyRow
* a
, PropertyRow
* b
)
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();
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
));
375 commonParent
= parentA
;
379 bool PropertyTree::onRowLMBDown(PropertyRow
* row
, const Rect
& rowRect
, Point point
, bool controlPressed
, bool shiftPressed
)
382 pressDelta_
= Point(0, 0);
383 pointerMovedSincePress_
= false;
384 row
= model()->root()->hit(this, point
);
386 if (!row
->isRoot()) {
387 if(row
->plusRect(this).contains(point
) && toggleRow(row
))
389 if (row
->validatorWarningIconRect(this).contains(point
)) {
390 jumpToNextHiddenValidatorIssue(false, row
);
393 if (row
->validatorErrorIconRect(this).contains(point
)) {
394 jumpToNextHiddenValidatorIssue(true, row
);
399 PropertyRow
* rowToSelect
= row
;
400 while (rowToSelect
&& !rowToSelect
->isSelectable())
401 rowToSelect
= rowToSelect
->parent();
404 if (!shiftPressed
|| !multiSelectable()) {
405 onRowSelected(std::vector
<PropertyRow
*>(1, rowToSelect
), multiSelectable() && controlPressed
, true);
406 lastSelectedRow_
= rowToSelect
;
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
);
435 else if(row
->widgetRect(this).contains(point
)){
436 if(row
->widgetPlacement() != PropertyRow::WIDGET_ICON
)
438 PropertyActivationEvent e
;
441 e
.clickPoint
= point
;
451 void PropertyTree::onMouseStill()
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
;
476 e
.clickPoint
= point
;
477 e
.reason
= e
.REASON_RELEASE
;
483 void PropertyTree::onRowRMBDown(PropertyRow
* row
, const Rect
& rowRect
, Point point
)
485 SharedPtr
<PropertyRow
> handle
= row
;
486 PropertyRow
* menuRow
= 0;
488 if (row
->isSelectable()){
492 if (row
->parent() && row
->parent()->isSelectable())
493 menuRow
= row
->parent();
497 onRowSelected(std::vector
<PropertyRow
*>(1, menuRow
), false, true);
498 std::unique_ptr
<property_tree::IMenu
> menu(ui()->createMenu());
500 if(onContextMenu(menuRow
, *menu
))
505 void PropertyTree::expandParents(PropertyRow
* row
)
507 bool hasChanges
= false;
508 typedef std::vector
<PropertyRow
*> Parents
;
510 PropertyRow
* p
= row
->nonPulledParent()->parent();
512 parents
.push_back(p
);
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);
524 updateValidatorIcons();
529 void PropertyTree::expandAll()
535 void PropertyTree::expandChildren(PropertyRow
* 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();
547 root
->setExpandedRecursive(this, true);
549 for (PropertyRow
* r
= root
; r
!= 0; r
= r
->parent())
550 r
->setLayoutChanged();
555 void PropertyTree::collapseAll()
560 void PropertyTree::collapseChildren(PropertyRow
* 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);
572 root
->setExpandedRecursive(this, false);
573 PropertyRow
* row
= model()->focusedRow();
576 model()->selectRow(row
, true);
583 for (PropertyRow
* r
= root
; r
!= 0; r
= r
->parent())
584 r
->setLayoutChanged();
590 void PropertyTree::expandRow(PropertyRow
* row
, bool expanded
, bool updateHeights
)
592 bool hasChanges
= false;
593 if (row
->expanded() != expanded
) {
594 row
->_setExpanded(expanded
);
598 for (PropertyRow
* r
= row
; r
!= 0; r
= r
->parent())
599 r
->setLayoutChanged();
601 if(!row
->expanded()){
602 PropertyRow
* f
= model()->focusedRow();
605 model()->selectRow(row
, true);
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);
629 ensureVisible(model()->focusedRow());
630 updateAttachedPropertyTree(false);
636 void PropertyTree::ensureVisible(PropertyRow
* row
, bool update
, bool considerChildren
)
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()));
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
];
662 bool addRowToSelection
= !(addSelection
&& row
->selected() && model()->selection().size() > 1) || i
> 0;
663 bool exclusiveSelection
= !addSelection
&& i
== 0;
664 model()->selectRow(row
, addRowToSelection
, exclusiveSelection
);
668 ensureVisible(rows
.back(), true, false);
670 cursorX_
= rows
.back()->nonPulledParent()->horizontalIndex(this, rows
.back());
672 updateAttachedPropertyTree(false);
676 bool PropertyTree::attach(const yasli::Serializers
& serializers
)
678 bool changed
= false;
679 if (attached_
.size() != serializers
.size())
682 for (size_t i
= 0; i
< serializers
.size(); ++i
) {
683 if (attached_
[i
].serializer() != serializers
[i
]) {
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.
695 attached_
.assign(serializers
.begin(), serializers
.end());
699 revertNoninterrupting();
704 void PropertyTree::attach(const yasli::Serializer
& serializer
)
706 if (attached_
.size() != 1 || attached_
[0].serializer() != serializer
) {
708 attached_
.push_back(yasli::Object(serializer
));
714 void PropertyTree::attach(const yasli::Object
& object
)
717 attached_
.push_back(object
);
722 void PropertyTree::detach()
724 capturedRow_
= nullptr;
725 mouseOverRow_
= nullptr;
726 lastSelectedRow_
.release();
730 model()->root()->clear();
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
)
745 for (size_t i
= 0; i
< objectAddresses
.size(); ++i
) {
746 if (revertObject(objectAddresses
[i
]))
752 bool PropertyTree::revertObject(void* objectAddress
)
754 PropertyRow
* row
= model()->root()->findByAddress(objectAddress
);
755 if (row
&& row
->isObject()) {
757 // revertObjectRow(row);
764 void PropertyTree::revert()
770 lastSelectedRow_
.release();
772 if (!attached_
.empty()) {
773 validatorBlock_
->clear();
774 PropertyOArchive
oa(model_
.get(), model_
->root(), validatorBlock_
.get());
775 oa
.setOutlineMode(outlineMode_
);
777 oa
.setLastContext(archiveContext_
);
778 oa
.setFilter(config_
.filter
);
780 Objects::iterator it
= attached_
.begin();
781 onAboutToSerialize(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
);
795 model_
->root()->intersect(model2
.root());
797 //revertTime_ = int(timer.elapsed());
799 if (attached_
.size() != 1)
800 validatorBlock_
->clear();
808 model_
->root()->updateLabel(this, 0, false);
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();
831 if (validator_
->findHandleEntries(&index
, &count
, rowHandle
, row
->typeId()))
833 validator_
->markAsUsed(index
, count
);
834 if (row
->setValidatorEntry(index
, count
))
835 row
->setLabelChanged();
839 if (row
->setValidatorEntry(0, 0))
840 row
->setLabelChanged();
843 return SCAN_CHILDREN_SIBLINGS
;
847 ValidatorBlock
* validator_
;
850 void PropertyTree::applyValidation()
852 if (!validatorBlock_
->isEnabled())
855 ValidatorVisitor
visitor(validatorBlock_
.get());
856 model()->root()->scanChildren(visitor
, this);
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()
874 void PropertyTree::apply(bool continuous
)
876 //QElapsedTimer timer;
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
);
897 onContinuousChange();
900 //applyTime_ = timer.elapsed();
903 void PropertyTree::applyInplaceEditor()
909 bool PropertyTree::spawnWidget(PropertyRow
* row
, bool ignoreReadOnly
)
911 if(!widget_
.get() || widgetRow_
!= row
){
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;
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
];
935 menuHandlers_
.clear();
938 static yasli::string
quoteIfNeeded(const char* str
)
941 return yasli::string();
942 if (strchr(str
, ' ') != 0) {
943 yasli::string result
;
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;
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
){
972 menu
.addAction("Undo", "Ctrl+Z", model()->canUndo() ? 0 : MENU_DISABLED
, handler
, &PropertyTreeMenuHandler::onMenuUndo
);
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();
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
);
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);
1018 void PropertyTree::onRowMouseMove(PropertyRow
* row
, const Rect
& rowRect
, Point point
, Modifier modifiers
)
1020 PropertyDragEvent e
;
1022 e
.modifier
= modifiers
;
1024 e
.start
= pressPoint_
;
1025 e
.totalDelta
= pressDelta_
;
1026 row
->onMouseDrag(e
);
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
)
1053 if(config_
.immediateUpdate
){
1061 updateAttachedPropertyTree(true);
1069 void PropertyTree::onModelPushUndo(PropertyTreeOperator
* op
, bool* handled
)
1074 void PropertyTree::setWidget(property_tree::InplaceWidget
* widget
, PropertyRow
* widgetRow
)
1077 widget_
.reset(widget
);
1078 widgetRow_
= widgetRow
;
1079 model()->dismissUpdate();
1081 YASLI_ASSERT(widget_
->actualWidget());
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();
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();
1103 return model()->rowFromPath(sel
.front());
1106 bool PropertyTree::getSelectedObject(yasli::Object
* object
)
1108 const PropertyTreeModel::Selection
&sel
= model()->selection();
1111 PropertyRow
* row
= model()->rowFromPath(sel
.front());
1112 while (row
&& !row
->isObject())
1113 row
= row
->parent();
1117 if (row
->isObject()) {
1118 if (PropertyRowObject
* obj
= static_cast<PropertyRowObject
*>(row
)) {
1119 *object
= obj
->object();
1126 bool PropertyTree::setSelectedRow(PropertyRow
* row
)
1130 sel
.push_back(model()->pathFromRow(row
));
1131 if (model()->selection() != sel
) {
1132 model()->setSelection(sel
);
1135 updateAttachedPropertyTree(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())
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
);
1186 if (!selectedRow
->isChildOf(row
)){
1187 keepSelection
= false;
1194 rows
.push_back(row
);
1197 if (!keepSelection
) {
1199 for (size_t j
= 0; j
< rows
.size(); ++j
) {
1200 PropertyRow
* row
= rows
[j
];
1202 sel
.push_back(model()->pathFromRow(row
));
1204 if (model()->selection() != sel
) {
1205 model()->setSelection(sel
);
1207 ensureVisible(rows
.back());
1210 if (attachedPropertyTree_
)
1211 updateAttachedPropertyTree(false);
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
)
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
);
1256 while(row
&& ((row
->pulledUp() || row
->pulledBefore()) || row
->isLeaf())) {
1257 row
= row
->parent();
1260 PropertyRow
* topmostContainerElement
= 0;
1261 PropertyRow
* r
= row
;
1262 while (r
&& r
->parent()) {
1263 if (r
->parent()->isContainer())
1264 topmostContainerElement
= r
;
1267 if (topmostContainerElement
!= 0)
1268 row
= topmostContainerElement
;
1270 Serializer ser
= row
->serializer();
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
) {
1292 substrings
[i
].clear();
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
;
1307 bool fromStart
= false;
1308 while (*str
== '^') {
1313 const char* tokenStart
= str
;
1318 while(*str
!= '\0' && *str
!= '\"')
1323 while (*str
!= '\0' && *str
!= ' ' && *str
!= '*' && *str
!= '=' && *str
!= ':' && *str
!= '#')
1326 if (str
!= tokenStart
) {
1327 if (*tokenStart
== '\"' && *str
== '\"') {
1328 start
[type
].assign(tokenStart
+ 1, str
);
1329 tillEnd
[type
] = true;
1335 start
[type
].assign(tokenStart
, str
);
1337 substrings
[type
].push_back(yasli::string(tokenStart
, str
));
1346 else if (*str
== '=') {
1350 else if(*str
== ':') {
1354 else if (*str
== '\0')
1359 bool PropertyTree::RowFilter::match(const char* textOriginal
, Type type
, size_t* matchStart
, size_t* matchEnd
) const
1361 YASLI_ESCAPE(textOriginal
, return false);
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
)
1372 const yasli::string
&start
= this->start
[type
];
1374 if (start
== text
) {
1378 *matchEnd
= start
.size();
1385 const vector
<yasli::string
> &substrings
= this->substrings
[type
];
1387 const char* startPos
= text
;
1393 if (!start
.empty()) {
1394 if (strncmp(text
, start
.c_str(), start
.size()) != 0){
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());
1409 startPos
+= substrings
[i
].size();
1410 if (matchStart
&& i
== 0 && start
.empty()) {
1411 *matchStart
= substr
- text
;
1414 *matchEnd
= substr
- text
+ substrings
[i
].size();
1419 PropertyRow
* PropertyTree::rowByPoint(const Point
& pt
)
1421 if (!model_
->root())
1423 if (!area_
.contains(pt
))
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))
1442 expandRow(row
, !row
->expanded());
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);
1462 void PropertyTree::setArchiveContext(yasli::Context
* lastContext
)
1464 archiveContext_
= lastContext
;
1467 PropertyTree::PropertyTree(const PropertyTree
&)
1472 PropertyTree
& PropertyTree::operator=(const PropertyTree
&)
1477 void PropertyTree::onAttachedTreeChanged()
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
)
1500 else if (validatorEntry
->type
== VALIDATOR_ENTRY_WARNING
)
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())
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
)
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
;
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
;
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_
)
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
));