Skip to content

Commit

Permalink
#5231: Decouple ModalProgressDialog handling from the MapExporter alg…
Browse files Browse the repository at this point in the history
…orithm. The MapExporter will broadcast messages for the UI to react to.
  • Loading branch information
codereader committed May 23, 2020
1 parent fd67df4 commit 959f147
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 7 deletions.
86 changes: 86 additions & 0 deletions libs/messages/MapExportOperation.h
@@ -0,0 +1,86 @@
#pragma once

#include <stdexcept>
#include "imessagebus.h"

namespace map
{

/**
* Message sent when the export code is about to start writing
* to its output stream.
*
* If any listener wishes to cancel the operation, the cancel()
* method is available, which will throw an internal exception.
*/
class ExportOperation :
public radiant::IMessage
{
public:
enum EventType
{
Started, // operation started, called once
Progress, // operation in progress, called 0..N times
Finished, // operation finished, called once
};

class OperationCancelled :
public std::runtime_error
{
public:
OperationCancelled() :
runtime_error("")
{}
};

private:
EventType _type;
float _progressFraction;
std::size_t _numTotalNodes;
std::string _message;

public:
ExportOperation(EventType type, std::size_t numTotalNodes) :
ExportOperation(type, numTotalNodes, 0)
{}

ExportOperation(EventType type, std::size_t numTotalNodes, float progressFraction) :
_type(type),
_progressFraction(progressFraction),
_numTotalNodes(numTotalNodes)
{}

const std::string& getText() const
{
return _message;
}

void setText(const std::string& message)
{
_message = message;
}

EventType getType() const
{
return _type;
}

float getProgressFraction() const
{
return _progressFraction;
}

std::size_t getNumTotalNodes() const
{
return _numTotalNodes;
}

// Call this to cancel the ongoing export process
// This will throw an exception, so control won't be returned to the caller
void cancelOperation()
{
throw OperationCancelled();
}
};

}
82 changes: 82 additions & 0 deletions libs/stream/ScopedFileOutputStream.h
@@ -0,0 +1,82 @@
#pragma once

#include <memory>
#include <fstream>
#include <string>
#include <cassert>
#include "util/Noncopyable.h"

namespace stream
{

/**
* Helper class wrapping around a std::ofstream. The instance can be empty,
* until a filename is been passed to it.
*
* If there is a contained stream, it is ensured that it is
* closed when the instance is destroyed.
*
* The underlying stream can be accessed through the dereference operator*()
*/
class ScopedOutputStream :
public std::ofstream
{
private:
std::unique_ptr<std::ofstream> _stream;

public:
// Constructs an empty stream object, no stream is opened
// until the open() method is invoked
ScopedFileOutputStream()
{}

// Constructs the wrapper and opens the internal stream
// using the given filename
ScopedFileOutputStream(const std::string& filename)
{
open(filename);
}

~ScopedFileOutputStream()
{
if (_stream)
{
_stream->flush();
_stream->close();
_stream.reset();
}
}

// Returns true if there's no underlying stream object instantiated
bool isEmpty() const
{
return static_cast<bool>(_stream);
}

bool isOpen() const
{
return _stream && _stream->is_open();
}

std::ostream& operator*()
{
assert(_stream);
return *_stream;
}

const std::ostream& operator*() const
{
assert(_stream);
return *_stream;
}

// Open the internal stream using the given filename
void open(const std::string& filename)
{
assert(!_stream); // no duplicate open calls

_stream.reset(new std::ofstream(filename));
}
};

}
27 changes: 20 additions & 7 deletions radiant/map/algorithm/MapExporter.cpp
Expand Up @@ -16,6 +16,7 @@
#include "string/string.h"

#include "ChildPrimitives.h"
#include "messages/MapExportOperation.h"

namespace map
{
Expand Down Expand Up @@ -68,7 +69,7 @@ void MapExporter::construct()
{
if (_totalNodeCount > 0 && GlobalMainFrame().isActiveApp())
{
enableProgressDialog();
//enableProgressDialog();
}

// Prepare the output stream
Expand All @@ -87,6 +88,9 @@ void MapExporter::construct()

void MapExporter::exportMap(const scene::INodePtr& root, const GraphTraversalFunc& traverse)
{
ExportOperation startedMsg(ExportOperation::Started, _totalNodeCount);
GlobalRadiantCore().getMessageBus().sendMessage(startedMsg);

try
{
auto mapRoot = std::dynamic_pointer_cast<scene::IMapRootNode>(root);
Expand Down Expand Up @@ -243,13 +247,19 @@ void MapExporter::onNodeProgress()

// Update the dialog text. This will throw an exception if the cancel
// button is clicked, which we must catch and handle.
if (_dialog && _dialogEventLimiter.readyForEvent())
if (_dialogEventLimiter.readyForEvent())
{
std::string text = fmt::format(_("Writing node {0:d}"), _curNodeCount);
_dialog->setTextAndFraction(
text,
static_cast<double>(_curNodeCount) / static_cast<double>(_totalNodeCount)
);
float progressFraction = static_cast<float>(_curNodeCount) / static_cast<float>(_totalNodeCount);

ExportOperation msg(ExportOperation::Progress, _totalNodeCount, progressFraction);
msg.setText(fmt::format(_("Writing node {0:d}"), _curNodeCount));

GlobalRadiantCore().getMessageBus().sendMessage(msg);

if (_dialog)
{
_dialog->setTextAndFraction(msg.getText(), progressFraction);
}
}
}

Expand All @@ -273,6 +283,9 @@ void MapExporter::finishScene()

// Re-evaluate all brushes, to update the Winding calculations
recalculateBrushWindings();

ExportOperation finishedMsg(ExportOperation::Finished, _totalNodeCount);
GlobalRadiantCore().getMessageBus().sendMessage(finishedMsg);
}

void MapExporter::recalculateBrushWindings()
Expand Down
60 changes: 60 additions & 0 deletions radiant/ui/MapExportProgressHandler.h
@@ -0,0 +1,60 @@
#pragma once

#include "iradiant.h"
#include <sigc++/functors/mem_fun.h>

#include "messages/MapExportOperation.h"
#include "wxutil/ModalProgressDialog.h"

namespace ui
{

class MapExportProgressHandler
{
private:
std::size_t _msgSubscription;

wxutil::ModalProgressDialogPtr _dialog;

public:
MapExportProgressHandler()
{
_msgSubscription = GlobalRadiantCore().getMessageBus().addListener(
radiant::TypeListener<map::ExportOperation>(
sigc::mem_fun(this, &MapExportProgressHandler::handleMapExportMessage)));
}

~MapExportProgressHandler()
{
GlobalRadiantCore().getMessageBus().removeListener(_msgSubscription);
}

private:
void handleMapExportMessage(map::ExportOperation& msg)
{
switch (msg.getType())
{
case map::ExportOperation::Started:
_dialog.reset(new wxutil::ModalProgressDialog(_("Writing map")));
break;

case map::ExportOperation::Progress:
if (msg.getNumTotalNodes() > 0)
{
_dialog->setTextAndFraction(msg.getText(), msg.getProgressFraction());
}
else
{
_dialog->setText(msg.getText());
_dialog->Pulse();
}
break;

case map::ExportOperation::Finished:
_dialog.reset();
break;
};
}
};

}
3 changes: 3 additions & 0 deletions radiant/ui/UserInterfaceModule.cpp
Expand Up @@ -43,6 +43,8 @@
#include "uimanager/colourscheme/ColourSchemeEditor.h"
#include "ui/layers/CreateLayerDialog.h"
#include "ui/patch/BulgePatchDialog.h"
#include "MapExportProgressHandler.h"
#include "messages/MapExportOperation.h"

namespace ui
{
Expand Down Expand Up @@ -138,6 +140,7 @@ void UserInterfaceModule::initialiseModule(const ApplicationContext& ctx)

_eClassColourManager.reset(new EntityClassColourManager);
_longOperationHandler.reset(new LongRunningOperationHandler);
_mapExportProgressHandler.reset(new MapExportProgressHandler);

initialiseEntitySettings();

Expand Down
2 changes: 2 additions & 0 deletions radiant/ui/UserInterfaceModule.h
Expand Up @@ -8,6 +8,7 @@

#include "EntityClassColourManager.h"
#include "LongRunningOperationHandler.h"
#include "MapExportProgressHandler.h"
#include "messages/CommandExecutionFailed.h"

namespace ui
Expand All @@ -26,6 +27,7 @@ class UserInterfaceModule :
private:
std::unique_ptr<EntityClassColourManager> _eClassColourManager;
std::unique_ptr<LongRunningOperationHandler> _longOperationHandler;
std::unique_ptr<MapExportProgressHandler> _mapExportProgressHandler;

sigc::connection _entitySettingsConn;
sigc::connection _coloursUpdatedConn;
Expand Down
1 change: 1 addition & 0 deletions tools/msvc/DarkRadiant.vcxproj
Expand Up @@ -1030,6 +1030,7 @@
<ClInclude Include="..\..\radiant\ui\layers\CreateLayerDialog.h" />
<ClInclude Include="..\..\radiant\ui\LongRunningOperationHandler.h" />
<ClInclude Include="..\..\radiant\ui\mainframe\TopLevelFrame.h" />
<ClInclude Include="..\..\radiant\ui\MapExportProgressHandler.h" />
<ClInclude Include="..\..\radiant\ui\mapinfo\LayerInfoTab.h" />
<ClInclude Include="..\..\radiant\ui\modelexport\ExportAsModelDialog.h" />
<ClInclude Include="..\..\radiant\ui\modelselector\MaterialsList.h" />
Expand Down
3 changes: 3 additions & 0 deletions tools/msvc/DarkRadiant.vcxproj.filters
Expand Up @@ -2130,6 +2130,9 @@
<ClInclude Include="..\..\radiant\patch\PatchModule.h">
<Filter>src\patch</Filter>
</ClInclude>
<ClInclude Include="..\..\radiant\ui\MapExportProgressHandler.h">
<Filter>src\ui</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\..\radiant\darkradiant.rc" />
Expand Down
1 change: 1 addition & 0 deletions tools/msvc/libs.vcxproj
Expand Up @@ -160,6 +160,7 @@
<ClInclude Include="..\..\libs\messages\CommandExecutionFailed.h" />
<ClInclude Include="..\..\libs\messages\GameConfigNeededMessage.h" />
<ClInclude Include="..\..\libs\messages\LongRunningOperationMessage.h" />
<ClInclude Include="..\..\libs\messages\MapExportOperation.h" />
<ClInclude Include="..\..\libs\messages\ScopedLongRunningOperation.h" />
<ClInclude Include="..\..\libs\ObservedSelectable.h" />
<ClInclude Include="..\..\libs\ObservedUndoable.h" />
Expand Down
3 changes: 3 additions & 0 deletions tools/msvc/libs.vcxproj.filters
Expand Up @@ -203,6 +203,9 @@
<ClInclude Include="..\..\libs\messages\CommandExecutionFailed.h">
<Filter>messages</Filter>
</ClInclude>
<ClInclude Include="..\..\libs\messages\MapExportOperation.h">
<Filter>messages</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="util">
Expand Down

0 comments on commit 959f147

Please sign in to comment.