/* * $Id$ * * Keramik KWin client (version 0.6) * * Copyright (C) 2002 Fredrik Höglund * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include "../../workspace.h" #include "../../options.h" #include "keramik.h" #include "../../../config.h" #include #include #ifdef HAVE_X11_EXTENSIONS_SHAPE_H #include #else #define XShapeCombineMask(a,b,c,d,e,f,g) #endif #include "keramik.moc" using namespace KWinInternal; // ------------------------------------------------------------------------------------------- static const int buttonMargin = 9; static const int buttonSpacing = 4; static const int iconSpacing = 5; static const bool recolorPixmaps = false; // ### only for debugging // Default button layout static const char default_left[] = "M"; static const char default_right[] = "IAX"; // Titlebar button bitmaps static unsigned char menu_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0xf0, 0x07, 0x00, 0xe0, 0x03, 0x00, 0xc0, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char sticky_on_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char sticky_off_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char help_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0xf0, 0x07, 0x00, 0x30, 0x06, 0x00, 0x00, 0x07, 0x00, 0x80, 0x03, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char iconify_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char maximize_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0xc0, 0x03, 0x00, 0xe0, 0x07, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char restore_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0xf0, 0x0f, 0x00, 0xe0, 0x07, 0x00, 0xc0, 0x03, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char close_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0c, 0x00, 0x70, 0x0e, 0x00, 0xe0, 0x07, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03, 0x00, 0xe0, 0x07, 0x00, 0x70, 0x0e, 0x00, 0x30, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // ------------------------------------------------------------------------------------------ KeramikHandler *clientHandler = NULL; bool keramik_initialized = false; // ------------------------------------------------------------------------------------------- KeramikHandler::KeramikHandler() : QObject( NULL, NULL ) { for ( int i = 0; i < NumTiles; i++ ) { activeTiles[i] = NULL; inactiveTiles[i] = NULL; } settings_cache = NULL; // Create the button deco bitmaps buttonDecos[ Menu ] = new QBitmap( 17, 17, menu_bits, true ); buttonDecos[ Sticky ] = new QBitmap( 17, 17, sticky_on_bits, true ); buttonDecos[ Unsticky ] = new QBitmap( 17, 17, sticky_off_bits, true ); buttonDecos[ Help ] = new QBitmap( 17, 17, help_bits, true ); buttonDecos[ Iconify ] = new QBitmap( 17, 17, iconify_bits, true ); buttonDecos[ Maximize ] = new QBitmap( 17, 17, maximize_bits, true ); buttonDecos[ Restore ] = new QBitmap( 17, 17, restore_bits, true ); buttonDecos[ Close ] = new QBitmap( 17, 17, close_bits, true ); // Selfmask the bitmaps for ( int i = 0; i < NumButtonDecos; i++ ) buttonDecos[i]->setMask( *buttonDecos[i] ); // Flip the bitmaps horizontally in right-to-left mode if ( QApplication::reverseLayout() ) { for ( int i = 0; i < NumButtonDecos; i++ ) flip( (QPixmap*)buttonDecos[i] ); } readConfig(); createPixmaps(); keramik_initialized = true; } KeramikHandler::~KeramikHandler() { keramik_initialized = false; destroyPixmaps(); for ( int i = 0; i < NumButtonDecos; i++ ) delete buttonDecos[i]; if ( settings_cache ) delete settings_cache; } void KeramikHandler::createPixmaps() { QColor titleColor = options->color( Options::TitleBar, true ); QColor buttonColor = options->color( Options::ButtonBg, true ); activeTiles[ TitleLeft ] = loadPixmap( "titlebar-active-left", titleColor ); activeTiles[ TitleCenter ] = loadPixmap( "titlebar-active-center", titleColor ); activeTiles[ TitleRight ] = loadPixmap( "titlebar-active-right", titleColor ); inactiveTiles[ TitleLeft ] = loadPixmap( "titlebar-inactive-left", titleColor ); inactiveTiles[ TitleCenter ] = loadPixmap( "titlebar-inactive-center", titleColor ); inactiveTiles[ TitleRight ] = loadPixmap( "titlebar-inactive-right", titleColor ); if ( smallCaptionBubbles ) { activeTiles[ CaptionLeft ] = loadPixmap( "caption-active-small-left", titleColor ); activeTiles[ CaptionCenter ] = loadPixmap( "caption-active-small-center", titleColor ); activeTiles[ CaptionRight ] = loadPixmap( "caption-active-small-right", titleColor ); } else { activeTiles[ CaptionLeft ] = loadPixmap( "caption-active-large-left", titleColor ); activeTiles[ CaptionCenter ] = loadPixmap( "caption-active-large-center", titleColor ); activeTiles[ CaptionRight ] = loadPixmap( "caption-active-large-right", titleColor ); } inactiveTiles[ CaptionLeft ] = loadPixmap( "caption-inactive-left", titleColor ); inactiveTiles[ CaptionCenter ] = loadPixmap( "caption-inactive-center", titleColor ); inactiveTiles[ CaptionRight ] = loadPixmap( "caption-inactive-right", titleColor ); activeTiles[ GrabBarLeft ] = loadPixmap( "grabbar-active-left", titleColor ); activeTiles[ GrabBarCenter ] = loadPixmap( "grabbar-active-center", titleColor ); activeTiles[ GrabBarRight ] = loadPixmap( "grabbar-active-right", titleColor ); inactiveTiles[ GrabBarLeft ] = loadPixmap( "grabbar-inactive-left", titleColor ); inactiveTiles[ GrabBarCenter ] = loadPixmap( "grabbar-inactive-center", titleColor ); inactiveTiles[ GrabBarRight ] = loadPixmap( "grabbar-inactive-right", titleColor ); activeTiles[ BorderLeft ] = loadPixmap( "border-active-left", titleColor ); activeTiles[ BorderRight ] = loadPixmap( "border-active-right", titleColor ); inactiveTiles[ BorderLeft ] = loadPixmap( "border-inactive-left", titleColor ); inactiveTiles[ BorderRight ] = loadPixmap( "border-inactive-right", titleColor ); titleButtonRound = loadPixmap( "titlebutton-round", buttonColor ); titleButtonSquare = loadPixmap( "titlebutton-square", buttonColor ); if ( QApplication::reverseLayout() ) { // Fix lighting flip( activeTiles[CaptionLeft], activeTiles[CaptionRight] ); flip( inactiveTiles[CaptionLeft], inactiveTiles[CaptionRight] ); flip( activeTiles[TitleLeft], activeTiles[TitleRight] ); flip( inactiveTiles[TitleLeft], inactiveTiles[TitleRight] ); flip( activeTiles[BorderLeft], activeTiles[BorderRight] ); flip( inactiveTiles[BorderLeft], inactiveTiles[BorderRight] ); flip( activeTiles[GrabBarLeft], activeTiles[GrabBarRight] ); flip( inactiveTiles[GrabBarLeft], inactiveTiles[GrabBarRight] ); flip( titleButtonRound ); flip( titleButtonSquare ); } } void KeramikHandler::destroyPixmaps() { for ( int i = 0; i < NumTiles; i++ ) { if ( activeTiles[i] ) { delete activeTiles[i]; activeTiles[i] = NULL; } if ( inactiveTiles[i] ) { delete inactiveTiles[i]; inactiveTiles[i] = NULL; } } delete titleButtonRound; delete titleButtonSquare; } void KeramikHandler::flip( QPixmap *&pix1, QPixmap *&pix2 ) { // Flip the pixmaps horizontally QPixmap *tmp = new QPixmap( pix1->xForm( QWMatrix(-1,0,0,1,pix1->width(),0) ) ); delete pix1; pix1 = new QPixmap( pix2->xForm( QWMatrix(-1,0,0,1,pix2->width(),0) ) ); delete pix2; pix2 = tmp; } void KeramikHandler::flip( QPixmap *&pix ) { // Flip the pixmap horizontally QPixmap *tmp = new QPixmap( pix->xForm( QWMatrix(-1,0,0,1,pix->width(),0) ) ); delete pix; pix = tmp; } void KeramikHandler::readConfig() { KConfig *c = new KConfig( "kwinkeramikrc" ); c->setGroup( "General" ); showIcons = c->readBoolEntry( "ShowAppIcons", true ); shadowedText = c->readBoolEntry( "UseShadowedText", true ); smallCaptionBubbles = c->readBoolEntry( "SmallCaptionBubbles", false ); if ( ! settings_cache ) { settings_cache = new SettingsCache; if ( options->customButtonPositions() ) { settings_cache->buttonsLeft = options->titleButtonsLeft(); settings_cache->buttonsRight = options->titleButtonsRight(); } else { settings_cache->buttonsLeft = QString( default_left ); settings_cache->buttonsRight = QString( default_right ); } settings_cache->titleColor = options->color( Options::TitleBar, true ); settings_cache->buttonColor = options->color( Options::ButtonBg, true ); settings_cache->smallCaptionBubbles = smallCaptionBubbles; } delete c; } QPixmap *KeramikHandler::loadPixmap( const QString &name, const QColor &col ) { if ( recolorPixmaps ) { QImage img = qembed_findImage( name ).copy(); //KIconEffect::colorize( img, col, 1.0 ); recolor( img, col ); return new QPixmap( img ); } else return new QPixmap( qembed_findImage(name) ); } // This is the recoloring method from the Keramik widget style, // copyright (c) 2002 Malte Starostik . // Modified to work with 8bpp images. void KeramikHandler::recolor( QImage &img, const QColor& color ) { int hue = -1, sat = 0, val = 228; if ( color.isValid() ) color.hsv( &hue, &sat, &val ); register int pixels = (img.depth() > 8 ? img.width() * img.height() : img.numColors()); register Q_UINT32* data = ( img.depth() > 8 ? reinterpret_cast< Q_UINT32* >( img.bits() ) : reinterpret_cast< Q_UINT32* >( img.colorTable() ) ); for ( int i = 0; i < pixels; i++ ) { QColor c( *data ); int h, s, v; c.hsv( &h, &s, &v ); if ( hue >= 0 && h >= 0 ) h = ( h + 114 + hue ) % 360; if ( s ) s += sat / 2; c.setHsv( h, QMIN( s, 255 ), QMIN( v * val / 228, 255 ) ); *data = ( c.rgb() & RGB_MASK ) | ( *data & ~RGB_MASK ); data++; } } void KeramikHandler::reset() { QString buttonsLeft, buttonsRight; keramik_initialized = false; bool needHardReset = false; bool pixmapsInvalid = false; // Re-read the config file readConfig(); // Check if the color scheme has changed if ( settings_cache->titleColor != options->color(Options::TitleBar, true) || settings_cache->buttonColor != options->color(Options::ButtonBg, true) ) { pixmapsInvalid = true; } // Check if the caption bubble size has changed if ( settings_cache->smallCaptionBubbles != smallCaptionBubbles ) { pixmapsInvalid = true; needHardReset = true; } // Check if button positions have changed if ( options->customButtonPositions() ) { buttonsLeft = options->titleButtonsLeft(); buttonsRight = options->titleButtonsRight(); } else { buttonsLeft = QString( default_left ); buttonsRight = QString( default_right ); } if ( (settings_cache->buttonsLeft != buttonsLeft) || (settings_cache->buttonsRight != buttonsRight) ) { needHardReset = true; } // Update our config cache settings_cache->titleColor = options->color( Options::TitleBar, true ); settings_cache->buttonColor = options->color( Options::ButtonBg, true ); settings_cache->smallCaptionBubbles = smallCaptionBubbles; settings_cache->buttonsLeft = buttonsLeft; settings_cache->buttonsRight = buttonsRight; // Do we need to recreate the pixmaps? if ( pixmapsInvalid ) { destroyPixmaps(); createPixmaps(); } keramik_initialized = true; // Do we need to "hit the wooden hammer" ? if ( needHardReset ) Workspace::self()->slotResetAllClients(); else emit softReset(); } const QPixmap *KeramikHandler::tile( TilePixmap tilePix, bool active ) const { return ( active ? activeTiles[ tilePix ] : inactiveTiles[ tilePix ] ); } // ------------------------------------------------------------------------------------------- KeramikButton::KeramikButton( Client* parent, const char *name, Button btn, const QString &tip ) : KWinButton( parent, name, tip ), client( parent ), button( btn ), hover( false ), lastbutton( 0 ) { setBackgroundMode( NoBackground ); setFixedSize( 17, 17 ); setToggleButton( (button == StickyButton) ); } KeramikButton::~KeramikButton() { // Empty. } void KeramikButton::enterEvent( QEvent *e ) { KWinButton::enterEvent( e ); hover = true; repaint( false ); } void KeramikButton::leaveEvent( QEvent *e ) { KWinButton::leaveEvent( e ); hover = false; repaint( false ); } void KeramikButton::mousePressEvent( QMouseEvent *e ) { lastbutton = e->button(); QMouseEvent me( e->type(), e->pos(), e->globalPos(), LeftButton, e->state() ); KWinButton::mousePressEvent( &me ); } void KeramikButton::mouseReleaseEvent( QMouseEvent *e ) { lastbutton = e->button(); QMouseEvent me( e->type(), e->pos(), e->globalPos(), LeftButton, e->state() ); KWinButton::mouseReleaseEvent( &me ); } void KeramikButton::drawButton( QPainter *p ) { const QPixmap *pix; const QBitmap *deco; // Get the bevel from the client handler if ( button == MenuButton || button == StickyButton || button == HelpButton ) pix = clientHandler->roundButton(); else pix = clientHandler->squareButton(); // Draw the button background p->drawPixmap( 0, 0, *clientHandler->tile( TitleCenter, client->isActive() ), 0, 5, 17, 17 ); if ( isDown() ) { // Pressed p->drawPixmap( QPoint(), *pix, QStyle::visualRect( QRect(34, 0, 17, 17), pix->rect() ) ); p->translate( QApplication::reverseLayout() ? -1 : 1, 1 ); } else if ( hover ) // Mouse over p->drawPixmap( QPoint(), *pix, QStyle::visualRect( QRect(17, 0, 17, 17), pix->rect() ) ); else // Normal p->drawPixmap( QPoint(), *pix, QStyle::visualRect( QRect(0, 0, 17, 17), pix->rect() ) ); // Draw the button deco on the bevel switch ( button ) { case MenuButton: deco = clientHandler->buttonDeco( Menu ); break; case StickyButton: deco = clientHandler->buttonDeco( isOn() ? Unsticky : Sticky ); break; case HelpButton: deco = clientHandler->buttonDeco( Help ); break; case MinButton: deco = clientHandler->buttonDeco( Iconify ); break; case MaxButton: deco = clientHandler->buttonDeco( client->isMaximized() ? Restore : Maximize ); break; case CloseButton: deco = clientHandler->buttonDeco( Close ); break; default: deco = NULL; } p->setPen( Qt::black ); // ### hardcoded color p->drawPixmap( 0, 0, *deco ); } // ------------------------------------------------------------------------------------------ KeramikClient::KeramikClient( Workspace *ws, WId w, QWidget *parent, const char *name ) : Client( ws, w, parent, name, WStaticContents | WResizeNoErase | WRepaintNoErase ), activeIcon( NULL ), inactiveIcon( NULL ), captionBufferDirty( true ), maskDirty( true ) { // Minimize flicker setBackgroundMode( NoBackground ); for ( int i=0; i < NumButtons; i++ ) button[i] = NULL; QVBoxLayout *mainLayout = new QVBoxLayout( this ); QHBoxLayout *titleLayout = new QHBoxLayout(); QHBoxLayout *windowLayout = new QHBoxLayout(); // Button margin int topMargin = clientHandler->titleBarHeight() - clientHandler->titleBarBaseHeight() + 1; mainLayout->addSpacing( topMargin ); mainLayout->addLayout( titleLayout ); // Titlebar mainLayout->addLayout( windowLayout, 1 ); // Left border + window + right border mainLayout->addSpacing( 8 ); // Bottom grab bar + shadow titleLayout->setSpacing( buttonSpacing ); titleLayout->addSpacing( buttonMargin ); // Left button margin addButtons( titleLayout, options->customButtonPositions() ? options->titleButtonsLeft() : QString(default_left) ); titlebar = new QSpacerItem( 10, clientHandler->titleBarHeight() - topMargin, QSizePolicy::Expanding, QSizePolicy::Minimum ); titleLayout->addItem( titlebar ); titleLayout->addSpacing( buttonSpacing ); addButtons( titleLayout, options->customButtonPositions() ? options->titleButtonsRight() : QString(default_right) ); titleLayout->addSpacing( buttonMargin ); // Right button margin windowLayout->addSpacing( 3 ); // Left border windowLayout->addWidget( windowWrapper() ); // Window wrapper windowLayout->addSpacing( 4 ); // Right border connect( clientHandler, SIGNAL(softReset()), SLOT(reset()) ); } KeramikClient::~KeramikClient() { if ( activeIcon ) delete activeIcon; if ( inactiveIcon ) delete inactiveIcon; activeIcon = inactiveIcon = NULL; } void KeramikClient::reset() { calculateCaptionRect(); captionBufferDirty = maskDirty = true; if ( isVisible() ) repaint( false ); } void KeramikClient::addButtons( QHBoxLayout *layout, const QString &s ) { for ( uint i=0; i < s.length(); i++ ) { switch ( s[i].latin1() ) { // Menu button case 'M' : if ( !button[MenuButton] ) { button[MenuButton] = new KeramikButton( this, "menu", MenuButton, i18n("Menu") ); connect( button[MenuButton], SIGNAL( pressed() ), SLOT( menuButtonPressed() ) ); layout->addWidget( button[MenuButton] ); } break; // Sticky button case 'S' : if ( !button[StickyButton] ) { button[StickyButton] = new KeramikButton( this, "sticky", StickyButton, i18n("Sticky") ); connect( button[StickyButton], SIGNAL( clicked() ), SLOT( toggleSticky() ) ); layout->addWidget( button[StickyButton] ); } break; // Help button case 'H' : if ( !button[HelpButton] && providesContextHelp() ) { button[HelpButton] = new KeramikButton( this, "help", HelpButton, i18n("Help") ); connect( button[HelpButton], SIGNAL( clicked() ), SLOT( contextHelp() ) ); layout->addWidget( button[HelpButton] ); } break; // Minimize button case 'I' : if ( !button[MinButton] && isMinimizable() ) { button[MinButton] = new KeramikButton( this, "iconify", MinButton, i18n("Minimize") ); connect( button[MinButton], SIGNAL( clicked() ), SLOT( iconify() ) ); layout->addWidget( button[MinButton] ); } break; // Maximize button case 'A' : if ( !button[MaxButton] && isMaximizable() ) { button[MaxButton] = new KeramikButton( this, "maximize", MaxButton, i18n("Maximize") ); connect( button[MaxButton], SIGNAL( clicked() ), SLOT( slotMaximize() ) ); layout->addWidget( button[MaxButton] ); } break; // Close button case 'X' : if ( !button[CloseButton] ) { button[CloseButton] = new KeramikButton( this, "close", CloseButton, i18n("Close") ); connect( button[CloseButton], SIGNAL( clicked() ), SLOT( closeWindow() ) ); layout->addWidget( button[CloseButton] ); } break; // Additional spacing case '_' : layout->addSpacing( buttonSpacing ); break; } } } void KeramikClient::updateMask() { if ( !keramik_initialized ) return; // This code is written in Xlib to work around a bug somewhere in the // Qt masking/bitmap code. Display *dpy = QPaintDevice::x11AppDisplay(); int screen = QPaintDevice::x11Screen(); Pixmap pix = XCreatePixmap( dpy, handle(), width(), height(), 1 ); const QBitmap *tile; GC gc = XCreateGC( dpy, pix, 0, 0 ); XSetFillStyle( dpy, gc, FillSolid ); // Clear the titlebar area XSetForeground( dpy, gc, BlackPixel(dpy, screen) ); XFillRectangle( dpy, pix, gc, 0, 0, width(), clientHandler->titleBarHeight() ); int titleBaseY = clientHandler->titleBarHeight() - clientHandler->titleBarBaseHeight(); // Set the background and foreground colors for XCopyArea() XSetForeground( dpy, gc, WhitePixel(dpy, screen) ); XSetBackground( dpy, gc, BlackPixel(dpy, screen) ); // Top left corner tile = clientHandler->tile( TitleLeft, isActive() )->mask(); XCopyArea( dpy, tile->handle(), pix, gc, 0, 0, tile->width(), tile->height(), 0, titleBaseY ); // Space between top left & top right corners XFillRectangle( dpy, pix, gc, 15, titleBaseY, width() - 30, clientHandler->titleBarBaseHeight() ); // Caption bubble if ( isActive() && titleBaseY && captionRect.width() >= 28 ) { // Left caption corner tile = clientHandler->tile( CaptionLeft, true )->mask(); XCopyArea( dpy, tile->handle(), pix, gc, 0, 0, tile->width(), tile->height(), captionRect.left(), 0 ); // Caption center XFillRectangle( dpy, pix, gc, captionRect.left() + 14, 0, captionRect.width() - 28, clientHandler->titleBarHeight() ); // Right caption corner tile = clientHandler->tile( CaptionRight, true )->mask(); XCopyArea( dpy, tile->handle(), pix, gc, 0, 0, tile->width(), tile->height(), captionRect.left() + captionRect.width() - 14, 0 ); } // Top right corner tile = clientHandler->tile( TitleRight, true )->mask(); XCopyArea( dpy, tile->handle(), pix, gc, 0, 0, tile->width(), tile->height(), width() - 15, titleBaseY ); // Bottom part of the window XFillRectangle( dpy, pix, gc, 0, clientHandler->titleBarHeight(), width(), height() - clientHandler->titleBarHeight() ); XFreeGC( dpy, gc ); // Set the mask XShapeCombineMask( dpy, handle(), ShapeBounding, 0, 0, pix, ShapeSet ); XFreePixmap( dpy, pix ); maskDirty = false; } void KeramikClient::updateCaptionBuffer() { if ( !keramik_initialized ) return; bool active = isActive(); QPixmap *icon = NULL; if ( captionBuffer.size() != captionRect.size() ) captionBuffer.resize( captionRect.size() ); QPainter p( &captionBuffer ); // Draw the caption bubble p.drawPixmap( 0, 0, *clientHandler->tile( CaptionLeft, active ) ); p.drawTiledPixmap( 14, 0, captionRect.width() - 28, clientHandler->titleBarHeight(), *clientHandler->tile( CaptionCenter, active ) ); p.drawPixmap( captionRect.width() - 14, 0, *clientHandler->tile( CaptionRight, active ) ); if ( clientHandler->showAppIcons() ) { if ( active ) { if ( ! activeIcon ) { if ( miniIcon().width() > 16 ) { QImage img = miniIcon().convertToImage(); img.smoothScale( 16, 16 ); activeIcon = new QPixmap( img ); } else activeIcon = new QPixmap( miniIcon() ); } icon = activeIcon; } else { if ( ! inactiveIcon ) { QImage img = miniIcon().convertToImage(); if ( img.width() > 16 ) img.smoothScale( 16, 16 ); KIconEffect::semiTransparent( img ); inactiveIcon = new QPixmap( img ); } icon = inactiveIcon; } } p.setFont( options->font( active ) ); int tw = p.fontMetrics().width( caption() ) + ( clientHandler->showAppIcons() ? 16 + iconSpacing : 0 ); int xpos = QMAX( (captionRect.width() - tw) / 3, 10 ); QRect tr = QStyle::visualRect( QRect(xpos, 1, captionRect.width() - xpos - 10, captionRect.height() - 4), captionBuffer.rect() ); //p.setPen( Qt::red ); // debug //p.drawRect( tr ); // debug // Application icon if ( clientHandler->showAppIcons() ) { if ( tr.width() > 14 ) { QRect iconRect = QStyle::visualRect( QRect(tr.x(), 1 + (captionRect.height() - 4 - 16) / 2, 16, 16), tr ); QRect r( icon->rect() ); r.moveCenter( iconRect.center() ); p.drawPixmap( r, *icon ); } //p.drawRect( r ); // debug if ( QApplication::reverseLayout() ) tr.addCoords( 0, 0, -(16 + iconSpacing), 0 ); else tr.addCoords( (16 + iconSpacing), 0, 0, 0 ); } // Draw the titlebar text int flags = AlignVCenter | SingleLine; flags |= ( QApplication::reverseLayout() ? AlignRight : AlignLeft ); if ( clientHandler->useShadowedText() ) { p.translate( QApplication::reverseLayout() ? -1 : 1, 1 ); p.setPen( active ? QColor(65,153,238).dark() : QColor(180,210,246).dark() ); // ### hardcoded color p.drawText( tr, flags, caption() ); p.translate( QApplication::reverseLayout() ? 1 : -1, -1 ); } p.setPen( Qt::white ); // ### hardcoded color //p.setPen( options->color( Options::Font, active ) ); p.drawText( tr, flags, caption() ); captionBufferDirty = false; } void KeramikClient::calculateCaptionRect() { QFontMetrics fm( options->font(isActive()) ); int cw = fm.width( caption() ) + 95; int titleBaseY = clientHandler->titleBarHeight() - clientHandler->titleBarBaseHeight(); if ( clientHandler->showAppIcons() ) cw += 16 + 4; // icon width + space cw = QMIN( cw, titlebar->geometry().width() ); captionRect = QStyle::visualRect( QRect(titlebar->geometry().x(), isActive() ? 0 : titleBaseY, cw, clientHandler->titleBarHeight() - ( isActive() ? 0 : titleBaseY )), titlebar->geometry() ); } void KeramikClient::captionChange( const QString& ) { QRect r( captionRect ); calculateCaptionRect(); if ( r.size() != captionRect.size() ) maskDirty = true; captionBufferDirty = true; repaint( r | captionRect, false ); } void KeramikClient::iconChange() { if ( clientHandler->showAppIcons() ) { // Force updateCaptionBuffer() to recreate the cached icons if ( activeIcon ) delete activeIcon; if ( inactiveIcon ) delete inactiveIcon; activeIcon = inactiveIcon = NULL; captionBufferDirty = true; repaint( captionRect, false ); } } void KeramikClient::activeChange( bool ) { // Note: It's assumed that the same font will always be used for both active // and inactive windows, since the fonts kcm hasn't supported setting // different fonts for different window states for some time. if ( clientHandler->titleBarHeight() != clientHandler->titleBarBaseHeight() ) { calculateCaptionRect(); maskDirty = true; } captionBufferDirty = true; repaint( false ); for ( int i=0; i < NumButtons; i++ ) if ( button[i] ) button[i]->repaint(); } void KeramikClient::maximizeChange( bool maximized ) { if ( button[ MaxButton ] ) { button[ MaxButton ]->setTipText( maximized ? i18n("Restore") : i18n("Maximize") ); button[ MaxButton ]->repaint(); } } void KeramikClient::stickyChange( bool on ) { if ( button[ StickyButton ] ) button[ StickyButton ]->setTipText( on ? i18n("Un-Sticky") : i18n("Sticky") ); } void KeramikClient::menuButtonPressed() { static QTime *t = NULL; static KeramikClient *tc = NULL; if ( !t ) t = new QTime; if ( tc != this || t->elapsed() > QApplication::doubleClickInterval() ) { QPoint menuPoint ( button[MenuButton]->rect().bottomLeft().x() - 6, button[MenuButton]->rect().bottomLeft().y() + 3 ); workspace()->clientPopup( this )->popup( button[MenuButton]->mapToGlobal( menuPoint ) ); // Post a fake mouse button release event to the menu button // to ensure that it's redrawn in its unpressed state QApplication::postEvent( button[MenuButton], new QMouseEvent( QEvent::MouseButtonRelease, QPoint(0,0), Qt::LeftButton, Qt::LeftButton ) ); } else closeWindow(); t->start(); tc = this; } void KeramikClient::slotMaximize() { if ( button[ MaxButton ]->lastButton() == MidButton ) maximize( MaximizeVertical ); else if ( button[ MaxButton ]->lastButton() == RightButton ) maximize( MaximizeHorizontal ); else maximize(); } void KeramikClient::paintEvent( QPaintEvent *e ) { if ( !keramik_initialized ) return; QPainter p( this ); QRect updateRect( e->rect() ); bool active = isActive(); int titleBaseY = clientHandler->titleBarHeight() - clientHandler->titleBarBaseHeight(); if ( maskDirty ) updateMask(); // Titlebar // ----------------------------------------------------------------------- if ( updateRect.y() < clientHandler->titleBarHeight() ) { if ( captionBufferDirty ) updateCaptionBuffer(); // Top left corner if ( updateRect.x() < 15 ) p.drawPixmap( 0, titleBaseY, *clientHandler->tile( TitleLeft, active ) ); // Space between the top left corner and the caption bubble if ( updateRect.x() < captionRect.left() && updateRect.right() > 15 ) { int x1 = QMAX( 15, updateRect.x() ); int x2 = QMIN( captionRect.left(), updateRect.right() ); p.drawTiledPixmap( x1, titleBaseY, x2 - x1 + 1, clientHandler->titleBarBaseHeight(), *clientHandler->tile( TitleCenter, active ) ); } // Caption bubble if ( updateRect.x() < captionRect.right() && updateRect.right() > 15 ) { if ( captionRect.width() >= 28 ) p.drawPixmap( captionRect.left(), active ? 0 : titleBaseY, captionBuffer ); else p.drawTiledPixmap( captionRect.x(), titleBaseY, captionRect.width(), clientHandler->titleBarBaseHeight(), *clientHandler->tile( TitleCenter, active ) ); } // Space between the caption bubble and the top right corner if ( updateRect.right() > captionRect.right() && updateRect.x() < width() - 15 ) { int x1 = QMAX( captionRect.right() + 1, updateRect.x() ); int x2 = QMIN( width() - 15, updateRect.right() ); p.drawTiledPixmap( x1, titleBaseY, x2 - x1 + 1, clientHandler->titleBarBaseHeight(), *clientHandler->tile( TitleCenter, active ) ); } // Top right corner if ( updateRect.right() > width() - 15 ) p.drawPixmap( width() - 15, titleBaseY, *clientHandler->tile( TitleRight, active ) ); } // Borders // ----------------------------------------------------------------------- if ( updateRect.bottom() > clientHandler->titleBarHeight() && updateRect.top() < height() - 8 ) { int top = QMAX( clientHandler->titleBarHeight(), updateRect.top() ); int bottom = QMIN( updateRect.bottom(), height() - 8 ); // Left border if ( updateRect.x() <= 4 ) { const QPixmap *tile = clientHandler->tile( BorderLeft, active ); p.drawTiledPixmap( 0, top, tile->width(), bottom - top + 1, *tile ); } // Right border if ( e->rect().right() >= width()-5 ) { const QPixmap *tile = clientHandler->tile( BorderRight, active ); p.drawTiledPixmap( width() - tile->width(), top, tile->width(), bottom - top + 1, *tile ); } } // Bottom grab bar // ----------------------------------------------------------------------- if ( updateRect.bottom() > height() - 8 ) { // Bottom left corner if ( updateRect.x() < 9 ) p.drawPixmap( 0, height() - 8, *clientHandler->tile( GrabBarLeft, active ) ); // Space between the left corner and the right corner if ( updateRect.x() < width() - 9 ) { int x1 = QMAX( 9, updateRect.x() ); int x2 = QMIN( width() - 9, updateRect.right() ); p.drawTiledPixmap( x1, height() - 8, x2 - x1 + 1, 8, *clientHandler->tile( GrabBarCenter, active ) ); } // Bottom right corner if ( updateRect.right() > width() - 9 ) p.drawPixmap( width()-9, height()-8, *clientHandler->tile( GrabBarRight, active ) ); } } void KeramikClient::resizeEvent( QResizeEvent *e ) { Client::resizeEvent( e ); QRect r( captionRect ); calculateCaptionRect(); if ( r.size() != captionRect.size() ) captionBufferDirty = true; maskDirty = true; if ( isVisible() ) { update( rect() ); int dx = 0; int dy = 0; if ( e->oldSize().width() != width() ) dx = 32 + QABS( e->oldSize().width() - width() ); if ( e->oldSize().height() != height() ) dy = 8 + QABS( e->oldSize().height() - height() ); if ( dy ) update( 0, height() - dy + 1, width(), dy ); if ( dx ) { update( width() - dx + 1, 0, dx, height() ); update( QRect( QPoint(4,4), titlebar->geometry().bottomLeft() - QPoint(1,0) ) ); update( QRect( titlebar->geometry().topRight(), QPoint( width() - 4, titlebar->geometry().bottom() ) ) ); // Titlebar needs no paint event QApplication::postEvent( this, new QPaintEvent( titlebar->geometry(), FALSE ) ); } } } void KeramikClient::mouseDoubleClickEvent( QMouseEvent *e ) { if ( QRect( 0, 0, width(), clientHandler->titleBarHeight() ).contains( e->pos() ) ) workspace()->performWindowOperation( this, options->operationTitlebarDblClick() ); } Client::MousePosition KeramikClient::mousePosition( const QPoint &p ) const { int titleBaseY = clientHandler->titleBarHeight() - clientHandler->titleBarBaseHeight(); int leftBorder = clientHandler->tile( BorderLeft, true )->width(); int rightBorder = width() - clientHandler->tile( BorderRight, true )->width() - 1; // Test corners & sides if ( p.x() < leftBorder + 11 ) { if ( p.y() < titleBaseY + 11 ) { if ( (p.y() < titleBaseY + 3 && p.x() < leftBorder + 11) || (p.y() < titleBaseY + 6 && p.x() < leftBorder + 6) || (p.y() < titleBaseY + 11 && p.x() < leftBorder + 3) ) return TopLeft; else return Center; } if ( p.y() > height()-23 ) return BottomLeft; if ( p.x() > leftBorder ) return Center; return Left; } if ( p.x() > rightBorder - 11 ) { if ( p.y() < titleBaseY + 11 ) { if ( (p.y() < titleBaseY + 3 && p.x() > rightBorder - 11) || (p.y() < titleBaseY + 6 && p.x() > rightBorder - 6) || (p.y() < titleBaseY + 11 && p.x() > rightBorder - 3) ) return TopRight; else return Center; } if ( p.y() > height()-23 ) return BottomRight; if ( p.x() < rightBorder ) return Center; return Right; } // Test top & bottom if ( p.y() <= 3 ) return Top; if ( (p.x() < captionRect.left() || p.x() > captionRect.right()) && p.y() < titleBaseY+3 ) return Top; if ( p.y() > height() - 8 ) return Bottom; return Center; } // ------------------------------------------------------------------------------------------- extern "C" { Client *allocate( Workspace *ws, WId w ) { return new KeramikClient( ws, w ); } void init() { clientHandler = new KeramikHandler(); } void reset() { clientHandler->reset(); } void deinit() { delete clientHandler; } } // vim: set noet ts=4 sw=4: