Files
qt5-legatus-legacy/Lts/Common/LtsWidgets/lts_dialog.h
T
2026-08-13 18:33:10 +02:00

542 lines
17 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Dialog for the new Layout system
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include "lts_base.h"
#include "lts_widgets_export.h"
#include "ui_requirement.h"
#include "lts_dialog_button_box.h" // so that LtsDialogs don't have to include it
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QPushButton>
#include <QLineEdit>
#include <QComboBox>
#include <QCheckBox>
#include <QSpinBox>
#include <QDateTimeEdit>
#include <QSlider>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QWidget>
#include <QRadioButton>
#include <QPointer>
#include <QDebug>
namespace LtsWidgets
{
class TopicInfo
{
public:
int entityId() const { return m_entityId; }
const QString &topicName() const { return m_topicName; }
bool isLocked() const { return m_locked; }
bool isValid() const { return m_valid; }
void setEntity(int entityId) { m_entityId = entityId; }
void setTopicName(const QString &topicName) { m_topicName = topicName; }
void setLocked(bool locked = true) { m_locked = locked; }
void setValid() { m_valid = true; }
private:
QString m_topicName;
int m_entityId{0};
bool m_locked{false};
bool m_valid{false};
};
class LtsBaseLayoutWidget;
class LtsDialogHelper;
class LtsProgressDialogHelper;
class LtsDialogButtonBox;
class LtsToolBarArea;
class OverlayContainer;
enum class ButtonCombination
{
OkApplyCancel, // Ok saves changes, closes the Dialog - Apply only saves changes, does not close the Dialog - Cancel closes the Dialog if there are no changes made
OkCancel, // TODO: rm
/* [deprecated => do not use! use 'OkApplyCancel' instead and tell the Dialog that 'this is a new element' by giving the
* translate-method inside the retranslate-method inside your LtsDialog either an empty/new dds element or by setting
* 'isNew' to true on one of the overloaded translate-methods
* OLD: Special case: only use if you are 100% sure what this is => example: New Message: there is only a SEND button, if sent, the dialog MUST be closed
*/
CloseOnly, // For Dialogs that do not save anything
SeamlessDialog, // No buttons, no margins!
};
/**
* @brief The LtsDialog class provides a Dialog for the Lts Layout System and is the equivalent to the QDialog. An LtsDialog can be added to a LayoutArea to interact with the user.
* WARNING: do not add widgets once the window is shown, as the ui requirements cannot be triggered after this point.
*/
class LTSWIDGETS_EXPORT LtsDialog : public QWidget
{
Q_OBJECT
Q_PROPERTY(bool unchangeable READ unchangeable WRITE setUnchangeable)
public:
static int s_object_count;
/**
* @brief LtsDialog TODO: !
* @param parent
*/
explicit LtsDialog(QWidget *parent = nullptr, ButtonCombination buttonCombination = ButtonCombination::CloseOnly, bool closeable = true);
~LtsDialog();
/**
* @brief getHelper returns the internal helper class which contains this LtsDialog on it (do not use, for LtsTabWidget's addTab only!)
* @return class internal helper class
*/
LtsDialogHelper *getHelper();
bool hasUnsavedChanges() const { return m_changesMade || m_currentlyBlocking; }
/**
* @brief retranslate is called on first show and when Qt is told to retranslate the whole application, should be used in all LtsDialogs
* NOTE: Everything inside this method does NOT trigger slotChangesMade (which is good ;). Is called by the framework.
* NOTE: Do not call retranslate yourself. Not even if you've overriden it. That won't work. The frameworks needs to call it.
*/
virtual void retranslate() = 0;
void closeRequest(); // call this from outside of this class to request the close of this dialog (save request dialog is shown if there are unsaved changes)
void setUnchangeable(bool unchangeable = true);
bool unchangeable() const { return m_unchangeableDialog; }
/**
* @brief addDialog
* @param dialog
* @param menuGroup use m_menuGroup / menuGroup() << This is NOT the Tab Title - override 'retranslate' for Tab Titles
* @param identifier use QStringLiteral - example: "exmEditDialog" << This is NOT the Tab Title - override 'retranslate' for Tab Titles
* @param layoutArea
* @return false if dialog / tab already exists - determined by menuGroup + identifier - and so the Dialog / Tab is not added
*/
bool addDialog(LtsDialog *dialog, const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea = LayoutArea::eDialogTab, bool setClosable = true, bool focusNewTab = true);
Q_DECL_DEPRECATED bool addDialogFixed(LtsDialog *dialog, const char *menuGroupUntranslated, const QString &identifier, const LayoutArea &layoutArea = LayoutArea::eDialogTab, bool setClosable = true, bool focusNewTab = true);
bool focusDialog(const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea);
bool amIFocused() const;
void setBaseLayoutWidget(LtsBaseLayoutWidget *baseLayoutWidget); // Do not use.
void __unchange__() { resetChanges(); } // Do not use.
void setClosable(bool closable) { m_isClosable = closable; }
bool isClosable() const { return m_isClosable; }
virtual void aboutToClose() {} // called just before the dialog is closed
void setMenuGroup(const QString &menuGroup) { m_menuGroup = menuGroup; } // Do not call. Called by Framework.
const QString &menuGroup() const { return m_menuGroup; }
void setIdentifier(const QString &identifier); // Do not call. Called by Framework.
virtual void tableColumnOrganiserSetting(){};
const QString &identifier() const { return m_identifier; }
void setOverlay(QWidget *widget);
void unsetOverlay();
/**
* @brief Sets the current width of the given widget as minimum width.
* Useful for Labels showing changing values to not jitter when then value changes
* Therefore you have to make sure, that the biggest (textual) value is set when calling this function
* @param Widget to set fixed width for
*/
static inline void setMinWidthForWidgetHelper(QWidget *w)
{
int width = w->sizeHint().width();
w->setMinimumWidth(width);
}
QString windowTitle() const
{
return m_title;
}
// neu tryLockTopic methode mit n Parametern, je nach Einsatzfall:
// ( LtsWidgets::TopicInfo )
// Diese n Überladungen entsprechen jeweils eine Lock-Bedarfsfall.
// a) Dlg über pro Exercise sperren ("Exercise Purpose")
// b) Dlg über pro Exercise, PartyId sperren ("Edit Partitipants")
// jeder PartyChanged @ "Edit Partitipants" aktualisieren
// c) 2 verschiedene Dialog (pro Exercise) -> nur einer ReadWrite
// In beiden Dialog DENSELBEN topicName nutzen
// "Gesperrt von anzeigen"
// a)
void tryLockTopicNew();
// b)
virtual void tryLockTopicNew (const QString &topicName, int entityId = -1);
void triggerTopicLock();
void setOnlineHelpFilename(const QString &fileName); // string without file extension !!
void setCurrentlyBlocking(bool currentlyBlocking = true) { m_currentlyBlocking = currentlyBlocking; }
signals:
/**
* @brief Emit this signal to start the onHideAnimated Animation
* Only works when the dialog is a child of the LtsBaseLayoutWidget
* @param layoutArea the LayoutArea that should hide with an animation
*/
void startMainSelect(LtsWidgets::LayoutArea);
/**
* @brief Emit this signal to start the onReShowAnimated Animation
* Only works when the dialog is a child of the LtsBaseLayoutWidget
* @param layoutArea the LayoutArea that should be shown again with an animation
*/
void endMainSelect(LtsWidgets::LayoutArea);
/**
* @brief signalChangesMade is emitted after slotChangesMade was called
* (if you want to enable the Ok and/or Apply button, use slotChangesMade)
* For a list of supported Widgets, see LtsDialog::showEvent.
*/
void signalChangesMade();
void signalHide(QObject *object); // tells the TabWidget to remove the tab
/**
* @brief Emit tells that all changes are reset
* and ok/apply buttons are now disabled
*/
void signalChangesReset(); //
void signalFocused();
void visibilityChanged(bool visible);
void signalLockTopic(const LtsWidgets::TopicInfo &topicInfo);
void signalShowOnlineHelp(const QString &fileName);
protected:
/**
* @brief translate #1 is the same as translate #2, but does not retranslate the ui. DO NOT USE until permission by Mike!
* @param title
*/
void translate(const QString &title);
template <typename T>
/**
* @brief translate #2 is the same as translate #3, but only sets the title by a single parameter. DO NOT USE until permission by Mike!
* @param ui
* @param title
*/
void translate(T *ui, const QString &title) // main translate method! Only use if Mike says it's okay!
{
translate(title);
if (ui)
ui->retranslateUi(this);
// after ui->retranslate() to make sure all labels have the correct content
m_uiRequirements->retranslate();
}
void hideEvent(QHideEvent *event) override
{
QWidget::hideEvent(event);
emit visibilityChanged(false);
}
template <typename T>
/**
* @brief translate #3 is the same as translate #4, but lacks Topic handling. The window title is a combination of the given parameters.
* @param ui
* @param custom
* @param isNew set to true if this dialog is about creating a new entry (equivalent to Topic id 0)
* @param title
*/
void translate(T *ui, const QString &custom, bool isNew, const QString &title)
{
QString newTitle;
if (!isNew)
{
if (unchangeable())
{
if (custom.isEmpty())
newTitle = (tr("Show %1").arg(title));
else
newTitle = (tr("Show %1: %2").arg(title, custom));
}
else
{
if (custom.isEmpty())
newTitle = (tr("Edit %1").arg(title));
else
newTitle = (tr("Edit %1: %2").arg(title, custom));
// tryLockTopic();
tryLockTopicNew();
}
}
else
{
newTitle = (tr("New %1").arg(title));
m_buttonBox->getButton(LtsWidgets::DialogButton::Apply)->hide(); // otherwise applying something after clicking apply results in another new object being created
m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setText(tr("Create")); // Deutsch: "Anlegen"
}
translate(ui, newTitle);
}
template <typename T>
/**
* @brief translate #4 is the same as translate #5, but without a Topic. 'customUniqueTopicName' and 'topicId' replace the missing Topic.
* @param ui
* @param customUniqueTopicName the name you would give the Topic class (e.g. "MeteoDataManagement::StaticWeather")
* @param topicId the Topic's id (e.g. 1) - use id smaller than 1 if it's a non-existent (new) entry
* @param custom
* @param title
*/
void translate(T* ui, const QString &customUniqueTopicName, int topicId, const QString &custom, const QString &title)
{
Q_UNUSED(customUniqueTopicName);
initTopicLockHandler(customUniqueTopicName, topicId);
translate(ui, custom /* can be empty */,
(topicId < 1 /* 0 is dds's equivalent to 'new', sadly there are cases where the id == -1, catch this case */),
title);
}
template <typename T, typename S>
/**
* @brief translate #5 is the same as translate #6, but uses 'custom' as Topic name (for Topics without a 'name' entry)
* @param ui
* @param data
* @param custom the Topic's name (e.g. "Test Exercise 42")
* @param title
*/
void translate(T *ui, const S &data, const QString &custom, const QString &title) // same as 'void translate(T *ui, const S &data, const QString &title)', but for topics without the 'name' entry
{
translate(ui, QString::fromUtf8(typeid(data).name()), data.id(), custom /* can be empty */, title);
}
template <typename T, typename S>
/**
* @brief translate #6 should be the most common and is easiest translate method for exactly one Topic.
* It locks the specific topic and creates a complex tab title with only a Topic and a general title.
* @param ui
* @param data the Topic which contains the id and the name (e.g. instance of ExerciseManagement::Exercise)
* @param title the topic of this dialog (e.g. "Exercise" if this Dialog is about a specific Exercise)
*/
void translate(T *ui, const S &data, const QString &title)
{
translate(ui, data, QString::fromStdString(data.name()), title); // -> translate #5
}
// this is called when the dialog was cancel to be able to reset changes made
virtual void dialogCanceled() {}
LtsBaseLayoutWidget* baseLayoutWidget();
void setNoTopicLock() { m_noLockTopic = true; } // Call in LtsDialog c_tor and nowhere else!
private:
friend class LtsDialogHelper;
bool m_changesMade;
bool m_currentlyBlocking { false }; // set to true if the dialog must not be closed because it is currently in a blocking call
bool m_firstShow;
QPointer<LtsBaseLayoutWidget> m_baseLayoutWidget; // "cannot" outlive BLW, but to be sure..
bool m_isClosable;
TopicInfo m_topicInfo;
public:
QString m_topicLockedBy;
private:
bool m_noLockTopic;
bool m_unchangeableDialog; // no '[*]' or asking in case of unsaved changes
QPointer<LtsDialogHelper> m_dialogHelper;
//LtsProgressDialogHelper *m_progressDialogHelper;
QString m_title;
QString m_onlineHelpFileName;
QString m_menuGroup;
QString m_identifier;
// overlay stuff:
QPointer<OverlayContainer> m_overlayContainer;
QPointer<QWidget> m_overlayWidget;
bool _apply();
void _close();
void _discard();
void _hide();
void resetChanges();
void addDebugMenuButton();
void tryLockTopic();
void initTopicLockHandler(const QString &topicName, int entityId);
/**
* @brief setWindowTitle sets this window title and of the Dialog Helper (use retranslate() instead!)
* @param title
*/
void setWindowTitle(const QString &title);
void updateWindowTitle();
void addConnections(QWidget *widget);
template <typename WidgetType, typename SignalFunc>
bool addChangesMadeSignal(QWidget *widget, SignalFunc signal)
{
WidgetType *concreteWidget = qobject_cast<WidgetType *>(widget);
if (!concreteWidget)
return false;
connect(concreteWidget, signal, this, &LtsDialog::slotChangesMade);
return true;
}
template <typename WidgetType>
void setWidgetsReadonly(bool readonly)
{
const auto children = findChildren<WidgetType *>();
for (WidgetType *widget : children )
{
widget->setProperty("lastReadOnlyState", widget->isReadOnly());
widget->setReadOnly(readonly);
}
}
template <typename WidgetType>
void setWidgetsDisabled(bool disabled)
{
const auto children = findChildren<WidgetType *>();
for (WidgetType *widget : children)
{
widget->setProperty("lastEnabledState", widget->isEnabled());
widget->setEnabled(!disabled);
}
}
template <typename WidgetType>
void restoreReadonly()
{
const auto children = findChildren<WidgetType *>();
for (WidgetType *widget : children)
{
QVariant lastState = widget->property("lastReadOnlyState");
if (lastState.isValid() && !lastState.isNull() && lastState.canConvert(QMetaType::Bool))
widget->setReadOnly(lastState.toBool());
}
}
template <typename WidgetType>
void restoreEnabled()
{
const auto children = findChildren<WidgetType *>();
for (WidgetType *widget : children)
{
QVariant lastState = widget->property("lastEnabledState");
if (lastState.isValid() && !lastState.isNull() && lastState.canConvert(QMetaType::Bool))
widget->setEnabled(lastState.toBool());
}
}
public slots:
/**
* @brief slotChangesMade emits 'signalChangesMade()' and tells the dialog that changes on editable Widgets were made. So that the Save-Dialog will be shown on Close.
* For a list of supported Widgets, see LtsDialog::showEvent.
*/
void slotChangesMade();
void slotHide(); // hides dialog, removes tab, does not delete dialog
void slotLock(const QString &lockedBy);
void slotUnlock();
private slots:
void slotApply(); // does not close! only saves.
void slotOkay(); // closes and saves. (calls 'apply()', then calls 'close()')
void slotClose(); // closes but does NOT save (but asks for it (SaveDialog) - if 'save' was selected by the user in the SaveDialog, then 'okay()' will be called).
//void slotSizeChanged(const QSize &size);
void onReadOnlyButtonClicked();
protected:
virtual void lock();
virtual void unlock();
virtual bool apply() { return true; } // reimpl to save your changes here, DO NOT CLOSE! return true if changes could be saved (this will close the dialog on 'okay') or return false if NOT (dialog will NOT be closed then)
void showEvent(QShowEvent *event) override;
Button askApplyDiscardCancel();
LtsDialogButtonBox *m_buttonBox;
UiRequirementList *m_uiRequirements;
NGL::INGLLogger &m_logger;
};
// support init WITHOUT parameter
template<typename T>
T *initDialog(T *dialog)
{
if (!dialog->init())
{
dialog->deleteLater();
return nullptr;
}
return dialog;
}
// support init WITH parameter
template<typename T, typename ... F>
T *initDialog(T *dialog, F&&... f)
{
if (!dialog->init(std::forward<F>(f)...))
{
dialog->deleteLater();
return nullptr;
}
return dialog;
}
template<typename T, typename ... F>
T *createThrowableDialog(F&&... f)
{
T *dlg = nullptr;
try
{
dlg = new T(std::forward<F>(f)...);
}
catch (const char *s)
{
fprintf(stderr, "Dialog could not be created: %s\n", s);
return nullptr;
}
catch (const QString &msg)
{
fprintf(stderr, "Dialog could not be created: %s\n", msg.toStdString().c_str());
return nullptr;
}
catch (...)
{
fprintf(stderr, "Dialog could not be created: unknown error\n");
return nullptr;
}
return dlg;
}
} // namespace LtsWidgets