Bogir-decorations/breezesizegrip.cpp
2020-04-25 14:20:33 -07:00

303 lines
9.8 KiB
C++

/*************************************************************************
* Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
*************************************************************************/
#include "breezesizegrip.h"
#include <KDecoration2/DecoratedClient>
#include <QPainter>
#include <QPolygon>
#include <QTimer>
#if BREEZE_HAVE_X11
#include <QX11Info>
#endif
namespace Breeze
{
//* scoped pointer convenience typedef
template <typename T> using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>;
//_____________________________________________
SizeGrip::SizeGrip( Decoration* decoration ):QWidget(nullptr)
,m_decoration( decoration )
{
setAttribute(Qt::WA_NoSystemBackground );
setAutoFillBackground( false );
// cursor
setCursor( Qt::SizeFDiagCursor );
// size
setFixedSize( QSize( GripSize, GripSize ) );
// mask
setMask( QRegion( QVector<QPoint>{
QPoint( 0, GripSize ),
QPoint( GripSize, 0 ),
QPoint( GripSize, GripSize ),
QPoint( 0, GripSize )} ) );
// embed
embed();
updatePosition();
// connections
auto c = decoration->client().data();
connect( c, &KDecoration2::DecoratedClient::widthChanged, this, &SizeGrip::updatePosition );
connect( c, &KDecoration2::DecoratedClient::heightChanged, this, &SizeGrip::updatePosition );
connect( c, &KDecoration2::DecoratedClient::activeChanged, this, &SizeGrip::updateActiveState );
// show
show();
}
//_____________________________________________
SizeGrip::~SizeGrip()
{}
//_____________________________________________
void SizeGrip::updateActiveState()
{
#if BREEZE_HAVE_X11
if( QX11Info::isPlatformX11() )
{
const quint32 value = XCB_STACK_MODE_ABOVE;
xcb_configure_window( QX11Info::connection(), winId(), XCB_CONFIG_WINDOW_STACK_MODE, &value );
xcb_map_window( QX11Info::connection(), winId() );
}
#endif
update();
}
//_____________________________________________
void SizeGrip::embed()
{
#if BREEZE_HAVE_X11
if( !QX11Info::isPlatformX11() ) return;
auto c = m_decoration.data()->client().data();
xcb_window_t windowId = c->windowId();
if( windowId )
{
/*
find client's parent
we want the size grip to be at the same level as the client in the stack
*/
xcb_window_t current = windowId;
auto connection = QX11Info::connection();
xcb_query_tree_cookie_t cookie = xcb_query_tree_unchecked( connection, current );
ScopedPointer<xcb_query_tree_reply_t> tree(xcb_query_tree_reply( connection, cookie, nullptr ) );
if( !tree.isNull() && tree->parent ) current = tree->parent;
// reparent
xcb_reparent_window( connection, winId(), current, 0, 0 );
setWindowTitle( "Breeze::SizeGrip" );
} else {
hide();
}
#endif
}
//_____________________________________________
void SizeGrip::paintEvent( QPaintEvent* )
{
if( !m_decoration ) return;
// get relevant colors
const QColor backgroundColor( m_decoration.data()->titleBarColor() );
// create and configure painter
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing );
painter.setPen( Qt::NoPen );
painter.setBrush( backgroundColor );
// polygon
painter.drawPolygon( QVector<QPoint> {
QPoint( 0, GripSize ),
QPoint( GripSize, 0 ),
QPoint( GripSize, GripSize ),
QPoint( 0, GripSize )} );
}
//_____________________________________________
void SizeGrip::mousePressEvent( QMouseEvent* event )
{
switch (event->button())
{
case Qt::RightButton:
{
hide();
QTimer::singleShot(5000, this, SLOT(show()));
break;
}
case Qt::MidButton:
{
hide();
break;
}
case Qt::LeftButton:
if( rect().contains( event->pos() ) )
{ sendMoveResizeEvent( event->pos() ); }
break;
default: break;
}
}
//_______________________________________________________________________________
void SizeGrip::updatePosition()
{
#if BREEZE_HAVE_X11
if( !QX11Info::isPlatformX11() ) return;
auto c = m_decoration.data()->client().data();
QPoint position(
c->width() - GripSize - Offset,
c->height() - GripSize - Offset );
quint32 values[2] = { quint32(position.x()), quint32(position.y()) };
xcb_configure_window( QX11Info::connection(), winId(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values );
#endif
}
//_____________________________________________
void SizeGrip::sendMoveResizeEvent( QPoint position )
{
#if BREEZE_HAVE_X11
if( !QX11Info::isPlatformX11() ) return;
// pointer to connection
auto connection( QX11Info::connection() );
// client
auto c = m_decoration.data()->client().data();
/*
get root position matching position
need to use xcb because the embedding of the widget
breaks QT's mapToGlobal and other methods
*/
QPoint rootPosition( position );
xcb_get_geometry_cookie_t cookie( xcb_get_geometry( connection, winId() ) );
ScopedPointer<xcb_get_geometry_reply_t> reply( xcb_get_geometry_reply( connection, cookie, nullptr ) );
if( reply )
{
// translate coordinates
xcb_translate_coordinates_cookie_t coordCookie( xcb_translate_coordinates(
connection, winId(), reply.data()->root,
-reply.data()->border_width,
-reply.data()->border_width ) );
ScopedPointer< xcb_translate_coordinates_reply_t> coordReply( xcb_translate_coordinates_reply( connection, coordCookie, nullptr ) );
if( coordReply )
{
rootPosition.rx() += coordReply.data()->dst_x;
rootPosition.ry() += coordReply.data()->dst_y;
}
}
// move/resize atom
if( !m_moveResizeAtom )
{
// create atom if not found
const QString atomName( "_NET_WM_MOVERESIZE" );
xcb_intern_atom_cookie_t cookie( xcb_intern_atom( connection, false, atomName.size(), qPrintable( atomName ) ) );
ScopedPointer<xcb_intern_atom_reply_t> reply( xcb_intern_atom_reply( connection, cookie, nullptr ) );
m_moveResizeAtom = reply ? reply->atom:0;
}
if( !m_moveResizeAtom ) return;
// button release event
xcb_button_release_event_t releaseEvent;
memset(&releaseEvent, 0, sizeof(releaseEvent));
releaseEvent.response_type = XCB_BUTTON_RELEASE;
releaseEvent.event = winId();
releaseEvent.child = XCB_WINDOW_NONE;
releaseEvent.root = QX11Info::appRootWindow();
releaseEvent.event_x = position.x();
releaseEvent.event_y = position.y();
releaseEvent.root_x = rootPosition.x();
releaseEvent.root_y = rootPosition.y();
releaseEvent.detail = XCB_BUTTON_INDEX_1;
releaseEvent.state = XCB_BUTTON_MASK_1;
releaseEvent.time = XCB_CURRENT_TIME;
releaseEvent.same_screen = true;
xcb_send_event( connection, false, winId(), XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char*>(&releaseEvent));
xcb_ungrab_pointer( connection, XCB_TIME_CURRENT_TIME );
// move resize event
xcb_client_message_event_t clientMessageEvent;
memset(&clientMessageEvent, 0, sizeof(clientMessageEvent));
clientMessageEvent.response_type = XCB_CLIENT_MESSAGE;
clientMessageEvent.type = m_moveResizeAtom;
clientMessageEvent.format = 32;
clientMessageEvent.window = c->windowId();
clientMessageEvent.data.data32[0] = rootPosition.x();
clientMessageEvent.data.data32[1] = rootPosition.y();
clientMessageEvent.data.data32[2] = 4; // bottom right
clientMessageEvent.data.data32[3] = Qt::LeftButton;
clientMessageEvent.data.data32[4] = 0;
xcb_send_event( connection, false, QX11Info::appRootWindow(),
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
reinterpret_cast<const char*>(&clientMessageEvent) );
xcb_flush( connection );
#endif
}
}