Skip to content

Commit

Permalink
#5643: Working on EntityInspector accepting and rejecting conflicting…
Browse files Browse the repository at this point in the history
… key/value changes and the way the associated nodes are rendered.
  • Loading branch information
codereader committed Jun 19, 2021
1 parent 3577090 commit e836dd2
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 11 deletions.
4 changes: 4 additions & 0 deletions include/imapmerge.h
Expand Up @@ -171,6 +171,10 @@ class IMergeActionNode :
// This can be 0 if the node has been cleared out after completing a merge operation
virtual std::size_t getMergeActionCount() = 0;

// Returns true if this node has one or more active actions.
// If all associated actions have been deactivated, this returns false.
virtual bool hasActiveActions() = 0;

// Iterate over all actions of this node
virtual void foreachMergeAction(const std::function<void(const merge::IMergeAction::Ptr&)>& functor) = 0;
};
Expand Down
4 changes: 2 additions & 2 deletions libs/wxutil/dataview/TreeViewItemStyle.h
Expand Up @@ -92,11 +92,11 @@ class TreeViewItemStyle
{
if (SupportsBackgroundColour())
{
SetBackgroundColour(attr, wxColour(89, 255, 0));
SetBackgroundColour(attr, wxColour(230, 120, 0));
}
else
{
attr.SetColour(wxColour(89, 255, 0));
attr.SetColour(wxColour(230, 120, 0));
attr.SetBold(true);
}
}
Expand Down
108 changes: 100 additions & 8 deletions radiant/ui/einspector/EntityInspector.cpp
Expand Up @@ -242,7 +242,9 @@ void EntityInspector::onKeyChange(const std::string& key,
}

// Conflicting actions get a special render style
if (_conflictActions.count(key) > 0)
auto conflict = _conflictActions.find(key);

if (conflict != _conflictActions.end() && conflict->second->getResolution() == scene::merge::ResolutionType::Unresolved)
{
wxutil::TreeViewItemStyle::ApplyKeyValueConflictStyle(style);
}
Expand Down Expand Up @@ -388,6 +390,13 @@ void EntityInspector::createContextMenu()

_contextMenu->addSeparator();

_contextMenu->addItem(
new wxutil::StockIconTextMenuItem(_("Accept selected Changes"), wxART_UNDO),
std::bind(&EntityInspector::_onAcceptMergeAction, this),
std::bind(&EntityInspector::_testAcceptMergeAction, this),
[] { return GlobalMapModule().getEditMode() == IMap::EditMode::Merge; }
);

_contextMenu->addItem(
new wxutil::StockIconTextMenuItem(_("Reject selected Changes"), wxART_UNDO),
std::bind(&EntityInspector::_onRejectMergeAction, this),
Expand Down Expand Up @@ -953,6 +962,30 @@ bool EntityInspector::_testPasteKey()
return !_clipboard.empty() && canUpdateEntity();
}

void EntityInspector::_onAcceptMergeAction()
{
wxDataViewItemArray selectedItems;
_keyValueTreeView->GetSelections(selectedItems);

for (const wxDataViewItem& item : selectedItems)
{
wxutil::TreeModel::Row row(item, *_kvStore);

auto key = row[_columns.name].getString().ToStdString();

auto conflict = _conflictActions.find(key);

if (conflict != _conflictActions.end())
{
conflict->second->setResolution(scene::merge::ResolutionType::ApplySourceChange);
}
}

// We perform a full refresh of the view
changeSelectedEntity(scene::INodePtr(), scene::INodePtr());
getEntityFromSelectionSystem();
}

void EntityInspector::_onRejectMergeAction()
{
wxDataViewItemArray selectedItems;
Expand All @@ -969,6 +1002,8 @@ void EntityInspector::_onRejectMergeAction()
if (conflict != _conflictActions.end())
{
conflict->second->setResolution(scene::merge::ResolutionType::RejectSourceChange);
// Deactivate the conflict action itself too, such that the node can be removed once the last action is gone
conflict->second->deactivate();
}

auto action = _mergeActions.find(key);
Expand All @@ -979,11 +1014,53 @@ void EntityInspector::_onRejectMergeAction()
}
}

// Check if the merge node is now completely empty, then remove it from the scene
// A single entity must be selected
if (GlobalSelectionSystem().countSelected() == 1)
{
auto selectedNode = GlobalSelectionSystem().ultimateSelected();

if (selectedNode && selectedNode->getNodeType() == scene::INode::Type::MergeAction)
{
auto mergeNode = std::dynamic_pointer_cast<scene::IMergeActionNode>(selectedNode);
assert(mergeNode);

if (!mergeNode->hasActiveActions())
{
// Remove this node from the scene, it's empty now
scene::removeNodeFromParent(mergeNode);
}
}
}

// We perform a full refresh of the view
changeSelectedEntity(scene::INodePtr(), scene::INodePtr());
getEntityFromSelectionSystem();
}

bool EntityInspector::_testAcceptMergeAction()
{
if (GlobalMapModule().getEditMode() != IMap::EditMode::Merge)
{
return false;
}

wxDataViewItemArray selectedItems;
_keyValueTreeView->GetSelections(selectedItems);

for (const wxDataViewItem& item : selectedItems)
{
wxutil::TreeModel::Row row(item, *_kvStore);

if (isItemAffecedByMergeConflict(row))
{
return true;
}
}

return false;
}

bool EntityInspector::_testRejectMergeAction()
{
if (GlobalMapModule().getEditMode() != IMap::EditMode::Merge)
Expand All @@ -1000,13 +1077,19 @@ bool EntityInspector::_testRejectMergeAction()

if (isItemAffecedByMergeOperation(row))
{
return true; // we have at least one non-inherited value that is not "classname"
return true;
}
}

return false;
}

bool EntityInspector::isItemAffecedByMergeConflict(const wxutil::TreeModel::Row& row)
{
auto key = row[_columns.name].getString().ToStdString();
return _mergeActions.count(key) > 0 && _conflictActions.count(key) > 0;
}

bool EntityInspector::isItemAffecedByMergeOperation(const wxutil::TreeModel::Row& row)
{
auto key = row[_columns.name].getString().ToStdString();
Expand Down Expand Up @@ -1491,14 +1574,23 @@ void EntityInspector::handleMergeActions(const scene::INodePtr& selectedNode)

if (conflictAction)
{
auto sourceAction = std::dynamic_pointer_cast<scene::merge::IEntityKeyValueMergeAction>(conflictAction->getSourceAction());
bool conflictIsUnresolved = conflictAction->getResolution() == scene::merge::ResolutionType::Unresolved;

if (sourceAction)
if (conflictIsUnresolved || conflictAction->getResolution() == scene::merge::ResolutionType::ApplySourceChange)
{
// The source action will be rendered as change
_mergeActions[sourceAction->getKey()] = sourceAction;
// Remember the conflict action too
_conflictActions[sourceAction->getKey()] = conflictAction;
auto sourceAction = std::dynamic_pointer_cast<scene::merge::IEntityKeyValueMergeAction>(conflictAction->getSourceAction());

if (sourceAction)
{
// The source action will be rendered as change
_mergeActions[sourceAction->getKey()] = sourceAction;

// Remember the conflict action if it's not yet resolved
if (conflictIsUnresolved)
{
_conflictActions[sourceAction->getKey()] = conflictAction;
}
}
}
}
});
Expand Down
3 changes: 3 additions & 0 deletions radiant/ui/einspector/EntityInspector.h
Expand Up @@ -174,18 +174,21 @@ class EntityInspector :
void _onCopyKey();
void _onCutKey();
void _onPasteKey();
void _onAcceptMergeAction();
void _onRejectMergeAction();

bool _testAddKey();
bool _testDeleteKey();
bool _testCopyKey();
bool _testCutKey();
bool _testPasteKey();
bool _testAcceptMergeAction();
bool _testRejectMergeAction();

// Shared by cut and delete keys
bool _testNonEmptyAndDeletableSelection();
bool isItemDeletable(const wxutil::TreeModel::Row& row);
bool isItemAffecedByMergeConflict(const wxutil::TreeModel::Row& row);
bool isItemAffecedByMergeOperation(const wxutil::TreeModel::Row& row);

// callbacks
Expand Down
38 changes: 37 additions & 1 deletion radiantcore/map/MergeActionNode.cpp
Expand Up @@ -163,6 +163,16 @@ std::size_t KeyValueMergeActionNode::getMergeActionCount()
return _actions.size();
}

bool KeyValueMergeActionNode::hasActiveActions()
{
for (const auto& action : _actions)
{
if (action->isActive()) return true;
}

return false;
}

void KeyValueMergeActionNode::foreachMergeAction(const std::function<void(const scene::merge::IMergeAction::Ptr&)>& functor)
{
for (const auto& action : _actions)
Expand Down Expand Up @@ -238,7 +248,28 @@ void RegularMergeActionNode::clear()

scene::merge::ActionType RegularMergeActionNode::getActionType() const
{
return _action ? _action->getType() : scene::merge::ActionType::NoAction;
if (!_action) return scene::merge::ActionType::NoAction;

if (_action->getType() == scene::merge::ActionType::ConflictResolution)
{
auto conflictAction = std::dynamic_pointer_cast<scene::merge::IConflictResolutionAction>(_action);
assert(conflictAction);

// Determine how this node should be rendered (unresolved conflict, or the type of the change that was accepted)
switch (conflictAction->getResolution())
{
case scene::merge::ResolutionType::Unresolved:
return scene::merge::ActionType::ConflictResolution;

case scene::merge::ResolutionType::ApplySourceChange: // render using the accepted action type
return conflictAction->getSourceAction()->getType();

case scene::merge::ResolutionType::RejectSourceChange:
return scene::merge::ActionType::NoAction;
}
}

return _action->getType();
}

void RegularMergeActionNode::foreachMergeAction(const std::function<void(const scene::merge::IMergeAction::Ptr&)>& functor)
Expand All @@ -254,6 +285,11 @@ std::size_t RegularMergeActionNode::getMergeActionCount()
return _action ? 1 : 0;
}

bool RegularMergeActionNode::hasActiveActions()
{
return _action && _action->isActive();
}

void RegularMergeActionNode::addPreviewNodeForAddAction()
{
// We add the node to the target scene, for preview purposes
Expand Down
2 changes: 2 additions & 0 deletions radiantcore/map/MergeActionNode.h
Expand Up @@ -81,6 +81,7 @@ class KeyValueMergeActionNode final :
scene::merge::ActionType getActionType() const override;
void foreachMergeAction(const std::function<void(const scene::merge::IMergeAction::Ptr&)>& functor) override;
std::size_t getMergeActionCount() override;
bool hasActiveActions() override;

void onInsertIntoScene(scene::IMapRootNode& rootNode) override;
void onRemoveFromScene(scene::IMapRootNode& rootNode) override;
Expand Down Expand Up @@ -112,6 +113,7 @@ class RegularMergeActionNode final :
scene::merge::ActionType getActionType() const override;
void foreachMergeAction(const std::function<void(const scene::merge::IMergeAction::Ptr&)>& functor) override;
std::size_t getMergeActionCount() override;
bool hasActiveActions() override;

private:
void addPreviewNodeForAddAction();
Expand Down

0 comments on commit e836dd2

Please sign in to comment.