dde-dock/frame/panel/mainpanel.cpp
listenerri 725891a6b1 fix: place holder item still display in dock after drag leave
Change-Id: Ibc21b1f984a4ecc1bff466012c2afe05c5df1285
2018-11-05 11:26:20 +08:00

692 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2011 ~ 2018 Deepin Technology Co., Ltd.
*
* Author: sbw <sbw@sbw.so>
*
* Maintainer: sbw <sbw@sbw.so>
* listenerri <listenerri@gmail.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/>.
*/
#include "mainpanel.h"
#include "item/appitem.h"
#include <QBoxLayout>
#include <QDragEnterEvent>
#include <QApplication>
#include <QScreen>
#include <QGraphicsView>
#include <window/mainwindow.h>
static DockItem *DraggingItem = nullptr;
static PlaceholderItem *RequestDockItem = nullptr;
const char *RequestDockKey = "RequestDock";
const char *RequestDockKeyFallback = "text/plain";
const char *DesktopMimeType = "application/x-desktop";
MainPanel::MainPanel(QWidget *parent)
: DBlurEffectWidget(parent),
m_position(Dock::Top),
m_displayMode(Dock::Fashion),
m_itemLayout(new QBoxLayout(QBoxLayout::LeftToRight)),
m_showDesktopItem(new ShowDesktopItem(this)),
m_itemAdjustTimer(new QTimer(this)),
m_checkMouseLeaveTimer(new QTimer(this)),
m_itemController(DockItemController::instance(this)),
m_appDragWidget(nullptr)
{
m_itemLayout->setSpacing(0);
m_itemLayout->setContentsMargins(0, 0, 0, 0);
setBlurRectXRadius(0);
setBlurRectYRadius(0);
setBlendMode(BehindWindowBlend);
setAcceptDrops(true);
setAccessibleName("dock-mainpanel");
setObjectName("MainPanel");
setMouseTracking(true);
QFile qssFile(":/qss/frame.qss");
qssFile.open(QFile::ReadOnly);
if(qssFile.isOpen()) {
setStyleSheet(qssFile.readAll());
qssFile.close();
}
connect(m_itemController, &DockItemController::itemInserted, this, &MainPanel::itemInserted, Qt::DirectConnection);
connect(m_itemController, &DockItemController::itemRemoved, this, &MainPanel::itemRemoved, Qt::DirectConnection);
connect(m_itemController, &DockItemController::itemMoved, this, &MainPanel::itemMoved);
connect(m_itemController, &DockItemController::itemManaged, this, &MainPanel::manageItem);
connect(m_itemController, &DockItemController::itemUpdated, m_itemAdjustTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
connect(m_itemAdjustTimer, &QTimer::timeout, this, &MainPanel::adjustItemSize, Qt::QueuedConnection);
connect(m_checkMouseLeaveTimer, &QTimer::timeout, this, &MainPanel::checkMouseReallyLeave, Qt::QueuedConnection);
connect(&DockSettings::Instance(), &DockSettings::opacityChanged, this, &MainPanel::setMaskAlpha);
m_itemAdjustTimer->setSingleShot(true);
m_itemAdjustTimer->setInterval(100);
m_checkMouseLeaveTimer->setSingleShot(true);
m_checkMouseLeaveTimer->setInterval(300);
const auto &itemList = m_itemController->itemList();
for (auto item : itemList)
{
manageItem(item);
m_itemLayout->addWidget(item);
}
m_showDesktopItem->setFixedSize(10, height());
m_itemLayout->addWidget(m_showDesktopItem);
setLayout(m_itemLayout);
}
MainPanel::~MainPanel() { }
///
/// \brief MainPanel::updateDockPosition change panel layout with spec position.
/// \param dockPosition
///
void MainPanel::updateDockPosition(const Position dockPosition)
{
m_position = dockPosition;
switch (m_position)
{
case Position::Top:
case Position::Bottom:
m_itemLayout->setDirection(QBoxLayout::LeftToRight);
m_showDesktopItem->setFixedSize(10, height());
break;
case Position::Left:
case Position::Right:
m_itemLayout->setDirection(QBoxLayout::TopToBottom);
m_showDesktopItem->setFixedSize(width(), 10);
break;
}
m_itemAdjustTimer->start();
}
///
/// \brief MainPanel::updateDockDisplayMode change panel style with spec mode.
/// \param displayMode
///
void MainPanel::updateDockDisplayMode(const DisplayMode displayMode)
{
m_displayMode = displayMode;
m_showDesktopItem->setVisible(displayMode == Dock::Efficient);
const auto &itemList = m_itemController->itemList();
for (auto item : itemList)
{
// we need to hide container item at fashion mode.
switch (item->itemType())
{
case DockItem::Container:
item->setVisible(displayMode == Dock::Efficient);
break;
default:;
}
}
// reload qss
setStyleSheet(styleSheet());
}
///
/// \brief MainPanel::displayMode interface for Q_PROPERTY, never use this func.
/// \return
///
int MainPanel::displayMode() const
{
return int(m_displayMode);
}
///
/// \brief MainPanel::position interface for Q_PROPERTY, never use this func.
/// \return
///
int MainPanel::position() const
{
return int(m_position);
}
void MainPanel::setEffectEnabled(const bool enabled)
{
if (enabled)
setMaskColor(DarkColor);
else
setMaskColor(QColor(55, 63, 71));
setMaskAlpha(DockSettings::Instance().Opacity());
m_itemAdjustTimer->start();
}
bool MainPanel::eventFilter(QObject *watched, QEvent *event)
{
if (watched == static_cast<QGraphicsView *>(m_appDragWidget)->viewport()) {
QDropEvent *e = static_cast<QDropEvent *>(event);
bool isContains = rect().contains(mapFromGlobal(m_appDragWidget->mapToGlobal(e->pos())));
if (isContains) {
if (event->type() == QEvent::DragMove) {
handleDragMove(static_cast<QDragMoveEvent *>(event), true);
} else if (event->type() == QEvent::Drop) {
m_appDragWidget->hide();
return true;
}
}
}
return false;
}
void MainPanel::moveEvent(QMoveEvent* e)
{
DBlurEffectWidget::moveEvent(e);
QTimer::singleShot(500, this, &MainPanel::geometryChanged);
}
void MainPanel::resizeEvent(QResizeEvent *e)
{
DBlurEffectWidget::resizeEvent(e);
m_itemAdjustTimer->start();
// m_effectWidget->resize(e->size());
QTimer::singleShot(500, this, &MainPanel::geometryChanged);
}
void MainPanel::dragEnterEvent(QDragEnterEvent *e)
{
// 不知道为什么有可能会收不到dragLeaveEvent因此使用timer来检测鼠标是否已经离开dock
m_checkMouseLeaveTimer->start();
// call dragEnterEvent of MainWindow to show dock when dock is hidden
static_cast<MainWindow *>(window())->dragEnterEvent(e);
DockItem *item = itemAt(e->pos());
if (item && item->itemType() == DockItem::Container)
return;
DockItem *dragSourceItem = qobject_cast<DockItem *>(e->source());
if (dragSourceItem)
{
e->accept();
if (DraggingItem)
DraggingItem->show();
return;
} else {
DraggingItem = nullptr;
}
m_draggingMimeKey = e->mimeData()->formats().contains(RequestDockKey) ? RequestDockKey : RequestDockKeyFallback;
// dragging item is NOT a desktop file
if (QMimeDatabase().mimeTypeForFile(e->mimeData()->data(m_draggingMimeKey)).name() != DesktopMimeType) {
m_draggingMimeKey.clear();
return;
}
// dragging item has been docked
if (m_itemController->appIsOnDock(e->mimeData()->data(m_draggingMimeKey))) {
m_draggingMimeKey.clear();
return;
}
e->accept();
}
void MainPanel::dragMoveEvent(QDragMoveEvent *e)
{
handleDragMove(e, false);
}
void MainPanel::dragLeaveEvent(QDragLeaveEvent *e)
{
Q_UNUSED(e)
if (RequestDockItem)
{
const QRect r(static_cast<QWidget *>(parent())->pos(), size());
const QPoint p(QCursor::pos());
if (r.contains(p))
return;
m_itemController->placeholderItemRemoved(RequestDockItem);
RequestDockItem->deleteLater();
RequestDockItem = nullptr;
}
if (DraggingItem) {
DockItem::ItemType type = DraggingItem->itemType();
if (type != DockItem::Plugins && type != DockItem::SystemTrayPlugin)
DraggingItem->hide();
}
}
void MainPanel::dropEvent(QDropEvent *e)
{
DraggingItem = nullptr;
if (RequestDockItem)
{
m_itemController->placeholderItemDocked(e->mimeData()->data(m_draggingMimeKey), RequestDockItem);
m_itemController->placeholderItemRemoved(RequestDockItem);
RequestDockItem->deleteLater();
RequestDockItem = nullptr;
}
}
///
/// \brief MainPanel::manageItem manage a dock item, all dock item should be managed after construct.
/// \param item
///
void MainPanel::manageItem(DockItem *item)
{
connect(item, &DockItem::dragStarted, this, &MainPanel::itemDragStarted, Qt::UniqueConnection);
connect(item, &DockItem::itemDropped, this, &MainPanel::itemDropped, Qt::UniqueConnection);
connect(item, &DockItem::requestRefershWindowVisible, this, &MainPanel::requestRefershWindowVisible, Qt::UniqueConnection);
connect(item, &DockItem::requestWindowAutoHide, this, &MainPanel::requestWindowAutoHide, Qt::UniqueConnection);
}
///
/// \brief MainPanel::itemAt get a dock item which placed at spec point,
/// \param point
/// \return if no item at spec point, return nullptr
///
DockItem *MainPanel::itemAt(const QPoint &point)
{
const auto &itemList = m_itemController->itemList();
for (auto item : itemList)
{
if (!item->isVisible())
continue;
QRect rect;
rect.setTopLeft(item->pos());
rect.setSize(item->size());
if (rect.contains(point))
return item;
}
return nullptr;
}
///
/// \brief MainPanel::adjustItemSize adjust all dock item size to fit panel size,
/// for optimize cpu usage, DO NOT call this func immediately, you should use m_itemAdjustTimer
/// to delay do this operate.
///
void MainPanel::adjustItemSize()
{
Q_ASSERT(sender() == m_itemAdjustTimer);
// ensure all item is update, whatever layout is changed
QTimer::singleShot(1, this, static_cast<void (MainPanel::*)()>(&MainPanel::update));
const auto ratio = devicePixelRatioF();
QSize itemSize;
switch (m_position)
{
case Top:
case Bottom:
itemSize.setHeight(height() - PANEL_BORDER);
itemSize.setWidth(std::round(qreal(AppItem::itemBaseWidth()) / ratio));
break;
case Left:
case Right:
itemSize.setHeight(std::round(qreal(AppItem::itemBaseHeight()) / ratio));
itemSize.setWidth(width() - PANEL_BORDER);
break;
default:
Q_ASSERT(false);
}
if (itemSize.height() < 0 || itemSize.width() < 0)
return;
int totalAppItemCount = 0;
int totalWidth = 0;
int totalHeight = 0;
const auto &itemList = m_itemController->itemList();
const QSize &fashionSystemTraySize = DockSettings::Instance().fashionSystemTraySize();
for (auto item : itemList)
{
const auto itemType = item->itemType();
if (m_itemController->itemIsInContainer(item))
continue;
if (m_displayMode == Fashion &&
itemType == DockItem::Container)
continue;
QMetaObject::invokeMethod(item, "setVisible", Qt::QueuedConnection, Q_ARG(bool, true));
switch (item->itemType())
{
case DockItem::Placeholder:
case DockItem::App:
case DockItem::Launcher:
item->setFixedSize(itemSize);
++totalAppItemCount;
totalWidth += itemSize.width();
totalHeight += itemSize.height();
break;
case DockItem::Plugins:
case DockItem::SystemTrayPlugin:
if (m_displayMode == Fashion) {
// 特殊处理时尚模式下的托盘插件
if (item->itemType() == DockItem::SystemTrayPlugin) {
if (m_position == Dock::Top || m_position == Dock::Bottom) {
item->setFixedWidth(fashionSystemTraySize.width());
item->setFixedHeight(itemSize.height());
totalWidth += fashionSystemTraySize.width();
totalHeight += itemSize.height();
} else {
item->setFixedWidth(itemSize.width());
item->setFixedHeight(fashionSystemTraySize.height());
totalWidth += itemSize.width();
totalHeight += fashionSystemTraySize.height();
}
} else {
item->setFixedSize(itemSize);
totalWidth += itemSize.width();
totalHeight += itemSize.height();
++totalAppItemCount;
}
}
else
{
const QSize size = item->sizeHint();
item->setFixedSize(size);
if (m_position == Dock::Top || m_position == Dock::Bottom)
item->setFixedHeight(itemSize.height());
else
item->setFixedWidth(itemSize.width());
totalWidth += size.width();
totalHeight += size.height();
}
break;
case DockItem::Container:
{
const QSize size = item->sizeHint();
item->setFixedSize(size);
if (m_position == Dock::Top || m_position == Dock::Bottom)
item->setFixedHeight(itemSize.height());
else
item->setFixedWidth(itemSize.width());
totalWidth += size.width();
totalHeight += size.height();
}
break;
case DockItem::Stretch:
break;
default:
Q_UNREACHABLE();
}
}
const int w = width() - PANEL_BORDER * 2 - PANEL_PADDING * 2 - PANEL_MARGIN * 2;
const int h = height() - PANEL_BORDER * 2 - PANEL_PADDING * 2 - PANEL_MARGIN * 2;
// test if panel can display all items completely
bool containsCompletely = false;
switch (m_position)
{
case Dock::Top:
case Dock::Bottom:
containsCompletely = totalWidth <= w; break;
case Dock::Left:
case Dock::Right:
containsCompletely = totalHeight <= h; break;
default:
Q_ASSERT(false);
}
// abort adjust.
if (containsCompletely)
return;
// now, we need to decrease item size to fit panel size
int overflow;
int base;
if (m_position == Dock::Top || m_position == Dock::Bottom)
{
overflow = totalWidth;
base = w;
}
else
{
overflow = totalHeight;
base = h;
}
const int decrease = double(overflow - base) / totalAppItemCount;
int extraDecrease = overflow - base - decrease * totalAppItemCount;
for (auto item : itemList)
{
const DockItem::ItemType itemType = item->itemType();
if (itemType == DockItem::Stretch || itemType == DockItem::Container)
continue;
if (itemType == DockItem::Plugins)
{
if (m_displayMode != Dock::Fashion)
continue;
if (m_itemController->itemIsInContainer(item))
continue;
}
if (itemType == DockItem::SystemTrayPlugin) {
continue;
}
switch (m_position)
{
case Dock::Top:
case Dock::Bottom:
item->setFixedWidth(item->width() - decrease - bool(extraDecrease));
break;
case Dock::Left:
case Dock::Right:
item->setFixedHeight(item->height() - decrease - bool(extraDecrease));
break;
}
if (extraDecrease)
--extraDecrease;
}
// ensure all extra space assigned
Q_ASSERT(extraDecrease == 0);
}
///
/// \brief MainPanel::itemInserted insert dock item into index position.
/// the new inserted item will be hideen first, and then shown after size
/// adjust finished.
/// \param index
/// \param item
///
void MainPanel::itemInserted(const int index, DockItem *item)
{
// hide new item, display it after size adjust finished
item->setVisible(false);
item->setParent(this);
manageItem(item);
m_itemLayout->insertWidget(index, item);
m_itemAdjustTimer->start();
}
///
/// \brief MainPanel::itemRemoved take out spec item from panel, this function
/// will NOT delete item, and NOT disconnect any signals between item and panel.
/// \param item
///
void MainPanel::itemRemoved(DockItem *item)
{
m_itemLayout->removeWidget(item);
m_itemAdjustTimer->start();
}
///
/// \brief MainPanel::itemMoved move item to spec index.
/// the index is start from 0 and counted before remove spec item.
/// \param item
/// \param index
///
void MainPanel::itemMoved(DockItem *item, const int index)
{
// remove old item
m_itemLayout->removeWidget(item);
// insert new position
m_itemLayout->insertWidget(index, item);
}
///
/// \brief MainPanel::itemDragStarted handle managed item dragging
///
void MainPanel::itemDragStarted()
{
DraggingItem = qobject_cast<DockItem *>(sender());
DockItem::ItemType draggingTyep = DraggingItem->itemType();
if (draggingTyep == DockItem::App)
{
AppItem *appItem = qobject_cast<AppItem *>(DraggingItem);
m_appDragWidget = appItem->appDragWidget();
appItem->setDockInfo(m_position, QRect(mapToGlobal(pos()), size()));
static_cast<QGraphicsView *>(m_appDragWidget)->viewport()->installEventFilter(this);
}
if (draggingTyep == DockItem::Plugins || draggingTyep == DockItem::SystemTrayPlugin)
{
if (static_cast<PluginsItem *>(DraggingItem)->allowContainer())
{
qobject_cast<PluginsItem *>(DraggingItem)->hidePopup();
m_itemController->setDropping(true);
}
}
QRect rect;
rect.setTopLeft(mapToGlobal(pos()));
rect.setSize(size());
DraggingItem->setVisible(rect.contains(QCursor::pos()));
}
///
/// \brief MainPanel::itemDropped handle managed item dropped.
/// \param destnation
///
void MainPanel::itemDropped(QObject *destnation)
{
m_itemController->setDropping(false);
DockItem *src = qobject_cast<DockItem *>(sender());
// DockItem *dst = qobject_cast<DockItem *>(destnation);
if (m_displayMode == Dock::Fashion)
return;
if (!src)
return;
const bool itemIsInContainer = m_itemController->itemIsInContainer(src);
// drag from container
if (itemIsInContainer
&& (src->itemType() == DockItem::Plugins || src->itemType() == DockItem::SystemTrayPlugin)
&& destnation == this)
m_itemController->itemDragOutFromContainer(src);
// drop to container
if (!itemIsInContainer && src->parent() == this && destnation != this)
m_itemController->itemDroppedIntoContainer(src);
m_itemAdjustTimer->start();
}
void MainPanel::handleDragMove(QDragMoveEvent *e, bool isFilter)
{
e->accept();
DockItem *dst = itemAt(isFilter ? mapFromGlobal(m_appDragWidget->mapToGlobal(e->pos())) : e->pos());
if (!dst)
return;
// internal drag swap
if (e->source())
{
if (dst == DraggingItem)
return;
if (!DraggingItem)
return;
if (m_itemController->itemIsInContainer(DraggingItem))
return;
m_itemController->itemMove(DraggingItem, dst);
} else {
DraggingItem = nullptr;
if (!RequestDockItem)
{
DockItem *insertPositionItem = itemAt(e->pos());
if (!insertPositionItem)
return;
const auto type = insertPositionItem->itemType();
if (type != DockItem::App && type != DockItem::Stretch)
return;
RequestDockItem = new PlaceholderItem;
m_itemController->placeholderItemAdded(RequestDockItem, insertPositionItem);
} else {
if (dst == RequestDockItem)
return;
m_itemController->itemMove(RequestDockItem, dst);
}
}
}
void MainPanel::checkMouseReallyLeave()
{
if (window()->geometry().contains(QCursor::pos())) {
return m_checkMouseLeaveTimer->start();
}
m_checkMouseLeaveTimer->stop();
dragLeaveEvent(new QDragLeaveEvent);
}