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

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 Skrooge plugin to generate categories.
0008  *
0009  * @author Stephane MANKOWSKI / Guillaume DE BURE
0010  */
0011 #include "skgcategoriespluginwidget.h"
0012 
0013 #include <klocalizedstring.h>
0014 #include <qaction.h>
0015 
0016 #include <qdom.h>
0017 #include <qevent.h>
0018 #include <qgraphicsscene.h>
0019 #include <qheaderview.h>
0020 #include <qwidget.h>
0021 
0022 #include "skgcategoryobject.h"
0023 #include "skgdocumentbank.h"
0024 #include "skgmainpanel.h"
0025 #include "skgobjectmodel.h"
0026 #include "skgsortfilterproxymodel.h"
0027 #include "skgtraces.h"
0028 #include "skgtransactionmng.h"
0029 
0030 SKGCategoriesPluginWidget::SKGCategoriesPluginWidget(QWidget* iParent, SKGDocumentBank* iDocument)
0031     : SKGTabPage(iParent, iDocument)
0032 {
0033     SKGTRACEINFUNC(10)
0034     if (iDocument == nullptr) {
0035         return;
0036     }
0037 
0038     ui.setupUi(this);
0039 
0040     // Define action
0041     QStringList overlaycategories;
0042     overlaycategories.push_back(QStringLiteral("view-categories"));
0043     if (SKGMainPanel::getMainPanel() != nullptr) {
0044         auto addCategoryAction = new QAction(SKGServices::fromTheme(QStringLiteral("list-add"), overlaycategories), i18nc("Verb", "Add category"), this);
0045         connect(addCategoryAction, &QAction::triggered, this, &SKGCategoriesPluginWidget::onAddCategory);
0046         SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_add_category"), addCategoryAction, true, QStringList() << QStringLiteral("category"), 1, -1, 450);
0047     }
0048 
0049     // Set show widget
0050     ui.kCategoriesView->getShowWidget()->addGroupedItem(QStringLiteral("all"), i18n("All"), QLatin1String(""), QLatin1String(""), QLatin1String(""), Qt::META + Qt::Key_A);
0051     ui.kCategoriesView->getShowWidget()->addGroupedItem(QStringLiteral("opened"), i18n("Opened"), QStringLiteral("vcs-normal"), QStringLiteral("t_close='N'"), QLatin1String(""), Qt::META + Qt::Key_O);
0052     ui.kCategoriesView->getShowWidget()->addGroupedItem(QStringLiteral("closed"), i18n("Closed"), QStringLiteral("vcs-conflicting"), QStringLiteral("t_close='Y'"), QLatin1String(""), Qt::META + Qt::Key_C);
0053     ui.kCategoriesView->getShowWidget()->addGroupedItem(QStringLiteral("income"), i18n("Income"), QStringLiteral("list-add"), QStringLiteral("f_REALCURRENTAMOUNT>=0"), QLatin1String(""), Qt::META + Qt::Key_Plus);
0054     ui.kCategoriesView->getShowWidget()->addGroupedItem(QStringLiteral("expenditure"), i18n("Expenditure"), QStringLiteral("list-remove"), QStringLiteral("f_REALCURRENTAMOUNT<=0"), QLatin1String(""), Qt::META + Qt::Key_Minus);
0055     ui.kCategoriesView->getShowWidget()->addGroupedItem(QStringLiteral("highlighted"), i18n("Highlighted"), QStringLiteral("bookmarks"), QStringLiteral("t_HASBOOKMARKEDCHILD<>'N'"), QLatin1String(""), Qt::META + Qt::Key_H);
0056     ui.kCategoriesView->getShowWidget()->setDefaultState(QStringLiteral("all"));
0057 
0058     ui.kAdd->setIcon(SKGServices::fromTheme(QStringLiteral("list-add"), overlaycategories));
0059     ui.kAdd->setToolTip(i18nc("An help", "Add a sub category to the selected one"));
0060     ui.kAdd->setMaximumWidth(ui.kAdd->height());
0061 
0062     ui.kNameLbl->setText(i18n("%1:", iDocument->getDisplay(QStringLiteral("t_name"))));
0063 
0064     ui.kModifyCategoryButton->setIcon(SKGServices::fromTheme(QStringLiteral("dialog-ok")));
0065     ui.kDeleteUnusedButton->setIcon(SKGServices::fromTheme(QStringLiteral("edit-delete")));
0066 
0067     ui.kCategoriesView->setModel(new SKGObjectModel(qobject_cast<SKGDocumentBank*>(getDocument()), QStringLiteral("v_category_display"), QStringLiteral("1=0"), this, QStringLiteral("rd_category_id"), false));
0068     ui.kCategoriesView->getView()->setRootIsDecorated(true);
0069     ui.kCategoriesView->getView()->setAnimated(false);  // Not compatible with double click
0070     ui.kCategoriesView->getView()->resizeColumnToContents(0);
0071     ui.kCategoriesView->getView()->header()->setStretchLastSection(false);
0072 
0073     connect(getDocument(), &SKGDocument::tableModified, this, &SKGCategoriesPluginWidget::dataModified, Qt::QueuedConnection);
0074     connect(ui.kCategoriesView->getView(), &SKGTreeView::clickEmptyArea, this, &SKGCategoriesPluginWidget::cleanEditor);
0075     connect(ui.kCategoriesView->getView(), &SKGTreeView::doubleClicked, SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("open")).data(), &QAction::trigger);
0076     connect(ui.kCategoriesView->getView(), &SKGTreeView::selectionChangedDelayed, this, [ = ] {this->onSelectionChanged();});
0077 
0078     connect(ui.kModifyCategoryButton, &QPushButton::clicked, this, &SKGCategoriesPluginWidget::onUpdateCategory);
0079     connect(ui.kNameInput1, &QLineEdit::textChanged, this, &SKGCategoriesPluginWidget::onEditorModified);
0080     connect(ui.kNameInput2, &QLineEdit::textChanged, this, &SKGCategoriesPluginWidget::onEditorModified);
0081     connect(ui.kDeleteUnusedButton, &QPushButton::clicked, this, &SKGCategoriesPluginWidget::onDeleteUnused);
0082     connect(ui.kAdd, &QPushButton::clicked, this, &SKGCategoriesPluginWidget::onAddCategory);
0083 
0084     // Set Event filters to catch CTRL+ENTER or SHIFT+ENTER
0085     this->installEventFilter(this);
0086 
0087     dataModified(QLatin1String(""), 0);
0088 }
0089 
0090 SKGCategoriesPluginWidget::~SKGCategoriesPluginWidget()
0091 {
0092     SKGTRACEINFUNC(10)
0093 }
0094 
0095 bool SKGCategoriesPluginWidget::eventFilter(QObject* iObject, QEvent* iEvent)
0096 {
0097     if ((iEvent != nullptr) && iEvent->type() == QEvent::KeyPress) {
0098         auto* keyEvent = dynamic_cast<QKeyEvent*>(iEvent);
0099         if (keyEvent && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) && iObject == this) {
0100             if ((QApplication::keyboardModifiers() & Qt::ControlModifier) != 0u && ui.kAdd->isEnabled()) {
0101                 ui.kAdd->click();
0102             } else if ((QApplication::keyboardModifiers() &Qt::ShiftModifier) != 0u && ui.kModifyCategoryButton->isEnabled()) {
0103                 ui.kModifyCategoryButton->click();
0104             }
0105         }
0106     }
0107 
0108     return SKGTabPage::eventFilter(iObject, iEvent);
0109 }
0110 
0111 void SKGCategoriesPluginWidget::dataModified(const QString& iTableName, int iIdTransaction, bool iLightTransaction)
0112 {
0113     SKGTRACEINFUNC(10)
0114     Q_UNUSED(iIdTransaction)
0115 
0116     if (!iLightTransaction) {
0117         if (iTableName == QStringLiteral("category") || iTableName.isEmpty()) {
0118             // Set completions
0119             SKGMainPanel::fillWithDistinctValue(QList<QWidget*>() << ui.kNameInput1, getDocument(), QStringLiteral("category"), QStringLiteral("t_name"), QLatin1String(""), true);
0120 
0121             onSelectionChanged();
0122         }
0123     }
0124 }
0125 
0126 void SKGCategoriesPluginWidget::onSelectionChanged()
0127 {
0128     SKGTRACEINFUNC(10)
0129     // Clean widgets
0130     int nb = ui.kLayout->count();
0131     for (int i = 0; i < nb; ++i) {
0132         QLayoutItem* item = ui.kLayout->itemAt(0);
0133         if (item != nullptr) {
0134             ui.kLayout->removeItem(item);
0135             delete item->widget();
0136             delete item;
0137         }
0138     }
0139 
0140     ui.kNameInput2->setText(QLatin1String(""));
0141 
0142     int nbSelect = getNbSelectedObjects();
0143     if (nbSelect == 1) {
0144         SKGCategoryObject obj(getFirstSelectedObject());
0145         ui.kNameInput1->setText(obj.getName());
0146 
0147         SKGCategoryObject parentCat;
0148         obj.getParentCategory(parentCat);
0149         QString parentName = parentCat.getFullName();
0150 
0151         QStringList items = SKGServices::splitCSVLine(parentName, QString(OBJECTSEPARATOR).trimmed().at(0));
0152         int nbItems = items.count();
0153         QString fullname;
0154         for (int i = 0; i < nbItems; ++i) {
0155             auto btn = new QPushButton(ui.kFrame);
0156             btn->setFlat(true);
0157             btn->setText(items.at(i).trimmed());
0158             if (!fullname.isEmpty()) {
0159                 fullname += OBJECTSEPARATOR;
0160             }
0161             fullname += items.at(i).trimmed();
0162             btn->setProperty("FULLNAME", fullname);
0163             connect(btn, &QPushButton::clicked, this, &SKGCategoriesPluginWidget::changeSelection);
0164             ui.kLayout->addWidget(btn);
0165 
0166             auto lbl = new QLabel(ui.kFrame);
0167             lbl->setText(OBJECTSEPARATOR);
0168             ui.kLayout->addWidget(lbl);
0169         }
0170     } else if (nbSelect > 1) {
0171         ui.kNameInput1->setText(NOUPDATE);
0172     }
0173 
0174     onEditorModified();
0175     Q_EMIT selectionChanged();
0176 }
0177 
0178 void SKGCategoriesPluginWidget::changeSelection()
0179 {
0180     QString fullname = sender()->property("FULLNAME").toString();
0181     SKGObjectBase::SKGListSKGObjectBase list;
0182     getDocument()->getObjects(QStringLiteral("v_category"), "t_fullname='" % SKGServices::stringToSqlString(fullname) % '\'', list);
0183 
0184     if (!list.isEmpty()) {
0185         ui.kCategoriesView->getView()->selectObject(list.at(0).getUniqueID());
0186         onSelectionChanged();
0187     }
0188 }
0189 
0190 QString SKGCategoriesPluginWidget::getState()
0191 {
0192     SKGTRACEINFUNC(10)
0193     QDomDocument doc(QStringLiteral("SKGML"));
0194     QDomElement root = doc.createElement(QStringLiteral("parameters"));
0195     doc.appendChild(root);
0196 
0197     // Memorize table settings
0198     root.setAttribute(QStringLiteral("view"), ui.kCategoriesView->getState());
0199     return doc.toString();
0200 }
0201 
0202 void SKGCategoriesPluginWidget::setState(const QString& iState)
0203 {
0204     SKGTRACEINFUNC(10)
0205     QDomDocument doc(QStringLiteral("SKGML"));
0206     doc.setContent(iState);
0207     QDomElement root = doc.documentElement();
0208 
0209     ui.kCategoriesView->setFilter(SKGServices::fromTheme(root.attribute(QStringLiteral("title_icon"))), root.attribute(QStringLiteral("title")), root.attribute(QStringLiteral("whereClause")));
0210     ui.kCategoriesView->setState(root.attribute(QStringLiteral("view")));
0211 }
0212 
0213 QString SKGCategoriesPluginWidget::getDefaultStateAttribute()
0214 {
0215     return QStringLiteral("SKGCATEGORIES_DEFAULT_PARAMETERS");
0216 }
0217 
0218 QWidget* SKGCategoriesPluginWidget::mainWidget()
0219 {
0220     return ui.kCategoriesView->getView();
0221 }
0222 
0223 void SKGCategoriesPluginWidget::onEditorModified()
0224 {
0225     _SKGTRACEINFUNC(10)
0226     int nb = getNbSelectedObjects();
0227     ui.kNameInput1->setVisible(nb >= 1);
0228     ui.kCatSeparator->setVisible(nb >= 1);
0229     ui.kModifyCategoryButton->setEnabled(!ui.kNameInput1->text().isEmpty() && nb >= 1);
0230     ui.kAdd->setEnabled(!ui.kNameInput2->text().isEmpty());
0231 }
0232 
0233 void SKGCategoriesPluginWidget::onAddCategory()
0234 {
0235     SKGError err;
0236     _SKGTRACEINFUNCRC(10, err)
0237     SKGCategoryObject cat;
0238     QString name = ui.kNameInput2->text();
0239     if (name.isEmpty()) {
0240         name = i18nc("Noun, default name for a new category", "New category");
0241     }
0242     {
0243         // Get Selection
0244         SKGObjectBase::SKGListSKGObjectBase selection = getSelectedObjects();
0245 
0246         int nb = selection.count();
0247         SKGBEGINTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Category creation '%1'", name), err)
0248 
0249         if (nb == 1) {
0250             SKGCategoryObject parentCat(selection[0]);
0251             name = parentCat.getFullName() % OBJECTSEPARATOR % name;
0252         }
0253         IFOKDO(err, SKGCategoryObject::createPathCategory(qobject_cast<SKGDocumentBank*>(getDocument()), name, cat, false, true))
0254 
0255         // Send message
0256         IFOKDO(err, cat.getDocument()->sendMessage(i18nc("An information message", "The category '%1' has been created", cat.getDisplayName()), SKGDocument::Hidden))
0257     }
0258 
0259     // status bar
0260     IFOK(err) {
0261         ui.kCategoriesView->getView()->selectObject(cat.getUniqueID());
0262         err = SKGError(0, i18nc("Successful message after an user action", "Category '%1' created", name));
0263     } else {
0264         err.addError(ERR_FAIL, i18nc("Error message", "Category creation failed"));
0265     }
0266 
0267     // Display error
0268     SKGMainPanel::displayErrorMessage(err, true);
0269 }
0270 
0271 void SKGCategoriesPluginWidget::onUpdateCategory()
0272 {
0273     SKGError err;
0274     _SKGTRACEINFUNCRC(10, err)
0275     // Get Selection
0276     SKGObjectBase::SKGListSKGObjectBase selection = getSelectedObjects();
0277 
0278     int nb = selection.count();
0279     {
0280         QString name = ui.kNameInput1->text();
0281         SKGBEGINPROGRESSTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Category update"), err, nb)
0282         if (nb > 1 && name != NOUPDATE && !name.startsWith(QLatin1String("="))) {
0283             getDocument()->sendMessage(i18nc("Information message", "You tried to modify all names of selected categories. Categories have been merged."));
0284 
0285             // Do the merge
0286             SKGCategoryObject catObj1(selection[0]);
0287             for (int i = 1; !err && i < nb; ++i) {
0288                 SKGCategoryObject catObj(selection.at(i));
0289 
0290                 // Send message
0291                 IFOKDO(err, catObj.getDocument()->sendMessage(i18nc("An information message", "The category '%1' has been merged with category '%2'", catObj1.getDisplayName(), catObj.getDisplayName()), SKGDocument::Hidden))
0292 
0293                 IFOKDO(err, catObj1.merge(catObj))
0294 
0295                 IFOKDO(err, getDocument()->stepForward(i))
0296             }
0297 
0298             // Change selection for the rest of the transaction
0299             selection.clear();
0300             selection.push_back(catObj1);
0301             nb = 1;
0302         }
0303 
0304         for (int i = 0; !err && i < nb; ++i) {
0305             // Modification of object
0306             SKGCategoryObject cat(selection.at(i));
0307             err = cat.setName(name);
0308             IFOKDO(err, cat.save())
0309 
0310             // Send message
0311             IFOKDO(err, cat.getDocument()->sendMessage(i18nc("An information message", "The category '%1' has been updated", cat.getDisplayName()), SKGDocument::Hidden))
0312         }
0313     }
0314     // status bar
0315     IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Category updated")))
0316     else {
0317         err.addError(ERR_FAIL, i18nc("Error message", "Category update failed"));
0318     }
0319 
0320     // Display error
0321     SKGMainPanel::displayErrorMessage(err, true);
0322 
0323     // Set focus on table
0324     ui.kCategoriesView->getView()->setFocus();
0325 }
0326 
0327 void SKGCategoriesPluginWidget::onDeleteUnused()
0328 {
0329     QAction* act = SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("clean_delete_unused_categories"));
0330     if (act != nullptr) {
0331         act->trigger();
0332     }
0333 }
0334 
0335 void SKGCategoriesPluginWidget::cleanEditor()
0336 {
0337     if (getNbSelectedObjects() == 0) {
0338         ui.kNameInput1->setText(QLatin1String(""));
0339         ui.kNameInput2->setText(QLatin1String(""));
0340     }
0341 }
0342 void SKGCategoriesPluginWidget::activateEditor()
0343 {
0344     ui.kNameInput1->setFocus();
0345 }
0346 
0347 bool SKGCategoriesPluginWidget::isEditor()
0348 {
0349     return true;
0350 }
0351 
0352