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

820 lines
22 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Dialog for the new Layout system
* $Id$
* $HeadURL$
* $Author$
*/
#include "lts_dialog.h"
#include "lts_progress_dialog_helper.h"
#include "lts_message_dialog.h"
#include "lts_dialog_helper.h"
#include "lts_dialog_button_box.h"
#include "lts_base_layout_widget.h"
#include "lts_date_time_edit.h"
#include <QHBoxLayout>
#include <QProgressBar>
#include <QLineEdit>
#include <QComboBox>
#include <QCheckBox>
#include <QSpinBox>
#include <QDateTimeEdit>
#include <QSlider>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QWidget>
#include <QRadioButton>
#include <QTableView>
#include <QTableWidget>
#include <QListView>
#include <QListWidget>
#include <QSlider>
#include <QScroller>
#include <QScrollArea>
#include <QMenu>
#include <QInputDialog>
#include <QHBoxLayout>
//#include <QGraphicsBlurEffect>
#include <QPainter>
#include <QColor>
#include <QMetaMethod>
#include <QDebug>
#include <LtsSession/lts_session_data.h>
namespace LtsWidgets
{
int LtsDialog::s_object_count = 0;
class OverlayContainer : public QWidget
{
Q_OBJECT
public:
OverlayContainer(QWidget *parent);
protected:
void paintEvent(QPaintEvent *e) override;
};
OverlayContainer::OverlayContainer(QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_TranslucentBackground);
}
void OverlayContainer::paintEvent(QPaintEvent *e)
{
QPainter p(this);
p.fillRect(rect(), { 80, 80, 80, 200 });
QWidget::paintEvent(e);
}
// LtsDialog
LtsDialog::LtsDialog(QWidget *parent, ButtonCombination buttonCombination, bool closeable)
: QWidget(/*parent*/)
, m_changesMade(false)
, m_firstShow(true)
, m_baseLayoutWidget(nullptr)
, m_isClosable(closeable)
, m_noLockTopic(false)
, m_unchangeableDialog(false)
, m_dialogHelper(new LtsDialogHelper(nullptr)) // has no parent, but LtsTabWidget sets it to itself
//, m_progressDialogHelper(new LtsProgressDialogHelper(this))
// protected
, m_buttonBox(nullptr)
, m_uiRequirements(new UiRequirementList())
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsWidget.LtsDialog"))
{
Q_UNUSED(parent);
s_object_count++;
m_dialogHelper->setDialog(this); // shadow this dialog on the helper class
m_buttonBox = m_dialogHelper->getButtonBox(); // for new dialogs, so they can connect to it
// connect(this, &QObject::destroyed,
// new QObject, [this]()
// {
// qWarning() << (qintptr)this << "LtsDialog destroyed";
// });
// connect(m_dialogHelper, &QObject::destroyed,
// new QObject, [this]()
// {
// qWarning() << (qintptr)m_dialogHelper.data() << "LtsDialogHelper destroyed";
// });
addDebugMenuButton();
/*
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, // 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!
*/
connect(m_buttonBox->getButton(DialogButton::Close), &QPushButton::clicked, this, &LtsDialog::slotClose); // is (almost) always shown
switch(buttonCombination)
{
case ButtonCombination::OkCancel:
{
m_buttonBox->getButton(DialogButton::Close)->setText(tr("Cancel")); // kb den button enum zu "cancel" umzubenennen
connect(m_buttonBox->getButton(DialogButton::Ok), &QPushButton::clicked, this, &LtsDialog::slotOkay);
break;
}
case ButtonCombination::OkApplyCancel:
{
m_buttonBox->getButton(DialogButton::Close)->setText(tr("Cancel")); // kb den button enum zu "cancel" umzubenennen
connect(m_buttonBox->getButton(DialogButton::Ok), &QPushButton::clicked, this, &LtsDialog::slotOkay);
connect(m_buttonBox->getButton(DialogButton::Apply), &QPushButton::clicked, this, &LtsDialog::slotApply);
m_buttonBox->getButton(DialogButton::Apply)->setVisible(true);
break;
}
case ButtonCombination::CloseOnly:
{
m_buttonBox->getButton(DialogButton::Ok)->setVisible(false);
setUnchangeable();
break;
}
default:
case ButtonCombination::SeamlessDialog:
{
m_buttonBox->setVisible(false);
setContentsMargins(0,0,0,0);
m_dialogHelper->setContentsMargins(0,0,0,0);
QLayout *l = m_dialogHelper->layout();
if (l)
{
l->setContentsMargins(0,0,0,0);
l->setMargin(0);
}
else
{
qWarning() << "LtsDialog::c_tor: Dialog Helper has no layout to set margins";
}
break;
}
}
if (!closeable)
{
m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setVisible(false);
m_buttonBox->getButton(LtsWidgets::DialogButton::Close)->setVisible(false);
}
}
LtsDialog::~LtsDialog()
{
delete m_uiRequirements;
if (m_overlayWidget)
m_overlayWidget->setParent(nullptr);
// do not delete the dialog helper, as the dialog helper deletes this class
// also do not delete, as it is parented! => delete m_progressDialogHelper;
s_object_count--;
}
void LtsDialog::setOverlay(QWidget *widget)
{
if (m_overlayContainer)
{
if (m_overlayWidget)
{
m_overlayWidget->hide();
m_overlayWidget->setParent(nullptr);
m_overlayWidget->hide();
m_overlayWidget = nullptr;
// NOTE: we didn't create the widget so we don't delete it
}
m_overlayContainer->hide();
m_overlayContainer->setParent(nullptr);
m_overlayContainer->hide();
m_overlayContainer->deleteLater();
m_overlayContainer = nullptr;
}
// add a cool transparent background as an overlay to block the underlying dialog from user input, etc.
OverlayContainer *overlayContainer = new OverlayContainer(m_dialogHelper); // TODO: check race condition with dialogHelper's deletion / unparenting
connect(m_dialogHelper.data(), &LtsDialogHelper::signalSizeChanged,
overlayContainer, [overlayContainer](const QSize &size)
{
overlayContainer->setGeometry(QRect(QPoint(0, 0), size));
});
overlayContainer->setGeometry(QRect(QPoint(0, 0), m_dialogHelper->size()));
m_overlayContainer = overlayContainer;
QHBoxLayout *l = new QHBoxLayout;
m_overlayContainer->setLayout(l);
widget->setAutoFillBackground(true);
l->addWidget(widget, 0, Qt::AlignCenter);
widget->show();
connect(widget, &QWidget::destroyed,
m_overlayContainer.data(), &QObject::deleteLater); // every widget has its own container, so deleteLater is okay here
m_overlayWidget = widget;
m_overlayContainer->show();
m_overlayContainer->raise();
}
void LtsDialog::unsetOverlay()
{
if (m_overlayWidget)
m_overlayWidget->setParent(nullptr);
if (!m_overlayContainer)
return;
m_overlayContainer->setParent(nullptr);
m_overlayContainer->deleteLater();
}
LtsDialogHelper *LtsDialog::getHelper()
{
return m_dialogHelper;
}
LtsBaseLayoutWidget* LtsDialog::baseLayoutWidget()
{
return m_baseLayoutWidget;
}
void LtsDialog::setOnlineHelpFilename(const QString &fileName) // string without file extension !!
{
m_onlineHelpFileName = fileName;
QPushButton *btn = m_buttonBox->getButton(DialogButton::Help);
if (btn == nullptr) // paranoid
return;
if (LtsSessionData::LtsSessionData::getInstance()->isTablet() && strcmp(PROJECTBUILD, "guez") == 0)
return; //no more help in Guez OC
connect(btn, &QPushButton::clicked,
this, [this]()
{
emit signalShowOnlineHelp(m_onlineHelpFileName);
});
btn->setVisible(true);
}
void LtsDialog::addDebugMenuButton()
{
#ifdef LTS_DEBUG // debug menu
QMenu *menu = new QMenu(this);
menu->addAction(QStringLiteral("simulate changes"), this, &LtsDialog::slotChangesMade);
menu->addAction(QStringLiteral("unchange"), this, &LtsDialog::__unchange__);
menu->addAction(QStringLiteral("retranslate"), this, &LtsDialog::retranslate);
menu->addAction(QStringLiteral("force unlock"), this, [this]() { setUnchangeable(false); });
menu->addAction(QStringLiteral("set unchangeable"), this, [this]() { setUnchangeable(); });
menu->addAction(QStringLiteral("try lock"), this, &LtsDialog::tryLockTopic);
menu->addAction(QStringLiteral("set closable"), this, [this]() { m_isClosable = true; });
menu->addAction(QStringLiteral("set unclosable"), this, [this]() { m_isClosable = false; });
menu->addAction(QStringLiteral("clear requirements"), this, [this] { m_uiRequirements->clear(); });
menu->addAction(QStringLiteral("enable all button box buttons"), this, [this]()
{
const auto children = m_buttonBox->findChildren<QPushButton *>();
for (QPushButton *pb : children)
pb->setEnabled(true);
});
menu->addAction(QStringLiteral("show button box button .."), this, [this]()
{
QString buttonObjectName = QInputDialog::getText(this, QString(), QStringLiteral("Enter button object name"));
if (buttonObjectName.isEmpty())
return;
const QList<QPushButton *> buttons = m_buttonBox->findChildren<QPushButton *>(buttonObjectName);
int btnCount = buttons.count();
if (btnCount == 0)
{
LtsMessageDialog::showMessage(this, QStringLiteral("button not found"));
return;
}
if (btnCount > 1)
{
QStringList buttonStringList;
buttonStringList.reserve(buttons.size());
for (QPushButton *pb : buttons)
buttonStringList += pb->objectName();
LtsMessageDialog::showMessage(this, QStringLiteral("button ambiguous: %1").arg(buttonStringList.join(QStringLiteral(", "))));
return;
}
buttons.at(0)->setVisible(true);
});
QPushButton *debugBtn = m_buttonBox->getButton(DialogButton::_debug_);
debugBtn->setMenu(menu);
debugBtn->setVisible(true);
#endif
}
void LtsDialog::updateWindowTitle()
{
QString completeTitle = m_title;
if (m_changesMade)
completeTitle.insert(0, QStringLiteral("\u2022")); // add modified-indicator unicode 'bullet'
if (!m_topicLockedBy.isEmpty())
completeTitle.push_back(QString(QStringLiteral(" - READ-ONLY (%1)")).arg(m_topicLockedBy));
QWidget::setWindowTitle(completeTitle);
if (m_dialogHelper != nullptr)
m_dialogHelper->setWindowTitle(completeTitle);
}
void LtsDialog::setWindowTitle(const QString &title)
{
m_title = title;
updateWindowTitle();
}
void LtsDialog::setBaseLayoutWidget(LtsBaseLayoutWidget *baseLayoutWidget)
{
m_baseLayoutWidget = baseLayoutWidget;
}
void LtsDialog::setIdentifier(const QString &identifier)
{
m_identifier = identifier;
tableColumnOrganiserSetting();
}
void LtsDialog::addConnections(QWidget *widget)
{
if (addChangesMadeSignal<QLineEdit>(widget, &QLineEdit::textChanged)) return;
if (addChangesMadeSignal<QDateTimeEdit>(widget, &QDateTimeEdit::dateTimeChanged)) return;
if (addChangesMadeSignal<QComboBox>(widget, &QComboBox::currentTextChanged)) return;
if (addChangesMadeSignal<QCheckBox>(widget, &QCheckBox::stateChanged)) return;
if (addChangesMadeSignal<QSpinBox>(widget, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged))) return;
if (addChangesMadeSignal<QDoubleSpinBox>(widget, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged))) return;
if (addChangesMadeSignal<QSlider>(widget, &QSlider::valueChanged)) return;
if (addChangesMadeSignal<QPlainTextEdit>(widget, &QPlainTextEdit::textChanged)) return;
if (addChangesMadeSignal<QTextEdit>(widget, &QTextEdit::textChanged)) return; // TODO: rm - plain text only!
if (addChangesMadeSignal<QAbstractButton>(widget, &QAbstractButton::toggled)) return;
if (addChangesMadeSignal<LtsDateTimeEdit>(widget, &LtsDateTimeEdit::dateTimeChanged)) return;
}
void LtsDialog::setUnchangeable(bool unchangeable) // adjust structure to new functionality (NOTE: calling this twice without restoring will currently result in bad behaviour)
{
m_unchangeableDialog = unchangeable;
}
bool LtsDialog::addDialogFixed(LtsDialog *dialog, const char *menuGroupUntranslated, const QString &identifier, const LayoutArea &layoutArea, bool setClosable, bool focusNewTab)
{
return addDialog(dialog, /* >> */ tr(menuGroupUntranslated) /* << */, identifier, layoutArea, setClosable, focusNewTab);
}
bool LtsDialog::addDialog(LtsDialog *dialog, const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea, bool setClosable, bool focusNewTab)
{
if (!m_baseLayoutWidget)
{
qCritical() << "LtsDialog: addDialog: no base layout set - cannot add Dialog !";
return false;
}
return m_baseLayoutWidget->addWidget(dialog, menuGroup, identifier, layoutArea, setClosable, focusNewTab);
}
bool LtsDialog::amIFocused() const
{
if (!m_baseLayoutWidget)
{
qCritical() << "LtsDialog: addDialog: no base layout set - cannot check for focus!";
return false;
}
return m_baseLayoutWidget->isDialogFocused(this);
}
bool LtsDialog::focusDialog(const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea)
{
if (!m_baseLayoutWidget)
{
qCritical() << "LtsDialog: addDialog: no base layout set - cannot focus Dialog !";
return false;
}
return m_baseLayoutWidget->focusTab(menuGroup, identifier, layoutArea);
}
void LtsDialog::showEvent(QShowEvent *event)
{
emit visibilityChanged(true);
bool tmp = m_unchangeableDialog;
if (!m_firstShow) // do not reconnect if already connected (and do nothing if it's an unchangeable dialog)
{
QWidget::showEvent(event);
return;
}
m_unchangeableDialog = true; // do not monitor changes, yet
m_firstShow = false;
// the fact that all widgets are created before a window is shown, lets us connect to all editable widgets and listen to changes
// TODO: mit chris drueber reden ;) evtl. event listener, im widget event abfangen oder an ganz anderer stelle ?
QList<QWidget *> listenWidgets = findChildren<QWidget *>();
for (auto i = listenWidgets.begin(); i != listenWidgets.end(); ++i)
{
// TODO: more widgets
// TODO: NGLPositionEdit / LtsPositionEdit
QWidget *widget = *i;
QVariant isConst = widget->property("const");
if (isConst.isValid() && isConst.toBool())
continue;
if (!tmp)
addConnections(widget);
#ifdef TABLET_MODE
LtsWidgets::addTouchScroll(widget);
#endif
}
QWidget::showEvent(event);
m_unchangeableDialog = tmp; // start monitoring changes only after this point, as user changes may now be made
}
void LtsDialog::tryLockTopic()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
if (!m_topicInfo.isValid())
return;
if (m_topicInfo.isLocked())
{
WARNING_LOG(m_logger) << LOGFUNCTION << " tried to lock with an already locked topic info, return";
return;
}
if (m_noLockTopic)
return;
// case ParticipantEdit:
// LtsDialog::tryLockTopicNew is called by ParticipantEdit::onPartyChanged
// This slot is executed while
// - initialization (signal not connected)
// and
// - by user interaction (signal connected)
bool bIsConnected = isSignalConnected(QMetaMethod::fromSignal(&LtsDialog::signalLockTopic));
DEBUG_LOG(m_logger) << LOGFUNCTION
<< " signalLockTopic(): "
<< ", TopicName =" << m_topicInfo.topicName()
<< ", entity =" << m_topicInfo.entityId()
<< ", signalLockTopic isConnected=" << bIsConnected;
if (bIsConnected)
{
emit signalLockTopic(m_topicInfo); // blocks!
// TODO: setUnchangeable(true);
m_topicInfo.setLocked();
}
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void LtsDialog::initTopicLockHandler(const QString &topicName, int entityId)
{
//qWarning() << "initTopicLockHandler";
m_topicInfo.setTopicName(topicName);
m_topicInfo.setEntity(entityId);
m_topicInfo.setValid();
}
void LtsDialog::tryLockTopicNew()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
tryLockTopic();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void LtsDialog::tryLockTopicNew(const QString &topicName, int entityId)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START"
<< ", topicName='" << topicName << "'"
<< ", entityId=" << entityId;
// similar to "initTopicLockHandler"
if (m_topicInfo.entityId() != entityId)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " topicName='" << topicName << "'"
<< " change from " << m_topicInfo.entityId() << " to " << entityId;
m_topicInfo.setLocked(false);
}
m_topicInfo.setTopicName(topicName);
m_topicInfo.setEntity(entityId);
m_topicInfo.setValid();
tryLockTopic();
TRACE_LOG(m_logger) << LOGFUNCTION << " END"
<< ", topicName='" << topicName << "'"
<< ", entityId=" << entityId;
}
void LtsDialog::triggerTopicLock()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " !!trigger!!";
tryLockTopic();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void LtsDialog::translate(const QString &title) // Only use if Mike says it's okay!
{
bool isAlreadyUnchangeable = unchangeable();
if (!isAlreadyUnchangeable)
setUnchangeable(true); // retranslation is no change!
setWindowTitle(title);
// qCritical() << " m_topicLockedBy : " << m_topicLockedBy.lockHint
// << ", operatorId=" << m_topicLockedBy.operatorId
// << ", workstationId=" << m_topicLockedBy.workstationId;
if (!isAlreadyUnchangeable)
setUnchangeable(false); // retranslation done
{
bool isNowAlreadyUnchangeable = unchangeable();
QPushButton *b = m_buttonBox->getButton(DialogButton::ReadOnly);
if (b)
{
connect(b, &QPushButton::clicked, this, &LtsDialog::onReadOnlyButtonClicked, Qt::UniqueConnection);
// ExerciseDialog (CloseOnlyDialog -> no Ok-, Apply-Btn)
b->setVisible(isNowAlreadyUnchangeable && !m_topicLockedBy.isEmpty());
if (b->isVisible())
b->setToolTip(tr("Locked by: %1").arg(m_topicLockedBy));
else
b->setToolTip({});
}
}
}
void LtsDialog::slotLock(const QString &lockedBy)
{
setUnchangeable(true);
m_topicLockedBy = lockedBy;
lock();
retranslate();
}
void LtsDialog::slotUnlock()
{
setUnchangeable(false);
m_topicLockedBy.clear();
unlock();
retranslate();
}
void LtsDialog::lock()
{
setWidgetsReadonly<QLineEdit>(true);
setWidgetsReadonly<QDateTimeEdit>(true);
// setWidgetsCustom<QComboBox>([](QComboBox *cb, bool readonly) { if (cb->lineEdit()) cb->lineEdit()->setReadOnly(readonly); }, unchangeable);
setWidgetsReadonly<QSpinBox>(true);
setWidgetsReadonly<QDoubleSpinBox>(true);
setWidgetsReadonly<QPlainTextEdit>(true);
setWidgetsReadonly<QTextEdit>(true);
setWidgetsDisabled<QComboBox>(true);
setWidgetsDisabled<QCheckBox>(true);
setWidgetsDisabled<QRadioButton>(true);
setWidgetsDisabled<QSlider>(true);
setWidgetsDisabled<QTableView>(true);
setWidgetsDisabled<QTableWidget>(true);
setWidgetsDisabled<QListView>(true);
setWidgetsDisabled<QListWidget>(true);
setWidgetsDisabled<QPushButton>(true);
}
void LtsDialog::unlock()
{
restoreReadonly<QLineEdit>();
restoreReadonly<QDateTimeEdit>();
// setWidgetsCustom<QComboBox>([](QComboBox *cb, bool readonly) { if (cb->lineEdit()) cb->lineEdit()->setReadOnly(readonly); }, unchangeable);
restoreReadonly<QSpinBox>();
restoreReadonly<QDoubleSpinBox>();
restoreReadonly<QPlainTextEdit>();
restoreReadonly<QTextEdit>();
restoreEnabled<QComboBox>();
restoreEnabled<QCheckBox>();
restoreEnabled<QRadioButton>();
restoreEnabled<QSlider>();
restoreEnabled<QTableView>();
restoreEnabled<QTableWidget>();
restoreEnabled<QListView>();
restoreEnabled<QListWidget>();
}
void LtsDialog::slotChangesMade() // TODO: get receiver's name, add to changes-made-list to identify changes to specific widgets
{
if (m_unchangeableDialog)
return;
m_changesMade = true;
updateWindowTitle();
m_buttonBox->getButton(DialogButton::Ok)->setEnabled(true);
m_buttonBox->getButton(DialogButton::Apply)->setEnabled(true);
emit signalChangesMade(); // tell dialogs that a Widget has received changes
}
void LtsDialog::resetChanges()
{
m_changesMade = false;
updateWindowTitle();
//if (!m_title.isEmpty())
//setWindowTitle(m_title); // set title back to original - no more [*] needed
m_buttonBox->getButton(DialogButton::Ok)->setEnabled(false);
m_buttonBox->getButton(DialogButton::Apply)->setEnabled(false);
emit signalChangesReset();
}
bool LtsDialog::_apply()
{
if (m_uiRequirements->checkRequirements(this) && apply())
{
resetChanges(); // as all changes were saved successfully
return true;
}
return false;
}
Button LtsDialog::askApplyDiscardCancel()
{
return LtsMessageDialog::askApplyDiscardCancel(this);
}
void LtsDialog::slotApply() // does not close! only saves.
{
_apply();
}
void LtsDialog::slotOkay() // closes and saves. (calls 'apply()', then calls 'close()')
{
if (_apply()) // if there were errors while applying, do not close! If not, close.
_close();
}
void LtsDialog::slotHide()
{
_hide();
}
void LtsDialog::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).
{
// m_changesMade == true means that apply-btn is enabled
if ((m_changesMade) && (!m_unchangeableDialog))
{
switch (askApplyDiscardCancel())
{
case Button::Apply:
slotOkay(); // save & close
break;
case Button::Discard:
_discard(); // do not save, just close
break;
default: // cancel
break; // do nothing
}
}
else
{
_close(); // nothing has changed, so just close
}
}
void LtsDialog::onReadOnlyButtonClicked()
{
if (m_topicLockedBy.isEmpty())
return;
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Locked by: %1").arg(m_topicLockedBy));
}
void LtsDialog::closeRequest()
{
slotClose();
}
void LtsDialog::_hide()
{
if (m_dialogHelper != nullptr)
{
emit signalHide(m_dialogHelper);
//m_dialogHelper->hide();
}
}
void LtsDialog::_close()
{
resetChanges(); // for safety reasons
aboutToClose();
if (m_dialogHelper != nullptr)
m_dialogHelper->close();
}
void LtsDialog::_discard()
{
dialogCanceled();
_close();
}
} // namespace LtsWidgets
#include "lts_dialog.moc"