Skip to content

Commit

Permalink
#410: initial mechanism to enable/disable Commands
Browse files Browse the repository at this point in the history
The ICommandSystem interface now exposes methods to test whether a given
command is currently runnable. Currently only the CSG module is adding
commands with such a check. This is only a back-end mechanism; there is
not yet any support in the UI for showing the status of commands.
  • Loading branch information
Matthew Mott committed Jun 15, 2022
1 parent 527574e commit f71deeb
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 38 deletions.
38 changes: 34 additions & 4 deletions include/icommandsystem.h
Expand Up @@ -119,7 +119,7 @@ class Argument
return _type;
}

std::string getString() const
std::string getString() const
{
return _strValue;
}
Expand Down Expand Up @@ -160,7 +160,7 @@ class Argument
}
catch (std::logic_error&) {}

try
try
{
_doubleValue = std::stod(_strValue);
// cast succeeded
Expand Down Expand Up @@ -207,6 +207,12 @@ typedef std::vector<Argument> ArgumentList;
*/
typedef std::function<void (const ArgumentList&)> Function;

/**
* @brief Signature for a function which can test if a particular command should
* be enabled.
*/
using CheckFunction = std::function<bool()>;

// A command signature consists just of arguments, return type is always void
typedef std::vector<std::size_t> Signature;

Expand Down Expand Up @@ -254,8 +260,32 @@ class ICommandSystem: public RegisterableModule
virtual void addCommand(const std::string& name, Function func,
const Signature& signature = Signature()) = 0;

// Returns true if the named command exists
virtual bool commandExists(const std::string& name) = 0;
/**
* @brief Add a new command with a check function which can test if the
* command is currently runnable.
*
* This is aimed at commands which are not always available, e.g. because
* they require one or more objects to be selected. If the command is not
* currently available, the UI might choose to disable the button or menu
* item which invokes it.
*
* @param name
* Name of the command.
*
* @param func
* Function to call when the command is invoked.
*
* @param check
* Function to check whether the command should be enabled based on current
* application state.
*/
virtual void addWithCheck(const std::string& name, Function func, CheckFunction check) = 0;

/// Returns true if the named command exists
virtual bool commandExists(const std::string& name) = 0;

/// Return true if the named command is currently able to execute
virtual bool canExecute(const std::string& name) const = 0;

/**
* Remove a named command.
Expand Down
14 changes: 10 additions & 4 deletions radiantcore/brush/csg/CSG.cpp
Expand Up @@ -89,6 +89,11 @@ void hollowBrush(const BrushNodePtr& sourceBrush, bool makeRoom)
scene::removeNodeFromParent(sourceBrush);
}

bool haveSelectedBrushes()
{
return !selection::algorithm::getSelectedBrushes().empty();
}

void hollowSelectedBrushes(const cmd::ArgumentList& args) {
UndoableCommand undo("hollowSelectedBrushes");

Expand Down Expand Up @@ -493,10 +498,11 @@ void mergeSelectedBrushes(const cmd::ArgumentList& args)

void registerCommands()
{
GlobalCommandSystem().addCommand("CSGSubtract", subtractBrushesFromUnselected);
GlobalCommandSystem().addCommand("CSGMerge", mergeSelectedBrushes);
GlobalCommandSystem().addCommand("CSGHollow", hollowSelectedBrushes);
GlobalCommandSystem().addCommand("CSGRoom", makeRoomForSelectedBrushes);
GlobalCommandSystem().addWithCheck("CSGSubtract", subtractBrushesFromUnselected,
haveSelectedBrushes);
GlobalCommandSystem().addWithCheck("CSGMerge", mergeSelectedBrushes, haveSelectedBrushes);
GlobalCommandSystem().addWithCheck("CSGHollow", hollowSelectedBrushes, haveSelectedBrushes);
GlobalCommandSystem().addWithCheck("CSGRoom", makeRoomForSelectedBrushes, haveSelectedBrushes);
}

} // namespace algorithm
Expand Down
20 changes: 17 additions & 3 deletions radiantcore/commandsystem/Command.h
@@ -1,6 +1,7 @@
#ifndef _COMMAND_H_
#define _COMMAND_H_

#include "icommandsystem.h"
#include "itextstream.h"
#include "Executable.h"

Expand All @@ -15,17 +16,30 @@ class Command :
// The number and types of arguments to use
Signature _signature;

// Optional function to test if command can be run
CheckFunction _checkFunction;

public:
Command(const Function& function, const Signature& signature) :
Command(const Function& function, const Signature& signature, CheckFunction checkFunc = {}) :
_function(function),
_signature(signature)
_signature(signature),
_checkFunction(checkFunc)
{}

Signature getSignature() {
return _signature;
}

virtual void execute(const ArgumentList& args) {
/// Test if this command is able to be executed
virtual bool canExecute() const
{
if (_checkFunction)
return _checkFunction();
else
return true;
}

virtual void execute(const ArgumentList& args) {
// Check arguments
if (_signature.size() < args.size()) {
// Too many arguments, that's for sure
Expand Down
33 changes: 24 additions & 9 deletions radiantcore/commandsystem/CommandSystem.cpp
Expand Up @@ -13,6 +13,7 @@
#include "Statement.h"

#include <functional>
#include <memory>
#include "string/trim.h"
#include "string/predicate.h"
#include "module/StaticModule.h"
Expand Down Expand Up @@ -190,26 +191,40 @@ void CommandSystem::foreachCommand(const std::function<void(const std::string&)>
}
}

void CommandSystem::addCommandObject(const std::string& name, CommandPtr cmd)
{
if (auto result = _commands.emplace(name, cmd); !result.second) {
rError() << "Cannot register command " << name << ", this command is already registered."
<< std::endl;
}
}

void CommandSystem::addCommand(const std::string& name, Function func,
const Signature& signature)
{
// Create a new command
auto cmd = std::make_shared<Command>(func, signature);

auto result = _commands.emplace(name, cmd);
addCommandObject(name, std::make_shared<Command>(func, signature));
}

if (!result.second)
{
rError() << "Cannot register command " << name
<< ", this command is already registered." << std::endl;
}
void CommandSystem::addWithCheck(const std::string& name, Function func, CheckFunction check)
{
addCommandObject(name, std::make_shared<Command>(func, Signature(), check));
}

bool CommandSystem::commandExists(const std::string& name)
{
return _commands.find(name) != _commands.end();
}

bool CommandSystem::canExecute(const std::string& name) const
{
if (auto i = _commands.find(name); i != _commands.end()) {
return i->second->canExecute();
}

// A non-existent command cannot execute
return false;
}

void CommandSystem::removeCommand(const std::string& name)
{
auto i = _commands.find(name);
Expand Down
24 changes: 15 additions & 9 deletions radiantcore/commandsystem/CommandSystem.h
@@ -1,5 +1,6 @@
#pragma once

#include "commandsystem/Command.h"
#include "icommandsystem.h"
#include <map>
#include "Executable.h"
Expand All @@ -19,12 +20,16 @@ class CommandSystem :
public:
void foreachCommand(const std::function<void(const std::string&)>& functor) override;

void addCommand(const std::string& name, Function func, const Signature& signature = Signature()) override;
bool commandExists(const std::string& name) override;
void removeCommand(const std::string& name) override;
void addCommand(const std::string& name, Function func,
const Signature& signature = Signature()) override;
void addWithCheck(const std::string& name, Function func, CheckFunction check) override;
bool commandExists(const std::string& name) override;
bool canExecute(const std::string& name) const override;
void removeCommand(const std::string& name) override;

void addStatement(const std::string& statementName, const std::string& string, bool saveStatementToRegistry = true) override;
void foreachStatement(const std::function<void(const std::string&)>& functor, bool customStatementsOnly) override;
void addStatement(const std::string& statementName, const std::string& string,
bool saveStatementToRegistry = true) override;
void foreachStatement(const std::function<void(const std::string&)>& functor, bool customStatementsOnly) override;

// Retrieve the signature for the given command
Signature getSignature(const std::string& name) override;
Expand All @@ -50,12 +55,13 @@ class CommandSystem :
void printCmd(const ArgumentList& args);

// RegisterableModule implementation
const std::string& getName() const;
const StringSet& getDependencies() const;
void initialiseModule(const IApplicationContext& ctx);
void shutdownModule();
const std::string& getName() const override;
const StringSet& getDependencies() const override;
void initialiseModule(const IApplicationContext& ctx) override;
void shutdownModule() override;

private:
void addCommandObject(const std::string& name, CommandPtr cmd);

// Save/load bind strings from Registry
void loadBinds();
Expand Down
16 changes: 7 additions & 9 deletions radiantcore/commandsystem/Executable.h
Expand Up @@ -6,22 +6,20 @@

namespace cmd {

/// Common interface for Command and Statement objects
class Executable
{
public:
/**
* Destructor
*/
/// Destructor
virtual ~Executable() {}

/**
* greebo: Execute this object with the given set of arguments.
*/
/// Execute this object with the given set of arguments.
virtual void execute(const ArgumentList& args) = 0;

/**
* Returns the function signature (argumen types) of this executable.
*/
/// Test if this Executable can currently run
virtual bool canExecute() const { return true; }

/// Returns the function signature (argumen types) of this executable.
virtual Signature getSignature() = 0;
};
typedef std::shared_ptr<Executable> ExecutablePtr;
Expand Down

0 comments on commit f71deeb

Please sign in to comment.