1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
/*
SPDX-FileCopyrightText: 2012 Ivan Shapovalov <intelfx100@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "shellutils.h"
#include <interfaces/icore.h>
#include <interfaces/iuicontroller.h>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KParts/MainWindow>
#include <KSharedConfig>
#include <QEvent>
#include <QFile>
#include <QGuiApplication>
#include <QList>
#include <QTextStream>
#include <QUrl>
#include <QWidget>
namespace {
class WidgetGeometrySaver : public QObject
{
Q_OBJECT
public:
explicit WidgetGeometrySaver(QWidget& widget, const QString& configGroupName, const QString& configSubgroupName)
: QObject(&widget)
, m_configGroupName{configGroupName}
, m_configSubgroupName{configSubgroupName}
{
widget.installEventFilter(this);
}
bool restoreWidgetGeometry() const
{
return widget().restoreGeometry(configGroup().readEntry(entryName(), QByteArray{}));
}
bool eventFilter(QObject* watched, QEvent* event) override
{
// Saving geometry on a nonspontaneous hide event is the only choice that is both reliable and safe:
// * the close event is received only when a dialog is dismissed via
// its standard window close button and not when it is accepted or rejected;
// * the QDialog::finished() signal is unavailable for non-dialog widgets (less general) and is not emitted
// if the dialog is explicitly hidden or destroyed while visible rather than finished normally;
// * Qt documentation does not directly promise to emit the QObject::destroyed() signal from ~QWidget()
// as opposed to the too-late ~QObject(). Also Qt developers do not guarantee that the state
// of the widget being destroyed can be safely accessed in a slot connected to this signal.
// Some parts of the widget, e.g. its layout, are destroyed before the destroyed() signal is emitted.
// A downside of handling the hide event is that when a widget is hidden, then shown again, its geometry
// is saved multiple times redundantly. Fortunately, a dialog is unlikely to be shown again
// after being hidden. If the redundant saving becomes a bottleneck for some widget, the visibility
// of which changes often, a custom saving of geometry can be implemented in that widget's destructor.
// See also the discussion of when to save geometry at https://codereview.qt-project.org/c/qt/qtbase/+/498797
if (event->type() == QEvent::Hide && !event->spontaneous()) {
const auto& widget = this->widget();
Q_ASSERT(watched == &widget);
configGroup().writeEntry(entryName(), widget.saveGeometry());
}
return false; // do not filter any events out
}
private:
static QString entryName()
{
return QStringLiteral("windowGeometry");
}
QWidget& widget() const
{
Q_ASSERT(parent());
Q_ASSERT(parent()->isWidgetType());
return *static_cast<QWidget*>(parent());
}
KConfigGroup configGroup() const
{
KConfigGroup group(KSharedConfig::openConfig(), m_configGroupName);
if (m_configSubgroupName.isEmpty()) {
return group;
}
KConfigGroup subgroup(&group, m_configSubgroupName);
return subgroup;
}
const QString m_configGroupName;
const QString m_configSubgroupName;
};
} // unnamed namespace
namespace KDevelop {
bool askUser(const QString& mainText,
const QString& ttyPrompt,
const QString& mboxTitle,
const QString& mboxAdditionalText,
const QString& confirmText,
const QString& rejectText,
bool ttyDefaultToYes)
{
if (!qobject_cast<QGuiApplication*>(qApp)) {
// no ui-mode e.g. for duchainify and other tools
QTextStream out(stdout);
out << mainText << Qt::endl;
QTextStream in(stdin);
QString input;
while (true) {
if (ttyDefaultToYes) {
out << ttyPrompt << QLatin1String(": [Y/n] ") << Qt::flush;
} else {
out << ttyPrompt << QLatin1String(": [y/N] ") << Qt::flush;
}
input = in.readLine().trimmed();
if (input.isEmpty()) {
return ttyDefaultToYes;
} else if (input.toLower() == QLatin1String("y")) {
return true;
} else if (input.toLower() == QLatin1String("n")) {
return false;
}
}
} else {
auto okButton = KStandardGuiItem::ok();
okButton.setText(confirmText);
auto rejectButton = KStandardGuiItem::cancel();
rejectButton.setText(rejectText);
int userAnswer = KMessageBox::questionTwoActions(ICore::self()->uiController()->activeMainWindow(),
mainText + QLatin1String("\n\n") + mboxAdditionalText,
mboxTitle, okButton, rejectButton);
return userAnswer == KMessageBox::PrimaryAction;
}
}
bool ensureWritable(const QList<QUrl>& urls)
{
QStringList notWritable;
for (const QUrl& url : urls) {
if (url.isLocalFile()) {
QFile file(url.toLocalFile());
if (file.exists() && !(file.permissions() & QFileDevice::WriteOwner) &&
!(file.permissions() & QFileDevice::WriteGroup)) {
notWritable << url.toLocalFile();
}
}
}
if (!notWritable.isEmpty()) {
int answer = KMessageBox::questionTwoActionsCancel(
ICore::self()->uiController()->activeMainWindow(),
i18n("You don't have write permissions for the following files; add write permissions for owner before "
"saving?")
+ QLatin1String("\n\n") + notWritable.join(QLatin1Char('\n')),
i18nc("@title:window", "Some Files are Write-Protected"),
KGuiItem(i18nc("@action:button", "Set Write Permissions"), QStringLiteral("dialog-ok")),
KGuiItem(i18nc("@action:button", "Ignore"), QStringLiteral("dialog-cancel")), KStandardGuiItem::cancel());
if (answer == KMessageBox::PrimaryAction) {
bool success = true;
for (const QString& filename : std::as_const(notWritable)) {
QFile file(filename);
QFileDevice::Permissions permissions = file.permissions();
permissions |= QFileDevice::WriteOwner;
success &= file.setPermissions(permissions);
}
if (!success) {
KMessageBox::error(ICore::self()->uiController()->activeMainWindow(),
i18n("Failed adding write permissions for some files."),
i18nc("@title:window", "Failed Setting Permissions"));
return false;
}
}
return answer != KMessageBox::Cancel;
}
return true;
}
bool restoreAndAutoSaveGeometry(QWidget& widget, const QString& configGroupName, const QString& configSubgroupName)
{
const auto* const saver = new WidgetGeometrySaver(widget, configGroupName, configSubgroupName);
return saver->restoreWidgetGeometry();
}
}
#include "shellutils.moc"
|