/** * @file PaiTabWidget.cpp * @date 2011-07-19 */ #include #include #include #include #include #include #include #include #include #include #include #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); } }