Warning, file /frameworks/kconfigwidgets/src/ktipdialog.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2000-2003 Matthias Hoelzer-Kluepfel <mhk@kde.org> 0003 SPDX-FileCopyrightText: 2000-2003 Tobias Koenig <tokoe@kde.org> 0004 SPDX-FileCopyrightText: 2000-2003 Daniel Molkentin <molkentin@kde.org> 0005 SPDX-FileCopyrightText: 2008 Urs Wolfer <uwolfer @ kde.org> 0006 0007 SPDX-License-Identifier: MIT 0008 */ 0009 0010 #include "ktipdialog.h" 0011 #include "kconfigwidgets_debug.h" 0012 0013 #if KCONFIGWIDGETS_BUILD_DEPRECATED_SINCE(5, 83) 0014 0015 #include <QApplication> 0016 #include <QCheckBox> 0017 #include <QFile> 0018 #include <QHBoxLayout> 0019 #include <QKeyEvent> 0020 #include <QLabel> 0021 #include <QPushButton> 0022 #include <QRandomGenerator> 0023 #include <QRegularExpression> 0024 #include <QScreen> 0025 #include <QTextBrowser> 0026 #include <QVBoxLayout> 0027 0028 #include <KConfig> 0029 #include <KConfigGroup> 0030 #include <KLocalizedString> 0031 #include <KRandom> 0032 #include <KSeparator> 0033 #include <KSharedConfig> 0034 #include <KStandardGuiItem> 0035 0036 class KTipDatabasePrivate 0037 { 0038 public: 0039 void loadTips(const QString &tipFile); 0040 void addTips(const QString &tipFile); 0041 0042 QStringList tips; 0043 int currentTip; 0044 }; 0045 0046 void KTipDatabasePrivate::loadTips(const QString &tipFile) 0047 { 0048 tips.clear(); 0049 addTips(tipFile); 0050 } 0051 0052 /** 0053 * If you change something here, please update the script 0054 * preparetips5, which depends on extracting exactly the same 0055 * text as done here. 0056 */ 0057 void KTipDatabasePrivate::addTips(const QString &tipFile) 0058 { 0059 const QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, tipFile); 0060 0061 if (fileName.isEmpty()) { 0062 qCDebug(KCONFIG_WIDGETS_LOG) << "KTipDatabase::addTips: can't find '" << tipFile << "' in standard dirs"; 0063 return; 0064 } 0065 0066 QFile file(fileName); 0067 if (!file.open(QIODevice::ReadOnly)) { 0068 qCDebug(KCONFIG_WIDGETS_LOG) << "KTipDatabase::addTips: can't open '" << fileName << "' for reading"; 0069 return; 0070 } 0071 0072 QByteArray data = file.readAll(); 0073 QString content = QString::fromUtf8(data.constData(), data.size()); 0074 const QRegularExpression rx(QStringLiteral("\\n{2,}")); 0075 0076 int pos = -1; 0077 const QLatin1Char newline('\n'); 0078 while ((pos = content.indexOf(QLatin1String("<html>"), pos + 1, Qt::CaseInsensitive)) != -1) { 0079 /** 0080 * To make translations work, tip extraction here must exactly 0081 * match what is done by the preparetips5 script. 0082 */ 0083 QString tip = content.mid(pos + 6, content.indexOf(QLatin1String("</html>"), pos, Qt::CaseInsensitive) - pos - 6).replace(rx, QStringLiteral("\n")); 0084 0085 if (!tip.endsWith(newline)) { 0086 tip += newline; 0087 } 0088 0089 if (tip.startsWith(newline)) { 0090 tip.remove(0, 1); 0091 } 0092 0093 if (tip.isEmpty()) { 0094 qCDebug(KCONFIG_WIDGETS_LOG) << "Empty tip found! Skipping! " << pos; 0095 continue; 0096 } 0097 0098 tips.append(tip); 0099 } 0100 0101 file.close(); 0102 } 0103 0104 KTipDatabase::KTipDatabase(const QString &_tipFile) 0105 : d(new KTipDatabasePrivate) 0106 { 0107 QString tipFile = _tipFile; 0108 0109 if (tipFile.isEmpty()) { 0110 tipFile = QCoreApplication::applicationName() + QStringLiteral("/tips"); 0111 } 0112 0113 d->loadTips(tipFile); 0114 0115 if (!d->tips.isEmpty()) { 0116 d->currentTip = QRandomGenerator::global()->bounded(d->tips.count()); 0117 } 0118 } 0119 0120 KTipDatabase::KTipDatabase(const QStringList &tipsFiles) 0121 : d(new KTipDatabasePrivate) 0122 { 0123 if (tipsFiles.isEmpty() || ((tipsFiles.count() == 1) && tipsFiles.first().isEmpty())) { 0124 d->addTips(QCoreApplication::applicationName() + QStringLiteral("/tips")); 0125 } else { 0126 for (QStringList::ConstIterator it = tipsFiles.begin(); it != tipsFiles.end(); ++it) { 0127 d->addTips(*it); 0128 } 0129 } 0130 0131 if (!d->tips.isEmpty()) { 0132 d->currentTip = QRandomGenerator::global()->bounded(d->tips.count()); 0133 } 0134 } 0135 0136 KTipDatabase::~KTipDatabase() = default; 0137 0138 void KTipDatabase::nextTip() 0139 { 0140 if (d->tips.isEmpty()) { 0141 return; 0142 } 0143 0144 d->currentTip += 1; 0145 0146 if (d->currentTip >= d->tips.count()) { 0147 d->currentTip = 0; 0148 } 0149 } 0150 0151 void KTipDatabase::prevTip() 0152 { 0153 if (d->tips.isEmpty()) { 0154 return; 0155 } 0156 0157 d->currentTip -= 1; 0158 0159 if (d->currentTip < 0) { 0160 d->currentTip = d->tips.count() - 1; 0161 } 0162 } 0163 0164 QString KTipDatabase::tip() const 0165 { 0166 if (d->tips.isEmpty()) { 0167 return QString(); 0168 } 0169 0170 return d->tips[d->currentTip]; 0171 } 0172 0173 class KTipDialogPrivate 0174 { 0175 public: 0176 KTipDialogPrivate(KTipDialog *qq) 0177 : q(qq) 0178 { 0179 } 0180 ~KTipDialogPrivate() 0181 { 0182 delete database; 0183 } 0184 0185 void _k_nextTip(); 0186 void _k_prevTip(); 0187 void _k_showOnStart(bool); 0188 0189 KTipDialog *const q; 0190 KTipDatabase *database; 0191 QCheckBox *tipOnStart; 0192 QTextBrowser *tipText; 0193 0194 static KTipDialog *mInstance; 0195 }; 0196 0197 KTipDialog *KTipDialogPrivate::mInstance = nullptr; 0198 0199 void KTipDialogPrivate::_k_prevTip() 0200 { 0201 database->prevTip(); 0202 tipText->setHtml( 0203 QStringLiteral("<html><body>%1</body></html>").arg(i18nd(KLocalizedString::applicationDomain().constData(), database->tip().toUtf8().constData()))); 0204 } 0205 0206 void KTipDialogPrivate::_k_nextTip() 0207 { 0208 database->nextTip(); 0209 tipText->setHtml( 0210 QStringLiteral("<html><body>%1</body></html>").arg(i18nd(KLocalizedString::applicationDomain().constData(), database->tip().toUtf8().constData()))); 0211 } 0212 0213 void KTipDialogPrivate::_k_showOnStart(bool on) 0214 { 0215 q->setShowOnStart(on); 0216 } 0217 0218 KTipDialog::KTipDialog(KTipDatabase *database, QWidget *parent) 0219 : QDialog(parent) 0220 , d(new KTipDialogPrivate(this)) 0221 { 0222 setWindowTitle(i18nc("@title:window", "Tip of the Day")); 0223 0224 /** 0225 * Parent is 0L when TipDialog is used as a mainWidget. This should 0226 * be the case only in ktip, so let's use the ktip layout. 0227 */ 0228 bool isTipDialog = (parent != nullptr); 0229 0230 d->database = database; 0231 0232 setWindowIcon(QIcon::fromTheme(QStringLiteral("ktip"))); 0233 0234 QVBoxLayout *mainLayout = new QVBoxLayout(this); 0235 0236 if (isTipDialog) { 0237 QLabel *titleLabel = new QLabel(this); 0238 titleLabel->setText(i18nc("@title", "Did you know...?\n")); 0239 titleLabel->setFont(QFont(qApp->font().family(), 20, QFont::Bold)); 0240 titleLabel->setAlignment(Qt::AlignCenter); 0241 mainLayout->addWidget(titleLabel); 0242 } 0243 0244 QHBoxLayout *browserLayout = new QHBoxLayout(); 0245 mainLayout->addLayout(browserLayout); 0246 0247 d->tipText = new QTextBrowser(this); 0248 0249 d->tipText->setOpenExternalLinks(true); 0250 0251 d->tipText->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 0252 0253 QStringList paths; 0254 paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory) 0255 << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdewizard/pics"), QStandardPaths::LocateDirectory); 0256 0257 d->tipText->setSearchPaths(paths); 0258 0259 d->tipText->setFrameStyle(QFrame::NoFrame); 0260 d->tipText->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0261 QPalette tipPal(d->tipText->palette()); 0262 tipPal.setColor(QPalette::Base, Qt::transparent); 0263 tipPal.setColor(QPalette::Text, tipPal.color(QPalette::WindowText)); 0264 d->tipText->setPalette(tipPal); 0265 0266 browserLayout->addWidget(d->tipText); 0267 0268 QLabel *label = new QLabel(this); 0269 label->setPixmap(QStringLiteral(":/kconfigwidgets/pics/ktip-bulb.png")); 0270 label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 0271 browserLayout->addWidget(label); 0272 0273 if (!isTipDialog) { 0274 resize(520, 280); 0275 QSize sh = size(); 0276 0277 QScreen *screen = QGuiApplication::screenAt(QCursor::pos()); 0278 if (screen) { 0279 const QRect rect = screen->geometry(); 0280 move(rect.x() + (rect.width() - sh.width()) / 2, rect.y() + (rect.height() - sh.height()) / 2); 0281 } 0282 } 0283 0284 KSeparator *sep = new KSeparator(Qt::Horizontal); 0285 mainLayout->addWidget(sep); 0286 0287 QHBoxLayout *buttonLayout = new QHBoxLayout(); 0288 0289 mainLayout->addLayout(buttonLayout); 0290 0291 d->tipOnStart = new QCheckBox(i18nc("@option:check", "&Show tips on startup")); 0292 buttonLayout->addWidget(d->tipOnStart, 1); 0293 0294 QPushButton *prev = new QPushButton; 0295 KGuiItem::assign(prev, KStandardGuiItem::back(KStandardGuiItem::UseRTL)); 0296 prev->setText(i18nc("@action:button Goes to previous tip", "&Previous")); 0297 buttonLayout->addWidget(prev); 0298 0299 QPushButton *next = new QPushButton; 0300 KGuiItem::assign(next, KStandardGuiItem::forward(KStandardGuiItem::UseRTL)); 0301 next->setText(i18nc("@action:button Goes to next tip, opposite to previous", "&Next")); 0302 buttonLayout->addWidget(next); 0303 0304 QPushButton *ok = new QPushButton; 0305 KGuiItem::assign(ok, KStandardGuiItem::close()); 0306 ok->setDefault(true); 0307 buttonLayout->addWidget(ok); 0308 0309 KConfigGroup config(KSharedConfig::openConfig(), "TipOfDay"); 0310 d->tipOnStart->setChecked(config.readEntry("RunOnStart", true)); 0311 0312 connect(next, &QPushButton::clicked, this, [this]() { 0313 d->_k_nextTip(); 0314 }); 0315 connect(prev, &QPushButton::clicked, this, [this]() { 0316 d->_k_prevTip(); 0317 }); 0318 connect(ok, &QAbstractButton::clicked, this, &QDialog::accept); 0319 connect(d->tipOnStart, &QCheckBox::toggled, this, [this](bool state) { 0320 d->_k_showOnStart(state); 0321 }); 0322 0323 ok->setFocus(); 0324 0325 d->_k_nextTip(); 0326 } 0327 0328 KTipDialog::~KTipDialog() 0329 { 0330 if (KTipDialogPrivate::mInstance == this) { 0331 KTipDialogPrivate::mInstance = nullptr; 0332 } 0333 } 0334 0335 /** 0336 * use the one with a parent window as parameter instead of this one 0337 * or you will get focus problems 0338 */ 0339 void KTipDialog::showTip(const QString &tipFile, bool force) 0340 { 0341 showTip(nullptr, tipFile, force); 0342 } 0343 0344 void KTipDialog::showTip(QWidget *parent, const QString &tipFile, bool force) 0345 { 0346 showMultiTip(parent, QStringList(tipFile), force); 0347 } 0348 0349 void KTipDialog::showMultiTip(QWidget *parent, const QStringList &tipFiles, bool force) 0350 { 0351 KConfigGroup configGroup(KSharedConfig::openConfig(), "TipOfDay"); 0352 0353 const bool runOnStart = configGroup.readEntry("RunOnStart", true); 0354 0355 if (!force) { 0356 if (!runOnStart) { 0357 return; 0358 } 0359 0360 // showing the tooltips on startup suggests the tooltip 0361 // will be shown *each time* on startup, not $random days later 0362 // TODO either remove or uncomment this code, but make the situation clear 0363 /*bool hasLastShown = configGroup.hasKey( "TipLastShown" ); 0364 if ( hasLastShown ) { 0365 const int oneDay = 24 * 60 * 60; 0366 QDateTime lastShown = configGroup.readEntry( "TipLastShown", QDateTime() ); 0367 0368 // Show tip roughly once a week 0369 if ( lastShown.secsTo( QDateTime::currentDateTime() ) < (oneDay + (QRandomGenerator::global()->bounded(10 * oneDay))) ) 0370 return; 0371 } 0372 0373 configGroup.writeEntry( "TipLastShown", QDateTime::currentDateTime() ); 0374 0375 if ( !hasLastShown ) 0376 return; // Don't show tip on first start*/ 0377 } 0378 0379 if (!KTipDialogPrivate::mInstance) { 0380 KTipDialogPrivate::mInstance = new KTipDialog(new KTipDatabase(tipFiles), parent); 0381 } else 0382 // The application might have changed the RunOnStart option in its own 0383 // configuration dialog, so we should update the checkbox. 0384 { 0385 KTipDialogPrivate::mInstance->d->tipOnStart->setChecked(runOnStart); 0386 } 0387 0388 KTipDialogPrivate::mInstance->show(); 0389 KTipDialogPrivate::mInstance->raise(); 0390 } 0391 0392 void KTipDialog::setShowOnStart(bool on) 0393 { 0394 KConfigGroup config(KSharedConfig::openConfig(), "TipOfDay"); 0395 config.writeEntry("RunOnStart", on); 0396 } 0397 0398 bool KTipDialog::eventFilter(QObject *object, QEvent *event) 0399 { 0400 if (object == d->tipText && event->type() == QEvent::KeyPress 0401 && (((QKeyEvent *)event)->key() == Qt::Key_Return || ((QKeyEvent *)event)->key() == Qt::Key_Space)) { 0402 accept(); 0403 } 0404 0405 /** 0406 * If the user presses Return or Space, we close the dialog as if the 0407 * default button was pressed even if the QTextBrowser has the keyboard 0408 * focus. This could have the bad side-effect that the user cannot use the 0409 * keyboard to open urls in the QTextBrowser, so we just let it handle 0410 * the key event _additionally_. (Antonio) 0411 */ 0412 0413 return QWidget::eventFilter(object, event); 0414 } 0415 0416 #include "moc_ktipdialog.cpp" 0417 #endif