logplus/Workflow/WFWidget/src/PaiTabWidget.cpp

592 lines
21 KiB
C++
Raw Normal View History

2026-01-16 17:18:41 +08:00
/**
* @file PaiTabWidget.cpp
* @date 2011-07-19
*/
#include <QApplication>
#include <QStyle>
#include <QPainter>
#include <QStyleOption>
#include <QMouseEvent>
#include <QByteArray>
#include <QDataStream>
#include <QToolTip>
#include <QStylePainter>
#include <QDrag>
#include <QMimeData>
#include "PaiTabWidget.h"
#include "PaiWorkspace.h"
// #include "Log.h"
#include "PaiWindow.h"
#include "GlobalUtility.h"
using namespace pai::gui;
void pai::gui::Serialize(const DragState & dragState, QByteArray & blob)
{
QDataStream stream(&blob, QIODevice::WriteOnly);
stream << QString("PAIDragState:"); // 二进制识别头
stream << dragState.isValid;
stream << dragState.isDragging;
stream << reinterpret_cast< qint64 > (dragState.pDraggingWidget);
stream << dragState.tabIcon;
stream << dragState.tabText;
stream << dragState.pressPosition;
stream << dragState.srcRect;
stream << dragState.appPid;
}
void pai::gui::Unserialize(DragState & dragState, QByteArray & blob)
{
QDataStream stream(&blob, QIODevice::ReadOnly);
QString strIdent;
stream >> strIdent;
if(strIdent != "PAIDragState:") // 二进制识别头验证
{
// pai::log::Debug(_FLF(QObject::tr("Unrecognized dragging state!").toStdString()));
return;
}
stream >> dragState.isValid;
stream >> dragState.isDragging;
qint64 pointer;
stream >> pointer;
dragState.pDraggingWidget = reinterpret_cast< QWidget* > (pointer);
stream >> dragState.tabIcon;
stream >> dragState.tabText;
stream >> dragState.pressPosition;
stream >> dragState.srcRect;
stream >> dragState.appPid;
}
PaiInfoTabBar::PaiInfoTabBar(QWidget *pParent, bool isSubTabBar, SelectionBehavior behavior) :
QTabBar(pParent),
m_draggable(true),
m_OutOfConsoleTimer(0)
{
setFocusPolicy(Qt::StrongFocus); // 保证LineEdit的editingFinished先执行
setMouseTracking(true);
setElideMode(Qt::ElideRight);
InitDragState(); // 由于setMouseTracking为true,所以mouseMoveEvent会无条件进入需要对该结构初始化
m_IsSubTabwidget = isSubTabBar;
setProperty("isSubTabBar", isSubTabBar);
setSelectionBehaviorOnRemove(behavior);
}
void PaiInfoTabBar::SetDraggable(bool draggable)
{
m_draggable = draggable;
}
//该重绘函数主要解决在未拖动的情况的绘制其中style sheet中
//margin: 0px 0px 1px 5px; padding-right:0px; padding-left:5px;必需设置,
//否则因系统会因为大约5像素的偏差而导致文字的...(elide)被设置错误在拖动的情况下则完全由QSS来决定。
void PaiInfoTabBar::paintEvent(QPaintEvent *pEvent)
{
QTabBar::paintEvent(pEvent);
QLinearGradient normalLG(QPointF(0, 0), QPointF(0, height()));
QLinearGradient selectedLG(QPointF(0, 0), QPointF(0, height()));
QLinearGradient hoverLG(QPointF(0, 0), QPointF(0, height()));
QLinearGradient shLG(QPointF(0, 0), QPointF(0, height()));
if(m_IsSubTabwidget)
{
normalLG.setColorAt(1.0, QColor("#D6E4F3"));
selectedLG.setColorAt(0.0, QColor("#E7F0F8"));
selectedLG.setColorAt(1.0, QColor("#FFFFFF"));
hoverLG.setColorAt(1.0, QColor("#F2F7FB"));
shLG.setColorAt(1.00, QColor("#F2F7FB"));
}
else
{
normalLG.setColorAt(0.0, QColor("#ECF3FC"));
normalLG.setColorAt(1.0, QColor("#DFEAF9"));
selectedLG.setColorAt(0.0, QColor("#FEF6D1"));
selectedLG.setColorAt(1.0, QColor("#FDE88C"));
hoverLG.setColorAt(0.0, QColor("#FFFFFF"));
hoverLG.setColorAt(1.0, QColor("#E1ECFA"));
shLG.setColorAt(0.00, QColor("#FFFBEB"));
shLG.setColorAt(0.49, QColor("#FEF7D8"));
shLG.setColorAt(0.50, QColor("#FEF4C2"));
shLG.setColorAt(1.00, QColor("#FDEB99"));
}
QLinearGradient grayLG(QPointF(0, 0), QPointF(0, height()));
grayLG.setColorAt(0.0, QColor("#E8F1F9"));
grayLG.setColorAt(1.0, QColor("#E8F1F9"));
QLinearGradient errorLG(QPointF(0, 0), QPointF(0, height()));
errorLG.setColorAt(0.0, QColor(255, 255, 255));
errorLG.setColorAt(1.0, QColor(255, 0, 0));
QPainter painter(this);
for(int i = 0; i < count(); i++)
{
QStyleOptionTab opt;
initStyleOption(&opt, i);
QRect rt = opt.rect.adjusted(0, 0, -2, 0); // tab 页之间预留2像素间距
QRect contentRect = rt.adjusted(5, 0, tabsClosable() ? -15 : -5, 0); // 内容区 margin-left:5px, margin-right:15px因右侧有关闭按钮
painter.setPen(QColor("#677B8E"));
if(opt.text == "Error") // 错误模式
{
painter.setBrush(QBrush(errorLG));
}
else if((opt.state & QStyle::State_Selected) && (opt.state & QStyle::State_MouseOver)) // 选中并且鼠标在上面
{
painter.setBrush(QBrush(shLG));
}
else if(opt.state & QStyle::State_Selected) // 仅仅选中
{
painter.setBrush(QBrush(selectedLG));
}
else if(opt.state & QStyle::State_MouseOver) // 鼠标在上面,没有选中
{
painter.setBrush(QBrush(hoverLG));
}
else // 其它情况
{
painter.setBrush(QBrush(normalLG));
}
if(!isTabEnabled(i))
{
painter.setBrush(QBrush(grayLG));
}
if((opt.shape == QTabBar::RoundedNorth) || (opt.shape == QTabBar::TriangularNorth)) // 页签在上面
{
painter.drawRoundedRect(rt.adjusted(0, 0, 0, 3), 3, 3); // 矩形留3px圆角为使底部无圆角向下拉大3px使下部隐藏
}
else if((opt.shape == QTabBar::RoundedSouth) || (opt.shape == QTabBar::TriangularSouth)) // 页签在下面
{
painter.drawRoundedRect(rt.adjusted(0, -3, 0, -1), 3, 3); // 矩形留3px圆角为使顶部无圆角向下拉大3px使顶部隐藏为了保证矩形底部直线可以显示向下移动1px
}
else
{
painter.drawRect(rt); // 其它情况
}
if(opt.icon.isNull()) // 没有图标
{
QString text = opt.fontMetrics.elidedText(opt.text, Qt::ElideRight, contentRect.width());
if(isTabEnabled(i))
{
painter.setPen(QColor(Qt::black));
}
painter.drawText(contentRect, Qt::AlignLeft | Qt::AlignVCenter, text);
}
else // 有图标
{
QString text = opt.fontMetrics.elidedText(opt.text, Qt::ElideRight, contentRect.width() - 20);
if(isTabEnabled(i))
{
painter.setPen(QColor(Qt::black));
}
painter.drawText(contentRect.adjusted(20, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, text); // 有图标时左侧预留20像素绘制图标
tabIcon(i).paint(&painter, contentRect, Qt::AlignLeft | Qt::AlignVCenter);
}
}
}
void PaiInfoTabBar::InitDragState()
{
m_DragState.appPid = QCoreApplication::applicationPid();
m_DragState.isValid = false;
m_DragState.isDragging = false;
m_DragState.pDraggingWidget = NULL;
m_DragState.tabIcon = QIcon();
m_DragState.tabText = "";
m_DragState.pressPosition = QPoint(0, 0);
}
bool PaiInfoTabBar::IsDraggable() const
{
if(!m_draggable)
{
return false;
}
return ((NULL != GetFirstParentObject< pai::gui::PaiWorkspace > (const_cast< PaiInfoTabBar* > (this)))
&& tabsClosable());
}
void PaiInfoTabBar::mousePressEvent(QMouseEvent *pEvent)
{
InitDragState();
if(pEvent->button() == Qt::LeftButton)
{
if(IsDraggable())
{
for(int i = 0; i < count(); ++i)
{
if(tabButton(i, RightSide) != NULL)
{
// 禁止从关闭按钮上发起拖拽
if(tabButton(i, RightSide)->geometry().contains(pEvent->pos()))
{
break;
}
}
if(tabRect(i).contains(pEvent->pos()))
{
QTabWidget *pTabWidget = dynamic_cast< QTabWidget* > (parentWidget());
if(pTabWidget != NULL)
{
m_DragState.pDraggingWidget = pTabWidget->widget(i);
m_DragState.tabIcon = tabIcon(i);
m_DragState.tabText = tabText(i);
}
break;
}
}
if(m_DragState.pDraggingWidget != NULL)
{
m_DragState.isValid = true;
m_DragState.pressPosition = pEvent->pos();
}
}
}
QTabBar::mousePressEvent(pEvent);
}
void PaiInfoTabBar::mouseMoveEvent(QMouseEvent *pEvent)
{
if(m_DragState.isValid)
{
QPoint pos = (pEvent->pos() - m_DragState.pressPosition);
//只有在以下条件满足时才修改拖拽状态,视为拖拽开始
//1.本身还未记录为拖拽状态
//2.鼠标按压状态下移动的布线长度大于系统设定的拖拽起始长度
//3.鼠标拖动轨迹大于30度角0.71约为30度角的直角边长之比如果
// 小于30度则认为其是移动页签而不是拖拽页签。注*:如果取消这个条
// 件,在小角度快速频繁按压移动鼠标时会误认为是拖拽,造成绘制混乱)
if((!m_DragState.isDragging)
&& (pos.manhattanLength() > QApplication::startDragDistance())
&& (qAbs(pos.y()) / (float) qAbs(pos.x())) > 0.71)
{
m_DragState.isDragging = true;
m_DragState.srcRect = parentWidget()->geometry();
}
if(m_DragState.isDragging)
{
if(!MapToGlobal(this, rect()).contains(pEvent->globalPos()))
{
QDrag *pDrag = new QDrag(this);
QPixmap pixmap(m_DragState.srcRect.size());
pixmap.fill(QColor(0, 0, 0, 0));
QStylePainter p(&pixmap, this);
QStyleOptionTabV3 tab;
initStyleOption(&tab, this->currentIndex());
p.drawControl(QStyle::CE_TabBarTab, tab);
m_DragState.pDraggingWidget->render(&pixmap, QPoint(0, this->height()));
pDrag->setPixmap(pixmap);
pDrag->setHotSpot(QPoint(rect().width() / 2, rect().height() / 2));
QMimeData *pMimeData = new QMimeData;
QByteArray blob;
Serialize(m_DragState, blob);
pMimeData->setData("text/csv", blob);
pDrag->setMimeData(pMimeData);
killTimer(m_OutOfConsoleTimer);
m_OutOfConsoleTimer = startTimer(150);
Qt::DropAction eDropAction = pDrag->exec(Qt::MoveAction | Qt::IgnoreAction, Qt::IgnoreAction);
killTimer(m_OutOfConsoleTimer);
qApp->restoreOverrideCursor();
// Note:为拖拽到类似firefox的页面的情况做特殊处理当拖拽到firefox页面上时底层返回Action认为是MoveAction这时的targe返回为空
// 如果是拖拽到前面拖出的窗口中返回的targe会是一个PaiWorkspace。
if(((eDropAction == Qt::IgnoreAction)
&& !window()->geometry().contains(QCursor::pos()))
|| ((eDropAction == Qt::MoveAction)
&& (pDrag->target() == NULL)
&& !window()->geometry().contains(QCursor::pos())))
{
PaiTabWidget *pTabWidget = dynamic_cast< PaiTabWidget* > (parentWidget());
if(pTabWidget != NULL)
{
// 将拖拽的页签先从之前的页签区域移除
int iDraggingIndex = pTabWidget->indexOf(m_DragState.pDraggingWidget);
QIcon icon = tabIcon(iDraggingIndex);
QString text = tabText(iDraggingIndex);
PaiWindow *pParent = GetLastParentObject< PaiWindow > (this);
PaiWindow *pNewWindow = new PaiWindow(pParent);
pai::gui::PaiWorkspace::RemoveViewFromTabWidget(m_DragState.pDraggingWidget, pTabWidget);
pai::gui::PaiWorkspace *pNewWorkspace = new pai::gui::PaiWorkspace(pNewWindow);
connect(pNewWorkspace, SIGNAL(SubtitleChanged()), pParent, SLOT(RefreshWindowTitle()));
pNewWorkspace->setAttribute(Qt::WA_DeleteOnClose);
pNewWorkspace->setWindowIcon(QIcon(":/Logo.png"));
if(!m_DragState.srcRect.isValid())
{
return;
}
// 将拖拽的页签添加到拖出来的新窗口
pNewWorkspace->AddWidget("#", m_DragState.pDraggingWidget, text, icon);
pNewWindow->setAttribute(Qt::WA_DeleteOnClose);
pNewWindow->setGeometry(QCursor::pos().x() - m_DragState.srcRect.width() / 2,
QCursor::pos().y() - 5,
m_DragState.srcRect.width(),
m_DragState.srcRect.height());
QHBoxLayout *pNewWindowLayout = new QHBoxLayout(pNewWindow->GetContainer());
pNewWindowLayout->setContentsMargins(0, 5, 0, 0);
pNewWindowLayout->addWidget(pNewWorkspace);
pNewWindow->setVisible(true);
pNewWindow->raise();
InitDragState();
pEvent->ignore();
return;
}
}
else
{
// 由于拖拽的页面可能落在其他地方,所以拖拽源需要刷新一下
parentWidget()->repaint();
// 由于拖拽功能劫持了页签的移动位置过程,下面让移动了的页签复位
QMouseEvent fake(QEvent::MouseButtonPress,
m_DragState.pressPosition,
mapToGlobal(m_DragState.pressPosition),
Qt::LeftButton,
QApplication::mouseButtons(),
QApplication::keyboardModifiers());
QTabBar::mousePressEvent(&fake);
return;
}
}
}
}
QTabBar::mouseMoveEvent(pEvent);
}
void PaiInfoTabBar::mouseReleaseEvent(QMouseEvent *pEvent)
{
InitDragState();
QTabBar::mouseReleaseEvent(pEvent);
}
bool PaiInfoTabBar::event(QEvent *pEvent)
{
if(pEvent->type() == QEvent::ToolTip)
{
QHelpEvent *pHelpEvent = static_cast< QHelpEvent * > (pEvent);
int index = tabAt(pHelpEvent->pos());
if(index != -1)
{
QString text = tabText(index);
// 如果文本的长度+关闭按钮的宽度大于页签宽度,则以tooltip形式将文本显示全
int widthText = 0;
int widthButton = 0;
if(!text.isNull() && !text.isEmpty())
{
widthText = fontMetrics().width(text);
}
if(tabButton(index, RightSide) != NULL)
{
widthButton = tabButton(index, RightSide)->width();
}
if((widthText + widthButton) > tabRect(index).width())
{
QToolTip::showText(pHelpEvent->globalPos(), text);
return true;
}
}
QToolTip::hideText();
pEvent->ignore();
return true;
}
return QTabBar::event(pEvent);
}
void PaiInfoTabBar::timerEvent(QTimerEvent *pEvent)
{
if(pEvent->timerId() == m_OutOfConsoleTimer)
{
QList< QWidget* > lstHiddenPaiWindow;
foreach (QWidget *pWidget, QApplication::topLevelWidgets())
{
if(dynamic_cast< pai::gui::PaiWindow* > (pWidget))
{
if(pWidget->isHidden())
{
lstHiddenPaiWindow << pWidget;
continue;
}
if(pWidget->geometry().contains(QCursor::pos()))
{
if(qApp->overrideCursor() == NULL)
{
qApp->setOverrideCursor(Qt::ArrowCursor);//拖到PaiWindow上则复原光标重载的功能
}
return;
}
}
}
for(int i = lstHiddenPaiWindow.size() - 1; i >= 0; --i)
{
delete lstHiddenPaiWindow[i];//ToDo 暂时在这里消除PaiWorkspace::OnAllTabRemoved函数中的close不能销毁PaiWindow实例的缺陷
lstHiddenPaiWindow[i] = NULL;
}
while(qApp->overrideCursor() != NULL)
{
qApp->restoreOverrideCursor();//拖到外面的情况下去掉光标重载
}
}
}
PaiTabWidget::PaiTabWidget(QWidget *pParent,
bool isSubTabBar,
QTabBar::SelectionBehavior behavior) :
QTabWidget(pParent),
m_IsDropableTab(true)
{
setTabBar(new PaiInfoTabBar(this, isSubTabBar, behavior));
setTabsClosable(true);
setMovable(true);
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(OnTabClose(int)));
connect(this, SIGNAL(currentChanged(int)), SLOT(OnCurrentChanged(int)));
}
void PaiTabWidget::CloseTab(int index)
{
emit tabCloseRequested(index);
}
void PaiTabWidget::CloseAll()
{
for(int i = count() - 1; i >= 0; --i)
{
emit tabCloseRequested(i);
}
}
void PaiTabWidget::SetTabVisible(bool visible)
{
tabBar()->setVisible(visible);
}
void PaiTabWidget::OnTabClose(int index)
{
QWidget *pTabPage = widget(index);
if(!pTabPage)
{
return;
}
bool deleteOnClose = true;
if(pTabPage->property("IsDeleteOnClose").isValid())
{
deleteOnClose = pTabPage->property("IsDeleteOnClose").toBool();
}
QVariant varExtensionID = pTabPage->property("ExtensionID");
QString extensionID = varExtensionID.isValid() ? varExtensionID.toString() : QString::number(index);
AcceptClosing_P(this);
emit TabWillBeClosed(extensionID); // 该功能的正常使用,是利用在主进程中信号即关联的槽函数是顺序执行的
if(!IsCloseable(this))
{
return;
}
if(widget(index) == pTabPage)
{
if(deleteOnClose)
{
removeTab(index);
pTabPage->deleteLater();
}
else
{
removeTab(index);
// 目前所有不允许析构的页面都是PageService下的辅助页面
// 此时由PageService::CloseExtraViewExtensions()释放pTabPage
pTabPage->setParent(NULL);
}
}
m_ClosedTabID = extensionID;
}
void PaiTabWidget::OnCurrentChanged(int index)
{
QWidget *pTabPage = this->widget(index);
if(pTabPage != NULL)
{
QVariant varExtensionID = pTabPage->property("ExtensionID");
QString extensionID = varExtensionID.isValid() ? varExtensionID.toString() : QString::number(index);
emit CurrentTabChanged(extensionID);
}
}
void PaiTabWidget::tabInserted(int index)
{
QTabWidget::tabInserted(index);
emit TabInserted(index);//发出一个Tab被添加的消息
}
void PaiTabWidget::tabRemoved(int index)
{
QTabWidget::tabRemoved(index);
if(0 == count())
{
QString areaName = this->property("AreaName").toString();
emit AllTabClosed(areaName);//发出最后一个Tab被移除的消息
}
if(!m_ClosedTabID.isEmpty())
{
emit TabRemoved(m_ClosedTabID);
m_ClosedTabID = "";
}
}
void PaiTabWidget::paintEvent(QPaintEvent *pEvent)
{
if(count() == 0)
{
QPainter p(this);
p.fillRect(pEvent->rect(), QBrush(QColor("#829BB5")));
}
else
{
QTabWidget::paintEvent(pEvent);
}
}
bool PaiTabWidget::eventFilter(QObject *pObj, QEvent *pEvent)
{
if((pEvent->type() == QEvent::DragEnter) || (pEvent->type() == QEvent::DragMove) || (pEvent->type() == QEvent::Drop))
{
pEvent->ignore();//ToDo 由于QWebView的拖拽功能和PAI系统拖拽页签不兼容暂时将页签中的QWebView的拖拽功能禁止掉
return true;
}
else
{
return QTabWidget::eventFilter(pObj, pEvent);
}
}
void PaiTabWidget::SetDropable(bool dropable)
{
m_IsDropableTab = dropable;
}
bool PaiTabWidget::IsDropable()
{
return m_IsDropableTab;
}
void PaiTabWidget::SetTabBarDraggable(bool draggable)
{
PaiInfoTabBar *pTabBar = qobject_cast< PaiInfoTabBar* > (tabBar());
if(pTabBar)
{
pTabBar->SetDraggable(draggable);
}
}