/* Gallium-Quartz KWin client Copyright 2001 Karol Szwed http://gallium.n3.net/ Based upon the Win2K kwin client, which is based on the KDE default client. Includes mini titlebars for ToolWindow Support. Button positions are now customizable. */ #include #include #include #include #include #include #include #include #include "../../workspace.h" #include "../../options.h" #include "quartz.h" using namespace KWinInternal; static unsigned char iconify_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 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}; static unsigned char pindown_white_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x1f, 0xa0, 0x03, 0xb0, 0x01, 0x30, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char pindown_gray_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0e, 0x00, 0x06, 0x00, 0x00, 0x80, 0x07, 0xc0, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char pindown_dgray_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x10, 0x70, 0x20, 0x50, 0x20, 0x48, 0x30, 0xc8, 0x38, 0x08, 0x1f, 0x08, 0x18, 0x10, 0x1c, 0x10, 0x0e, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char pinup_white_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x11, 0x3f, 0x15, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char pinup_gray_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0a, 0xbf, 0x0a, 0x80, 0x15, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned char pinup_dgray_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0x40, 0x31, 0x40, 0x2e, 0x40, 0x20, 0x40, 0x20, 0x7f, 0x2a, 0x40, 0x3f, 0xc0, 0x31, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; /////////////////////////////////////////////////////////////////////////// // Titlebar button positions QString* quartzButtonsLeft; QString* quartzButtonsRight; bool stickyButtonOnLeft = true; KPixmap* titleBlocks = NULL; KPixmap* ititleBlocks = NULL; KPixmap* pinDownPix = NULL; KPixmap* pinUpPix = NULL; KPixmap* ipinDownPix = NULL; KPixmap* ipinUpPix = NULL; QuartzHandler* clientHandler; /////////////////////////////////////////////////////////////////////////// QuartzHandler::QuartzHandler() { quartzButtonsLeft = new QString(); quartzButtonsRight = new QString(); readConfig(); createPixmaps(); connect( options, SIGNAL(resetClients()), this, SLOT(slotReset()) ); } QuartzHandler::~QuartzHandler() { delete quartzButtonsLeft; delete quartzButtonsRight; freePixmaps(); } void QuartzHandler::slotReset() { freePixmaps(); readConfig(); createPixmaps(); // Make kwin create new clients for each window Workspace::self()->slotResetAllClientsDelayed(); } void QuartzHandler::readConfig() { KConfig* conf = KGlobal::config(); conf->setGroup("Style"); // Do we want to use custom button positions? if( conf->readBoolEntry("CustomButtonPositions", false) ) { // Read the positions from the config file... *quartzButtonsLeft = conf->readEntry("ButtonsOnLeft", "MS"); *quartzButtonsRight = conf->readEntry("ButtonsOnRight", "HIAX"); } else { // Use defaults *quartzButtonsLeft = "MS"; *quartzButtonsRight = "HIAX"; } // A small hack to make the sticky button look nicer stickyButtonOnLeft = (bool)quartzButtonsLeft->contains( 'S' ); } // This does the colour transition magic. (You say "Oh, is that it?") // This may be made configurable at a later stage void QuartzHandler::drawBlocks( KPixmap *pi, KPixmap &p, const QColor &c1, const QColor &c2 ) { QPainter px; px.begin( pi ); // Draw a background gradient first KPixmapEffect::gradient(p, c1, c2, KPixmapEffect::HorizontalGradient); px.fillRect( 2, 1, 3, 3, c1.light(120) ); px.fillRect( 2, 5, 3, 3, c1 ); px.fillRect( 2, 9, 3, 3, c1.light(110) ); px.fillRect( 2, 13, 3, 3, c1 ); px.fillRect( 6, 1, 3, 3, c1.light(110) ); px.fillRect( 6, 5, 3, 3, c2.light(110) ); px.fillRect( 6, 9, 3, 3, c1.light(120) ); px.fillRect( 6, 13, 3, 3, c2.light(130) ); px.fillRect( 10, 5, 3, 3, c1.light(110) ); px.fillRect( 10, 9, 3, 3, c2.light(120) ); px.fillRect( 10, 13, 3, 3, c2.light(150) ); px.fillRect( 14, 1, 3, 3, c1.dark(110) ); px.fillRect( 14, 9, 3, 3, c2.light(120) ); px.fillRect( 14, 13, 3, 3, c1.dark(120) ); px.fillRect( 18, 5, 3, 3, c1.light(110) ); px.fillRect( 18, 13, 3, 3, c1.dark(110) ); px.fillRect( 22, 9, 3, 3, c2.light(120)); px.fillRect( 22, 13, 3, 3, c2.light(110) ); } // This paints the button pixmaps upon loading the style. void QuartzHandler::createPixmaps() { // Obtain titlebar blend colours, and create the block stuff on pixmaps. QColorGroup g2 = options->colorGroup(Options::TitleBlend, true); QColor c2 = g2.background(); g2 = options->colorGroup(Options::TitleBar, true ); QColor c = g2.background().light(130); titleBlocks = new KPixmap(); titleBlocks->resize( 25, 18 ); drawBlocks( titleBlocks, *titleBlocks, c, c2 ); g2 = options->colorGroup(Options::TitleBlend, false); c2 = g2.background(); g2 = options->colorGroup(Options::TitleBar, false ); c = g2.background().light(130); ititleBlocks = new KPixmap(); ititleBlocks->resize( 25, 18 ); drawBlocks( ititleBlocks, *ititleBlocks, c, c2 ); // Set the sticky pin pixmaps; QPainter p; g2 = options->colorGroup( stickyButtonOnLeft ? Options::TitleBar : Options::TitleBlend, true ); if ( stickyButtonOnLeft ) c = g2.background().light(130); else c = g2.background(); pinUpPix = new KPixmap(); pinUpPix->resize(16, 16); p.begin( pinUpPix ); p.fillRect( 0, 0, 16, 16, c); kColorBitmaps( &p, g2, 0, 0, 16, 16, true, pinup_white_bits, pinup_gray_bits, NULL, pinup_dgray_bits, NULL, NULL ); p.end(); pinDownPix = new KPixmap(); pinDownPix->resize(16, 16); p.begin( pinDownPix ); p.fillRect( 0, 0, 16, 16, c); kColorBitmaps( &p, g2, 0, 0, 16, 16, true, pindown_white_bits, pindown_gray_bits, NULL, pindown_dgray_bits, NULL, NULL ); p.end(); // Inactive pins g2 = options->colorGroup( stickyButtonOnLeft ? Options::TitleBar : Options::TitleBlend, false ); if ( stickyButtonOnLeft ) c = g2.background().light(130); else c = g2.background(); ipinUpPix = new KPixmap(); ipinUpPix->resize(16, 16); p.begin( ipinUpPix ); p.fillRect( 0, 0, 16, 16, c); kColorBitmaps( &p, g2, 0, 0, 16, 16, true, pinup_white_bits, pinup_gray_bits, NULL, pinup_dgray_bits, NULL, NULL ); p.end(); ipinDownPix = new KPixmap(); ipinDownPix->resize(16, 16); p.begin( ipinDownPix ); p.fillRect( 0, 0, 16, 16, c); kColorBitmaps( &p, g2, 0, 0, 16, 16, true, pindown_white_bits, pindown_gray_bits, NULL, pindown_dgray_bits, NULL, NULL ); p.end(); } void QuartzHandler::freePixmaps() { // Title images if (titleBlocks) delete titleBlocks; if (ititleBlocks) delete ititleBlocks; // Sticky pin images if (pinUpPix) delete pinUpPix; if (ipinUpPix) delete ipinUpPix; if (pinDownPix) delete pinDownPix; if (ipinDownPix) delete ipinDownPix; } QuartzButton::QuartzButton(Client *parent, const char *name, bool largeButton, bool isLeftButton, bool isStickyButton, const unsigned char *bitmap ) : QButton(parent, name, WStyle_Customize | WRepaintNoErase | WResizeNoErase | WStyle_NoBorder ) { // Eliminate any possible background flicker setBackgroundMode( QWidget::NoBackground ); setToggleButton( isStickyButton ); client = parent; deco = NULL; large = largeButton; isLeft = isLeftButton; isSticky = isStickyButton; if ( large ) setFixedSize(16, 16); else setFixedSize(10, 10); if(bitmap) setBitmap(bitmap); } QuartzButton::~QuartzButton() { if (deco) delete deco; } QSize QuartzButton::sizeHint() const { if ( large ) return( QSize(16,16) ); else return( QSize(10,10) ); } void QuartzButton::setBitmap(const unsigned char *bitmap) { if (deco) delete deco; deco = new QBitmap(10, 10, bitmap, true); deco->setMask( *deco ); repaint( false ); } void QuartzButton::drawButton(QPainter *p) { QColor c; if (isLeft) c = options->color(Options::TitleBar, client->isActive()).light(130); else c = options->color(Options::TitleBlend, client->isActive()); // Fill the button background with an appropriate color p->fillRect(0, 0, width(), height(), c ); // If we have a decoration bitmap, then draw that // otherwise we paint a menu button (with mini icon), or a sticky button. if( deco ) { int xOff = (width()-10)/2; int yOff = (height()-10)/2; p->setPen( Qt::black ); p->drawPixmap(isDown() ? xOff+2: xOff+1, isDown() ? yOff+2 : yOff+1, *deco); p->setPen( options->color(Options::ButtonBg, client->isActive()).light(150) ); p->drawPixmap(isDown() ? xOff+1: xOff, isDown() ? yOff+1 : yOff, *deco); } else { QPixmap btnpix; int Offset = 0; if (isSticky) { if (isDown()) Offset = 1; // Select the right sticky button to paint if (client->isActive()) { if (isOn()) btnpix = *pinDownPix; else btnpix = *pinUpPix; } else { if (isOn()) btnpix = *ipinDownPix; else btnpix = *ipinUpPix; } } else btnpix = client->miniIcon(); // Shrink the miniIcon for tiny titlebars. if ( !large ) { QPixmap tmpPix; // Smooth scale the image tmpPix.convertFromImage( btnpix.convertToImage().smoothScale(10, 10)); p->drawPixmap( 0, 0, tmpPix ); } else p->drawPixmap( Offset, Offset, btnpix ); } } // Make the protected member public void QuartzButton::turnOn( bool isOn ) { if ( isToggleButton() ) setOn( isOn ); } void QuartzButton::mousePressEvent( QMouseEvent* e ) { last_button = e->button(); QMouseEvent me( e->type(), e->pos(), e->globalPos(), LeftButton, e->state() ); QButton::mousePressEvent( &me ); } void QuartzButton::mouseReleaseEvent( QMouseEvent* e ) { last_button = e->button(); QMouseEvent me( e->type(), e->pos(), e->globalPos(), LeftButton, e->state() ); QButton::mouseReleaseEvent( &me ); } /////////////////////////////////////////////////////////////////////////// QuartzClient::QuartzClient( Workspace *ws, WId w, QWidget *parent, const char *name ) : Client( ws, w, parent, name, WResizeNoErase | WNorthWestGravity | WRepaintNoErase ) { // No flicker thanks setBackgroundMode( QWidget::NoBackground ); // Set button pointers to NULL so we can track things for(int i=0; i < QuartzClient::BtnCount; i++) button[i] = NULL; // Finally, toolWindows look small if ( isTool() ) { titleHeight = 12; largeButtons = false; } else { titleHeight = 18; largeButtons = true; } // Pack the windowWrapper() window within a grid QGridLayout* g = new QGridLayout(this, 0, 0, 0); g->setResizeMode(QLayout::FreeResize); g->addRowSpacing(0, 3); // Top grab bar g->addWidget(windowWrapper(), 3, 1); g->setRowStretch(3, 10); // Wrapped window g->addRowSpacing(2, 1); // line under titlebar g->addRowSpacing(4, 4); // bottom handles g->addColSpacing(0, 4); g->addColSpacing(2, 4); // Pack the titlebar HBox with items hb = new QHBoxLayout(); hb->setResizeMode( QLayout::FreeResize ); g->addLayout ( hb, 1, 1 ); addClientButtons( *quartzButtonsLeft ); titlebar = new QSpacerItem( 10, titleHeight, QSizePolicy::Expanding, QSizePolicy::Minimum ); hb->addItem(titlebar); hb->addSpacing(2); addClientButtons( *quartzButtonsRight, false ); hb->addSpacing(2); } void QuartzClient::addClientButtons( const QString& s, bool isLeft ) { if (s.length() > 0) for(unsigned int i = 0; i < s.length(); i++) { switch( s[i].latin1() ) { // Menu button case 'M': if (!button[BtnMenu]) { button[BtnMenu] = new QuartzButton(this, "menu", largeButtons, isLeft, false, NULL); connect( button[BtnMenu], SIGNAL(pressed()), this, SLOT(menuButtonPressed()) ); hb->addWidget( button[BtnMenu] ); } break; // Sticky button case 'S': if (!button[BtnSticky]) { button[BtnSticky] = new QuartzButton(this, "menu", largeButtons, isLeft, true, NULL); button[BtnSticky]->turnOn( isSticky() ); connect( button[BtnSticky], SIGNAL(clicked()), this, SLOT(toggleSticky()) ); hb->addSpacing(1); hb->addWidget( button[BtnSticky] ); hb->addSpacing(1); } break; // Help button case 'H': if( providesContextHelp() && (!button[BtnHelp]) ) { button[BtnHelp] = new QuartzButton(this, "help", largeButtons, isLeft, true, question_bits); connect( button[BtnHelp], SIGNAL( clicked() ), this, SLOT( contextHelp() )); hb->addWidget( button[BtnHelp] ); } break; // Minimize button case 'I': if ( (!button[BtnIconify]) && isMinimizable()) { button[BtnIconify] = new QuartzButton(this, "iconify", largeButtons, isLeft, true, iconify_bits); connect( button[BtnIconify], SIGNAL( clicked()), this, SLOT(iconify()) ); hb->addWidget( button[BtnIconify] ); } break; // Maximize button case 'A': if ( (!button[BtnMax]) && isMaximizable()) { button[BtnMax] = new QuartzButton(this, "maximize", largeButtons, isLeft, true, maximize_bits); connect( button[BtnMax], SIGNAL( clicked()), this, SLOT(slotMaximize()) ); hb->addWidget( button[BtnMax] ); } break; // Close button case 'X': if (!button[BtnClose]) { button[BtnClose] = new QuartzButton(this, "close", largeButtons, isLeft, true, close_bits); connect( button[BtnClose], SIGNAL( clicked()), this, SLOT(closeWindow()) ); hb->addWidget( button[BtnClose] ); } } } } void QuartzClient::iconChange() { if (button[BtnMenu] && button[BtnMenu]->isVisible()) button[BtnMenu]->repaint(false); } void QuartzClient::slotMaximize() { if ( button[BtnMax]->last_button == MidButton ) maximize( MaximizeVertical ); else if ( button[BtnMax]->last_button == RightButton ) maximize( MaximizeHorizontal ); else maximize(); } void QuartzClient::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 QuartzClient::captionChange( const QString& ) { repaint( titlebar->geometry(), false ); } // Quartz Paint magic goes here. void QuartzClient::paintEvent( QPaintEvent* ) { 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 title color QColorGroup g = options->colorGroup(Options::TitleBar, isActive()); // Draw outer highlights and lowlights p.setPen( g.light().light(120) ); p.drawLine( x, y, x2-1, y ); p.drawLine( x, y+1, x, y2-1 ); p.setPen( g.dark().light(120) ); p.drawLine( x2, y, x2, y2 ); p.drawLine( x, y2, x2, y2 ); // Fill out the border edges p.setPen( g.background().light(130) ); p.drawRect( x+1, y+1, w-2, h-2 ); p.drawRect( x+2, y+2, w-4, h-4 ); // Draw a frame around the wrapped widget. p.setPen( g.background() ); p.drawRect( x+3, y + titleHeight + 3, w-6, h-titleHeight-6 ); // Highlight top corner p.setPen( g.light().light(160) ); p.drawPoint( x, y ); p.setPen( g.light().light(140) ); p.drawPoint( x+1, y ); p.drawPoint( x, y+1 ); // Draw the title bar. // =================== r = titlebar->geometry(); // Obtain titlebar blend colours QColor c1 = options->color(Options::TitleBar, isActive() ).light(130); QColor c2 = options->color(Options::TitleBlend, isActive() ); // Create a disposable pixmap buffer for the titlebar KPixmap* titleBuffer = new KPixmap; titleBuffer->resize( w-6, titleHeight ); QPainter p2( titleBuffer, this ); int rightoffset = r.x()+r.width()-25-4; // subtract titleBlocks pixmap width and some p2.fillRect( 0, 0, w, r.height(), c1 ); p2.fillRect( rightoffset, 0, w-rightoffset-6, r.height(), c2 ); // 8 bit displays will be a bit dithered, but they still look ok. if ( isActive() ) p2.drawPixmap( rightoffset, 0, *titleBlocks ); else p2.drawPixmap( rightoffset, 0, *ititleBlocks ); // Draw the title text on the pixmap, and with a smaller font // for toolwindows than the default. QFont fnt = options->font(true); if ( !largeButtons ) { fnt.setPointSize( fnt.pointSize() - 2 ); // Shrink font by 2pt fnt.setWeight( QFont::Normal ); // and disable bold } p2.setFont( fnt ); p2.setPen( options->color(Options::Font, isActive() )); p2.drawText(r.x(), 0, r.width()-3, r.height()-1, AlignLeft | AlignVCenter, caption() ); p2.end(); p.drawPixmap( 3, 3, *titleBuffer ); delete titleBuffer; } void QuartzClient::showEvent(QShowEvent *ev) { calcHiddenButtons(); show(); Client::showEvent(ev); } void QuartzClient::mouseDoubleClickEvent( QMouseEvent * e ) { if (titlebar->geometry().contains( e->pos() ) ) workspace()->performWindowOperation( this, options->operationTitlebarDblClick() ); workspace()->requestFocus( this ); } void QuartzClient::maximizeChange(bool m) { if (button[BtnMax]) button[BtnMax]->setBitmap(m ? minmax_bits : maximize_bits); } void QuartzClient::activeChange(bool) { for(int i=QuartzClient::BtnHelp; i < QuartzClient::BtnCount; i++) if(button[i]) button[i]->repaint(false); repaint(false); } // The hiding button while shrinking, show button while expanding magic void QuartzClient::calcHiddenButtons() { //Hide buttons in this order - Sticky, Help, Maximize, Menu, Minimize, Close. QuartzButton* btnArray[] = { button[BtnSticky], button[BtnHelp], button[BtnMax], button[BtnMenu], button[BtnIconify], button[BtnClose] }; int minwidth = largeButtons ? 180 : 140; // Start hiding buttons at this width int btn_width = largeButtons ? 16 : 10; int current_width = width(); int count = 0; int i; // Find out how many buttons we have to hide. while (current_width < minwidth) { current_width += btn_width; count++; } // Bound the number of buttons to hide if (count > 6) count = 6; // Hide the required buttons... for(i = 0; i < count; i++) { if (btnArray[i] && btnArray[i]->isVisible() ) btnArray[i]->hide(); } // Show the rest of the buttons... for(i = count; i < 6; i++) { if (btnArray[i] && (!btnArray[i]->isVisible()) ) btnArray[i]->show(); } } // Make sure the menu button follows double click conventions set in kcontrol void QuartzClient::menuButtonPressed() { static QTime* t = 0; static QuartzClient* tc = 0; if ( !t ) t = new QTime; if ( tc != this || t->elapsed() > QApplication::doubleClickInterval() ) { QPoint menupoint ( button[BtnMenu]->rect().bottomLeft().x()-1, button[BtnMenu]->rect().bottomLeft().y()+2 ); workspace()->clientPopup(this)->popup(button[BtnMenu]->mapToGlobal( menupoint )); } else closeWindow(); t->start(); tc = this; } // Extended KWin plugin interface ///////////////////////////////// extern "C" { Client *allocate(Workspace *ws, WId w, int) { return(new QuartzClient(ws, w)); } void init() { clientHandler = new QuartzHandler(); } void reset() { // QuartzHandler takes care of this for us } void deinit() { delete clientHandler; } } #include "quartz.moc"