/* * Copyright (C) 2011 ~ 2018 Deepin Technology Co., Ltd. * * Author: listenerri * * Maintainer: listenerri * * 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 . */ #include "../appitem.h" #include "appdragwidget.h" #include "utils.h" #include "displaymanager.h" #include "org_deepin_api_xeventmonitor.h" #define SPLIT_NONE 0 #define SPLIT_LEFT 1 #define SPLIT_RIGHT 2 using XEventMonitor = ::org::deepin::api::XEventMonitor1; AppDragWidget::AppDragWidget(QWidget *parent) : QGraphicsView(parent) , m_object(new AppGraphicsObject) , m_scene(new QGraphicsScene(this)) , m_followMouseTimer(new QTimer(this)) , m_animScale(new QPropertyAnimation(m_object.get(), "scale", this)) , m_animRotation(new QPropertyAnimation(m_object.get(), "rotation", this)) , m_animOpacity(new QPropertyAnimation(m_object.get(), "opacity", this)) , m_animGroup(new QParallelAnimationGroup(this)) , m_goBackAnim(new QPropertyAnimation(this, "pos", this)) , m_dockPosition(Dock::Position::Bottom) , m_popupWindow(new DockPopupWindow(nullptr)) , m_distanceMultiple(Utils::SettingValue("com.deepin.dde.dock.distancemultiple", "/com/deepin/dde/dock/distancemultiple/", "distance-multiple", 1.5).toDouble()) , m_item(nullptr) , m_dockScreen(nullptr) { m_popupWindow->setShadowBlurRadius(20); m_popupWindow->setRadius(18); m_popupWindow->setShadowYOffset(2); m_popupWindow->setShadowXOffset(0); m_popupWindow->setArrowWidth(18); m_popupWindow->setArrowHeight(10); m_popupWindow->setRadius(18); m_scene->addItem(m_object.get()); setScene(m_scene); setAttribute(Qt::WA_TranslucentBackground); if (Utils::IS_WAYLAND_DISPLAY) { setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint | Qt::Window | Qt::FramelessWindowHint); setAttribute(Qt::WA_NativeWindow); initWaylandEnv(); } else { setWindowFlags(Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); } viewport()->setAutoFillBackground(false); setFrameShape(QFrame::NoFrame); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setMouseTracking(true); setAcceptDrops(true); initAnimations(); if (!Utils::IS_WAYLAND_DISPLAY) { m_followMouseTimer->setInterval(16); connect(m_followMouseTimer, &QTimer::timeout, this, &AppDragWidget::onFollowMouse); m_followMouseTimer->start(); QTimer::singleShot(0, this, &AppDragWidget::onFollowMouse); } } void AppDragWidget::execFinished() { if (!m_bDragDrop) return; dropHandler(QCursor::pos()); } void AppDragWidget::mouseMoveEvent(QMouseEvent *event) { QGraphicsView::mouseMoveEvent(event); // hide widget when receiving mouseMoveEvent because this means drag-and-drop has been finished if (m_goBackAnim->state() != QPropertyAnimation::State::Running && m_animGroup->state() != QParallelAnimationGroup::Running) { hide(); } } void AppDragWidget::dragEnterEvent(QDragEnterEvent *event) { event->accept(); m_bDragDrop = true; } void AppDragWidget::dragMoveEvent(QDragMoveEvent *event) { if (Utils::IS_WAYLAND_DISPLAY) return QGraphicsView::dragMoveEvent(event); if (m_bDragDrop) moveHandler(QCursor::pos()); } /**获取应用的左上角坐标 * @brief AppDragWidget::topleftPoint * @return 返回应用左上角坐标 */ const QPoint AppDragWidget::topleftPoint() const { QPoint p; const QWidget *w = this; do { p += w->pos(); w = qobject_cast(w->parent()); } while (w); return p; } /**拖动从任务栏移除应用时浮窗坐标 * @brief AppDragWidget::popupMarkPoint * @param pos 任务栏所在位置 * @return 拖动从任务栏移除应用时浮窗坐标 */ const QPoint AppDragWidget::popupMarkPoint(Dock::Position pos) { QPoint p(topleftPoint()); QRect r = rect(); //关闭特效,原本的图标设置小,然后隐藏,需要手动设置大小保证tips位置正确 if (!DWindowManagerHelper::instance()->hasComposite()) { r.setWidth(m_iconSize.width() + 3); r.setHeight(m_iconSize.height() + 3); } switch (pos) { case Top: p += QPoint(r.width() / 2, r.height()); break; case Bottom: if (!DWindowManagerHelper::instance()->hasComposite()) { p += QPoint(0 , -r.height() / 2); } else { p += QPoint(r.width() / 2, 0); } break; case Left: p += QPoint(r.width(), r.height() / 2); break; case Right: p += QPoint(0, r.height() / 2); break; } return p; } void AppDragWidget::dropEvent(QDropEvent *event) { if (Utils::IS_WAYLAND_DISPLAY) return dropEvent(event); m_followMouseTimer->stop(); dropHandler(QCursor::pos()); } void AppDragWidget::hideEvent(QHideEvent *event) { deleteLater(); if (Utils::IS_WAYLAND_DISPLAY) QGraphicsView::hideEvent(event); } void AppDragWidget::dropHandler(const QPoint &pos) { m_bDragDrop = false; if (canSplitWindow(pos)) { if (DWindowManagerHelper::instance()->hasComposite()) { showRemoveAnimation(); } else { hide(); } m_popupWindow->setVisible(false); Q_EMIT requestSplitWindow(splitPosition()); } else { if (DWindowManagerHelper::instance()->hasComposite()) { showGoBackAnimation(); } else { hide(); } } } void AppDragWidget::moveHandler(const QPoint &pos) { if (canSplitWindow(pos)) { QRect screenGeometry = splitGeometry(pos); if (screenGeometry.isValid() && screenGeometry != m_lastMouseGeometry) { qDebug() << "change area:" << screenGeometry; Q_EMIT requestChangedArea(screenGeometry); m_lastMouseGeometry = screenGeometry; } } } void AppDragWidget::moveCurrent(const QPoint &destPos) { if (DWindowManagerHelper::instance()->hasComposite()) { move(destPos.x() - width() / 2, destPos.y() - height() / 2); } else { // 窗口特效未开启时会隐藏m_object绘制的图标,移动的图标为QDrag绘制的图标 move(destPos.x(), destPos.y()); } } void AppDragWidget::setAppPixmap(const QPixmap &pix) { if (DWindowManagerHelper::instance()->hasComposite()) { // QSize(3, 3) to fix pixmap be cliped setFixedSize(pix.size() + QSize(3, 3)); } else { setFixedSize(QSize(10, 10)); } m_iconSize = pix.size(); m_object->setAppPixmap(pix); m_object->setTransformOriginPoint(pix.rect().center()); } void AppDragWidget::setDockInfo(Dock::Position dockPosition, const QRect &dockGeometry) { m_dockPosition = dockPosition; m_dockGeometry = dockGeometry; } void AppDragWidget::setOriginPos(const QPoint position) { m_originPoint = position; } void AppDragWidget::setPixmapOpacity(qreal opacity) { if (canSplitWindow(QCursor::pos())) { m_object->setOpacity(opacity); m_animOpacity->setStartValue(opacity); } else { m_object->setOpacity(1.0); m_animOpacity->setStartValue(1.0); } } void AppDragWidget::initAnimations() { m_animScale->setDuration(300); m_animScale->setStartValue(1.0); m_animScale->setEndValue(0.0); m_animRotation->setDuration(300); m_animRotation->setStartValue(0); m_animRotation->setEndValue(90); m_animOpacity->setDuration(300); m_animOpacity->setStartValue(1.0); m_animOpacity->setEndValue(0.0); m_animGroup->addAnimation(m_animScale); m_animGroup->addAnimation(m_animRotation); m_animGroup->addAnimation(m_animOpacity); connect(m_animGroup, &QParallelAnimationGroup::stateChanged, this, &AppDragWidget::onRemoveAnimationStateChanged); connect(m_goBackAnim, &QPropertyAnimation::finished, this, &AppDragWidget::hide); } /**显示移除动画 * @brief AppDragWidget::showRemoveAnimation */ void AppDragWidget::showRemoveAnimation() { if (m_animGroup->state() == QParallelAnimationGroup::Running) { m_animGroup->stop(); } m_object->resetProperty(); m_animGroup->start(); } /**显示放弃移除后的动画 * @brief AppDragWidget::showGoBackAnimation */ void AppDragWidget::showGoBackAnimation() { m_goBackAnim->setDuration(300); m_goBackAnim->setStartValue(pos()); m_goBackAnim->setEndValue(m_originPoint); m_goBackAnim->start(); } void AppDragWidget::onRemoveAnimationStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) { Q_UNUSED(oldState); if (newState == QAbstractAnimation::Stopped) { hide(); } } /** 判断应用区域图标是否被拖出任务栏 * @brief AppDragWidget::canSplitWindow * @return 返回true应用移出任务栏,false应用在任务栏内 */ bool AppDragWidget::canSplitWindow(const QPoint &pos) const { switch (m_dockPosition) { case Dock::Position::Left: if ((pos.x() > m_dockGeometry.topRight().x())) { return true; } break; case Dock::Position::Top: if ((pos.y() > m_dockGeometry.bottomLeft().y())) { return true; } break; case Dock::Position::Right: if ((m_dockGeometry.topLeft().x() > pos.x())) { return true; } break; case Dock::Position::Bottom: if ((m_dockGeometry.topLeft().y() > pos.y())) { return true; } break; } return false; } /** * @brief AppDragWidget::splitPosition * @return 1 左分屏;2 右分屏;5 左上;6 右上;9 左下;10 右下;15全屏。这些值是窗管给的 */ ScreenSpliter::SplitDirection AppDragWidget::splitPosition() const { QPoint pos = QCursor::pos(); QScreen *currentScreen = DisplayManager::instance()->screenAt(pos); if (!currentScreen) return ScreenSpliter::None; int xCenter = currentScreen->geometry().x() + currentScreen->size().width() / 2; // 1表示左分屏 if (pos.x() < xCenter) return ScreenSpliter::Left; // 2表示右分屏 if (pos.x() > xCenter) return ScreenSpliter::Right; return ScreenSpliter::None; } void AppDragWidget::adjustDesktopGeometry(QRect &rect) const { QRect rectGeometry = m_dockGeometry; rectGeometry.setWidth(rectGeometry.width() * qApp->devicePixelRatio()); rectGeometry.setHeight(rectGeometry.height() * qApp->devicePixelRatio()); switch (m_dockPosition) { case Dock::Position::Left: { int leftX = (rectGeometry.x() + rectGeometry.width()) * qApp->devicePixelRatio(); if (rect.x() < leftX) { rect.setX(leftX); rect.setWidth(rect.width() - (leftX - rect.x())); } break; } case Dock::Position::Top: { int topY = (rectGeometry.y() + rectGeometry.height()) * qApp->devicePixelRatio(); if (rect.y() < topY) { rect.setY(topY); rect.setHeight(rect.height() - (topY - rect.y())); } break; } case Dock::Position::Right: { int rightX = rectGeometry.x() * qApp->devicePixelRatio(); if (rightX < rect.x() + rect.width() * qApp->devicePixelRatio()) rect.setWidth(rect.width() - (rect.x() + rect.width() - rightX)); break; } case Dock::Position::Bottom: { int bottomY = rectGeometry.y() * qApp->devicePixelRatio(); if (bottomY < rect.y() + rect.height() * qApp->devicePixelRatio()) rect.setHeight(rect.height() - (rect.y() + rect.height() - bottomY)); break; } } } QRect AppDragWidget::splitGeometry(const QPoint &pos) const { QList screens = DisplayManager::instance()->screens(); for (QScreen *screen : screens) { QRect screenGeometry = screen->geometry(); screenGeometry.setWidth(screenGeometry.width() * qApp->devicePixelRatio()); screenGeometry.setHeight(screenGeometry.height() * qApp->devicePixelRatio()); if (!screenGeometry.contains(pos)) continue; // 左右分屏即可 int centerX = screenGeometry.x() + screenGeometry.width() / 2; if (pos.x() < centerX) { // 左分屏 QRect rectLeft = screenGeometry; rectLeft.setWidth(screenGeometry.width() / 2); adjustDesktopGeometry(rectLeft); return rectLeft; } if (pos.x() > centerX) { // 右分屏 QRect rectRight = screenGeometry; rectRight.setLeft(screenGeometry.x() + screenGeometry.width() / 2); rectRight.setWidth(screenGeometry.width() / 2); adjustDesktopGeometry(rectRight); return rectRight; } break; } return QRect(); } void AppDragWidget::initWaylandEnv() { if (!Utils::IS_WAYLAND_DISPLAY) return; // 由于在wayland环境下无法触发drop事件,导致鼠标无法释放,所以这里暂时用XEventMonitor的方式(具体原因待查) Dock::Position position = qApp->property(PROP_POSITION).value(); XEventMonitor *extralEventInter = new XEventMonitor(xEventMonitorService, xEventMonitorPath, QDBusConnection::sessionBus()); QList extralRectList; QList screens = DisplayManager::instance()->screens(); for (QScreen *screen : screens) { MonitRect monitorRect; QRect screenRect = screen->geometry(); screenRect.setSize(screenRect.size() * screen->devicePixelRatio()); switch (position) { case Top: { monitorRect.x1 = screenRect.x(); monitorRect.y1 = screenRect.y(); monitorRect.x2 = screenRect.x() + screenRect.width(); monitorRect.y2 = screenRect.y(); } break; case Bottom: { monitorRect.x1 = screenRect.x(); monitorRect.y1 = screenRect.y() + screenRect.height(); monitorRect.x2 = screenRect.x() + screenRect.width(); monitorRect.y2 = screenRect.y() + screenRect.height(); } break; case Left: { monitorRect.x1 = screenRect.x(); monitorRect.y1 = screenRect.y(); monitorRect.x2 = screenRect.x(); monitorRect.y2 = screenRect.y() + screenRect.height(); } break; case Right: { monitorRect.x1 = screenRect.x() + screenRect.width(); monitorRect.y1 = screenRect.y(); monitorRect.x2 = screenRect.x() + screenRect.width(); monitorRect.y2 = screenRect.y() + screenRect.height(); } break; } if (!extralRectList.contains(monitorRect)) extralRectList << monitorRect; } QString key = extralEventInter->RegisterAreas(extralRectList, 1 << 1); connect(this, &AppDragWidget::destroyed, this, [ key, extralEventInter ] { extralEventInter->UnregisterArea(key); delete extralEventInter; QDrag::cancel(); }); connect(extralEventInter, &XEventMonitor::ButtonRelease, this, &AppDragWidget::onButtonRelease); connect(extralEventInter, &XEventMonitor::CursorMove,this, &AppDragWidget::onCursorMove); } void AppDragWidget::onButtonRelease(int, int x, int y, const QString &) { if (!m_bDragDrop) return; dropHandler(QPoint(x, y)); QDrag::cancel(); } void AppDragWidget::onCursorMove(int x, int y, const QString &) { QPoint pos = QPoint(x, y); moveCurrent(pos); moveHandler(pos); } void AppDragWidget::onFollowMouse() { moveCurrent(QCursor::pos()); } void AppDragWidget::enterEvent(QEvent *event) { Q_UNUSED(event); if (m_goBackAnim->state() != QPropertyAnimation::State::Running && m_animGroup->state() != QParallelAnimationGroup::Running) { hide(); } } QuickDragWidget::QuickDragWidget(QWidget *parent) : AppDragWidget(parent) { } QuickDragWidget::~QuickDragWidget() { } void QuickDragWidget::dropEvent(QDropEvent *event) { Q_UNUSED(event); m_followMouseTimer->stop(); m_bDragDrop = false; if (isRemoveAble(QCursor::pos())) { if (DWindowManagerHelper::instance()->hasComposite()) showRemoveAnimation(); else hide(); m_popupWindow->setVisible(false); } else { if (DWindowManagerHelper::instance()->hasComposite()) showGoBackAnimation(); else hide(); Q_EMIT requestDropItem(event); } } void QuickDragWidget::dragMoveEvent(QDragMoveEvent *event) { AppDragWidget::dragMoveEvent(event); requestDragMove(event); } /**判断图标拖到一定高度(默认任务栏高度的1.5倍)后是否可以移除 * @brief AppDragWidget::isRemoveAble * @param curPos 当前鼠标所在位置 * @return 返回true可移除,false不可移除 */ bool QuickDragWidget::isRemoveAble(const QPoint &curPos) { const QPoint &p = curPos; switch (m_dockPosition) { case Dock::Position::Left: if ((p.x() - m_dockGeometry.topRight().x()) > (m_dockGeometry.width() * m_distanceMultiple)) { return true; } break; case Dock::Position::Top: if ((p.y() - m_dockGeometry.bottomLeft().y()) > (m_dockGeometry.height() * m_distanceMultiple)) { return true; } break; case Dock::Position::Right: if ((m_dockGeometry.topLeft().x() - p.x()) > (m_dockGeometry.width() * m_distanceMultiple)) { return true; } break; case Dock::Position::Bottom: if ((m_dockGeometry.topLeft().y() - p.y()) > (m_dockGeometry.height() * m_distanceMultiple)) { return true; } break; } return false; }