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