Skip to content

Commit

Permalink
#5761: Add Cut command and corresponding unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
codereader committed Apr 3, 2022
1 parent 784a3e0 commit 2d14d45
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 15 deletions.
1 change: 1 addition & 0 deletions install/input.xml
Expand Up @@ -48,6 +48,7 @@
<shortcut command="Undo" key="Z" modifiers="CONTROL" />
<shortcut command="Redo" key="Y" modifiers="CONTROL" />
<shortcut command="Copy" key="C" modifiers="CONTROL" />
<shortcut command="Cut" key="X" modifiers="CONTROL" />
<shortcut command="Paste" key="V" modifiers="CONTROL" />
<shortcut command="PasteToCamera" key="V" modifiers="ALT" />
<shortcut command="CloneSelection" key="space" />
Expand Down
7 changes: 4 additions & 3 deletions radiantcore/selection/algorithm/General.cpp
Expand Up @@ -1007,9 +1007,10 @@ void registerCommands()

GlobalCommandSystem().addCommand("CreateDecalsForFaces", createDecalsForSelectedFaces);

GlobalCommandSystem().addCommand("Copy", selection::clipboard::copy);
GlobalCommandSystem().addCommand("Paste", selection::clipboard::paste);
GlobalCommandSystem().addCommand("PasteToCamera", selection::clipboard::pasteToCamera);
GlobalCommandSystem().addCommand("Copy", clipboard::copy);
GlobalCommandSystem().addCommand("Cut", clipboard::cut);
GlobalCommandSystem().addCommand("Paste", clipboard::paste);
GlobalCommandSystem().addCommand("PasteToCamera", clipboard::pasteToCamera);

GlobalCommandSystem().addCommand("ConnectSelection", connectSelectedEntities);
GlobalCommandSystem().addCommand("BindSelection", bindEntities);
Expand Down
50 changes: 39 additions & 11 deletions radiantcore/selection/clipboard/Clipboard.cpp
Expand Up @@ -35,6 +35,19 @@ void pasteToMap()
map::algorithm::importFromStream(stream);
}

void copySelectedMapElementsToClipboard()
{
// When exporting to the system clipboard, use the portable format
auto format = GlobalMapFormatManager().getMapFormatByName(map::PORTABLE_MAP_FORMAT_NAME);

// Stream selected objects into a stringstream
std::stringstream out;
GlobalMap().exportSelected(out, format);

// Copy the resulting string to the clipboard
GlobalClipboard().setString(out.str());
}

void copy(const cmd::ArgumentList& args)
{
if (FaceInstance::Selection().empty())
Expand All @@ -50,16 +63,7 @@ void copy(const cmd::ArgumentList& args)
return;
}

// When exporting to the system clipboard, use the portable format
auto format = GlobalMapFormatManager().getMapFormatByName(map::PORTABLE_MAP_FORMAT_NAME);

// Stream selected objects into a stringstream
std::stringstream out;
GlobalMap().exportSelected(out, format);

// Copy the resulting string to the clipboard
GlobalClipboard().setString(out.str());

copySelectedMapElementsToClipboard();
map::OperationMessage::Send(_("Selection copied to Clipboard"));
}
else
Expand All @@ -69,6 +73,30 @@ void copy(const cmd::ArgumentList& args)
}
}

void cut(const cmd::ArgumentList& args)
{
if (!module::GlobalModuleRegistry().moduleExists(MODULE_CLIPBOARD))
{
throw cmd::ExecutionNotPossible(_("No clipboard module attached, cannot perform this action."));
}

if (!FaceInstance::Selection().empty())
{
throw cmd::ExecutionNotPossible(_("Cannot cut selected Faces."));
}

if (GlobalSelectionSystem().countSelected() == 0)
{
map::OperationMessage::Send(_("Nothing to cut"));
return;
}

UndoableCommand cmd("Cut Selection");

copySelectedMapElementsToClipboard();
algorithm::deleteSelection();
}

std::string getMaterialNameFromClipboard()
{
if (!module::GlobalModuleRegistry().moduleExists(MODULE_CLIPBOARD))
Expand Down Expand Up @@ -111,7 +139,7 @@ void paste(const cmd::ArgumentList& args)
}

// Try to parse the map and apply it
UndoableCommand undo("paste");
UndoableCommand undo("Paste");
pasteToMap();
}
else
Expand Down
6 changes: 6 additions & 0 deletions radiantcore/selection/clipboard/Clipboard.h
Expand Up @@ -20,6 +20,12 @@ void pasteToMap();
*/
void copy(const cmd::ArgumentList& args);

/**
* Cuts the current map selection to the clipboard (in map format).
* Only valid for non-component selections.
*/
void cut(const cmd::ArgumentList& args);

/**
* Either pastes the clipboard contents to the current map
* or (when faces are selected component-wise) applies the previously
Expand Down
40 changes: 39 additions & 1 deletion test/Selection.cpp
Expand Up @@ -512,6 +512,21 @@ TEST_F(ClipboardTest, CopyEmptySelection)
EXPECT_TRUE(operationMonitor.messageReceived()) << "Command should have sent out an OperationMessage";
}

TEST_F(ClipboardTest, CutEmptySelection)
{
EXPECT_EQ(GlobalSelectionSystem().countSelected(), 0) << "Should start with an empty selection";

// Monitor radiant to catch the messages
CommandFailureHelper helper;
MapOperationMonitor operationMonitor;

// This should do nothing, and it should not throw any execution failures neither
GlobalCommandSystem().executeCommand("Cut");

EXPECT_FALSE(helper.messageReceived()) << "Command execution shouldn't have failed";
EXPECT_TRUE(operationMonitor.messageReceived()) << "Command should have sent out an OperationMessage";
}

TEST_F(ClipboardTest, CopyNonEmptySelection)
{
auto worldspawn = GlobalMapModule().findOrInsertWorldspawn();
Expand All @@ -523,7 +538,6 @@ TEST_F(ClipboardTest, CopyNonEmptySelection)
CommandFailureHelper helper;
MapOperationMonitor operationMonitor;

// This should do nothing, and it should not throw any execution failures neither
GlobalCommandSystem().executeCommand("Copy");

EXPECT_FALSE(helper.messageReceived()) << "Command execution should not have failed";
Expand All @@ -533,6 +547,30 @@ TEST_F(ClipboardTest, CopyNonEmptySelection)
algorithm::assertStringIsMapxFile(GlobalClipboard().getString());
}

TEST_F(ClipboardTest, CutNonEmptySelection)
{
auto worldspawn = GlobalMapModule().findOrInsertWorldspawn();
auto brush = algorithm::createCubicBrush(worldspawn);

Node_setSelected(brush, true);

// Monitor radiant to catch the messages
CommandFailureHelper helper;
MapOperationMonitor operationMonitor;

GlobalCommandSystem().executeCommand("Cut");

EXPECT_FALSE(helper.messageReceived()) << "Command execution should not have failed";
EXPECT_TRUE(operationMonitor.messageReceived()) << "Command should have sent out an OperationMessage";

// Check the clipboard contents, it should contain a mapx file
algorithm::assertStringIsMapxFile(GlobalClipboard().getString());

EXPECT_FALSE(Node_isSelected(brush));
EXPECT_FALSE(brush->getParent()) << "Brush should have been removed from the map";
EXPECT_EQ(GlobalSelectionSystem().countSelected(), 0) << "Selection should now be empty";
}

TEST_F(ClipboardTest, CopyFaceSelection)
{
// Create a brush and select a single face
Expand Down

0 comments on commit 2d14d45

Please sign in to comment.