File indexing completed on 2024-06-16 04:47:14
0001 /*************************************************************************** 0002 * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr 0003 * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 ***************************************************************************/ 0006 /** @file 0007 * This file is plugin for advice. 0008 * 0009 * @author Stephane MANKOWSKI / Guillaume DE BURE 0010 */ 0011 #include "skgadviceboardwidget.h" 0012 0013 #include <qdom.h> 0014 #include <qlayoutitem.h> 0015 #include <qmenu.h> 0016 #include <qpushbutton.h> 0017 #include <qtoolbutton.h> 0018 0019 #include <klocalizedstring.h> 0020 0021 #include "skgdocument.h" 0022 #include "skginterfaceplugin.h" 0023 #include "skgmainpanel.h" 0024 #include "skgservices.h" 0025 #include "skgtraces.h" 0026 #include "skgtransactionmng.h" 0027 0028 SKGAdviceBoardWidget::SKGAdviceBoardWidget(QWidget* iParent, SKGDocument* iDocument) 0029 : SKGBoardWidget(iParent, iDocument, i18nc("Dashboard widget title", "Advices")), m_maxAdvice(7), m_refreshNeeded(true), m_refresh(nullptr), m_inapplyall(false) 0030 { 0031 SKGTRACEINFUNC(10) 0032 0033 // Create menu 0034 setContextMenuPolicy(Qt::ActionsContextMenu); 0035 0036 auto g = new QWidget(this); 0037 m_layout = new QFormLayout(g); 0038 m_layout->setContentsMargins(0, 0, 0, 0); 0039 m_layout->setObjectName(QStringLiteral("Slayout")); 0040 m_layout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); 0041 m_layout->setHorizontalSpacing(1); 0042 m_layout->setVerticalSpacing(1); 0043 setMainWidget(g); 0044 0045 // menu 0046 auto menuResetAdvice = new QAction(SKGServices::fromTheme(QStringLiteral("edit-undo")), i18nc("Noun, a user action", "Activate all advice"), this); 0047 connect(menuResetAdvice, &QAction::triggered, this, &SKGAdviceBoardWidget::activateAllAdvice); 0048 addAction(menuResetAdvice); 0049 0050 auto sep = new QAction(this); 0051 sep->setSeparator(true); 0052 addAction(sep); 0053 0054 m_menuAuto = new QAction(i18nc("Noun, a type of refresh for advice", "Automatic refresh"), this); 0055 m_menuAuto->setCheckable(true); 0056 m_menuAuto->setChecked(true); 0057 connect(m_menuAuto, &QAction::triggered, this, &SKGAdviceBoardWidget::dataModifiedNotForce); 0058 addAction(m_menuAuto); 0059 0060 // Refresh 0061 connect(getDocument(), &SKGDocument::transactionSuccessfullyEnded, this, &SKGAdviceBoardWidget::dataModifiedNotForce, Qt::QueuedConnection); 0062 connect(SKGMainPanel::getMainPanel(), &SKGMainPanel::currentPageChanged, this, &SKGAdviceBoardWidget::pageChanged, Qt::QueuedConnection); 0063 connect(this, &SKGAdviceBoardWidget::refreshNeeded, this, [ = ]() { 0064 this->dataModifiedNotForce(); 0065 }, Qt::QueuedConnection); 0066 } 0067 0068 SKGAdviceBoardWidget::~SKGAdviceBoardWidget() 0069 { 0070 SKGTRACEINFUNC(10) 0071 m_menuAuto = nullptr; 0072 m_refresh = nullptr; 0073 } 0074 0075 QString SKGAdviceBoardWidget::getState() 0076 { 0077 QDomDocument doc(QStringLiteral("SKGML")); 0078 doc.setContent(SKGBoardWidget::getState()); 0079 QDomElement root = doc.documentElement(); 0080 0081 root.setAttribute(QStringLiteral("maxAdvice"), SKGServices::intToString(m_maxAdvice)); 0082 root.setAttribute(QStringLiteral("automatic"), (m_menuAuto->isChecked() ? QStringLiteral("Y") : QStringLiteral("N"))); 0083 return doc.toString(); 0084 } 0085 0086 void SKGAdviceBoardWidget::setState(const QString& iState) 0087 { 0088 SKGBoardWidget::setState(iState); 0089 0090 QDomDocument doc(QStringLiteral("SKGML")); 0091 doc.setContent(iState); 0092 QDomElement root = doc.documentElement(); 0093 0094 QString maxAdviceS = root.attribute(QStringLiteral("maxAdvice")); 0095 if (maxAdviceS.isEmpty()) { 0096 maxAdviceS = '7'; 0097 } 0098 m_maxAdvice = SKGServices::stringToInt(maxAdviceS); 0099 0100 QString automatic = root.attribute(QStringLiteral("automatic")); 0101 if (automatic.isEmpty()) { 0102 automatic = 'Y'; 0103 } 0104 0105 if (m_menuAuto != nullptr) { 0106 bool previous = m_menuAuto->blockSignals(true); 0107 m_menuAuto->setChecked(automatic == QStringLiteral("Y")); 0108 m_menuAuto->blockSignals(previous); 0109 } 0110 0111 dataModifiedForce(); 0112 } 0113 0114 void SKGAdviceBoardWidget::pageChanged() 0115 { 0116 if (m_refreshNeeded) { 0117 dataModifiedNotForce(); 0118 } 0119 } 0120 0121 void SKGAdviceBoardWidget::dataModifiedNotForce() 0122 { 0123 dataModified(false); 0124 } 0125 0126 void SKGAdviceBoardWidget::dataModifiedForce() 0127 { 0128 dataModified(true); 0129 } 0130 0131 void SKGAdviceBoardWidget::dataModified(bool iForce) 0132 { 0133 SKGTRACEINFUNC(10) 0134 SKGTabPage* page = SKGTabPage::parentTabPage(this); 0135 if (m_inapplyall || (!iForce && ((page != nullptr && page != SKGMainPanel::getMainPanel()->currentPage()) || !m_menuAuto->isChecked()))) { 0136 m_refreshNeeded = true; 0137 if (m_refresh != nullptr) { 0138 m_refresh->show(); 0139 } 0140 return; 0141 } 0142 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 0143 m_refreshNeeded = false; 0144 0145 // Remove all item of the layout 0146 while (m_layout->count() != 0) { 0147 QLayoutItem* child = m_layout->takeAt(0); 0148 if (child != nullptr) { 0149 QWidget* w = child->widget(); 0150 delete w; 0151 delete child; 0152 } 0153 } 0154 0155 // Get list of advice 0156 double elapse = SKGServices::getMicroTime(); 0157 SKGAdviceList globalAdviceList = SKGMainPanel::getMainPanel()->getAdvice(); 0158 elapse = SKGServices::getMicroTime() - elapse; 0159 0160 // Get list of ignored advice 0161 QString currentMonth = QDate::currentDate().toString(QStringLiteral("yyyy-MM")); 0162 QStringList ignoredAdvice = getDocument()->getParameters(QStringLiteral("advice"), "t_value='I' OR t_value='I_" % currentMonth % '\''); 0163 if (elapse > 3000 && m_menuAuto->isChecked() && !ignoredAdvice.contains(QStringLiteral("skgadviceboardwidget_verylong"))) { 0164 SKGAdvice ad; 0165 ad.setUUID(QStringLiteral("skgadviceboardwidget_verylong")); 0166 ad.setPriority(2); 0167 ad.setShortMessage(i18nc("Advice on making the best (short)", "Advice are very long to compute")); 0168 ad.setLongMessage(i18nc("Advice on making the best (long)", "To improve performances, you should switch the widget in 'Manual refresh' (see contextual menu).")); 0169 globalAdviceList.push_back(ad); 0170 } 0171 0172 // Fill layout 0173 int nbDisplayedAdvice = 0; 0174 int nb = globalAdviceList.count(); 0175 m_recommendedActions.clear(); 0176 for (int i = 0; i < m_maxAdvice && i < nb; ++i) { 0177 // Get advice 0178 const SKGAdvice& ad = globalAdviceList.at(i); 0179 0180 // Create icon 0181 QString iconName = (ad.getPriority() == -1 ? QStringLiteral("dialog-information") : ad.getPriority() >= 8 ? QStringLiteral("security-low") : ad.getPriority() <= 4 ? QStringLiteral("security-high") : QStringLiteral("security-medium")); 0182 QString toolTipString = i18n("<p><b>Priority %1:</b>%2</p>", SKGServices::intToString(ad.getPriority()), ad.getLongMessage()); 0183 0184 // Add ignored action 0185 SKGAdvice::SKGAdviceActionList autoCorrections = ad.getAutoCorrections(); 0186 { 0187 SKGAdvice::SKGAdviceAction dismiss; 0188 dismiss.Title = i18nc("Dismiss an advice provided", "Dismiss"); 0189 dismiss.IconName = QStringLiteral("edit-delete"); 0190 dismiss.IsRecommended = false; 0191 autoCorrections.push_back(dismiss); 0192 } 0193 { 0194 SKGAdvice::SKGAdviceAction dismiss; 0195 dismiss.Title = i18nc("Dismiss an advice provided", "Dismiss during current month"); 0196 dismiss.IconName = QStringLiteral("edit-delete"); 0197 dismiss.IsRecommended = false; 0198 autoCorrections.push_back(dismiss); 0199 } 0200 { 0201 SKGAdvice::SKGAdviceAction dismiss; 0202 dismiss.Title = i18nc("Dismiss an advice provided", "Dismiss this kind"); 0203 dismiss.IconName = QStringLiteral("edit-delete"); 0204 dismiss.IsRecommended = false; 0205 autoCorrections.push_back(dismiss); 0206 } 0207 { 0208 SKGAdvice::SKGAdviceAction dismiss; 0209 dismiss.Title = i18nc("Dismiss an advice provided", "Dismiss this kind during current month"); 0210 dismiss.IconName = QStringLiteral("edit-delete"); 0211 dismiss.IsRecommended = false; 0212 autoCorrections.push_back(dismiss); 0213 } 0214 0215 int nbSolution = autoCorrections.count(); 0216 0217 // Build button 0218 QStringList overlays; 0219 overlays.push_back(nbSolution > 2 ? QStringLiteral("system-run") : QStringLiteral("edit-delete")); 0220 auto icon = new QToolButton(this); 0221 icon->setObjectName(ad.getUUID()); 0222 icon->setIcon(SKGServices::fromTheme(iconName, overlays)); 0223 icon->setIconSize(QSize(24, 24)); 0224 icon->setMaximumSize(QSize(24, 24)); 0225 icon->setCursor(Qt::PointingHandCursor); 0226 icon->setAutoRaise(true); 0227 0228 auto menu = new QMenu(this); 0229 menu->setIcon(icon->icon()); 0230 for (int k = 0; k < nbSolution; ++k) { 0231 SKGAdvice::SKGAdviceAction adviceAction = autoCorrections.at(k); 0232 QString actionText = adviceAction.Title; 0233 QAction* action = SKGMainPanel::getMainPanel()->getGlobalAction(QString(actionText).remove(QStringLiteral("skg://")), false); 0234 QAction* act; 0235 if (action != nullptr) { 0236 // This is an action 0237 act = menu->addAction(action->icon(), action->text(), SKGMainPanel::getMainPanel(), static_cast<bool (SKGMainPanel::*)()>(&SKGMainPanel::openPage)); 0238 act->setData(actionText); 0239 } else { 0240 // This is a text 0241 act = menu->addAction(SKGServices::fromTheme(adviceAction.IconName.isEmpty() ? QStringLiteral("system-run") : adviceAction.IconName), autoCorrections.at(k).Title, this, &SKGAdviceBoardWidget::adviceClicked); 0242 if (act != nullptr) { 0243 act->setProperty("id", ad.getUUID()); 0244 act->setProperty("solution", k < nbSolution - 4 ? k : k - nbSolution); 0245 } 0246 } 0247 0248 if ((act != nullptr) && adviceAction.IsRecommended) { 0249 act->setToolTip(act->text()); 0250 act->setText(act->text() % i18nc("To recommend this action", " (recommended)")); 0251 m_recommendedActions.append(act); 0252 } 0253 } 0254 0255 icon->setMenu(menu); 0256 icon->setPopupMode(QToolButton::InstantPopup); 0257 0258 icon->setToolTip(toolTipString); 0259 0260 // Create text 0261 auto label = new QLabel(this); 0262 label->setText(ad.getShortMessage()); 0263 label->setToolTip(toolTipString); 0264 0265 // Add them 0266 m_layout->addRow(icon, label); 0267 0268 ++nbDisplayedAdvice; 0269 } 0270 0271 // Add apply all recommended actions 0272 QPushButton* apply = nullptr; 0273 int nb2 = m_recommendedActions.count(); 0274 if (nb2 != 0) { 0275 apply = new QPushButton(this); 0276 apply->setIcon(SKGServices::fromTheme(QStringLiteral("games-solve"))); 0277 apply->setIconSize(QSize(22, 22)); 0278 apply->setMaximumSize(QSize(22, 22)); 0279 apply->setCursor(Qt::PointingHandCursor); 0280 QString ToolTip; 0281 for (int i = 0; i < nb2; ++i) { 0282 if (i > 0) { 0283 ToolTip += '\n'; 0284 } 0285 ToolTip += m_recommendedActions.at(i)->toolTip(); 0286 } 0287 apply->setToolTip(ToolTip); 0288 connect(apply, &QPushButton::clicked, this, &SKGAdviceBoardWidget::applyRecommended, Qt::QueuedConnection); 0289 } 0290 0291 // Add more 0292 if (nb > m_maxAdvice) { 0293 auto more = new QPushButton(this); 0294 more->setIcon(SKGServices::fromTheme(QStringLiteral("arrow-down-double"))); 0295 more->setIconSize(QSize(22, 22)); 0296 more->setMaximumSize(QSize(22, 22)); 0297 more->setCursor(Qt::PointingHandCursor); 0298 more->setToolTip(i18nc("Information message", "Display all advices")); 0299 connect(more, &QPushButton::clicked, this, &SKGAdviceBoardWidget::moreAdvice, Qt::QueuedConnection); 0300 0301 if (apply != nullptr) { 0302 m_layout->addRow(more, apply); 0303 apply = nullptr; 0304 } else { 0305 m_layout->addRow(more, new QLabel(this)); 0306 } 0307 } else if (nbDisplayedAdvice > 7) { 0308 // Add less 0309 auto less = new QPushButton(this); 0310 less->setIcon(SKGServices::fromTheme(QStringLiteral("arrow-up-double"))); 0311 less->setIconSize(QSize(22, 22)); 0312 less->setMaximumSize(QSize(22, 22)); 0313 less->setCursor(Qt::PointingHandCursor); 0314 less->setToolTip(i18nc("Information message", "Display less advices")); 0315 connect(less, &QPushButton::clicked, this, &SKGAdviceBoardWidget::lessAdvice, Qt::QueuedConnection); 0316 if (apply != nullptr) { 0317 m_layout->addRow(less, apply); 0318 apply = nullptr; 0319 } else { 0320 m_layout->addRow(less, new QLabel(this)); 0321 } 0322 } 0323 0324 if (apply != nullptr) { 0325 m_layout->addRow(apply, new QLabel(this)); 0326 } 0327 0328 // Add manual refresh button 0329 m_refresh = new QPushButton(this); 0330 m_refresh->setIcon(SKGServices::fromTheme(QStringLiteral("view-refresh"))); 0331 m_refresh->setIconSize(QSize(22, 22)); 0332 m_refresh->setMaximumSize(QSize(22, 22)); 0333 m_refresh->setCursor(Qt::PointingHandCursor); 0334 m_refresh->setToolTip(i18nc("Information message", "Refresh advices")); 0335 m_refresh->hide(); 0336 connect(m_refresh, &QPushButton::clicked, this, &SKGAdviceBoardWidget::dataModifiedForce, Qt::QueuedConnection); 0337 0338 m_layout->addRow(m_refresh, new QLabel(this)); 0339 0340 QApplication::restoreOverrideCursor(); 0341 } 0342 0343 void SKGAdviceBoardWidget::moreAdvice() 0344 { 0345 m_maxAdvice = 9999999; 0346 dataModifiedForce(); 0347 } 0348 0349 void SKGAdviceBoardWidget::lessAdvice() 0350 { 0351 m_maxAdvice = 7; 0352 dataModifiedForce(); 0353 } 0354 0355 void SKGAdviceBoardWidget::applyRecommended() 0356 { 0357 SKGError err; 0358 SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Apply all recommended corrections"), err) 0359 m_inapplyall = true; 0360 int nb = m_recommendedActions.count(); 0361 for (int i = 0; i < nb; ++i) { 0362 m_recommendedActions.at(i)->trigger(); 0363 } 0364 m_inapplyall = false; 0365 } 0366 0367 void SKGAdviceBoardWidget::activateAllAdvice() 0368 { 0369 SKGError err; 0370 { 0371 SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Activate all advice"), err) 0372 err = getDocument()->executeSqliteOrder(QStringLiteral("DELETE FROM parameters WHERE t_uuid_parent='advice'")); 0373 } 0374 0375 // status bar 0376 IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Advice activated."))) 0377 else { 0378 err.addError(ERR_FAIL, i18nc("Error message", "Advice activation failed")); 0379 } 0380 0381 SKGMainPanel::displayErrorMessage(err); 0382 } 0383 0384 void SKGAdviceBoardWidget::adviceClicked() 0385 { 0386 // Get advice identifier 0387 auto* act = qobject_cast< QAction* >(sender()); 0388 if (act != nullptr) { 0389 QString uuid = act->property("id").toString(); 0390 if (!uuid.isEmpty()) { 0391 // Get solution clicker 0392 int solution = sender()->property("solution").toInt(); 0393 0394 if (solution < 0) { 0395 // We have to ignore this advice 0396 SKGError err; 0397 { 0398 SKGBEGINLIGHTTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Dismiss advice"), err) 0399 QString currentMonth = QDate::currentDate().toString(QStringLiteral("yyyy-MM")); 0400 0401 // Create dismiss 0402 if (solution == -1 || solution == -2) { 0403 uuid = SKGServices::splitCSVLine(uuid, '|').at(0); 0404 } 0405 IFOKDO(err, getDocument()->setParameter(uuid, solution == -2 || solution == -4 ? QStringLiteral("I") : 0406 QString("I_" % currentMonth), QVariant(), QStringLiteral("advice"))) 0407 0408 // Delete useless dismiss 0409 IFOKDO(err, getDocument()->executeSqliteOrder("DELETE FROM parameters WHERE t_uuid_parent='advice' AND t_value like 'I_ % ' AND t_value!='I_" % currentMonth % '\'')) 0410 } 0411 0412 // status bar 0413 IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Advice dismissed."))) 0414 else { 0415 err.addError(ERR_FAIL, i18nc("Error message", "Advice dismiss failed")); 0416 } 0417 } else { 0418 // Get last transaction id 0419 int previous = getDocument()->getTransactionToProcess(SKGDocument::UNDO); 0420 0421 // Execute the advice correction on all plugin 0422 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 0423 int index = 0; 0424 while (index >= 0) { 0425 SKGInterfacePlugin* plugin = SKGMainPanel::getMainPanel()->getPluginByIndex(index); 0426 if (plugin != nullptr) { 0427 SKGError err = plugin->executeAdviceCorrection(uuid, solution); 0428 if (!err || err.getReturnCode() != ERR_NOTIMPL) { 0429 // The correction has been done or failed. This is the end. 0430 index = -2; 0431 } 0432 } else { 0433 index = -2; 0434 } 0435 ++index; 0436 } 0437 0438 // Get last transaction id 0439 int next = getDocument()->getTransactionToProcess(SKGDocument::UNDO); 0440 0441 // If this is the same transaction, it means that an action has been done outside the document ==> a refresh is needed 0442 if (next == previous) { 0443 emit refreshNeeded(); 0444 } 0445 0446 QApplication::restoreOverrideCursor(); 0447 } 0448 } 0449 } 0450 } 0451 0452