一、NcFramelessHelper.h
//Version 3.0.0
#ifndef NC_FRAMELESS_HELPER_H
#define NC_FRAMELESS_HELPER_H
#include <QObject>
class NcFramelessHelperImpl;
class NcFramelessHelper : public QObject
{
public:
explicit NcFramelessHelper( QObject* parent = 0 );
~NcFramelessHelper();
void activateOn( QWidget* topLevelWidget );
void removeFrom( QWidget* topLevelWidget );
void setWidgetMovable( bool movable );
bool isWidgetMovable();
void setWidgetResizable( bool resizable );
bool isWidgetResizable();
void useRubberBandOnMove( bool use );
bool isUsingRubberBandOnMove();
void useRubberBandOnResize( bool use );
bool isUsingRubberBandOnResisze();
void setIsGlobal(bool global);
bool isGlobal();
//Make sure to leave the same content margins
//around the widget as the newBorderWidth
//this can be done by
//yourWidget->layout()->setMargin( newBorderWidth );
//otherwise your widget will not expose the
//area where this class works
void setBorderWidth( int newBorderWidth );
int borderWidth();
protected:
virtual bool eventFilter( QObject* obj, QEvent* event );
private:
NcFramelessHelperImpl* d;
};
#endif // NC_FRAMELESS_HELPER_H
二、NcFramelessHelper.cpp
#include <QRubberBand>
#include <QMouseEvent>
#include <QMutex>
#include <QDebug>
#include "NcFramelessHelper.h"
class NcCursorPosCalculator
{
public:
NcCursorPosCalculator();
void reset();
void recalculate( const QPoint& globalMousePos, const QRect& frameRect );
public:
bool onEdges;
bool onLeftEdge;
bool onRightEdge;
bool onTopEdge;
bool onBottomEdge;
bool onTopLeftEdge;
bool onBottomLeftEdge;
bool onTopRightEdge;
bool onBottomRightEdge;
static int mBorderWidth;
};
//TODO: Should not be static.
int NcCursorPosCalculator::mBorderWidth = 5;
class NcWidgetData
{
public:
NcWidgetData( NcFramelessHelperImpl* _d, QWidget* topLevelWidget );
~NcWidgetData();
//void setWidget( QWidget* topLevelWidget );
QWidget* widget();
void handleWidgetEvent( QEvent* event );
void updateRubberBandStatus();
void setGlobal(bool global);
private:
void updateCursorShape( const QPoint& globalMousePos );
void resizeWidget( const QPoint& globalMousePos );
void moveWidget( const QPoint& globalMousePos );
void handleMousePressEvent( QMouseEvent* event );
void handleMouseReleaseEvent( QMouseEvent* event );
void handleMouseMoveEvent( QMouseEvent* event );
void handleLeaveEvent( QEvent* event );
void handleHoverMoveEvent( QHoverEvent* event );
private:
NcFramelessHelperImpl* d;
QRubberBand* mRubberBand;
bool mLeftButtonPressed;
QWidget* mWidget;
QPoint mDragPos;
NcCursorPosCalculator mPressedMousePos;
NcCursorPosCalculator mMoveMousePos;
bool mCursorShapeChanged;
Qt::WindowFlags mWindowFlags;
bool mGlobal;
};
class NcFramelessHelperImpl
{
public:
QHash< QWidget*, NcWidgetData* > mHashWidgetData;
bool mWidgetMovable;
bool mWidgetResizable;
bool mUseRubberBandOnResize;
bool mUseRubberBandOnMove;
bool mGlobal;
};
NcCursorPosCalculator::NcCursorPosCalculator()
{
reset();
}
void NcCursorPosCalculator::reset()
{
onEdges = false;
onLeftEdge = false;
onRightEdge = false;
onTopEdge = false;
onBottomEdge = false;
onTopLeftEdge = false;
onBottomLeftEdge = false;
onTopRightEdge = false;
onBottomRightEdge = false;
}
void NcCursorPosCalculator::recalculate( const QPoint& globalMousePos, const QRect& frameRect )
{
int globalMouseX = globalMousePos.x();
int globalMouseY = globalMousePos.y();
int frameX = frameRect.x();
int frameY = frameRect.y();
int frameWidth = frameRect.width();
int frameHeight = frameRect.height();
onLeftEdge = globalMouseX >= frameX &&
globalMouseX <= frameX + mBorderWidth;
onRightEdge = globalMouseX >= frameX + frameWidth - mBorderWidth &&
globalMouseX <= frameX + frameWidth;
onTopEdge = globalMouseY >= frameY &&
globalMouseY <= frameY + mBorderWidth;
onBottomEdge = globalMouseY >= frameY + frameHeight - mBorderWidth &&
globalMouseY <= frameY + frameHeight;
onTopLeftEdge = onTopEdge && onLeftEdge;
onBottomLeftEdge = onBottomEdge && onLeftEdge;
onTopRightEdge = onTopEdge && onRightEdge;
onBottomRightEdge = onBottomEdge && onRightEdge;
//only these checks would be enough
onEdges = onLeftEdge || onRightEdge ||
onTopEdge || onBottomEdge;
}
NcWidgetData::NcWidgetData( NcFramelessHelperImpl* _d, QWidget* topLevelWidget )
{
d = _d;
mWidget = topLevelWidget;
mLeftButtonPressed = false;
mRubberBand = 0;
mCursorShapeChanged = false;
mGlobal = true;
mWindowFlags = mWidget->windowFlags();
//---from Qt docs of setWindowFlags()----
//Note: This function calls setParent() when
//changing the flags for a window, causing the
//widget to be hidden. You must call show()
//to make the widget visible again..
bool visible = mWidget->isVisible();
mWidget->setMouseTracking( true );
mWidget->setWindowFlags( /*Qt::CustomizeWindowHint|*/mWidget->windowFlags() | Qt::FramelessWindowHint|Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint);
//Bug fix, mouse move events does not propagate from child widgets.
//so need the hover events.
mWidget->setAttribute( Qt::WA_Hover );
updateRubberBandStatus();
mWidget->setVisible( visible );
}
NcWidgetData::~NcWidgetData()
{
//---from Qt docs of setWindowFlags()----
//Note: This function calls setParent() when
//changing the flags for a window, causing the
//widget to be hidden. You must call show()
//to make the widget visible again..
bool visible = mWidget->isVisible();
mWidget->setMouseTracking( false );
mWidget->setWindowFlags( mWindowFlags );//^ Qt::CustomizeWindowHint ^ Qt::FramelessWindowHint );
mWidget->setAttribute( Qt::WA_Hover, false );
mWidget->setVisible( visible );
delete mRubberBand;
}
void NcWidgetData::updateRubberBandStatus()
{
if ( d->mUseRubberBandOnMove || d->mUseRubberBandOnResize )
{
if ( !mRubberBand )
mRubberBand = new QRubberBand( QRubberBand::Rectangle );
}
else
{
delete mRubberBand;
mRubberBand = 0;
}
}
void NcWidgetData::setGlobal(bool global)
{
mGlobal = global;
}
QWidget* NcWidgetData::widget()
{
return mWidget;
}
void NcWidgetData::handleWidgetEvent( QEvent* event )
{
switch ( event->type() )
{
default: //qDebug() << "Event = " << event;
break;
case QEvent::MouseButtonPress:
handleMousePressEvent( static_cast<QMouseEvent*>( event ) );
break;
case QEvent::MouseButtonRelease:
handleMouseReleaseEvent( static_cast<QMouseEvent*>( event ) );
break;
case QEvent::MouseMove:
handleMouseMoveEvent( static_cast<QMouseEvent*>( event ) );
break;
case QEvent::Leave:
handleLeaveEvent( event );
break;
//Bug fix, hover event is necessary coz child widget does not
//propagate mousemove events. so the cursor remains in edge shape
//even in middle of widget.
case QEvent::HoverMove:
handleHoverMoveEvent( static_cast<QHoverEvent*>( event ) );
break;
//case QEvent::Enter:
//qDebug() << "Enter event";//d->handleEnterEvent( event );
//break;
}
}
void NcWidgetData::updateCursorShape( const QPoint& globalMousePos )
{
if ( mWidget->isFullScreen() || mWidget->isMaximized() )
{
if ( mCursorShapeChanged )
mWidget->unsetCursor();
return;
}
mMoveMousePos.recalculate( globalMousePos, mWidget->frameGeometry() );
if( mMoveMousePos.onTopLeftEdge || mMoveMousePos.onBottomRightEdge )
{
mWidget->setCursor( Qt::SizeFDiagCursor );
mCursorShapeChanged = true;
}
else if( mMoveMousePos.onTopRightEdge || mMoveMousePos.onBottomLeftEdge )
{
mWidget->setCursor( Qt::SizeBDiagCursor );
mCursorShapeChanged = true;
}
else if( mMoveMousePos.onLeftEdge || mMoveMousePos.onRightEdge )
{
mWidget->setCursor( Qt::SizeHorCursor );
mCursorShapeChanged = true;
}
else if( mMoveMousePos.onTopEdge || mMoveMousePos.onBottomEdge )
{
mWidget->setCursor( Qt::SizeVerCursor );
mCursorShapeChanged = true;
}
else
{
if ( mCursorShapeChanged )
{
mWidget->unsetCursor();
mCursorShapeChanged = false;
}
}
}
void NcWidgetData::resizeWidget( const QPoint& globalMousePos )
{
QRect origRect;
if ( d->mUseRubberBandOnResize )
origRect = mRubberBand->frameGeometry();
else
origRect = mWidget->frameGeometry();
int left = origRect.left();
int top = origRect.top();
int right = origRect.right();
int bottom = origRect.bottom();
origRect.getCoords( &left, &top, &right, &bottom );
int minWidth = mWidget->minimumWidth();
int minHeight = mWidget->minimumHeight();
if ( mPressedMousePos.onTopLeftEdge )
{
left = globalMousePos.x();
top = globalMousePos.y();
}
else if ( mPressedMousePos.onBottomLeftEdge )
{
left = globalMousePos.x();
bottom = globalMousePos.y();
}
else if ( mPressedMousePos.onTopRightEdge )
{
right = globalMousePos.x();
top = globalMousePos.y();
}
else if ( mPressedMousePos.onBottomRightEdge )
{
right = globalMousePos.x();
bottom = globalMousePos.y();
}
else if ( mPressedMousePos.onLeftEdge )
{
left = globalMousePos.x();
}
else if ( mPressedMousePos.onRightEdge )
{
right = globalMousePos.x();
}
else if ( mPressedMousePos.onTopEdge )
{
top = globalMousePos.y();
}
else if ( mPressedMousePos.onBottomEdge )
{
bottom = globalMousePos.y();
}
QRect newRect( QPoint(left, top), QPoint(right, bottom) );
if ( newRect.isValid() )
{
if ( minWidth > newRect.width() )
{
//determine what has caused the width change.
if( left != origRect.left() )
newRect.setLeft( origRect.left() );
else
newRect.setRight( origRect.right() );
}
if ( minHeight > newRect.height() )
{
//determine what has caused the height change.
if ( top != origRect.top() )
newRect.setTop( origRect.top() );
else
newRect.setBottom( origRect.bottom() );
}
if ( d->mUseRubberBandOnResize )
{
mRubberBand->setGeometry( newRect );
}
else
{
mWidget->setGeometry( newRect );
}
}
else
{
//qDebug() << "Calculated Rect is not valid" << newRect;
}
}
void NcWidgetData::moveWidget( const QPoint& globalMousePos )
{
if ( d->mUseRubberBandOnMove )
{
mRubberBand->move( globalMousePos - mDragPos );
}
else
{
mWidget->move( globalMousePos - mDragPos );
}
}
void NcWidgetData::handleMousePressEvent( QMouseEvent* event )
{
if ( event->button() == Qt::LeftButton )
{
mLeftButtonPressed = true;
QRect frameRect = mWidget->frameGeometry();
if(mGlobal)
{
mPressedMousePos.recalculate( event->globalPos(), frameRect );
mDragPos = event->globalPos() - frameRect.topLeft();
}else{
mPressedMousePos.recalculate( mWidget->mapToParent(event->pos()), frameRect );
mDragPos = event->globalPos() - frameRect.topLeft();
}
if ( mPressedMousePos.onEdges )
{
if ( d->mUseRubberBandOnResize )
{
mRubberBand->setGeometry( frameRect );
mRubberBand->show();
}
}
else if ( d->mUseRubberBandOnMove )
{
mRubberBand->setGeometry( frameRect );
mRubberBand->show();
}
}
}
void NcWidgetData::handleMouseReleaseEvent( QMouseEvent* event )
{
if ( event->button() == Qt::LeftButton )
{
mLeftButtonPressed = false;
mPressedMousePos.reset();
if ( mRubberBand && mRubberBand->isVisible() )
{
mRubberBand->hide();
mWidget->setGeometry( mRubberBand->geometry() );
}
}
}
void NcWidgetData::handleMouseMoveEvent( QMouseEvent* event )
{
if ( mLeftButtonPressed )
{
if ( d->mWidgetResizable && mPressedMousePos.onEdges )
{
if(mGlobal)
resizeWidget( event->globalPos() );
else
resizeWidget( mWidget->mapToParent(event->pos()) );
}
else if ( d->mWidgetMovable )
{
if(mGlobal)
moveWidget( event->globalPos() );
else
moveWidget( event->globalPos() );
}
}
else if ( d->mWidgetResizable )
{
if(mGlobal)
updateCursorShape( event->globalPos() );
else
updateCursorShape( mWidget->mapToParent(event->pos()) );
}
}
void NcWidgetData::handleLeaveEvent( QEvent* /*event*/ )
{
if ( !mLeftButtonPressed )
mWidget->unsetCursor();
}
void NcWidgetData::handleHoverMoveEvent( QHoverEvent* event )
{
if ( d->mWidgetResizable )
{
if(mGlobal)
updateCursorShape( mWidget->mapToGlobal( event->pos() ) );
else
updateCursorShape( mWidget->mapToParent(event->pos()));
}
}
NcFramelessHelper::NcFramelessHelper( QObject* parent )
: QObject( parent ),
d( new NcFramelessHelperImpl )
{
d->mWidgetMovable = true;
d->mWidgetResizable = true;
d->mUseRubberBandOnResize = false;
d->mUseRubberBandOnMove = false;
d->mGlobal = true;
}
NcFramelessHelper::~NcFramelessHelper()
{
QList<QWidget*> keys = d->mHashWidgetData.keys();
int size = keys.size();
for ( int i = 0; i < size; ++i )
{
delete d->mHashWidgetData.take( keys[i] );
}
delete d;
}
bool NcFramelessHelper::eventFilter( QObject *obj, QEvent *event )
{
QEvent::Type type = event->type();
if ( type == QEvent::MouseMove ||
type == QEvent::HoverMove ||
type == QEvent::MouseButtonPress ||
type == QEvent::MouseButtonRelease ||
type == QEvent::Leave
)
{
NcWidgetData* data = d->mHashWidgetData.value( static_cast<QWidget*>(obj) );
if ( data )
{
data->handleWidgetEvent( event );
}
}
return false;
}
void NcFramelessHelper::activateOn( QWidget* topLevelWidget )
{
if ( d->mHashWidgetData.contains( topLevelWidget ) )
return;
NcWidgetData* data = new NcWidgetData( d, topLevelWidget );
data->setGlobal(d->mGlobal);
d->mHashWidgetData.insert( topLevelWidget, data );
topLevelWidget->installEventFilter( this );
}
void NcFramelessHelper::removeFrom( QWidget* topLevelWidget )
{
NcWidgetData* data = d->mHashWidgetData.take( topLevelWidget );
if ( data )
{
topLevelWidget->removeEventFilter( this );
delete data;
}
}
void NcFramelessHelper::setWidgetMovable( bool movable )
{
d->mWidgetMovable = movable;
}
bool NcFramelessHelper::isWidgetMovable()
{
return d->mWidgetMovable;
}
void NcFramelessHelper::setWidgetResizable( bool resizable )
{
d->mWidgetResizable = resizable;
}
bool NcFramelessHelper::isWidgetResizable()
{
return d->mWidgetResizable;
}
void NcFramelessHelper::useRubberBandOnMove( bool use )
{
d->mUseRubberBandOnMove = use;
QList<NcWidgetData*> list = d->mHashWidgetData.values();
int size = list.size();
for ( int i = 0; i < size; ++i )
list[i]->updateRubberBandStatus();
}
bool NcFramelessHelper::isUsingRubberBandOnMove()
{
return d->mUseRubberBandOnMove;
}
void NcFramelessHelper::useRubberBandOnResize( bool use )
{
d->mUseRubberBandOnResize = use;
QList<NcWidgetData*> list = d->mHashWidgetData.values();
int size = list.size();
for ( int i = 0; i < size; ++i )
list[i]->updateRubberBandStatus();
}
bool NcFramelessHelper::isUsingRubberBandOnResisze()
{
return d->mUseRubberBandOnResize;
}
void NcFramelessHelper::setIsGlobal(bool global)
{
d->mGlobal = global;
}
bool NcFramelessHelper::isGlobal()
{
return d->mGlobal;
}
void NcFramelessHelper::setBorderWidth( int newBorderWidth )
{
//TODO: Make it non-static.
if ( newBorderWidth >= 0 )
NcCursorPosCalculator::mBorderWidth = newBorderWidth;
}
int NcFramelessHelper::borderWidth()
{
return NcCursorPosCalculator::mBorderWidth;
}
三、调用方式
NcFramelessHelper *Frameless = new NcFramelessHelper(this);
//设置是否能移动
Frameless->setWidgetMovable(true);
Frameless->activateOn(this);
//设置是否能改变大小
Frameless->setWidgetResizable(false);