File indexing completed on 2025-03-16 06:55:10
0001 /* 0002 SPDX-FileCopyrightText: 2001, 2002, 2003 Joseph Wenninger <jowenn@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kmultitabbar.h" 0008 #include "kmultitabbar_p.h" 0009 #include "moc_kmultitabbar.cpp" 0010 #include "moc_kmultitabbar_p.cpp" 0011 0012 #include <QMouseEvent> 0013 #include <QPainter> 0014 #include <QStyle> 0015 #include <QStyleOptionButton> 0016 0017 #include <math.h> 0018 0019 /* 0020 A bit of an overview about what's going on here: 0021 There are two sorts of things that can be in the tab bar: tabs and regular 0022 buttons. The former are managed by KMultiTabBarInternal, the later 0023 by the KMultiTabBar itself. 0024 */ 0025 0026 class KMultiTabBarTabPrivate 0027 { 0028 }; 0029 class KMultiTabBarButtonPrivate 0030 { 0031 }; 0032 0033 class KMultiTabBarPrivate 0034 { 0035 public: 0036 class KMultiTabBarInternal *m_internal; 0037 QBoxLayout *m_l; 0038 QFrame *m_btnTabSep; 0039 QList<KMultiTabBarButton *> m_buttons; 0040 KMultiTabBar::KMultiTabBarPosition m_position; 0041 }; 0042 0043 KMultiTabBarInternal::KMultiTabBarInternal(QWidget *parent, KMultiTabBar::KMultiTabBarPosition pos) 0044 : QFrame(parent) 0045 { 0046 m_position = pos; 0047 if (pos == KMultiTabBar::Left || pos == KMultiTabBar::Right) { 0048 mainLayout = new QVBoxLayout(this); 0049 } else { 0050 mainLayout = new QHBoxLayout(this); 0051 } 0052 mainLayout->setContentsMargins(0, 0, 0, 0); 0053 mainLayout->setSpacing(0); 0054 mainLayout->addStretch(); 0055 setFrameStyle(NoFrame); 0056 setBackgroundRole(QPalette::Window); 0057 } 0058 0059 KMultiTabBarInternal::~KMultiTabBarInternal() 0060 { 0061 qDeleteAll(m_tabs); 0062 m_tabs.clear(); 0063 } 0064 0065 void KMultiTabBarInternal::setStyle(enum KMultiTabBar::KMultiTabBarStyle style) 0066 { 0067 m_style = style; 0068 for (int i = 0; i < m_tabs.count(); i++) { 0069 m_tabs.at(i)->setStyle(m_style); 0070 } 0071 0072 updateGeometry(); 0073 } 0074 0075 void KMultiTabBarInternal::contentsMousePressEvent(QMouseEvent *ev) 0076 { 0077 ev->ignore(); 0078 } 0079 0080 void KMultiTabBarInternal::mousePressEvent(QMouseEvent *ev) 0081 { 0082 ev->ignore(); 0083 } 0084 0085 KMultiTabBarTab *KMultiTabBarInternal::tab(int id) const 0086 { 0087 QListIterator<KMultiTabBarTab *> it(m_tabs); 0088 while (it.hasNext()) { 0089 KMultiTabBarTab *tab = it.next(); 0090 if (tab->id() == id) { 0091 return tab; 0092 } 0093 } 0094 return nullptr; 0095 } 0096 0097 int KMultiTabBarInternal::appendTab(const QIcon &icon, int id, const QString &text) 0098 { 0099 KMultiTabBarTab *tab; 0100 m_tabs.append(tab = new KMultiTabBarTab(icon, text, id, this, m_position, m_style)); 0101 0102 // Insert before the stretch.. 0103 mainLayout->insertWidget(m_tabs.size() - 1, tab); 0104 tab->show(); 0105 return 0; 0106 } 0107 0108 int KMultiTabBarInternal::appendTab(const QPixmap &pic, int id, const QString &text) 0109 { 0110 // reuse icon variant 0111 return appendTab(QIcon(pic), id, text); 0112 } 0113 0114 void KMultiTabBarInternal::removeTab(int id) 0115 { 0116 for (int pos = 0; pos < m_tabs.count(); pos++) { 0117 if (m_tabs.at(pos)->id() == id) { 0118 // remove & delete the tab 0119 delete m_tabs.takeAt(pos); 0120 break; 0121 } 0122 } 0123 } 0124 0125 void KMultiTabBarInternal::setPosition(enum KMultiTabBar::KMultiTabBarPosition pos) 0126 { 0127 m_position = pos; 0128 for (int i = 0; i < m_tabs.count(); i++) { 0129 m_tabs.at(i)->setPosition(m_position); 0130 } 0131 updateGeometry(); 0132 } 0133 0134 // KMultiTabBarButton 0135 /////////////////////////////////////////////////////////////////////////////// 0136 0137 KMultiTabBarButton::KMultiTabBarButton(const QIcon &icon, const QString &text, int id, QWidget *parent) 0138 : QPushButton(icon, text, parent) 0139 , m_id(id) 0140 , d(nullptr) 0141 { 0142 connect(this, &QPushButton::clicked, this, &KMultiTabBarButton::slotClicked); 0143 0144 // we can't see the focus, so don't take focus. #45557 0145 // If keyboard navigation is wanted, then only the bar should take focus, 0146 // and arrows could change the focused button; but generally, tabbars don't take focus anyway. 0147 setFocusPolicy(Qt::NoFocus); 0148 setAttribute(Qt::WA_LayoutUsesWidgetRect); 0149 Q_UNUSED(d); 0150 } 0151 0152 KMultiTabBarButton::~KMultiTabBarButton() 0153 { 0154 } 0155 0156 void KMultiTabBarButton::setText(const QString &text) 0157 { 0158 QPushButton::setText(text); 0159 } 0160 0161 void KMultiTabBarButton::slotClicked() 0162 { 0163 updateGeometry(); 0164 Q_EMIT clicked(m_id); 0165 } 0166 0167 int KMultiTabBarButton::id() const 0168 { 0169 return m_id; 0170 } 0171 0172 void KMultiTabBarButton::hideEvent(QHideEvent *he) 0173 { 0174 QPushButton::hideEvent(he); 0175 KMultiTabBar *tb = dynamic_cast<KMultiTabBar *>(parentWidget()); 0176 if (tb) { 0177 tb->updateSeparator(); 0178 } 0179 } 0180 0181 void KMultiTabBarButton::showEvent(QShowEvent *he) 0182 { 0183 QPushButton::showEvent(he); 0184 KMultiTabBar *tb = dynamic_cast<KMultiTabBar *>(parentWidget()); 0185 if (tb) { 0186 tb->updateSeparator(); 0187 } 0188 } 0189 0190 void KMultiTabBarButton::paintEvent(QPaintEvent *) 0191 { 0192 QStyleOptionButton opt; 0193 opt.initFrom(this); 0194 opt.icon = icon(); 0195 opt.iconSize = iconSize(); 0196 // removes the QStyleOptionButton::HasMenu ButtonFeature 0197 opt.features = QStyleOptionButton::Flat; 0198 QPainter painter(this); 0199 style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this); 0200 } 0201 0202 // KMultiTabBarTab 0203 /////////////////////////////////////////////////////////////////////////////// 0204 0205 KMultiTabBarTab::KMultiTabBarTab(const QIcon &icon, 0206 const QString &text, 0207 int id, 0208 QWidget *parent, 0209 KMultiTabBar::KMultiTabBarPosition pos, 0210 KMultiTabBar::KMultiTabBarStyle style) 0211 : KMultiTabBarButton(icon, text, id, parent) 0212 , m_style(style) 0213 , d(nullptr) 0214 { 0215 m_position = pos; 0216 setToolTip(text); 0217 setCheckable(true); 0218 // shrink down to icon only, but prefer to show text if it's there 0219 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 0220 } 0221 0222 KMultiTabBarTab::~KMultiTabBarTab() 0223 { 0224 } 0225 0226 void KMultiTabBarTab::setPosition(KMultiTabBar::KMultiTabBarPosition pos) 0227 { 0228 m_position = pos; 0229 updateGeometry(); 0230 } 0231 0232 void KMultiTabBarTab::setStyle(KMultiTabBar::KMultiTabBarStyle style) 0233 { 0234 m_style = style; 0235 updateGeometry(); 0236 } 0237 0238 void KMultiTabBarTab::initStyleOption(QStyleOptionToolButton *opt) const 0239 { 0240 opt->initFrom(this); 0241 0242 // Setup icon.. 0243 if (!icon().isNull()) { 0244 opt->iconSize = iconSize(); 0245 opt->icon = icon(); 0246 } 0247 0248 // Should we draw text? 0249 if (shouldDrawText()) { 0250 opt->text = text(); 0251 } 0252 0253 opt->state |= QStyle::State_AutoRaise; 0254 if (!isChecked() && !isDown()) { 0255 opt->state |= QStyle::State_Raised; 0256 } 0257 if (isDown()) { 0258 opt->state |= QStyle::State_Sunken; 0259 } 0260 if (isChecked()) { 0261 opt->state |= QStyle::State_On; 0262 } 0263 0264 opt->font = font(); 0265 opt->toolButtonStyle = shouldDrawText() ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly; 0266 opt->subControls = QStyle::SC_ToolButton; 0267 } 0268 0269 QSize KMultiTabBarTab::sizeHint() const 0270 { 0271 return computeSizeHint(shouldDrawText()); 0272 } 0273 0274 QSize KMultiTabBarTab::minimumSizeHint() const 0275 { 0276 return computeSizeHint(false); 0277 } 0278 0279 void KMultiTabBarTab::computeMargins(int *hMargin, int *vMargin) const 0280 { 0281 // Unfortunately, QStyle does not give us enough information to figure out 0282 // where to place things, so we try to reverse-engineer it 0283 QStyleOptionToolButton opt; 0284 initStyleOption(&opt); 0285 0286 QSize trialSize = opt.iconSize; 0287 QSize expandSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, trialSize, this); 0288 0289 *hMargin = (expandSize.width() - trialSize.width()) / 2; 0290 *vMargin = (expandSize.height() - trialSize.height()) / 2; 0291 } 0292 0293 QSize KMultiTabBarTab::computeSizeHint(bool withText) const 0294 { 0295 // Compute as horizontal first, then flip around if need be. 0296 QStyleOptionToolButton opt; 0297 initStyleOption(&opt); 0298 0299 int hMargin; 0300 int vMargin; 0301 computeMargins(&hMargin, &vMargin); 0302 0303 // Compute interior size, starting from pixmap.. 0304 QSize size = opt.iconSize; 0305 0306 // Always include text height in computation, to avoid resizing the minor direction 0307 // when expanding text.. 0308 QSize textSize = fontMetrics().size(0, text()); 0309 size.setHeight(qMax(size.height(), textSize.height())); 0310 0311 // Pick margins for major/minor direction, depending on orientation 0312 int majorMargin = isVertical() ? vMargin : hMargin; 0313 int minorMargin = isVertical() ? hMargin : vMargin; 0314 0315 size.setWidth(size.width() + 2 * majorMargin); 0316 size.setHeight(size.height() + 2 * minorMargin); 0317 0318 if (withText && !text().isEmpty()) 0319 // Add enough room for the text, and an extra major margin. 0320 { 0321 size.setWidth(size.width() + textSize.width() + majorMargin); 0322 } 0323 0324 // flip time? 0325 if (isVertical()) { 0326 return QSize(size.height(), size.width()); 0327 } else { 0328 return size; 0329 } 0330 } 0331 0332 void KMultiTabBarTab::setState(bool newState) 0333 { 0334 setChecked(newState); 0335 updateGeometry(); 0336 } 0337 0338 bool KMultiTabBarTab::shouldDrawText() const 0339 { 0340 return (m_style == KMultiTabBar::KDEV3ICON) || isChecked(); 0341 } 0342 0343 bool KMultiTabBarTab::isVertical() const 0344 { 0345 return m_position == KMultiTabBar::Right || m_position == KMultiTabBar::Left; 0346 } 0347 0348 void KMultiTabBarTab::paintEvent(QPaintEvent *) 0349 { 0350 QPainter painter(this); 0351 0352 QStyleOptionToolButton opt; 0353 initStyleOption(&opt); 0354 0355 // Paint bevel.. 0356 if (underMouse() || isChecked()) { 0357 opt.text.clear(); 0358 opt.icon = QIcon(); 0359 style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &painter, this); 0360 } 0361 0362 int hMargin; 0363 int vMargin; 0364 computeMargins(&hMargin, &vMargin); 0365 0366 // We first figure out how much room we have for the text, based on 0367 // icon size and margin, try to fit in by eliding, and perhaps 0368 // give up on drawing the text entirely if we're too short on room 0369 int textRoom = 0; 0370 int iconRoom = 0; 0371 0372 QString t; 0373 if (shouldDrawText()) { 0374 if (isVertical()) { 0375 iconRoom = opt.iconSize.height() + 2 * vMargin; 0376 textRoom = height() - iconRoom - vMargin; 0377 } else { 0378 iconRoom = opt.iconSize.width() + 2 * hMargin; 0379 textRoom = width() - iconRoom - hMargin; 0380 } 0381 0382 t = painter.fontMetrics().elidedText(text(), Qt::ElideRight, textRoom); 0383 0384 // See whether anything is left. Qt will return either 0385 // ... or the ellipsis unicode character, 0x2026 0386 if (t == QLatin1String("...") || t == QChar(0x2026)) { 0387 t.clear(); 0388 } 0389 } 0390 0391 const QIcon::Mode iconMode = (opt.state & QStyle::State_MouseOver) ? QIcon::Active : QIcon::Normal; 0392 const QPixmap iconPixmap = icon().pixmap(opt.iconSize, devicePixelRatioF(), iconMode, QIcon::On); 0393 0394 // Label time.... Simple case: no text, so just plop down the icon right in the center 0395 // We only do this when the button never draws the text, to avoid jumps in icon position 0396 // when resizing 0397 if (!shouldDrawText()) { 0398 style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter | Qt::AlignVCenter, iconPixmap); 0399 return; 0400 } 0401 0402 // Now where the icon/text goes depends on text direction and tab position 0403 QRect iconArea; 0404 QRect labelArea; 0405 0406 bool bottomIcon = false; 0407 bool rtl = layoutDirection() == Qt::RightToLeft; 0408 if (isVertical()) { 0409 if (m_position == KMultiTabBar::Left && !rtl) { 0410 bottomIcon = true; 0411 } 0412 if (m_position == KMultiTabBar::Right && rtl) { 0413 bottomIcon = true; 0414 } 0415 } 0416 // alignFlags = Qt::AlignLeading | Qt::AlignVCenter; 0417 0418 const int iconXShift = (isChecked() || isDown()) ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &opt, this) : 0; 0419 const int iconYShift = (isChecked() || isDown()) ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical, &opt, this) : 0; 0420 if (isVertical()) { 0421 if (bottomIcon) { 0422 labelArea = QRect(0, vMargin, width(), textRoom); 0423 iconArea = QRect(0, vMargin + textRoom, width(), iconRoom); 0424 iconArea.translate(iconYShift, -iconXShift); 0425 } else { 0426 labelArea = QRect(0, iconRoom, width(), textRoom); 0427 iconArea = QRect(0, 0, width(), iconRoom); 0428 iconArea.translate(-iconYShift, iconXShift); 0429 } 0430 } else { 0431 // Pretty simple --- depends only on RTL/LTR 0432 if (rtl) { 0433 labelArea = QRect(hMargin, 0, textRoom, height()); 0434 iconArea = QRect(hMargin + textRoom, 0, iconRoom, height()); 0435 } else { 0436 labelArea = QRect(iconRoom, 0, textRoom, height()); 0437 iconArea = QRect(0, 0, iconRoom, height()); 0438 } 0439 iconArea.translate(iconXShift, iconYShift); 0440 } 0441 0442 style()->drawItemPixmap(&painter, iconArea, Qt::AlignCenter | Qt::AlignVCenter, iconPixmap); 0443 0444 if (t.isEmpty()) { 0445 return; 0446 } 0447 0448 QRect labelPaintArea = labelArea; 0449 0450 if (isVertical()) { 0451 // If we're vertical, we paint to a simple 0,0 origin rect, 0452 // and get the transformations to get us in the right place 0453 labelPaintArea = QRect(0, 0, labelArea.height(), labelArea.width()); 0454 0455 QTransform tr; 0456 0457 if (bottomIcon) { 0458 tr.translate(labelArea.x(), labelPaintArea.width() + labelArea.y()); 0459 tr.rotate(-90); 0460 } else { 0461 tr.translate(labelPaintArea.height() + labelArea.x(), labelArea.y()); 0462 tr.rotate(90); 0463 } 0464 painter.setTransform(tr); 0465 } 0466 0467 opt.text = t; 0468 opt.icon = QIcon(); 0469 opt.rect = labelPaintArea; 0470 style()->drawControl(QStyle::CE_ToolButtonLabel, &opt, &painter, this); 0471 } 0472 0473 // KMultiTabBar 0474 /////////////////////////////////////////////////////////////////////////////// 0475 0476 KMultiTabBar::KMultiTabBar(QWidget *parent) 0477 : KMultiTabBar(Left, parent) 0478 { 0479 } 0480 0481 KMultiTabBar::KMultiTabBar(KMultiTabBarPosition pos, QWidget *parent) 0482 : QWidget(parent) 0483 , d(new KMultiTabBarPrivate) 0484 { 0485 if (pos == Left || pos == Right) { 0486 d->m_l = new QVBoxLayout(this); 0487 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding /*, true*/); 0488 } else { 0489 d->m_l = new QHBoxLayout(this); 0490 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed /*, true*/); 0491 } 0492 d->m_l->setContentsMargins(0, 0, 0, 0); 0493 d->m_l->setSpacing(0); 0494 0495 d->m_internal = new KMultiTabBarInternal(this, pos); 0496 setPosition(pos); 0497 setStyle(VSNET); 0498 d->m_l->insertWidget(0, d->m_internal); 0499 d->m_l->insertWidget(0, d->m_btnTabSep = new QFrame(this)); 0500 d->m_btnTabSep->setFixedHeight(4); 0501 d->m_btnTabSep->setFrameStyle(QFrame::Panel | QFrame::Sunken); 0502 d->m_btnTabSep->setLineWidth(2); 0503 d->m_btnTabSep->hide(); 0504 0505 updateGeometry(); 0506 } 0507 0508 KMultiTabBar::~KMultiTabBar() 0509 { 0510 qDeleteAll(d->m_buttons); 0511 d->m_buttons.clear(); 0512 } 0513 0514 int KMultiTabBar::appendButton(const QIcon &icon, int id, QMenu *popup, const QString &) 0515 { 0516 KMultiTabBarButton *btn = new KMultiTabBarButton(icon, QString(), id, this); 0517 // a button with a QMenu can have another size. Make sure the button has always the same size. 0518 btn->setFixedWidth(btn->height()); 0519 btn->setMenu(popup); 0520 d->m_buttons.append(btn); 0521 d->m_l->insertWidget(0, btn); 0522 btn->show(); 0523 d->m_btnTabSep->show(); 0524 return 0; 0525 } 0526 0527 void KMultiTabBar::updateSeparator() 0528 { 0529 bool hideSep = true; 0530 QListIterator<KMultiTabBarButton *> it(d->m_buttons); 0531 while (it.hasNext()) { 0532 if (it.next()->isVisibleTo(this)) { 0533 hideSep = false; 0534 break; 0535 } 0536 } 0537 if (hideSep) { 0538 d->m_btnTabSep->hide(); 0539 } else { 0540 d->m_btnTabSep->show(); 0541 } 0542 } 0543 0544 int KMultiTabBar::appendTab(const QIcon &icon, int id, const QString &text) 0545 { 0546 d->m_internal->appendTab(icon, id, text); 0547 return 0; 0548 } 0549 0550 KMultiTabBarButton *KMultiTabBar::button(int id) const 0551 { 0552 QListIterator<KMultiTabBarButton *> it(d->m_buttons); 0553 while (it.hasNext()) { 0554 KMultiTabBarButton *button = it.next(); 0555 if (button->id() == id) { 0556 return button; 0557 } 0558 } 0559 0560 return nullptr; 0561 } 0562 0563 KMultiTabBarTab *KMultiTabBar::tab(int id) const 0564 { 0565 return d->m_internal->tab(id); 0566 } 0567 0568 void KMultiTabBar::removeButton(int id) 0569 { 0570 for (int pos = 0; pos < d->m_buttons.count(); pos++) { 0571 if (d->m_buttons.at(pos)->id() == id) { 0572 d->m_buttons.takeAt(pos)->deleteLater(); 0573 break; 0574 } 0575 } 0576 0577 if (d->m_buttons.isEmpty()) { 0578 d->m_btnTabSep->hide(); 0579 } 0580 } 0581 0582 void KMultiTabBar::removeTab(int id) 0583 { 0584 d->m_internal->removeTab(id); 0585 } 0586 0587 void KMultiTabBar::setTab(int id, bool state) 0588 { 0589 KMultiTabBarTab *ttab = tab(id); 0590 if (ttab) { 0591 ttab->setState(state); 0592 } 0593 } 0594 0595 bool KMultiTabBar::isTabRaised(int id) const 0596 { 0597 KMultiTabBarTab *ttab = tab(id); 0598 if (ttab) { 0599 return ttab->isChecked(); 0600 } 0601 0602 return false; 0603 } 0604 0605 void KMultiTabBar::setStyle(KMultiTabBarStyle style) 0606 { 0607 d->m_internal->setStyle(style); 0608 } 0609 0610 KMultiTabBar::KMultiTabBarStyle KMultiTabBar::tabStyle() const 0611 { 0612 return d->m_internal->m_style; 0613 } 0614 0615 void KMultiTabBar::setPosition(KMultiTabBarPosition pos) 0616 { 0617 d->m_position = pos; 0618 d->m_internal->setPosition(pos); 0619 } 0620 0621 KMultiTabBar::KMultiTabBarPosition KMultiTabBar::position() const 0622 { 0623 return d->m_position; 0624 } 0625 0626 void KMultiTabBar::fontChange(const QFont & /* oldFont */) 0627 { 0628 updateGeometry(); 0629 } 0630 0631 void KMultiTabBar::paintEvent(class QPaintEvent *) 0632 { 0633 // By default plain QWidget don't call the QStyle and are transparent 0634 // using drawPrimitive(QStyle::PE_Widget) allow QStyle implementation 0635 // to theme this class. 0636 QPainter painter(this); 0637 QStyleOption opt; 0638 opt.initFrom(this); 0639 style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); 0640 }