kwin/clients/redmond/redmond.cpp
Karol Szwed bbdfd0193a - Shade/Un-Shade titlebar flicker fix.
svn path=/trunk/kdebase/kwin/; revision=114652
2001-09-18 11:12:21 +00:00

790 lines
21 KiB
C++

/*
* $Id$
*
* Redmond KWin client
*
* Copyright 2001
* Karol Szwed <gallium@kde.org>
* http://gallium.n3.net/
*
* Based on the default KWin client.
*
* Updated to support toolwindows 3/2001 (KS)
*
*/
#include "redmond.h"
#include <qlayout.h>
#include <qdrawutil.h>
#include <qdatetime.h>
#include <kpixmapeffect.h>
#include <kdrawutil.h>
#include <klocale.h>
#include <qbitmap.h>
#include "../../workspace.h"
#include "../../options.h"
using namespace KWinInternal;
static const char *kdelogo[] = {
/* columns rows colors chars-per-pixel */
"16 16 8 1",
" c None",
". c #000000",
"+ c #A0A0A4",
"@ c #FFFFFF",
"# c #585858",
"$ c #C0C0C0",
"% c #808080",
"& c #DCDCDC",
" ",
" .. .. ",
" .+@. .@#. ",
" .@@@. .@@@# ",
" .@@@..$@@$. ",
" .@@@.@@@$. ",
" .@@@%@@$. ",
" .@@@&@@. ",
" .@@@@@@. ",
" .@@@$@@&. ",
" .@@@.@@@. ",
" .@@@.+@@@. ",
" .@@@..$@@&. ",
" .@@%. .@@@. ",
" .... ... ",
" "};
static unsigned char iconify_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00};
static unsigned char close_bits[] = {
0x00, 0x00, 0x86, 0x01, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0x78, 0x00,
0xcc, 0x00, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00};
static unsigned char maximize_bits[] = {
0xff, 0x01, 0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0xff, 0x01, 0x00, 0x00};
static unsigned char minmax_bits[] = {
0xfc, 0x00, 0xfc, 0x00, 0x84, 0x00, 0xbf, 0x00, 0xbf, 0x00, 0xe1, 0x00,
0x21, 0x00, 0x21, 0x00, 0x3f, 0x00, 0x00, 0x00};
static unsigned char question_bits[] = {
0x00, 0x00, 0x3c, 0x00, 0x66, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00,
0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00};
// Up / Down titlebar button images
static KPixmap *btnPix1;
static KPixmap *iBtnPix1;
static KPixmap *btnDownPix1;
static KPixmap *iBtnDownPix1;
static KPixmap *miniBtnPix1;
static KPixmap *iMiniBtnPix1;
static KPixmap *miniBtnDownPix1;
static KPixmap *iMiniBtnDownPix1;
static QPixmap *defaultMenuPix;
static QColor *btnForeground;
static bool pixmaps_created = false;
static void drawButtonFrame( KPixmap *pix, const QColorGroup &g, bool sunken )
{
QPainter p;
int x2 = pix->width() - 1;
int y2 = pix->height() - 1;
p.begin(pix);
// titlebar button frame
p.setPen( sunken ? Qt::black : g.light());
p.drawLine(0, 0, x2-1, 0);
p.drawLine(0, 0, 0, y2-1);
if (sunken)
{
p.setPen( g.dark() );
p.drawLine(1, 1, x2-2, 1);
p.drawLine(1, 1, 1, y2-2);
}
p.setPen( sunken ? g.light() : g.dark());
p.drawLine(1, y2-1, x2-1, y2-1);
p.drawLine(x2-1, 1, x2-1, y2-1);
p.setPen( sunken ? g.light() : Qt::black);
p.drawLine(0, y2, x2, y2);
p.drawLine(x2, 0, x2, y2);
}
static void create_pixmaps( )
{
if(pixmaps_created)
return;
pixmaps_created = true;
bool highcolor = QPixmap::defaultDepth() > 8;
btnPix1 = new KPixmap;
btnDownPix1 = new KPixmap;
iBtnPix1 = new KPixmap;
iBtnDownPix1 = new KPixmap;
miniBtnPix1 = new KPixmap;
miniBtnDownPix1 = new KPixmap;
iMiniBtnPix1 = new KPixmap;
iMiniBtnDownPix1 = new KPixmap;
defaultMenuPix = new QPixmap(kdelogo);
// buttons (active/inactive, sunken/unsunken)
QColorGroup g = options->colorGroup(Options::ButtonBg, true);
QColor c = g.background();
btnPix1->resize(16, 14);
btnDownPix1->resize(16, 14);
iBtnPix1->resize(16, 14);
iBtnDownPix1->resize(16, 14);
miniBtnPix1->resize(12, 12);
miniBtnDownPix1->resize(12, 12);
iMiniBtnPix1->resize(12, 12);
iMiniBtnDownPix1->resize(12, 12);
if(highcolor)
{
KPixmapEffect::gradient(*btnPix1, c.light(130), c.dark(130),
KPixmapEffect::VerticalGradient);
KPixmapEffect::gradient(*btnDownPix1, c.dark(130), c.light(130),
KPixmapEffect::VerticalGradient);
KPixmapEffect::gradient(*miniBtnPix1, c.light(130), c.dark(130),
KPixmapEffect::VerticalGradient);
KPixmapEffect::gradient(*miniBtnDownPix1, c.dark(130), c.light(130),
KPixmapEffect::VerticalGradient);
g = options->colorGroup(Options::ButtonBg, false);
c = g.background();
KPixmapEffect::gradient(*iBtnPix1, c.light(130), c.dark(130),
KPixmapEffect::VerticalGradient);
KPixmapEffect::gradient(*iBtnDownPix1, c.dark(130), c.light(130),
KPixmapEffect::VerticalGradient);
KPixmapEffect::gradient(*iMiniBtnPix1, c.light(130), c.dark(130),
KPixmapEffect::VerticalGradient);
KPixmapEffect::gradient(*iMiniBtnDownPix1, c.dark(130), c.light(130),
KPixmapEffect::VerticalGradient);
}
else
{
btnPix1->fill(c.rgb());
btnDownPix1->fill(c.rgb());
miniBtnPix1->fill(c.rgb());
miniBtnDownPix1->fill(c.rgb());
g = options->colorGroup(Options::ButtonBg, false);
c = g.background();
iBtnPix1->fill(c.rgb());
iBtnDownPix1->fill(c.rgb());
iMiniBtnPix1->fill(c.rgb());
iMiniBtnDownPix1->fill(c.rgb());
}
g = options->colorGroup(Options::ButtonBg, true);
drawButtonFrame(btnPix1, g, false);
drawButtonFrame(btnDownPix1, g, true);
drawButtonFrame(miniBtnPix1, g, false);
drawButtonFrame(miniBtnDownPix1, g, true);
g = options->colorGroup(Options::ButtonBg, false);
drawButtonFrame(iBtnPix1, g, false);
drawButtonFrame(iBtnDownPix1, g, true);
drawButtonFrame(iMiniBtnPix1, g, false);
drawButtonFrame(iMiniBtnDownPix1, g, true);
// Make sure button pixmaps contrast with the current colour scheme.
if(qGray(options->color(Options::ButtonBg, true).rgb()) > 127)
btnForeground = new QColor(Qt::black);
else
btnForeground = new QColor(Qt::white);
}
void delete_pixmaps()
{
delete btnPix1;
delete btnDownPix1;
delete iBtnPix1;
delete iBtnDownPix1;
delete miniBtnPix1;
delete miniBtnDownPix1;
delete iMiniBtnPix1;
delete iMiniBtnDownPix1;
delete defaultMenuPix;
delete btnForeground;
pixmaps_created = false;
}
GalliumButton::GalliumButton(Client *parent, const char *name,
const unsigned char *bitmap, bool menuButton, bool isMini, const QString& tip)
: KWinButton(parent, name, tip)
{
// Eliminate background flicker
setBackgroundMode( QWidget::NoBackground );
menuBtn = menuButton;
miniBtn = isMini;
client = parent;
// Use larger button for the menu, or mini-buttons for toolwindows.
if ( isMini )
{
setFixedSize(12, 12);
resize(12, 12);
} else
if ( menuButton )
{
setFixedSize(16, 16);
resize(16, 16);
}
else
{
setFixedSize(16, 14);
resize(16, 14);
}
if(bitmap)
setBitmap(bitmap);
}
QSize GalliumButton::sizeHint() const
{
if ( miniBtn )
return(QSize(12, 12));
if ( menuBtn )
return(QSize(16, 16));
else
return(QSize(16, 14));
}
void GalliumButton::reset()
{
repaint(false);
}
void GalliumButton::setBitmap(const unsigned char *bitmap)
{
pix.resize(0, 0);
deco = QBitmap(10, 10, bitmap, true);
deco.setMask(deco);
repaint( false );
}
void GalliumButton::setPixmap( const QPixmap &p )
{
deco.resize(0, 0);
pix = p;
if (miniBtn)
setMask(QRect(0, 0, 12, 12));
else if (menuBtn)
setMask(QRect(0, 0, 16, 16));
else
setMask(QRect(0, 0, 16, 14));
repaint( false );
}
void GalliumButton::mousePressEvent( QMouseEvent* e )
{
last_button = e->button();
QMouseEvent me ( e->type(), e->pos(), e->globalPos(),
LeftButton, e->state() );
KWinButton::mousePressEvent( &me );
}
void GalliumButton::mouseReleaseEvent( QMouseEvent* e )
{
last_button = e->button();
QMouseEvent me ( e->type(), e->pos(), e->globalPos(),
LeftButton, e->state() );
KWinButton::mouseReleaseEvent( &me );
}
void GalliumButton::drawButton(QPainter *p)
{
if(pix.isNull())
{
if(client->isActive())
{
if(isDown())
p->drawPixmap(0, 0, miniBtn ? *miniBtnDownPix1 : *btnDownPix1);
else
p->drawPixmap(0, 0, miniBtn ? *miniBtnPix1 : *btnPix1);
}
else
{
if(isDown())
p->drawPixmap(0, 0, miniBtn ? *iMiniBtnDownPix1 : *iBtnDownPix1);
else
p->drawPixmap(0, 0, miniBtn ? *iMiniBtnPix1 : *iBtnPix1);
}
p->setPen( *btnForeground );
int xOff = (width()-10)/2;
int yOff = (height()-10)/2;
p->drawPixmap(isDown() ? xOff+1: xOff, isDown() ? yOff+1 : yOff, deco);
}
else
{
p->fillRect(0, 0, width(), height(),
options->color(Options::TitleBar, client->isActive()));
if ( menuBtn && miniBtn )
{
QPixmap tmpPix;
// Smooth scale the menu button pixmap
tmpPix.convertFromImage( pix.convertToImage().smoothScale(12, 12));
p->drawPixmap( 0, 0, tmpPix );
} else
p->drawPixmap( 0, 0, pix );
}
}
GalliumClient::GalliumClient( Workspace *ws, WId w, QWidget *parent,
const char *name )
: Client( ws, w, parent, name, WResizeNoErase | WNorthWestGravity |
WRepaintNoErase )
{
setBackgroundMode( QWidget::NoBackground );
// Finally, toolwindows look small
if ( isTool() ) {
titleHeight = 14;
smallButtons = true;
} else {
titleHeight = 18;
smallButtons = false;
}
lastButtonWidth = 0;
QGridLayout* g = new QGridLayout(this, 0, 0, 0);
g->setResizeMode(QLayout::FreeResize);
g->addRowSpacing(0, 4); // Top grab bar
g->addWidget(windowWrapper(), 3, 1);
// without the next line, unshade flickers
g->addItem( new QSpacerItem( 0, 0, QSizePolicy::Fixed,
QSizePolicy::Expanding ) );
g->setRowStretch(3, 10); // Wrapped window
g->addRowSpacing(4, 4); // bottom handles
g->addRowSpacing(2, 1); // Line below title bar
g->addColSpacing(0, 4);
g->addColSpacing(2, 4);
button[BtnMenu] = new GalliumButton(this, "menu", NULL, true, smallButtons,
i18n("Menu"));
button[BtnClose] = new GalliumButton(this, "close", close_bits, false, smallButtons,
i18n("Close"));
button[BtnIconify] = new GalliumButton(this, "iconify", iconify_bits, false, smallButtons,
i18n("Minimize"));
button[BtnMax] = new GalliumButton(this, "maximize", maximize_bits, false, smallButtons,
i18n("Maximize"));
// Connect required stuff together
connect( button[BtnMenu], SIGNAL(pressed()), this, SLOT( menuButtonPressed() ));
connect( button[BtnClose], SIGNAL( clicked() ), this, SLOT( closeWindow() ));
connect( button[BtnIconify], SIGNAL( clicked() ), this, SLOT( iconify() ));
connect( button[BtnMax], SIGNAL( clicked() ), this, SLOT( slotMaximize() ));
connect( options, SIGNAL(resetClients()), this, SLOT( slotReset() ));
// Pack the titleBar hbox with items
hb = new QHBoxLayout();
hb->setResizeMode(QLayout::FreeResize);
g->addLayout( hb, 1, 1 );
hb->addSpacing(2);
hb->addWidget( button[BtnMenu] );
titlebar = new QSpacerItem(10, titleHeight, QSizePolicy::Expanding, QSizePolicy::Minimum);
hb->addItem(titlebar);
hb->addSpacing(2);
if( providesContextHelp() )
{
button[BtnHelp] = new GalliumButton(this, "help", question_bits, false, smallButtons,
i18n("Help"));
connect( button[BtnHelp], SIGNAL( clicked() ), this, SLOT( contextHelp() ));
hb->addWidget( button[BtnHelp] );
}
else
button[BtnHelp] = NULL;
hb->addWidget( button[BtnIconify] );
hb->addWidget( button[BtnMax] );
hb->addSpacing(2);
hb->addWidget( button[BtnClose] );
hb->addSpacing(2);
// Hide buttons which are not required
// We can un-hide them if required later
if ( !isMinimizable() )
button[BtnIconify]->hide();
if ( !isMaximizable() )
button[BtnMax]->hide();
hiddenItems = false;
// Make sure that the menu button uses the correct mini-icon
iconChange();
}
void GalliumClient::slotReset()
{
// 0 to 3 ( 4 buttons - Help, Max, Iconify, Close )
for(int i = GalliumClient::BtnHelp; i <= GalliumClient::BtnClose; i++)
if(button[i])
button[i]->reset();
// The menu is reset by iconChange()
repaint( false );
}
void GalliumClient::iconChange()
{
if(!miniIcon().isNull())
button[BtnMenu]->setPixmap(miniIcon());
else
button[BtnMenu]->setPixmap(*defaultMenuPix);
if (button[BtnMenu]->isVisible())
button[BtnMenu]->repaint(false);
}
void GalliumClient::slotMaximize()
{
if ( button[BtnMax]->last_button == MidButton )
maximize( MaximizeVertical );
else if ( button[BtnMax]->last_button == RightButton )
maximize( MaximizeHorizontal );
else
maximize();
}
void GalliumClient::resizeEvent( QResizeEvent* e)
{
Client::resizeEvent( e );
calcHiddenButtons();
if (isVisibleToTLW())
{
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 GalliumClient::captionChange( const QString& )
{
repaint( titlebar->geometry(), false );
}
void GalliumClient::paintEvent( QPaintEvent* )
{
bool hicolor = QPixmap::defaultDepth() > 8;
int fontoffset = 1;
QPainter p(this);
// Obtain widget bounds.
QRect r(rect());
int x = r.x();
int y = r.y();
int x2 = r.width()-1;
int y2 = r.height()-1;
int w = r.width();
int h = r.height();
// Draw part of the frame that is the frame color
// ==============================================
QColorGroup g = options->colorGroup(Options::Frame, isActive());
p.setPen( g.background() );
p.drawLine( x, y, x2-1, y );
p.drawLine( x, y, x, y2-1 );
// Draw line under title bar
p.drawLine( x + 4, y + titleHeight + 4, x2 - 4, y + titleHeight + 4 );
// Draw a hidden line that appears during shading
p.drawLine( x + 4, y2 - 4, x2 - 4, y2 - 4 );
// Fill out the border edges
p.drawRect( x+2, y+2, w-4, h-4 );
p.drawRect( x+3, y+3, w-6, h-6 );
// Draw highlights and lowlights
p.setPen(g.light());
p.drawLine( x+1, y+1, x2-2, y+1);
p.drawLine( x+1, y+1, x+1, y2-2);
p.setPen(g.dark());
p.drawLine( x2-1, y+1, x2-1, y2-1);
p.drawLine( x+1, y2-1, x2-1, y2-1);
// Draw black edges
p.setPen( Qt::black );
p.drawLine(x2, y, x2, y2);
p.drawLine(x, y2, x2, y2);
// Draw the title bar.
// ===================
r = titlebar->geometry();
QFontMetrics fm(options->font(true));
// Obtain blend colours.
QColor c1 = options->color(Options::TitleBar, isActive() );
QColor c2 = options->color(Options::TitleBlend, isActive() );
// Paint without a buffer if the colours are the same to
// improve performance, and only draw gradients on hicolor displays.
if ((c1 != c2) && hicolor)
{
// KS - Add gradient caching if needed at a later stage.
// Create a disposable pixmap buffer for the title blend
KPixmap* titleBuffer = new KPixmap;
titleBuffer->resize(w-8, titleHeight);
KPixmapEffect::gradient(*titleBuffer, c1, c2,
KPixmapEffect::HorizontalGradient);
QPainter p2( titleBuffer, this );
// Since drawing the gradient is (relatively) slow, it is best
// to draw the title text on the pixmap.
// Reduce the font size and weight for toolwindows.
QFont fnt = options->font(true);
if ( smallButtons )
{
fnt.setPointSize( fnt.pointSize() - 2 ); // Shrink font by 2 pt.
fnt.setWeight( QFont::Normal );
fontoffset = 0;
}
p2.setFont( fnt );
p2.setPen( options->color(Options::Font, isActive() ));
p2.drawText( r.x(), fontoffset, r.width()-3, r.height()-1,
AlignLeft | AlignVCenter, caption() );
p2.end();
p.drawPixmap( 4, 4, *titleBuffer );
delete titleBuffer;
} else
{
// Assume lower ended hardware, so don't use buffers.
// Don't draw a gradient either.
p.fillRect( 4, 4, w-8, titleHeight, c1 );
// Draw the title text.
QFont fnt = options->font(true);
if ( smallButtons )
{
fnt.setPointSize( fnt.pointSize() - 2 ); // Shrink font by 2 pt.
fnt.setWeight( QFont::Normal );
fontoffset = 0;
}
p.setFont( fnt );
p.setPen(options->color(Options::Font, isActive() ));
p.drawText(r.x()+4, r.y()+fontoffset, r.width()-3, r.height()-1,
AlignLeft | AlignVCenter, caption() );
}
}
void GalliumClient::showEvent(QShowEvent *ev)
{
calcHiddenButtons();
show();
Client::showEvent(ev);
}
void GalliumClient::mouseDoubleClickEvent( QMouseEvent * e )
{
if (titlebar->geometry().contains( e->pos() ) )
workspace()->performWindowOperation( this, options->operationTitlebarDblClick() );
}
void GalliumClient::maximizeChange(bool m)
{
button[BtnMax]->setBitmap(m ? minmax_bits : maximize_bits);
button[BtnMax]->setTipText(m ? i18n("Restore") : i18n("Maximize"));
}
void GalliumClient::activeChange(bool)
{
if(!miniIcon().isNull())
{
button[BtnMenu]->setPixmap(miniIcon());
}
else
button[BtnMenu]->setPixmap(kdelogo);
for(int i=GalliumClient::BtnHelp; i < GalliumClient::BtnMenu; i++)
{
if(button[i])
button[i]->reset();
}
// Reset the menu button ?
repaint(false);
}
void GalliumClient::calcHiddenButtons()
{
// order of hiding is help, maximize, minimize, close, then menu;
int minWidth = 32 + 16*4 + (providesContextHelp() ? 16*2 : 16 );
if(lastButtonWidth > width()) // Shrinking
{
lastButtonWidth = width();
if(width() < minWidth)
{
hiddenItems = true;
for(int i = GalliumClient::BtnHelp; i <= GalliumClient::BtnMenu; i++)
{
if(button[i])
{
if( !button[i]->isHidden() )
{
button[i]->hide();
}
minWidth -= button[i]->sizeHint().width();
if(width() >= minWidth)
return;
}
}
}
}
else
if(hiddenItems) // Expanding
{
lastButtonWidth = width();
int totalSize = 16*3;
for(int i = GalliumClient::BtnMenu; i >= GalliumClient::BtnHelp; i--)
{
if(button[i])
{
if(button[i]->sizeHint().width() + totalSize <= width())
{
totalSize += button[i]->sizeHint().width();
button[i]->resize(button[i]->sizeHint());
button[i]->show();
}
else
return;
}
}
// all items shown now
hiddenItems = false;
}
else
lastButtonWidth = width();
}
void GalliumClient::menuButtonPressed()
{
static QTime* t = 0;
static GalliumClient* tc = 0;
if ( !t )
t = new QTime;
if ( tc != this || t->elapsed() > QApplication::doubleClickInterval() )
{
// KS - move the menu left by 3 pixels, and down 2 pixels.
QPoint menupoint ( button[BtnMenu]->rect().bottomLeft().x()-3,
button[BtnMenu]->rect().bottomLeft().y()+2 );
workspace()->clientPopup(this)->popup(
button[BtnMenu]->mapToGlobal( menupoint ));
}
else {
closeWindow();
}
t->start();
tc = this;
}
extern "C"
{
Client *allocate(Workspace *ws, WId w, int)
{
return(new GalliumClient(ws, w));
}
void init()
{
create_pixmaps();
}
void reset()
{
delete_pixmaps();
create_pixmaps();
Workspace::self()->slotResetAllClientsDelayed();
}
void deinit()
{
delete_pixmaps();
}
}
#include "redmond.moc"
// vim: ts=4