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 widget to select what to show.
8  *
9  * @author Stephane MANKOWSKI / Guillaume DE BURE
10  */
11 #include "skgshow.h"
12 
13 #include <klocalizedstring.h>
14 #include <qdom.h>
15 #include <qicon.h>
16 #include <qmenu.h>
17 #include <qwidgetaction.h>
18 
19 #include "skgperiodedit.h"
20 #include "skgservices.h"
21 
SKGShow(QWidget * iParent)22 SKGShow::SKGShow(QWidget* iParent)
23     : QToolButton(iParent), m_mode(OR), m_inTrigger(false), m_displayTitle(true)
24 {
25     setPopupMode(QToolButton::InstantPopup);
26     setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
27     setAutoRaise(true);
28 
29     m_menu = new QMenu(this);
30     setMenu(m_menu);
31 
32     // Time to emit stateChanged
33     m_timer.setSingleShot(true);
34     connect(&m_timer, &QTimer::timeout, this, &SKGShow::stateChanged, Qt::QueuedConnection);
35 
36     hide();
37 }
38 
~SKGShow()39 SKGShow::~SKGShow()
40 {
41     m_menu = nullptr;
42 }
43 
getMode()44 SKGShow::OperatorMode SKGShow::getMode()
45 {
46     return m_mode;
47 }
48 
setMode(SKGShow::OperatorMode iMode)49 void SKGShow::setMode(SKGShow::OperatorMode iMode)
50 {
51     if (m_mode != iMode) {
52         m_mode = iMode;
53         Q_EMIT modified();
54     }
55 }
56 
getState()57 QString SKGShow::getState()
58 {
59     QStringList itemsChecked;
60     if (m_menu != nullptr) {
61         QList<QAction*> actionsList = m_menu->actions();
62         int nb = actionsList.count();
63         itemsChecked.reserve(nb);
64         for (int i = 0; i < nb; ++i) {
65             QAction* act = actionsList.at(i);
66             if (act != nullptr) {
67                 auto* wact = qobject_cast<QWidgetAction*>(act);
68                 if (wact != nullptr) {
69                     auto* pedit = qobject_cast<SKGPeriodEdit*>(wact->defaultWidget());
70                     itemsChecked.push_back(act->data().toString() % ":" % pedit->getState());
71                 } else {
72                     if (act->isChecked()) {
73                         itemsChecked.push_back(act->data().toString());
74                     }
75                 }
76             }
77         }
78     }
79     return SKGServices::stringsToCsv(itemsChecked);
80 }
81 
setDefaultState(const QString & iState)82 void SKGShow::setDefaultState(const QString& iState)
83 {
84     m_defaultState = iState;
85     setState(m_defaultState);
86 }
87 
setState(const QString & iState)88 void SKGShow::setState(const QString& iState)
89 {
90     if (m_menu != nullptr) {
91         QStringList itemsChecked = SKGServices::splitCSVLine(iState.isEmpty() ? m_defaultState : iState);
92 
93         int nb = m_actions.count();
94         for (int i = 0; i < nb; ++i) {
95             QAction* act = m_actions.at(i);
96             if (act != nullptr) {
97                 QString identifier = m_actions.at(i)->data().toString();
98                 auto* wact = qobject_cast<QWidgetAction*>(act);
99                 if (wact != nullptr) {
100                     auto* pedit = qobject_cast<SKGPeriodEdit*>(wact->defaultWidget());
101                     for (const auto& item : qAsConst(itemsChecked)) {
102                         if (item.startsWith(identifier % ":")) {
103                             pedit->setState(item.right(item.length() - identifier.length() - 1));
104                             break;
105                         }
106                     }
107                 } else {
108                     act->setChecked(itemsChecked.contains(identifier));
109                 }
110             }
111         }
112 
113         // Change tooltip
114         setToolTip(getTitle());
115 
116         // Emit event
117         emit stateChanged();
118     }
119 }
120 
getWhereClause() const121 QString SKGShow::getWhereClause() const
122 {
123     QString wc;
124     if (m_menu != nullptr) {
125         QList<QAction*> actionsList = m_menu->actions();
126         int nb = actionsList.count();
127         bool noCheck = true;
128         for (int i = 0; i < nb; ++i) {
129             QAction* act = actionsList.at(i);
130 
131             if (act != nullptr) {
132                 auto* wact = qobject_cast<QWidgetAction*>(act);
133                 if (wact != nullptr) {
134                     auto* pedit = qobject_cast<SKGPeriodEdit*>(wact->defaultWidget());
135                     if (!wc.isEmpty()) {
136                         wc += (m_mode == OR ? QStringLiteral(" OR ") : QStringLiteral(" AND "));
137                     }
138                     wc += '(' % pedit->getWhereClause() % ')';
139                     noCheck = false;
140 
141                 } else {
142                     if (act->isChecked()) {
143                         if (!wc.isEmpty()) {
144                             wc += (m_mode == OR ? QStringLiteral(" OR ") : QStringLiteral(" AND "));
145                         }
146                         wc += '(' % m_whereclause.value(act) % ')';
147                         noCheck = false;
148 
149                         if (m_whereclause.value(act).isEmpty()) {
150                             wc = QLatin1String("");
151                             break;
152                         }
153                     }
154                 }
155             }
156         }
157         if ((nb != 0) && noCheck) {
158             wc = QStringLiteral("1=0");
159         }
160     }
161     return wc;
162 }
163 
getTitle() const164 QString SKGShow::getTitle() const
165 {
166     QString wc;
167     if (m_menu != nullptr) {
168         int nb = m_actions.count();
169         for (int i = 0; i < nb; ++i) {
170             QAction* act = m_actions.at(i);
171             if (act != nullptr) {
172                 auto* wact = qobject_cast<QWidgetAction*>(act);
173                 if (wact != nullptr) {
174                     auto* pedit = qobject_cast<SKGPeriodEdit*>(wact->defaultWidget());
175                     if (!wc.isEmpty()) {
176                         wc += (m_mode == OR ? QStringLiteral(" + ") : QStringLiteral(" , "));
177                     }
178                     wc += pedit->text();
179                 } else {
180                     if (act->isChecked()) {
181                         if (!wc.isEmpty()) {
182                             wc += (m_mode == OR ? QStringLiteral(" + ") : QStringLiteral(" , "));
183                         }
184                         wc += act->toolTip();
185                     }
186                 }
187             }
188         }
189     }
190     return wc;
191 }
192 
clear()193 void SKGShow::clear()
194 {
195     m_check_to_check.clear();
196     m_uncheck_to_check.clear();
197     m_check_to_uncheck.clear();
198     m_uncheck_to_uncheck.clear();
199     m_actions.clear();
200     m_icons.clear();
201     m_whereclause.clear();
202     m_defaultState.clear();
203     m_menu->clear();
204 }
205 
count()206 int SKGShow::count()
207 {
208     return m_check_to_check.count();
209 }
210 
addGroupedItem(const QString & iIdentifier,const QString & iText,const QString & iIcon,const QString & iWhereClose,const QString & iGroup,const QKeySequence & iShortcut)211 int SKGShow::addGroupedItem(const QString& iIdentifier, const QString& iText, const QString& iIcon,
212                             const QString& iWhereClose, const QString& iGroup, const QKeySequence& iShortcut)
213 {
214     if (m_menu != nullptr) {
215         QActionGroup* groupAction = m_groups.value(iGroup);
216         if (groupAction == nullptr) {
217             groupAction = new QActionGroup(this);
218             m_groups[iGroup] = groupAction;
219         }
220 
221         QString name = iText;
222         name = name.replace('&', QStringLiteral("&&"));
223         QAction* act = m_menu->addAction(name);
224         if (act != nullptr) {
225             act->setToolTip(name);
226             act->setIcon(SKGServices::fromTheme(iIcon));
227             act->setData(iIdentifier);
228             act->setCheckable(true);
229             if (!iShortcut.isEmpty()) {
230                 act->setShortcuts(QList<QKeySequence>() << iShortcut << QKeySequence::fromString("Ctrl+Alt+" % iShortcut.toString()));
231             }
232 
233             m_check_to_check[act] = QLatin1String("");
234             m_check_to_uncheck[act] = QLatin1String("");
235             m_uncheck_to_check[act] = QLatin1String("");
236             m_uncheck_to_uncheck[act] = QLatin1String("");
237             m_actions.push_back(act);
238             m_icons.push_back(iIcon);
239 
240             m_whereclause[act] = iWhereClose;
241 
242             connect(act, &QAction::toggled, this, &SKGShow::trigger);
243 
244             groupAction->addAction(act);
245         }
246 
247         show();
248 
249         return (m_actions.count() - 1);
250     }
251     return -1;
252 }
253 
addPeriodItem(const QString & iIdentifier)254 int SKGShow::addPeriodItem(const QString& iIdentifier)
255 {
256     if (m_menu != nullptr) {
257         auto m_periodEdit1 = new SKGPeriodEdit(this);
258 
259         // Set default
260         QDomDocument doc(QStringLiteral("SKGML"));
261         QDomElement root = doc.createElement(QStringLiteral("parameters"));
262         doc.appendChild(root);
263         root.setAttribute(QStringLiteral("period"), SKGServices::intToString(static_cast<int>(SKGPeriodEdit::ALL)));
264         m_periodEdit1->setState(doc.toString());
265 
266         // Add widget in menu
267         auto act = new QWidgetAction(this);
268         if (act != nullptr) {
269             act->setData(iIdentifier);
270             act->setDefaultWidget(m_periodEdit1);
271 
272             m_check_to_check[act] = QLatin1String("");
273             m_check_to_uncheck[act] = QLatin1String("");
274             m_uncheck_to_check[act] = QLatin1String("");
275             m_uncheck_to_uncheck[act] = QLatin1String("");
276             m_actions.push_back(act);
277             m_icons.push_back(QLatin1String(""));
278 
279             m_whereclause[act] = QLatin1String("");
280 
281             connect(m_periodEdit1, &SKGPeriodEdit::changed, this, &SKGShow::triggerRefreshOnly);
282 
283             m_menu->addAction(act);
284         }
285 
286         show();
287 
288         return (m_actions.count() - 1);
289     }
290     return -1;
291 }
292 
addItem(const QString & iIdentifier,const QString & iText,const QString & iIcon,const QString & iWhereClose,const QString & iListIdToCheckWhenChecked,const QString & iListIdToUncheckWhenChecked,const QString & iListIdToCheckWhenUnchecked,const QString & iListIdToUncheckWhenUnchecked,const QKeySequence & iShortcut)293 int SKGShow::addItem(const QString& iIdentifier, const QString& iText, const QString& iIcon,
294                      const QString& iWhereClose,
295                      const QString& iListIdToCheckWhenChecked,
296                      const QString& iListIdToUncheckWhenChecked,
297                      const QString& iListIdToCheckWhenUnchecked,
298                      const QString& iListIdToUncheckWhenUnchecked,
299                      const QKeySequence& iShortcut
300                     )
301 {
302     if (m_menu != nullptr) {
303         QString name = iText;
304         name = name.replace('&', QStringLiteral("&&"));
305         QAction* act = m_menu->addAction(name);
306         if (act != nullptr) {
307             act->setToolTip(name);
308             act->setIcon(SKGServices::fromTheme(iIcon));
309             act->setData(iIdentifier);
310             act->setCheckable(true);
311             if (!iShortcut.isEmpty()) {
312                 act->setShortcuts(QList<QKeySequence>() << iShortcut << QKeySequence::fromString("Ctrl+Alt+" % iShortcut.toString()));
313             }
314 
315             m_check_to_check[act] = iListIdToCheckWhenChecked;
316             m_check_to_uncheck[act] = iListIdToUncheckWhenChecked;
317             m_uncheck_to_check[act] = iListIdToCheckWhenUnchecked;
318             m_uncheck_to_uncheck[act] = iListIdToUncheckWhenUnchecked;
319             m_actions.push_back(act);
320             m_icons.push_back(iIcon);
321 
322             m_whereclause[act] = iWhereClose;
323 
324             connect(act, &QAction::toggled, this, &SKGShow::trigger);
325         }
326 
327         show();
328 
329         return (m_actions.count() - 1);
330     }
331     return -1;
332 }
333 
setListIdToCheckWhenChecked(int iIndex,const QString & iIds)334 void SKGShow::setListIdToCheckWhenChecked(int iIndex, const QString& iIds)
335 {
336     m_check_to_check[m_actions.at(iIndex)] = iIds;
337 }
338 
setListIdToCheckWhenUnchecked(int iIndex,const QString & iIds)339 void SKGShow::setListIdToCheckWhenUnchecked(int iIndex, const QString& iIds)
340 {
341     m_uncheck_to_check[m_actions.at(iIndex)] = iIds;
342 }
343 
setListIdToUncheckWhenChecked(int iIndex,const QString & iIds)344 void SKGShow::setListIdToUncheckWhenChecked(int iIndex, const QString& iIds)
345 {
346     m_check_to_uncheck[m_actions.at(iIndex)] = iIds;
347 }
348 
setListIdToUncheckWhenUnchecked(int iIndex,const QString & iIds)349 void SKGShow::setListIdToUncheckWhenUnchecked(int iIndex, const QString& iIds)
350 {
351     m_uncheck_to_uncheck[m_actions.at(iIndex)] = iIds;
352 }
353 
addSeparator()354 void SKGShow::addSeparator()
355 {
356     if (m_menu != nullptr) {
357         m_menu->addSeparator();
358     }
359 }
360 
trigger()361 void SKGShow::trigger()
362 {
363     auto* act = qobject_cast<QAction*>(sender());
364     if ((act != nullptr) && !m_inTrigger) {
365         m_inTrigger = true;
366 
367         // Apply rules
368         QStringList over;
369         if (act->isChecked()) {
370             {
371                 // Check items
372                 QStringList items = SKGServices::splitCSVLine(m_check_to_check.value(act));
373                 int nb = items.count();
374                 for (int i = 0; i < nb; ++i) {
375                     QAction* act2 = getAction(items.at(i));
376                     if ((act2 != nullptr) && act2 != act) {
377                         act2->setChecked(true);
378                     }
379                 }
380             }
381 
382             {
383                 // Uncheck items
384                 QStringList items = SKGServices::splitCSVLine(m_check_to_uncheck.value(act));
385                 int nb = items.count();
386                 for (int i = 0; i < nb; ++i) {
387                     QAction* act2 = getAction(items.at(i));
388                     if ((act2 != nullptr) && act2 != act) {
389                         act2->setChecked(false);
390                     }
391                 }
392             }
393         } else {
394             {
395                 // Check items
396                 QStringList items = SKGServices::splitCSVLine(m_uncheck_to_check.value(act));
397                 int nb = items.count();
398                 for (int i = 0; i < nb; ++i) {
399                     QAction* act2 = getAction(items.at(i));
400                     if ((act2 != nullptr) && act2 != act) {
401                         act2->setChecked(true);
402                     }
403                 }
404             }
405 
406             {
407                 // Uncheck items
408                 QStringList items = SKGServices::splitCSVLine(m_uncheck_to_uncheck.value(act));
409                 int nb = items.count();
410                 for (int i = 0; i < nb; ++i) {
411                     QAction* act2 = getAction(items.at(i));
412                     if ((act2 != nullptr) && act2 != act) {
413                         act2->setChecked(false);
414                     }
415                 }
416             }
417         }
418 
419         // Change tooltip
420         setToolTip(getTitle());
421 
422         // Change icon
423         QStringList icons;
424         QString mainIcon;
425         if (m_menu != nullptr) {
426             int nb = m_actions.count();
427             icons.reserve(nb);
428             for (int i = 0; i < nb; ++i) {
429                 QAction* act2 = m_actions.at(i);
430                 if ((act2 != nullptr) && act2->isChecked()) {
431                     if (!m_icons.at(i).isEmpty()) {
432                         if (mainIcon.isEmpty()) {
433                             mainIcon = m_icons.at(i);
434                         } else {
435                             icons.push_back(m_icons.at(i));
436                         }
437                     } else {
438                         if (mainIcon.isEmpty()) {
439                             mainIcon = QStringLiteral("show-menu");
440                         }
441                     }
442                 }
443             }
444         }
445         if (mainIcon.isEmpty()) {
446             mainIcon = QStringLiteral("show-menu");
447         }
448         setIcon(SKGServices::fromTheme(mainIcon, icons));
449 
450         triggerRefreshOnly();
451 
452         m_inTrigger = false;
453     }
454 }
455 
triggerRefreshOnly()456 void SKGShow::triggerRefreshOnly()
457 {
458     // Emit event
459     m_timer.start(300);
460 
461     // Change title
462     refreshTitle();
463 }
464 
465 
getDisplayTitle()466 bool SKGShow::getDisplayTitle()
467 {
468     return m_displayTitle;
469 }
470 
setDisplayTitle(bool iDisplay)471 void SKGShow::setDisplayTitle(bool iDisplay)
472 {
473     if (m_displayTitle != iDisplay) {
474         m_displayTitle = iDisplay;
475         refreshTitle();
476         Q_EMIT modified();
477     }
478 }
479 
refreshTitle()480 void SKGShow::refreshTitle()
481 {
482     if (m_displayTitle) {
483         setText(i18n("Show: %1", getTitle()));
484     } else {
485         setText(i18n("Show"));
486     }
487 }
488 
getAction(const QString & iIdentifier) const489 QAction* SKGShow::getAction(const QString& iIdentifier) const
490 {
491     QAction* output = nullptr;
492     if (m_menu != nullptr) {
493         QList<QAction*> actionsList = m_menu->actions();
494         int nb = actionsList.count();
495         for (int i = 0; (output == nullptr) && i < nb; ++i) {
496             QAction* act = actionsList.at(i);
497             if ((act != nullptr) && act->data().toString() == iIdentifier) {
498                 output = act;
499             }
500         }
501     }
502     return output;
503 }
504 
505 
506