diff options
Diffstat (limited to 'kwin/clients/b2/b2client.cpp')
-rw-r--r-- | kwin/clients/b2/b2client.cpp | 1433 |
1 files changed, 1433 insertions, 0 deletions
diff --git a/kwin/clients/b2/b2client.cpp b/kwin/clients/b2/b2client.cpp new file mode 100644 index 000000000..6a5d821b2 --- /dev/null +++ b/kwin/clients/b2/b2client.cpp @@ -0,0 +1,1433 @@ +/* + * B-II KWin Client + * + * Changes: + * Customizable button positions by Karol Szwed <gallium@kde.org> + * + * Thin frame in fixed size windows, titlebar gradient support, accessibility + * improvements, customizable menu double click action and button hover + * effects are + * Copyright (c) 2003,2004 Luciano Montanaro <mikelima@cirulla.net> + */ + +#include "b2client.h" +#include <qapplication.h> +#include <qlayout.h> +#include <qdrawutil.h> +#include <kpixmapeffect.h> +#include <kimageeffect.h> +#include <kicontheme.h> +#include <kiconeffect.h> +#include <kdrawutil.h> +#include <klocale.h> +#include <kconfig.h> +#include <qbitmap.h> +#include <qlabel.h> +#include <qtooltip.h> + +#include <X11/Xlib.h> + +namespace B2 { + +#include "bitmaps.h" + +enum { + Norm = 0, + Hover, Down, INorm, IHover, IDown, + NumStates +}; + +enum { + P_CLOSE = 0, + P_MAX, P_NORMALIZE, P_ICONIFY, P_PINUP, P_MENU, P_HELP, P_SHADE, P_RESIZE, + P_NUM_BUTTON_TYPES +}; + +#define NUM_PIXMAPS (P_NUM_BUTTON_TYPES * NumStates) + +static KPixmap *pixmap[NUM_PIXMAPS]; + +// active +#define PIXMAP_A(i) (pixmap[(i) * NumStates + Norm]) +// active, hover +#define PIXMAP_AH(i) (pixmap[(i) * NumStates + Hover]) +// active, down +#define PIXMAP_AD(i) (pixmap[(i) * NumStates + Down]) +// inactive +#define PIXMAP_I(i) (pixmap[(i) * NumStates + INorm]) +// inactive, hover +#define PIXMAP_IH(i) (pixmap[(i) * NumStates + IHover]) +// inactive, down +#define PIXMAP_ID(i) (pixmap[(i) * NumStates + IDown]) + +static KPixmap* titleGradient[2] = {0, 0}; + +static int thickness = 4; // Frame thickness +static int buttonSize = 16; + +enum DblClickOperation { + NoOp = 0, + MinimizeOp, + ShadeOp, + CloseOp +}; + +static DblClickOperation menu_dbl_click_op = NoOp; + +static bool pixmaps_created = false; +static bool colored_frame = false; +static bool do_draw_handle = true; +static bool drawSmallBorders = false; + +// ===================================== + +extern "C" KDE_EXPORT KDecorationFactory* create_factory() +{ + return new B2::B2ClientFactory(); +} + +// ===================================== + +static inline const KDecorationOptions *options() +{ + return KDecoration::options(); +} + +static void redraw_pixmaps(); + +static void read_config(B2ClientFactory *f) +{ + // Force button size to be in a reasonable range. + // If the frame width is large, the button size must be large too. + buttonSize = (QFontMetrics(options()->font(true)).height() + 1) & 0x3e; + if (buttonSize < 16) buttonSize = 16; + + KConfig conf("kwinb2rc"); + conf.setGroup("General"); + colored_frame = conf.readBoolEntry("UseTitleBarBorderColors", false); + do_draw_handle = conf.readBoolEntry("DrawGrabHandle", true); + drawSmallBorders = !options()->moveResizeMaximizedWindows(); + + QString opString = conf.readEntry("MenuButtonDoubleClickOperation", "NoOp"); + if (opString == "Close") { + menu_dbl_click_op = B2::CloseOp; + } else if (opString == "Minimize") { + menu_dbl_click_op = B2::MinimizeOp; + } else if (opString == "Shade") { + menu_dbl_click_op = B2::ShadeOp; + } else { + menu_dbl_click_op = B2::NoOp; + } + + switch (options()->preferredBorderSize(f)) { + case KDecoration::BorderTiny: + thickness = 2; + break; + case KDecoration::BorderLarge: + thickness = 5; + break; + case KDecoration::BorderVeryLarge: + thickness = 8; + break; + case KDecoration::BorderHuge: + thickness = 12; + break; + case KDecoration::BorderVeryHuge: + case KDecoration::BorderOversized: + case KDecoration::BorderNormal: + default: + thickness = 4; + } +} + +static void drawB2Rect(KPixmap *pix, const QColor &primary, bool down) +{ + QPainter p(pix); + QColor hColor = primary.light(150); + QColor lColor = primary.dark(150); + + if (down) qSwap(hColor, lColor); + + if (QPixmap::defaultDepth() > 8) { + KPixmapEffect::gradient(*pix, hColor, lColor, + KPixmapEffect::DiagonalGradient); + } + else + pix->fill(primary); + int x2 = pix->width() - 1; + int y2 = pix->height() - 1; + p.setPen(lColor); + p.drawLine(0, 0, x2, 0); + p.drawLine(0, 0, 0, y2); + p.drawLine(1, x2 - 1, x2 - 1, y2 - 1); + p.drawLine(x2 - 1, 1, x2 - 1, y2 - 1); + p.setPen(hColor); + p.drawRect(1, 1, x2, y2); + +} + +QPixmap* kwin_get_menu_pix_hack() +{ + //return menu_pix; FIXME + return PIXMAP_A(P_MENU); +} + +static void create_pixmaps() +{ + if (pixmaps_created) + return; + pixmaps_created = true; + + int i; + int bsize = buttonSize - 2; + if (bsize < 16) bsize = 16; + + for (i = 0; i < NUM_PIXMAPS; i++) { + pixmap[i] = new KPixmap; + switch (i / NumStates) { + case P_MAX: // will be initialized by copying P_CLOSE + case P_RESIZE: + break; + case P_ICONIFY: + pixmap[i]->resize(10, 10); break; + case P_SHADE: + case P_CLOSE: + pixmap[i]->resize(bsize, bsize); break; + default: + pixmap[i]->resize(16, 16); break; + } + } + + // there seems to be no way to load X bitmaps from data properly, so + // we need to create new ones for each mask :P + QBitmap pinupMask(16, 16, pinup_mask_bits, true); + PIXMAP_A(P_PINUP)->setMask(pinupMask); + PIXMAP_I(P_PINUP)->setMask(pinupMask); + QBitmap pindownMask(16, 16, pindown_mask_bits, true); + PIXMAP_AD(P_PINUP)->setMask(pindownMask); + PIXMAP_ID(P_PINUP)->setMask(pindownMask); + + QBitmap menuMask(16, 16, menu_mask_bits, true); + for (i = 0; i < NumStates; i++) + pixmap[P_MENU * NumStates + i]->setMask(menuMask); + + QBitmap helpMask(16, 16, help_mask_bits, true); + for (i = 0; i < NumStates; i++) + pixmap[P_HELP * NumStates + i]->setMask(helpMask); + + QBitmap normalizeMask(16, 16, true); + // draw normalize icon mask + QPainter mask; + mask.begin(&normalizeMask); + + QBrush one(Qt::color1); + mask.fillRect(normalizeMask.width() - 12, normalizeMask.height() - 12, + 12, 12, one); + mask.fillRect(0, 0, 10, 10, one); + mask.end(); + + for (i = 0; i < NumStates; i++) + pixmap[P_NORMALIZE * NumStates + i]->setMask(normalizeMask); + + QBitmap shadeMask(bsize, bsize, true); + mask.begin(&shadeMask); + mask.fillRect(0, 0, bsize, 6, one); + mask.end(); + for (i = 0; i < NumStates; i++) + pixmap[P_SHADE * NumStates + i]->setMask(shadeMask); + + titleGradient[0] = 0; + titleGradient[1] = 0; + + redraw_pixmaps(); +} + +static void delete_pixmaps() +{ + for (int i = 0; i < NUM_PIXMAPS; i++) { + delete pixmap[i]; + pixmap[i] = 0; + } + for (int i = 0; i < 2; i++) { + delete titleGradient[i]; + titleGradient[i] = 0; + } + pixmaps_created = false; +} + +// ===================================== + +B2ClientFactory::B2ClientFactory() +{ + read_config(this); + create_pixmaps(); +} + +B2ClientFactory::~B2ClientFactory() +{ + delete_pixmaps(); +} + +KDecoration *B2ClientFactory::createDecoration(KDecorationBridge *b) +{ + return new B2::B2Client(b, this); +} + +bool B2ClientFactory::reset(unsigned long changed) +{ + bool needsReset = SettingColors ? true : false; + // TODO Do not recreate decorations if it is not needed. Look at + // ModernSystem for how to do that + read_config(this); + if (changed & SettingFont) { + delete_pixmaps(); + create_pixmaps(); + needsReset = true; + } + redraw_pixmaps(); + // For now just return true. + return needsReset; +} + +bool B2ClientFactory::supports( Ability ability ) +{ + switch( ability ) + { + case AbilityAnnounceButtons: + case AbilityButtonMenu: + case AbilityButtonOnAllDesktops: + case AbilityButtonSpacer: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + case AbilityButtonResize: + return true; + default: + return false; + }; +} + +QValueList< B2ClientFactory::BorderSize > B2ClientFactory::borderSizes() const +{ + // the list must be sorted + return QValueList< BorderSize >() << BorderTiny << BorderNormal << + BorderLarge << BorderVeryLarge << BorderHuge; +} + +// ===================================== + +void B2Client::maxButtonClicked() +{ + maximize(button[BtnMax]->last_button); +} + +void B2Client::shadeButtonClicked() +{ + setShade(!isSetShade()); +} + +void B2Client::resizeButtonPressed() +{ + performWindowOperation(ResizeOp); +} + +B2Client::B2Client(KDecorationBridge *b, KDecorationFactory *f) + : KDecoration(b, f), bar_x_ofs(0), in_unobs(0) +{ +} + +void B2Client::init() +{ + const QString tips[] = { + i18n("Menu"), + isOnAllDesktops() ? + i18n("Not on all desktops") : i18n("On all desktops"), + i18n("Minimize"), i18n("Maximize"), + i18n("Close"), i18n("Help"), + isSetShade() ? i18n("Unshade") : i18n("Shade"), + i18n("Resize") + }; + + // Check this early, otherwise the preview will be rendered badly. + resizable = isResizable(); + + createMainWidget(WResizeNoErase | WRepaintNoErase); + widget()->installEventFilter(this); + + widget()->setBackgroundMode(NoBackground); + + // Set button pointers to NULL so we know what has been created + for (int i = 0; i < BtnCount; i++) + button[i] = NULL; + + g = new QGridLayout(widget(), 3, 3); + // Left and right border width + + leftSpacer = new QSpacerItem(thickness, 16, + QSizePolicy::Fixed, QSizePolicy::Expanding); + rightSpacer = new QSpacerItem(thickness, 16, + QSizePolicy::Fixed, QSizePolicy::Expanding); + + g->addItem(leftSpacer, 1, 0); + g->addItem(rightSpacer, 1, 2); + + // Top border height + topSpacer = new QSpacerItem(10, buttonSize + 4, + QSizePolicy::Expanding, QSizePolicy::Fixed); + g->addItem(topSpacer, 0, 1); + + // Bottom border height. + bottomSpacer = new QSpacerItem(10, + thickness + (mustDrawHandle() ? 4 : 0), + QSizePolicy::Expanding, QSizePolicy::Fixed); + g->addItem(bottomSpacer, 2, 1); + if (isPreview()) { + QLabel *previewLabel = new QLabel( + i18n("<b><center>B II preview</center></b>"), + widget()); + g->addWidget(previewLabel, 1, 1); + + } else { + g->addItem(new QSpacerItem(0, 0), 1, 1); + } + + // titlebar + g->setRowSpacing(0, buttonSize + 4); + + titlebar = new B2Titlebar(this); + titlebar->setMinimumWidth(buttonSize + 4); + titlebar->setFixedHeight(buttonSize + 4); + + QBoxLayout *titleLayout = new QBoxLayout(titlebar, + QBoxLayout::LeftToRight, 0, 1, 0); + titleLayout->addSpacing(3); + + if (options()->customButtonPositions()) { + addButtons(options()->titleButtonsLeft(), tips, titlebar, titleLayout); + titleLayout->addItem(titlebar->captionSpacer); + addButtons(options()->titleButtonsRight(), tips, titlebar, titleLayout); + } else { + addButtons("MSH", tips, titlebar, titleLayout); + titleLayout->addItem(titlebar->captionSpacer); + addButtons("IAX", tips, titlebar, titleLayout); + } + + titleLayout->addSpacing(3); + + QColor c = options()->colorGroup(KDecoration::ColorTitleBar, isActive()). + color(QColorGroup::Button); + + for (int i = 0; i < BtnCount; i++) { + if (button[i]) + button[i]->setBg(c); + } + + titlebar->updateGeometry(); + positionButtons(); + titlebar->recalcBuffer(); + titlebar->installEventFilter(this); +} + +void B2Client::addButtons(const QString& s, const QString tips[], + B2Titlebar* tb, QBoxLayout* titleLayout) +{ + if (s.length() <= 0) + return; + + for (unsigned int i = 0; i < s.length(); i++) { + switch (s[i].latin1()) { + case 'M': // Menu button + if (!button[BtnMenu]) { + button[BtnMenu] = new B2Button(this, tb, tips[BtnMenu], + LeftButton | RightButton); + button[BtnMenu]->setPixmaps(P_MENU); + button[BtnMenu]->setUseMiniIcon(); + connect(button[BtnMenu], SIGNAL(pressed()), + this, SLOT(menuButtonPressed())); + titleLayout->addWidget(button[BtnMenu]); + } + break; + case 'S': // Sticky button + if (!button[BtnSticky]) { + button[BtnSticky] = new B2Button(this, tb, tips[BtnSticky]); + button[BtnSticky]->setPixmaps(P_PINUP); + button[BtnSticky]->setToggle(); + button[BtnSticky]->setDown(isOnAllDesktops()); + connect(button[BtnSticky], SIGNAL(clicked()), + this, SLOT(toggleOnAllDesktops())); + titleLayout->addWidget(button[BtnSticky]); + } + break; + case 'H': // Help button + if (providesContextHelp() && (!button[BtnHelp])) { + button[BtnHelp] = new B2Button(this, tb, tips[BtnHelp]); + button[BtnHelp]->setPixmaps(P_HELP); + connect(button[BtnHelp], SIGNAL(clicked()), + this, SLOT(showContextHelp())); + titleLayout->addWidget(button[BtnHelp]); + } + break; + case 'I': // Minimize button + if (isMinimizable() && (!button[BtnIconify])) { + button[BtnIconify] = new B2Button(this, tb,tips[BtnIconify]); + button[BtnIconify]->setPixmaps(P_ICONIFY); + connect(button[BtnIconify], SIGNAL(clicked()), + this, SLOT(minimize())); + titleLayout->addWidget(button[BtnIconify]); + } + break; + case 'A': // Maximize button + if (isMaximizable() && (!button[BtnMax])) { + button[BtnMax] = new B2Button(this, tb, tips[BtnMax], + LeftButton | MidButton | RightButton); + button[BtnMax]->setPixmaps(maximizeMode() == MaximizeFull ? + P_NORMALIZE : P_MAX); + connect(button[BtnMax], SIGNAL(clicked()), + this, SLOT(maxButtonClicked())); + titleLayout->addWidget(button[BtnMax]); + } + break; + case 'X': // Close button + if (isCloseable() && !button[BtnClose]) { + button[BtnClose] = new B2Button(this, tb, tips[BtnClose]); + button[BtnClose]->setPixmaps(P_CLOSE); + connect(button[BtnClose], SIGNAL(clicked()), + this, SLOT(closeWindow())); + titleLayout->addWidget(button[BtnClose]); + } + break; + case 'L': // Shade button + if (isShadeable() && !button[BtnShade]) { + button[BtnShade] = new B2Button(this, tb, tips[BtnShade]); + button[BtnShade]->setPixmaps(P_SHADE); + connect(button[BtnShade], SIGNAL(clicked()), + this, SLOT(shadeButtonClicked())); + titleLayout->addWidget(button[BtnShade]); + } + break; + case 'R': // Resize button + if (resizable && !button[BtnResize]) { + button[BtnResize] = new B2Button(this, tb, tips[BtnResize]); + button[BtnResize]->setPixmaps(P_RESIZE); + connect(button[BtnResize], SIGNAL(pressed()), + this, SLOT(resizeButtonPressed())); + titleLayout->addWidget(button[BtnResize]); + } + break; + case '_': // Additional spacing + titleLayout->addSpacing(4); + break; + } + } +} + +bool B2Client::mustDrawHandle() const +{ + if (drawSmallBorders && (maximizeMode() & MaximizeVertical)) { + return false; + } else { + return do_draw_handle && resizable; + } +} + +void B2Client::iconChange() +{ + if (button[BtnMenu]) + button[BtnMenu]->repaint(false); +} + +// Gallium: New button show/hide magic for customizable +// button positions. +void B2Client::calcHiddenButtons() +{ + // Hide buttons in this order: + // Shade, Sticky, Help, Resize, Maximize, Minimize, Close, Menu + B2Button* btnArray[] = { + button[BtnShade], button[BtnSticky], button[BtnHelp], button[BtnResize], + button[BtnMax], button[BtnIconify], button[BtnClose], button[BtnMenu] + }; + int minWidth = 120; + int currentWidth = width(); + int count = 0; + int i; + + // Determine how many buttons we need to hide + while (currentWidth < minWidth) { + currentWidth += buttonSize + 1; // Allow for spacer (extra 1pix) + count++; + } + // Bound the number of buttons to hide + if (count > BtnCount) count = BtnCount; + + // 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 < BtnCount; i++) { + if (btnArray[i] && (!btnArray[i]->isVisible())) + btnArray[i]->show(); + } +} + +void B2Client::resizeEvent(QResizeEvent * /*e*/) +{ + calcHiddenButtons(); + titlebar->layout()->activate(); + positionButtons(); + + /* may be the resize cut off some space occupied by titlebar, which + was moved, so instead of reducing it, we first try to move it */ + titleMoveAbs(bar_x_ofs); + + doShape(); + widget()->repaint(); // the frame is misrendered without this +} + +void B2Client::captionChange() +{ + positionButtons(); + titleMoveAbs(bar_x_ofs); + doShape(); + titlebar->recalcBuffer(); + titlebar->repaint(false); +} + +void B2Client::paintEvent(QPaintEvent* e) +{ + QPainter p(widget()); + + KDecoration::ColorType frameColorGroup = colored_frame ? + KDecoration::ColorTitleBar : KDecoration::ColorFrame; + + QRect t = titlebar->geometry(); + + // Frame height, this is used a lot of times + int fHeight = height() - t.height(); + + // distance from the bottom border - it is different if window is resizable + int bb = mustDrawHandle() ? 4 : 0; + int bDepth = thickness + bb; + + QColorGroup fillColor = options()->colorGroup(frameColorGroup, isActive()); + QBrush fillBrush(options()->color(frameColorGroup, isActive())); + + // outer frame rect + p.drawRect(0, t.bottom() - thickness + 1, + width(), fHeight - bb + thickness); + + if (thickness >= 2) { + // inner window rect + p.drawRect(thickness - 1, t.bottom(), + width() - 2 * (thickness - 1), fHeight - bDepth + 2); + + if (thickness >= 3) { + // frame shade panel + qDrawShadePanel(&p, 1, t.bottom() - thickness + 2, + width() - 2, fHeight - 2 - bb + thickness, fillColor, false); + if (thickness == 4) { + p.setPen(fillColor.background()); + p.drawRect(thickness - 2, t.bottom() - 1, + width() - 2 * (thickness - 2), fHeight + 4 - bDepth); + } else if (thickness > 4) { + qDrawShadePanel(&p, thickness - 2, + t.bottom() - 1, width() - 2 * (thickness - 2), + fHeight + 4 - bDepth, fillColor, true); + if (thickness >= 5) { + // draw frame interior + p.fillRect(2, t.bottom() - thickness + 3, + width() - 4, thickness - 4, fillBrush); + p.fillRect(2, height() - bDepth + 2, + width() - 4, thickness - 4, fillBrush); + p.fillRect(2, t.bottom() - 1, + thickness - 4, fHeight - bDepth + 4, fillBrush); + p.fillRect(width() - thickness + 2, t.bottom() - 1, + thickness - 4, fHeight - bDepth + 4, fillBrush); + } + } + } + } + + // bottom handle rect + if (mustDrawHandle()) { + p.setPen(Qt::black); + int hx = width() - 40; + int hw = 40; + + p.drawLine(width() - 1, height() - thickness - 4, + width() - 1, height() - 1); + p.drawLine(hx, height() - 1, width() - 1, height() - 1); + p.drawLine(hx, height() - 4, hx, height() - 1); + + p.fillRect(hx + 1, height() - thickness - 3, + hw - 2, thickness + 2, fillBrush); + + p.setPen(fillColor.dark()); + p.drawLine(width() - 2, height() - thickness - 4, + width() - 2, height() - 2); + p.drawLine(hx + 1, height() - 2, width() - 2, height() - 2); + + p.setPen(fillColor.light()); + p.drawLine(hx + 1, height() - thickness - 2, + hx + 1, height() - 3); + p.drawLine(hx + 1, height() - thickness - 3, + width() - 3, height() - thickness - 3); + } + + /* OK, we got a paint event, which means parts of us are now visible + which were not before. We try the titlebar if it is currently fully + obscured, and if yes, try to unobscure it, in the hope that some + of the parts which we just painted were in the titlebar area. + It can happen, that the titlebar, as it got the FullyObscured event + had no chance of becoming partly visible. The problem is, that + we now might have the space available, but the titlebar gets no + visibilitinotify events until its state changes, so we just try + */ + if (titlebar->isFullyObscured()) { + /* We first see, if our repaint contained the titlebar area */ + QRegion reg(QRect(0, 0, width(), buttonSize + 4)); + reg = reg.intersect(e->region()); + if (!reg.isEmpty()) + unobscureTitlebar(); + } +} + +void B2Client::doShape() +{ + QRect t = titlebar->geometry(); + QRegion mask(widget()->rect()); + // top to the tilebar right + if (bar_x_ofs) { + // left from bar + mask -= QRect(0, 0, bar_x_ofs, t.height() - thickness); + // top left point + mask -= QRect(0, t.height() - thickness, 1, 1); + } + if (t.right() < width() - 1) { + mask -= QRect(width() - 1, + t.height() - thickness, 1, 1); //top right point + mask -= QRect(t.right() + 1, 0, + width() - t.right() - 1, t.height() - thickness); + } + // bottom right point + mask -= QRect(width() - 1, height() - 1, 1, 1); + if (mustDrawHandle()) { + // bottom left point + mask -= QRect(0, height() - 5, 1, 1); + // handle left point + mask -= QRect(width() - 40, height() - 1, 1, 1); + // bottom left + mask -= QRect(0, height() - 4, width() - 40, 4); + } else { + // bottom left point + mask -= QRect(0, height() - 1, 1, 1); + } + + setMask(mask); +} + +void B2Client::showEvent(QShowEvent *) +{ + calcHiddenButtons(); + positionButtons(); + doShape(); +} + +KDecoration::Position B2Client::mousePosition(const QPoint& p) const +{ + const int range = 16; + QRect t = titlebar->geometry(); + t.setHeight(buttonSize + 4 - thickness); + int ly = t.bottom(); + int lx = t.right(); + int bb = mustDrawHandle() ? 0 : 5; + + if (p.x() > t.right()) { + if (p.y() <= ly + range && p.x() >= width() - range) + return PositionTopRight; + else if (p.y() <= ly + thickness) + return PositionTop; + } else if (p.x() < bar_x_ofs) { + if (p.y() <= ly + range && p.x() <= range) + return PositionTopLeft; + else if (p.y() <= ly + thickness) + return PositionTop; + } else if (p.y() < ly) { + if (p.x() > bar_x_ofs + thickness && + p.x() < lx - thickness && p.y() > thickness) + return KDecoration::mousePosition(p); + if (p.x() > bar_x_ofs + range && p.x() < lx - range) + return PositionTop; + if (p.y() <= range) { + if (p.x() <= bar_x_ofs + range) + return PositionTopLeft; + else return PositionTopRight; + } else { + if (p.x() <= bar_x_ofs + range) + return PositionLeft; + else return PositionRight; + } + } + + if (p.y() >= height() - 8 + bb) { + /* the normal Client:: only wants border of 4 pixels */ + if (p.x() <= range) return PositionBottomLeft; + if (p.x() >= width() - range) return PositionBottomRight; + return PositionBottom; + } + + return KDecoration::mousePosition(p); +} + +void B2Client::titleMoveAbs(int new_ofs) +{ + if (new_ofs < 0) new_ofs = 0; + if (new_ofs + titlebar->width() > width()) { + new_ofs = width() - titlebar->width(); + } + if (bar_x_ofs != new_ofs) { + bar_x_ofs = new_ofs; + positionButtons(); + doShape(); + widget()->repaint(0, 0, width(), buttonSize + 4, false); + titlebar->repaint(false); + } +} + +void B2Client::titleMoveRel(int xdiff) +{ + titleMoveAbs(bar_x_ofs + xdiff); +} + +void B2Client::desktopChange() +{ + bool on = isOnAllDesktops(); + if (B2Button *b = button[BtnSticky]) { + b->setDown(on); + QToolTip::remove(b); + QToolTip::add(b, + on ? i18n("Not on all desktops") : i18n("On all desktops")); + } +} + +void B2Client::maximizeChange() +{ + bool m = maximizeMode() == MaximizeFull; + if (button[BtnMax]) { + button[BtnMax]->setPixmaps(m ? P_NORMALIZE : P_MAX); + button[BtnMax]->repaint(); + QToolTip::remove(button[BtnMax]); + QToolTip::add(button[BtnMax], + m ? i18n("Restore") : i18n("Maximize")); + } + bottomSpacer->changeSize(10, thickness + (mustDrawHandle() ? 4 : 0), + QSizePolicy::Expanding, QSizePolicy::Minimum); + + g->activate(); + doShape(); + widget()->repaint(false); +} + +void B2Client::activeChange() +{ + widget()->repaint(false); + titlebar->repaint(false); + + QColor c = options()->colorGroup( + KDecoration::ColorTitleBar, isActive()).color(QColorGroup::Button); + + for (int i = 0; i < BtnCount; i++) + if (button[i]) { + button[i]->setBg(c); + button[i]->repaint(false); + } +} + +void B2Client::shadeChange() +{ + bottomSpacer->changeSize(10, thickness + (mustDrawHandle() ? 4 : 0), + QSizePolicy::Expanding, QSizePolicy::Minimum); + g->activate(); + doShape(); + if (B2Button *b = button[BtnShade]) { + QToolTip::remove(b); + QToolTip::add(b, isSetShade() ? i18n("Unshade") : i18n("Shade")); + } +} + +QSize B2Client::minimumSize() const +{ + int left, right, top, bottom; + borders(left, right, top, bottom); + return QSize(left + right + 2 * buttonSize, top + bottom); +} + +void B2Client::resize(const QSize& s) +{ + widget()->resize(s); +} + +void B2Client::borders(int &left, int &right, int &top, int &bottom) const +{ + left = right = thickness; + top = buttonSize + 4; + bottom = thickness + (mustDrawHandle() ? 4 : 0); +} + +void B2Client::menuButtonPressed() +{ + static B2Client *lastClient = NULL; + + bool dbl = (lastClient == this && + time.elapsed() <= QApplication::doubleClickInterval()); + lastClient = this; + time.start(); + if (!dbl) { + KDecorationFactory* f = factory(); + QRect menuRect = button[BtnMenu]->rect(); + QPoint menuTop = button[BtnMenu]->mapToGlobal(menuRect.topLeft()); + QPoint menuBottom = button[BtnMenu]->mapToGlobal(menuRect.bottomRight()); + showWindowMenu(QRect(menuTop, menuBottom)); + if (!f->exists(this)) // 'this' was destroyed + return; + button[BtnMenu]->setDown(false); + } else { + switch (menu_dbl_click_op) { + case B2::MinimizeOp: + minimize(); + break; + case B2::ShadeOp: + setShade(!isSetShade()); + break; + case B2::CloseOp: + closeWindow(); + break; + case B2::NoOp: + default: + break; + } + } +} + +void B2Client::unobscureTitlebar() +{ + /* we just noticed, that we got obscured by other windows + so we look at all windows above us (stacking_order) merging their + masks, intersecting it with our titlebar area, and see if we can + find a place not covered by any window */ + if (in_unobs) { + return; + } + in_unobs = 1; + QRegion reg(QRect(0,0,width(), buttonSize + 4)); + reg = unobscuredRegion(reg); + if (!reg.isEmpty()) { + // there is at least _one_ pixel from our title area, which is not + // obscured, we use the first rect we find + // for a first test, we use boundingRect(), later we may refine + // to rect(), and search for the nearest, or biggest, or smthg. + titleMoveAbs(reg.boundingRect().x()); + } + in_unobs = 0; +} + +static void redraw_pixmaps() +{ + int i; + QColorGroup aGrp = options()->colorGroup(KDecoration::ColorButtonBg, true); + QColorGroup iGrp = options()->colorGroup(KDecoration::ColorButtonBg, false); + + // close + drawB2Rect(PIXMAP_A(P_CLOSE), aGrp.button(), false); + drawB2Rect(PIXMAP_AH(P_CLOSE), aGrp.button(), true); + drawB2Rect(PIXMAP_AD(P_CLOSE), aGrp.button(), true); + + drawB2Rect(PIXMAP_I(P_CLOSE), iGrp.button(), false); + drawB2Rect(PIXMAP_IH(P_CLOSE), iGrp.button(), true); + drawB2Rect(PIXMAP_ID(P_CLOSE), iGrp.button(), true); + + // shade + KPixmap thinBox; + thinBox.resize(buttonSize - 2, 6); + for (i = 0; i < NumStates; i++) { + bool is_act = (i < 2); + bool is_down = ((i & 1) == 1); + KPixmap *pix = pixmap[P_SHADE * NumStates + i]; + QColor color = is_act ? aGrp.button() : iGrp.button(); + drawB2Rect(&thinBox, color, is_down); + pix->fill(Qt::black); + bitBlt(pix, 0, 0, &thinBox, + 0, 0, thinBox.width(), thinBox.height(), Qt::CopyROP, true); + } + + // maximize + for (i = 0; i < NumStates; i++) { + *pixmap[P_MAX * NumStates + i] = *pixmap[P_CLOSE * NumStates + i]; + pixmap[P_MAX * NumStates + i]->detach(); + } + + // normalize + iconify + KPixmap smallBox; + smallBox.resize(10, 10); + KPixmap largeBox; + largeBox.resize(12, 12); + + for (i = 0; i < NumStates; i++) { + bool is_act = (i < 3); + bool is_down = (i == Down || i == IDown); + KPixmap *pix = pixmap[P_NORMALIZE * NumStates + i]; + drawB2Rect(&smallBox, is_act ? aGrp.button() : iGrp.button(), is_down); + drawB2Rect(&largeBox, is_act ? aGrp.button() : iGrp.button(), is_down); + pix->fill(options()->color(KDecoration::ColorTitleBar, is_act)); + bitBlt(pix, pix->width() - 12, pix->width() - 12, &largeBox, + 0, 0, 12, 12, Qt::CopyROP, true); + bitBlt(pix, 0, 0, &smallBox, 0, 0, 10, 10, Qt::CopyROP, true); + + bitBlt(pixmap[P_ICONIFY * NumStates + i], 0, 0, + &smallBox, 0, 0, 10, 10, Qt::CopyROP, true); + } + + // resize + for (i = 0; i < NumStates; i++) { + bool is_act = (i < 3); + bool is_down = (i == Down || i == IDown); + *pixmap[P_RESIZE * NumStates + i] = *pixmap[P_CLOSE * NumStates + i]; + pixmap[P_RESIZE * NumStates + i]->detach(); + drawB2Rect(&smallBox, is_act ? aGrp.button() : iGrp.button(), is_down); + bitBlt(pixmap[P_RESIZE * NumStates + i], + 0, 0, &smallBox, 0, 0, 10, 10, Qt::CopyROP, true); + } + + + QPainter p; + // x for close + menu + help + for (int j = 0; j < 3; j++) { + int pix; + unsigned const char *light, *dark; + switch (j) { + case 0: + pix = P_CLOSE; light = close_white_bits; dark = close_dgray_bits; + break; + case 1: + pix = P_MENU; light = menu_white_bits; dark = menu_dgray_bits; + break; + default: + pix = P_HELP; light = help_light_bits; dark = help_dark_bits; + break; + } + int off = (pixmap[pix * NumStates]->width() - 16) / 2; + for (i = 0; i < NumStates; i++) { + p.begin(pixmap[pix * NumStates + i]); + kColorBitmaps(&p, (i < 3) ? aGrp : iGrp, off, off, 16, 16, true, + light, NULL, NULL, dark, NULL, NULL); + p.end(); + } + } + + // pin + for (i = 0; i < NumStates; i++) { + bool isDown = (i == Down || i == IDown); + unsigned const char *white = isDown ? pindown_white_bits : pinup_white_bits; + unsigned const char *gray = isDown ? pindown_gray_bits : pinup_gray_bits; + unsigned const char *dgray =isDown ? pindown_dgray_bits : pinup_dgray_bits; + p.begin(pixmap[P_PINUP * NumStates + i]); + kColorBitmaps(&p, (i < 3) ? aGrp : iGrp, 0, 0, 16, 16, true, white, + gray, NULL, dgray, NULL, NULL); + p.end(); + } + + // Apply the hilight effect to the 'Hover' icons + KIconEffect ie; + QPixmap hilighted; + for (i = 0; i < P_NUM_BUTTON_TYPES; i++) { + int offset = i * NumStates; + hilighted = ie.apply(*pixmap[offset + Norm], + KIcon::Small, KIcon::ActiveState); + *pixmap[offset + Hover] = hilighted; + + hilighted = ie.apply(*pixmap[offset + INorm], + KIcon::Small, KIcon::ActiveState); + *pixmap[offset + IHover] = hilighted; + } + + + // Create the titlebar gradients + if (QPixmap::defaultDepth() > 8) { + QColor titleColor[4] = { + options()->color(KDecoration::ColorTitleBar, true), + options()->color(KDecoration::ColorFrame, true), + + options()->color(KDecoration::ColorTitleBlend, false), + options()->color(KDecoration::ColorTitleBar, false) + }; + + if (colored_frame) { + titleColor[0] = options()->color(KDecoration::ColorTitleBlend, true); + titleColor[1] = options()->color(KDecoration::ColorTitleBar, true); + } + + for (i = 0; i < 2; i++) { + if (titleColor[2 * i] != titleColor[2 * i + 1]) { + if (!titleGradient[i]) { + titleGradient[i] = new KPixmap; + } + titleGradient[i]->resize(64, buttonSize + 3); + KPixmapEffect::gradient(*titleGradient[i], + titleColor[2 * i], titleColor[2 * i + 1], + KPixmapEffect::VerticalGradient); + } else { + delete titleGradient[i]; + titleGradient[i] = 0; + } + } + } +} + +void B2Client::positionButtons() +{ + QFontMetrics fm(options()->font(isActive())); + QString cap = caption(); + if (cap.length() < 5) // make sure the titlebar has sufficiently wide + cap = "XXXXX"; // area for dragging the window + int textLen = fm.width(cap); + + QRect t = titlebar->captionSpacer->geometry(); + int titleWidth = titlebar->width() - t.width() + textLen + 2; + if (titleWidth > width()) titleWidth = width(); + + titlebar->resize(titleWidth, buttonSize + 4); + titlebar->move(bar_x_ofs, 0); +} + +// Transparent bound stuff. + +static QRect *visible_bound; +static QPointArray bound_shape; + +bool B2Client::drawbound(const QRect& geom, bool clear) +{ + if (clear) { + if (!visible_bound) return true; + } + + if (!visible_bound) { + visible_bound = new QRect(geom); + QRect t = titlebar->geometry(); + int frameTop = geom.top() + t.bottom(); + int barLeft = geom.left() + bar_x_ofs; + int barRight = barLeft + t.width() - 1; + if (barRight > geom.right()) barRight = geom.right(); + // line width is 5 pixels, so compensate for the 2 outer pixels (#88657) + QRect g = geom; + g.setLeft( g.left() + 2 ); + g.setTop( g.top() + 2 ); + g.setRight( g.right() - 2 ); + g.setBottom( g.bottom() - 2 ); + frameTop += 2; + barLeft += 2; + barRight -= 2; + + bound_shape.putPoints(0, 8, + g.left(), frameTop, + barLeft, frameTop, + barLeft, g.top(), + barRight, g.top(), + barRight, frameTop, + g.right(), frameTop, + g.right(), g.bottom(), + g.left(), g.bottom()); + } else { + *visible_bound = geom; + } + QPainter p(workspaceWidget()); + p.setPen(QPen(Qt::white, 5)); + p.setRasterOp(Qt::XorROP); + p.drawPolygon(bound_shape); + + if (clear) { + delete visible_bound; + visible_bound = 0; + } + return true; +} + +bool B2Client::eventFilter(QObject *o, QEvent *e) +{ + if (o != widget()) + return false; + switch (e->type()) { + case QEvent::Resize: + resizeEvent(static_cast< QResizeEvent* >(e)); + return true; + case QEvent::Paint: + paintEvent(static_cast< QPaintEvent* >(e)); + return true; + case QEvent::MouseButtonDblClick: + titlebar->mouseDoubleClickEvent(static_cast< QMouseEvent* >(e)); + return true; + case QEvent::Wheel: + titlebar->wheelEvent(static_cast< QWheelEvent* >(e)); + return true; + case QEvent::MouseButtonPress: + processMousePressEvent(static_cast< QMouseEvent* >(e)); + return true; + case QEvent::Show: + showEvent(static_cast< QShowEvent* >(e)); + return true; + default: + break; + } + return false; +} + +// ===================================== + +B2Button::B2Button(B2Client *_client, QWidget *parent, + const QString& tip, const int realizeBtns) + : QButton(parent, 0), hover(false) +{ + setBackgroundMode(NoBackground); + setCursor(arrowCursor); + realizeButtons = realizeBtns; + client = _client; + useMiniIcon = false; + setFixedSize(buttonSize, buttonSize); + QToolTip::add(this, tip); +} + + +QSize B2Button::sizeHint() const +{ + return QSize(buttonSize, buttonSize); +} + +QSizePolicy B2Button::sizePolicy() const +{ + return(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); +} + +void B2Button::drawButton(QPainter *p) +{ + KPixmap* gradient = titleGradient[client->isActive() ? 0 : 1]; + if (gradient) { + p->drawTiledPixmap(0, 0, buttonSize, buttonSize, *gradient, 0, 2); + } else { + p->fillRect(rect(), bg); + } + if (useMiniIcon) { + QPixmap miniIcon = client->icon().pixmap(QIconSet::Small, + client->isActive() ? QIconSet::Normal : QIconSet::Disabled); + p->drawPixmap((width() - miniIcon.width()) / 2, + (height() - miniIcon.height()) / 2, miniIcon); + } else { + int type; + if (client->isActive()) { + if (isOn() || isDown()) + type = Down; + else if (hover) + type = Hover; + else + type = Norm; + } else { + if (isOn() || isDown()) + type = IDown; + else if (hover) + type = IHover; + else + type = INorm; + } + p->drawPixmap((width() - icon[type]->width()) / 2, + (height() - icon[type]->height()) / 2, *icon[type]); + } +} + +void B2Button::setPixmaps(int button_id) +{ + button_id *= NumStates; + for (int i = 0; i < NumStates; i++) { + icon[i] = B2::pixmap[button_id + i]; + } + repaint(false); +} + +void B2Button::mousePressEvent(QMouseEvent * e) +{ + last_button = e->button(); + QMouseEvent me(e->type(), e->pos(), e->globalPos(), + (e->button() & realizeButtons) ? LeftButton : NoButton, + e->state()); + QButton::mousePressEvent(&me); +} + +void B2Button::mouseReleaseEvent(QMouseEvent * e) +{ + last_button = e->button(); + QMouseEvent me(e->type(), e->pos(), e->globalPos(), + (e->button() & realizeButtons) ? LeftButton : NoButton, + e->state()); + QButton::mouseReleaseEvent(&me); +} + +void B2Button::enterEvent(QEvent *e) +{ + hover = true; + repaint(false); + QButton::enterEvent(e); +} + +void B2Button::leaveEvent(QEvent *e) +{ + hover = false; + repaint(false); + QButton::leaveEvent(e); +} + +// ===================================== + +B2Titlebar::B2Titlebar(B2Client *parent) + : QWidget(parent->widget(), 0, WStyle_Customize | WRepaintNoErase), + client(parent), + set_x11mask(false), isfullyobscured(false), shift_move(false) +{ + setBackgroundMode(NoBackground); + captionSpacer = new QSpacerItem(buttonSize, buttonSize + 4, + QSizePolicy::Expanding, QSizePolicy::Fixed); +} + +bool B2Titlebar::x11Event(XEvent *e) +{ + if (!set_x11mask) { + set_x11mask = true; + XSelectInput(qt_xdisplay(), winId(), + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + KeymapStateMask | + ButtonMotionMask | + EnterWindowMask | LeaveWindowMask | + FocusChangeMask | + ExposureMask | + PropertyChangeMask | + StructureNotifyMask | SubstructureRedirectMask | + VisibilityChangeMask); + } + switch (e->type) { + case VisibilityNotify: + isfullyobscured = false; + if (e->xvisibility.state == VisibilityFullyObscured) { + isfullyobscured = true; + client->unobscureTitlebar(); + } + break; + default: + break; + } + return QWidget::x11Event(e); +} + +void B2Titlebar::drawTitlebar(QPainter &p, bool state) +{ + KPixmap* gradient = titleGradient[state ? 0 : 1]; + + QRect t = rect(); + // black titlebar frame + p.setPen(Qt::black); + p.drawLine(0, 0, 0, t.bottom()); + p.drawLine(0, 0, t.right(), 0); + p.drawLine(t.right(), 0, t.right(), t.bottom()); + + // titlebar fill + const QColorGroup cg = + options()->colorGroup(KDecoration::ColorTitleBar, state); + QBrush brush(cg.background()); + if (gradient) brush.setPixmap(*gradient); + qDrawShadeRect(&p, 1, 1, t.right() - 1, t.height() - 1, + cg, false, 1, 0, &brush); + + // and the caption + p.setPen(options()->color(KDecoration::ColorFont, state)); + p.setFont(options()->font(state)); + t = captionSpacer->geometry(); + p.drawText(t, AlignLeft | AlignVCenter, client->caption()); +} + +void B2Titlebar::recalcBuffer() +{ + titleBuffer.resize(width(), height()); + + QPainter p(&titleBuffer); + drawTitlebar(p, true); + oldTitle = caption(); +} + +void B2Titlebar::resizeEvent(QResizeEvent *) +{ + recalcBuffer(); + repaint(false); +} + + +void B2Titlebar::paintEvent(QPaintEvent *) +{ + if(client->isActive()) + bitBlt(this, 0, 0, &titleBuffer, 0, 0, titleBuffer.width(), + titleBuffer.height(), Qt::CopyROP, true); + else { + QPainter p(this); + drawTitlebar(p, false); + } +} + +void B2Titlebar::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() == LeftButton && e->y() < height()) { + client->titlebarDblClickOperation(); + } +} + +void B2Titlebar::wheelEvent(QWheelEvent *e) +{ + if (client->isSetShade() || rect().contains(e->pos())) + client->titlebarMouseWheelOperation( e->delta()); +} + +void B2Titlebar::mousePressEvent(QMouseEvent * e) +{ + shift_move = e->state() & ShiftButton; + if (shift_move) { + moveOffset = e->globalPos(); + } else { + e->ignore(); + } +} + +void B2Titlebar::mouseReleaseEvent(QMouseEvent * e) +{ + if (shift_move) shift_move = false; + else e->ignore(); +} + +void B2Titlebar::mouseMoveEvent(QMouseEvent * e) +{ + if (shift_move) { + int oldx = mapFromGlobal(moveOffset).x(); + int xdiff = e->globalPos().x() - moveOffset.x(); + moveOffset = e->globalPos(); + if (oldx >= 0 && oldx <= rect().right()) { + client->titleMoveRel(xdiff); + } + } else { + e->ignore(); + } +} + +} // namespace B2 + +#include "b2client.moc" + +// vim: sw=4 + |