From 46996d318e8d085be82453059dca3bdc6fbde2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Sun, 19 Aug 2012 12:00:53 +0200 Subject: [PATCH] Split the User Actions Menu out of Workspace All methods and variables related to the User Actions Menu (rmb window deco, Alt+F3) is moved out of the Workspace class into an own UserActionsMenu class. The class needs only a very small public interface containing methods to show the menu for a Client, closing the menu and discarding the menu. Everything else is actually private to the implementation which is one of the reasons why it makes sense to split the functionality out of the Workspace class. As a result the methods and variables have more sane names and the variable names are standardized. REVIEW: 106085 BUG: 305832 FIXED-IN: 4.10 --- activation.cpp | 4 + composite.cpp | 5 +- events.cpp | 3 +- useractions.cpp | 1283 +++++++++++++++++++++++++---------------------- useractions.h | 255 ++++++++++ workspace.cpp | 104 +--- workspace.h | 67 +-- 7 files changed, 989 insertions(+), 732 deletions(-) create mode 100644 useractions.h diff --git a/activation.cpp b/activation.cpp index 7c07d5e1bb..12fbabde59 100644 --- a/activation.cpp +++ b/activation.cpp @@ -39,6 +39,7 @@ along with this program. If not, see . #include "atoms.h" #include "group.h" #include "rules.h" +#include "useractions.h" #include namespace KWin @@ -228,6 +229,9 @@ void Workspace::setActiveClient(Client* c, allowed_t) if (active_popup && active_popup_client != c && set_active_client_recursion == 0) closeActivePopup(); + if (m_userActionsMenu->hasClient() && !m_userActionsMenu->isMenuClient(c) && set_active_client_recursion == 0) { + m_userActionsMenu->close(); + } StackingUpdatesBlocker blocker(this); ++set_active_client_recursion; updateFocusMousePosition(cursorPos()); diff --git a/composite.cpp b/composite.cpp index c65716b5ca..b6406e8cd0 100644 --- a/composite.cpp +++ b/composite.cpp @@ -52,6 +52,7 @@ along with this program. If not, see . #include "scene_xrender.h" #include "scene_opengl.h" #include "shadow.h" +#include "useractions.h" #include "compositingprefs.h" #include "notifications.h" @@ -192,7 +193,7 @@ void Workspace::slotCompositingOptionsInitialized() c->setupCompositing(); foreach (Unmanaged * c, unmanaged) c->setupCompositing(); - discardPopup(); // force re-creation of the Alt+F3 popup (opacity option) + m_userActionsMenu->discard(); // force re-creation of the Alt+F3 popup (opacity option) // render at least once compositeTimer.stop(); @@ -238,7 +239,7 @@ void Workspace::finishCompositing() i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff)); } } - discardPopup(); // force re-creation of the Alt+F3 popup (opacity option) + m_userActionsMenu->discard(); // force re-creation of the Alt+F3 popup (opacity option) // discard all Deleted windows (#152914) while (!deleted.isEmpty()) deleted.first()->discard(Allowed); diff --git a/events.cpp b/events.cpp index 6856922e76..2c54da31f7 100644 --- a/events.cpp +++ b/events.cpp @@ -37,6 +37,7 @@ along with this program. If not, see . #include "overlaywindow.h" #include "rules.h" #include "unmanaged.h" +#include "useractions.h" #include "effects.h" #include @@ -879,7 +880,7 @@ void Client::enterNotifyEvent(XCrossingEvent* e) } #undef MOUSE_DRIVEN_FOCUS - if (options->focusPolicy() == Options::ClickToFocus || workspace()->windowMenuShown()) + if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) return; if (options->isAutoRaise() && !isDesktop() && diff --git a/useractions.cpp b/useractions.cpp index a50178256f..c2778b69b6 100755 --- a/useractions.cpp +++ b/useractions.cpp @@ -31,6 +31,7 @@ along with this program. If not, see . // NOTE: if you change the menu, keep kde-workspace/libs/taskmanager/taskactions.cpp in sync ////////////////////////////////////////////////////////////////////////////// +#include "useractions.h" #include "client.h" #include "workspace.h" #include "effects.h" @@ -39,6 +40,9 @@ along with this program. If not, see . #include #endif +#include +#include + #include #ifndef KWIN_NO_XF86VM #include @@ -68,158 +72,697 @@ along with this program. If not, see . namespace KWin { +UserActionsMenu::UserActionsMenu(QObject *parent) + : QObject(parent) + , m_menu(NULL) + , m_desktopMenu(NULL) + , m_screenMenu(NULL) + , m_activityMenu(NULL) + , m_addTabsMenu(NULL) + , m_switchToTabMenu(NULL) + , m_resizeOperation(NULL) + , m_moveOperation(NULL) + , m_maximizeOperation(NULL) + , m_shadeOperation(NULL) + , m_keepAboveOperation(NULL) + , m_keepBelowOperation(NULL) + , m_fullScreenOperation(NULL) + , m_noBorderOperation(NULL) + , m_minimizeOperation(NULL) + , m_closeOperation(NULL) + , m_removeFromTabGroup(NULL) + , m_closeTabGroup(NULL) + , m_client(QWeakPointer()) +{ +} + +UserActionsMenu::~UserActionsMenu() +{ + discard(); +} + +bool UserActionsMenu::isShown() const +{ + return m_menu && m_menu->isVisible(); +} + +bool UserActionsMenu::hasClient() const +{ + return !m_client.isNull(); +} + +void UserActionsMenu::close() +{ + if (!m_menu) { + return; + } + m_menu->close(); + m_client.clear();; +} + +bool UserActionsMenu::isMenuClient(const Client *c) const +{ + if (!c || m_client.isNull()) { + return false; + } + return c == m_client.data(); +} + +void UserActionsMenu::show(const QRect &pos, const QWeakPointer &cl) +{ + if (!KAuthorized::authorizeKAction("kwin_rmb")) + return; + if (cl.isNull()) + return; + if (!m_client.isNull()) // recursion + return; + if (cl.data()->isDesktop() + || cl.data()->isDock()) + return; + + m_client = cl; + init(); + Workspace *ws = Workspace::self(); + int x = pos.left(); + int y = pos.bottom(); + if (y == pos.top()) + m_menu->exec(QPoint(x, y)); + else { + QRect area = ws->clientArea(ScreenArea, QPoint(x, y), ws->currentDesktop()); + menuAboutToShow(); // needed for sizeHint() to be correct :-/ + int popupHeight = m_menu->sizeHint().height(); + if (y + popupHeight < area.height()) + m_menu->exec(QPoint(x, y)); + else + m_menu->exec(QPoint(x, pos.top() - popupHeight)); + } + m_client.clear();; +} + +void UserActionsMenu::helperDialog(const QString& message, const QWeakPointer &c) +{ + QStringList args; + QString type; + KActionCollection *keys = Workspace::self()->actionCollection(); + if (message == "noborderaltf3") { + KAction* action = qobject_cast(keys->action("Window Operations Menu")); + assert(action != NULL); + QString shortcut = QString("%1 (%2)").arg(action->text()) + .arg(action->globalShortcut().primary().toString(QKeySequence::NativeText)); + args << "--msgbox" << i18n( + "You have selected to show a window without its border.\n" + "Without the border, you will not be able to enable the border " + "again using the mouse: use the window operations menu instead, " + "activated using the %1 keyboard shortcut.", + shortcut); + type = "altf3warning"; + } else if (message == "fullscreenaltf3") { + KAction* action = qobject_cast(keys->action("Window Operations Menu")); + assert(action != NULL); + QString shortcut = QString("%1 (%2)").arg(action->text()) + .arg(action->globalShortcut().primary().toString(QKeySequence::NativeText)); + args << "--msgbox" << i18n( + "You have selected to show a window in fullscreen mode.\n" + "If the application itself does not have an option to turn the fullscreen " + "mode off you will not be able to disable it " + "again using the mouse: use the window operations menu instead, " + "activated using the %1 keyboard shortcut.", + shortcut); + type = "altf3warning"; + } else + abort(); + if (!type.isEmpty()) { + KConfig cfg("kwin_dialogsrc"); + KConfigGroup cg(&cfg, "Notification Messages"); // Depends on KMessageBox + if (!cg.readEntry(type, true)) + return; + args << "--dontagain" << "kwin_dialogsrc:" + type; + } + if (!c.isNull()) + args << "--embed" << QString::number(c.data()->window()); + KProcess::startDetached("kdialog", args); +} + + +QStringList configModules(bool controlCenter) +{ + QStringList args; + args << "kwindecoration"; + if (controlCenter) + args << "kwinoptions"; + else if (KAuthorized::authorizeControlModule("kde-kwinoptions.desktop")) + args << "kwinactions" << "kwinfocus" << "kwinmoving" << "kwinadvanced" + << "kwinrules" << "kwincompositing" +#ifdef KWIN_BUILD_TABBOX + << "kwintabbox" +#endif +#ifdef KWIN_BUILD_SCREENEDGES + << "kwinscreenedges" +#endif +#ifdef KWIN_BUILD_SCRIPTING + << "kwinscripts" +#endif + ; + return args; +} + +void UserActionsMenu::configureWM() +{ + QStringList args; + args << "--icon" << "preferences-system-windows" << configModules(false); + KToolInvocation::kdeinitExec("kcmshell4", args); +} + +void UserActionsMenu::init() +{ + if (m_menu) { + return; + } + m_menu = new QMenu; + m_menu->setFont(KGlobalSettings::menuFont()); + connect(m_menu, SIGNAL(aboutToShow()), this, SLOT(menuAboutToShow())); + connect(m_menu, SIGNAL(triggered(QAction*)), this, SLOT(slotWindowOperation(QAction*))); + + QMenu *advancedMenu = new QMenu(m_menu); + advancedMenu->setFont(KGlobalSettings::menuFont()); + + m_moveOperation = advancedMenu->addAction(i18n("&Move")); + m_moveOperation->setIcon(KIcon("transform-move")); + KActionCollection *keys = Workspace::self()->actionCollection(); + KAction *kaction = qobject_cast(keys->action("Window Move")); + if (kaction != 0) + m_moveOperation->setShortcut(kaction->globalShortcut().primary()); + m_moveOperation->setData(Options::UnrestrictedMoveOp); + + m_resizeOperation = advancedMenu->addAction(i18n("Re&size")); + kaction = qobject_cast(keys->action("Window Resize")); + if (kaction != 0) + m_resizeOperation->setShortcut(kaction->globalShortcut().primary()); + m_resizeOperation->setData(Options::ResizeOp); + + m_keepAboveOperation = advancedMenu->addAction(i18n("Keep &Above Others")); + m_keepAboveOperation->setIcon(KIcon("go-up")); + kaction = qobject_cast(keys->action("Window Above Other Windows")); + if (kaction != 0) + m_keepAboveOperation->setShortcut(kaction->globalShortcut().primary()); + m_keepAboveOperation->setCheckable(true); + m_keepAboveOperation->setData(Options::KeepAboveOp); + + m_keepBelowOperation = advancedMenu->addAction(i18n("Keep &Below Others")); + m_keepBelowOperation->setIcon(KIcon("go-down")); + kaction = qobject_cast(keys->action("Window Below Other Windows")); + if (kaction != 0) + m_keepBelowOperation->setShortcut(kaction->globalShortcut().primary()); + m_keepBelowOperation->setCheckable(true); + m_keepBelowOperation->setData(Options::KeepBelowOp); + + m_fullScreenOperation = advancedMenu->addAction(i18n("&Fullscreen")); + m_fullScreenOperation->setIcon(KIcon("view-fullscreen")); + kaction = qobject_cast(keys->action("Window Fullscreen")); + if (kaction != 0) + m_fullScreenOperation->setShortcut(kaction->globalShortcut().primary()); + m_fullScreenOperation->setCheckable(true); + m_fullScreenOperation->setData(Options::FullScreenOp); + + m_shadeOperation = advancedMenu->addAction(i18n("Sh&ade")); + kaction = qobject_cast(keys->action("Window Shade")); + if (kaction != 0) + m_shadeOperation->setShortcut(kaction->globalShortcut().primary()); + m_shadeOperation->setCheckable(true); + m_shadeOperation->setData(Options::ShadeOp); + + m_noBorderOperation = advancedMenu->addAction(i18n("&No Border")); + kaction = qobject_cast(keys->action("Window No Border")); + if (kaction != 0) + m_noBorderOperation->setShortcut(kaction->globalShortcut().primary()); + m_noBorderOperation->setCheckable(true); + m_noBorderOperation->setData(Options::NoBorderOp); + + advancedMenu->addSeparator(); + + QAction *action = advancedMenu->addAction(i18n("Window &Shortcut...")); + action->setIcon(KIcon("configure-shortcuts")); + kaction = qobject_cast(keys->action("Setup Window Shortcut")); + if (kaction != 0) + action->setShortcut(kaction->globalShortcut().primary()); + action->setData(Options::SetupWindowShortcutOp); + + action = advancedMenu->addAction(i18n("&Special Window Settings...")); + action->setIcon(KIcon("preferences-system-windows-actions")); + action->setData(Options::WindowRulesOp); + + action = advancedMenu->addAction(i18n("S&pecial Application Settings...")); + action->setIcon(KIcon("preferences-system-windows-actions")); + action->setData(Options::ApplicationRulesOp); + if (!KGlobal::config()->isImmutable() && + !KAuthorized::authorizeControlModules(configModules(true)).isEmpty()) { + advancedMenu->addSeparator(); + action = advancedMenu->addAction(i18nc("Entry in context menu of window decoration to open the configuration module of KWin", + "Window &Manager Settings...")); + action->setIcon(KIcon("configure")); + connect(action, SIGNAL(triggered()), this, SLOT(configureWM())); + } + + m_minimizeOperation = m_menu->addAction(i18n("Mi&nimize")); + kaction = qobject_cast(keys->action("Window Minimize")); + if (kaction != 0) + m_minimizeOperation->setShortcut(kaction->globalShortcut().primary()); + m_minimizeOperation->setData(Options::MinimizeOp); + + m_maximizeOperation = m_menu->addAction(i18n("Ma&ximize")); + kaction = qobject_cast(keys->action("Window Maximize")); + if (kaction != 0) + m_maximizeOperation->setShortcut(kaction->globalShortcut().primary()); + m_maximizeOperation->setCheckable(true); + m_maximizeOperation->setData(Options::MaximizeOp); + + m_menu->addSeparator(); + + // Actions for window tabbing + if (Workspace::self()->decorationSupportsTabbing()) { + m_removeFromTabGroup = m_menu->addAction(i18n("&Untab")); + kaction = qobject_cast(keys->action("Untab")); + if (kaction != 0) + m_removeFromTabGroup->setShortcut(kaction->globalShortcut().primary()); + m_removeFromTabGroup->setData(Options::RemoveTabFromGroupOp); + + m_closeTabGroup = m_menu->addAction(i18n("Close Entire &Group")); + m_closeTabGroup->setIcon(KIcon("window-close")); + kaction = qobject_cast(keys->action("Close TabGroup")); + if (kaction != 0) + m_closeTabGroup->setShortcut(kaction->globalShortcut().primary()); + m_closeTabGroup->setData(Options::CloseTabGroupOp); + + m_menu->addSeparator(); + } + + m_menu->addSeparator(); + + action = m_menu->addMenu(advancedMenu); + action->setText(i18n("More Actions")); + + m_menu->addSeparator(); + + m_closeOperation = m_menu->addAction(i18n("&Close")); + m_closeOperation->setIcon(KIcon("window-close")); + kaction = qobject_cast(keys->action("Window Close")); + if (kaction != 0) + m_closeOperation->setShortcut(kaction->globalShortcut().primary()); + m_closeOperation->setData(Options::CloseOp); +} + +void UserActionsMenu::discard() +{ + delete m_menu; + m_menu = NULL; + m_desktopMenu = NULL; + m_screenMenu = NULL; + m_activityMenu = NULL; + m_switchToTabMenu = NULL; + m_addTabsMenu = NULL; +} + +void UserActionsMenu::menuAboutToShow() +{ + if (m_client.isNull() || !m_menu) + return; + Workspace *ws = Workspace::self(); + + if (ws->numberOfDesktops() == 1) { + delete m_desktopMenu; + m_desktopMenu = 0; + } else { + initDesktopPopup(); + } + if (ws->numScreens() == 1 || (!m_client.data()->isMovable() && !m_client.data()->isMovableAcrossScreens())) { + delete m_screenMenu; + m_screenMenu = NULL; + } else { + initScreenPopup(); + } +#ifdef KWIN_BUILD_ACTIVITIES + ws->updateActivityList(true, false, this, "showHideActivityMenu"); +#endif + + m_resizeOperation->setEnabled(m_client.data()->isResizable()); + m_moveOperation->setEnabled(m_client.data()->isMovableAcrossScreens()); + m_maximizeOperation->setEnabled(m_client.data()->isMaximizable()); + m_maximizeOperation->setChecked(m_client.data()->maximizeMode() == Client::MaximizeFull); + m_shadeOperation->setEnabled(m_client.data()->isShadeable()); + m_shadeOperation->setChecked(m_client.data()->shadeMode() != ShadeNone); + m_keepAboveOperation->setChecked(m_client.data()->keepAbove()); + m_keepBelowOperation->setChecked(m_client.data()->keepBelow()); + m_fullScreenOperation->setEnabled(m_client.data()->userCanSetFullScreen()); + m_fullScreenOperation->setChecked(m_client.data()->isFullScreen()); + m_noBorderOperation->setEnabled(m_client.data()->userCanSetNoBorder()); + m_noBorderOperation->setChecked(m_client.data()->noBorder()); + m_minimizeOperation->setEnabled(m_client.data()->isMinimizable()); + m_closeOperation->setEnabled(m_client.data()->isCloseable()); + + if (ws->decorationSupportsTabbing()) { + initTabbingPopups(); + } else { + delete m_addTabsMenu; + m_addTabsMenu = 0; + } +} + +void UserActionsMenu::showHideActivityMenu() +{ +#ifdef KWIN_BUILD_ACTIVITIES + const QStringList &openActivities_ = Workspace::self()->openActivities(); + kDebug() << "activities:" << openActivities_.size(); + if (openActivities_.size() < 2) { + delete m_activityMenu; + m_activityMenu = 0; + } else { + initActivityPopup(); + } +#endif +} + +void UserActionsMenu::selectPopupClientTab(QAction* action) +{ + if (!(!m_client.isNull() && m_client.data()->tabGroup()) || !action->data().isValid()) + return; + + if (Client *other = action->data().value()) { + m_client.data()->tabGroup()->setCurrent(other); + return; + } + + // failed conversion, try "1" & "2", being prev and next + int direction = action->data().toInt(); + if (direction == 1) + m_client.data()->tabGroup()->activatePrev(); + else if (direction == 2) + m_client.data()->tabGroup()->activateNext(); +} + +static QString shortCaption(const QString &s) +{ + if (s.length() < 64) + return s; + QString ss = s; + return ss.replace(32,s.length()-64,"..."); +} + +void UserActionsMenu::rebuildTabListPopup() +{ + Q_ASSERT(m_switchToTabMenu); + + m_switchToTabMenu->clear(); + // whatever happens "0x1" and "0x2" are no heap positions ;-) + m_switchToTabMenu->addAction(i18nc("Switch to tab -> Previous", "Previous"))->setData(1); + m_switchToTabMenu->addAction(i18nc("Switch to tab -> Next", "Next"))->setData(2); + + m_switchToTabMenu->addSeparator(); + + for (QList::const_iterator i = m_client.data()->tabGroup()->clients().constBegin(), + end = m_client.data()->tabGroup()->clients().constEnd(); i != end; ++i) { + if ((*i)->noBorder() || *i == m_client.data()->tabGroup()->current()) + continue; // cannot tab there anyway + m_switchToTabMenu->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); + } + +} + +void UserActionsMenu::entabPopupClient(QAction* action) +{ + if (m_client.isNull() || !action->data().isValid()) + return; + Client *other = action->data().value(); + if (!Workspace::self()->clientList().contains(other)) // might have been lost betwenn pop-up and selection + return; + m_client.data()->tabBehind(other, true); + if (options->focusPolicyIsReasonable()) + Workspace::self()->requestFocus(m_client.data()); +} + +void UserActionsMenu::rebuildTabGroupPopup() +{ + Q_ASSERT(m_addTabsMenu); + + m_addTabsMenu->clear(); + QList handled; + const ClientList &clientList = Workspace::self()->clientList(); + for (QList::const_iterator i = clientList.constBegin(), end = clientList.constEnd(); i != end; ++i) { + if (*i == m_client.data() || (*i)->noBorder()) + continue; + m_addTabsMenu->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); + } +} + +void UserActionsMenu::initTabbingPopups() +{ + bool needTabManagers = false; + if (m_client.data()->tabGroup() && m_client.data()->tabGroup()->count() > 1) { + needTabManagers = true; + if (!m_switchToTabMenu) { + m_switchToTabMenu = new QMenu(i18n("Switch to Tab"), m_menu); + m_switchToTabMenu->setFont(KGlobalSettings::menuFont()); + connect(m_switchToTabMenu, SIGNAL(triggered(QAction*)), SLOT(selectPopupClientTab(QAction*))); + connect(m_switchToTabMenu, SIGNAL(aboutToShow()), SLOT(rebuildTabListPopup())); + m_menu->insertMenu(m_removeFromTabGroup, m_switchToTabMenu); + } + } else { + delete m_switchToTabMenu; + m_switchToTabMenu = 0; + } + + if (!m_addTabsMenu) { + m_addTabsMenu = new QMenu(i18n("Attach as tab to"), m_menu); + m_addTabsMenu->setFont(KGlobalSettings::menuFont()); + connect(m_addTabsMenu, SIGNAL(triggered(QAction*)), SLOT(entabPopupClient(QAction*))); + connect(m_addTabsMenu, SIGNAL(aboutToShow()), SLOT(rebuildTabGroupPopup())); + m_menu->insertMenu(m_removeFromTabGroup, m_addTabsMenu); + } + + m_removeFromTabGroup->setVisible(needTabManagers); + m_closeTabGroup->setVisible(needTabManagers); +} + +void UserActionsMenu::initDesktopPopup() +{ + if (m_desktopMenu) + return; + + m_desktopMenu = new QMenu(m_menu); + m_desktopMenu->setFont(KGlobalSettings::menuFont()); + connect(m_desktopMenu, SIGNAL(triggered(QAction*)), SLOT(slotSendToDesktop(QAction*))); + connect(m_desktopMenu, SIGNAL(aboutToShow()), SLOT(desktopPopupAboutToShow())); + + QAction *action = m_desktopMenu->menuAction(); + // set it as the first item + m_menu->insertAction(m_minimizeOperation, action); + action->setText(i18n("Move To &Desktop")); +} + +void UserActionsMenu::initScreenPopup() +{ + if (m_screenMenu) { + return; + } + + m_screenMenu = new QMenu(m_menu); + m_screenMenu->setFont(KGlobalSettings::menuFont()); + connect(m_screenMenu, SIGNAL(triggered(QAction*)), SLOT(slotSendToScreen(QAction*))); + connect(m_screenMenu, SIGNAL(aboutToShow()), SLOT(screenPopupAboutToShow())); + + QAction *action = m_screenMenu->menuAction(); + // set it as the first item after desktop + m_menu->insertAction(m_activityMenu ? m_activityMenu->menuAction() : m_minimizeOperation, action); + action->setText(i18n("Move To &Screen")); +} + +void UserActionsMenu::initActivityPopup() +{ + if (m_activityMenu) + return; + + m_activityMenu = new QMenu(m_menu); + m_activityMenu->setFont(KGlobalSettings::menuFont()); + connect(m_activityMenu, SIGNAL(triggered(QAction*)), + this, SLOT(slotToggleOnActivity(QAction*))); + connect(m_activityMenu, SIGNAL(aboutToShow()), + this, SLOT(activityPopupAboutToShow())); + + QAction *action = m_activityMenu->menuAction(); + // set it as the first item + m_menu->insertAction(m_minimizeOperation, action); + action->setText(i18n("Ac&tivities")); //FIXME is that a good string? +} + +void UserActionsMenu::desktopPopupAboutToShow() +{ + if (!m_desktopMenu) + return; + const Workspace *ws = Workspace::self(); + + m_desktopMenu->clear(); + QActionGroup *group = new QActionGroup(m_desktopMenu); + QAction *action = m_desktopMenu->addAction(i18n("&All Desktops")); + action->setData(0); + action->setCheckable(true); + group->addAction(action); + + if (!m_client.isNull() && m_client.data()->isOnAllDesktops()) + action->setChecked(true); + m_desktopMenu->addSeparator(); + + const int BASE = 10; + for (int i = 1; i <= ws->numberOfDesktops(); ++i) { + QString basic_name("%1 %2"); + if (i < BASE) { + basic_name.prepend('&'); + } + action = m_desktopMenu->addAction(basic_name.arg(i).arg(ws->desktopName(i).replace('&', "&&"))); + action->setData(i); + action->setCheckable(true); + group->addAction(action); + + if (!m_client.isNull() && + !m_client.data()->isOnAllDesktops() && m_client.data()->desktop() == i) + action->setChecked(true); + } + + m_desktopMenu->addSeparator(); + action = m_desktopMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop")); + action->setData(ws->numberOfDesktops() + 1); + + if (ws->numberOfDesktops() >= Workspace::self()->maxNumberOfDesktops()) + action->setEnabled(false); +} + +void UserActionsMenu::screenPopupAboutToShow() +{ + if (!m_screenMenu) { + return; + } + + m_screenMenu->clear(); + QActionGroup *group = new QActionGroup(m_screenMenu); + + for (int i = 0; inumScreens(); ++i) { + // TODO: retrieve the screen name? + // assumption: there are not more than 9 screens attached. + QAction *action = m_screenMenu->addAction(i18nc("@item:inmenu List of all Screens to send a window to", + "Screen &%1", (i+1))); + action->setData(i); + action->setCheckable(true); + if (!m_client.isNull() && i == m_client.data()->screen()) { + action->setChecked(true); + } + group->addAction(action); + } +} + +void UserActionsMenu::activityPopupAboutToShow() +{ + if (!m_activityMenu) + return; + +#ifdef KWIN_BUILD_ACTIVITIES + m_activityMenu->clear(); + QAction *action = m_activityMenu->addAction(i18n("&All Activities")); + action->setData(QString()); + action->setCheckable(true); + + if (!m_client.isNull() && m_client.data()->isOnAllActivities()) + action->setChecked(true); + m_activityMenu->addSeparator(); + + foreach (const QString &id, Workspace::self()->openActivities()) { + KActivities::Info activity(id); + QString name = activity.name(); + name.replace('&', "&&"); + action = m_activityMenu->addAction(KIcon(activity.icon()), name); + action->setData(id); + action->setCheckable(true); + + if (!m_client.isNull() && + !m_client.data()->isOnAllActivities() && m_client.data()->isOnActivity(id)) + action->setChecked(true); + } +#endif +} + +void UserActionsMenu::slotWindowOperation(QAction *action) +{ + if (!action->data().isValid()) + return; + + Options::WindowOperation op = static_cast< Options::WindowOperation >(action->data().toInt()); + QWeakPointer c = (!m_client.isNull()) ? m_client : QWeakPointer(Workspace::self()->activeClient()); + if (c.isNull()) + return; + QString type; + switch(op) { + case Options::FullScreenOp: + if (!c.data()->isFullScreen() && c.data()->userCanSetFullScreen()) + type = "fullscreenaltf3"; + break; + case Options::NoBorderOp: + if (!c.data()->noBorder() && c.data()->userCanSetNoBorder()) + type = "noborderaltf3"; + break; + default: + break; + }; + if (!type.isEmpty()) + helperDialog(type, c); + Workspace::self()->performWindowOperation(c.data(), op); +} + +void UserActionsMenu::slotSendToDesktop(QAction *action) +{ + int desk = action->data().toInt(); + if (m_client.isNull()) + return; + Workspace *ws = Workspace::self(); + if (desk == 0) { + // the 'on_all_desktops' menu entry + m_client.data()->setOnAllDesktops(!m_client.data()->isOnAllDesktops()); + return; + } else if (desk > ws->numberOfDesktops()) { + ws->setNumberOfDesktops(desk); + } + + ws->sendClientToDesktop(m_client.data(), desk, false); +} + +void UserActionsMenu::slotSendToScreen(QAction *action) +{ + const int screen = action->data().toInt(); + if (m_client.isNull()) { + return; + } + Workspace *ws = Workspace::self(); + if (screen >= ws->numScreens()) { + return; + } + + ws->sendClientToScreen(m_client.data(), screen); +} + +void UserActionsMenu::slotToggleOnActivity(QAction *action) +{ + QString activity = action->data().toString(); + if (m_client.isNull()) + return; + if (activity.isEmpty()) { + // the 'on_all_activities' menu entry + m_client.data()->setOnAllActivities(!m_client.data()->isOnAllActivities()); + return; + } + + Workspace::self()->toggleClientOnActivity(m_client.data(), activity, false); +} + //**************************************** // Workspace //**************************************** -QMenu* Workspace::clientPopup() -{ - if (!popup) { - popup = new QMenu; - popup->setFont(KGlobalSettings::menuFont()); - connect(popup, SIGNAL(aboutToShow()), this, SLOT(clientPopupAboutToShow())); - connect(popup, SIGNAL(triggered(QAction*)), this, SLOT(clientPopupActivated(QAction*))); - - advanced_popup = new QMenu(popup); - advanced_popup->setFont(KGlobalSettings::menuFont()); - - mMoveOpAction = advanced_popup->addAction(i18n("&Move")); - mMoveOpAction->setIcon(KIcon("transform-move")); - KAction *kaction = qobject_cast(keys->action("Window Move")); - if (kaction != 0) - mMoveOpAction->setShortcut(kaction->globalShortcut().primary()); - mMoveOpAction->setData(Options::UnrestrictedMoveOp); - - mResizeOpAction = advanced_popup->addAction(i18n("Re&size")); - kaction = qobject_cast(keys->action("Window Resize")); - if (kaction != 0) - mResizeOpAction->setShortcut(kaction->globalShortcut().primary()); - mResizeOpAction->setData(Options::ResizeOp); - - mKeepAboveOpAction = advanced_popup->addAction(i18n("Keep &Above Others")); - mKeepAboveOpAction->setIcon(KIcon("go-up")); - kaction = qobject_cast(keys->action("Window Above Other Windows")); - if (kaction != 0) - mKeepAboveOpAction->setShortcut(kaction->globalShortcut().primary()); - mKeepAboveOpAction->setCheckable(true); - mKeepAboveOpAction->setData(Options::KeepAboveOp); - - mKeepBelowOpAction = advanced_popup->addAction(i18n("Keep &Below Others")); - mKeepBelowOpAction->setIcon(KIcon("go-down")); - kaction = qobject_cast(keys->action("Window Below Other Windows")); - if (kaction != 0) - mKeepBelowOpAction->setShortcut(kaction->globalShortcut().primary()); - mKeepBelowOpAction->setCheckable(true); - mKeepBelowOpAction->setData(Options::KeepBelowOp); - - mFullScreenOpAction = advanced_popup->addAction(i18n("&Fullscreen")); - mFullScreenOpAction->setIcon(KIcon("view-fullscreen")); - kaction = qobject_cast(keys->action("Window Fullscreen")); - if (kaction != 0) - mFullScreenOpAction->setShortcut(kaction->globalShortcut().primary()); - mFullScreenOpAction->setCheckable(true); - mFullScreenOpAction->setData(Options::FullScreenOp); - - mShadeOpAction = advanced_popup->addAction(i18n("Sh&ade")); - kaction = qobject_cast(keys->action("Window Shade")); - if (kaction != 0) - mShadeOpAction->setShortcut(kaction->globalShortcut().primary()); - mShadeOpAction->setCheckable(true); - mShadeOpAction->setData(Options::ShadeOp); - - mNoBorderOpAction = advanced_popup->addAction(i18n("&No Border")); - kaction = qobject_cast(keys->action("Window No Border")); - if (kaction != 0) - mNoBorderOpAction->setShortcut(kaction->globalShortcut().primary()); - mNoBorderOpAction->setCheckable(true); - mNoBorderOpAction->setData(Options::NoBorderOp); - - advanced_popup->addSeparator(); - - QAction *action = advanced_popup->addAction(i18n("Window &Shortcut...")); - action->setIcon(KIcon("configure-shortcuts")); - kaction = qobject_cast(keys->action("Setup Window Shortcut")); - if (kaction != 0) - action->setShortcut(kaction->globalShortcut().primary()); - action->setData(Options::SetupWindowShortcutOp); - - action = advanced_popup->addAction(i18n("&Special Window Settings...")); - action->setIcon(KIcon("preferences-system-windows-actions")); - action->setData(Options::WindowRulesOp); - - action = advanced_popup->addAction(i18n("S&pecial Application Settings...")); - action->setIcon(KIcon("preferences-system-windows-actions")); - action->setData(Options::ApplicationRulesOp); - if (!KGlobal::config()->isImmutable() && - !KAuthorized::authorizeControlModules(Workspace::configModules(true)).isEmpty()) { - advanced_popup->addSeparator(); - action = advanced_popup->addAction(i18nc("Entry in context menu of window decoration to open the configuration module of KWin", - "Window &Manager Settings...")); - action->setIcon(KIcon("configure")); - connect(action, SIGNAL(triggered()), this, SLOT(configureWM())); - } - - mMinimizeOpAction = popup->addAction(i18n("Mi&nimize")); - kaction = qobject_cast(keys->action("Window Minimize")); - if (kaction != 0) - mMinimizeOpAction->setShortcut(kaction->globalShortcut().primary()); - mMinimizeOpAction->setData(Options::MinimizeOp); - - mMaximizeOpAction = popup->addAction(i18n("Ma&ximize")); - kaction = qobject_cast(keys->action("Window Maximize")); - if (kaction != 0) - mMaximizeOpAction->setShortcut(kaction->globalShortcut().primary()); - mMaximizeOpAction->setCheckable(true); - mMaximizeOpAction->setData(Options::MaximizeOp); - - popup->addSeparator(); - - // Actions for window tabbing - if (decorationSupportsTabbing()) { - mRemoveFromTabGroup = popup->addAction(i18n("&Untab")); - kaction = qobject_cast(keys->action("Untab")); - if (kaction != 0) - mRemoveFromTabGroup->setShortcut(kaction->globalShortcut().primary()); - mRemoveFromTabGroup->setData(Options::RemoveTabFromGroupOp); - - mCloseTabGroup = popup->addAction(i18n("Close Entire &Group")); - mCloseTabGroup->setIcon(KIcon("window-close")); - kaction = qobject_cast(keys->action("Close TabGroup")); - if (kaction != 0) - mCloseTabGroup->setShortcut(kaction->globalShortcut().primary()); - mCloseTabGroup->setData(Options::CloseTabGroupOp); - - popup->addSeparator(); - } - - popup->addSeparator(); - - action = popup->addMenu(advanced_popup); - action->setText(i18n("More Actions")); - - popup->addSeparator(); - - mCloseOpAction = popup->addAction(i18n("&Close")); - mCloseOpAction->setIcon(KIcon("window-close")); - kaction = qobject_cast(keys->action("Window Close")); - if (kaction != 0) - mCloseOpAction->setShortcut(kaction->globalShortcut().primary()); - mCloseOpAction->setData(Options::CloseOp); - } - return popup; -} - -void Workspace::discardPopup() -{ - delete popup; - popup = NULL; - desk_popup = NULL; - screen_popup = NULL; - activity_popup = NULL; - switch_to_tab_popup = NULL; - add_tabs_popup = NULL; -} - void Workspace::slotIncreaseWindowOpacity() { if (!active_client) { @@ -236,329 +779,6 @@ void Workspace::slotLowerWindowOpacity() active_client->setOpacity(qMax(active_client->opacity() - 0.05, 0.05)); } -/*! - The client popup menu will become visible soon. - - Adjust the items according to the respective popup client. - */ -void Workspace::clientPopupAboutToShow() -{ - if (!active_popup_client || !popup) - return; - - if (numberOfDesktops() == 1) { - delete desk_popup; - desk_popup = 0; - } else { - initDesktopPopup(); - } - if (numScreens() == 1 || (!active_popup_client->isMovable() && !active_popup_client->isMovableAcrossScreens())) { - delete screen_popup; - screen_popup = NULL; - } else { - initScreenPopup(); - } -#ifdef KWIN_BUILD_ACTIVITIES - updateActivityList(true, false, "showHideActivityMenu"); -#endif - - mResizeOpAction->setEnabled(active_popup_client->isResizable()); - mMoveOpAction->setEnabled(active_popup_client->isMovableAcrossScreens()); - mMaximizeOpAction->setEnabled(active_popup_client->isMaximizable()); - mMaximizeOpAction->setChecked(active_popup_client->maximizeMode() == Client::MaximizeFull); - mShadeOpAction->setEnabled(active_popup_client->isShadeable()); - mShadeOpAction->setChecked(active_popup_client->shadeMode() != ShadeNone); - mKeepAboveOpAction->setChecked(active_popup_client->keepAbove()); - mKeepBelowOpAction->setChecked(active_popup_client->keepBelow()); - mFullScreenOpAction->setEnabled(active_popup_client->userCanSetFullScreen()); - mFullScreenOpAction->setChecked(active_popup_client->isFullScreen()); - mNoBorderOpAction->setEnabled(active_popup_client->userCanSetNoBorder()); - mNoBorderOpAction->setChecked(active_popup_client->noBorder()); - mMinimizeOpAction->setEnabled(active_popup_client->isMinimizable()); - mCloseOpAction->setEnabled(active_popup_client->isCloseable()); - - if (decorationSupportsTabbing()) { - initTabbingPopups(); - } else { - delete add_tabs_popup; - add_tabs_popup = 0; - } -} - -void Workspace::showHideActivityMenu() -{ -#ifdef KWIN_BUILD_ACTIVITIES - kDebug() << "activities:" << openActivities_.size(); - if (openActivities_.size() < 2) { - delete activity_popup; - activity_popup = 0; - } else { - initActivityPopup(); - } -#endif -} - -void Workspace::selectPopupClientTab(QAction* action) -{ - if (!(active_popup_client && active_popup_client->tabGroup()) || !action->data().isValid()) - return; - - if (Client *other = action->data().value()) { - active_popup_client->tabGroup()->setCurrent(other); - return; - } - - // failed conversion, try "1" & "2", being prev and next - int direction = action->data().toInt(); - if (direction == 1) - active_popup_client->tabGroup()->activatePrev(); - else if (direction == 2) - active_popup_client->tabGroup()->activateNext(); -} - -static QString shortCaption(const QString &s) -{ - if (s.length() < 64) - return s; - QString ss = s; - return ss.replace(32,s.length()-64,"..."); -} - -void Workspace::rebuildTabListPopup() -{ - Q_ASSERT(switch_to_tab_popup); - - switch_to_tab_popup->clear(); - // whatever happens "0x1" and "0x2" are no heap positions ;-) - switch_to_tab_popup->addAction(i18nc("Switch to tab -> Previous", "Previous"))->setData(1); - switch_to_tab_popup->addAction(i18nc("Switch to tab -> Next", "Next"))->setData(2); - - switch_to_tab_popup->addSeparator(); - - for (QList::const_iterator i = active_popup_client->tabGroup()->clients().constBegin(), - end = active_popup_client->tabGroup()->clients().constEnd(); i != end; ++i) { - if ((*i)->noBorder() || *i == active_popup_client->tabGroup()->current()) - continue; // cannot tab there anyway - switch_to_tab_popup->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); - } - -} - -void Workspace::entabPopupClient(QAction* action) -{ - if (!active_popup_client || !action->data().isValid()) - return; - Client *other = action->data().value(); - if (!clients.contains(other)) // might have been lost betwenn pop-up and selection - return; - active_popup_client->tabBehind(other, true); - if (options->focusPolicyIsReasonable()) - requestFocus(active_popup_client); -} - -void Workspace::rebuildTabGroupPopup() -{ - Q_ASSERT(add_tabs_popup); - - add_tabs_popup->clear(); - QList handled; - for (QList::const_iterator i = clientList().constBegin(), end = clientList().constEnd(); i != end; ++i) { - if (*i == active_popup_client || (*i)->noBorder()) - continue; - add_tabs_popup->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i)); - } -} - -void Workspace::initTabbingPopups() -{ - bool needTabManagers = false; - if (active_popup_client->tabGroup() && active_popup_client->tabGroup()->count() > 1) { - needTabManagers = true; - if (!switch_to_tab_popup) { - switch_to_tab_popup = new QMenu(i18n("Switch to Tab"), popup); - switch_to_tab_popup->setFont(KGlobalSettings::menuFont()); - connect(switch_to_tab_popup, SIGNAL(triggered(QAction*)), SLOT(selectPopupClientTab(QAction*))); - connect(switch_to_tab_popup, SIGNAL(aboutToShow()), SLOT(rebuildTabListPopup())); - popup->insertMenu(mRemoveFromTabGroup, switch_to_tab_popup); - } - } else { - delete switch_to_tab_popup; - switch_to_tab_popup = 0; - } - - if (!add_tabs_popup) { - add_tabs_popup = new QMenu(i18n("Attach as tab to"), popup); - add_tabs_popup->setFont(KGlobalSettings::menuFont()); - connect(add_tabs_popup, SIGNAL(triggered(QAction*)), SLOT(entabPopupClient(QAction*))); - connect(add_tabs_popup, SIGNAL(aboutToShow()), SLOT(rebuildTabGroupPopup())); - popup->insertMenu(mRemoveFromTabGroup, add_tabs_popup); - } - - mRemoveFromTabGroup->setVisible(needTabManagers); - mCloseTabGroup->setVisible(needTabManagers); -} - -void Workspace::initDesktopPopup() -{ - if (desk_popup) - return; - - desk_popup = new QMenu(popup); - desk_popup->setFont(KGlobalSettings::menuFont()); - connect(desk_popup, SIGNAL(triggered(QAction*)), SLOT(slotSendToDesktop(QAction*))); - connect(desk_popup, SIGNAL(aboutToShow()), SLOT(desktopPopupAboutToShow())); - - QAction *action = desk_popup->menuAction(); - // set it as the first item - popup->insertAction(mMinimizeOpAction, action); - action->setText(i18n("Move To &Desktop")); -} - -void Workspace::initScreenPopup() -{ - if (screen_popup) { - return; - } - - screen_popup = new QMenu(popup); - screen_popup->setFont(KGlobalSettings::menuFont()); - connect(screen_popup, SIGNAL(triggered(QAction*)), SLOT(slotSendToScreen(QAction*))); - connect(screen_popup, SIGNAL(aboutToShow()), SLOT(screenPopupAboutToShow())); - - QAction *action = screen_popup->menuAction(); - // set it as the first item after desktop - popup->insertAction(activity_popup ? activity_popup->menuAction() : mMinimizeOpAction, action); - action->setText(i18n("Move To &Screen")); -} - -/*! - Creates activity popup. - I'm going with checkable ones instead of "copy to" and "move to" menus; I *think* it's an easier way. - Oh, and an 'all' option too of course - */ -void Workspace::initActivityPopup() -{ - if (activity_popup) - return; - - activity_popup = new QMenu(popup); - activity_popup->setFont(KGlobalSettings::menuFont()); - connect(activity_popup, SIGNAL(triggered(QAction*)), - this, SLOT(slotToggleOnActivity(QAction*))); - connect(activity_popup, SIGNAL(aboutToShow()), - this, SLOT(activityPopupAboutToShow())); - - QAction *action = activity_popup->menuAction(); - // set it as the first item - popup->insertAction(mMinimizeOpAction, action); - action->setText(i18n("Ac&tivities")); //FIXME is that a good string? -} - -/*! - Adjusts the desktop popup to the current values and the location of - the popup client. - */ -void Workspace::desktopPopupAboutToShow() -{ - if (!desk_popup) - return; - - desk_popup->clear(); - QActionGroup *group = new QActionGroup(desk_popup); - QAction *action = desk_popup->addAction(i18n("&All Desktops")); - action->setData(0); - action->setCheckable(true); - group->addAction(action); - - if (active_popup_client && active_popup_client->isOnAllDesktops()) - action->setChecked(true); - desk_popup->addSeparator(); - - const int BASE = 10; - for (int i = 1; i <= numberOfDesktops(); i++) { - QString basic_name("%1 %2"); - if (i < BASE) { - basic_name.prepend('&'); - } - action = desk_popup->addAction(basic_name.arg(i).arg(desktopName(i).replace('&', "&&"))); - action->setData(i); - action->setCheckable(true); - group->addAction(action); - - if (active_popup_client && - !active_popup_client->isOnAllDesktops() && active_popup_client->desktop() == i) - action->setChecked(true); - } - - desk_popup->addSeparator(); - action = desk_popup->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop")); - action->setData(numberOfDesktops() + 1); - - if (numberOfDesktops() >= Workspace::self()->maxNumberOfDesktops()) - action->setEnabled(false); -} - -/*! - Adjusts the screen popup to the current values and the location of - the popup client. - */ -void Workspace::screenPopupAboutToShow() -{ - if (!screen_popup) { - return; - } - - screen_popup->clear(); - QActionGroup *group = new QActionGroup(screen_popup); - - for (int i = 0; iaddAction(i18nc("@item:inmenu List of all Screens to send a window to", - "Screen &%1", (i+1))); - action->setData(i); - action->setCheckable(true); - if (active_popup_client && i == active_popup_client->screen()) { - action->setChecked(true); - } - group->addAction(action); - } -} - -/*! - Adjusts the activity popup to the current values and the location of - the popup client. - */ -void Workspace::activityPopupAboutToShow() -{ - if (!activity_popup) - return; - -#ifdef KWIN_BUILD_ACTIVITIES - activity_popup->clear(); - QAction *action = activity_popup->addAction(i18n("&All Activities")); - action->setData(QString()); - action->setCheckable(true); - - if (active_popup_client && active_popup_client->isOnAllActivities()) - action->setChecked(true); - activity_popup->addSeparator(); - - foreach (const QString &id, openActivities_) { - KActivities::Info activity(id); - QString name = activity.name(); - name.replace('&', "&&"); - action = activity_popup->addAction(KIcon(activity.icon()), name); - action->setData(id); - action->setCheckable(true); - - if (active_popup_client && - !active_popup_client->isOnAllActivities() && active_popup_client->isOnActivity(id)) - action->setChecked(true); - } -#endif -} - void Workspace::closeActivePopup() { if (active_popup) { @@ -566,6 +786,7 @@ void Workspace::closeActivePopup() active_popup = NULL; active_popup_client = NULL; } + m_userActionsMenu->close(); } /*! @@ -590,7 +811,7 @@ void Workspace::initShortcuts() tab_box->initShortcuts(actionCollection); } #endif - discardPopup(); // so that it's recreated next time + m_userActionsMenu->discard(); // so that it's recreated next time } void Workspace::setupWindowShortcut(Client* c) @@ -655,34 +876,6 @@ void Workspace::clientShortcutUpdated(Client* c) } } -void Workspace::clientPopupActivated(QAction *action) -{ - if (!action->data().isValid()) - return; - - WindowOperation op = static_cast< WindowOperation >(action->data().toInt()); - Client* c = active_popup_client ? active_popup_client : active_client; - if (!c) - return; - QString type; - switch(op) { - case FullScreenOp: - if (!c->isFullScreen() && c->userCanSetFullScreen()) - type = "fullscreenaltf3"; - break; - case NoBorderOp: - if (!c->noBorder() && c->userCanSetNoBorder()) - type = "noborderaltf3"; - break; - default: - break; - }; - if (!type.isEmpty()) - helperDialog(type, c); - performWindowOperation(c, op); -} - - void Workspace::performWindowOperation(Client* c, Options::WindowOperation op) { if (!c) @@ -1432,66 +1625,6 @@ void Workspace::slotKillWindow() kill.start(); } -/*! - Sends the popup client to desktop \a desk - - Internal slot for the window operation menu - */ -void Workspace::slotSendToDesktop(QAction *action) -{ - int desk = action->data().toInt(); - if (!active_popup_client) - return; - if (desk == 0) { - // the 'on_all_desktops' menu entry - active_popup_client->setOnAllDesktops(!active_popup_client->isOnAllDesktops()); - return; - } else if (desk > numberOfDesktops()) { - setNumberOfDesktops(desk); - } - - sendClientToDesktop(active_popup_client, desk, false); - -} - -/*! - Sends the popup client to screen \a screen - - Internal slot for the window operation menu - */ -void Workspace::slotSendToScreen(QAction *action) -{ - const int screen = action->data().toInt(); - if (!active_popup_client) { - return; - } - if (screen >= numScreens()) { - return; - } - - sendClientToScreen(active_popup_client, screen); -} - -/*! - Toggles whether the popup client is on the \a activity - - Internal slot for the window operation menu - */ -void Workspace::slotToggleOnActivity(QAction *action) -{ - QString activity = action->data().toString(); - if (!active_popup_client) - return; - if (activity.isEmpty()) { - // the 'on_all_activities' menu entry - active_popup_client->setOnAllActivities(!active_popup_client->isOnAllActivities()); - return; - } - - toggleClientOnActivity(active_popup_client, activity, false); - -} - /*! Switches to the nearest window in given direction */ @@ -1605,35 +1738,7 @@ void Workspace::slotWindowOperations() void Workspace::showWindowMenu(const QRect &pos, Client* cl) { - if (!KAuthorized::authorizeKAction("kwin_rmb")) - return; - if (!cl) - return; - if (active_popup_client != NULL) // recursion - return; - if (cl->isDesktop() - || cl->isDock()) - return; - - active_popup_client = cl; - QMenu* p = clientPopup(); - active_popup = p; - int x = pos.left(); - int y = pos.bottom(); - if (y == pos.top()) - p->exec(QPoint(x, y)); - else { - QRect area = clientArea(ScreenArea, QPoint(x, y), currentDesktop()); - clientPopupAboutToShow(); // needed for sizeHint() to be correct :-/ - int popupHeight = p->sizeHint().height(); - if (y + popupHeight < area.height()) - p->exec(QPoint(x, y)); - else - p->exec(QPoint(x, pos.top() - popupHeight)); - } - // active popup may be already changed (e.g. the window shortcut dialog) - if (active_popup == p) - closeActivePopup(); + m_userActionsMenu->show(pos, cl); } /*! diff --git a/useractions.h b/useractions.h new file mode 100644 index 0000000000..094a614ef4 --- /dev/null +++ b/useractions.h @@ -0,0 +1,255 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2012 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_USERACTIONS_H +#define KWIN_USERACTIONS_H + +#include +#include + +class QAction; +class QMenu; +class QRect; + +namespace KWin +{ +class Client; + +/** + * @brief Menu shown for a Client. + * + * The UserActionsMenu implements the Menu which is shown on: + * @li context-menu event on Window decoration + * @li window menu button + * @li Keyboard Shortcut (by default Alt+F3) + * + * The menu contains various window management related actions for the Client the menu is opened + * for, this is normally the active Client. + * + * The menu which is shown is tried to be as close as possible to the menu implemented in + * libtaskmanager, though there are differences as there are some actions only the window manager + * can provide and on the other hand the libtaskmanager cares also about things like e.g. grouping. + * + * Whenever the menu is changed it should be tried to also adjust the menu in libtaskmanager. + * + * @author Martin Gräßlin + **/ +class UserActionsMenu : public QObject +{ + Q_OBJECT +public: + explicit UserActionsMenu(QObject *parent = 0); + virtual ~UserActionsMenu(); + /** + * Discards the constructed menu, so that it gets recreates + * on next show event. + * @see show + **/ + void discard(); + + /** + * @returns Whether the menu is currently visible + **/ + bool isShown() const; + /** + * @returns Whether the menu has a Client set to operate on. + **/ + bool hasClient() const; + /** + * Checks whether the given Client @p c is the Client + * for which the Menu is shown. + * @param c The Client to compare + * @returns Whether the Client is the one related to this Menu + **/ + bool isMenuClient(const Client *c) const; + /** + * Closes the Menu and prepares it for next usage. + **/ + void close(); + /** + * @brief Shows the menu at the given @p pos for the given @p client. + * + * @param pos The position where the menu should be shown. + * @param client The Client for which the Menu has to be shown. + **/ + void show(const QRect &pos, const QWeakPointer &client); + +public slots: + /** + * Delayed initialization of the activity menu. + * + * The call to retrieve the current list of activities is performed in a thread and this + * slot is invoked once the list has been fetched. Only task of this method is to decide + * whether to show the activity menu and to invoke the initialization of it. + * + * @see initActivityPopup + **/ + void showHideActivityMenu(); + +private slots: + /** + * The menu will become visible soon. + * + * Adjust the items according to the respective Client. + **/ + void menuAboutToShow(); + /** + * Adjusts the add to tab group menu to the current value of the Client. + **/ + void rebuildTabGroupPopup(); + /** + * Adjusts the switch to tab menu to the current values of the Client. + **/ + void rebuildTabListPopup(); + /** + * Adds the Client as tab to the Client identified by the @p action. + * + * @param action The invoked action containing the Client to which the active Client should be tabbed. + **/ + void entabPopupClient(QAction *action); + /** + * Activates the selected tabbed Client. + * + * @param action The invoked action containing the tabbed Client which should be activated. + **/ + void selectPopupClientTab(QAction *action); + /** + * Adjusts the desktop popup to the current values and the location of + * the Client. + **/ + void desktopPopupAboutToShow(); + /** + * Adjusts the screen popup to the current values and the location of + * the Client. + **/ + void screenPopupAboutToShow(); + /** + * Adjusts the activity popup to the current values and the location of + * the Client. + **/ + void activityPopupAboutToShow(); + /** + * Sends the client to desktop \a desk + * + * @param action Invoked Action containing the Desktop as data element + **/ + void slotSendToDesktop(QAction *action); + /** + * Sends the Client to screen \a screen + * + * @param action Invoked Action containing the Screen as data element + **/ + void slotSendToScreen(QAction *action); + /** + * Toggles whether the Client is on the \a activity + * + * @param action Invoked Action containing the Id of the Activity to toggle the Client on + **/ + void slotToggleOnActivity(QAction *action); + /** + * Performs a window operation. + * + * @param action Invoked Action containing the Window Operation to perform for the Client + **/ + void slotWindowOperation(QAction *action); + /** + * Invokes the kcmshell with the Window Manager related config modules. + **/ + void configureWM(); + +private: + /** + * Creates the menu if not already created. + **/ + void init(); + /** + * Creates the Move To Desktop sub-menu. + **/ + void initDesktopPopup(); + /** + * Creates the Move To Screen sub-menu. + **/ + void initScreenPopup(); + /** + * Creates activity popup. + * I'm going with checkable ones instead of "copy to" and "move to" menus; I *think* it's an easier way. + * Oh, and an 'all' option too of course + **/ + void initActivityPopup(); + /** + * Creates the Window Tabbing related menus. + **/ + void initTabbingPopups(); + /** + * Shows a helper Dialog to inform the user how to get back in case he triggered + * an action which hides the window decoration (e.g. NoBorder or Fullscreen). + * @param message The message type to be shown + * @param c The Client for which the dialog should be shown. + **/ + void helperDialog(const QString &message, const QWeakPointer &c); + /** + * The actual main context menu which is show when the UserActionsMenu is invoked. + **/ + QMenu* m_menu; + /** + * The move to desktop sub menu. + **/ + QMenu* m_desktopMenu; + /** + * The move to screen sub menu. + **/ + QMenu* m_screenMenu; + /** + * The activities sub menu. + **/ + QMenu* m_activityMenu; + /** + * Menu to add the group to other group. + **/ + QMenu* m_addTabsMenu; + /** + * Menu to change tab. + **/ + QMenu* m_switchToTabMenu; + QAction* m_resizeOperation; + QAction* m_moveOperation; + QAction* m_maximizeOperation; + QAction* m_shadeOperation; + QAction* m_keepAboveOperation; + QAction* m_keepBelowOperation; + QAction* m_fullScreenOperation; + QAction* m_noBorderOperation; + QAction* m_minimizeOperation; + QAction* m_closeOperation; + /** + * Remove client from group. + **/ + QAction* m_removeFromTabGroup; + /** + * Close all clients in the group. + **/ + QAction* m_closeTabGroup; + /** + * The Client for which the menu is shown. + **/ + QWeakPointer m_client; +}; +} // namespace + +#endif // KWIN_USERACTIONS_H diff --git a/workspace.cpp b/workspace.cpp index cf3f308a86..f60015e915 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -59,6 +59,7 @@ along with this program. If not, see . #include "deleted.h" #include "effects.h" #include "overlaywindow.h" +#include "useractions.h" #include #include #ifdef KWIN_BUILD_SCRIPTING @@ -71,8 +72,6 @@ along with this program. If not, see . #include #include #include -#include -#include #include #include #include @@ -128,13 +127,7 @@ Workspace::Workspace(bool restore) #ifdef KWIN_BUILD_TABBOX , tab_box(0) #endif - , popup(0) - , advanced_popup(0) - , desk_popup(0) - , screen_popup(NULL) - , activity_popup(0) - , add_tabs_popup(0) - , switch_to_tab_popup(0) + , m_userActionsMenu(new UserActionsMenu(this)) , keys(0) , client_keys(NULL) , disable_shortcuts_keys(NULL) @@ -520,7 +513,6 @@ Workspace::~Workspace() for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(true); delete m_outline; - discardPopup(); XDeleteProperty(display(), rootWindow(), atoms->kwin_running); writeWindowRules(); @@ -632,6 +624,9 @@ void Workspace::removeClient(Client* c, allowed_t) if (c == active_popup_client) closeActivePopup(); + if (m_userActionsMenu->isMenuClient(c)) { + m_userActionsMenu->close(); + } c->untab(); @@ -932,7 +927,7 @@ void Workspace::slotSettingsChanged(int category) { kDebug(1212) << "Workspace::slotSettingsChanged()"; if (category == KGlobalSettings::SETTINGS_SHORTCUTS) - discardPopup(); + m_userActionsMenu->discard(); } /** @@ -957,7 +952,7 @@ void Workspace::slotReconfigure() unsigned long changed = options->updateSettings(); emit configChanged(); - discardPopup(); + m_userActionsMenu->discard(); updateToolWindows(true); if (hasDecorationPlugin() && mgr->reset(changed)) { @@ -1140,35 +1135,6 @@ void Workspace::saveDesktopSettings() group.sync(); } -QStringList Workspace::configModules(bool controlCenter) -{ - QStringList args; - args << "kwindecoration"; - if (controlCenter) - args << "kwinoptions"; - else if (KAuthorized::authorizeControlModule("kde-kwinoptions.desktop")) - args << "kwinactions" << "kwinfocus" << "kwinmoving" << "kwinadvanced" - << "kwinrules" << "kwincompositing" -#ifdef KWIN_BUILD_TABBOX - << "kwintabbox" -#endif -#ifdef KWIN_BUILD_SCREENEDGES - << "kwinscreenedges" -#endif -#ifdef KWIN_BUILD_SCRIPTING - << "kwinscripts" -#endif - ; - return args; -} - -void Workspace::configureWM() -{ - QStringList args; - args << "--icon" << "preferences-system-windows" << configModules(false); - KToolInvocation::kdeinitExec("kcmshell4", args); -} - /** * Avoids managing a window with title \a title */ @@ -1429,19 +1395,23 @@ fetchActivityListAndCurrent(KActivities::Controller *controller) return CurrentAndList(c, l); } -void Workspace::updateActivityList(bool running, bool updateCurrent, QString slot) +void Workspace::updateActivityList(bool running, bool updateCurrent, QObject *target, QString slot) { if (updateCurrent) { QFutureWatcher* watcher = new QFutureWatcher; connect( watcher, SIGNAL(finished()), SLOT(handleActivityReply()) ); - if (!slot.isEmpty()) + if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" + watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); + } watcher->setFuture(QtConcurrent::run(fetchActivityListAndCurrent, &activityController_ )); } else { QFutureWatcher* watcher = new QFutureWatcher; connect(watcher, SIGNAL(finished()), SLOT(handleActivityReply())); - if (!slot.isEmpty()) + if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" + watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); + } QStringList *target = running ? &openActivities_ : &allActivities_; watcher->setFuture(QtConcurrent::run(fetchActivityList, &activityController_, target, running)); } @@ -1465,9 +1435,10 @@ void Workspace::handleActivityReply() if (watcherObject) { QString slot = watcherObject->property("activityControllerCallback").toString(); + QObject *target = static_cast(watcherObject->property("activityControllerCallbackTarget").value()); watcherObject->deleteLater(); // has done it's job if (!slot.isEmpty()) - QMetaObject::invokeMethod(this, slot.toAscii().data(), Qt::DirectConnection); + QMetaObject::invokeMethod(target, slot.toAscii().data(), Qt::DirectConnection); } } //END threaded activity list fetching @@ -1971,49 +1942,6 @@ void Workspace::focusToNull() XSetInputFocus(display(), null_focus_window, RevertToPointerRoot, xTime()); } -void Workspace::helperDialog(const QString& message, const Client* c) -{ - QStringList args; - QString type; - if (message == "noborderaltf3") { - KAction* action = qobject_cast(keys->action("Window Operations Menu")); - assert(action != NULL); - QString shortcut = QString("%1 (%2)").arg(action->text()) - .arg(action->globalShortcut().primary().toString(QKeySequence::NativeText)); - args << "--msgbox" << i18n( - "You have selected to show a window without its border.\n" - "Without the border, you will not be able to enable the border " - "again using the mouse: use the window operations menu instead, " - "activated using the %1 keyboard shortcut.", - shortcut); - type = "altf3warning"; - } else if (message == "fullscreenaltf3") { - KAction* action = qobject_cast(keys->action("Window Operations Menu")); - assert(action != NULL); - QString shortcut = QString("%1 (%2)").arg(action->text()) - .arg(action->globalShortcut().primary().toString(QKeySequence::NativeText)); - args << "--msgbox" << i18n( - "You have selected to show a window in fullscreen mode.\n" - "If the application itself does not have an option to turn the fullscreen " - "mode off you will not be able to disable it " - "again using the mouse: use the window operations menu instead, " - "activated using the %1 keyboard shortcut.", - shortcut); - type = "altf3warning"; - } else - abort(); - if (!type.isEmpty()) { - KConfig cfg("kwin_dialogsrc"); - KConfigGroup cg(&cfg, "Notification Messages"); // Depends on KMessageBox - if (!cg.readEntry(type, true)) - return; - args << "--dontagain" << "kwin_dialogsrc:" + type; - } - if (c != NULL) - args << "--embed" << QString::number(c->window()); - KProcess::startDetached("kdialog", args); -} - void Workspace::setShowingDesktop(bool showing) { rootInfo->setShowingDesktop(showing); diff --git a/workspace.h b/workspace.h index 16630f7569..554d1df30e 100644 --- a/workspace.h +++ b/workspace.h @@ -78,6 +78,7 @@ class PluginMgr; class Placement; class Rules; class Scripting; +class UserActionsMenu; class WindowRules; class Workspace : public QObject, public KDecorationDefines @@ -325,6 +326,12 @@ public: QStringList activityList() const { return allActivities_; } + const QStringList &openActivities() const { + return openActivities_; + } +#ifdef KWIN_BUILD_ACTIVITIES + void updateActivityList(bool running, bool updateCurrent, QObject *target = NULL, QString slot = QString()); +#endif // True when performing Workspace::updateClientArea(). // The calls below are valid only in that case. bool inUpdateClientArea() const; @@ -397,7 +404,9 @@ public: */ void showWindowMenu(int x, int y, Client* cl); void showWindowMenu(QPoint pos, Client* cl); - bool windowMenuShown(); + const UserActionsMenu *userActionsMenu() const { + return m_userActionsMenu; + } void updateMinimizedOfTransients(Client*); void updateOnAllDesktopsOfTransients(Client*); @@ -503,8 +512,6 @@ public: int packPositionUp(const Client* cl, int oldy, bool top_edge) const; int packPositionDown(const Client* cl, int oldy, bool bottom_edge) const; - static QStringList configModules(bool controlCenter); - void cancelDelayFocus(); void requestDelayFocus(Client*); void updateFocusMousePosition(const QPoint& pos); @@ -628,19 +635,6 @@ public slots: void slotUntab(); // Slot to remove the active client from its group. private slots: - void rebuildTabGroupPopup(); - void rebuildTabListPopup(); - void entabPopupClient(QAction*); - void selectPopupClientTab(QAction*); - void desktopPopupAboutToShow(); - void screenPopupAboutToShow(); - void activityPopupAboutToShow(); - void clientPopupAboutToShow(); - void slotSendToDesktop(QAction*); - void slotSendToScreen(QAction*); - void slotToggleOnActivity(QAction*); - void clientPopupActivated(QAction*); - void configureWM(); void desktopResized(); void screenChangeTimeout(); void slotUpdateToolWindows(); @@ -667,7 +661,6 @@ private slots: void slotActivityAdded(const QString &activity); void reallyStopActivity(const QString &id); //dbus deadlocks suck void handleActivityReply(); - void showHideActivityMenu(); protected: void timerEvent(QTimerEvent *te); @@ -717,17 +710,9 @@ signals: private: void init(); void initShortcuts(); - void initDesktopPopup(); - void initScreenPopup(); - void initActivityPopup(); - void initTabbingPopups(); void restartKWin(const QString &reason); - void discardPopup(); void setupWindowShortcut(Client* c); void checkCursorPos(); -#ifdef KWIN_BUILD_ACTIVITIES - void updateActivityList(bool running, bool updateCurrent, QString slot = QString()); -#endif enum Direction { DirectionNorth, DirectionEast, @@ -765,9 +750,6 @@ private: //--------------------------------------------------------------------- - void helperDialog(const QString& message, const Client* c); - - QMenu* clientPopup(); void closeActivePopup(); void updateClientArea(bool force); @@ -843,31 +825,17 @@ private: TabBox::TabBox* tab_box; #endif - QMenu* popup; - QMenu* advanced_popup; - QMenu* desk_popup; - QMenu* screen_popup; - QMenu* activity_popup; - QMenu* add_tabs_popup; // Menu to add the group to other group - QMenu* switch_to_tab_popup; // Menu to change tab + /** + * Holds the menu containing the user actions which is shown + * on e.g. right click the window decoration. + **/ + UserActionsMenu *m_userActionsMenu; void modalActionsSwitch(bool enabled); KActionCollection* keys; KActionCollection* client_keys; KActionCollection* disable_shortcuts_keys; - QAction* mResizeOpAction; - QAction* mMoveOpAction; - QAction* mMaximizeOpAction; - QAction* mShadeOpAction; - QAction* mKeepAboveOpAction; - QAction* mKeepBelowOpAction; - QAction* mFullScreenOpAction; - QAction* mNoBorderOpAction; - QAction* mMinimizeOpAction; - QAction* mCloseOpAction; - QAction* mRemoveFromTabGroup; // Remove client from group - QAction* mCloseTabGroup; // Close all clients in the group ShortcutDialog* client_keys_dialog; Client* client_keys_client; bool global_shortcuts_disabled; @@ -1068,11 +1036,6 @@ inline void Workspace::showWindowMenu(int x, int y, Client* cl) showWindowMenu(QRect(QPoint(x, y), QPoint(x, y)), cl); } -inline bool Workspace::windowMenuShown() -{ - return popup && ((QWidget*)popup)->isVisible(); -} - inline void Workspace::setWasUserInteraction() { was_user_interaction = true;