KWidgetsAddons

kmultitabbar.cpp
1/*
2 SPDX-FileCopyrightText: 2001, 2002, 2003 Joseph Wenninger <jowenn@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kmultitabbar.h"
8#include "kmultitabbar_p.h"
9#include "moc_kmultitabbar.cpp"
10#include "moc_kmultitabbar_p.cpp"
11
12#include <QMouseEvent>
13#include <QPainter>
14#include <QStyle>
15#include <QStyleOptionButton>
16
17#include <math.h>
18
19/*
20 A bit of an overview about what's going on here:
21 There are two sorts of things that can be in the tab bar: tabs and regular
22 buttons. The former are managed by KMultiTabBarInternal, the later
23 by the KMultiTabBar itself.
24*/
25
26class KMultiTabBarTabPrivate
27{
28};
29class KMultiTabBarButtonPrivate
30{
31};
32
33class KMultiTabBarPrivate
34{
35public:
36 class KMultiTabBarInternal *m_internal;
37 QBoxLayout *m_l;
38 QFrame *m_btnTabSep;
40 KMultiTabBar::KMultiTabBarPosition m_position;
41};
42
43KMultiTabBarInternal::KMultiTabBarInternal(QWidget *parent, KMultiTabBar::KMultiTabBarPosition pos)
44 : QFrame(parent)
45{
46 m_position = pos;
47 if (pos == KMultiTabBar::Left || pos == KMultiTabBar::Right) {
48 mainLayout = new QVBoxLayout(this);
49 } else {
50 mainLayout = new QHBoxLayout(this);
51 }
52 mainLayout->setContentsMargins(0, 0, 0, 0);
53 mainLayout->setSpacing(0);
54 mainLayout->addStretch();
55 setFrameStyle(NoFrame);
56 setBackgroundRole(QPalette::Window);
57}
58
59KMultiTabBarInternal::~KMultiTabBarInternal()
60{
61 qDeleteAll(m_tabs);
62 m_tabs.clear();
63}
64
65void KMultiTabBarInternal::setStyle(enum KMultiTabBar::KMultiTabBarStyle style)
66{
67 m_style = style;
68 for (int i = 0; i < m_tabs.count(); i++) {
69 m_tabs.at(i)->setStyle(m_style);
70 }
71
72 updateGeometry();
73}
74
75void KMultiTabBarInternal::contentsMousePressEvent(QMouseEvent *ev)
76{
77 ev->ignore();
78}
79
80void KMultiTabBarInternal::mousePressEvent(QMouseEvent *ev)
81{
82 ev->ignore();
83}
84
85KMultiTabBarTab *KMultiTabBarInternal::tab(int id) const
86{
88 while (it.hasNext()) {
89 KMultiTabBarTab *tab = it.next();
90 if (tab->id() == id) {
91 return tab;
92 }
93 }
94 return nullptr;
95}
96
97int KMultiTabBarInternal::appendTab(const QIcon &icon, int id, const QString &text)
98{
99 KMultiTabBarTab *tab;
100 m_tabs.append(tab = new KMultiTabBarTab(icon, text, id, this, m_position, m_style));
101
102 // Insert before the stretch..
103 mainLayout->insertWidget(m_tabs.size() - 1, tab);
104 tab->show();
105 return 0;
106}
107
108int KMultiTabBarInternal::appendTab(const QPixmap &pic, int id, const QString &text)
109{
110 // reuse icon variant
111 return appendTab(QIcon(pic), id, text);
112}
113
114void KMultiTabBarInternal::removeTab(int id)
115{
116 for (int pos = 0; pos < m_tabs.count(); pos++) {
117 if (m_tabs.at(pos)->id() == id) {
118 // remove & delete the tab
119 delete m_tabs.takeAt(pos);
120 break;
121 }
122 }
123}
124
125void KMultiTabBarInternal::setPosition(enum KMultiTabBar::KMultiTabBarPosition pos)
126{
127 m_position = pos;
128 for (int i = 0; i < m_tabs.count(); i++) {
129 m_tabs.at(i)->setPosition(m_position);
130 }
131 updateGeometry();
132}
133
134// KMultiTabBarButton
135///////////////////////////////////////////////////////////////////////////////
136
137KMultiTabBarButton::KMultiTabBarButton(const QIcon &icon, const QString &text, int id, QWidget *parent)
138 : QPushButton(icon, text, parent)
139 , m_id(id)
140 , d(nullptr)
141{
142 connect(this, &QPushButton::clicked, this, &KMultiTabBarButton::slotClicked);
143
144 // we can't see the focus, so don't take focus. #45557
145 // If keyboard navigation is wanted, then only the bar should take focus,
146 // and arrows could change the focused button; but generally, tabbars don't take focus anyway.
149 Q_UNUSED(d);
150}
151
152KMultiTabBarButton::~KMultiTabBarButton()
153{
154}
155
156void KMultiTabBarButton::setText(const QString &text)
157{
159}
160
161void KMultiTabBarButton::slotClicked()
162{
164 Q_EMIT clicked(m_id);
165}
166
167int KMultiTabBarButton::id() const
168{
169 return m_id;
170}
171
172void KMultiTabBarButton::hideEvent(QHideEvent *he)
173{
175 KMultiTabBar *tb = dynamic_cast<KMultiTabBar *>(parentWidget());
176 if (tb) {
177 tb->updateSeparator();
178 }
179}
180
181void KMultiTabBarButton::showEvent(QShowEvent *he)
182{
184 KMultiTabBar *tb = dynamic_cast<KMultiTabBar *>(parentWidget());
185 if (tb) {
186 tb->updateSeparator();
187 }
188}
189
190void KMultiTabBarButton::paintEvent(QPaintEvent *)
191{
193 opt.initFrom(this);
194 opt.icon = icon();
195 opt.iconSize = iconSize();
196 // removes the QStyleOptionButton::HasMenu ButtonFeature
197 opt.features = QStyleOptionButton::Flat;
198 QPainter painter(this);
199 style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this);
200}
201
202// KMultiTabBarTab
203///////////////////////////////////////////////////////////////////////////////
204
205KMultiTabBarTab::KMultiTabBarTab(const QIcon &icon,
206 const QString &text,
207 int id,
208 QWidget *parent,
209 KMultiTabBar::KMultiTabBarPosition pos,
211 : KMultiTabBarButton(icon, text, id, parent)
212 , m_style(style)
213 , d(nullptr)
214{
215 m_position = pos;
217 setCheckable(true);
218 // shrink down to icon only, but prefer to show text if it's there
220}
221
222KMultiTabBarTab::~KMultiTabBarTab()
223{
224}
225
226void KMultiTabBarTab::setPosition(KMultiTabBar::KMultiTabBarPosition pos)
227{
228 m_position = pos;
230}
231
237
238void KMultiTabBarTab::initStyleOption(QStyleOptionToolButton *opt) const
239{
240 opt->initFrom(this);
241
242 // Setup icon..
243 if (!icon().isNull()) {
244 opt->iconSize = iconSize();
245 opt->icon = icon();
246 }
247
248 // Should we draw text?
249 if (shouldDrawText()) {
250 opt->text = text();
251 }
252
253 opt->state |= QStyle::State_AutoRaise;
254 if (!isChecked() && !isDown()) {
255 opt->state |= QStyle::State_Raised;
256 }
257 if (isDown()) {
258 opt->state |= QStyle::State_Sunken;
259 }
260 if (isChecked()) {
261 opt->state |= QStyle::State_On;
262 }
263
264 opt->font = font();
265 opt->toolButtonStyle = shouldDrawText() ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly;
266 opt->subControls = QStyle::SC_ToolButton;
267}
268
269QSize KMultiTabBarTab::sizeHint() const
270{
271 return computeSizeHint(shouldDrawText());
272}
273
274QSize KMultiTabBarTab::minimumSizeHint() const
275{
276 return computeSizeHint(false);
277}
278
279void KMultiTabBarTab::computeMargins(int *hMargin, int *vMargin) const
280{
281 // Unfortunately, QStyle does not give us enough information to figure out
282 // where to place things, so we try to reverse-engineer it
284 initStyleOption(&opt);
285
286 QSize trialSize = opt.iconSize;
287 QSize expandSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, trialSize, this);
288
289 *hMargin = (expandSize.width() - trialSize.width()) / 2;
290 *vMargin = (expandSize.height() - trialSize.height()) / 2;
291}
292
293QSize KMultiTabBarTab::computeSizeHint(bool withText) const
294{
295 // Compute as horizontal first, then flip around if need be.
297 initStyleOption(&opt);
298
299 int hMargin;
300 int vMargin;
301 computeMargins(&hMargin, &vMargin);
302
303 // Compute interior size, starting from pixmap..
304 QSize size = opt.iconSize;
305
306 // Always include text height in computation, to avoid resizing the minor direction
307 // when expanding text..
308 QSize textSize = fontMetrics().size(0, text());
309 size.setHeight(qMax(size.height(), textSize.height()));
310
311 // Pick margins for major/minor direction, depending on orientation
312 int majorMargin = isVertical() ? vMargin : hMargin;
313 int minorMargin = isVertical() ? hMargin : vMargin;
314
315 size.setWidth(size.width() + 2 * majorMargin);
316 size.setHeight(size.height() + 2 * minorMargin);
317
318 if (withText && !text().isEmpty())
319 // Add enough room for the text, and an extra major margin.
320 {
321 size.setWidth(size.width() + textSize.width() + majorMargin);
322 }
323
324 // flip time?
325 if (isVertical()) {
326 return QSize(size.height(), size.width());
327 } else {
328 return size;
329 }
330}
331
332void KMultiTabBarTab::setState(bool newState)
333{
334 setChecked(newState);
336}
337
338bool KMultiTabBarTab::shouldDrawText() const
339{
340 return (m_style == KMultiTabBar::KDEV3ICON) || isChecked();
341}
342
343bool KMultiTabBarTab::isVertical() const
344{
345 return m_position == KMultiTabBar::Right || m_position == KMultiTabBar::Left;
346}
347
348void KMultiTabBarTab::paintEvent(QPaintEvent *)
349{
350 QPainter painter(this);
351
353 initStyleOption(&opt);
354
355 // Paint bevel..
356 if (underMouse() || isChecked()) {
357 opt.text.clear();
358 opt.icon = QIcon();
359 style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &painter, this);
360 }
361
362 int hMargin;
363 int vMargin;
364 computeMargins(&hMargin, &vMargin);
365
366 // We first figure out how much room we have for the text, based on
367 // icon size and margin, try to fit in by eliding, and perhaps
368 // give up on drawing the text entirely if we're too short on room
369 int textRoom = 0;
370 int iconRoom = 0;
371
372 QString t;
373 if (shouldDrawText()) {
374 if (isVertical()) {
375 iconRoom = opt.iconSize.height() + 2 * vMargin;
376 textRoom = height() - iconRoom - vMargin;
377 } else {
378 iconRoom = opt.iconSize.width() + 2 * hMargin;
379 textRoom = width() - iconRoom - hMargin;
380 }
381
382 t = painter.fontMetrics().elidedText(text(), Qt::ElideRight, textRoom);
383
384 // See whether anything is left. Qt will return either
385 // ... or the ellipsis unicode character, 0x2026
386 if (t == QLatin1String("...") || t == QChar(0x2026)) {
387 t.clear();
388 }
389 }
390
391 const QIcon::Mode iconMode = (opt.state & QStyle::State_MouseOver) ? QIcon::Active : QIcon::Normal;
392 const QPixmap iconPixmap = icon().pixmap(opt.iconSize, devicePixelRatioF(), iconMode, QIcon::On);
393
394 // Label time.... Simple case: no text, so just plop down the icon right in the center
395 // We only do this when the button never draws the text, to avoid jumps in icon position
396 // when resizing
397 if (!shouldDrawText()) {
398 style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter | Qt::AlignVCenter, iconPixmap);
399 return;
400 }
401
402 // Now where the icon/text goes depends on text direction and tab position
403 QRect iconArea;
404 QRect labelArea;
405
406 bool bottomIcon = false;
407 bool rtl = layoutDirection() == Qt::RightToLeft;
408 if (isVertical()) {
409 if (m_position == KMultiTabBar::Left && !rtl) {
410 bottomIcon = true;
411 }
412 if (m_position == KMultiTabBar::Right && rtl) {
413 bottomIcon = true;
414 }
415 }
416 // alignFlags = Qt::AlignLeading | Qt::AlignVCenter;
417
418 const int iconXShift = (isChecked() || isDown()) ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &opt, this) : 0;
419 const int iconYShift = (isChecked() || isDown()) ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical, &opt, this) : 0;
420 if (isVertical()) {
421 if (bottomIcon) {
422 labelArea = QRect(0, vMargin, width(), textRoom);
423 iconArea = QRect(0, vMargin + textRoom, width(), iconRoom);
424 iconArea.translate(iconYShift, -iconXShift);
425 } else {
426 labelArea = QRect(0, iconRoom, width(), textRoom);
427 iconArea = QRect(0, 0, width(), iconRoom);
428 iconArea.translate(-iconYShift, iconXShift);
429 }
430 } else {
431 // Pretty simple --- depends only on RTL/LTR
432 if (rtl) {
433 labelArea = QRect(hMargin, 0, textRoom, height());
434 iconArea = QRect(hMargin + textRoom, 0, iconRoom, height());
435 } else {
436 labelArea = QRect(iconRoom, 0, textRoom, height());
437 iconArea = QRect(0, 0, iconRoom, height());
438 }
439 iconArea.translate(iconXShift, iconYShift);
440 }
441
442 style()->drawItemPixmap(&painter, iconArea, Qt::AlignCenter | Qt::AlignVCenter, iconPixmap);
443
444 if (t.isEmpty()) {
445 return;
446 }
447
448 QRect labelPaintArea = labelArea;
449
450 if (isVertical()) {
451 // If we're vertical, we paint to a simple 0,0 origin rect,
452 // and get the transformations to get us in the right place
453 labelPaintArea = QRect(0, 0, labelArea.height(), labelArea.width());
454
456
457 if (bottomIcon) {
458 tr.translate(labelArea.x(), labelPaintArea.width() + labelArea.y());
459 tr.rotate(-90);
460 } else {
461 tr.translate(labelPaintArea.height() + labelArea.x(), labelArea.y());
462 tr.rotate(90);
463 }
464 painter.setTransform(tr);
465 }
466
467 opt.text = t;
468 opt.icon = QIcon();
469 opt.rect = labelPaintArea;
470 style()->drawControl(QStyle::CE_ToolButtonLabel, &opt, &painter, this);
471}
472
473// KMultiTabBar
474///////////////////////////////////////////////////////////////////////////////
475
477 : KMultiTabBar(Left, parent)
478{
479}
480
481KMultiTabBar::KMultiTabBar(KMultiTabBarPosition pos, QWidget *parent)
482 : QWidget(parent)
483 , d(new KMultiTabBarPrivate)
484{
485 if (pos == Left || pos == Right) {
486 d->m_l = new QVBoxLayout(this);
488 } else {
489 d->m_l = new QHBoxLayout(this);
491 }
492 d->m_l->setContentsMargins(0, 0, 0, 0);
493 d->m_l->setSpacing(0);
494
495 d->m_internal = new KMultiTabBarInternal(this, pos);
498 d->m_l->insertWidget(0, d->m_internal);
499 d->m_l->insertWidget(0, d->m_btnTabSep = new QFrame(this));
500 d->m_btnTabSep->setFixedHeight(4);
501 d->m_btnTabSep->setFrameStyle(QFrame::Panel | QFrame::Sunken);
502 d->m_btnTabSep->setLineWidth(2);
503 d->m_btnTabSep->hide();
504
506}
507
508KMultiTabBar::~KMultiTabBar()
509{
510 qDeleteAll(d->m_buttons);
511 d->m_buttons.clear();
512}
513
514int KMultiTabBar::appendButton(const QIcon &icon, int id, QMenu *popup, const QString &)
515{
516 KMultiTabBarButton *btn = new KMultiTabBarButton(icon, QString(), id, this);
517 // a button with a QMenu can have another size. Make sure the button has always the same size.
518 btn->setFixedWidth(btn->height());
519 btn->setMenu(popup);
520 d->m_buttons.append(btn);
521 d->m_l->insertWidget(0, btn);
522 btn->show();
523 d->m_btnTabSep->show();
524 return 0;
525}
526
527void KMultiTabBar::updateSeparator()
528{
529 bool hideSep = true;
531 while (it.hasNext()) {
532 if (it.next()->isVisibleTo(this)) {
533 hideSep = false;
534 break;
535 }
536 }
537 if (hideSep) {
538 d->m_btnTabSep->hide();
539 } else {
540 d->m_btnTabSep->show();
541 }
542}
543
544int KMultiTabBar::appendTab(const QIcon &icon, int id, const QString &text)
545{
546 d->m_internal->appendTab(icon, id, text);
547 return 0;
548}
549
551{
553 while (it.hasNext()) {
555 if (button->id() == id) {
556 return button;
557 }
558 }
559
560 return nullptr;
561}
562
564{
565 return d->m_internal->tab(id);
566}
567
569{
570 for (int pos = 0; pos < d->m_buttons.count(); pos++) {
571 if (d->m_buttons.at(pos)->id() == id) {
572 d->m_buttons.takeAt(pos)->deleteLater();
573 break;
574 }
575 }
576
577 if (d->m_buttons.isEmpty()) {
578 d->m_btnTabSep->hide();
579 }
580}
581
583{
584 d->m_internal->removeTab(id);
585}
586
587void KMultiTabBar::setTab(int id, bool state)
588{
589 KMultiTabBarTab *ttab = tab(id);
590 if (ttab) {
591 ttab->setState(state);
592 }
593}
594
596{
597 KMultiTabBarTab *ttab = tab(id);
598 if (ttab) {
599 return ttab->isChecked();
600 }
601
602 return false;
603}
604
606{
607 d->m_internal->setStyle(style);
608}
609
610KMultiTabBar::KMultiTabBarStyle KMultiTabBar::tabStyle() const
611{
612 return d->m_internal->m_style;
613}
614
615void KMultiTabBar::setPosition(KMultiTabBarPosition pos)
616{
617 d->m_position = pos;
618 d->m_internal->setPosition(pos);
619}
620
621KMultiTabBar::KMultiTabBarPosition KMultiTabBar::position() const
622{
623 return d->m_position;
624}
625
626void KMultiTabBar::fontChange(const QFont & /* oldFont */)
627{
629}
630
631void KMultiTabBar::paintEvent(class QPaintEvent *)
632{
633 // By default plain QWidget don't call the QStyle and are transparent
634 // using drawPrimitive(QStyle::PE_Widget) allow QStyle implementation
635 // to theme this class.
636 QPainter painter(this);
637 QStyleOption opt;
638 opt.initFrom(this);
639 style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
640}
Use KMultiTabBar::appendButton to append a button, which creates a KMultiTabBarButton instance.
void clicked(int id)
this is emitted if the button is clicked
KMultiTabBarButton(const QIcon &icon, const QString &, int id, QWidget *parent)
Should not be created directly.
Use KMultiTabBar::appendTab to append a tab, which creates a KMultiTabBarTab instance.
void setStyle(KMultiTabBar::KMultiTabBarStyle)
this is used internally, but can be used by the user, if (s)he wants to It the according call of KMul...
void setState(bool state)
set the active state of the tab
void setPosition(KMultiTabBar::KMultiTabBarPosition)
this is used internally, but can be used by the user, if (s)he wants to It the according call of KMul...
A Widget for horizontal and vertical tabs.
void setStyle(KMultiTabBarStyle style)
set the display style of the tabs
int appendButton(const QIcon &icon, int id=-1, QMenu *popup=nullptr, const QString &not_used_yet=QString())
append a new button to the button area.
KMultiTabBarStyle
The list of available styles for KMultiTabBar.
@ KDEV3ICON
KDevelop 3 like, always shows the text and icons.
@ VSNET
Visual Studio .Net like, always shows icon, only show the text of active tabs.
void removeTab(int id)
remove a tab with a given ID
int appendTab(const QIcon &icon, int id=-1, const QString &text=QString())
append a new tab to the tab area.
void removeButton(int id)
remove a button with the given ID
KMultiTabBar(QWidget *parent=nullptr)
Create a KMultiTabBar with Left as KMultiTabBar position.
void setTab(int id, bool state)
set a tab to "raised"
class KMultiTabBarButton * button(int id) const
get a pointer to a button within the button area identified by its ID
class KMultiTabBarTab * tab(int id) const
get a pointer to a tab within the tab area, identified by its ID
bool isTabRaised(int id) const
return the state of a tab, identified by its ID
void setPosition(KMultiTabBarPosition pos)
set the real position of the widget.
void setCheckable(bool)
bool isChecked() const const
void clicked(bool checked)
bool isDown() const const
void setText(const QString &text)
void ignore()
QSize size(int flags, const QString &text, int tabStops, int *tabArray) const const
bool hasNext() const const
const T & next()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
qreal devicePixelRatioF() const const
void setMenu(QMenu *menu)
int height() const const
void translate(const QPoint &offset)
int width() const const
int x() const const
int y() const const
int height() const const
void setHeight(int height)
int width() const const
void clear()
bool isEmpty() const const
PM_ButtonShiftHorizontal
virtual void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const const=0
virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const=0
virtual void drawItemPixmap(QPainter *painter, const QRect &rectangle, int alignment, const QPixmap &pixmap) const const
virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const=0
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const const=0
void initFrom(const QWidget *widget)
AlignCenter
RightToLeft
ElideRight
ToolButtonTextBesideIcon
WA_LayoutUsesWidgetRect
void setFocusPolicy(Qt::FocusPolicy policy)
QFontMetrics fontMetrics() const const
virtual void hideEvent(QHideEvent *event)
QWidget * parentWidget() const const
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void setContentsMargins(const QMargins &margins)
void setFixedWidth(int w)
void show()
virtual void showEvent(QShowEvent *event)
void setSizePolicy(QSizePolicy)
QStyle * style() const const
void setToolTip(const QString &)
bool underMouse() const const
void updateGeometry()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:11:51 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.