File indexing completed on 2024-06-16 04:47:18

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 a plugin for undo and redo operation.
0008  *
0009  * @author Stephane MANKOWSKI / Guillaume DE BURE
0010  */
0011 #include "skgundoredoplugin.h"
0012 
0013 #include <qwidget.h>
0014 
0015 #include <kaboutdata.h>
0016 #include <kactioncollection.h>
0017 #include <kpluginfactory.h>
0018 #include <kstandardaction.h>
0019 #include <ktoolbarpopupaction.h>
0020 
0021 #include "skgerror.h"
0022 #include "skgmainpanel.h"
0023 #include "skgservices.h"
0024 #include "skgtraces.h"
0025 #include "skgundoredo_settings.h"
0026 #include "skgundoredoplugindockwidget.h"
0027 
0028 /**
0029  * This plugin factory.
0030  */
0031 K_PLUGIN_CLASS_WITH_JSON(SKGUndoRedoPlugin, "metadata.json")
0032 
0033 SKGUndoRedoPlugin::SKGUndoRedoPlugin(QWidget* iWidget, QObject* iParent, const QVariantList& /*iArg*/) :
0034     SKGInterfacePlugin(iParent),
0035     m_undoSaveAction(nullptr), m_undoAction(nullptr), m_redoAction(nullptr), m_undoMenu(nullptr), m_redoMenu(nullptr), m_currentDocument(nullptr), m_dockWidget(nullptr)
0036 {
0037     Q_UNUSED(iWidget)
0038     SKGTRACEINFUNC(10)
0039 }
0040 
0041 SKGUndoRedoPlugin::~SKGUndoRedoPlugin()
0042 {
0043     SKGTRACEINFUNC(10)
0044     m_currentDocument = nullptr;
0045     m_dockWidget = nullptr;
0046     m_undoSaveAction = nullptr;
0047     m_undoAction = nullptr;
0048     m_redoAction = nullptr;
0049     m_undoMenu = nullptr;
0050     m_redoMenu = nullptr;
0051 }
0052 
0053 bool SKGUndoRedoPlugin::setupActions(SKGDocument* iDocument)
0054 {
0055     SKGTRACEINFUNC(10)
0056 
0057     m_currentDocument = iDocument;
0058 
0059     setComponentName(QStringLiteral("skg_undoredo"), title());
0060     setXMLFile(QStringLiteral("skg_undoredo.rc"));
0061 
0062     // Menu
0063     m_undoSaveAction = new QAction(SKGServices::fromTheme(QStringLiteral("document-revert")), i18nc("Verb, action to cancel previous action", "Revert document"), this);
0064     connect(m_undoSaveAction, &QAction::triggered, this, &SKGUndoRedoPlugin::onUndoSave);
0065     actionCollection()->setDefaultShortcut(m_undoSaveAction, Qt::CTRL + Qt::ALT + Qt::Key_Z);
0066     registerGlobalAction(QStringLiteral("edit_undolastsave"), m_undoSaveAction);
0067 
0068     m_undoAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("edit-undo")), i18nc("Verb, action to cancel previous action", "Undo"), this);
0069     connect(m_undoAction, &KToolBarPopupAction::triggered, this, &SKGUndoRedoPlugin::onUndo);
0070     actionCollection()->setDefaultShortcut(m_undoAction, Qt::CTRL + Qt::Key_Z);
0071     m_undoAction->setPriority(QAction::LowPriority);
0072     m_undoMenu = m_undoAction->menu();
0073     connect(m_undoMenu, &QMenu::aboutToShow, this, &SKGUndoRedoPlugin::onShowUndoMenu);
0074     m_undoAction->setStickyMenu(false);
0075     m_undoAction->setData(1);
0076     registerGlobalAction(QStringLiteral("edit_undo"), m_undoAction);
0077 
0078     m_redoAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("edit-redo")), i18nc("Verb, action to redo previous cancelled action", "Redo"), this);
0079     connect(m_redoAction, &KToolBarPopupAction::triggered, this, &SKGUndoRedoPlugin::onRedo);
0080     actionCollection()->setDefaultShortcut(m_redoAction, Qt::CTRL + Qt::SHIFT + Qt::Key_Z);
0081     m_redoAction->setPriority(QAction::LowPriority);
0082     m_redoMenu = m_redoAction->menu();
0083     connect(m_redoMenu, &QMenu::aboutToShow, this, &SKGUndoRedoPlugin::onShowRedoMenu);
0084     m_redoAction->setStickyMenu(false);
0085     m_redoAction->setData(1);
0086     registerGlobalAction(QStringLiteral("edit_redo"), m_redoAction);
0087 
0088     // Menu
0089     auto act = new QAction(SKGServices::fromTheme(QStringLiteral("edit-clear-history")), i18nc("Verb, action to cancel previous action", "Clear history"), this);
0090     connect(act, &QAction::triggered, this, &SKGUndoRedoPlugin::onClearHistory);
0091     registerGlobalAction(QStringLiteral("edit_clear_history"), act);
0092 
0093     // Dock creation
0094     m_dockWidget = new QDockWidget(SKGMainPanel::getMainPanel());
0095     m_dockWidget->setObjectName(QStringLiteral("skg_undoredo_docwidget"));
0096     m_dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
0097     m_dockWidget->setWindowTitle(title());
0098 
0099     // add action to control hide / display of history
0100     QAction* toggle = m_dockWidget->toggleViewAction();
0101     QAction* panelAction = actionCollection()->addAction(QStringLiteral("view_transactions"));
0102     registerGlobalAction(QStringLiteral("view_transactions"), panelAction);
0103     panelAction->setCheckable(true);
0104     panelAction->setChecked(toggle->isChecked());
0105     panelAction->setText(toggle->text());
0106     actionCollection()->setDefaultShortcut(panelAction, Qt::SHIFT + Qt::Key_F11);
0107     connect(panelAction, &QAction::triggered, toggle, &QAction::trigger);
0108     connect(toggle, &QAction::toggled, panelAction, &QAction::setChecked);
0109 
0110     return true;
0111 }
0112 
0113 void SKGUndoRedoPlugin::refresh()
0114 {
0115     SKGTRACEINFUNC(10)
0116 
0117     if (m_dockWidget->widget() == nullptr) {
0118         auto w = new SKGUndoRedoPluginDockWidget(SKGMainPanel::getMainPanel(), m_currentDocument);
0119         connect(w, &SKGUndoRedoPluginDockWidget::selectionChanged, SKGMainPanel::getMainPanel(), &SKGMainPanel::refresh);
0120         m_dockWidget->setWidget(w);
0121     }
0122 
0123     if (m_currentDocument != nullptr) {
0124         bool undoPossible = (m_currentDocument->getNbTransaction(SKGDocument::UNDO) > 0);
0125         if (m_undoSaveAction != nullptr) {
0126             m_undoSaveAction->setEnabled(undoPossible);
0127         }
0128         if (m_undoAction != nullptr) {
0129             m_undoAction->setEnabled(undoPossible);
0130         }
0131         if (m_redoAction != nullptr) {
0132             m_redoAction->setEnabled(m_currentDocument->getNbTransaction(SKGDocument::REDO) > 0);
0133         }
0134 
0135         // Refresh undo redo
0136         QString name;
0137         m_currentDocument->getTransactionToProcess(SKGDocument::UNDO, &name);
0138         QString message = i18nc("Verb",  "Undo transaction '%1'.", name);
0139         if (name.isEmpty()) {
0140             message = QLatin1String("");
0141         }
0142         if (m_undoAction != nullptr) {
0143             m_undoAction->setStatusTip(message);
0144         }
0145 
0146         m_currentDocument->getTransactionToProcess(SKGDocument::REDO, &name);
0147         message = i18nc("Verb",  "Redo transaction '%1'.", name);
0148         if (name.isEmpty()) {
0149             message = QLatin1String("");
0150         }
0151         if (m_redoAction != nullptr) {
0152             m_redoAction->setStatusTip(message);
0153         }
0154     }
0155 }
0156 
0157 void SKGUndoRedoPlugin::initPreferences()
0158 {
0159     // Read Setting
0160     if (m_currentDocument != nullptr) {
0161         KSharedConfigPtr config = KSharedConfig::openConfig();
0162         KConfigGroup pref = config->group("skg_undoredo");
0163         pref.writeEntry("maxNumberOfUndo", SKGServices::stringToInt(m_currentDocument->getParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH"))));
0164         pref.writeEntry("cleanHistoryOnSave", (m_currentDocument->getParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE")) == QStringLiteral("Y")));
0165 
0166         skgundoredo_settings::self()->read();
0167     }
0168 }
0169 
0170 
0171 QWidget* SKGUndoRedoPlugin::getPreferenceWidget()
0172 {
0173     SKGTRACEINFUNC(10)
0174     // Create widget
0175     auto w = new QWidget();
0176     ui.setupUi(w);
0177     return w;
0178 }
0179 
0180 
0181 KConfigSkeleton* SKGUndoRedoPlugin::getPreferenceSkeleton()
0182 {
0183     return skgundoredo_settings::self();
0184 }
0185 
0186 SKGError SKGUndoRedoPlugin::savePreferences() const
0187 {
0188     SKGError err;
0189     if (m_currentDocument != nullptr) {
0190         // Read Setting
0191         QString max = SKGServices::intToString(skgundoredo_settings::maxNumberOfUndo());
0192         QString clean = (skgundoredo_settings::cleanHistoryOnSave() ? QStringLiteral("Y") : QStringLiteral("N"));
0193 
0194         // Save setting in document
0195         if (max != m_currentDocument->getParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH"))) {
0196             err = m_currentDocument->setParameter(QStringLiteral("SKG_UNDO_MAX_DEPTH"), max);
0197         }
0198         if (clean != m_currentDocument->getParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE"))) {
0199             err = m_currentDocument->setParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE"), clean);
0200         }
0201     }
0202     return err;
0203 }
0204 
0205 QString SKGUndoRedoPlugin::title() const
0206 {
0207     return i18nc("Noun", "History");
0208 }
0209 
0210 QString SKGUndoRedoPlugin::icon() const
0211 {
0212     return QStringLiteral("edit-undo");
0213 }
0214 
0215 QString SKGUndoRedoPlugin::toolTip() const
0216 {
0217     return i18nc("Noun", "History");
0218 }
0219 
0220 QStringList SKGUndoRedoPlugin::tips() const
0221 {
0222     QStringList output;
0223     output.push_back(i18nc("Description of a tips", "<p>… you can undo and redo your modifications.</p>"));
0224     output.push_back(i18nc("Description of a tips", "<p>… you can modify the maximum size of the undo/redo stack in the <a href=\"skg://tab_configure?page=Undo redo plugin\">settings</a>.</p>"));
0225     return output;
0226 }
0227 
0228 int SKGUndoRedoPlugin::getOrder() const
0229 {
0230     return 4;
0231 }
0232 
0233 SKGAdviceList SKGUndoRedoPlugin::advice(const QStringList& iIgnoredAdvice)
0234 {
0235     SKGTRACEINFUNC(10)
0236     SKGAdviceList output;
0237     if (!iIgnoredAdvice.contains(QStringLiteral("skgundoredoplugin_too_big"))) {
0238         // Get nb transaction
0239         int nbObject = m_currentDocument->getNbTransaction();
0240         int priority = qMin(10, nbObject / 50);
0241         if (priority > 0) {
0242             SKGAdvice ad;
0243             ad.setUUID(QStringLiteral("skgundoredoplugin_too_big"));
0244             ad.setPriority(priority);
0245             ad.setShortMessage(i18nc("Advice on making the best (short)", "History is too large"));
0246             ad.setLongMessage(i18nc("Advice on making the best (long)", "You can improve performances by reducing your history size in settings."));
0247             QStringList autoCorrections;
0248             autoCorrections.push_back(QStringLiteral("skg://edit_clear_history"));
0249             autoCorrections.push_back(i18nc("Advice on making the best (action)", "Open settings panel"));
0250             ad.setAutoCorrections(autoCorrections);
0251             output.push_back(ad);
0252         }
0253     }
0254 
0255     return output;
0256 }
0257 
0258 SKGError SKGUndoRedoPlugin::executeAdviceCorrection(const QString& iAdviceIdentifier, int iSolution)
0259 {
0260     SKGError err;
0261     if ((m_currentDocument != nullptr) && iAdviceIdentifier == QStringLiteral("skgundoredoplugin_too_big")) {
0262         SKGMainPanel::getMainPanel()->optionsPreferences(this->objectName());
0263         return err;
0264     }
0265     return SKGInterfacePlugin::executeAdviceCorrection(iAdviceIdentifier, iSolution);
0266 }
0267 
0268 void SKGUndoRedoPlugin::onClearHistory()
0269 {
0270     SKGError err;
0271     SKGTRACEINFUNCRC(10, err)
0272     if ((m_currentDocument != nullptr) && (SKGMainPanel::getMainPanel() != nullptr)) {
0273         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0274         err = m_currentDocument->removeAllTransactions();
0275         QApplication::restoreOverrideCursor();
0276 
0277         // status bar
0278         IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Clear history successfully done.")))
0279         else {
0280             err.addError(ERR_FAIL, i18nc("Error message", "Clear history failed"));
0281         }
0282 
0283         // Display error
0284         SKGMainPanel::displayErrorMessage(err);
0285     }
0286 }
0287 
0288 void SKGUndoRedoPlugin::onUndoSave()
0289 {
0290     SKGError err;
0291     SKGTRACEINFUNCRC(10, err)
0292     if ((m_currentDocument != nullptr) && (SKGMainPanel::getMainPanel() != nullptr)) {
0293         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0294         err = m_currentDocument->undoRedoTransaction(SKGDocument::UNDOLASTSAVE);
0295         QApplication::restoreOverrideCursor();
0296 
0297         // status bar
0298         IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Undo successfully done.")))
0299         else {
0300             err.addError(ERR_FAIL, i18nc("Error message",  "Undo failed"));
0301         }
0302 
0303         // Display error
0304         SKGMainPanel::displayErrorMessage(err);
0305     }
0306 }
0307 
0308 void SKGUndoRedoPlugin::onUndo()
0309 {
0310     SKGError err;
0311     SKGTRACEINFUNCRC(10, err)
0312     if ((m_currentDocument != nullptr) && (SKGMainPanel::getMainPanel() != nullptr)) {
0313         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0314         int pos = qobject_cast<QAction*>(sender())->data().toInt();
0315         for (int i = 1 ; !err && i <= pos; ++i) {
0316             err = m_currentDocument->undoRedoTransaction(SKGDocument::UNDO);
0317         }
0318         QApplication::restoreOverrideCursor();
0319 
0320         // status bar
0321         IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Undo successfully done.")))
0322         else {
0323             err.addError(ERR_FAIL, i18nc("Error message",  "Undo failed"));
0324         }
0325 
0326         // Display error
0327         SKGMainPanel::displayErrorMessage(err);
0328     }
0329 }
0330 
0331 void SKGUndoRedoPlugin::onShowUndoMenu()
0332 {
0333     if ((m_undoMenu != nullptr) && (m_currentDocument != nullptr)) {
0334         m_undoMenu->clear();
0335         SKGStringListList listTmp;
0336         m_currentDocument->executeSelectSqliteOrder(
0337             QStringLiteral("SELECT t_name, t_savestep FROM doctransaction WHERE t_mode='U' ORDER BY d_date DESC LIMIT 7"),
0338             listTmp);
0339         int nb = listTmp.count();
0340         for (int i = 1; i < nb; ++i) {
0341             QAction* act = m_undoMenu->addAction(listTmp.at(i).at(1) == QStringLiteral("Y") ?  SKGServices::fromTheme(QStringLiteral("document-revert")) : SKGServices::fromTheme(QStringLiteral("edit-undo")), listTmp.at(i).at(0));
0342             if (act != nullptr) {
0343                 act->setData(i);
0344                 connect(act, &QAction::triggered, this, &SKGUndoRedoPlugin::onUndo);
0345             }
0346         }
0347     }
0348 }
0349 
0350 void SKGUndoRedoPlugin::onRedo()
0351 {
0352     SKGError err;
0353     SKGTRACEINFUNCRC(10, err)
0354     if ((m_currentDocument != nullptr) && (SKGMainPanel::getMainPanel() != nullptr)) {
0355         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0356         int pos = qobject_cast<QAction*>(sender())->data().toInt();
0357         for (int i = 1 ; !err && i <= pos; ++i) {
0358             err = m_currentDocument->undoRedoTransaction(SKGDocument::REDO);
0359         }
0360         QApplication::restoreOverrideCursor();
0361 
0362         // status bar
0363         IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Redo successfully done.")))
0364         else {
0365             err.addError(ERR_FAIL, i18nc("Error message",  "Redo failed"));
0366         }
0367 
0368         // Display error
0369         SKGMainPanel::displayErrorMessage(err);
0370     }
0371 }
0372 
0373 void SKGUndoRedoPlugin::onShowRedoMenu()
0374 {
0375     if ((m_redoMenu != nullptr) && (m_currentDocument != nullptr)) {
0376         m_redoMenu->clear();
0377         SKGStringListList listTmp;
0378         m_currentDocument->executeSelectSqliteOrder(
0379             QStringLiteral("SELECT t_name FROM doctransaction WHERE t_mode='R' ORDER BY d_date ASC LIMIT 7"),
0380             listTmp);
0381         int nb = listTmp.count();
0382         for (int i = 1; i < nb; ++i) {
0383             QAction* act = m_redoMenu->addAction(SKGServices::fromTheme(QStringLiteral("edit-redo")), listTmp.at(i).at(0));
0384             if (act != nullptr) {
0385                 act->setData(i);
0386                 connect(act, &QAction::triggered, this, &SKGUndoRedoPlugin::onRedo);
0387             }
0388         }
0389     }
0390 }
0391 
0392 QDockWidget* SKGUndoRedoPlugin::getDockWidget()
0393 {
0394     return m_dockWidget;
0395 }
0396 
0397 #include <skgundoredoplugin.moc>