You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
663 lines
19 KiB
663 lines
19 KiB
/* BEGIN_COMMON_COPYRIGHT_HEADER
|
|
* (c)LGPL2+
|
|
*
|
|
* LXDE-Qt - a lightweight, Qt based, desktop toolset
|
|
* http://razor-qt.org
|
|
* http://lxqt.org
|
|
*
|
|
* Copyright: 2011 Razor team
|
|
* 2014 LXQt team
|
|
* Authors:
|
|
* Alexander Sokoloff <sokoloff.a@gmail.com>
|
|
* Maciej Płaza <plaza.maciej@gmail.com>
|
|
* Kuzma Shapran <kuzma.shapran@gmail.com>
|
|
*
|
|
* This program or library is free software; you can redistribute it
|
|
* and/or modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU Lesser General
|
|
* Public License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA
|
|
*
|
|
* END_COMMON_COPYRIGHT_HEADER */
|
|
|
|
#include "lxqttaskgroup.h"
|
|
#include "lxqttaskbar.h"
|
|
|
|
#include <QDebug>
|
|
#include <QMimeData>
|
|
#include <QFocusEvent>
|
|
#include <QDragLeaveEvent>
|
|
#include <QStringBuilder>
|
|
#include <QMenu>
|
|
#include <XdgIcon>
|
|
#include <KF5/KWindowSystem/KWindowSystem>
|
|
#include <functional>
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
LXQtTaskGroup::LXQtTaskGroup(const QString &groupName, WId window, LXQtTaskBar *parent)
|
|
: LXQtTaskButton(window, parent, parent),
|
|
mGroupName(groupName),
|
|
mPopup(new LXQtGroupPopup(this)),
|
|
mPreventPopup(false),
|
|
mSingleButton(true)
|
|
{
|
|
Q_ASSERT(parent);
|
|
|
|
setObjectName(groupName);
|
|
setText(groupName);
|
|
|
|
connect(this, SIGNAL(clicked(bool)), this, SLOT(onClicked(bool)));
|
|
connect(KWindowSystem::self(), SIGNAL(currentDesktopChanged(int)), this, SLOT(onDesktopChanged(int)));
|
|
connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), this, SLOT(onActiveWindowChanged(WId)));
|
|
connect(parent, &LXQtTaskBar::buttonRotationRefreshed, this, &LXQtTaskGroup::setAutoRotation);
|
|
connect(parent, &LXQtTaskBar::refreshIconGeometry, this, &LXQtTaskGroup::refreshIconsGeometry);
|
|
connect(parent, &LXQtTaskBar::buttonStyleRefreshed, this, &LXQtTaskGroup::setToolButtonsStyle);
|
|
connect(parent, &LXQtTaskBar::showOnlySettingChanged, this, &LXQtTaskGroup::refreshVisibility);
|
|
connect(parent, &LXQtTaskBar::popupShown, this, &LXQtTaskGroup::groupPopupShown);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::contextMenuEvent(QContextMenuEvent *event)
|
|
{
|
|
setPopupVisible(false, true);
|
|
mPreventPopup = true;
|
|
if (mSingleButton)
|
|
{
|
|
LXQtTaskButton::contextMenuEvent(event);
|
|
return;
|
|
}
|
|
|
|
QMenu * menu = new QMenu(tr("Group"));
|
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
QAction *a = menu->addAction(XdgIcon::fromTheme("process-stop"), tr("Close group"));
|
|
connect(a, SIGNAL(triggered()), this, SLOT(closeGroup()));
|
|
connect(menu, &QMenu::aboutToHide, [this] {
|
|
mPreventPopup = false;
|
|
});
|
|
menu->setGeometry(plugin()->panel()->calculatePopupWindowPos(mapToGlobal(event->pos()), menu->sizeHint()));
|
|
plugin()->willShowWindow(menu);
|
|
menu->show();
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::closeGroup()
|
|
{
|
|
foreach (LXQtTaskButton * button, mButtonHash.values())
|
|
if (button->isOnDesktop(KWindowSystem::currentDesktop()))
|
|
button->closeApplication();
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
LXQtTaskButton * LXQtTaskGroup::addWindow(WId id)
|
|
{
|
|
if (mButtonHash.contains(id))
|
|
return mButtonHash.value(id);
|
|
|
|
LXQtTaskButton *btn = new LXQtTaskButton(id, parentTaskBar(), mPopup);
|
|
btn->setToolButtonStyle(popupButtonStyle());
|
|
|
|
if (btn->isApplicationActive())
|
|
{
|
|
btn->setChecked(true);
|
|
setChecked(true);
|
|
}
|
|
|
|
mButtonHash.insert(id, btn);
|
|
mPopup->addButton(btn);
|
|
|
|
connect(btn, SIGNAL(clicked()), this, SLOT(onChildButtonClicked()));
|
|
refreshVisibility();
|
|
|
|
return btn;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
LXQtTaskButton * LXQtTaskGroup::checkedButton() const
|
|
{
|
|
foreach (LXQtTaskButton* button, mButtonHash.values())
|
|
if (button->isChecked())
|
|
return button;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
LXQtTaskButton * LXQtTaskGroup::getNextPrevChildButton(bool next, bool circular)
|
|
{
|
|
LXQtTaskButton *button = checkedButton();
|
|
int idx = mPopup->indexOf(button);
|
|
int inc = next ? 1 : -1;
|
|
idx += inc;
|
|
|
|
// if there is no cheked button, get the first one if next equals true
|
|
// or the last one if not
|
|
if (!button)
|
|
{
|
|
idx = -1;
|
|
if (next)
|
|
{
|
|
for (int i = 0; i < mPopup->count() && idx == -1; i++)
|
|
if (mPopup->itemAt(i)->widget()->isVisibleTo(mPopup))
|
|
idx = i;
|
|
}
|
|
else
|
|
{
|
|
for (int i = mPopup->count() - 1; i >= 0 && idx == -1; i--)
|
|
if (mPopup->itemAt(i)->widget()->isVisibleTo(mPopup))
|
|
idx = i;
|
|
}
|
|
}
|
|
|
|
if (circular)
|
|
idx = (idx + mButtonHash.count()) % mButtonHash.count();
|
|
else if (mPopup->count() <= idx || idx < 0)
|
|
return NULL;
|
|
|
|
// return the next or the previous child
|
|
QLayoutItem *item = mPopup->itemAt(idx);
|
|
if (item)
|
|
{
|
|
button = qobject_cast<LXQtTaskButton*>(item->widget());
|
|
if (button->isVisibleTo(mPopup))
|
|
return button;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::onActiveWindowChanged(WId window)
|
|
{
|
|
LXQtTaskButton *button = mButtonHash.value(window, nullptr);
|
|
foreach (LXQtTaskButton *btn, mButtonHash.values())
|
|
btn->setChecked(false);
|
|
|
|
if (button)
|
|
{
|
|
button->setChecked(true);
|
|
if (button->hasUrgencyHint())
|
|
button->setUrgencyHint(false);
|
|
}
|
|
setChecked(nullptr != button);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::onDesktopChanged(int number)
|
|
{
|
|
refreshVisibility();
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::onWindowRemoved(WId window)
|
|
{
|
|
if (mButtonHash.contains(window))
|
|
{
|
|
LXQtTaskButton *button = mButtonHash.value(window);
|
|
mButtonHash.remove(window);
|
|
mPopup->removeWidget(button);
|
|
button->deleteLater();
|
|
|
|
if (mButtonHash.count())
|
|
regroup();
|
|
else
|
|
{
|
|
if (isVisible())
|
|
emit visibilityChanged(false);
|
|
hide();
|
|
emit groupBecomeEmpty(groupName());
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::onChildButtonClicked()
|
|
{
|
|
setPopupVisible(false, true);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
Qt::ToolButtonStyle LXQtTaskGroup::popupButtonStyle() const
|
|
{
|
|
// do not set icons-only style in the buttons in the group,
|
|
// as they'll be indistinguishable
|
|
const Qt::ToolButtonStyle style = toolButtonStyle();
|
|
return style == Qt::ToolButtonIconOnly ? Qt::ToolButtonTextBesideIcon : style;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::setToolButtonsStyle(Qt::ToolButtonStyle style)
|
|
{
|
|
setToolButtonStyle(style);
|
|
|
|
const Qt::ToolButtonStyle styleInPopup = popupButtonStyle();
|
|
for (auto & button : mButtonHash)
|
|
{
|
|
button->setToolButtonStyle(styleInPopup);
|
|
}
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
int LXQtTaskGroup::buttonsCount() const
|
|
{
|
|
return mButtonHash.count();
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
int LXQtTaskGroup::visibleButtonsCount() const
|
|
{
|
|
int i = 0;
|
|
foreach (LXQtTaskButton *btn, mButtonHash.values())
|
|
if (btn->isVisibleTo(mPopup))
|
|
i++;
|
|
return i;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::draggingTimerTimeout()
|
|
{
|
|
if (mSingleButton)
|
|
setPopupVisible(false);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::onClicked(bool)
|
|
{
|
|
if (visibleButtonsCount() > 1)
|
|
{
|
|
setChecked(mButtonHash.contains(KWindowSystem::activeWindow()));
|
|
setPopupVisible(true);
|
|
}
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::regroup()
|
|
{
|
|
int cont = visibleButtonsCount();
|
|
recalculateFrameIfVisible();
|
|
|
|
if (cont == 1)
|
|
{
|
|
mSingleButton = true;
|
|
// Get first visible button
|
|
LXQtTaskButton * button = NULL;
|
|
foreach (LXQtTaskButton *btn, mButtonHash.values())
|
|
{
|
|
if (btn->isVisibleTo(mPopup))
|
|
{
|
|
button = btn;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (button)
|
|
{
|
|
setText(button->text());
|
|
setToolTip(button->toolTip());
|
|
setWindowId(button->windowId());
|
|
}
|
|
}
|
|
else if (cont == 0)
|
|
hide();
|
|
else
|
|
{
|
|
mSingleButton = false;
|
|
QString t = QString("%1 - %2 windows").arg(mGroupName).arg(cont);
|
|
setText(t);
|
|
setToolTip(parentTaskBar()->isShowGroupOnHover() ? QString() : t);
|
|
}
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::recalculateFrameIfVisible()
|
|
{
|
|
if (mPopup->isVisible())
|
|
{
|
|
recalculateFrameSize();
|
|
if (plugin()->panel()->position() == ILXQtPanel::PositionBottom)
|
|
recalculateFramePosition();
|
|
}
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::setAutoRotation(bool value, ILXQtPanel::Position position)
|
|
{
|
|
foreach (LXQtTaskButton *button, mButtonHash.values())
|
|
button->setAutoRotation(false, position);
|
|
|
|
LXQtTaskButton::setAutoRotation(value, position);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::refreshVisibility()
|
|
{
|
|
bool will = false;
|
|
LXQtTaskBar const * taskbar = parentTaskBar();
|
|
const int showDesktop = taskbar->showDesktopNum();
|
|
foreach(LXQtTaskButton * btn, mButtonHash.values())
|
|
{
|
|
bool visible = taskbar->isShowOnlyOneDesktopTasks() ? btn->isOnDesktop(0 == showDesktop ? KWindowSystem::currentDesktop() : showDesktop) : true;
|
|
visible &= taskbar->isShowOnlyCurrentScreenTasks() ? btn->isOnCurrentScreen() : true;
|
|
visible &= taskbar->isShowOnlyMinimizedTasks() ? btn->isMinimized() : true;
|
|
btn->setVisible(visible);
|
|
will |= visible;
|
|
}
|
|
|
|
bool is = isVisible();
|
|
setVisible(will);
|
|
regroup();
|
|
|
|
if (is != will)
|
|
emit visibilityChanged(will);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
QMimeData * LXQtTaskGroup::mimeData()
|
|
{
|
|
QMimeData *mimedata = new QMimeData;
|
|
QByteArray byteArray;
|
|
QDataStream stream(&byteArray, QIODevice::WriteOnly);
|
|
stream << groupName();
|
|
mimedata->setData(mimeDataFormat(), byteArray);
|
|
return mimedata;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::setPopupVisible(bool visible, bool fast)
|
|
{
|
|
if (visible && !mPreventPopup && !mSingleButton)
|
|
{
|
|
if (!mPopup->isVisible())
|
|
{
|
|
// setup geometry
|
|
recalculateFrameSize();
|
|
recalculateFramePosition();
|
|
}
|
|
|
|
plugin()->willShowWindow(mPopup);
|
|
mPopup->show();
|
|
emit popupShown(this);
|
|
}
|
|
else
|
|
mPopup->hide(fast);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::refreshIconsGeometry()
|
|
{
|
|
QRect rect = geometry();
|
|
rect.moveTo(mapToGlobal(QPoint(0, 0)));
|
|
|
|
if (mSingleButton)
|
|
{
|
|
refreshIconGeometry(rect);
|
|
return;
|
|
}
|
|
|
|
foreach(LXQtTaskButton *but, mButtonHash.values())
|
|
{
|
|
but->refreshIconGeometry(rect);
|
|
but->setIconSize(QSize(plugin()->panel()->iconSize(), plugin()->panel()->iconSize()));
|
|
}
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
QSize LXQtTaskGroup::recalculateFrameSize()
|
|
{
|
|
int height = recalculateFrameHeight();
|
|
mPopup->setMaximumHeight(1000);
|
|
mPopup->setMinimumHeight(0);
|
|
|
|
int hh = recalculateFrameWidth();
|
|
mPopup->setMaximumWidth(hh);
|
|
mPopup->setMinimumWidth(0);
|
|
|
|
QSize newSize(hh, height);
|
|
mPopup->resize(newSize);
|
|
|
|
return newSize;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
int LXQtTaskGroup::recalculateFrameHeight() const
|
|
{
|
|
int cont = visibleButtonsCount();
|
|
int h = !plugin()->panel()->isHorizontal() && parentTaskBar()->isAutoRotate() ? width() : height();
|
|
return cont * h + (cont + 1) * mPopup->spacing();
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
int LXQtTaskGroup::recalculateFrameWidth() const
|
|
{
|
|
const QFontMetrics fm = fontMetrics();
|
|
int max = 100 * fm.width (' '); // elide after the max width
|
|
int txtWidth = 0;
|
|
foreach (LXQtTaskButton *btn, mButtonHash.values())
|
|
txtWidth = qMax(fm.width(btn->text()), txtWidth);
|
|
return iconSize().width() + qMin(txtWidth, max) + 30/* give enough room to margins and borders*/;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
QPoint LXQtTaskGroup::recalculateFramePosition()
|
|
{
|
|
// Set position
|
|
int x_offset = 0, y_offset = 0;
|
|
switch (plugin()->panel()->position())
|
|
{
|
|
case ILXQtPanel::PositionTop:
|
|
y_offset += height();
|
|
break;
|
|
case ILXQtPanel::PositionBottom:
|
|
y_offset = -recalculateFrameHeight();
|
|
break;
|
|
case ILXQtPanel::PositionLeft:
|
|
x_offset += width();
|
|
break;
|
|
case ILXQtPanel::PositionRight:
|
|
x_offset = -recalculateFrameWidth();
|
|
break;
|
|
}
|
|
|
|
QPoint pos = mapToGlobal(QPoint(x_offset, y_offset));
|
|
mPopup->move(pos);
|
|
|
|
return pos;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::leaveEvent(QEvent *event)
|
|
{
|
|
setPopupVisible(false);
|
|
QToolButton::leaveEvent(event);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::enterEvent(QEvent *event)
|
|
{
|
|
QToolButton::enterEvent(event);
|
|
|
|
if (sDraggging)
|
|
return;
|
|
|
|
if (parentTaskBar()->isShowGroupOnHover())
|
|
setPopupVisible(true);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
// only show the popup if we aren't dragging a taskgroup
|
|
if (!event->mimeData()->hasFormat(mimeDataFormat()))
|
|
{
|
|
setPopupVisible(true);
|
|
}
|
|
LXQtTaskButton::dragEnterEvent(event);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::dragLeaveEvent(QDragLeaveEvent *event)
|
|
{
|
|
// if draggind something into the taskgroup or the taskgroups' popup,
|
|
// do not close the popup
|
|
if (!sDraggging)
|
|
setPopupVisible(false);
|
|
LXQtTaskButton::dragLeaveEvent(event);
|
|
}
|
|
|
|
void LXQtTaskGroup::mouseMoveEvent(QMouseEvent* event)
|
|
{
|
|
// if dragging the taskgroup, do not show the popup
|
|
setPopupVisible(false, true);
|
|
LXQtTaskButton::mouseMoveEvent(event);
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
bool LXQtTaskGroup::onWindowChanged(WId window, NET::Properties prop, NET::Properties2 prop2)
|
|
{
|
|
bool consumed{false};
|
|
bool needsRefreshVisibility{false};
|
|
QVector<LXQtTaskButton *> buttons;
|
|
if (mButtonHash.contains(window))
|
|
buttons.append(mButtonHash.value(window));
|
|
|
|
// If group is based on that window properties must be changed also on button group
|
|
if (window == windowId())
|
|
buttons.append(this);
|
|
|
|
if (!buttons.isEmpty())
|
|
{
|
|
consumed = true;
|
|
// if class is changed the window won't belong to our group any more
|
|
if (parentTaskBar()->isGroupingEnabled() && prop2.testFlag(NET::WM2WindowClass))
|
|
{
|
|
KWindowInfo info(window, 0, NET::WM2WindowClass);
|
|
if (info.windowClassClass() != mGroupName)
|
|
{
|
|
consumed = false;
|
|
onWindowRemoved(window);
|
|
}
|
|
}
|
|
// window changed virtual desktop
|
|
if (prop.testFlag(NET::WMDesktop) || prop.testFlag(NET::WMGeometry))
|
|
{
|
|
if (parentTaskBar()->isShowOnlyOneDesktopTasks()
|
|
|| parentTaskBar()->isShowOnlyCurrentScreenTasks())
|
|
{
|
|
needsRefreshVisibility = true;
|
|
}
|
|
}
|
|
|
|
if (prop.testFlag(NET::WMVisibleName) || prop.testFlag(NET::WMName))
|
|
std::for_each(buttons.begin(), buttons.end(), std::mem_fn(&LXQtTaskButton::updateText));
|
|
|
|
// XXX: we are setting window icon geometry -> don't need to handle NET::WMIconGeometry
|
|
// Icon of the button can be based on windowClass
|
|
if (prop.testFlag(NET::WMIcon) || prop2.testFlag(NET::WM2WindowClass))
|
|
std::for_each(buttons.begin(), buttons.end(), std::mem_fn(&LXQtTaskButton::updateIcon));
|
|
|
|
if (prop.testFlag(NET::WMState))
|
|
{
|
|
KWindowInfo info{window, NET::WMState};
|
|
if (info.hasState(NET::SkipTaskbar))
|
|
{
|
|
consumed = false;
|
|
onWindowRemoved(window);
|
|
}
|
|
std::for_each(buttons.begin(), buttons.end(), std::bind(&LXQtTaskButton::setUrgencyHint, std::placeholders::_1, info.hasState(NET::DemandsAttention)));
|
|
|
|
if (parentTaskBar()->isShowOnlyMinimizedTasks())
|
|
{
|
|
needsRefreshVisibility = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needsRefreshVisibility)
|
|
refreshVisibility();
|
|
|
|
return consumed;
|
|
}
|
|
|
|
/************************************************
|
|
|
|
************************************************/
|
|
void LXQtTaskGroup::groupPopupShown(LXQtTaskGroup * const sender)
|
|
{
|
|
//close all popups (should they be visible because of close delay)
|
|
if (this != sender && isVisible())
|
|
setPopupVisible(false, true/*fast*/);
|
|
}
|