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