Skip to content

Commit

Permalink
Improve directive handling
Browse files Browse the repository at this point in the history
Make a dedicated type for directives that need a response,
RequestDirective, along with related concepts.

Have response return from the postDirective() call for RequestDirective
types.

Use ADL to simplify visit pattern in frontend.
  • Loading branch information
oblivioncth committed Nov 14, 2024
1 parent ed5ee0b commit 4f45cba
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 101 deletions.
43 changes: 20 additions & 23 deletions app/gui/src/frontend/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@ QIcon& FrontendGui::trayExitIconFromResources() { static QIcon ico(u":/frontend/

//-Instance Functions------------------------------------------------------------------------------------------------------
//Private:
void FrontendGui::handleMessage(const DMessage& d)
void FrontendGui::handleDirective(const DMessage& d)
{
auto mb = prepareMessageBox(d);
mb->setAttribute(Qt::WA_DeleteOnClose);
mb->show();
}

void FrontendGui::handleError(const DError& d) { Qx::postError(d.error); }
void FrontendGui::handleDirective(const DError& d) { Qx::postError(d.error); }

void FrontendGui::handleProcedureStart(const DProcedureStart& d)
void FrontendGui::handleDirective(const DProcedureStart& d)
{
// Set label
mProgressDialog.setLabelText(d.label);
Expand All @@ -96,7 +96,7 @@ void FrontendGui::handleProcedureStart(const DProcedureStart& d)
mProgressDialog.setValue(0);
}

void FrontendGui::handleProcedureStop(const DProcedureStop& d)
void FrontendGui::handleDirective(const DProcedureStop& d)
{
Q_UNUSED(d);
/* Always reset the dialog regardless of whether it is visible or not as it may not be currently visible,
Expand All @@ -106,49 +106,46 @@ void FrontendGui::handleProcedureStop(const DProcedureStop& d)
mProgressDialog.reset();
}

void FrontendGui::handleProcedureProgress(const DProcedureProgress& d) { mProgressDialog.setValue(d.current); }
void FrontendGui::handleProcedureScale(const DProcedureScale& d) { mProgressDialog.setMaximum(d.max); }
void FrontendGui::handleStatusUpdate(const DStatusUpdate& d) { mStatusHeading = d.heading; mStatusMessage = d.message; }
void FrontendGui::handleDirective(const DProcedureProgress& d) { mProgressDialog.setValue(d.current); }
void FrontendGui::handleDirective(const DProcedureScale& d) { mProgressDialog.setMaximum(d.max); }
void FrontendGui::handleDirective(const DStatusUpdate& d) { mStatusHeading = d.heading; mStatusMessage = d.message; }

// Sync directive handlers
void FrontendGui::handleBlockingMessage(const DBlockingMessage& d)
void FrontendGui::handleDirective(const DBlockingMessage& d)
{
auto mb = prepareMessageBox(d);
mb->exec();
delete mb;
}

void FrontendGui::handleBlockingError(const DBlockingError& d)
// Request directive handlers
void FrontendGui::handleDirective(const DBlockingError& d, DBlockingError::Choice* response)
{
Q_ASSERT(d.response && d.choices != DBlockingError::Choice::NoChoice);
Q_ASSERT(d.choices != DBlockingError::Choice::NoChoice);
auto btns = choicesToButtons(d.choices);
auto def = smChoiceButtonMap.toRight(d.defaultChoice);
int rawRes = Qx::postBlockingError(d.error, btns, def);
*d.response = smChoiceButtonMap.toLeft(static_cast<QMessageBox::StandardButton>(rawRes));
*response = smChoiceButtonMap.toLeft(static_cast<QMessageBox::StandardButton>(rawRes));
}

void FrontendGui::handleSaveFilename(const DSaveFilename& d)
void FrontendGui::handleDirective(const DSaveFilename& d, QString* response)
{
Q_ASSERT(d.response);
*d.response = QFileDialog::getSaveFileName(nullptr, d.caption, d.dir, d.filter, d.selectedFilter);
*response = QFileDialog::getSaveFileName(nullptr, d.caption, d.dir, d.filter, d.selectedFilter);
}

void FrontendGui::handleExistingDir(const DExistingDir& d)
void FrontendGui::handleDirective(const DExistingDir& d, QString* response)
{
Q_ASSERT(d.response);
*d.response = QFileDialog::getExistingDirectory(nullptr, d.caption, d.startingDir);
*response = QFileDialog::getExistingDirectory(nullptr, d.caption, d.startingDir);
}

void FrontendGui::handleItemSelection(const DItemSelection& d)
void FrontendGui::handleDirective(const DItemSelection& d, QString* response)
{
Q_ASSERT(d.response);
*d.response = QInputDialog::getItem(nullptr, d.caption, d.label, d.items, 0, false);
*response = QInputDialog::getItem(nullptr, d.caption, d.label, d.items, 0, false);
}

void FrontendGui::handleYesOrNo(const DYesOrNo& d)
void FrontendGui::handleDirective(const DYesOrNo& d, bool* response)
{
Q_ASSERT(d.response);
*d.response = QMessageBox::question(nullptr, QString(), d.question) == QMessageBox::Yes;
*response = QMessageBox::question(nullptr, QString(), d.question) == QMessageBox::Yes;
}

bool FrontendGui::aboutToExit()
Expand Down
28 changes: 15 additions & 13 deletions app/gui/src/frontend/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,23 @@ static QIcon& trayExitIconFromResources();
//-Instance Functions------------------------------------------------------------------------------------------------------
private:
// Async directive handlers
void handleMessage(const DMessage& d) override;
void handleError(const DError& d) override;
void handleProcedureStart(const DProcedureStart& d) override;
void handleProcedureStop(const DProcedureStop& d) override;
void handleProcedureProgress(const DProcedureProgress& d) override;
void handleProcedureScale(const DProcedureScale& d) override;
void handleStatusUpdate(const DStatusUpdate& d) override;
void handleDirective(const DMessage& d) override;
void handleDirective(const DError& d) override;
void handleDirective(const DProcedureStart& d) override;
void handleDirective(const DProcedureStop& d) override;
void handleDirective(const DProcedureProgress& d) override;
void handleDirective(const DProcedureScale& d) override;
void handleDirective(const DStatusUpdate& d) override;

// Sync directive handlers
void handleBlockingMessage(const DBlockingMessage& d) override;
void handleBlockingError(const DBlockingError& d) override;
void handleSaveFilename(const DSaveFilename& d) override;
void handleExistingDir(const DExistingDir& d) override;
void handleItemSelection(const DItemSelection& d) override;
void handleYesOrNo(const DYesOrNo& d) override;
void handleDirective(const DBlockingMessage& d) override;

// Request directive handlers
void handleDirective(const DBlockingError& d, DBlockingError::Choice* response) override;
void handleDirective(const DSaveFilename& d, QString* response) override;
void handleDirective(const DExistingDir& d, QString* response) override;
void handleDirective(const DItemSelection& d, QString* response) override;
void handleDirective(const DYesOrNo& d, bool* response) override;

// Control
bool aboutToExit() override;
Expand Down
30 changes: 21 additions & 9 deletions lib/backend/include/kernel/directive.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ struct DBlockingMessage
bool selectable = false;
};

using SyncDirective = std::variant<DBlockingMessage>;

template<typename T>
concept SyncDirectiveT = requires(SyncDirective sd, T t) { sd = t; };

//-Blocking Directives With Response-------------------------------------------------------
struct DBlockingError
{
enum class Choice
Expand All @@ -113,7 +119,8 @@ struct DBlockingError
Qx::Error error;
Choices choices = Choice::Ok;
Choice defaultChoice = Choice::No;
Choice* response = nullptr;

using response_type = Choice;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(DBlockingError::Choices);

Expand All @@ -123,32 +130,35 @@ struct DSaveFilename
QString dir;
QString filter;
QString* selectedFilter = nullptr;
QString* response = nullptr;

using response_type = QString;
};

struct DExistingDir
{
QString caption;
QString startingDir;
QString* response = nullptr;

using response_type = QString;
};

struct DItemSelection
{
QString caption;
QString label;
QStringList items;
QString* response = nullptr;

using response_type = QString;
};

struct DYesOrNo
{
QString question;
bool* response = nullptr;

using response_type = bool;
};

using SyncDirective = std::variant<
DBlockingMessage,
using RequestDirective = std::variant<
DBlockingError,
DSaveFilename,
DExistingDir,
Expand All @@ -157,14 +167,16 @@ using SyncDirective = std::variant<
>;

template<typename T>
concept SyncDirectiveT = requires(SyncDirective sd, T t) { sd = t; };
concept RequestDirectiveT = requires(RequestDirective rd, T t) { rd = t; typename T::response_type; } &&
!std::same_as<typename T::response_type, void>;

//-Any---------------------------------------------------------------------
template<typename T>
concept DirectiveT = AsyncDirectiveT<T> || SyncDirectiveT<T>;
concept DirectiveT = AsyncDirectiveT<T> || SyncDirectiveT<T> || RequestDirectiveT<T>;

//-Metatype Declarations-----------------------------------------------------------------------------------------
Q_DECLARE_METATYPE(AsyncDirective);
Q_DECLARE_METATYPE(SyncDirective);
Q_DECLARE_METATYPE(RequestDirective);

#endif // DIRECTIVE_H
1 change: 1 addition & 0 deletions lib/backend/include/kernel/driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public slots:
// Director forwarders
void asyncDirectiveAccounced(const AsyncDirective& aDirective);
void syncDirectiveAccounced(const SyncDirective& sDirective);
void requestDirectiveAccounced(const RequestDirective& rDirective, void* response);
};

#endif // DRIVER_H
4 changes: 1 addition & 3 deletions lib/backend/src/command/c-link.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,9 @@ Qx::Error CLink::perform()
logEvent(LOG_EVENT_NO_PATH);

// Prompt user for path
QString selectedPath; // Default is empty dir
postDirective(DExistingDir{
QString selectedPath = postDirective(DExistingDir{
.caption = DIAG_CAPTION,
.startingDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
.response = &selectedPath
});

if(selectedPath.isEmpty())
Expand Down
3 changes: 1 addition & 2 deletions lib/backend/src/command/c-update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,7 @@ CUpdateError CUpdate::checkAndPrepareUpdate() const
return CUpdateError();
}

bool shouldUpdate = false;
postDirective<DYesOrNo>(QUES_UPDATE.arg(rd.name), &shouldUpdate);
bool shouldUpdate = postDirective<DYesOrNo>(QUES_UPDATE.arg(rd.name));
if(shouldUpdate)
{
logEvent(LOG_EVENT_UPDATE_ACCEPED);
Expand Down
6 changes: 2 additions & 4 deletions lib/backend/src/kernel/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,13 @@ Qx::Error Core::searchAndFilterEntity(QUuid& returnBuffer, QString name, bool ex
}

// Get user choice
QString userChoice = idChoices.front(); // Default
postDirective(DItemSelection{
QString userChoice = postDirective(DItemSelection{
.caption = MULTI_TITLE_SEL_CAP,
.label = MULTI_TITLE_SEL_LABEL,
.items = idChoices,
.response = &userChoice
});

if(userChoice.isNull())
if(userChoice.isNull()) // Default if diag is silenced
logEvent(LOG_EVENT_TITLE_SEL_CANCELED);
else
{
Expand Down
36 changes: 29 additions & 7 deletions lib/backend/src/kernel/director.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ class Director : public QObject
bool isLogOpen() const;
void logQtMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg);

// Helper
template<DirectiveT T>
auto ddr() // Default directive return helper
{
if constexpr(RequestDirectiveT<T>)
return typename T::response_type{};
}

public:
// Data
Verbosity verbosity() const;
Expand All @@ -122,37 +130,51 @@ class Director : public QObject

// Directives
template<DirectiveT T>
void postDirective(const QString& src, const T& directive)
auto postDirective(const QString& src, const T& directive)
{
// Special handling
if constexpr(Qx::any_of<T, DError, DBlockingError>)
{
logError(src, directive.error);
if(mVerbosity == Verbosity::Silent || (mVerbosity == Verbosity::Quiet && directive.error.severity() != Qx::Critical))
return;
return ddr<T>();
}
else
{
if(mVerbosity != Verbosity::Full)
return;
return ddr<T>();
}

// Send
if constexpr(AsyncDirectiveT<T>)
{
emit announceAsyncDirective(directive);
}
else if constexpr(SyncDirectiveT<T>)
emit announceSyncDirective(directive);
else
{
static_assert(SyncDirectiveT<T>);
emit announceSyncDirective(directive);
static_assert(RequestDirectiveT<T>);
/* Normally using a void pointer is basically obsolete, and the much newer std::any should be used
* instead; however, here we must use a pointer to get a result back since this is a signal, and the
* type safety of std::any is less important since the pointer is cast automatically by our scaffolding
* based on ResponseDirectiveT::response_type. So, mind as well take advantage of the performance of
* void* then anyway.
*
* Also: If a default response is needed other than the default provided by value-initialization, we
* can add a static member "DEFAULT_RESPONSE" to each RequestDirectiveT struct and use that here. If
* various defaults are needed on a case-by-case basis, that gets tricky as we'd need another postDirective()
* overload.
*/
auto response = ddr<T>();
emit announceRequestDirective(directive, &response);
return response;
}
}

//-Signals & Slots------------------------------------------------------------------------------------------------------------
signals:
void announceAsyncDirective(const AsyncDirective& aDirective);
void announceSyncDirective(const SyncDirective& sDirective);
void announceRequestDirective(const RequestDirective& rDirective, void* response);
};

#endif // DIRECTOR_H
12 changes: 6 additions & 6 deletions lib/backend/src/kernel/directorate.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ class Directorate
void logError(const Qx::Error& error) const;
void logEvent(const QString& event) const;

// These two only return a value when a RequestDirective is used, otherwise 'return' is ignored
template<DirectiveT T>
void postDirective(const T& t) const
[[nodiscard]] auto postDirective(const T& t) const
{
Q_ASSERT(mDirector);
mDirector->postDirective(name(), t);
return mDirector->postDirective(name(), t);
}

template<typename T, typename... Args>
void postDirective(Args&&... args) const
template<DirectiveT T, typename... Args>
[[nodiscard]] auto postDirective(Args&&... args) const
{
Q_ASSERT(mDirector);
mDirector->postDirective(name(), T{std::forward<Args>(args)...});
return postDirective(T{std::forward<Args>(args)...});
}

protected:
Expand Down
1 change: 1 addition & 0 deletions lib/backend/src/kernel/driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void DriverPrivate::init()
});
QObject::connect(dtor, &Director::announceAsyncDirective, q, &Driver::asyncDirectiveAccounced);
QObject::connect(dtor, &Director::announceSyncDirective, q, &Driver::syncDirectiveAccounced);
QObject::connect(dtor, &Director::announceRequestDirective, q, &Driver::requestDirectiveAccounced);

//-Setup deferred process manager------
/* NOTE: It looks like the manager should just be a stack member of TExec that is constructed
Expand Down
4 changes: 1 addition & 3 deletions lib/backend/src/task/t-download.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,10 @@ TDownload::TDownload(Core& core) :

// Download event handlers
connect(&mDownloadManager, &Qx::AsyncDownloadManager::sslErrors, this, [this](Qx::Error errorMsg, bool* ignore) {
DBlockingError::Choice choice;
postDirective(DBlockingError{
auto choice = postDirective(DBlockingError{
.error = errorMsg,
.choices = DBlockingError::Choice::Yes | DBlockingError::Choice::No,
.defaultChoice = DBlockingError::Choice::No,
.response = &choice
});
*ignore = choice == DBlockingError::Choice::Yes;
});
Expand Down
Loading

0 comments on commit 4f45cba

Please sign in to comment.