#include "lts_tab_widget.h" #include "lts_tab_bar.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace LtsWidgets { LtsTabWidget::LtsTabWidget(QWidget *parent, bool isSecondary) : QTabWidget(parent) , m_tabbar(nullptr) , m_hasSubTabs(false) , m_isSecondary(isSecondary) , m_mainSplitterState(QByteArray()) { setDocumentMode(true); setMinimumSize(200, 100); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); LtsTabBar *tabBar = new LtsTabBar(isSecondary, this); setTabBar(tabBar); QFont currentFont = tabBar->font(); QFont::Capitalization c = QFont::SmallCaps; bool bold = true; if (m_isSecondary) { c = QFont::MixedCase; bold = false; } currentFont.setCapitalization(c); currentFont.setBold(bold); tabBar->setFont(currentFont); connect(this, &LtsTabWidget::currentChanged, this, &LtsTabWidget::slotCurrentChanged); QSplitter *splitter = static_cast(parent); if (splitter && splitter->objectName() == QStringLiteral("mainSplitter") && splitter->widget(1) == this && !isSecondary) { int tabBarHeight = m_tabbar->height() * 1.33; int tabBarWidth = 20; setStyleSheet(styleSheet() + QStringLiteral("QTabWidget::tab-bar {" "left: %1px; " "} ").arg(tabBarWidth)); QPushButton *maxBtn = new QPushButton(this); maxBtn->setObjectName(QStringLiteral("MinMaxTabButton")); maxBtn->setText(QStringLiteral("▲\n▼")); maxBtn->setFixedSize(tabBarWidth, tabBarHeight * 2); maxBtn->setStyleSheet(QStringLiteral("QPushButton { " "border-radius: 0px; " "border: 1px solid gray; " "} ")); connect(maxBtn, &QPushButton::clicked, this, &LtsTabWidget::slotMaximizeButtonClicked); } } LtsTabWidget::~LtsTabWidget() { clearAll(); } void LtsTabWidget::slotCurrentChanged(int newIdx) { if (newIdx < 0) return; QWidget *w = widget(newIdx); if (w == nullptr) return; LtsDialogHelper *dialog1 = qobject_cast(w); if (dialog1 != nullptr) { LtsDialog *mainDialog = dialog1->getDialog(); if (mainDialog != nullptr) emit mainDialog->signalFocused(); return; } LtsTabWidget *tab = qobject_cast(w); if (tab != nullptr) { LtsDialogHelper *dialog2 = qobject_cast(tab->widget(tab->currentIndex())); if (dialog2 != nullptr) { LtsDialog *mainDialog = dialog2->getDialog(); if (mainDialog != nullptr) emit mainDialog->signalFocused(); return; } } } void LtsTabWidget::slotMaximizeButtonClicked() { LtsSplitter *splitter = static_cast(parent()); if (splitter) { if (splitter->widget(0)->height() != splitter->widget(0)->minimumHeight()) { m_mainSplitterState = splitter->saveState(); splitter->collapseChild(0); } else { splitter->restoreState(m_mainSplitterState); } } } void LtsTabWidget::closeRequestTabWidget(LtsTabWidget *tabWidget) { for (int i = 0; i < tabWidget->count(); i++) { QWidget *w = tabWidget->widget(i); LtsDialogHelper *helper = qobject_cast(w); if (helper) { LtsDialog *dlg = qobject_cast(helper->getDialog()); if (dlg) dlg->closeRequest(); } LtsTabWidget *tab = qobject_cast(w); if (tab) closeRequestTabWidget(tab); } } void LtsTabWidget::slotTabCloseRequested(int index) { QWidget *w = widget(index); if (w == nullptr) return; LtsDialogHelper *helper = qobject_cast(w); if (helper != nullptr) { LtsDialog *dialog = helper->getDialog(); if (dialog != nullptr) { if (dialog->isClosable()) { setCurrentIndex(index); dialog->closeRequest(); return; } return; } } LtsTabWidget *tab = qobject_cast(w); if (tab != nullptr) { if (LtsTabWidget::hasUnsavedChanges(tab)) { LtsWidgets::Buttons buttons; buttons.setFlag(Button::Yes, true); buttons.setFlag(Button::Cancel, true); QHash buttonTexts; buttonTexts.insert(Button::Yes, tr("Discard All")); const auto ret = LtsMessageDialog::showMessage(this, tr("There are dialogs with unsaved changes.
Please save or discard their changes before closing."), tr("Unsaved changes"), Behaviour::Modal, buttons, buttonTexts); switch (ret) { case Button::Yes: tab->deleteLater(); return; default: return; } } else { closeRequestTabWidget(tab); } } w->deleteLater(); } void LtsTabWidget::onCheckTabBarVisibility() { if (!m_isSecondary) { if (count() < 1) setHidden(true); } else { if (count() < 1) deleteLater(); } } void LtsTabWidget::setSubTabsVisible(bool visible) { if (count()) { return; } m_hasSubTabs = visible; } void LtsTabWidget::addTab(const QString &menuGroup, const QString &identifier, QWidget *widget, bool setClosable, bool focusNewTab) { setFocus(); LtsTabWidget *tab = this; QString fullName; if (m_hasSubTabs) { fullName = identifier; tab = qobject_cast(m_tabMap.value(menuGroup, nullptr)); if (!tab) { tab = new LtsTabWidget(nullptr, true); tab->setMovable(true); addWidget(menuGroup, tab, true , focusNewTab); } else { tab->setHidden(false); } if (focusNewTab) setCurrentIndex(indexOf(tab)); } else { fullName = menuGroup + QLatin1Char('.') + identifier; } setHidden(false); tab->addWidget(fullName, widget, setClosable, focusNewTab); if (m_hasSubTabs) { LtsTabBar *tabbar = tab->tabBar(); for (int i = 0; i < tabbar->count(); i++) { if (tabbar->tabButton(i, QTabBar::RightSide)->isHidden()) { m_tabbar->tabButton(currentIndex(), QTabBar::RightSide)->hide(); break; } } } } void LtsTabWidget::addWidget(const QString &fullName, QWidget *widget, bool setClosable, bool focusNewTab) { QString theName = fullName; if (fullName.isEmpty()) { const QString possibleCharacters(QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")); const int randomStringLength = 24; QString randomString; for (int i = 0; i < randomStringLength; i++) { int index = qrand() % possibleCharacters.length(); QChar nextChar = possibleCharacters.at(index); randomString.append(nextChar); } theName = randomString; } widget->setObjectName(theName); LtsDialogHelper *helper = qobject_cast(widget); if (helper) helper->retranslate(); QString title = widget->windowTitle(); if (title.isEmpty()) { if (helper) title = QStringLiteral("retranslate() fehlt!"); else title = widget->objectName(); } if (helper) { connect(helper->getDialog(), &LtsDialog::signalHide, this, static_cast(&LtsTabWidget::onRemoveTab), static_cast(Qt::QueuedConnection | Qt::UniqueConnection)); } connect(widget, &QWidget::destroyed, this, static_cast(&LtsTabWidget::onRemoveTab), Qt::DirectConnection); connect(widget, &QWidget::windowTitleChanged, this, &LtsTabWidget::slotWindowTitleChanged); if (helper) { m_tabMap.insert(theName, helper); } else { m_tabMap.insert(theName, widget); } int newTabIndex = QTabWidget::addTab(widget, title); #ifdef LTS_DEBUG setTabToolTip(newTabIndex, tr("DEBUG: WindowName: %1").arg(theName)); #endif if (focusNewTab) setCurrentIndex(newTabIndex); LtsTabBar *tabbar = qobject_cast(tabBar()); if (tabbar) { CloseButton *closeBtn = new CloseButton(); connect(closeBtn, &QPushButton::clicked, tabbar, &LtsTabBar::_internal_closeTab); tabbar->setTabButton(newTabIndex, QTabBar::RightSide, closeBtn); } if (!setClosable) m_tabbar->tabButton(newTabIndex, QTabBar::RightSide)->hide(); if (tabbar) tabbar->updateCloseButtonColors(); } void LtsTabWidget::slotWindowTitleChanged(const QString &title) { QWidget *w = qobject_cast(sender()); if (!w) { qWarning() << "LtsTabWidget: slotWindowTitleChanged failed! (not a QWidget) " << sender(); return; } int index = indexOf(w); if (index < 0) { qWarning() << "LtsTabWidget: slotWindowTitleChanged failed! (widget not found on TabBar)"; return; } QString realTitle = title; QString readOnlyHint; QStringList sl = title.split(QStringLiteral("- READ-ONLY")); if (sl.size() == 2) { realTitle = sl.first().trimmed(); readOnlyHint = sl.back().trimmed(); if (readOnlyHint.startsWith(QStringLiteral("("))) readOnlyHint.remove(0,1); if (readOnlyHint.endsWith(QStringLiteral(")"))) readOnlyHint.remove(readOnlyHint.size()-1,1); readOnlyHint = tr("Locked by: %1").arg(readOnlyHint); } setTabText(index, realTitle); QTabBar *tabBar = this->tabBar(); if (!readOnlyHint.isEmpty()) { QLabel *readOnly = new QLabel(QStringLiteral("READ-ONLY"), this); readOnly->setToolTip(readOnlyHint); readOnly->setPixmap(QIcon(QStringLiteral("icon:warning.svg")).pixmap(QSize(25, 25))); tabBar->setTabButton(index, QTabBar::ButtonPosition::LeftSide, readOnly); } else { QWidget *readOnly = tabBar->tabButton(index, QTabBar::ButtonPosition::LeftSide); if (readOnly) { tabBar->setTabButton(index, QTabBar::ButtonPosition::LeftSide, nullptr); readOnly->setParent(this); delete readOnly; } } } QWidget *LtsTabWidget::getWidget(const QString &menuGroup, const QString &identifier) { LtsTabWidget *tab = this; QString fullName; if (m_hasSubTabs) { fullName = identifier; tab = qobject_cast(m_tabMap.value(menuGroup, nullptr)); if (tab == nullptr) return nullptr; } else { fullName = menuGroup + QLatin1Char('.') + identifier; } return tab->getWidget(fullName); } QWidget *LtsTabWidget::getWidget(const QString &fullName) { return m_tabMap.value(fullName, nullptr); } bool LtsTabWidget::focusTab(const QString &menuGroup, const QString &identifier) { QPointer tab = this; QString fullName; if (m_hasSubTabs) { fullName = identifier; tab = qobject_cast(m_tabMap.value(menuGroup, nullptr)); if (!tab) { return false; } setCurrentIndex(indexOf(tab)); } else fullName = menuGroup + QLatin1Char('.') + identifier; if (!tab) return false; LtsSplitter *splitter = static_cast(parent()); QTimer::singleShot(500, this, [splitter, tab] (){ if (splitter && tab && (tab->count() >= 1) && splitter->sizes().count() && !(splitter->sizes().at(1))) { splitter->animate(1); } }); return tab->focusWidget(fullName); } bool LtsTabWidget::focusWidget(const QString &fullName) { QWidget *widget = m_tabMap.value(fullName, nullptr); if (widget == nullptr) return false; setCurrentIndex(indexOf(widget)); widget->setFocus(); return true; } bool LtsTabWidget::removeWidget(QWidget * widget) { if (nullptr == widget) return false; for (auto i = m_tabMap.begin(); i != m_tabMap.end(); ++i) { QWidget *w = i.value(); if (w == nullptr) continue; if (w == widget) { m_tabMap.erase(i); disconnect(widget, &QWidget::destroyed, this, static_cast(&LtsTabWidget::onRemoveTab)); return true; } if (m_hasSubTabs) { LtsTabWidget *tab = qobject_cast(w); if (tab != nullptr && tab->removeWidget(widget)) { return true; } } } return false; } void LtsTabWidget::onRemoveTab(QObject *object) { if (object == nullptr) { qWarning() << "onRemoveTab: obj == nullptr"; return; } QWidget *w = static_cast(object); bool rmd = false; for (auto i = m_tabMap.begin(); i != m_tabMap.end(); ++i) { if (i.value() == w) { m_tabMap.erase(i); rmd = true; break; } } if (!rmd) { qWarning() << "LtsTabWidget::onRemoveTab: widget to remove was not on tab map!"; } int index = indexOf(w); if (index < 0) { qWarning() << "LtsTabWidget::onRemoveTab: cannot remove widget, as it was not found on the tab widget" << (void *)w; } else { removeTab(index); } onCheckTabBarVisibility(); } void LtsTabWidget::setActuatorTabStyle(bool enable) { if (enable) setTabBar(new LtsTabBar(false, this)); } LtsTabBar *LtsTabWidget::tabBar() { return m_tabbar; } void LtsTabWidget::setTabBar(LtsTabBar *tabbar) { if (m_tabbar) m_tabbar->deleteLater(); QFont currentFont = font(); currentFont.setCapitalization(QFont::SmallCaps); currentFont.setBold(true); tabbar->setFont(currentFont); connect(tabbar, &LtsTabBar::tabCloseRequested, this, &LtsTabWidget::slotTabCloseRequested); m_tabbar = tabbar; QTabWidget::setTabBar(m_tabbar); } void LtsTabWidget::clearAll() { for (int i = 0; i < count(); i++) { QWidget *w = widget(i); if (w == nullptr) continue; disconnect(w, &QWidget::destroyed, this, static_cast(&LtsTabWidget::onRemoveTab)); w->deleteLater(); } clear(); } void LtsTabWidget::aboutToClose() { for (int i = 0; i < count(); i++) { if (m_hasSubTabs) { LtsTabWidget *tab = qobject_cast(widget(i)); tab->aboutToClose(); } else { LtsDialogHelper *helper = qobject_cast(widget(i)); if (helper == nullptr) continue; LtsDialog *dialog = qobject_cast(helper->getDialog()); if (dialog == nullptr) continue; dialog->aboutToClose(); } } } bool LtsTabWidget::hasUnsavedChanges(LtsTabWidget *tabWidget) { for (int i = 0; i < tabWidget->count(); i++) { QWidget *w = tabWidget->widget(i); LtsDialogHelper *helper = qobject_cast(w); if (helper) { LtsDialog *dlg = qobject_cast(helper->getDialog()); if (dlg != nullptr) { if (dlg->hasUnsavedChanges()) { int dlgIndex = tabWidget->indexOf(helper); if (dlgIndex != -1) tabWidget->setCurrentIndex(dlgIndex); return true; } } } LtsTabWidget *tab = qobject_cast(w); if (tab) { bool _hasUnsavedChanges = hasUnsavedChanges(tab); if (_hasUnsavedChanges) { int tabIndex = tabWidget->indexOf(tab); if (tabIndex != -1) tabWidget->setCurrentIndex(tabIndex); return true; } } } return false; } void CloseButton::paintEvent(QPaintEvent *) { QPainter p(this); p.save(); p.setPen(QPen(QColor(m_fontColor), 1.5)); p.setRenderHint(QPainter::Antialiasing); p.drawLine(line()); p.setPen(QPen(QColor(m_fontColor), 2.5)); QRectF c = cross(); p.drawLine(c.topLeft(), c.bottomRight()); p.drawLine(c.bottomLeft(), c.topRight()); p.restore(); } QLineF CloseButton::line() const { qreal forceBlur = 0.001; qreal lineStart = height() * (0.25 / 2) + forceBlur; qreal lineSize = height() - lineStart; return QLineF(QPointF(lineStart, lineStart), QPointF(lineStart, lineSize)); } QRectF CloseButton::cross() const { QLineF l = line(); qreal lineLeft = l.x1(); qreal forceBlur = 0.001; qreal crossStartX = lineLeft * 3; qreal crossStartY = lineLeft * 2; qreal crossEndX = width(); crossEndX -= crossEndX * 0.15 + forceBlur; qreal crossEndY = height() - crossStartY; return QRectF(QPointF(crossStartX, crossStartY), QPointF(crossEndX, crossEndY)); } QSize CloseButton::sizeHint() const { QFontMetrics fm (QApplication::font()); int fontHeight = fm.height(); int fontHeightQuarter = fontHeight * 0.25; int finalSize = fontHeight + fontHeightQuarter; #ifdef TABLET_MODE finalSize = finalSize*1.5; #endif return QSize(finalSize, finalSize); } void CloseButton::mousePressEvent(QMouseEvent *e) { e->ignore(); m_isPressed = true; } void CloseButton::mouseReleaseEvent(QMouseEvent *e) { bool isPressed = m_isPressed; m_isPressed = false; if (!isPressed) { e->ignore(); return; } QRectF c = cross(); QRect adjusted = QRect(QPoint(floor(c.x()), floor(c.y())), QPoint(ceil(c.right()), ceil(c.bottom() +1 ))); if (!adjusted.contains(e->localPos().toPoint())) { e->ignore(); return; } e->accept(); emit clicked(); } }