Repository: TrenchBroom/TrenchBroom (C++)

Issue:
Temporary zoom setting on 3D viewport cannot be reset
### System Information
TrenchBroom 2022.1, Windows 8.1

### Expected Behavior
After using shift+wheel to change the FOV/zoom directly in the 3D viewport, the manual states that shift+esc resets it, but pressing that does nothing. 

### Steps to Reproduce
- shift+wheel on 3d viewport, zoom changes
- shift+esc to reset said zoom, nothing happens


Discussion:
It does seem to work if an object is selected. But if you only have the 3D View active and no object selected, no worky. In the latter case (according to gdb breakpoint) MapViewBase::resetCameraZoom is not getting called.

Same results if I change the binding to something simpler like the X key, instead of Shift+Esc.
Curiously, if I change its action definition to say AnyView instead of View3D it seems to behave just like you'd want it to, i.e. if you have the 3D view active with nothing selected you can now reset the zoom. However, weirdly, changing to AnyView doesn't seem to let you reset the zoom while you're in the 2D view. Which is great I guess! but I am a little confused.

(edit: in the 2D view it will actually reset the "real zoom", i.e. the zoom from mousewheel-without-shift)
OK I think I more or less see what is going on.

When I temporarily changed the action definition to use AnyView as a test, that made the overall action context be equivalent to Any (since it already had AnyTool and AnySelection). In ActionExecutionContext::hasActionContext there is an initial check for action context == Any, and it decides that means the action is always enabled... which, fair enough. So it bails out there and doesn't go through any bitmask checking.

However let's look at what happens with the actual current action context definition for this action, which is : ActionContext::View3D | ActionContext::AnyTool | ActionContext::AnySelection

That is NOT equivalent to "Any" since View2D is not included, so it will get into the bitmask-checking logic in actionContextMatches:
```c++
bool actionContextMatches(ActionContext::Type lhs, ActionContext::Type rhs) {
  return actionContextMatches(lhs, rhs, ActionContext::AnyView) &&
         (actionContextMatches(lhs, rhs, ActionContext::AnyTool) ||
          actionContextMatches(lhs, rhs, ActionContext::AnySelection));
}

bool actionContextMatches(
  const ActionContext::Type lhs, const ActionContext::Type rhs, const ActionContext::Type mask) {
  return (lhs & rhs & mask) != 0;
}
```

Essentially this says that the action context's "view" bits must have at least one overlap with the view bits of the current context, AND either there must be overlap among the tool bits or overlap among the selection bits.

The problem is that in the "nothing selected" case there are zero tool bits or selection bits in the current context, so in that case it can _never_ match with whatever the action context's bits are.

I'm a little surprised that there are not other actions meant to be used with "nothing selected" that this problem affects. Although, perhaps all other such actions just use the Any context and so bypass the issue.
FWIW here's a way to work around that limitation, but it does feel more like a hack rather than a generalized solution for modelling than an action applies when nothing is selected: https://github.com/neogeographica/TrenchBroom/commit/d70cc8919c77166dec8ae2293ea9a9762738b329



Patch:
diff --git a/common/src/View/ActionContext.cpp b/common/src/View/ActionContext.cpp
index 695535a2b2..3dee89b0e7 100644
--- a/common/src/View/ActionContext.cpp
+++ b/common/src/View/ActionContext.cpp
@@ -28,8 +28,8 @@ namespace TrenchBroom {
 namespace View {
 bool actionContextMatches(ActionContext::Type lhs, ActionContext::Type rhs) {
   return actionContextMatches(lhs, rhs, ActionContext::AnyView) &&
-         (actionContextMatches(lhs, rhs, ActionContext::AnyTool) ||
-          actionContextMatches(lhs, rhs, ActionContext::AnySelection));
+         actionContextMatches(lhs, rhs, ActionContext::AnyOrNoTool) &&
+         actionContextMatches(lhs, rhs, ActionContext::AnyOrNoSelection);
 }
 
 bool actionContextMatches(
@@ -39,42 +39,56 @@ bool actionContextMatches(
 
 std::string actionContextName(const ActionContext::Type actionContext) {
   if (actionContext == ActionContext::Any) {
-    return "Any";
+    return "any";
   }
 
   std::vector<std::string> actionContexts;
-  if (actionContext & ActionContext::NodeSelection) {
-    actionContexts.push_back("Objects");
+  if ((actionContext & ActionContext::AnyView) == ActionContext::AnyView) {
+    actionContexts.emplace_back("any view");
+  } else if (actionContext & ActionContext::View3D) {
+    actionContexts.emplace_back("3D view");
+  } else if (actionContext & ActionContext::View2D) {
+    actionContexts.emplace_back("2D view");
   }
-  if (actionContext & ActionContext::FaceSelection) {
-    actionContexts.push_back("Textures");
+
+  if ((actionContext & ActionContext::AnyOrNoSelection) == ActionContext::AnyOrNoSelection) {
+    actionContexts.emplace_back("any or no selection");
+  } else if ((actionContext & ActionContext::AnyOrNoSelection) == ActionContext::AnySelection) {
+    actionContexts.emplace_back("any selection");
+  } else {
+    if (actionContext & ActionContext::NoSelection) {
+      actionContexts.emplace_back("no selection");
+    }
+    if (actionContext & ActionContext::NodeSelection) {
+      actionContexts.emplace_back("objects selected");
+    }
+    if (actionContext & ActionContext::FaceSelection) {
+      actionContexts.emplace_back("faces selected");
+    }
   }
 
-  if ((actionContext & ActionContext::AnyTool) == ActionContext::AnyTool) {
-    actionContexts.push_back("Any Tool");
+  if ((actionContext & ActionContext::AnyOrNoTool) == ActionContext::AnyOrNoTool) {
+    actionContexts.emplace_back("any or no tool");
+  } else if ((actionContext & ActionContext::AnyOrNoTool) == ActionContext::AnyTool) {
+    actionContexts.emplace_back("any tool");
   } else {
+    if (actionContext & ActionContext::NoTool) {
+      actionContexts.emplace_back("no tool");
+    }
     if (actionContext & ActionContext::CreateComplexBrushTool) {
-      actionContexts.push_back("Create Brush Tool");
+      actionContexts.emplace_back("brush tool");
     }
     if (actionContext & ActionContext::ClipTool) {
-      actionContexts.push_back("Clip Tool");
+      actionContexts.emplace_back("clip tool");
     }
     if (actionContext & ActionContext::RotateTool) {
-      actionContexts.push_back("Rotate Tool");
+      actionContexts.emplace_back("rotate tool");
     }
     if (actionContext & ActionContext::AnyVertexTool) {
-      actionContexts.push_back("Any Vertex Tool");
+      actionContexts.emplace_back("any vertex tool");
     }
   }
 
-  if ((actionContext & ActionContext::AnyView) == ActionContext::AnyView) {
-    actionContexts.push_back("Any View");
-  } else if (actionContext & ActionContext::View3D) {
-    actionContexts.push_back("3D View");
-  } else if (actionContext & ActionContext::View2D) {
-    actionContexts.push_back("2D View");
-  }
-
   return kdl::str_join(actionContexts, ", ");
 }
 } // namespace View
diff --git a/common/src/View/ActionContext.h b/common/src/View/ActionContext.h
index 2906b50ffb..fd519e9351 100644
--- a/common/src/View/ActionContext.h
+++ b/common/src/View/ActionContext.h
@@ -28,18 +28,22 @@ using Type = size_t;
 static const Type View3D = 1u << 0u;
 static const Type View2D = 1u << 1u;
 static const Type AnyView = View3D | View2D;
-static const Type CreateComplexBrushTool = 1u << 2u;
-static const Type ClipTool = 1u << 3u;
-static const Type RotateTool = 1u << 4u;
-static const Type ScaleTool = 1u << 5u;
-static const Type ShearTool = 1u << 6u;
-static const Type AnyVertexTool = 1u << 7u;
+static const Type NoTool = 1u << 2u;
+static const Type CreateComplexBrushTool = 1u << 3u;
+static const Type ClipTool = 1u << 4u;
+static const Type RotateTool = 1u << 5u;
+static const Type ScaleTool = 1u << 6u;
+static const Type ShearTool = 1u << 7u;
+static const Type AnyVertexTool = 1u << 8u;
 static const Type AnyTool =
   AnyVertexTool | CreateComplexBrushTool | ClipTool | RotateTool | ScaleTool | ShearTool;
-static const Type NodeSelection = 1u << 8u;
-static const Type FaceSelection = 1u << 9u;
+static const Type AnyOrNoTool = AnyTool | NoTool;
+static const Type NoSelection = 1u << 9u;
+static const Type NodeSelection = 1u << 10u;
+static const Type FaceSelection = 1u << 11u;
 static const Type AnySelection = NodeSelection | FaceSelection;
-static const Type Any = AnySelection | AnyTool | AnyView;
+static const Type AnyOrNoSelection = AnySelection | NoSelection;
+static const Type Any = AnyView | AnyOrNoSelection | AnyOrNoTool;
 } // namespace ActionContext
 
 bool actionContextMatches(ActionContext::Type lhs, ActionContext::Type rhs);
diff --git a/common/src/View/Actions.cpp b/common/src/View/Actions.cpp
index 7f415dffb8..de2be51ae0 100644
--- a/common/src/View/Actions.cpp
+++ b/common/src/View/Actions.cpp
@@ -245,8 +245,7 @@ std::vector<std::unique_ptr<Action>> ActionManager::createTagActions(
       result.push_back(makeAction(
         IO::Path("Tags/" + tag.name() + "/Enable"),
         QObject::tr("Turn Selection into %1").arg(QString::fromStdString(tag.name())),
-        ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::FaceSelection |
-          ActionContext::AnyTool,
+        ActionContext::AnyView | ActionContext::AnySelection | ActionContext::AnyOrNoTool,
         [&tag](ActionExecutionContext& context) {
           context.view()->enableTag(tag);
         },
@@ -258,8 +257,7 @@ std::vector<std::unique_ptr<Action>> ActionManager::createTagActions(
       result.push_back(makeAction(
         IO::Path("Tags/" + tag.name() + "/Disable"),
         QObject::tr("Turn Selection into non-%1").arg(QString::fromStdString(tag.name())),
-        ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::FaceSelection |
-          ActionContext::AnyTool,
+        ActionContext::AnyView | ActionContext::AnySelection | ActionContext::AnyOrNoTool,
         [&tag](ActionExecutionContext& context) {
           context.view()->disableTag(tag);
         },
@@ -378,7 +376,8 @@ void ActionManager::createViewActions() {
   /* ========== Tool Specific Actions ========== */
   createAction(
     IO::Path("Controls/Map view/Create brush"), QObject::tr("Create Brush"),
-    ActionContext::View3D | ActionContext::CreateComplexBrushTool, QKeySequence(Qt::Key_Return),
+    ActionContext::View3D | ActionContext::AnyOrNoSelection | ActionContext::CreateComplexBrushTool,
+    QKeySequence(Qt::Key_Return),
     [](ActionExecutionContext& context) {
       context.view()->createComplexBrush();
     },
@@ -387,7 +386,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Toggle clip side"), QObject::tr("Toggle Clip Side"),
-    ActionContext::AnyView | ActionContext::ClipTool, QKeySequence(Qt::CTRL + Qt::Key_Return),
+    ActionContext::AnyView | ActionContext::AnyOrNoSelection | ActionContext::ClipTool,
+    QKeySequence(Qt::CTRL + Qt::Key_Return),
     [](ActionExecutionContext& context) {
       context.view()->toggleClipSide();
     },
@@ -396,7 +396,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Perform clip"), QObject::tr("Perform Clip"),
-    ActionContext::AnyView | ActionContext::ClipTool, QKeySequence(Qt::Key_Return),
+    ActionContext::AnyView | ActionContext::AnyOrNoSelection | ActionContext::ClipTool,
+    QKeySequence(Qt::Key_Return),
     [](ActionExecutionContext& context) {
       context.view()->performClip();
     },
@@ -411,7 +412,7 @@ void ActionManager::createViewActions() {
     IO::Path("Controls/Map view/Move objects up; Move objects forward"),
     QObject::tr("Move Forward"),
     ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyVertexTool |
-      ActionContext::RotateTool,
+      ActionContext::RotateTool | ActionContext::NoTool,
     QKeySequence(Qt::Key_Up),
     [](ActionExecutionContext& context) {
       context.view()->move(vm::direction::forward);
@@ -423,7 +424,7 @@ void ActionManager::createViewActions() {
     IO::Path("Controls/Map view/Move objects down; Move objects backward"),
     QObject::tr("Move Backward"),
     ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyVertexTool |
-      ActionContext::RotateTool,
+      ActionContext::RotateTool | ActionContext::NoTool,
     QKeySequence(Qt::Key_Down),
     [](ActionExecutionContext& context) {
       context.view()->move(vm::direction::backward);
@@ -434,7 +435,7 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Move objects left"), QObject::tr("Move Left"),
     ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyVertexTool |
-      ActionContext::RotateTool,
+      ActionContext::RotateTool | ActionContext::NoTool,
     QKeySequence(Qt::Key_Left),
     [](ActionExecutionContext& context) {
       context.view()->move(vm::direction::left);
@@ -445,7 +446,7 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Move objects right"), QObject::tr("Move Right"),
     ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyVertexTool |
-      ActionContext::RotateTool,
+      ActionContext::RotateTool | ActionContext::NoTool,
     QKeySequence(Qt::Key_Right),
     [](ActionExecutionContext& context) {
       context.view()->move(vm::direction::right);
@@ -456,7 +457,7 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Move objects backward; Move objects up"), QObject::tr("Move Up"),
     ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyVertexTool |
-      ActionContext::RotateTool,
+      ActionContext::RotateTool | ActionContext::NoTool,
     QKeySequence(Qt::Key_PageUp),
     [](ActionExecutionContext& context) {
       context.view()->move(vm::direction::up);
@@ -467,7 +468,7 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Move objects forward; Move objects down"), QObject::tr("Move Down"),
     ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyVertexTool |
-      ActionContext::RotateTool,
+      ActionContext::RotateTool | ActionContext::NoTool,
     QKeySequence(Qt::Key_PageDown),
     [](ActionExecutionContext& context) {
       context.view()->move(vm::direction::down);
@@ -481,7 +482,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Duplicate and move objects up; Duplicate and move objects forward"),
     QObject::tr("Duplicate and Move Forward"),
-    ActionContext::AnyView | ActionContext::NodeSelection, QKeySequence(Qt::CTRL + Qt::Key_Up),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::CTRL + Qt::Key_Up),
     [](ActionExecutionContext& context) {
       context.view()->duplicateAndMoveObjects(vm::direction::forward);
     },
@@ -492,7 +494,8 @@ void ActionManager::createViewActions() {
     IO::Path(
       "Controls/Map view/Duplicate and move objects down; Duplicate and move objects backward"),
     QObject::tr("Duplicate and Move Backward"),
-    ActionContext::AnyView | ActionContext::NodeSelection, QKeySequence(Qt::CTRL + Qt::Key_Down),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::CTRL + Qt::Key_Down),
     [](ActionExecutionContext& context) {
       context.view()->duplicateAndMoveObjects(vm::direction::backward);
     },
@@ -501,7 +504,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Duplicate and move objects left"),
-    QObject::tr("Duplicate and Move Left"), ActionContext::AnyView | ActionContext::NodeSelection,
+    QObject::tr("Duplicate and Move Left"),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_Left),
     [](ActionExecutionContext& context) {
       context.view()->duplicateAndMoveObjects(vm::direction::left);
@@ -511,7 +515,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Duplicate and move objects right"),
-    QObject::tr("Duplicate and Move Right"), ActionContext::AnyView | ActionContext::NodeSelection,
+    QObject::tr("Duplicate and Move Right"),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_Right),
     [](ActionExecutionContext& context) {
       context.view()->duplicateAndMoveObjects(vm::direction::right);
@@ -522,7 +527,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path(
       "Controls/Map view/Duplicate and move objects backward; Duplicate and move objects up"),
-    QObject::tr("Duplicate and Move Up"), ActionContext::AnyView | ActionContext::NodeSelection,
+    QObject::tr("Duplicate and Move Up"),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_PageUp),
     [](ActionExecutionContext& context) {
       context.view()->duplicateAndMoveObjects(vm::direction::up);
@@ -533,7 +539,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path(
       "Controls/Map view/Duplicate and move objects forward; Duplicate and move objects down"),
-    QObject::tr("Duplicate and Move Down"), ActionContext::AnyView | ActionContext::NodeSelection,
+    QObject::tr("Duplicate and Move Down"),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_PageDown),
     [](ActionExecutionContext& context) {
       context.view()->duplicateAndMoveObjects(vm::direction::down);
@@ -546,7 +553,8 @@ void ActionManager::createViewActions() {
   // applies to objects, vertices, handles (e.g. rotation center)
   createAction(
     IO::Path("Controls/Map view/Roll objects clockwise"), QObject::tr("Roll Clockwise"),
-    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool,
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool |
+      ActionContext::NoTool,
     QKeySequence(Qt::ALT + Qt::Key_Up),
     [](ActionExecutionContext& context) {
       context.view()->rotateObjects(vm::rotation_axis::roll, true);
@@ -557,7 +565,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Roll objects counter-clockwise"),
     QObject::tr("Roll Counter-clockwise"),
-    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool,
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool |
+      ActionContext::NoTool,
     QKeySequence(Qt::ALT + Qt::Key_Down),
     [](ActionExecutionContext& context) {
       context.view()->rotateObjects(vm::rotation_axis::roll, false);
@@ -567,7 +576,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Yaw objects clockwise"), QObject::tr("Yaw Clockwise"),
-    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool,
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool |
+      ActionContext::NoTool,
     QKeySequence(Qt::ALT + Qt::Key_Left),
     [](ActionExecutionContext& context) {
       context.view()->rotateObjects(vm::rotation_axis::yaw, true);
@@ -578,7 +588,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Yaw objects counter-clockwise"),
     QObject::tr("Yaw Counter-clockwise"),
-    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool,
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool |
+      ActionContext::NoTool,
     QKeySequence(Qt::ALT + Qt::Key_Right),
     [](ActionExecutionContext& context) {
       context.view()->rotateObjects(vm::rotation_axis::yaw, false);
@@ -588,7 +599,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Pitch objects clockwise"), QObject::tr("Pitch Clockwise"),
-    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool,
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool |
+      ActionContext::NoTool,
     QKeySequence(Qt::ALT + Qt::Key_PageUp),
     [](ActionExecutionContext& context) {
       context.view()->rotateObjects(vm::rotation_axis::pitch, true);
@@ -599,7 +611,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Pitch objects counter-clockwise"),
     QObject::tr("Pitch Counter-clockwise"),
-    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool,
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool |
+      ActionContext::NoTool,
     QKeySequence(Qt::ALT + Qt::Key_PageDown),
     [](ActionExecutionContext& context) {
       context.view()->rotateObjects(vm::rotation_axis::pitch, false);
@@ -611,7 +624,8 @@ void ActionManager::createViewActions() {
   /* ========== Texturing ========== */
   createAction(
     IO::Path("Controls/Map view/Move textures up"), QObject::tr("Move Textures Up"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::Key_Up),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::Key_Up),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::up, MapViewBase::TextureActionMode::Normal);
     },
@@ -620,7 +634,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures up (coarse)"),
-    QObject::tr("Move Textures Up (Coarse)"), ActionContext::View3D | ActionContext::FaceSelection,
+    QObject::tr("Move Textures Up (Coarse)"),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::SHIFT + Qt::Key_Up),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::up, MapViewBase::TextureActionMode::Coarse);
@@ -630,7 +645,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures up (fine)"), QObject::tr("Move Textures Up (Fine)"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::CTRL + Qt::Key_Up),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::CTRL + Qt::Key_Up),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::up, MapViewBase::TextureActionMode::Fine);
     },
@@ -639,7 +655,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures down"), QObject::tr("Move Textures Down"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::Key_Down),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::Key_Down),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::down, MapViewBase::TextureActionMode::Normal);
     },
@@ -649,7 +666,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Move textures down (coarse)"),
     QObject::tr("Move Textures Down (Coarse)"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::SHIFT + Qt::Key_Down),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::SHIFT + Qt::Key_Down),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::down, MapViewBase::TextureActionMode::Coarse);
     },
@@ -658,7 +676,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures down (fine)"),
-    QObject::tr("Move Textures Down (Fine)"), ActionContext::View3D | ActionContext::FaceSelection,
+    QObject::tr("Move Textures Down (Fine)"),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_Down),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::down, MapViewBase::TextureActionMode::Fine);
@@ -668,7 +687,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures left"), QObject::tr("Move Textures Left"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::Key_Left),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::Key_Left),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::left, MapViewBase::TextureActionMode::Normal);
     },
@@ -678,7 +698,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Move textures left (coarse)"),
     QObject::tr("Move Textures Left (Coarse)"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::SHIFT + Qt::Key_Left),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::SHIFT + Qt::Key_Left),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::left, MapViewBase::TextureActionMode::Coarse);
     },
@@ -687,7 +708,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures left (fine)"),
-    QObject::tr("Move Textures Left (Fine)"), ActionContext::View3D | ActionContext::FaceSelection,
+    QObject::tr("Move Textures Left (Fine)"),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_Left),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::left, MapViewBase::TextureActionMode::Fine);
@@ -697,7 +719,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures right"), QObject::tr("Move Textures Right"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::Key_Right),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::Key_Right),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::right, MapViewBase::TextureActionMode::Normal);
     },
@@ -707,7 +730,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Move textures right (coarse)"),
     QObject::tr("Move Textures Right (Coarse)"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::SHIFT + Qt::Key_Right),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::SHIFT + Qt::Key_Right),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::right, MapViewBase::TextureActionMode::Coarse);
     },
@@ -716,7 +740,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Move textures right (fine)"),
-    QObject::tr("Move Textures Right (Fine)"), ActionContext::View3D | ActionContext::FaceSelection,
+    QObject::tr("Move Textures Right (Fine)"),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_Right),
     [](ActionExecutionContext& context) {
       context.view()->moveTextures(vm::direction::right, MapViewBase::TextureActionMode::Fine);
@@ -726,7 +751,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Rotate textures clockwise"),
-    QObject::tr("Rotate Textures Clockwise"), ActionContext::View3D | ActionContext::FaceSelection,
+    QObject::tr("Rotate Textures Clockwise"),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::Key_PageUp),
     [](ActionExecutionContext& context) {
       context.view()->rotateTextures(true, MapViewBase::TextureActionMode::Normal);
@@ -737,7 +763,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Rotate textures clockwise (coarse)"),
     QObject::tr("Rotate Textures Clockwise (Coarse)"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::SHIFT + Qt::Key_PageUp),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::SHIFT + Qt::Key_PageUp),
     [](ActionExecutionContext& context) {
       context.view()->rotateTextures(true, MapViewBase::TextureActionMode::Coarse);
     },
@@ -747,7 +774,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Rotate textures clockwise (fine)"),
     QObject::tr("Rotate Textures Clockwise (Fine)"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::CTRL + Qt::Key_PageUp),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::CTRL + Qt::Key_PageUp),
     [](ActionExecutionContext& context) {
       context.view()->rotateTextures(true, MapViewBase::TextureActionMode::Fine);
     },
@@ -757,7 +785,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Rotate textures counter-clockwise"),
     QObject::tr("Rotate Textures Counter-clockwise"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::Key_PageDown),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::Key_PageDown),
     [](ActionExecutionContext& context) {
       context.view()->rotateTextures(false, MapViewBase::TextureActionMode::Normal);
     },
@@ -767,7 +796,7 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Rotate textures counter-clockwise (coarse)"),
     QObject::tr("Rotate Textures Counter-clockwise (Coarse)"),
-    ActionContext::View3D | ActionContext::FaceSelection,
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::SHIFT + Qt::Key_PageDown),
     [](ActionExecutionContext& context) {
       context.view()->rotateTextures(false, MapViewBase::TextureActionMode::Coarse);
@@ -778,7 +807,8 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Rotate textures counter-clockwise (fine)"),
     QObject::tr("Rotate Textures Counter-clockwise (Fine)"),
-    ActionContext::View3D | ActionContext::FaceSelection, QKeySequence(Qt::CTRL + Qt::Key_PageDown),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::CTRL + Qt::Key_PageDown),
     [](ActionExecutionContext& context) {
       context.view()->rotateTextures(false, MapViewBase::TextureActionMode::Fine);
     },
@@ -787,7 +817,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Reveal in texture browser"),
-    QObject::tr("Reveal in texture browser"), ActionContext::View3D | ActionContext::AnySelection,
+    QObject::tr("Reveal in texture browser"),
+    ActionContext::View3D | ActionContext::AnySelection | ActionContext::AnyOrNoTool,
     QKeySequence(),
     [](ActionExecutionContext& context) {
       context.frame()->revealTexture();
@@ -797,7 +828,8 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Flip textures horizontally"),
-    QObject::tr("Flip textures horizontally"), ActionContext::View3D | ActionContext::FaceSelection,
+    QObject::tr("Flip textures horizontally"),
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::Key_F),
     [](ActionExecutionContext& context) {
       context.view()->flipTextures(vm::direction::right);
@@ -807,7 +839,7 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Flip textures vertically"), QObject::tr("Flip textures vertically"),
-    ActionContext::View3D | ActionContext::FaceSelection,
+    ActionContext::View3D | ActionContext::FaceSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_F),
     [](ActionExecutionContext& context) {
       context.view()->flipTextures(vm::direction::up);
@@ -817,7 +849,7 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Reset texture alignment"), QObject::tr("Reset texture alignment"),
-    ActionContext::AnyView | ActionContext::AnyTool | ActionContext::AnySelection,
+    ActionContext::AnyView | ActionContext::AnySelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::SHIFT + Qt::Key_R),
     [](ActionExecutionContext& context) {
       context.view()->resetTextures();
@@ -828,7 +860,7 @@ void ActionManager::createViewActions() {
   createAction(
     IO::Path("Controls/Map view/Reset texture alignment to world aligned"),
     QObject::tr("Reset texture alignment to world aligned"),
-    ActionContext::AnyView | ActionContext::AnyTool | ActionContext::AnySelection,
+    ActionContext::AnyView | ActionContext::AnySelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_R),
     [](ActionExecutionContext& context) {
       context.view()->resetTexturesToWorld();
@@ -840,7 +872,8 @@ void ActionManager::createViewActions() {
   /* ========== Tag Actions ========== */
   createAction(
     IO::Path("Controls/Map view/Make structural"), QObject::tr("Make Structural"),
-    ActionContext::AnyView | ActionContext::NodeSelection, QKeySequence(Qt::ALT + Qt::Key_S),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::ALT + Qt::Key_S),
     [](ActionExecutionContext& context) {
       context.view()->makeStructural();
     },
@@ -1015,7 +1048,7 @@ void ActionManager::createViewActions() {
     });
   createAction(
     IO::Path("Controls/Map view/Reset camera zoom"), QObject::tr("Reset Camera Zoom"),
-    ActionContext::View3D | ActionContext::AnyTool | ActionContext::AnySelection,
+    ActionContext::View3D | ActionContext::AnyOrNoTool | ActionContext::AnyOrNoSelection,
     QKeySequence(Qt::SHIFT + Qt::Key_Escape),
     [](ActionExecutionContext& context) {
       context.view()->resetCameraZoom();
@@ -1429,7 +1462,8 @@ void ActionManager::createEditMenu() { /* ========== Edit Menu ========== */
 
   editMenu.addItem(createAction(
     IO::Path("Controls/Map view/Flip objects horizontally"), QObject::tr("Flip Horizontally"),
-    ActionContext::AnyView | ActionContext::NodeSelection, QKeySequence(Qt::CTRL + Qt::Key_F),
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
+    QKeySequence(Qt::CTRL + Qt::Key_F),
     [](ActionExecutionContext& context) {
       context.view()->flipObjects(vm::direction::left);
     },
@@ -1439,7 +1473,7 @@ void ActionManager::createEditMenu() { /* ========== Edit Menu ========== */
     IO::Path("FlipHorizontally.svg")));
   editMenu.addItem(createAction(
     IO::Path("Controls/Map view/Flip objects vertically"), QObject::tr("Flip Vertically"),
-    ActionContext::AnyView | ActionContext::NodeSelection,
+    ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyOrNoTool,
     QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_F),
     [](ActionExecutionContext& context) {
       context.view()->flipObjects(vm::direction::up);
diff --git a/common/src/View/Actions.h b/common/src/View/Actions.h
index 268b978296..ff1dd98f60 100644
--- a/common/src/View/Actions.h
+++ b/common/src/View/Actions.h
@@ -73,7 +73,7 @@ class Action {
 
 public:
   Action(
-    const IO::Path& preferencePath, const QString& label, const ActionContext::Type actionContext,
+    const IO::Path& preferencePath, const QString& label, ActionContext::Type actionContext,
     const QKeySequence& defaultShortcut, const IO::Path& iconPath, const QString& statusTip);
   virtual ~Action();
 
diff --git a/common/src/View/CameraTool3D.cpp b/common/src/View/CameraTool3D.cpp
index f1ce20f258..83c8d300b7 100644
--- a/common/src/View/CameraTool3D.cpp
+++ b/common/src/View/CameraTool3D.cpp
@@ -131,7 +131,13 @@ const Tool& CameraTool3D::tool() const {
 void CameraTool3D::mouseScroll(const InputState& inputState) {
   const float factor = pref(Preferences::CameraMouseWheelInvert) ? -1.0f : 1.0f;
   const bool zoom = inputState.modifierKeysPressed(ModifierKeys::MKShift);
-  const float scrollDist = inputState.scrollY();
+  const float scrollDist =
+#ifdef __APPLE__
+    inputState.modifierKeysPressed(ModifierKeys::MKShift) ? inputState.scrollX()
+                                                          : inputState.scrollY();
+#else
+    inputState.scrollY();
+#endif
 
   if (shouldMove(inputState)) {
     if (zoom) {
diff --git a/common/src/View/MapViewBase.cpp b/common/src/View/MapViewBase.cpp
index 2bd67dfb4e..4eb6f76043 100644
--- a/common/src/View/MapViewBase.cpp
+++ b/common/src/View/MapViewBase.cpp
@@ -788,29 +788,21 @@ void MapViewBase::focusOutEvent(QFocusEvent* event) {
 }
 
 ActionContext::Type MapViewBase::actionContext() const {
+  auto document = kdl::mem_lock(m_document);
+
   const auto derivedContext = doGetActionContext();
-  if (m_toolBox.createComplexBrushToolActive()) {
-    return derivedContext | ActionContext::CreateComplexBrushTool;
-  } else if (m_toolBox.clipToolActive()) {
-    return derivedContext | ActionContext::ClipTool;
-  } else if (m_toolBox.anyVertexToolActive()) {
-    return derivedContext | ActionContext::AnyVertexTool;
-  } else if (m_toolBox.rotateObjectsToolActive()) {
-    return derivedContext | ActionContext::RotateTool;
-  } else if (m_toolBox.scaleObjectsToolActive()) {
-    return derivedContext | ActionContext::ScaleTool;
-  } else if (m_toolBox.shearObjectsToolActive()) {
-    return derivedContext | ActionContext::ShearTool;
-  } else {
-    auto document = kdl::mem_lock(m_document);
-    if (document->hasSelectedNodes()) {
-      return derivedContext | ActionContext::NodeSelection;
-    } else if (document->hasSelectedBrushFaces()) {
-      return derivedContext | ActionContext::FaceSelection;
-    } else {
-      return derivedContext;
-    }
-  }
+  const auto toolContext = m_toolBox.createComplexBrushToolActive()
+                             ? ActionContext::CreateComplexBrushTool
+                           : m_toolBox.clipToolActive()          ? ActionContext::ClipTool
+                           : m_toolBox.anyVertexToolActive()     ? ActionContext::AnyVertexTool
+                           : m_toolBox.rotateObjectsToolActive() ? ActionContext::RotateTool
+                           : m_toolBox.scaleObjectsToolActive()  ? ActionContext::ScaleTool
+                           : m_toolBox.shearObjectsToolActive()  ? ActionContext::ShearTool
+                                                                 : ActionContext::NoTool;
+  const auto selectionContext = document->hasSelectedNodes()        ? ActionContext::NodeSelection
+                                : document->hasSelectedBrushFaces() ? ActionContext::FaceSelection
+                                                                    : ActionContext::NoSelection;
+  return derivedContext | toolContext | selectionContext;
 }
 
 void MapViewBase::doFlashSelection() {


Test patch:
diff --git a/common/test/CMakeLists.txt b/common/test/CMakeLists.txt
index b3197b0a2b..32721b3b97 100644
--- a/common/test/CMakeLists.txt
+++ b/common/test/CMakeLists.txt
@@ -92,6 +92,7 @@ set(COMMON_TEST_SOURCE
         "${COMMON_TEST_SOURCE_DIR}/Renderer/CameraTest.cpp"
         "${COMMON_TEST_SOURCE_DIR}/Renderer/VertexTest.cpp"
         "${COMMON_TEST_SOURCE_DIR}/View/AddNodesTest.cpp"
+        "${COMMON_TEST_SOURCE_DIR}/View/ActionContextTest.cpp"
         "${COMMON_TEST_SOURCE_DIR}/View/AutosaverTest.cpp"
         "${COMMON_TEST_SOURCE_DIR}/View/ChangeBrushFaceAttributesTest.cpp"
         "${COMMON_TEST_SOURCE_DIR}/View/ClipToolControllerTest.cpp"
diff --git a/common/test/src/View/ActionContextTest.cpp b/common/test/src/View/ActionContextTest.cpp
new file mode 100644
index 0000000000..f3aab4074b
--- /dev/null
+++ b/common/test/src/View/ActionContextTest.cpp
@@ -0,0 +1,115 @@
+/*
+ Copyright (C) 2022 Kristian Duske
+
+ This file is part of TrenchBroom.
+
+ TrenchBroom is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ TrenchBroom is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "View/ActionContext.h"
+
+#include "Catch2.h"
+
+namespace TrenchBroom {
+namespace View {
+TEST_CASE("actionContextMatches_WithMask") {
+  using T = std::tuple<ActionContext::Type, ActionContext::Type, ActionContext::Type, bool>;
+  const auto [lhs, rhs, mask, expected] = GENERATE(values<T>({
+    // clang-format off
+    {ActionContext::View3D,     ActionContext::View2D,  ActionContext::AnyView, false},
+    {ActionContext::View3D,     ActionContext::View3D,  ActionContext::AnyView, true},
+    {ActionContext::View3D,     ActionContext::View3D,  ActionContext::AnyTool, false},
+    {ActionContext::AnyView,    ActionContext::View2D,  ActionContext::AnyView, true},
+    {ActionContext::AnyView,    ActionContext::View3D,  ActionContext::AnyView, true},
+    {ActionContext::AnyView,    ActionContext::View3D,  ActionContext::AnyTool, false},
+    {ActionContext::RotateTool, ActionContext::Any,     ActionContext::AnyTool, true},
+    {ActionContext::View3D,     ActionContext::AnyTool, ActionContext::AnyTool, false},
+    // clang-format on
+  }));
+
+  CAPTURE(actionContextName(lhs), actionContextName(rhs), actionContextName(mask));
+
+  CHECK(actionContextMatches(lhs, rhs, mask) == expected);
+}
+
+TEST_CASE("actionContextMatches") {
+  using T = std::tuple<ActionContext::Type, ActionContext::Type, bool>;
+  const auto [lhs, rhs, expected] = GENERATE(values<T>({
+    // clang-format off
+    // required context                                                                 actual context
+    {ActionContext::Any,                                                                ActionContext::View2D | ActionContext::NoSelection | ActionContext::NoTool,       true},
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       true},
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     true},
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   true},
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, true},
+
+    {ActionContext::View2D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       false},
+    {ActionContext::View2D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     false},
+    {ActionContext::View2D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::View2D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, false},
+
+    {ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       true},
+    {ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     false},
+    {ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,        ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, false},
+
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::NoTool,       ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       true},
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::NoTool,       ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     false},
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::NoTool,       ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::NoTool,       ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, false},
+
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       true},
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     true},
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   true},
+    {ActionContext::Any,                                                                ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, true},
+
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::NoTool,     ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::NoTool,     ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     true},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::NoTool,     ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::NoTool,     ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, false},
+
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::NoTool,      ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       false},
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::NoTool,      ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     true},
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::NoTool,      ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::NoTool,      ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, false},
+
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::RotateTool,   ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       false},
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::RotateTool,   ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     false},
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::RotateTool,   ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   true},
+    {ActionContext::AnyView | ActionContext::NoSelection | ActionContext::RotateTool,   ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, false},
+
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool, ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool, ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool, ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::RotateTool, ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, true},
+
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::RotateTool,  ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       false},
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::RotateTool,  ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     false},
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::RotateTool,  ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::AnyView | ActionContext::AnySelection | ActionContext::RotateTool,  ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, true},
+
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyTool,    ActionContext::View3D | ActionContext::NoSelection | ActionContext::NoTool,       false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyTool,    ActionContext::View3D | ActionContext::NodeSelection | ActionContext::NoTool,     false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyTool,    ActionContext::View3D | ActionContext::NoSelection | ActionContext::RotateTool,   false},
+    {ActionContext::AnyView | ActionContext::NodeSelection | ActionContext::AnyTool,    ActionContext::View3D | ActionContext::NodeSelection | ActionContext::RotateTool, true},
+    // clang-format on
+  }));
+
+  CAPTURE(actionContextName(lhs), actionContextName(rhs));
+
+  CHECK(actionContextMatches(lhs, rhs) == expected);
+}
+
+} // namespace View
+} // namespace TrenchBroom