logplus/Workflow/WFWidget/src/PaiTabWidget.cpp
2026-01-16 17:18:41 +08:00

592 lines
21 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.

/**
* @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);
}
}