1 /***************************************************************************
2  * SPDX-FileCopyrightText: 2021 S. MANKOWSKI stephane@mankowski.fr
3  * SPDX-FileCopyrightText: 2021 G. DE BURE support@mankowski.fr
4  * SPDX-License-Identifier: GPL-3.0-or-later
5  ***************************************************************************/
6 /** @file
7  * A skrooge plugin to manage scheduled operations.
8  *
9  * @author Stephane MANKOWSKI
10  */
11 #include "skgscheduledpluginwidget.h"
12 
13 #include <qlineedit.h>
14 
15 #include <qdom.h>
16 #include <qevent.h>
17 #include <qheaderview.h>
18 
19 #include "skgdocumentbank.h"
20 #include "skgmainpanel.h"
21 #include "skgobjectmodel.h"
22 #include "skgoperationobject.h"
23 #include "skgrecurrentoperationobject.h"
24 #include "skgtraces.h"
25 #include "skgtransactionmng.h"
26 
SKGScheduledPluginWidget(QWidget * iParent,SKGDocumentBank * iDocument)27 SKGScheduledPluginWidget::SKGScheduledPluginWidget(QWidget* iParent, SKGDocumentBank* iDocument)
28     : SKGTabPage(iParent, iDocument)
29 {
30     SKGTRACEINFUNC(1)
31     if (iDocument == nullptr) {
32         return;
33     }
34 
35     ui.setupUi(this);
36 
37     // Define action
38     if (SKGMainPanel::getMainPanel() != nullptr) {
39         auto actJumpToOperation = new QAction(SKGServices::fromTheme(QStringLiteral("quickopen")), ui.kJumpBtn->text(), this);
40         connect(actJumpToOperation, &QAction::triggered, this, &SKGScheduledPluginWidget::onJumpToTheOperation);
41         SKGMainPanel::getMainPanel()->registerGlobalAction(QStringLiteral("edit_reconcile"), actJumpToOperation, true, QStringList() << QStringLiteral("recurrentoperation"), 1, -1, 160);
42     }
43 
44     // Set show widget
45     ui.kView->getShowWidget()->addGroupedItem(QStringLiteral("all"), i18n("All"), QLatin1String(""), QLatin1String(""), QLatin1String(""), Qt::META + Qt::Key_A);
46     ui.kView->getShowWidget()->addGroupedItem(QStringLiteral("ongoing"), i18n("Ongoing"), QStringLiteral("vcs-normal"), QStringLiteral("t_times='N' OR i_nb_times>0"), QLatin1String(""), Qt::META + Qt::Key_O);
47     ui.kView->getShowWidget()->addGroupedItem(QStringLiteral("finished"), i18n("Complete"), QStringLiteral("vcs-conflicting"), QStringLiteral("t_times='Y' AND i_nb_times=0"), QLatin1String(""), Qt::META + Qt::Key_C);
48     ui.kView->getShowWidget()->setDefaultState(QStringLiteral("all"));
49 
50     ui.kView->setModel(new SKGObjectModel(qobject_cast<SKGDocumentBank*>(getDocument()), QStringLiteral("v_recurrentoperation_display"), QLatin1String(""), this, QLatin1String(""), false));
51 
52     connect(ui.kView->getView(), &SKGTreeView::doubleClicked, SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("open")).data(), &QAction::trigger);
53     connect(ui.kView->getView(), &SKGTreeView::selectionChangedDelayed, this, [ = ] {this->onSelectionChanged();});
54 
55     // Add Standard KDE Icons to buttons to Operations
56     ui.kModifyBtn->setIcon(SKGServices::fromTheme(QStringLiteral("dialog-ok")));
57     ui.kProcessBtn->setIcon(SKGServices::fromTheme(QStringLiteral("system-run")));
58     connect(ui.kProcessBtn, &QToolButton::clicked, this, &SKGScheduledPluginWidget::onProcess);
59 
60     auto processImmediatelyAction = new QAction(SKGServices::fromTheme(QStringLiteral("system-run")), i18nc("User action", "Process immediately"), this);
61     connect(processImmediatelyAction, &QAction::triggered, this, &SKGScheduledPluginWidget::onProcessImmediately);
62     auto processMenu = new QMenu(this);
63     processMenu->addAction(processImmediatelyAction);
64     ui.kProcessBtn->setMenu(processMenu);
65 
66     ui.kJumpBtn->setIcon(SKGServices::fromTheme(QStringLiteral("quickopen")));
67 
68     ui.kTitle->setIcon(SKGServices::fromTheme(QStringLiteral("dialog-information")), KTitleWidget::ImageLeft);
69     bool exist = false;
70     getDocument()->existObjects(QStringLiteral("recurrentoperation"), QLatin1String(""), exist);
71     ui.kTitle->setVisible(!exist);
72 
73     // Set Event filters to catch CTRL+ENTER or SHIFT+ENTER
74     this->installEventFilter(this);
75 
76     connect(ui.kRemindMe, &QCheckBox::toggled, ui.kRemindMeVal, &QSpinBox::setEnabled);
77     connect(ui.kRemindMe, &QCheckBox::toggled, ui.label_3, &QSpinBox::setEnabled);
78 
79     connect(ui.kNbTimes, &QCheckBox::toggled, ui.kNbTimesVal, &QSpinBox::setEnabled);
80     connect(ui.kNbTimes, &QCheckBox::toggled, ui.kLastOccurenceDate, &QSpinBox::setEnabled);
81 
82     connect(ui.kAutoWrite, &QCheckBox::toggled, ui.kAutoWriteVal, &QSpinBox::setEnabled);
83     connect(ui.kAutoWrite, &QCheckBox::toggled, ui.label_4, &QSpinBox::setEnabled);
84 
85     connect(ui.kModifyBtn, &QPushButton::clicked, this, &SKGScheduledPluginWidget::onUpdate);
86     connect(ui.kJumpBtn, &QPushButton::clicked, this, &SKGScheduledPluginWidget::onJumpToTheOperation);
87 
88     connect(ui.kOnceEveryUnit, static_cast<void (KComboBox::*)(const QString&)>(&KComboBox::currentTextChanged), this, &SKGScheduledPluginWidget::onNbOccurrenceChanged);
89     connect(ui.kLastOccurenceDate, &SKGDateEdit::dateChanged, this, &SKGScheduledPluginWidget::onNbOccurrenceChanged);
90     connect(ui.kNbTimesVal, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &SKGScheduledPluginWidget::onNbOccurrenceChanged);
91     connect(ui.kOnceEveryVal, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &SKGScheduledPluginWidget::onNbOccurrenceChanged);
92 }
93 
~SKGScheduledPluginWidget()94 SKGScheduledPluginWidget::~SKGScheduledPluginWidget()
95 {
96     SKGTRACEINFUNC(1)
97 }
98 
eventFilter(QObject * iObject,QEvent * iEvent)99 bool SKGScheduledPluginWidget::eventFilter(QObject* iObject, QEvent* iEvent)
100 {
101     if ((iEvent != nullptr) && iEvent->type() == QEvent::KeyPress) {
102         auto* keyEvent = dynamic_cast<QKeyEvent*>(iEvent);
103         if (keyEvent && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) && iObject == this) {
104             if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0u && ui.kModifyBtn->isEnabled()) {
105                 ui.kModifyBtn->click();
106             }
107         }
108     }
109 
110     return SKGTabPage::eventFilter(iObject, iEvent);
111 }
112 
getState()113 QString SKGScheduledPluginWidget::getState()
114 {
115     SKGTRACEINFUNC(10)
116     QDomDocument doc(QStringLiteral("SKGML"));
117     QDomElement root = doc.createElement(QStringLiteral("parameters"));
118     doc.appendChild(root);
119     root.setAttribute(QStringLiteral("view"), ui.kView->getState());
120     return doc.toString();
121 }
122 
setState(const QString & iState)123 void SKGScheduledPluginWidget::setState(const QString& iState)
124 {
125     SKGTRACEINFUNC(10)
126     QDomDocument doc(QStringLiteral("SKGML"));
127     doc.setContent(iState);
128     QDomElement root = doc.documentElement();
129     ui.kView->setState(root.attribute(QStringLiteral("view")));
130 
131     QString selection = root.attribute(QStringLiteral("selection"));
132 
133     if (!selection.isEmpty()) {
134         QStringList uuids = SKGServices::splitCSVLine(selection);
135         ui.kView->getView()->selectObjects(uuids, true);  // FIXME // TODO(Stephane MANKOWSKI)
136         onSelectionChanged();
137     }
138 }
139 
getDefaultStateAttribute()140 QString SKGScheduledPluginWidget::getDefaultStateAttribute()
141 {
142     return QStringLiteral("SKGSCHEDULED_DEFAULT_PARAMETERS");
143 }
144 
mainWidget()145 QWidget* SKGScheduledPluginWidget::mainWidget()
146 {
147     return ui.kView->getView();
148 }
149 
onJumpToTheOperation()150 void SKGScheduledPluginWidget::onJumpToTheOperation()
151 {
152     // Get selection
153     SKGObjectBase::SKGListSKGObjectBase selection = getSelectedObjects();
154     if (!selection.isEmpty()) {
155         // Build where clause and title
156         QString wc = QStringLiteral("id IN (");
157         QString title = i18nc("Noun, a list of items", "Operations of the schedule");
158         int nb = selection.count();
159         for (int i = 0; i < nb; ++i) {
160             SKGRecurrentOperationObject recOp(selection.at(i));
161 
162             SKGOperationObject op;
163             recOp.getParentOperation(op);
164 
165             wc += SKGServices::intToString(op.getID());
166             if (i < nb - 1) {
167                 wc += ',';
168             }
169         }
170         wc += ')';
171 
172         // Call operation plugin
173         SKGMainPanel::getMainPanel()->openPage("skg://skrooge_operation_plugin/?template=Y&title_icon=chronometer&operationTable=v_operation_display_all&title=" %
174                                                SKGServices::encodeForUrl(title) % "&operationWhereClause=" % SKGServices::encodeForUrl(wc));
175     }
176 }
177 
onSelectionChanged()178 void SKGScheduledPluginWidget::onSelectionChanged()
179 {
180     SKGTRACEINFUNC(10)
181 
182     int nb = getNbSelectedObjects();
183     ui.kModifyBtn->setEnabled(nb != 0);
184     ui.kProcessBtn->setEnabled(nb != 0);
185     ui.kJumpBtn->setEnabled(nb > 0);
186 
187     if (nb == 1) {
188         SKGRecurrentOperationObject recOp(ui.kView->getView()->getFirstSelectedObject());
189 
190         ui.kFirstOccurenceDate->setDate(recOp.getDate());
191         ui.kOnceEveryVal->setValue(recOp.getPeriodIncrement());
192         ui.kOnceEveryUnit->setCurrentIndex(static_cast<int>(recOp.getPeriodUnit()));
193 
194         ui.kRemindMeVal->setValue(recOp.getWarnDays());
195         ui.kRemindMe->setCheckState(recOp.isWarnEnabled() ? Qt::Checked : Qt::Unchecked);
196 
197         ui.kAutoWriteVal->setValue(recOp.getAutoWriteDays());
198         ui.kAutoWrite->setCheckState(recOp.isAutoWriteEnabled() ? Qt::Checked : Qt::Unchecked);
199 
200         ui.kNbTimesVal->setValue(recOp.getTimeLimit());
201         ui.kNbTimes->setCheckState(recOp.hasTimeLimit() ? Qt::Checked : Qt::Unchecked);
202     } else if (nb > 1) {
203         ui.kFirstOccurenceDate->setEditText(NOUPDATE);
204     }
205 
206     Q_EMIT selectionChanged();
207 }
208 
onNbOccurrenceChanged()209 void SKGScheduledPluginWidget::onNbOccurrenceChanged()
210 {
211     QDate firstDate = ui.kFirstOccurenceDate->date();
212     auto punit = static_cast<SKGRecurrentOperationObject::PeriodUnit>(ui.kOnceEveryUnit->currentIndex());
213     int p = ui.kOnceEveryVal->value();
214 
215     if (ui.kLastOccurenceDate == this->sender()) {
216         // End date has been modified.
217         // We must set the number of occurrence
218         QDate lastDate = ui.kLastOccurenceDate->date();
219         if (lastDate <= firstDate) {
220             ui.kLastOccurenceDate->setDate(firstDate);
221             ui.kNbTimesVal->setValue(1);
222         } else {
223             int nbd = firstDate.daysTo(lastDate);
224             if (punit == SKGRecurrentOperationObject::DAY) {
225                 nbd = nbd / p;
226             } else if (punit == SKGRecurrentOperationObject::WEEK) {
227                 nbd = nbd / p / 7;
228             } else if (punit == SKGRecurrentOperationObject::MONTH) {
229                 nbd = (lastDate.day() >= firstDate.day() ? 0 : -1) + (lastDate.year() - firstDate.year()) * 12 + (lastDate.month() - firstDate.month());
230             } else if (punit == SKGRecurrentOperationObject::YEAR) {
231                 nbd = nbd / (365 * p);
232             }
233 
234             bool previous = ui.kNbTimesVal->blockSignals(true);
235             ui.kNbTimesVal->setValue(nbd + 1);
236             ui.kNbTimesVal->blockSignals(previous);
237         }
238 
239     } else {
240         // We must compute the date
241         p *= (ui.kNbTimesVal->value() - 1);
242         if (punit == SKGRecurrentOperationObject::DAY) {
243             firstDate = firstDate.addDays(p);
244         } else if (punit == SKGRecurrentOperationObject::WEEK) {
245             firstDate = firstDate.addDays(7 * p);
246         } else if (punit == SKGRecurrentOperationObject::MONTH) {
247             firstDate = firstDate.addMonths(p);
248         } else if (punit == SKGRecurrentOperationObject::YEAR) {
249             firstDate = firstDate.addYears(p);
250         }
251 
252         bool previous = ui.kLastOccurenceDate->blockSignals(true);
253         ui.kLastOccurenceDate->setDate(firstDate);
254         ui.kLastOccurenceDate->blockSignals(previous);
255     }
256 }
257 
onUpdate()258 void SKGScheduledPluginWidget::onUpdate()
259 {
260     SKGError err;
261     SKGTRACEINFUNCRC(10, err) {
262         // Get Selection
263         SKGObjectBase::SKGListSKGObjectBase selection = getSelectedObjects();
264 
265         int nb = selection.count();
266         SKGBEGINPROGRESSTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Recurrent operation update"), err, nb)
267         for (int i = 0; !err && i < nb; ++i) {
268             // Get the real object, not the object from the view
269             SKGRecurrentOperationObject recOp = SKGRecurrentOperationObject(selection.at(i).getDocument(), selection.at(i).getID());
270 
271             // Update it
272             if (ui.kFirstOccurenceDate->currentText() != NOUPDATE) {
273                 err = recOp.setDate(ui.kFirstOccurenceDate->date());
274             }
275             IFOKDO(err, recOp.setPeriodIncrement(ui.kOnceEveryVal->value()))
276             IFOKDO(err, recOp.setPeriodUnit(static_cast<SKGRecurrentOperationObject::PeriodUnit>(ui.kOnceEveryUnit->currentIndex())))
277             IFOKDO(err, recOp.setWarnDays(ui.kRemindMeVal->value()))
278             IFOKDO(err, recOp.warnEnabled(ui.kRemindMe->checkState() == Qt::Checked))
279             IFOKDO(err, recOp.setAutoWriteDays(ui.kAutoWriteVal->value()))
280             IFOKDO(err, recOp.autoWriteEnabled(ui.kAutoWrite->checkState() == Qt::Checked))
281             IFOKDO(err, recOp.setTimeLimit(ui.kNbTimesVal->value()))
282             IFOKDO(err, recOp.timeLimit(ui.kNbTimes->checkState() == Qt::Checked))
283             IFOKDO(err, recOp.save())
284 
285             // Send message
286             IFOKDO(err, getDocument()->sendMessage(i18nc("An information to the user", "The recurrent operation '%1' has been updated", recOp.getDisplayName()), SKGDocument::Hidden))
287 
288             IFOKDO(err, getDocument()->stepForward(i + 1))
289         }
290     }
291     // status bar
292     IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Recurrent operation updated.")))
293     else {
294         err.addError(ERR_FAIL, i18nc("Error message",  "Update failed"));
295     }
296 
297     // Display error
298     SKGMainPanel::displayErrorMessage(err, true);
299 
300     // Set focus on table
301     ui.kView->getView()->setFocus();
302 }
303 
onProcessImmediately()304 void SKGScheduledPluginWidget::onProcessImmediately()
305 {
306     onProcess(true);
307 }
308 
onProcess(bool iImmediately)309 void SKGScheduledPluginWidget::onProcess(bool iImmediately)
310 {
311     SKGError err;
312     SKGTRACEINFUNCRC(10, err) {
313         // Get Selection
314         SKGObjectBase::SKGListSKGObjectBase selection = getSelectedObjects();
315 
316         int nb = selection.count();
317         SKGBEGINPROGRESSTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Insert recurrent operations"), err, nb)
318         for (int i = 0; !err && i < nb; ++i) {
319             // Get the real object, not the object from the view
320             SKGRecurrentOperationObject recOp = SKGRecurrentOperationObject(selection.at(i).getDocument(), selection.at(i).getID());
321 
322             // Process it
323             int nbi = 0;
324             err = recOp.process(nbi, true, (iImmediately ? recOp.getDate() : QDate::currentDate()));
325             IFOKDO(err, getDocument()->stepForward(i + 1))
326         }
327     }
328     // status bar
329     IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Recurrent operation inserted.")))
330     else {
331         err.addError(ERR_FAIL, i18nc("Error message",  "Insertion failed"));
332     }
333 
334     // Display error
335     SKGMainPanel::displayErrorMessage(err);
336 }
337 
338 
339 
340 
341