feat: 任务栏拖拽图标交互优化

单指长按应用图标超过1s后显示右键菜单,继续拖拽应用进行调整位置,拖拽的过程中右键菜单隐藏

Log: 优化任务栏拖拽图标人机交互逻辑
Influence: 优化任务栏拖拽图标与右键菜单显示的人机交互逻辑
Task: https://pms.uniontech.com/zentao/task-view-86283.html
Change-Id: I15b4e0cafeb94fc4545090e60965d217b93ab8cd
This commit is contained in:
songwentao 2021-10-21 16:41:16 +08:00
parent 5a1f0c9bf8
commit 04762453fa
12 changed files with 303 additions and 29 deletions

View File

@ -42,6 +42,7 @@ DockItem::DockItem(QWidget *parent)
, m_draging(false)
, m_popupTipsDelayTimer(new QTimer(this))
, m_popupAdjustDelayTimer(new QTimer(this))
, m_contextMenu(new Menu(this, this))
{
if (PopupWindow.isNull()) {
DockPopupWindow *arrowRectangle = new DockPopupWindow(nullptr);
@ -64,7 +65,7 @@ DockItem::DockItem(QWidget *parent)
connect(m_popupTipsDelayTimer, &QTimer::timeout, this, &DockItem::showHoverTips);
connect(m_popupAdjustDelayTimer, &QTimer::timeout, this, &DockItem::updatePopupPosition, Qt::QueuedConnection);
connect(&m_contextMenu, &QMenu::triggered, this, &DockItem::menuActionClicked);
connect(m_contextMenu, &Menu::triggered, this, &DockItem::menuActionClicked);
grabGesture(Qt::TapAndHoldGesture);
@ -233,7 +234,7 @@ void DockItem::showContextMenu()
QJsonObject jsonMenu = jsonDocument.object();
qDeleteAll(m_contextMenu.actions());
qDeleteAll(m_contextMenu->actions());
QJsonArray jsonMenuItems = jsonMenu.value("items").toArray();
for (auto item : jsonMenuItems) {
@ -243,13 +244,13 @@ void DockItem::showContextMenu()
action->setChecked(itemObj.value("checked").toBool());
action->setData(itemObj.value("itemId").toString());
action->setEnabled(itemObj.value("isActive").toBool());
m_contextMenu.addAction(action);
m_contextMenu->addAction(action);
}
hidePopup();
emit requestWindowAutoHide(false);
m_contextMenu.popup(QCursor::pos());
m_contextMenu->popup(QCursor::pos());
onContextMenuAccepted();
}

View File

@ -24,6 +24,7 @@
#include "constants.h"
#include "dockpopupwindow.h"
#include "menudialog.h"
#include <QFrame>
#include <QPointer>
@ -110,12 +111,12 @@ protected:
bool m_popupShown;
bool m_tapAndHold;
bool m_draging;
QMenu m_contextMenu;
QPointer<QWidget> m_lastPopupWidget;
QTimer *m_popupTipsDelayTimer;
QTimer *m_popupAdjustDelayTimer;
Menu *m_contextMenu;
static Position DockPosition;
static DisplayMode DockDisplayMode;

97
frame/util/menudialog.cpp Normal file
View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2020 ~ 2021 Deepin Technology Co., Ltd.
*
* Author: songwentao <songwentao@uniontech.com>
*
* Maintainer: songwentao <songwentao@uniontech.com>
*
* 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 3 of the License, or
* 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 <http://www.gnu.org/licenses/>.
*
* This program aims to cache the the icon and name of apps to the hash table,
* which can decrease the repeated resource consumption of loading the app info in the
* running time.
*/
#include "menudialog.h"
#include <QApplication>
#include <QEvent>
#include <QMouseEvent>
Menu::Menu(QWidget *dockItem, QWidget *parent)
: QMenu(parent)
, m_dockItem(dockItem)
, m_eventInter(new XEventMonitor("com.deepin.api.XEventMonitor", "/com/deepin/api/XEventMonitor", QDBusConnection::sessionBus(), this))
, m_dockInter(new DBusDock("com.deepin.dde.daemon.Dock", "/com/deepin/dde/daemon/Dock", QDBusConnection::sessionBus(), this))
{
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::Dialog);
setObjectName("rightMenu");
qApp->installEventFilter(this);
m_dockItem->installEventFilter(this);
// 点击任务栏以外区域时,关闭右键菜单
connect(m_eventInter, &XEventMonitor::ButtonPress, this, &Menu::onButtonPress);
}
void Menu::onButtonPress()
{
if (!QRect(m_dockInter->frontendWindowRect()).contains(QCursor::pos()))
this->hide();
}
/** 右键菜单显示后在很多场景下都需要隐藏,为避免在各个控件中分别做隐藏右键菜单窗口的处理,
*
* @brief Menu::eventFilter
* @param watched
* @param event
* @return true, false
*/
bool Menu::eventFilter(QObject *watched, QEvent *event)
{
// 存在rightMenu和rightMenuWindow的对象名
if (!watched->objectName().startsWith("rightMenu")) {
if (event->type() == QEvent::MouseButtonPress) {
if (watched != m_dockItem && watched != this && this->isVisible()) {
// 鼠标点击时,除当前菜单外,其他显示的菜单都要隐藏
hide();
}
} else if (event->type() == QEvent::DragMove || event->type() == QEvent::Wheel || event->type() == QEvent::Move) {
// 按下应用拖动,按下应用从菜单上方移动时,鼠标滚轮滚动时,隐藏右键菜单
hide();
}
// 当右键菜单显示时捕获鼠标的release事件,click=press+release
// 让click无效从而让启动器窗口不关闭
if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->source() == Qt::MouseEventSynthesizedByQt) {
if (isVisible())
return true;
}
}
// 处理当右键菜单显示时,esc按下关闭右键菜单保持和模态框一样的效果
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape) {
if (isVisible()) {
hide();
return true;
}
}
}
}
return QMenu::eventFilter(watched, event);
}

56
frame/util/menudialog.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 ~ 2021 Deepin Technology Co., Ltd.
*
* Author: songwentao <songwentao@uniontech.com>
*
* Maintainer: songwentao <songwentao@uniontech.com>
*
* 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 3 of the License, or
* 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 <http://www.gnu.org/licenses/>.
*
* This program aims to cache the the icon and name of apps to the hash table,
* which can decrease the repeated resource consumption of loading the app info in the
* running time.
*/
#ifndef MYMENU_H
#define MYMENU_H
#include <com_deepin_api_xeventmonitor.h>
#include <com_deepin_dde_daemon_dock.h>
#include <QMenu>
#include <QWidget>
using DBusDock = com::deepin::dde::daemon::Dock;
using XEventMonitor = ::com::deepin::api::XEventMonitor;
class Menu : public QMenu
{
Q_OBJECT
public:
explicit Menu(QWidget *dockItem = nullptr, QWidget *parent = nullptr);
void onButtonPress();
protected:
bool eventFilter(QObject *watched, QEvent *event);
private:
QWidget *m_dockItem;
XEventMonitor *m_eventInter;
DBusDock *m_dockInter;
};
#endif //MYMENU_H

View File

@ -28,7 +28,7 @@ DBusAdaptors::DBusAdaptors(QObject *parent)
m_keyboard(new Keyboard("com.deepin.daemon.InputDevices",
"/com/deepin/daemon/InputDevice/Keyboard",
QDBusConnection::sessionBus(), this)),
m_menu(new QMenu()),
m_menu(new Menu()),
m_gsettings(Utils::ModuleSettingsPtr("keyboard", QByteArray(), this))
{
m_keyboard->setSync(false);
@ -36,7 +36,7 @@ DBusAdaptors::DBusAdaptors(QObject *parent)
connect(m_keyboard, &Keyboard::CurrentLayoutChanged, this, &DBusAdaptors::onCurrentLayoutChanged);
connect(m_keyboard, &Keyboard::UserLayoutListChanged, this, &DBusAdaptors::onUserLayoutListChanged);
connect(m_menu, &QMenu::triggered, this, &DBusAdaptors::handleActionTriggered);
connect(m_menu, &Menu::triggered, this, &DBusAdaptors::handleActionTriggered);
// init data
initAllLayoutList();

View File

@ -20,6 +20,8 @@
#ifndef DBUSADAPTORS_H
#define DBUSADAPTORS_H
#include "menudialog.h"
#include <QMenu>
#include <QtDBus/QtDBus>
#include <com_deepin_daemon_inputdevice_keyboard.h>
@ -73,7 +75,7 @@ private:
private:
Keyboard *m_keyboard;
QMenu *m_menu;
Menu *m_menu;
QAction *m_addLayoutAction;
QString m_currentLayoutRaw;

View File

@ -39,10 +39,10 @@ QPointer<DockPopupWindow> SNITrayWidget::PopupWindow = nullptr;
Dock::Position SNITrayWidget::DockPosition = Dock::Position::Top;
using namespace Dock;
SNITrayWidget::SNITrayWidget(const QString &sniServicePath, QWidget *parent)
: AbstractTrayWidget(parent),
m_dbusMenuImporter(nullptr),
m_menu(nullptr),
m_updateIconTimer(new QTimer(this))
: AbstractTrayWidget(parent)
, m_dbusMenuImporter(nullptr)
, m_menu(nullptr)
, m_updateIconTimer(new QTimer(this))
, m_updateOverlayIconTimer(new QTimer(this))
, m_updateAttentionIconTimer(new QTimer(this))
, m_sniServicePath(sniServicePath)
@ -181,6 +181,7 @@ void SNITrayWidget::sendClick(uint8_t mouseButton, int x, int y)
if (LeftClickInvalidIdList.contains(m_sniId)) {
showContextMenu(x, y);
} else {
// 输入法切换等功能入口
m_sniInter->Activate(x, y);
}
break;
@ -191,7 +192,6 @@ void SNITrayWidget::sendClick(uint8_t mouseButton, int x, int y)
showContextMenu(x, y);
break;
default:
qDebug() << "unknown mouse button key";
break;
}
}
@ -272,15 +272,13 @@ void SNITrayWidget::initMenu()
return;
}
qDebug() << "using sni service path:" << m_dbusService << "menu path:" << sniMenuPath;
m_dbusMenuImporter = new DBusMenuImporter(m_dbusService, sniMenuPath, ASYNCHRONOUS, this);
qDebug() << "generate the sni menu object";
// 创建sni协议插件的右键菜单
m_menu = m_dbusMenuImporter->menu();
qDebug() << "the sni menu obect is:" << m_menu;
m_menu->setObjectName("sniMenu");
m_menu->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::Dialog);
qApp->installEventFilter(this);
}
void SNITrayWidget::refreshIcon()
@ -337,7 +335,7 @@ void SNITrayWidget::showContextMenu(int x, int y)
// 这里的PopupWindow属性是置顶的,如果不隐藏,会导致菜单显示不出来
hidePopup();
// ContextMenu does not work
// 第三方的右键菜单事件过滤方式无法捕获修改x,y值不能改变菜单弹出位置
if (m_sniMenuPath.path().startsWith("/NO_DBUSMENU")) {
m_sniInter->ContextMenu(x, y);
} else {
@ -732,6 +730,66 @@ const QPoint SNITrayWidget::popupMarkPoint() const
return p;
}
bool SNITrayWidget::eventFilter(QObject *watched, QEvent *event)
{
if (!m_menu)
return QWidget::eventFilter(watched, event);
QPoint startPos = QPoint();
if (!watched->objectName().startsWith("sniMenu")) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::LeftButton) {
if (m_sniMenuPath.path().startsWith("/NO_DBUSMENU")) {
// 关闭中文输入法或者五笔输入法,当前的关闭方式无效
QMouseEvent *pressEvent = new QMouseEvent(QEvent::MouseButtonPress, mapToGlobal(this->pos()), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->postEvent(this->parentWidget(), pressEvent);
} else {
m_menu->hide();
}
}
startPos = mouseEvent->pos();
} else if (event->type() == QEvent::DragMove || event->type() == QEvent::Move) {
if (m_sniMenuPath.path().startsWith("/NO_DBUSMENU")) {
// 关闭中文输入法或者五笔输入法,当前的关闭方式无效
QMouseEvent *pressEvent = new QMouseEvent(QEvent::MouseButtonPress, mapToGlobal(this->pos()), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->postEvent(this->parentWidget(), pressEvent);
} else {
m_menu->hide();
}
}
// 让click无效,避免点击到插件上
if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->source() == Qt::MouseEventSynthesizedByQt) {
if (isVisible())
return true;
}
}
// 处理当右键菜单显示时,esc按下关闭右键菜单保持和模态框一样的效果
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape) {
if (!isVisible())
return false;
if (m_sniMenuPath.path().startsWith("/NO_DBUSMENU")) {
// 关闭中文输入法或者五笔输入法,当前的关闭方式无效
QMouseEvent *pressEvent = new QMouseEvent(QEvent::MouseButtonPress, mapToGlobal(this->pos()), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
qApp->postEvent(this->parentWidget(), pressEvent);
} else {
m_menu->hide();
}
return true;
}
}
}
return QWidget::eventFilter(watched, event);
}
void SNITrayWidget::showPopupWindow(QWidget *const content, const bool model)
{
m_popupShown = true;

View File

@ -74,6 +74,13 @@ public:
static void setDockPostion(const Dock::Position pos) { DockPosition = pos; }
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *e) override;
Q_SIGNALS:
void statusChanged(SNITrayWidget::ItemStatus status);
@ -100,10 +107,6 @@ private Q_SLOTS:
void hidePopup();
void hideNonModel();
void popupWindowAccept();
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
void paintEvent(QPaintEvent *e) override;

View File

@ -24,6 +24,7 @@
#include <QProcess>
#include <QDebug>
#include <QObject>
#include <xcb/xproto.h>
@ -34,6 +35,7 @@ SystemTrayItem::SystemTrayItem(PluginsItemInterface *const pluginInter, const QS
: AbstractTrayWidget(parent)
, m_popupShown(false)
, m_tapAndHold(false)
, m_contextMenu(new QMenu)
, m_pluginInter(pluginInter)
, m_centralWidget(m_pluginInter->itemWidget(itemKey))
, m_popupTipsDelayTimer(new QTimer(this))
@ -82,18 +84,24 @@ SystemTrayItem::SystemTrayItem(PluginsItemInterface *const pluginInter, const QS
connect(m_popupTipsDelayTimer, &QTimer::timeout, this, &SystemTrayItem::showHoverTips);
connect(m_popupAdjustDelayTimer, &QTimer::timeout, this, &SystemTrayItem::updatePopupPosition, Qt::QueuedConnection);
connect(&m_contextMenu, &QMenu::triggered, this, &SystemTrayItem::menuActionClicked);
connect(m_contextMenu, &QMenu::triggered, this, &SystemTrayItem::menuActionClicked);
if (m_gsettings)
connect(m_gsettings, &QGSettings::changed, this, &SystemTrayItem::onGSettingsChanged);
grabGesture(Qt::TapAndHoldGesture);
m_contextMenu->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::Dialog);
m_contextMenu->setObjectName("trayMenu");
qApp->installEventFilter(m_contextMenu);
}
SystemTrayItem::~SystemTrayItem()
{
if (m_popupShown)
popupWindowAccept();
delete m_contextMenu;
}
QString SystemTrayItem::itemKeyForConfig()
@ -261,6 +269,37 @@ void SystemTrayItem::showEvent(QShowEvent *event)
return AbstractTrayWidget::showEvent(event);
}
bool SystemTrayItem::eventFilter(QObject *watched, QEvent *event)
{
if (!watched->objectName().startsWith("trayMenu")) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::LeftButton)
m_contextMenu->hide();
} else if (event->type() == QEvent::DragMove || event->type() == QEvent::Move || event->type() == QEvent::Leave) {
m_contextMenu->hide();
}
// 让click无效,避免点击到插件上
if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->source() == Qt::MouseEventSynthesizedByQt && isVisible())
return true;
}
// 处理当右键菜单显示时,esc按下关闭右键菜单保持和模态框一样的效果
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape && isVisible()) {
m_contextMenu->hide();
return true;
}
}
}
return QWidget::eventFilter(watched, event);
}
const QPoint SystemTrayItem::popupMarkPoint() const
{
QPoint p(topleftPoint());
@ -427,7 +466,7 @@ void SystemTrayItem::showContextMenu()
QJsonObject jsonMenu = jsonDocument.object();
qDeleteAll(m_contextMenu.actions());
qDeleteAll(m_contextMenu->actions());
QJsonArray jsonMenuItems = jsonMenu.value("items").toArray();
for (auto item : jsonMenuItems) {
@ -437,13 +476,13 @@ void SystemTrayItem::showContextMenu()
action->setChecked(itemObj.value("checked").toBool());
action->setData(itemObj.value("itemId").toString());
action->setEnabled(itemObj.value("isActive").toBool());
m_contextMenu.addAction(action);
m_contextMenu->addAction(action);
}
hidePopup();
emit requestWindowAutoHide(false);
m_contextMenu.exec(QCursor::pos());
m_contextMenu->popup(QCursor::pos());
onContextMenuAccepted();
}

View File

@ -70,6 +70,7 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void showEvent(QShowEvent* event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
protected:
const QPoint popupMarkPoint() const;
@ -96,7 +97,7 @@ private:
private:
bool m_popupShown;
bool m_tapAndHold;
QMenu m_contextMenu;
QMenu *m_contextMenu;
PluginsItemInterface* m_pluginInter;
QWidget *m_centralWidget;

View File

@ -134,12 +134,27 @@ void XEmbedTrayWidget::paintEvent(QPaintEvent *e)
painter.end();
}
void XEmbedTrayWidget::mousePressEvent(QMouseEvent *e)
{
// 支持触摸屏触摸按下,显示右键菜单
if (e->source() == Qt::MouseEventSynthesizedByQt) {
QTimer::singleShot(100, this, [ & ] {
// 右键
sendClick(XCB_BUTTON_INDEX_3, QCursor::pos().x(), QCursor::pos().y());
});
return;
}
}
void XEmbedTrayWidget::mouseMoveEvent(QMouseEvent *e)
{
AbstractTrayWidget::mouseMoveEvent(e);
// ignore the touchEvent
if (e->source() == Qt::MouseEventSynthesizedByQt) {
// 临时方案隐藏微信等应用的右键菜单
// 左键
sendClick(XCB_BUTTON_INDEX_2, QCursor::pos().x(), QCursor::pos().y());
return;
}

View File

@ -47,6 +47,7 @@ public:
private:
void showEvent(QShowEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void configContainerPosition();