kwin/clients/oxygen/oxygenclient.cpp
Martin Gräßlin 287ddf8446 Remove reset method from OxygenClient
Connects to the appropriate changed signals instead.
2013-09-12 09:27:38 +02:00

2050 lines
69 KiB
C++

//////////////////////////////////////////////////////////////////////////////
// oxygenclient.cpp
// -------------------
//
// Copyright (c) 2009 Hugo Pereira Da Costa <hugo.pereira@free.fr>
// Copyright (c) 2006, 2007 Casper Boemann <cbr@boemann.dk>
// Copyright (c) 2006, 2007 Riccardo Iaconelli <riccardo@kde.org>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//////////////////////////////////////////////////////////////////////////////
#include "oxygenclient.h"
#include "oxygenclient.moc"
#include "oxygenbutton.h"
#include "oxygensizegrip.h"
#include <cassert>
#include <cmath>
#include <KLocalizedString>
#include <KColorUtils>
#include <KStyle>
#include <QApplication>
#include <QDrag>
#include <QLabel>
#include <QPainter>
#include <QBitmap>
#include <QX11Info>
#include <QObjectList>
#include <QMimeData>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
namespace Oxygen
{
//___________________________________________
Client::Client(KDecorationBridge *b, Factory *f):
KCommonDecoration(b, f),
_factory( f ),
_sizeGrip( 0 ),
_glowAnimation( new Animation( 200, this ) ),
_titleAnimationData( new TitleAnimationData( this ) ),
_glowIntensity(0),
_initialized( false ),
_forceActive( false ),
_mouseButton( Qt::NoButton ),
_itemData( this ),
_sourceItem( -1 ),
_shadowAtom( 0 )
{
connect(options(), &KDecorationOptions::compositingChanged, this, &Client::updateCompositing);
connect(options(), &KDecorationOptions::configChanged, this, &Client::updateConfig);
}
//___________________________________________
Client::~Client()
{
// delete sizegrip if any
if( hasSizeGrip() ) deleteSizeGrip();
}
//___________________________________________
QString Client::visibleName() const
{ return i18n("Oxygen"); }
//___________________________________________
void Client::init()
{
// make sure valid configuration is set
if( !_configuration ) _configuration = _factory->configuration( *this );
KCommonDecoration::init();
widget()->setAttribute(Qt::WA_NoSystemBackground );
widget()->setAutoFillBackground( false );
widget()->setAcceptDrops( true );
// setup glow animation
_glowAnimation->setStartValue( glowBias() );
_glowAnimation->setEndValue( 1.0 );
_glowAnimation->setTargetObject( this );
_glowAnimation->setPropertyName( "glowIntensity" );
_glowAnimation->setEasingCurve( QEasingCurve::InOutQuad );
connect( _glowAnimation, SIGNAL(finished()), this, SLOT(clearForceActive()) );
// title animation data
_titleAnimationData->initialize();
connect( _titleAnimationData, SIGNAL(pixmapsChanged()), SLOT(updateTitleRect()) );
// lists
connect( _itemData.animation().data(), SIGNAL(finished()), this, SLOT(clearTargetItem()) );
// in case of preview, one wants to make the label used
// for the central widget transparent. This allows one to have
// the correct background (with gradient) rendered
// Remark: this is minor (and safe) a hack.
// This should be moved upstream (into kwin/lib/kdecoration)
if( isPreview() )
{
QList<QLabel*> children( widget()->findChildren<QLabel*>() );
foreach( QLabel* widget, children )
{ widget->setAutoFillBackground( false ); }
// also change shadow configuration size to something that fits in the preview list
shadowCache().setShadowSize( QPalette::Active, 15 );
shadowCache().setShadowSize( QPalette::Inactive, 15 );
}
setAlphaEnabled(!isMaximized());
_initialized = true;
// first reset is needed to store Oxygen configuration
updateConfig();
}
//___________________________________________
void Client::updateCompositing()
{
// update window mask when compositing is changed
if( !_initialized ) return;
updateWindowShape();
widget()->update();
updateConfig();
}
//___________________________________________
void Client::updateConfig()
{
if( !_initialized ) return;
_configuration = _factory->configuration( *this );
// glow animations
_glowAnimation->setDuration( _configuration->shadowAnimationsDuration() );
// title transitions
_titleAnimationData->setDuration( _configuration->titleAnimationsDuration() );
// tabs
_itemData.setAnimationsEnabled( animationsEnabled() && _configuration->tabAnimationsEnabled() );
_itemData.animation().data()->setDuration( _configuration->tabAnimationsDuration() );
// reset title transitions
_titleAnimationData->reset();
// should also update animations for buttons
resetButtons();
// also reset tab buttons
for( int index = 0; index < _itemData.count(); index++ )
{
ClientGroupItemData& item( _itemData[index] );
if( item._closeButton ) { item._closeButton.data()->reset(0); }
}
// reset tab geometry
_itemData.setDirty( true );
// handle size grip
if( _configuration->drawSizeGrip() && _configuration->frameBorder() == Configuration::BorderNone )
{
if( !hasSizeGrip() ) createSizeGrip();
} else if( hasSizeGrip() ) deleteSizeGrip();
// needs to remove shadow property on window since shadows are handled by the decoration
removeShadowHint();
}
//___________________________________________
bool Client::decorationBehaviour(DecorationBehaviour behaviour) const
{
switch (behaviour)
{
case DB_MenuClose:
return _configuration->closeWindowFromMenuButton();
case DB_WindowMask:
return false;
default:
return KCommonDecoration::decorationBehaviour(behaviour);
}
}
//_________________________________________________________
KCommonDecorationButton *Client::createButton(::ButtonType type)
{
switch (type) {
case MenuButton:
return new Button(*this, i18n("Window Actions Menu"), ButtonMenu);
case AppMenuButton:
return new Button(*this, i18n("Application Menu"), ButtonApplicationMenu);
case HelpButton:
return new Button(*this, i18n("Help"), ButtonHelp);
case MinButton:
return new Button(*this, i18n("Minimize"), ButtonMin);
case MaxButton:
return new Button(*this, i18n("Maximize"), ButtonMax);
case CloseButton:
return new Button(*this, i18n("Close"), ButtonClose);
case AboveButton:
return new Button(*this, i18n("Keep Above Others"), ButtonAbove);
case BelowButton:
return new Button(*this, i18n("Keep Below Others"), ButtonBelow);
case OnAllDesktopsButton:
return new Button(*this, i18n("On All Desktops"), ButtonSticky);
case ShadeButton:
return new Button(*this, i18n("Shade Button"), ButtonShade);
default: break;
}
return NULL;
}
//_________________________________________________________
QRegion Client::calcMask( void ) const
{
if( isMaximized() ) { return widget()->rect(); }
const QRect frame( widget()->rect().adjusted(
layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ),
-layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) ) );
QRegion mask;
if( _configuration->frameBorder() == Configuration::BorderNone && !isShade() )
{
if( hideTitleBar() ) mask = QRegion();
else if( compositingActive() ) mask = QRegion();
else mask = helper().roundedMask( frame, 1, 1, 1, 0 );
} else {
if( compositingActive() ) mask = QRegion();
else mask = helper().roundedMask( frame );
}
return mask;
}
//___________________________________________
int Client::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const
{
const bool maximized( isMaximized() );
const bool shaded( isShade() );
const bool narrowSpacing( _configuration->useNarrowButtonSpacing() );
const int frameBorder( this->frameBorder() );
const int buttonSize( hideTitleBar() ? 0 : this->buttonSize() );
switch (lm)
{
case LM_BorderLeft:
case LM_BorderRight:
{
int border( frameBorder );
if( respectWindowState && maximized )
{
border = 0;
} else if( _configuration->frameBorder() < Configuration::BorderTiny ) {
border = 0;
} else if( !compositingActive() && _configuration->frameBorder() == Configuration::BorderTiny ) {
border = qMax( frameBorder, 3 );
}
return border;
}
case LM_BorderBottom:
{
int border( frameBorder );
if( (respectWindowState && maximized) || shaded )
{
border = 0;
} else if( _configuration->frameBorder() >= Configuration::BorderNoSide ) {
// for tiny border, the convention is to have a larger bottom area in order to
// make resizing easier
border = qMax(frameBorder, 4);
} else if( _configuration->frameBorder() < Configuration::BorderTiny ) {
border = 0;
} else if( !compositingActive() && _configuration->frameBorder() == Configuration::BorderTiny ) {
border = qMax( frameBorder, 3 );
}
return border;
}
case LM_TitleEdgeTop:
{
int border = 0;
if( _configuration->frameBorder() == Configuration::BorderNone && hideTitleBar() )
{
border = 0;
} else if( !( respectWindowState && maximized )) {
border = TFRAMESIZE;
}
return border;
}
case LM_TitleEdgeBottom:
{
return 0;
}
case LM_TitleEdgeLeft:
case LM_TitleEdgeRight:
{
int border = 0;
if( !(respectWindowState && maximized) )
{ border = 4; }
return border;
}
case LM_TitleBorderLeft:
case LM_TitleBorderRight:
{
int border = 5;
// if title outline is to be drawn, one adds the space needed to
// separate title and tab border. namely the same value
if( _configuration->drawTitleOutline() ) border += border;
return border;
}
case LM_ButtonWidth:
case LM_ButtonHeight:
case LM_TitleHeight:
{
return buttonSize;
}
case LM_ButtonSpacing:
return narrowSpacing ? 1:3;
case LM_ButtonMarginTop:
return 0;
// outer margin for shadow/glow
case LM_OuterPaddingLeft:
case LM_OuterPaddingRight:
case LM_OuterPaddingTop:
case LM_OuterPaddingBottom:
if( maximized ) return 0;
else return shadowCache().shadowSize();
default:
return KCommonDecoration::layoutMetric(lm, respectWindowState, btn);
}
}
//_________________________________________________________
QRect Client::defaultTitleRect( bool active ) const
{
QRect titleRect( this->titleRect().adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 ) );
// when drawing title outline, shrink the rect so that it matches the actual caption size
if( active && _configuration->drawTitleOutline() && isActive() )
{
if( _configuration->titleAlignment() == Configuration::AlignCenterFullWidth )
{
titleRect.setLeft( widget()->rect().left() + layoutMetric( LM_OuterPaddingLeft ) );
titleRect.setRight( widget()->rect().right() - layoutMetric( LM_OuterPaddingRight ) );
}
const QRect textRect( titleBoundingRect( options()->font( true, false), titleRect, caption() ) );
titleRect.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) );
titleRect.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) );
} else {
// buttons are properly accounted for in titleBoundingRect method
titleRect.setLeft( widget()->rect().left() + layoutMetric( LM_OuterPaddingLeft ) );
titleRect.setRight( widget()->rect().right() - layoutMetric( LM_OuterPaddingRight ) );
}
return titleRect;
}
//_________________________________________________________
QRect Client::titleBoundingRect( const QFont& font, QRect rect, const QString& caption ) const
{
// get title bounding rect
QRect boundingRect( QFontMetrics( font ).boundingRect( rect, titleAlignment() | Qt::AlignVCenter, caption ) );
// adjust to make sure bounding rect
// 1/ has same vertical alignment as original titleRect
// 2/ does not exceeds available horizontal space
boundingRect.setTop( rect.top() );
boundingRect.setBottom( rect.bottom() );
// check bounding rect against input rect
boundRectTo( boundingRect, rect );
if( _configuration->titleAlignment() == Configuration::AlignCenterFullWidth )
{
/*
check bounding rect against max available space, for buttons
this is not needed if centerTitleOnFullWidth flag is set to false,
because it was already done before calling titleBoundingRect
*/
boundRectTo( boundingRect, titleRect() );
}
return boundingRect;
}
//_________________________________________________________
void Client::boundRectTo( QRect& rect, const QRect& bound ) const
{
if( bound.left() > rect.left() )
{
rect.moveLeft( bound.left() );
if( bound.right() < rect.right() )
{ rect.setRight( bound.right() ); }
} else if( bound.right() < rect.right() ) {
rect.moveRight( bound.right() );
if( bound.left() > rect.left() )
{ rect.setLeft( bound.left() ); }
}
return;
}
//_________________________________________________________
void Client::clearTargetItem( void )
{
if( _itemData.animationType() == AnimationLeave )
{ _itemData.setDirty( true ); }
}
//_________________________________________________________
void Client::updateItemBoundingRects( bool alsoUpdate )
{
// make sure items are not animated
_itemData.animate( AnimationNone );
// maximum available space
const QRect titleRect( this->titleRect() );
// get tabs
const int items( tabCount() );
// make sure item data have the correct number of items
while( _itemData.count() < items ) _itemData.push_back( ClientGroupItemData() );
while( _itemData.count() > items )
{
if( _itemData.back()._closeButton ) delete _itemData.back()._closeButton.data();
_itemData.pop_back();
}
assert( !_itemData.isEmpty() );
// create buttons
if( _itemData.count() == 1 )
{
// remove button
if( _itemData.front()._closeButton )
{ delete _itemData.front()._closeButton.data(); }
// set active rect
_itemData.front()._activeRect = titleRect.adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 );
} else {
int left( titleRect.left() );
const int width( titleRect.width()/items );
for( int index = 0; index < _itemData.count(); index++ )
{
ClientGroupItemData& item(_itemData[index]);
// make sure button exists
if( !item._closeButton )
{
item._closeButton = ClientGroupItemData::ButtonPointer( new Button( *this, QStringLiteral("Close this tab"), ButtonItemClose ) );
item._closeButton.data()->show();
item._closeButton.data()->installEventFilter( this );
}
// set active rect
QRect local( QPoint( left, titleRect.top() ), QSize( width, titleRect.height() ) );
local.adjust( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 );
item._activeRect = local;
left += width;
}
}
if( _itemData.count() == 1 )
{
_itemData.front().reset( defaultTitleRect() );
} else {
for( int index = 0; index < _itemData.count(); index++ )
{ _itemData[index].reset( _itemData[index]._activeRect ); }
}
// button activity
_itemData.updateButtonActivity( currentTabId() );
// reset buttons location
_itemData.updateButtons( alsoUpdate );
_itemData.setDirty( false );
return;
}
//_________________________________________________________
QColor Client::titlebarTextColor(const QPalette &palette) const
{
if( glowIsAnimated() ) return KColorUtils::mix(
titlebarTextColor( palette, false ),
titlebarTextColor( palette, true ),
glowIntensity() );
else return titlebarTextColor( palette, isActive() );
}
//_________________________________________________________
void Client::renderWindowBackground( QPainter* painter, const QRect& rect, const QWidget* widget, const QPalette& palette ) const
{
// window background
if(
_configuration->blendStyle() == Configuration::BlendNone ||
( _configuration->blendStyle() == Configuration::BlendFromStyle &&
!helper().hasBackgroundGradient( windowId() )
) )
{
painter->fillRect( rect, palette.color( QPalette::Window ) );
} else {
int offset = layoutMetric( LM_OuterPaddingTop );
// radial gradient positionning
const int height = hideTitleBar() ? 0:buttonSize();
if( isMaximized() ) offset -= 3;
const QWidget* window( isPreview() ? this->widget() : widget->window() );
helper().renderWindowBackground(painter, rect, widget, window, palette, offset, height );
}
// background pixmap
if( isPreview() || helper().hasBackgroundPixmap( windowId() ) )
{
int offset = layoutMetric( LM_OuterPaddingTop );
// radial gradient positionning
const int height = hideTitleBar() ? 0:buttonSize();
if( isMaximized() ) offset -= 3;
// background pixmap
QPoint backgroundPixmapOffset( layoutMetric( LM_OuterPaddingLeft ) + layoutMetric( LM_BorderLeft ), 0 );
helper().setBackgroundPixmapOffset( backgroundPixmapOffset );
const QWidget* window( isPreview() ? this->widget() : widget->window() );
helper().renderBackgroundPixmap(painter, rect, widget, window, offset, height );
}
}
//_________________________________________________________
void Client::renderWindowBorder( QPainter* painter, const QRect& clipRect, const QWidget* widget, const QPalette& palette ) const
{
// get coordinates relative to the client area
// this is annoying. One could use mapTo if this was taking const QWidget* and not
// const QWidget* as argument.
const QWidget* window = (isPreview()) ? this->widget() : widget->window();
const QWidget* w = widget;
QPoint position( 0, 0 );
while ( w != window && !w->isWindow() && w != w->parentWidget() )
{
position += w->geometry().topLeft();
w = w->parentWidget();
}
// save painter
if( clipRect.isValid() )
{
painter->save();
painter->setClipRegion(clipRect,Qt::IntersectClip);
}
QRect r = (isPreview()) ? this->widget()->rect():window->rect();
r.adjust( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) );
r.adjust(0,0, 1, 1);
// base color
QColor color( palette.window().color() );
// add alpha channel
if( _itemData.count() == 1 && glowIsAnimated() )
{ color = helper().alphaColor( color, glowIntensity() ); }
// title height
const int titleHeight( layoutMetric( LM_TitleEdgeTop ) + layoutMetric( LM_TitleEdgeBottom ) + layoutMetric( LM_TitleHeight ) );
// make titlebar background darker for tabbed, non-outline window
if( ( tabCount() >= 2 || _itemData.isAnimated() ) && !(_configuration->drawTitleOutline() && isActive() ) )
{
const QPoint topLeft( r.topLeft()-position );
const QRect rect( topLeft, QSize( r.width(), titleHeight ) );
QLinearGradient lg( rect.topLeft(), rect.bottomLeft() );
lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) );
lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) );
painter->setBrush( lg );
painter->setPen( Qt::NoPen );
painter->drawRect( rect );
}
// horizontal line
{
const int shadowSize = 7;
const int height = shadowSize-3;
const QPoint topLeft( r.topLeft()+QPoint(0,titleHeight-height)-position );
QRect rect( topLeft, QSize( r.width(), height ) );
// adjustements to cope with shadow size and outline border.
rect.adjust( -shadowSize, 0, shadowSize-1, 0 );
if( _configuration->drawTitleOutline() && ( isActive() || glowIsAnimated() ) && !isMaximized() )
{
if( _configuration->frameBorder() == Configuration::BorderTiny ) rect.adjust( 1, 0, -1, 0 );
else if( _configuration->frameBorder() > Configuration::BorderTiny ) rect.adjust( HFRAMESIZE-1, 0, -HFRAMESIZE+1, 0 );
}
if( rect.isValid() )
{ helper().slab( color, 0, shadowSize )->render( rect, painter, TileSet::Top ); }
}
if( _configuration->drawTitleOutline() && ( isActive() || glowIsAnimated() ) )
{
// save old hints and turn off anti-aliasing
const QPainter::RenderHints hints( painter->renderHints() );
painter->setRenderHint( QPainter::Antialiasing, false );
// save mask and frame to where
// grey window background is to be rendered
QRegion mask;
QRect frame;
// bottom line
const int leftOffset = qMin( layoutMetric( LM_BorderLeft ), int(HFRAMESIZE) );
const int rightOffset = qMin( layoutMetric( LM_BorderRight ), int(HFRAMESIZE) );
if( _configuration->frameBorder() > Configuration::BorderNone )
{
const int height = qMax( 0, layoutMetric( LM_BorderBottom ) - HFRAMESIZE );
const int width = r.width() - leftOffset - rightOffset - 1;
const QRect rect( r.bottomLeft()-position + QPoint( leftOffset, -layoutMetric( LM_BorderBottom ) ), QSize( width, height ) );
if( height > 0 ) { mask += rect; frame |= rect; }
const QColor shadow( helper().calcDarkColor( color ) );
painter->setPen( shadow );
painter->drawLine( rect.bottomLeft()+QPoint(-1,1), rect.bottomRight()+QPoint(1,1) );
}
// left and right
const int topOffset = titleHeight;
const int bottomOffset = qMin( layoutMetric( LM_BorderBottom ), int(HFRAMESIZE) );
const int height = r.height() - topOffset - bottomOffset - 1;
if( _configuration->frameBorder() >= Configuration::BorderTiny )
{
const QColor shadow( helper().calcLightColor( color ) );
painter->setPen( shadow );
// left
int width = qMax( 0, layoutMetric( LM_BorderLeft ) - HFRAMESIZE );
QRect rect( r.topLeft()-position + QPoint( layoutMetric( LM_BorderLeft ) - width, topOffset ), QSize( width, height ) );
if( width > 0 ) { mask += rect; frame |= rect; }
painter->drawLine( rect.topLeft()-QPoint(1,0), rect.bottomLeft()-QPoint(1, 0) );
// right
width = qMax( 0, layoutMetric( LM_BorderRight ) - HFRAMESIZE );
rect = QRect(r.topRight()-position + QPoint( -layoutMetric( LM_BorderRight ), topOffset ), QSize( width, height ));
if( width > 0 ) { mask += rect; frame |= rect; }
painter->drawLine( rect.topRight()+QPoint(1,0), rect.bottomRight()+QPoint(1, 0) );
}
// restore old hints
painter->setRenderHints( hints );
// in preview mode also adds center square
if( isPreview() )
{
const QRect rect( r.topLeft()-position + QPoint( layoutMetric( LM_BorderLeft ), topOffset ), QSize(r.width()-layoutMetric( LM_BorderLeft )-layoutMetric( LM_BorderRight ),height) );
mask += rect; frame |= rect;
}
// paint
if( !mask.isEmpty() )
{
painter->setClipRegion( mask, Qt::IntersectClip);
renderWindowBackground(painter, frame, widget, palette );
}
}
// restore painter
if( clipRect.isValid() )
{ painter->restore(); }
}
//_________________________________________________________
void Client::renderSeparator( QPainter* painter, const QRect& clipRect, const QWidget* widget, const QColor& color ) const
{
const QWidget* window = (isPreview()) ? this->widget() : widget->window();
// get coordinates relative to the client area
// this is annoying. One could use mapTo if this was taking const QWidget* and not
// const QWidget* as argument.
QPoint position( 0, 0 );
{
const QWidget* w = widget;
while ( w != window && !w->isWindow() && w != w->parentWidget() ) {
position += w->geometry().topLeft();
w = w->parentWidget();
}
}
// setup painter
if (clipRect.isValid())
{
painter->save();
painter->setClipRegion(clipRect,Qt::IntersectClip);
}
QRect r = (isPreview()) ? this->widget()->rect():window->rect();
r.adjust( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) );
// dimensions
const int titleHeight = layoutMetric(LM_TitleHeight);
const int titleTop = layoutMetric(LM_TitleEdgeTop) + r.top();
// set color
QColor local( color );
if( glowIsAnimated() && _configuration->separatorMode() != Configuration::SeparatorAlways )
{ local = helper().alphaColor( color, glowIntensity() ); }
// render
helper().drawSeparator( painter, QRect(r.top(), titleTop+titleHeight-1.5, r.width(), 2).translated( -position ), local, Qt::Horizontal);
if (clipRect.isValid()) { painter->restore(); }
}
//_________________________________________________________
void Client::renderTitleOutline( QPainter* painter, const QRect& rect, const QPalette& palette ) const
{
// center (for active windows only)
{
painter->save();
QRect adjustedRect( rect.adjusted( 1, 1, -1, 1 ) );
// prepare painter mask
QRegion mask( adjustedRect.adjusted( 1, 0, -1, 0 ) );
mask += adjustedRect.adjusted( 0, 1, 0, 0 );
painter->setClipRegion( mask, Qt::IntersectClip );
// draw window background
renderWindowBackground(painter, adjustedRect, widget(), palette );
painter->restore();
}
// shadow
const int shadowSize( 7 );
const int offset( -3 );
const int voffset( 5-shadowSize );
const QRect adjustedRect( rect.adjusted(offset, voffset, -offset, shadowSize) );
QColor color( palette.color( widget()->backgroundRole() ) );
// add alpha channel
if( _itemData.count() == 1 && glowIsAnimated() )
{ color = helper().alphaColor( color, glowIntensity() ); }
// render slab
helper().slab( color, 0, shadowSize )->render( adjustedRect, painter, TileSet::Tiles(TileSet::Top|TileSet::Left|TileSet::Right) );
}
//_________________________________________________________
void Client::renderTitleText( QPainter* painter, const QRect& rect, const QColor& color, const QColor& contrast ) const
{
if( !_titleAnimationData->isValid() )
{
// contrast pixmap
_titleAnimationData->reset(
rect,
renderTitleText( rect, caption(), color ),
renderTitleText( rect, caption(), contrast ) );
}
if( _titleAnimationData->isDirty() )
{
// clear dirty flags
_titleAnimationData->setDirty( false );
// finish current animation if running
if( _titleAnimationData->isAnimated() )
{ _titleAnimationData->finishAnimation(); }
if( !_titleAnimationData->isLocked() )
{
// set pixmaps
_titleAnimationData->setPixmaps(
rect,
renderTitleText( rect, caption(), color ),
renderTitleText( rect, caption(), contrast ) );
_titleAnimationData->startAnimation();
renderTitleText( painter, rect, color, contrast );
} else if( !caption().isEmpty() ) {
renderTitleText( painter, rect, caption(), color, contrast );
}
// lock animations (this must be done whether or not
// animation was actually started, in order to extend locking
// every time title get changed too rapidly
_titleAnimationData->lockAnimations();
} else if( _titleAnimationData->isAnimated() ) {
if( isMaximized() ) painter->translate( 0, 2 );
if( !_titleAnimationData->contrastPixmap().isNull() )
{
painter->translate( 0, 1 );
painter->drawPixmap( rect.topLeft(), _titleAnimationData->contrastPixmap() );
painter->translate( 0, -1 );
}
painter->drawPixmap( rect.topLeft(), _titleAnimationData->pixmap() );
if( isMaximized() ) painter->translate( 0, -2 );
} else if( !caption().isEmpty() ) {
renderTitleText( painter, rect, caption(), color, contrast );
}
}
//_______________________________________________________________________
void Client::renderTitleText( QPainter* painter, const QRect& rect, const QString& caption, const QColor& color, const QColor& contrast, bool elide ) const
{
const Qt::Alignment alignment( titleAlignment() | Qt::AlignVCenter );
const QString local( elide ? QFontMetrics( painter->font() ).elidedText( caption, Qt::ElideRight, rect.width() ):caption );
// translate title down in case of maximized window
if( isMaximized() ) painter->translate( 0, 2 );
if( contrast.isValid() )
{
painter->setPen( contrast );
painter->translate( 0, 1 );
painter->drawText( rect, alignment, local );
painter->translate( 0, -1 );
}
painter->setPen( color );
painter->drawText( rect, alignment, local );
// translate back
if( isMaximized() ) painter->translate( 0, -2 );
}
//_______________________________________________________________________
QPixmap Client::renderTitleText( const QRect& rect, const QString& caption, const QColor& color, bool elide ) const
{
if( !rect.isValid() ) return QPixmap();
QPixmap out( rect.size() );
out.fill( Qt::transparent );
if( caption.isEmpty() || !color.isValid() ) return out;
QPainter painter( &out );
painter.setFont( options()->font(isActive(), false) );
const Qt::Alignment alignment( titleAlignment() | Qt::AlignVCenter );
const QString local( elide ? QFontMetrics( painter.font() ).elidedText( caption, Qt::ElideRight, rect.width() ):caption );
painter.setPen( color );
painter.drawText( out.rect(), alignment, local );
painter.end();
return out;
}
//_______________________________________________________________________
void Client::renderItem( QPainter* painter, int index, const QPalette& palette )
{
const ClientGroupItemData& item( _itemData[index] );
// see if tag is active
const int itemCount( _itemData.count() );
// check item bounding rect
if( !item._boundingRect.isValid() ) return;
// create rect in which text is to be drawn
QRect textRect( item._boundingRect.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) );
// add extra space needed for title outline
if( itemCount > 1 || _itemData.isAnimated() )
{ textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); }
// add extra space for the button
if( itemCount > 1 && item._closeButton && item._closeButton.data()->isVisible() )
{ textRect.adjust( 0, 0, - buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 ); }
// check if current item is active
const bool active( tabId(index) == currentTabId() );
// get current item caption and update text rect
const QString caption( itemCount == 1 ? this->caption() : this->caption(index) );
if( _configuration->titleAlignment() != Configuration::AlignCenterFullWidth )
{ boundRectTo( textRect, titleRect() ); }
// adjust textRect
textRect = titleBoundingRect( painter->font(), textRect, caption );
// title outline
if( itemCount == 1 )
{
// no title outline if the window caption is empty
if( !caption.trimmed().isEmpty() )
{
if( _itemData.isAnimated() ) {
renderTitleOutline( painter, item._boundingRect, palette );
} else if( (isActive()||glowIsAnimated()) && _configuration->drawTitleOutline() ) {
// adjusts boundingRect accordingly
QRect boundingRect( item._boundingRect );
boundingRect.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) );
boundingRect.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) );
// render bounding rect around it with extra margins
renderTitleOutline( painter, boundingRect, palette );
}
}
} else if( active ) {
// in multiple tabs render title outline in all cases
renderTitleOutline( painter, item._boundingRect, palette );
}
// render text
if( active || itemCount == 1 )
{
// for active tab, current caption is "merged" with old caption, if any
renderTitleText(
painter, textRect,
titlebarTextColor( palette ),
titlebarContrastColor( palette ) );
} else {
QColor background( backgroundPalette( widget(), palette ).color( widget()->window()->backgroundRole() ) );
// add extra shade (as used in renderWindowBorder
if( !( isActive() && _configuration->drawTitleOutline() ) )
{ background = KColorUtils::mix( background, Qt::black, 0.10 ); }
// otherwise current caption is rendered directly
renderTitleText(
painter, textRect, caption,
titlebarTextColor( backgroundPalette( widget(), palette ), false ),
titlebarContrastColor( background ) );
}
// render separators between inactive tabs
if( !( active || itemCount == 1 ) && item._closeButton && item._closeButton.data()->isVisible() )
{
// separators
// draw Left separator
const QColor color( backgroundPalette( widget(), palette ).window().color() );
const bool isFirstItem( index == 0 || (index == 1 && !_itemData[0]._boundingRect.isValid() ) );
if( !active && ( ( isFirstItem && buttonsLeftWidth() > 0 ) || _itemData.isTarget( index ) ) )
{
const QRect local( item._boundingRect.topLeft()+QPoint(0,2), QSize( 2, item._boundingRect.height()-3 ) );
helper().drawSeparator( painter, local, color, Qt::Vertical);
}
// draw right separator
if(
( index == itemCount-1 && buttonsRightWidth() > 0 ) ||
( index+1 < itemCount && ( _itemData.isTarget( index+1 ) ||
!( tabId(index+1) == currentTabId() && _itemData[index+1]._boundingRect.isValid() ) ) ) )
{
const QRect local( item._boundingRect.topRight()+QPoint(0,2), QSize( 2, item._boundingRect.height()-3 ) );
helper().drawSeparator( painter, local, color, Qt::Vertical);
}
}
}
//_______________________________________________________________________
void Client::renderTargetRect( QPainter* p, const QPalette& palette )
{
if( _itemData.targetRect().isNull() || _itemData.isAnimationRunning() ) return;
const QColor color = palette.color(QPalette::Highlight);
p->setPen(KColorUtils::mix(color, palette.color(QPalette::Active, QPalette::WindowText)));
p->setBrush( helper().alphaColor( color, 0.5 ) );
p->drawRect( QRectF(_itemData.targetRect()).adjusted( 4.5, 2.5, -4.5, -2.5 ) );
}
//_______________________________________________________________________
void Client::renderCorners( QPainter* painter, const QRect& frame, const QPalette& palette ) const
{
const QColor color( backgroundColor( widget(), palette ) );
QLinearGradient lg = QLinearGradient(0, -0.5, 0, qreal( frame.height() )+0.5);
lg.setColorAt(0.0, helper().calcLightColor( helper().backgroundTopColor(color) ));
lg.setColorAt(0.51, helper().backgroundBottomColor(color) );
lg.setColorAt(1.0, helper().backgroundBottomColor(color) );
painter->setPen( QPen( lg, 1 ) );
painter->setBrush( Qt::NoBrush );
painter->drawRoundedRect( QRectF( frame ).adjusted( 0.5, 0.5, -0.5, -0.5 ), 3.5, 3.5 );
}
//_______________________________________________________________________
void Client::renderFloatFrame( QPainter* painter, const QRect& frame, const QPalette& palette ) const
{
// shadow and resize handles
if( !isMaximized() )
{
if( _configuration->frameBorder() >= Configuration::BorderTiny )
{
helper().drawFloatFrame(
painter, frame, backgroundColor( widget(), palette ),
!compositingActive(), isActive() && shadowCache().isEnabled( QPalette::Active ),
KDecoration::options()->color(ColorTitleBar)
);
} else {
// for small borders, use a frame that matches the titlebar only
const QRect local( frame.topLeft(), QSize( frame.width(), layoutMetric(LM_TitleHeight) + layoutMetric(LM_TitleEdgeTop) ) );
helper().drawFloatFrame(
painter, local, backgroundColor( widget(), palette ),
false, isActive() && shadowCache().isEnabled( QPalette::Active ),
KDecoration::options()->color(ColorTitleBar)
);
}
} else if( isShade() ) {
// for shaded maximized windows adjust frame and draw the bottom part of it
helper().drawFloatFrame(
painter, frame, backgroundColor( widget(), palette ),
!( compositingActive() || _configuration->frameBorder() == Configuration::BorderNone ), isActive(),
KDecoration::options()->color(ColorTitleBar),
TileSet::Bottom
);
}
}
//____________________________________________________________________________
void Client::renderDots( QPainter* painter, const QRect& frame, const QColor& color ) const
{
if( _configuration->frameBorder() >= Configuration::BorderTiny )
{
// dimensions
int x,y,w,h;
frame.getRect(&x, &y, &w, &h);
if( isResizable() && !isShade() && !isMaximized() )
{
// Draw right side 3-dots resize handles
const int cenY = (h / 2 + y) ;
const int posX = (w + x - 3);
helper().renderDot( painter, QPoint(posX, cenY - 3), color);
helper().renderDot( painter, QPoint(posX, cenY), color);
helper().renderDot( painter, QPoint(posX, cenY + 3), color);
}
// Draw bottom-right cornet 3-dots resize handles
if( isResizable() && !isShade() && !_configuration->drawSizeGrip() )
{
painter->save();
painter->translate(x + w-9, y + h-9);
helper().renderDot( painter, QPoint(2, 6), color);
helper().renderDot( painter, QPoint(5, 5), color);
helper().renderDot( painter, QPoint(6, 2), color);
painter->restore();
}
}
}
//_________________________________________________________
void Client::activeChange( void )
{
KCommonDecoration::activeChange();
_itemData.setDirty( true );
// reset animation
if( shadowAnimationsEnabled() )
{
_glowAnimation->setDirection( isActive() ? Animation::Forward : Animation::Backward );
if(!glowIsAnimated()) { _glowAnimation->start(); }
}
// update size grip so that it gets the right color
// also make sure it is remaped to from z stack,
// unless hidden
if( hasSizeGrip() && !(isShade() || isMaximized() ))
{
sizeGrip().activeChange();
sizeGrip().update();
}
}
//_________________________________________________________
void Client::maximizeChange( void )
{
if( hasSizeGrip() ) sizeGrip().setVisible( !( isShade() || isMaximized() ) );
setAlphaEnabled(!isMaximized());
KCommonDecoration::maximizeChange();
}
//_________________________________________________________
void Client::shadeChange( void )
{
if( hasSizeGrip() ) sizeGrip().setVisible( !( isShade() || isMaximized() ) );
KCommonDecoration::shadeChange();
}
//_________________________________________________________
void Client::captionChange( void )
{
KCommonDecoration::captionChange();
_itemData.setDirty( true );
if( titleAnimationsEnabled() )
{ _titleAnimationData->setDirty( true ); }
}
//_________________________________________________________
QPalette Client::backgroundPalette( const QWidget* widget, QPalette palette ) const
{
if( _configuration->drawTitleOutline() )
{
if( glowIsAnimated() && !isForcedActive() )
{
const QColor inactiveColor( backgroundColor( widget, palette, false ) );
const QColor activeColor( backgroundColor( widget, palette, true ) );
const QColor mixed( KColorUtils::mix( inactiveColor, activeColor, glowIntensity() ) );
palette.setColor( QPalette::Window, mixed );
palette.setColor( QPalette::Button, mixed );
} else if( isActive() || isForcedActive() ) {
const QColor color = options()->color( KDecorationDefines::ColorTitleBar, true );
palette.setColor( QPalette::Window, color );
palette.setColor( QPalette::Button, color );
}
}
return palette;
}
//_________________________________________________________
QColor Client::backgroundColor( const QWidget*, QPalette palette, bool active ) const
{
return ( _configuration->drawTitleOutline() && active ) ?
options()->color( KDecorationDefines::ColorTitleBar, true ):
palette.color( QPalette::Window );
}
//_________________________________________________________
QString Client::defaultButtonsLeft() const
{ return KCommonDecoration::defaultButtonsLeft(); }
//_________________________________________________________
QString Client::defaultButtonsRight() const
{ return QStringLiteral("HIAX"); }
//________________________________________________________________
void Client::updateWindowShape()
{
if(isMaximized()) clearMask();
else setMask( calcMask() );
}
//______________________________________________________________________________
bool Client::eventFilter( QObject* object, QEvent* event )
{
// all dedicated event filtering is here to handle multiple tabs.
bool state = false;
switch( event->type() )
{
case QEvent::Show:
if( widget() == object )
{ _itemData.setDirty( true ); }
break;
case QEvent::MouseButtonPress:
if( widget() == object )
{ state = mousePressEvent( static_cast< QMouseEvent* >( event ) ); }
break;
case QEvent::MouseButtonRelease:
if( widget() == object ) state = mouseReleaseEvent( static_cast< QMouseEvent* >( event ) );
else if( Button *btn = qobject_cast< Button* >( object ) )
{
QMouseEvent* mouseEvent( static_cast< QMouseEvent* >( event ) );
if( mouseEvent->button() == Qt::LeftButton && btn->rect().contains( mouseEvent->pos() ) )
{ state = closeItem( btn ); }
}
break;
case QEvent::MouseMove:
state = mouseMoveEvent( static_cast< QMouseEvent* >( event ) );
break;
case QEvent::DragEnter:
if( widget() == object )
{ state = dragEnterEvent( static_cast< QDragEnterEvent* >( event ) ); }
break;
case QEvent::DragMove:
if( widget() == object )
{ state = dragMoveEvent( static_cast< QDragMoveEvent* >( event ) ); }
break;
case QEvent::DragLeave:
if( widget() == object )
{ state = dragLeaveEvent( static_cast< QDragLeaveEvent* >( event ) ); }
break;
case QEvent::Drop:
if( widget() == object )
{ state = dropEvent( static_cast< QDropEvent* >( event ) ); }
break;
default: break;
}
return state || KCommonDecoration::eventFilter( object, event );
}
//_________________________________________________________
void Client::resizeEvent( QResizeEvent* event )
{
// prepare item data updates
_itemData.setDirty( true );
// mark title animation as dirty
if( event->oldSize().width() != event->size().width() )
{ _titleAnimationData->setDirty( true ); }
// resize backing store pixmap
if( !compositingActive() )
{ _pixmap = QPixmap( event->size() ); }
// base class implementation
KCommonDecoration::resizeEvent( event );
}
//_________________________________________________________
void Client::paintBackground( QPainter& painter ) const
{
if( !compositingActive() )
{ painter.drawPixmap( QPoint(), _pixmap ); }
}
//_________________________________________________________
QRegion Client::region( KDecorationDefines::Region r )
{
// return empty region for anything but extended borders, when enabled
if( !( r == KDecorationDefines::ExtendedBorderRegion && configuration()->useExtendedWindowBorders() ) )
{ return QRegion(); }
// return empty region for maximized windows
if( isMaximized() ) return QRegion();
// return 3 pixels extended borders for sides that have no visible borders
// also add the invisible pixels at the masked rounded corners, in non compositing mode
if( configuration()->frameBorder() <= Configuration::BorderNoSide || !compositingActive() )
{
QRect rect = widget()->rect().adjusted(
layoutMetric( LM_OuterPaddingLeft ),
layoutMetric( LM_OuterPaddingTop ),
- layoutMetric( LM_OuterPaddingRight ),
- layoutMetric( LM_OuterPaddingBottom ) );
rect.translate( -layoutMetric( LM_OuterPaddingLeft ), -layoutMetric( LM_OuterPaddingTop ) );
// mask
QRegion mask( calcMask() );
if( mask.isEmpty() ) mask = rect;
else mask.translate( -layoutMetric( LM_OuterPaddingLeft ), -layoutMetric( LM_OuterPaddingTop ) );
// only return non-empty region on the sides for which there is no border
if( configuration()->frameBorder() == Configuration::BorderNone ) return QRegion( rect.adjusted( -3, 0, 3, 3 ) ) - mask;
else if( configuration()->frameBorder() == Configuration::BorderNoSide ) return QRegion( rect.adjusted( -3, 0, 3, 0 ) ) - mask;
else if( !compositingActive() ) return QRegion( rect ) - mask;
}
// fall back
return QRegion();
}
//_________________________________________________________
void Client::paintEvent( QPaintEvent* event )
{
// factory
if(!( _initialized && _factory->initialized() ) ) return;
if( compositingActive() )
{
QPainter painter(widget());
painter.setRenderHint(QPainter::Antialiasing);
painter.setClipRegion( event->region() );
paint( painter );
// update buttons
QList<Button*> buttons( widget()->findChildren<Button*>() );
foreach( Button* button, buttons )
{
if( ( button->isVisible() || isPreview() ) && event->rect().intersects( button->geometry() ) )
{
painter.save();
painter.setViewport( button->geometry() );
painter.setWindow( button->rect() );
button->paint( painter );
painter.restore();
}
}
} else {
{
// update backing store pixmap
QPainter painter( &_pixmap );
painter.setRenderHint(QPainter::Antialiasing);
painter.setClipRegion( event->region() );
paint( painter );
}
QPainter painter( widget() );
painter.setClipRegion( event->region() );
painter.drawPixmap( QPoint(), _pixmap );
// update buttons
QList<Button*> buttons( widget()->findChildren<Button*>() );
foreach( Button* button, buttons )
{
if( event->rect().intersects( button->geometry() ) )
{ button->update(); }
}
}
}
//_________________________________________________________
void Client::paint( QPainter& painter )
{
// palette
QPalette palette = widget()->palette();
palette.setCurrentColorGroup( (isActive() ) ? QPalette::Active : QPalette::Inactive );
// define frame
QRect frame = widget()->rect();
// base color
QColor color = palette.window().color();
// draw shadows
if( compositingActive() && shadowCache().shadowSize() > 0 && !isMaximized() )
{
TileSet *tileSet( 0 );
const ShadowCache::Key key( this->key() );
if( shadowCache().isEnabled( QPalette::Active ) && glowIsAnimated() && !isForcedActive() )
{
tileSet = shadowCache().tileSet( key, glowIntensity() );
} else {
tileSet = shadowCache().tileSet( key );
}
tileSet->render( frame, &painter, TileSet::Ring);
}
// adjust frame
frame.adjust(
layoutMetric(LM_OuterPaddingLeft),
layoutMetric(LM_OuterPaddingTop),
-layoutMetric(LM_OuterPaddingRight),
-layoutMetric(LM_OuterPaddingBottom) );
// adjust mask
if( compositingActive() || isPreview() )
{
if( isMaximized() ) {
painter.setClipRect( frame, Qt::IntersectClip );
} else {
// multipliers
const int left = 1;
const int right = 1;
const int top = 1;
int bottom = 1;
// disable bottom corners when border frame is too small and window is not shaded
if( _configuration->frameBorder() == Configuration::BorderNone && !isShade() ) bottom = 0;
QRegion mask( helper().roundedMask( frame, left, right, top, bottom ) );
renderCorners( &painter, frame, palette );
painter.setClipRegion( mask, Qt::IntersectClip );
}
}
// make sure ItemData and tabList are synchronized
/*
this needs to be done before calling RenderWindowBorder
since some painting in there depend on the clientGroups state
*/
if( _itemData.isDirty() || _itemData.count() != tabCount() )
{ updateItemBoundingRects( false ); }
// window background
renderWindowBackground( &painter, frame, widget(), backgroundPalette( widget(), palette ) );
// window border (for title outline)
if( hasTitleOutline() ) renderWindowBorder( &painter, frame, widget(), palette );
// clipping
if( compositingActive() )
{
painter.setClipping(false);
frame.adjust(-1,-1, 1, 1);
}
// float frame
renderFloatFrame( &painter, frame, palette );
// resize handles
renderDots( &painter, frame, backgroundColor( widget(), palette ) );
if( !hideTitleBar() )
{
// title bounding rect
painter.setFont( options()->font(isActive(), false) );
// draw ClientGroupItems
const int itemCount( _itemData.count() );
for( int i = 0; i < itemCount; i++ ) renderItem( &painter, i, palette );
// draw target rect
renderTargetRect( &painter, widget()->palette() );
// separator
if( itemCount == 1 && !_itemData.isAnimated() && drawSeparator() )
{ renderSeparator(&painter, frame, widget(), color ); }
}
}
//_____________________________________________________________
bool Client::mousePressEvent( QMouseEvent* event )
{
const QPoint point = event->pos();
if( tabIndexAt( point ) < 0 ) return false;
_dragPoint = point;
_mouseButton = event->button();
bool accepted( false );
if( buttonToWindowOperation( _mouseButton ) == TabDragOp )
{
accepted = true;
} else if( buttonToWindowOperation( _mouseButton ) == OperationsOp ) {
QPoint point = event->pos();
const int clickedIndex( tabIndexAt( point ) );
_mouseButton = Qt::NoButton;
if ( tabIndexAt( point ) > -1)
{ showWindowMenu( widget()->mapToGlobal( event->pos() ), tabId(clickedIndex) ); }
accepted = true; // displayClientMenu can possibly destroy the deco...
}
return accepted;
}
//_____________________________________________________________
bool Client::mouseReleaseEvent( QMouseEvent* event )
{
bool accepted( false );
if( _mouseButton == event->button() && buttonToWindowOperation( _mouseButton ) != OperationsOp )
{
const QPoint point = event->pos();
const long visibleItem = currentTabId();
const int clickedIndex( tabIndexAt( point ) );
if( clickedIndex >= 0 && visibleItem != tabId(clickedIndex) )
{
setCurrentTab( tabId(clickedIndex) );
setForceActive( true );
accepted = true;
}
}
_mouseButton = Qt::NoButton;
return accepted;
}
//_____________________________________________________________
bool Client::mouseMoveEvent( QMouseEvent* event )
{
// check button and distance to drag point
if( hideTitleBar() || _mouseButton == Qt::NoButton || ( event->pos() - _dragPoint ).manhattanLength() <= QApplication::startDragDistance() )
{ return false; }
bool accepted( false );
if( buttonToWindowOperation( _mouseButton ) == TabDragOp )
{
const QPoint point = event->pos();
const int clickedIndex( tabIndexAt( point ) );
if( clickedIndex < 0 ) return false;
_titleAnimationData->reset();
QDrag *drag = new QDrag( widget() );
QMimeData *groupData = new QMimeData();
groupData->setData( tabDragMimeType(), QByteArray().setNum( (qint64) tabId(clickedIndex) ) );
drag->setMimeData( groupData );
_sourceItem = tabIndexAt( _dragPoint );
// get tab geometry
QRect geometry( _itemData[clickedIndex]._boundingRect );
// remove space used for buttons
if( _itemData.count() > 1 )
{
geometry.adjust( 0, 0, - buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 );
} else if( !( isActive() && _configuration->drawTitleOutline() ) ) {
geometry.adjust(
buttonsLeftWidth() + layoutMetric( LM_TitleEdgeLeft ) , 0,
-( buttonsRightWidth() + layoutMetric( LM_TitleEdgeRight )), 0 );
}
// adjust geometry to include shadow size
const int shadowSize( shadowCache().shadowSize() );
const bool drawShadow(
compositingActive() &&
KStyle::customStyleHint( QStringLiteral("SH_ArgbDndWindow"), widget() ) &&
shadowSize > 0 );
if( drawShadow )
{ geometry.adjust( -shadowSize, -shadowSize, shadowSize, shadowSize ); }
// compute pixmap and assign
drag->setPixmap( itemDragPixmap( clickedIndex, geometry, drawShadow ) );
// note: the pixmap is moved just above the pointer on purpose
// because overlapping pixmap and pointer slows down the pixmap a lot.
QPoint hotSpot( QPoint( event->pos().x() - geometry.left(), -1 ) );
if( drawShadow ) hotSpot += QPoint( 0, shadowSize );
// make sure the horizontal hotspot position is not too far away (more than 1px)
// from the pixmap
if( hotSpot.x() < -1 ) hotSpot.setX(-1);
if( hotSpot.x() > geometry.width() ) hotSpot.setX( geometry.width() );
drag->setHotSpot( hotSpot );
_dragStartTimer.start( 50, this );
drag->exec( Qt::MoveAction );
// detach tab from window
if( drag->target() == 0 && _itemData.count() > 1 )
{
_itemData.setDirty( true );
untab( tabId(_sourceItem),
widget()->frameGeometry().adjusted(
layoutMetric( LM_OuterPaddingLeft ),
layoutMetric( LM_OuterPaddingTop ),
-layoutMetric( LM_OuterPaddingRight ),
-layoutMetric( LM_OuterPaddingBottom )
).translated( QCursor::pos() - event->pos() +
QPoint( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop )))
);
}
// reset button
_mouseButton = Qt::NoButton;
accepted = true;
}
return accepted;
}
//_____________________________________________________________
bool Client::dragEnterEvent( QDragEnterEvent* event )
{
// check if drag enter is allowed
if( !event->mimeData()->hasFormat( tabDragMimeType() ) || hideTitleBar() ) return false;
// accept event
event->acceptProposedAction();
// animate
if( event->source() != widget() )
{
_itemData.animate( AnimationEnter, tabIndexAt( event->pos(), true ) );
} else if( _itemData.count() > 1 ) {
_itemData.animate( AnimationEnter|AnimationSameTarget, tabIndexAt( event->pos(), true ) );
}
return true;
}
//_____________________________________________________________
bool Client::dragLeaveEvent( QDragLeaveEvent* )
{
if( _itemData.animationType() & AnimationSameTarget )
{
if( _dragStartTimer.isActive() ) _dragStartTimer.stop();
_itemData.animate( AnimationLeave|AnimationSameTarget, _sourceItem );
} else if( _itemData.isAnimated() ) {
_itemData.animate( AnimationLeave );
}
return true;
}
//_____________________________________________________________
bool Client::dragMoveEvent( QDragMoveEvent* event )
{
// check format
if( !event->mimeData()->hasFormat( tabDragMimeType() ) ) return false;
// animate
if( event->source() != widget() )
{
_itemData.animate( AnimationMove, tabIndexAt( event->pos(), true ) );
} else if( _itemData.count() > 1 ) {
if( _dragStartTimer.isActive() ) _dragStartTimer.stop();
_itemData.animate( AnimationMove|AnimationSameTarget, tabIndexAt( event->pos(), true ) );
}
return false;
}
//_____________________________________________________________
bool Client::dropEvent( QDropEvent* event )
{
const QPoint point = event->pos();
_itemData.animate( AnimationNone );
const QMimeData *groupData = event->mimeData();
if( !groupData->hasFormat( tabDragMimeType() ) ) return false;
_itemData.setDirty( true );
if( widget() != event->source() ) setForceActive( true );
const long source = QString::fromUtf8( groupData->data( tabDragMimeType() ) ).toLong();
const int clickedIndex( tabIndexAt( point, true ) );
if( clickedIndex < 0 ) tab_A_behind_B( source, tabId(_itemData.count()-1) );
else tab_A_before_B( source, tabId(clickedIndex) );
// update title
if( widget() == event->source() ) updateTitleRect();
_titleAnimationData->reset();
return true;
}
//_____________________________________________________________
void Client::timerEvent( QTimerEvent* event )
{
if( event->timerId() != _dragStartTimer.timerId() )
{ return KCommonDecoration::timerEvent( event ); }
_dragStartTimer.stop();
// do nothing if there is only one tab
if( _itemData.count() > 1 )
{
_itemData.animate( AnimationMove|AnimationSameTarget, _sourceItem );
_itemData.animate( AnimationLeave|AnimationSameTarget, _sourceItem );
}
}
//_____________________________________________________________
bool Client::closeItem( const Button* button )
{
for( int i=0; i < _itemData.count(); i++ )
{
if( button == _itemData[i]._closeButton.data() )
{
_itemData.setDirty( true );
closeTab( tabId(i) );
return true;
}
}
return false;
}
//________________________________________________________________
QPixmap Client::itemDragPixmap( int index, QRect geometry, bool drawShadow )
{
const bool itemValid( index >= 0 && index < tabCount() );
QPixmap pixmap( geometry.size() );
pixmap.fill( Qt::transparent );
QPainter painter( &pixmap );
painter.setRenderHints(QPainter::SmoothPixmapTransform|QPainter::Antialiasing);
painter.translate( -geometry.topLeft() );
// draw shadows
if( drawShadow )
{
// shadow
const int shadowSize( shadowCache().shadowSize() );
TileSet *tileSet( shadowCache().tileSet( ShadowCache::Key() ) );
tileSet->render( geometry, &painter, TileSet::Ring);
geometry.adjust( shadowSize, shadowSize, -shadowSize, -shadowSize );
renderCorners( &painter, geometry, widget()->palette() );
}
// mask
painter.setClipRegion( helper().roundedMask( geometry ), Qt::IntersectClip );
// render window background
renderWindowBackground( &painter, geometry, widget(), widget()->palette() );
// darken background if item is inactive
const bool itemActive = (tabCount() <= 1) || !( itemValid && tabId(index) != currentTabId() );
if( !itemActive )
{
QLinearGradient lg( geometry.topLeft(), geometry.bottomLeft() );
lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) );
lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) );
painter.setBrush( lg );
painter.setPen( Qt::NoPen );
painter.drawRect( geometry );
}
// render title text
painter.setFont( options()->font(isActive(), false) );
QRect textRect( geometry.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) );
if( itemValid )
{ textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); }
const QString caption( itemValid ? this->caption(index) : this->caption() );
renderTitleText(
&painter, textRect, caption,
titlebarTextColor( widget()->palette(), isActive() && itemActive ),
titlebarContrastColor( widget()->palette() ) );
// adjust geometry for floatFrame when compositing is on.
if( drawShadow )
{ geometry.adjust(-1, -1, 1, 1 ); }
// floating frame
helper().drawFloatFrame(
&painter, geometry, widget()->palette().window().color(),
!drawShadow, false,
KDecoration::options()->color(ColorTitleBar)
);
painter.end();
return pixmap;
}
//_________________________________________________________________
void Client::createSizeGrip( void )
{
assert( !hasSizeGrip() );
if( ( isResizable() && windowId() != 0 ) || isPreview() )
{
_sizeGrip = new SizeGrip( this );
sizeGrip().setVisible( !( isMaximized() || isShade() ) );
}
}
//_________________________________________________________________
void Client::deleteSizeGrip( void )
{
assert( hasSizeGrip() );
_sizeGrip->deleteLater();
_sizeGrip = 0;
}
//_________________________________________________________________
void Client::removeShadowHint( void )
{
// do nothing if no window id
if( !windowId() ) return;
// create atom
if( !_shadowAtom )
{ _shadowAtom = XInternAtom( QX11Info::display(), "_KDE_NET_WM_SHADOW", False); }
XDeleteProperty(QX11Info::display(), windowId(), _shadowAtom);
}
}