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