File indexing completed on 2024-04-28 05:50:55

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Tomaz Canabrava <tcanabrava@kde.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "TerminalHeaderBar.h"
0008 
0009 #include "KonsoleSettings.h"
0010 #include "ViewProperties.h"
0011 #include "session/SessionController.h"
0012 #include "terminalDisplay/TerminalDisplay.h"
0013 #include "widgets/ViewSplitter.h"
0014 
0015 #include <KLocalizedString>
0016 #include <QApplication>
0017 #include <QBoxLayout>
0018 #include <QDrag>
0019 #include <QLabel>
0020 #include <QMimeData>
0021 #include <QPaintEvent>
0022 #include <QPainter>
0023 #include <QSplitter>
0024 #include <QStyleOptionTabBarBase>
0025 #include <QStylePainter>
0026 #include <QTabBar>
0027 #include <QToolButton>
0028 
0029 namespace Konsole
0030 {
0031 TerminalHeaderBar::TerminalHeaderBar(QWidget *parent)
0032     : QWidget(parent)
0033 {
0034     m_boxLayout = new QBoxLayout(QBoxLayout::LeftToRight);
0035     m_boxLayout->setSpacing(0);
0036     m_boxLayout->setContentsMargins(0, 0, 0, 0);
0037 
0038     // Session icon
0039 
0040     m_terminalIcon = new QLabel(this);
0041     m_terminalIcon->setAlignment(Qt::AlignCenter);
0042     m_terminalIcon->setFixedSize(20, 20);
0043 
0044     m_boxLayout->addWidget(m_terminalIcon);
0045 
0046     // Status icons
0047 
0048     QLabel **statusIcons[] = {&m_statusIconReadOnly, &m_statusIconCopyInput, &m_statusIconSilence, &m_statusIconActivity, &m_statusIconBell};
0049 
0050     for (auto **statusIcon : statusIcons) {
0051         *statusIcon = new QLabel(this);
0052         (*statusIcon)->setAlignment(Qt::AlignCenter);
0053         (*statusIcon)->setFixedSize(20, 20);
0054         (*statusIcon)->setVisible(false);
0055 
0056         m_boxLayout->addWidget(*statusIcon);
0057     }
0058 
0059     m_statusIconReadOnly->setPixmap(QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(QSize(16, 16)));
0060     m_statusIconCopyInput->setPixmap(QIcon::fromTheme(QStringLiteral("irc-voice")).pixmap(QSize(16, 16)));
0061     m_statusIconSilence->setPixmap(QIcon::fromTheme(QStringLiteral("system-suspend")).pixmap(QSize(16, 16)));
0062     m_statusIconActivity->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(QSize(16, 16)));
0063     m_statusIconBell->setPixmap(QIcon::fromTheme(QStringLiteral("notifications")).pixmap(QSize(16, 16)));
0064 
0065     // Title
0066 
0067     m_terminalTitle = new QLabel(this);
0068     m_terminalTitle->setFont(QApplication::font());
0069 
0070     m_boxLayout->addStretch();
0071     m_boxLayout->addWidget(m_terminalTitle);
0072     m_boxLayout->addStretch();
0073 
0074     // Expand button
0075 
0076     m_toggleExpandedMode = new QToolButton(this);
0077     m_toggleExpandedMode->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); // fake 'expand' icon. VDG input?
0078     m_toggleExpandedMode->setAutoRaise(true);
0079     m_toggleExpandedMode->setCheckable(true);
0080     m_toggleExpandedMode->setToolTip(i18nc("@info:tooltip", "Maximize terminal"));
0081 
0082     connect(m_toggleExpandedMode, &QToolButton::clicked, this, &TerminalHeaderBar::requestToggleExpansion);
0083 
0084     m_boxLayout->addWidget(m_toggleExpandedMode);
0085 
0086     // Move to new tab button
0087 
0088     m_moveToNewTab = new QToolButton(this);
0089     m_moveToNewTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
0090     m_moveToNewTab->setAutoRaise(true);
0091     m_moveToNewTab->setToolTip(i18nc("@info:tooltip", "Move terminal to new tab"));
0092 
0093     connect(m_moveToNewTab, &QToolButton::clicked, this, &TerminalHeaderBar::requestMoveToNewTab);
0094 
0095     m_boxLayout->addWidget(m_moveToNewTab);
0096 
0097     // Close button
0098 
0099     m_closeBtn = new QToolButton(this);
0100     m_closeBtn->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
0101     m_closeBtn->setToolTip(i18nc("@info:tooltip", "Close terminal"));
0102     m_closeBtn->setObjectName(QStringLiteral("close-terminal-button"));
0103     m_closeBtn->setAutoRaise(true);
0104 
0105     m_boxLayout->addWidget(m_closeBtn);
0106 
0107     // The widget itself
0108 
0109     setLayout(m_boxLayout);
0110     setAutoFillBackground(true);
0111     setFocusIndicatorState(false);
0112 }
0113 
0114 void TerminalHeaderBar::mouseDoubleClickEvent(QMouseEvent *ev)
0115 {
0116     if (ev->button() != Qt::LeftButton) {
0117         return;
0118     }
0119     m_toggleExpandedMode->click();
0120 }
0121 
0122 // Hack until I can detangle the creation of the TerminalViews
0123 void TerminalHeaderBar::finishHeaderSetup(ViewProperties *properties)
0124 {
0125     auto controller = dynamic_cast<SessionController *>(properties);
0126     connect(properties, &Konsole::ViewProperties::titleChanged, this, [this, properties] {
0127         m_terminalTitle->setText(properties->title());
0128     });
0129     m_terminalTitle->setText(properties->title());
0130 
0131     connect(properties, &Konsole::ViewProperties::iconChanged, this, [this, properties] {
0132         m_terminalIcon->setPixmap(properties->icon().pixmap(QSize(22, 22)));
0133     });
0134     m_terminalIcon->setPixmap(properties->icon().pixmap(QSize(22, 22)));
0135 
0136     connect(properties, &Konsole::ViewProperties::notificationChanged, this, &Konsole::TerminalHeaderBar::updateNotification);
0137 
0138     connect(properties, &Konsole::ViewProperties::readOnlyChanged, this, &Konsole::TerminalHeaderBar::updateSpecialState);
0139 
0140     connect(properties, &Konsole::ViewProperties::copyInputChanged, this, &Konsole::TerminalHeaderBar::updateSpecialState);
0141 
0142     connect(m_closeBtn, &QToolButton::clicked, controller, &SessionController::closeSession);
0143 }
0144 
0145 void TerminalHeaderBar::setFocusIndicatorState(bool focused)
0146 {
0147     m_terminalIsFocused = focused;
0148     update();
0149 }
0150 
0151 void TerminalHeaderBar::updateNotification(ViewProperties *item, Session::Notification notification, bool enabled)
0152 {
0153     Q_UNUSED(item)
0154 
0155     switch (notification) {
0156     case Session::Notification::Silence:
0157         m_statusIconSilence->setVisible(enabled);
0158         break;
0159     case Session::Notification::Activity:
0160         m_statusIconActivity->setVisible(enabled);
0161         break;
0162     case Session::Notification::Bell:
0163         m_statusIconBell->setVisible(enabled);
0164         break;
0165     default:
0166         break;
0167     }
0168 }
0169 
0170 void TerminalHeaderBar::updateSpecialState(ViewProperties *item)
0171 {
0172     auto controller = dynamic_cast<SessionController *>(item);
0173 
0174     if (controller != nullptr) {
0175         m_statusIconReadOnly->setVisible(controller->isReadOnly());
0176         m_statusIconCopyInput->setVisible(controller->isCopyInputActive());
0177     }
0178 }
0179 
0180 void TerminalHeaderBar::setExpandedMode(bool expand)
0181 {
0182     if (m_toggleExpandedMode->isChecked() != expand) {
0183         m_toggleExpandedMode->setChecked(expand);
0184     }
0185     if (expand) {
0186         m_toggleExpandedMode->setToolTip(i18nc("@info:tooltip", "Restore terminal"));
0187     } else {
0188         m_toggleExpandedMode->setToolTip(i18nc("@info:tooltip", "Maximize terminal"));
0189     }
0190 }
0191 
0192 void TerminalHeaderBar::paintEvent(QPaintEvent *paintEvent)
0193 {
0194     /* Try to get the widget that's 10px above this one.
0195      * If the widget is something else than a TerminalWidget, a TabBar or a QSplitter,
0196      * draw a 1px line to separate it from the others.
0197      */
0198 
0199     const auto globalPos = parentWidget()->mapToGlobal(pos());
0200     auto *widget = qApp->widgetAt(globalPos.x() + 10, globalPos.y() - 10);
0201 
0202     const bool isTabbar = qobject_cast<QTabBar *>(widget) != nullptr;
0203     const bool isTerminalWidget = qobject_cast<TerminalDisplay *>(widget) != nullptr;
0204     const bool isSplitter = (qobject_cast<QSplitter *>(widget) != nullptr) || (qobject_cast<QSplitterHandle *>(widget) != nullptr);
0205     if ((widget != nullptr) && !isTabbar && !isTerminalWidget && !isSplitter) {
0206         QStyleOptionTabBarBase optTabBase;
0207         QStylePainter p(this);
0208         optTabBase.initFrom(this);
0209         optTabBase.shape = QTabBar::Shape::RoundedSouth;
0210         optTabBase.documentMode = false;
0211         p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
0212     }
0213 
0214     QWidget::paintEvent(paintEvent);
0215     if (!m_terminalIsFocused) {
0216         auto p = qApp->palette();
0217         auto shadowColor = p.color(QPalette::ColorRole::Shadow);
0218         shadowColor.setAlphaF(qreal(0.2) * shadowColor.alphaF()); // same as breeze.
0219 
0220         QPainter painter(this);
0221         painter.setPen(Qt::NoPen);
0222         painter.setBrush(shadowColor);
0223         painter.drawRect(rect());
0224     }
0225 }
0226 
0227 void TerminalHeaderBar::mouseMoveEvent(QMouseEvent *ev)
0228 {
0229     if (m_toggleExpandedMode->isChecked()) {
0230         return;
0231     }
0232     auto point = ev->pos() - m_startDrag;
0233     if (point.manhattanLength() > 10) {
0234         auto drag = new QDrag(parent());
0235         auto mimeData = new QMimeData();
0236         QByteArray payload;
0237         payload.setNum(qApp->applicationPid());
0238         mimeData->setData(QStringLiteral("konsole/terminal_display"), payload);
0239         drag->setMimeData(mimeData);
0240         drag->exec();
0241     }
0242 }
0243 
0244 void TerminalHeaderBar::mousePressEvent(QMouseEvent *ev)
0245 {
0246     m_startDrag = ev->pos();
0247 }
0248 
0249 void TerminalHeaderBar::mouseReleaseEvent(QMouseEvent *ev)
0250 {
0251     Q_UNUSED(ev)
0252 }
0253 
0254 QSize TerminalHeaderBar::minimumSizeHint() const
0255 {
0256     auto height = sizeHint().height();
0257     return {height, height};
0258 }
0259 
0260 QSplitter *TerminalHeaderBar::getTopLevelSplitter()
0261 {
0262     QWidget *p = parentWidget();
0263     // This is expected.
0264     if (qobject_cast<TerminalDisplay *>(p) != nullptr) {
0265         p = p->parentWidget();
0266     }
0267 
0268     // this is also expected.
0269     auto *innerSplitter = qobject_cast<ViewSplitter *>(p);
0270     if (innerSplitter == nullptr) {
0271         return nullptr;
0272     }
0273 
0274     return innerSplitter->getToplevelSplitter();
0275 }
0276 
0277 void TerminalHeaderBar::applyVisibilitySettings()
0278 {
0279     auto *settings = KonsoleSettings::self();
0280     auto toVisibility = settings->splitViewVisibility();
0281     const bool singleTerminalView = (getTopLevelSplitter()->findChildren<TerminalDisplay *>().count() == 1);
0282     switch (toVisibility) {
0283     case KonsoleSettings::AlwaysShowSplitHeader:
0284         m_toggleExpandedMode->setDisabled(singleTerminalView);
0285         setVisible(true);
0286         break;
0287     case KonsoleSettings::ShowSplitHeaderWhenNeeded: {
0288         const bool visible = !(singleTerminalView);
0289         setVisible(visible);
0290     } break;
0291     case KonsoleSettings::AlwaysHideSplitHeader:
0292         setVisible(false);
0293     default:
0294         break;
0295     }
0296 }
0297 
0298 }
0299 
0300 #include "moc_TerminalHeaderBar.cpp"