1 /*
2 SPDX-FileCopyrightText: 2018 Michail Vourlakos <mvourlakos@gmail.com>
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "menu.h"
7
8 // local
9 #include "contextmenudata.h"
10 #include "layoutmenuitemwidget.h"
11
12 // Qt
13 #include <QAction>
14 #include <QDebug>
15 #include <QFont>
16 #include <QMenu>
17 #include <QtDBus>
18 #include <QTimer>
19 #include <QLatin1String>
20
21 // KDE
22 #include <KActionCollection>
23 #include <KLocalizedString>
24
25 // Plasma
26 #include <Plasma/Containment>
27 #include <Plasma/Corona>
28 #include <Plasma/ServiceJob>
29
30 const int MEMORYINDEX = 0;
31 const int ACTIVELAYOUTSINDEX = 1;
32 const int CURRENTLAYOUTSINDEX = 2;
33 const int ACTIONSALWAYSSHOWN = 3;
34 const int LAYOUTMENUINDEX = 4;
35 const int VIEWTYPEINDEX = 5;
36 const int VIEWLAYOUTINDEX = 6;
37
38 enum ViewType
39 {
40 DockView = 0,
41 PanelView
42 };
43
44 enum LayoutsMemoryUsage
45 {
46 SingleLayout = 0,
47 MultipleLayouts
48 };
49
50 enum LatteConfigPage
51 {
52 LayoutPage = 0,
53 PreferencesPage
54 };
55
Menu(QObject * parent,const QVariantList & args)56 Menu::Menu(QObject *parent, const QVariantList &args)
57 : Plasma::ContainmentActions(parent, args)
58 {
59 }
60
~Menu()61 Menu::~Menu()
62 {
63 //! sub-menus
64 m_addViewMenu->deleteLater();
65 m_switchLayoutsMenu->deleteLater();
66 m_moveToLayoutMenu->deleteLater();
67
68 //! clear menu actions that have been created from submenus
69 m_actions.remove(Latte::Data::ContextMenu::ADDVIEWACTION);
70 m_actions.remove(Latte::Data::ContextMenu::LAYOUTSACTION);
71
72 //! actions
73 qDeleteAll(m_actions.values());
74 m_actions.clear();
75 }
76
restore(const KConfigGroup & config)77 void Menu::restore(const KConfigGroup &config)
78 {
79 if (!m_actions.isEmpty()) {
80 return;
81 }
82
83 m_actions[Latte::Data::ContextMenu::SECTIONACTION] = new QAction(this);
84 m_actions[Latte::Data::ContextMenu::SECTIONACTION]->setSeparator(true);
85 m_actions[Latte::Data::ContextMenu::SECTIONACTION]->setText("Latte");
86
87 m_actions[Latte::Data::ContextMenu::SEPARATOR1ACTION] = new QAction(this);
88 m_actions[Latte::Data::ContextMenu::SEPARATOR1ACTION]->setSeparator(true);
89
90 //! Print Message...
91 m_actions[Latte::Data::ContextMenu::PRINTACTION] = new QAction(QIcon::fromTheme("edit"), "Print Message...", this);
92 connect(m_actions[Latte::Data::ContextMenu::PRINTACTION], &QAction::triggered, [ = ]() {
93 qDebug() << "Action Trigerred !!!";
94 });
95
96 //! Add Widgets...
97 m_actions[Latte::Data::ContextMenu::ADDWIDGETSACTION] = new QAction(QIcon::fromTheme("list-add"), i18n("&Add Widgets..."), this);
98 m_actions[Latte::Data::ContextMenu::ADDWIDGETSACTION]->setStatusTip(i18n("Show Widget Explorer"));
99 connect(m_actions[Latte::Data::ContextMenu::ADDWIDGETSACTION], &QAction::triggered, this, &Menu::requestWidgetExplorer);
100 this->containment()->actions()->addAction(Latte::Data::ContextMenu::ADDWIDGETSACTION, m_actions[Latte::Data::ContextMenu::ADDWIDGETSACTION]);
101
102 /*connect(m_addWidgetsAction, &QAction::triggered, [ = ]() {
103 QDBusInterface iface("org.kde.plasmashell", "/PlasmaShell", "", QDBusConnection::sessionBus());
104
105 if (iface.isValid()) {
106 iface.call("toggleWidgetExplorer");
107 }
108 });*/
109
110 //! Edit Dock/Panel...
111 m_actions[Latte::Data::ContextMenu::EDITVIEWACTION] = new QAction(QIcon::fromTheme("document-edit"), "Edit Dock...", this);
112 connect(m_actions[Latte::Data::ContextMenu::EDITVIEWACTION], &QAction::triggered, this, &Menu::requestConfiguration);
113 this->containment()->actions()->addAction(Latte::Data::ContextMenu::EDITVIEWACTION, m_actions[Latte::Data::ContextMenu::EDITVIEWACTION]);
114
115
116 //! Quit Application
117 m_actions[Latte::Data::ContextMenu::QUITLATTEACTION] = new QAction(QIcon::fromTheme("application-exit"), i18nc("quit application", "Quit &Latte"));
118 connect(m_actions[Latte::Data::ContextMenu::QUITLATTEACTION], &QAction::triggered, this, &Menu::quitApplication);
119 this->containment()->actions()->addAction(Latte::Data::ContextMenu::QUITLATTEACTION, m_actions[Latte::Data::ContextMenu::QUITLATTEACTION]);
120
121 //! Layouts submenu
122 m_switchLayoutsMenu = new QMenu;
123 m_actions[Latte::Data::ContextMenu::LAYOUTSACTION] = m_switchLayoutsMenu->menuAction();
124 m_actions[Latte::Data::ContextMenu::LAYOUTSACTION]->setText(i18n("&Layouts"));
125 m_actions[Latte::Data::ContextMenu::LAYOUTSACTION]->setIcon(QIcon::fromTheme("user-identity"));
126 m_actions[Latte::Data::ContextMenu::LAYOUTSACTION]->setStatusTip(i18n("Switch to another layout"));
127 this->containment()->actions()->addAction(Latte::Data::ContextMenu::LAYOUTSACTION, m_actions[Latte::Data::ContextMenu::LAYOUTSACTION]);
128
129 connect(m_switchLayoutsMenu, &QMenu::aboutToShow, this, &Menu::populateLayouts);
130 connect(m_switchLayoutsMenu, &QMenu::triggered, this, &Menu::switchToLayout);
131
132 //! Add View submenu
133 m_addViewMenu = new QMenu;
134 m_actions[Latte::Data::ContextMenu::ADDVIEWACTION] = m_addViewMenu->menuAction();
135 m_actions[Latte::Data::ContextMenu::ADDVIEWACTION]->setText(i18n("&Add Dock/Panel"));
136 m_actions[Latte::Data::ContextMenu::ADDVIEWACTION]->setIcon(QIcon::fromTheme("list-add"));
137 m_actions[Latte::Data::ContextMenu::ADDVIEWACTION]->setStatusTip(i18n("Add dock or panel based on specific template"));
138 this->containment()->actions()->addAction(Latte::Data::ContextMenu::ADDVIEWACTION, m_actions[Latte::Data::ContextMenu::ADDVIEWACTION]);
139
140 connect(m_addViewMenu, &QMenu::aboutToShow, this, &Menu::populateViewTemplates);
141 connect(m_addViewMenu, &QMenu::triggered, this, &Menu::addView);
142
143 //! Move submenu
144 m_moveToLayoutMenu = new QMenu;
145 m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION] = m_moveToLayoutMenu->menuAction();
146 m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION]->setText("Move To Layout");
147 m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION]->setIcon(QIcon::fromTheme("transform-move-horizontal"));
148 m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION]->setStatusTip(i18n("Move dock or panel to different layout"));
149 this->containment()->actions()->addAction(Latte::Data::ContextMenu::MOVEVIEWACTION, m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION]);
150
151 connect(m_moveToLayoutMenu, &QMenu::aboutToShow, this, &Menu::populateMoveToLayouts);
152 connect(m_moveToLayoutMenu, &QMenu::triggered, this, &Menu::moveToLayout);
153
154 //! Configure Latte
155 m_actions[Latte::Data::ContextMenu::PREFERENCESACTION] = new QAction(QIcon::fromTheme("configure"), i18nc("global settings window", "&Configure Latte..."), this);
156 this->containment()->actions()->addAction(Latte::Data::ContextMenu::PREFERENCESACTION, m_actions[Latte::Data::ContextMenu::PREFERENCESACTION]);
157 connect(m_actions[Latte::Data::ContextMenu::PREFERENCESACTION], &QAction::triggered, [=](){
158 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
159
160 if (iface.isValid()) {
161 iface.call("showSettingsWindow", (int)PreferencesPage);
162 }
163 });
164
165 //! Duplicate Action
166 m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION] = new QAction(QIcon::fromTheme("edit-copy"), "Duplicate Dock as Template", this);
167 connect(m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION], &QAction::triggered, [=](){
168 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
169
170 if (iface.isValid()) {
171 iface.call("duplicateView", containment()->id());
172 }
173 });
174 this->containment()->actions()->addAction(Latte::Data::ContextMenu::DUPLICATEVIEWACTION, m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION]);
175
176 //! Export View Template Action
177 m_actions[Latte::Data::ContextMenu::EXPORTVIEWTEMPLATEACTION] = new QAction(QIcon::fromTheme("document-export"), "Export as Template...", this);
178 connect(m_actions[Latte::Data::ContextMenu::EXPORTVIEWTEMPLATEACTION], &QAction::triggered, [=](){
179 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
180
181 if (iface.isValid()) {
182 iface.call("exportViewTemplate", containment()->id());
183 }
184 });
185 this->containment()->actions()->addAction(Latte::Data::ContextMenu::EXPORTVIEWTEMPLATEACTION, m_actions[Latte::Data::ContextMenu::EXPORTVIEWTEMPLATEACTION]);
186
187 //! Remove Action
188 m_actions[Latte::Data::ContextMenu::REMOVEVIEWACTION] = new QAction(QIcon::fromTheme("delete"), "Remove Dock", this);
189 connect(m_actions[Latte::Data::ContextMenu::REMOVEVIEWACTION], &QAction::triggered, [=](){
190 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
191
192 if (iface.isValid()) {
193 iface.call("removeView", containment()->id());
194 }
195 });
196 this->containment()->actions()->addAction(Latte::Data::ContextMenu::REMOVEVIEWACTION, m_actions[Latte::Data::ContextMenu::REMOVEVIEWACTION]);
197
198 //! Signals
199 connect(this->containment(), &Plasma::Containment::userConfiguringChanged, [=](){
200 updateVisibleActions();
201 });
202 }
203
requestConfiguration()204 void Menu::requestConfiguration()
205 {
206 if (this->containment()) {
207 emit this->containment()->configureRequested(containment());
208 }
209 }
210
requestWidgetExplorer()211 void Menu::requestWidgetExplorer()
212 {
213 if (this->containment()) {
214 emit this->containment()->showAddWidgetsInterface(QPointF());
215 }
216 }
217
contextualActions()218 QList<QAction *> Menu::contextualActions()
219 {
220 QList<QAction *> actions;
221
222 actions << m_actions[Latte::Data::ContextMenu::SECTIONACTION];
223 actions << m_actions[Latte::Data::ContextMenu::PRINTACTION];
224 for(int i=0; i<Latte::Data::ContextMenu::ACTIONSEDITORDER.count(); ++i) {
225 actions << m_actions[Latte::Data::ContextMenu::ACTIONSEDITORDER[i]];
226 }
227 actions << m_actions[Latte::Data::ContextMenu::EDITVIEWACTION];
228
229 m_data.clear();
230 m_viewTemplates.clear();
231 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
232
233 if (iface.isValid()) {
234 QDBusReply<QStringList> contextData = iface.call("contextMenuData", containment()->id());
235 m_data = contextData.value();
236
237 QDBusReply<QStringList> templatesData = iface.call("viewTemplatesData");
238 m_viewTemplates = templatesData.value();
239 }
240
241 m_actionsAlwaysShown = m_data[ACTIONSALWAYSSHOWN].split(";;");
242
243 ViewType viewType{static_cast<ViewType>((m_data[VIEWTYPEINDEX]).toInt())};
244
245 const QString configureActionText = (viewType == DockView) ? i18n("&Edit Dock...") : i18n("&Edit Panel...");
246 m_actions[Latte::Data::ContextMenu::EDITVIEWACTION]->setText(configureActionText);
247
248 const QString duplicateActionText = (viewType == DockView) ? i18n("&Duplicate Dock") : i18n("&Duplicate Panel");
249 m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION]->setText(duplicateActionText);
250
251 const QString exportTemplateText = (viewType == DockView) ? i18n("E&xport Dock as Template") : i18n("E&xport Panel as Template");
252 m_actions[Latte::Data::ContextMenu::EXPORTVIEWTEMPLATEACTION]->setText(exportTemplateText);
253
254 m_activeLayoutNames = m_data[ACTIVELAYOUTSINDEX].split(";;");
255 const QString moveText = (viewType == DockView) ? i18n("&Move Dock To Layout") : i18n("&Move Panel To Layout");
256 m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION]->setText(moveText);
257
258 const QString removeActionText = (viewType == DockView) ? i18n("&Remove Dock") : i18n("&Remove Panel");
259 m_actions[Latte::Data::ContextMenu::REMOVEVIEWACTION]->setText(removeActionText);
260
261 updateVisibleActions();
262
263 return actions;
264 }
265
action(const QString & name)266 QAction *Menu::action(const QString &name)
267 {
268 if (m_actions.contains(name)) {
269 return m_actions[name];
270 }
271
272 return nullptr;
273 }
274
updateVisibleActions()275 void Menu::updateVisibleActions()
276 {
277 if (!m_actions.contains(Latte::Data::ContextMenu::EDITVIEWACTION)
278 || !m_actions.contains(Latte::Data::ContextMenu::REMOVEVIEWACTION)) {
279 return;
280 }
281
282 bool configuring = containment()->isUserConfiguring();
283
284 // normal actions that the user can specify their visibility
285 for(auto actionName: m_actions.keys()) {
286 if (Latte::Data::ContextMenu::ACTIONSSPECIAL.contains(actionName)) {
287 continue;
288 } else if (Latte::Data::ContextMenu::ACTIONSALWAYSHIDDEN.contains(actionName)) {
289 m_actions[actionName]->setVisible(false);
290 continue;
291 }
292
293 bool isvisible = m_actionsAlwaysShown.contains(actionName) || configuring;
294 m_actions[actionName]->setVisible(isvisible);
295 }
296
297 // normal actions with more criteria
298 bool isshown = (m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION]->isVisible() && m_activeLayoutNames.count()>1);
299 m_actions[Latte::Data::ContextMenu::MOVEVIEWACTION]->setVisible(isshown);
300
301 // special actions
302 m_actions[Latte::Data::ContextMenu::EDITVIEWACTION]->setVisible(!configuring);
303 m_actions[Latte::Data::ContextMenu::SECTIONACTION]->setVisible(true);
304
305 // because sometimes they are disabled unexpectedly, we should reenable them
306 for(auto actionName: m_actions.keys()) {
307 m_actions[actionName]->setEnabled(true);
308 }
309 }
310
311
populateLayouts()312 void Menu::populateLayouts()
313 {
314 m_switchLayoutsMenu->clear();
315
316 LayoutsMemoryUsage memoryUsage = static_cast<LayoutsMemoryUsage>((m_data[MEMORYINDEX]).toInt());
317 QStringList activeNames = m_data[ACTIVELAYOUTSINDEX].split(";;");
318 QStringList currentNames = m_data[CURRENTLAYOUTSINDEX].split(";;");
319
320 QList<LayoutInfo> layoutsmenulist;
321
322 QStringList layoutsdata = m_data[LAYOUTMENUINDEX].split(";;");
323
324 for (int i=0; i<layoutsdata.count(); ++i) {
325 QStringList cdata = layoutsdata[i].split("**");
326
327 LayoutInfo info;
328 info.layoutName = cdata[0];
329 info.isBackgroundFileIcon = cdata[1].toInt();
330 info.iconName = cdata[2];
331
332 layoutsmenulist << info;
333 }
334
335 for (int i = 0; i < layoutsmenulist.count(); ++i) {
336 bool isActive = activeNames.contains(layoutsmenulist[i].layoutName);
337
338 QString layoutText = layoutsmenulist[i].layoutName;
339
340 bool isCurrent = ((memoryUsage == SingleLayout && isActive)
341 || (memoryUsage == MultipleLayouts && currentNames.contains(layoutsmenulist[i].layoutName)));
342
343
344 QWidgetAction *action = new QWidgetAction(m_switchLayoutsMenu);
345 action->setText(layoutsmenulist[i].layoutName);
346 action->setCheckable(true);
347 action->setChecked(isCurrent);
348 action->setData(layoutsmenulist[i].layoutName);
349
350 LayoutMenuItemWidget *menuitem = new LayoutMenuItemWidget(action, m_switchLayoutsMenu);
351 menuitem->setIcon(layoutsmenulist[i].isBackgroundFileIcon, layoutsmenulist[i].iconName);
352 action->setDefaultWidget(menuitem);
353 m_switchLayoutsMenu->addAction(action);
354 }
355
356 m_switchLayoutsMenu->addSeparator();
357
358 QWidgetAction *editaction = new QWidgetAction(m_switchLayoutsMenu);
359 editaction->setText(i18n("Edit &Layouts..."));
360 editaction->setCheckable(false);
361 editaction->setData(QStringLiteral(" _show_latte_settings_dialog_"));
362 editaction->setVisible(false);
363
364 LayoutMenuItemWidget *editmenuitem = new LayoutMenuItemWidget(editaction, m_switchLayoutsMenu);
365 editmenuitem->setIcon(false, "document-edit");
366 editaction->setDefaultWidget(editmenuitem);
367 m_switchLayoutsMenu->addAction(editaction);
368 }
369
populateMoveToLayouts()370 void Menu::populateMoveToLayouts()
371 {
372 m_moveToLayoutMenu->clear();
373
374 LayoutsMemoryUsage memoryUsage = static_cast<LayoutsMemoryUsage>((m_data[MEMORYINDEX]).toInt());
375
376 if (memoryUsage == LayoutsMemoryUsage::MultipleLayouts) {
377 QStringList activeNames = m_data[ACTIVELAYOUTSINDEX].split(";;");
378 QStringList currentNames = m_data[CURRENTLAYOUTSINDEX].split(";;");
379 QString viewLayoutName = m_data[VIEWLAYOUTINDEX];
380
381 QList<LayoutInfo> layoutsmenulist;
382
383 QStringList layoutsdata = m_data[LAYOUTMENUINDEX].split(";;");
384
385 for (int i=0; i<layoutsdata.count(); ++i) {
386 QStringList cdata = layoutsdata[i].split("**");
387
388 LayoutInfo info;
389 info.layoutName = cdata[0];
390 info.isBackgroundFileIcon = cdata[1].toInt();
391 info.iconName = cdata[2];
392
393 layoutsmenulist << info;
394 }
395
396 for (int i = 0; i < layoutsmenulist.count(); ++i) {
397 bool isCurrent = currentNames.contains(layoutsmenulist[i].layoutName) && activeNames.contains(layoutsmenulist[i].layoutName);
398 bool isViewCurrentLayout = layoutsmenulist[i].layoutName == viewLayoutName;
399
400 QWidgetAction *action = new QWidgetAction(m_moveToLayoutMenu);
401 action->setText(layoutsmenulist[i].layoutName);
402 action->setCheckable(true);
403 action->setChecked(isViewCurrentLayout);
404 action->setData(isViewCurrentLayout ? QString() : layoutsmenulist[i].layoutName);
405
406 LayoutMenuItemWidget *menuitem = new LayoutMenuItemWidget(action, m_moveToLayoutMenu);
407 menuitem->setIcon(layoutsmenulist[i].isBackgroundFileIcon, layoutsmenulist[i].iconName);
408 action->setDefaultWidget(menuitem);
409 m_moveToLayoutMenu->addAction(action);
410 }
411 }
412 }
413
populateViewTemplates()414 void Menu::populateViewTemplates()
415 {
416 m_addViewMenu->clear();
417
418 for(int i=0; i<m_viewTemplates.count(); ++i) {
419 if (i % 2 == 1) {
420 //! even records are the templates ids and they have already been registered
421 continue;
422 }
423
424 QAction *templateAction = m_addViewMenu->addAction(m_viewTemplates[i]);
425 templateAction->setIcon(QIcon::fromTheme("list-add"));
426 templateAction->setData(m_viewTemplates[i+1]);
427 }
428
429 QAction *templatesSeparatorAction = m_addViewMenu->addSeparator();
430 QAction *duplicateAction = m_addViewMenu->addAction(m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION]->text());
431 duplicateAction->setToolTip(m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION]->toolTip());
432 duplicateAction->setIcon(m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION]->icon());
433 connect(duplicateAction, &QAction::triggered, m_actions[Latte::Data::ContextMenu::DUPLICATEVIEWACTION], &QAction::triggered);
434 }
435
addView(QAction * action)436 void Menu::addView(QAction *action)
437 {
438 const QString templateId = action->data().toString();
439
440 QTimer::singleShot(400, [this, templateId]() {
441 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
442
443 if (iface.isValid()) {
444 iface.call("addView", containment()->id(), templateId);
445 }
446 });
447 }
448
moveToLayout(QAction * action)449 void Menu::moveToLayout(QAction *action)
450 {
451 const QString layoutName = action->data().toString();
452
453 QTimer::singleShot(400, [this, layoutName]() {
454 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
455
456 if (iface.isValid()) {
457 iface.call("moveViewToLayout", containment()->id(), layoutName);
458 }
459 });
460 }
461
switchToLayout(QAction * action)462 void Menu::switchToLayout(QAction *action)
463 {
464 const QString layout = action->data().toString();
465
466 if (layout == QLatin1String(" _show_latte_settings_dialog_")) {
467 QTimer::singleShot(400, [this]() {
468 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
469
470 if (iface.isValid()) {
471 iface.call("showSettingsWindow", (int)LayoutPage);
472 }
473 });
474 } else {
475 QTimer::singleShot(400, [this, layout]() {
476 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
477
478 if (iface.isValid()) {
479 iface.call("switchToLayout", layout);
480 }
481 });
482 }
483 }
484
quitApplication()485 void Menu::quitApplication()
486 {
487 QDBusInterface iface("org.kde.lattedock", "/Latte", "", QDBusConnection::sessionBus());
488
489 if (iface.isValid()) {
490 iface.call("quitApplication");
491 }
492 }
493
494 K_EXPORT_PLASMA_CONTAINMENTACTIONS_WITH_JSON(lattecontextmenu, Menu, "plasma-containmentactions-lattecontextmenu.json")
495
496 #include "menu.moc"
497