1 /*
2     SPDX-FileCopyrightText: 2017-2018 Michail Vourlakos <mvourlakos@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "activitiesdelegate.h"
7 
8 // local
9 #include "../../generic/persistentmenu.h"
10 #include "../layoutsmodel.h"
11 #include "../../generic/generictools.h"
12 #include "../../../data/layoutdata.h"
13 
14 // Qt
15 #include <QApplication>
16 #include <QDebug>
17 #include <QDialogButtonBox>
18 #include <QMenu>
19 #include <QModelIndex>
20 #include <QPainter>
21 #include <QPushButton>
22 #include <QString>
23 #include <QTextDocument>
24 #include <QWidget>
25 #include <QWidgetAction>
26 
27 // KDE
28 #include <KLocalizedString>
29 
30 #define OKPRESSED "OKPRESSED"
31 
32 namespace Latte {
33 namespace Settings {
34 namespace Layout {
35 namespace Delegate {
36 
Activities(QObject * parent)37 Activities::Activities(QObject *parent)
38     : QStyledItemDelegate(parent)
39 {
40 }
41 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const42 QWidget *Activities::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
43 {
44     QPushButton *button = new QPushButton(parent);
45 
46     PersistentMenu *menu = new PersistentMenu(button);
47     button->setMenu(menu);
48     menu->setMinimumWidth(option.rect.width());
49 
50     bool isLayoutActive = index.data(Model::Layouts::ISACTIVEROLE).toBool();
51 
52     QStringList allActivities = index.data(Model::Layouts::ALLACTIVITIESSORTEDROLE).toStringList();
53     Latte::Data::ActivitiesTable allActivitiesTable = index.data(Model::Layouts::ALLACTIVITIESDATAROLE).value<Latte::Data::ActivitiesTable>();
54 
55     QStringList assignedActivities = index.data(Qt::UserRole).toStringList();
56 
57     QList<int> originalChecked;
58 
59     QString currentrealactivityid;
60 
61     for (int i=0; i<allActivitiesTable.rowCount(); ++i) {
62         if (allActivitiesTable[i].isCurrent) {
63             currentrealactivityid = allActivitiesTable[i].id;
64             break;
65         }
66     }
67 
68     for (int i=0; i<allActivities.count(); ++i) {
69         Latte::Data::Activity activitydata = allActivitiesTable[allActivities[i]];
70 
71         if (!activitydata.isValid()) {
72             continue;
73         }
74 
75 
76         bool inCurrentActivity = (activitydata.id == Data::Layout::CURRENTACTIVITYID && assignedActivities.contains(currentrealactivityid));
77         bool ischecked = assignedActivities.contains(activitydata.id) || inCurrentActivity;
78 
79         if (ischecked) {
80             originalChecked << i;
81         }
82 
83         QAction *action = new QAction(activitydata.name);
84         action->setData(activitydata.id);
85         action->setIcon(QIcon::fromTheme(activitydata.icon));
86         action->setCheckable(true);
87         action->setChecked(ischecked);
88 
89         if (activitydata.id == Data::Layout::FREEACTIVITIESID
90                 || activitydata.id == Data::Layout::ALLACTIVITIESID
91                 || activitydata.id == Data::Layout::CURRENTACTIVITYID) {
92             if (isLayoutActive) {
93                 QFont font = action->font();
94                 font.setBold(true);
95                 action->setFont(font);
96             }
97 
98             if (ischecked) {
99                 menu->setMasterIndex(i);
100             }
101 
102             connect(action, &QAction::toggled, this, [this, menu, button, action, i, allActivitiesTable, currentrealactivityid]() {
103                 if (action->isChecked()) {
104                     menu->setMasterIndex(i);
105 
106                     if (action->data().toString() == Data::Layout::CURRENTACTIVITYID) {
107                         auto actions = menu->actions();
108                         for (int i=0; i<actions.count(); ++i) {
109                             if (actions[i]->data().toString() == currentrealactivityid) {
110                                 actions[i]->setChecked(true);
111                             }
112                         }
113                     }
114                 } else {
115                     if (menu->masterIndex() == i) {
116                         menu->setMasterIndex(-1);
117                     }
118 
119                     if (action->data().toString() == Data::Layout::CURRENTACTIVITYID) {
120                         auto actions = menu->actions();
121                         for (int i=0; i<actions.count(); ++i) {
122                             if (actions[i]->data().toString() == currentrealactivityid) {
123                                 actions[i]->setChecked(false);
124                             }
125                         }
126 
127                         updateCurrentActivityAction(menu);
128                     }
129                 }
130 
131                 updateButton(button, allActivitiesTable);
132             });
133         } else {
134             if (activitydata.isRunning()) {
135                 QFont font = action->font();
136                 font.setBold(true);
137                 action->setFont(font);
138             }
139 
140             connect(action, &QAction::toggled, this, [this, menu, button, action, i, allActivitiesTable]() {
141                 if (action->isChecked()) {
142                     menu->setMasterIndex(-1);
143                 }
144 
145                 updateButton(button, allActivitiesTable);
146             });
147         }
148 
149         menu->addAction(action);
150 
151         if (activitydata.id == Data::Layout::CURRENTACTIVITYID) {
152             //! After CurrentActivity record we can add Separator
153             menu->addSeparator();
154         }
155     }
156 
157     connect(menu, &PersistentMenu::masterIndexChanged, this, [this, menu, button, allActivitiesTable]() {
158         int masterRow = menu->masterIndex();
159         if (masterRow>=0) {
160             auto actions = button->menu()->actions();
161 
162             for (int i=0; i<actions.count(); ++i) {
163                 if (i != masterRow && actions[i]->isChecked()) {
164                     actions[i]->setChecked(false);
165                 }
166             }
167         } else {
168             foreach (QAction *action, button->menu()->actions()) {
169                 QString actId = action->data().toString();
170                 if (actId == Data::Layout::FREEACTIVITIESID || actId == Data::Layout::ALLACTIVITIESID) {
171                     action->setChecked(false);
172                 }
173             }
174         }
175 
176         updateCurrentActivityAction(menu);
177         updateButton(button, allActivitiesTable);
178     });
179 
180     //! Ok, Apply Buttons behavior
181     menu->addSeparator();
182 
183     QDialogButtonBox *menuDialogButtons = new QDialogButtonBox(menu);
184     menuDialogButtons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Reset);
185     menuDialogButtons->setContentsMargins(3, 0, 3, 3);
186 
187     QWidgetAction* menuDialogButtonsWidgetAction = new QWidgetAction(menu);
188     menuDialogButtonsWidgetAction->setDefaultWidget(menuDialogButtons);
189 
190     menu->addAction(menuDialogButtonsWidgetAction);
191 
192     connect(menuDialogButtons->button(QDialogButtonBox::Ok), &QPushButton::clicked,  [this, menu, button]() {
193         button->setProperty(OKPRESSED, true);
194         menu->hide();
195     });
196 
197     connect(menuDialogButtons->button(QDialogButtonBox::Cancel), &QPushButton::clicked,  menu, &QMenu::hide);
198 
199     connect(menuDialogButtons->button(QDialogButtonBox::Reset), &QPushButton::clicked,  [this, menu, originalChecked]() {
200         for (int i=0; i<menu->actions().count(); ++i) {
201             if (!originalChecked.contains(i)) {
202                 menu->actions().at(i)->setChecked(false);
203             } else {
204                 menu->actions().at(i)->setChecked(true);
205             }
206         }
207     });
208 
209     connect(menu, &QMenu::aboutToHide, button, &QWidget::clearFocus);
210 
211     return button;
212 }
213 
updateCurrentActivityAction(QMenu * menu) const214 void Activities::updateCurrentActivityAction(QMenu *menu) const
215 {
216     if (!menu) {
217         return;
218     }
219 
220     auto actions = menu->actions();
221     for (int i=0; i<actions.count(); ++i) {
222         if (actions[i]->data().toString() == Data::Layout::CURRENTACTIVITYID) {
223             if (actions[i]->isChecked()) {
224                 QFont font = actions[i]->font();
225                 font.setBold(true);
226                 actions[i]->setFont(font);
227             } else {
228                 QFont font = actions[i]->font();
229                 font.setBold(false);
230                 actions[i]->setFont(font);
231             }
232         }
233     }
234 
235 }
236 
setEditorData(QWidget * editor,const QModelIndex & index) const237 void Activities::setEditorData(QWidget *editor, const QModelIndex &index) const
238 {
239     Latte::Data::ActivitiesTable allActivitiesTable = index.data(Model::Layouts::ALLACTIVITIESDATAROLE).value<Latte::Data::ActivitiesTable>();
240 
241     updateButton(editor, allActivitiesTable);
242 }
243 
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const244 void Activities::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
245 {
246     QPushButton *button = static_cast<QPushButton *>(editor);
247 
248     if (button->property(OKPRESSED).isNull() || !button->property(OKPRESSED).toBool()) {
249         return;
250     }
251 
252     //! keep activities that are present in other computers
253     QStringList assignedActivities = index.data(Qt::UserRole).toStringList();
254 
255     foreach (QAction *action, button->menu()->actions()) {
256         QString activityid = action->data().toString();
257 
258         if (activityid == Data::Layout::CURRENTACTIVITYID) {
259             continue;
260         }
261 
262         if (activityid == Data::Layout::ALLACTIVITIESID && action->isChecked()) {
263             assignedActivities = QStringList(Data::Layout::ALLACTIVITIESID);
264             break;
265         } else if (activityid == Data::Layout::FREEACTIVITIESID && action->isChecked()) {
266             assignedActivities = QStringList(Data::Layout::FREEACTIVITIESID);
267             break;
268         }
269 
270         //! try to not remove activityids that belong to different machines that are not
271         //! currently present
272         if (!action->isChecked()) {
273             assignedActivities.removeAll(activityid);
274         } else if (action->isChecked() && !assignedActivities.contains(activityid)) {
275             assignedActivities << activityid;
276         }
277     }
278 
279     model->setData(index, assignedActivities, Qt::UserRole);
280 }
281 
updateEditorGeometry(QWidget * editor,const QStyleOptionViewItem & option,const QModelIndex & index) const282 void Activities::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
283 {
284     editor->setGeometry(option.rect);
285 }
286 
editorEvent(QEvent * event,QAbstractItemModel * model,const QStyleOptionViewItem & option,const QModelIndex & index)287 bool Activities::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
288                              const QModelIndex &index)
289 {
290     Q_ASSERT(event);
291     Q_ASSERT(model);
292 
293     return QStyledItemDelegate::editorEvent(event, model, option, index);
294 }
295 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const296 void Activities::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
297 {
298     QStyleOptionViewItem myOptions = option;
299     //! Remove the focus dotted lines
300     myOptions.state = (myOptions.state & ~QStyle::State_HasFocus);
301 
302     bool isLayoutActive = index.data(Model::Layouts::ISACTIVEROLE).toBool();
303 
304     QList<Latte::Data::Activity> assignedActivities;
305     QStringList assignedIds = index.model()->data(index, Qt::UserRole).toStringList();
306     QStringList assignedOriginalIds = index.model()->data(index, Model::Layouts::ORIGINALASSIGNEDACTIVITIESROLE).toStringList();
307 
308     Latte::Data::ActivitiesTable allActivitiesTable = index.data(Model::Layouts::ALLACTIVITIESDATAROLE).value<Latte::Data::ActivitiesTable>();
309 
310     for (int i=0; i<assignedIds.count(); ++i) {
311         assignedActivities << allActivitiesTable[assignedIds[i]];
312     }
313 
314     if (assignedActivities.count() > 0) {
315         myOptions.text = joinedActivities(assignedActivities, assignedOriginalIds, isLayoutActive);
316     } else {
317         myOptions.text = "";
318     }
319 
320     Latte::drawBackground(painter, option);
321     Latte::drawFormattedText(painter, myOptions);
322 }
323 
joinedActivities(const QList<Latte::Data::Activity> & activities,const QStringList & originalIds,bool isActive,bool formatText) const324 QString Activities::joinedActivities(const QList<Latte::Data::Activity> &activities, const QStringList &originalIds, bool isActive, bool formatText) const
325 {
326     QString finalText;
327 
328     int i = 0;
329 
330     for (int i=0; i<activities.count(); ++i) {
331         bool bold{false};
332         bool italic = (!originalIds.contains(activities[i].id));
333 
334         if (activities[i].id == Data::Layout::FREEACTIVITIESID || activities[i].id == Data::Layout::ALLACTIVITIESID) {
335             bold = isActive;
336         } else {
337             bold = activities[i].isRunning();
338         }
339 
340         if (i > 0) {
341             finalText += ", ";
342         }
343 
344         QString styledText = activities[i].name;
345 
346         if (bold && formatText) {
347             styledText = "<b>" + styledText + "</b>";
348         }
349 
350         if (italic && formatText) {
351             styledText = "<i>" + styledText + "</i>";
352         }
353 
354         finalText += styledText;
355     }
356 
357     return finalText;
358 }
359 
updateButton(QWidget * editor,const Latte::Data::ActivitiesTable & allActivitiesTable) const360 void Activities::updateButton(QWidget *editor, const Latte::Data::ActivitiesTable &allActivitiesTable) const
361 {
362     if (!editor) {
363         return;
364     }
365 
366     QPushButton *button = static_cast<QPushButton *>(editor);
367     QList<Latte::Data::Activity> assignedActivities;
368 
369     foreach (QAction *action, button->menu()->actions()) {
370         if (action->isChecked() && action->data().toString() != Data::Layout::CURRENTACTIVITYID) {
371             assignedActivities << allActivitiesTable[action->data().toString()];
372         }
373     }
374 
375     button->setText(joinedActivities(assignedActivities, QStringList(), false, false));
376 }
377 
378 }
379 }
380 }
381 }
382 
383