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