1 /*
2  *  SPDX-FileCopyrightText: 2019 Tomaz Canabrava <tcanabrava@kde.org>
3  *
4  *  SPDX-License-Identifier: GPL-2.0-or-later
5  */
6 
7 #include "TerminalHeaderBar.h"
8 
9 #include "KonsoleSettings.h"
10 #include "ViewProperties.h"
11 #include "session/SessionController.h"
12 #include "terminalDisplay/TerminalDisplay.h"
13 #include "widgets/ViewSplitter.h"
14 
15 #include <KLocalizedString>
16 #include <QApplication>
17 #include <QBoxLayout>
18 #include <QDrag>
19 #include <QLabel>
20 #include <QMimeData>
21 #include <QPaintEvent>
22 #include <QPainter>
23 #include <QSplitter>
24 #include <QStyleOptionTabBarBase>
25 #include <QStylePainter>
26 #include <QTabBar>
27 #include <QToolButton>
28 
29 namespace Konsole
30 {
TerminalHeaderBar(QWidget * parent)31 TerminalHeaderBar::TerminalHeaderBar(QWidget *parent)
32     : QWidget(parent)
33 {
34     m_boxLayout = new QBoxLayout(QBoxLayout::LeftToRight);
35     m_boxLayout->setSpacing(0);
36     m_boxLayout->setContentsMargins(0, 0, 0, 0);
37 
38     // Session icon
39 
40     m_terminalIcon = new QLabel(this);
41     m_terminalIcon->setAlignment(Qt::AlignCenter);
42     m_terminalIcon->setFixedSize(20, 20);
43 
44     m_boxLayout->addWidget(m_terminalIcon);
45 
46     // Status icons
47 
48     QLabel **statusIcons[] = {&m_statusIconReadOnly, &m_statusIconCopyInput, &m_statusIconSilence, &m_statusIconActivity, &m_statusIconBell};
49 
50     for (auto **statusIcon : statusIcons) {
51         *statusIcon = new QLabel(this);
52         (*statusIcon)->setAlignment(Qt::AlignCenter);
53         (*statusIcon)->setFixedSize(20, 20);
54         (*statusIcon)->setVisible(false);
55 
56         m_boxLayout->addWidget(*statusIcon);
57     }
58 
59     m_statusIconReadOnly->setPixmap(QIcon::fromTheme(QStringLiteral("object-locked")).pixmap(QSize(16, 16)));
60     m_statusIconCopyInput->setPixmap(QIcon::fromTheme(QStringLiteral("irc-voice")).pixmap(QSize(16, 16)));
61     m_statusIconSilence->setPixmap(QIcon::fromTheme(QStringLiteral("system-suspend")).pixmap(QSize(16, 16)));
62     m_statusIconActivity->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(QSize(16, 16)));
63     m_statusIconBell->setPixmap(QIcon::fromTheme(QStringLiteral("notifications")).pixmap(QSize(16, 16)));
64 
65     // Title
66 
67     m_terminalTitle = new QLabel(this);
68     m_terminalTitle->setFont(QApplication::font());
69 
70     m_boxLayout->addStretch();
71     m_boxLayout->addWidget(m_terminalTitle);
72     m_boxLayout->addStretch();
73 
74     // Expand button
75 
76     m_toggleExpandedMode = new QToolButton(this);
77     m_toggleExpandedMode->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); // fake 'expand' icon. VDG input?
78     m_toggleExpandedMode->setAutoRaise(true);
79     m_toggleExpandedMode->setCheckable(true);
80     m_toggleExpandedMode->setToolTip(i18nc("@info:tooltip", "Maximize terminal"));
81 
82     connect(m_toggleExpandedMode, &QToolButton::clicked, this, &TerminalHeaderBar::requestToggleExpansion);
83 
84     m_boxLayout->addWidget(m_toggleExpandedMode);
85 
86     // Move to new tab button
87 
88     m_moveToNewTab = new QToolButton(this);
89     m_moveToNewTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
90     m_moveToNewTab->setAutoRaise(true);
91     m_moveToNewTab->setToolTip(i18nc("@info:tooltip", "Move terminal to new tab"));
92 
93     connect(m_moveToNewTab, &QToolButton::clicked, this, &TerminalHeaderBar::requestMoveToNewTab);
94 
95     m_boxLayout->addWidget(m_moveToNewTab);
96 
97     // Close button
98 
99     m_closeBtn = new QToolButton(this);
100     m_closeBtn->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
101     m_closeBtn->setToolTip(i18nc("@info:tooltip", "Close terminal"));
102     m_closeBtn->setObjectName(QStringLiteral("close-terminal-button"));
103     m_closeBtn->setAutoRaise(true);
104 
105     m_boxLayout->addWidget(m_closeBtn);
106 
107     // The widget itself
108 
109     setLayout(m_boxLayout);
110     setAutoFillBackground(true);
111     setFocusIndicatorState(false);
112 }
113 
mouseDoubleClickEvent(QMouseEvent * ev)114 void TerminalHeaderBar::mouseDoubleClickEvent(QMouseEvent *ev)
115 {
116     if (ev->button() != Qt::LeftButton) {
117         return;
118     }
119     m_toggleExpandedMode->click();
120 }
121 
122 // Hack until I can detangle the creation of the TerminalViews
finishHeaderSetup(ViewProperties * properties)123 void TerminalHeaderBar::finishHeaderSetup(ViewProperties *properties)
124 {
125     auto controller = dynamic_cast<SessionController *>(properties);
126     connect(properties, &Konsole::ViewProperties::titleChanged, this, [this, properties] {
127         m_terminalTitle->setText(properties->title());
128     });
129     m_terminalTitle->setText(properties->title());
130 
131     connect(properties, &Konsole::ViewProperties::iconChanged, this, [this, properties] {
132         m_terminalIcon->setPixmap(properties->icon().pixmap(QSize(22, 22)));
133     });
134     m_terminalIcon->setPixmap(properties->icon().pixmap(QSize(22, 22)));
135 
136     connect(properties, &Konsole::ViewProperties::notificationChanged, this, &Konsole::TerminalHeaderBar::updateNotification);
137 
138     connect(properties, &Konsole::ViewProperties::readOnlyChanged, this, &Konsole::TerminalHeaderBar::updateSpecialState);
139 
140     connect(properties, &Konsole::ViewProperties::copyInputChanged, this, &Konsole::TerminalHeaderBar::updateSpecialState);
141 
142     connect(m_closeBtn, &QToolButton::clicked, controller, &SessionController::closeSession);
143 }
144 
setFocusIndicatorState(bool focused)145 void TerminalHeaderBar::setFocusIndicatorState(bool focused)
146 {
147     m_terminalIsFocused = focused;
148     update();
149 }
150 
updateNotification(ViewProperties * item,Session::Notification notification,bool enabled)151 void TerminalHeaderBar::updateNotification(ViewProperties *item, Session::Notification notification, bool enabled)
152 {
153     Q_UNUSED(item)
154 
155     switch (notification) {
156     case Session::Notification::Silence:
157         m_statusIconSilence->setVisible(enabled);
158         break;
159     case Session::Notification::Activity:
160         m_statusIconActivity->setVisible(enabled);
161         break;
162     case Session::Notification::Bell:
163         m_statusIconBell->setVisible(enabled);
164         break;
165     default:
166         break;
167     }
168 }
169 
updateSpecialState(ViewProperties * item)170 void TerminalHeaderBar::updateSpecialState(ViewProperties *item)
171 {
172     auto controller = dynamic_cast<SessionController *>(item);
173 
174     if (controller != nullptr) {
175         m_statusIconReadOnly->setVisible(controller->isReadOnly());
176         m_statusIconCopyInput->setVisible(controller->isCopyInputActive());
177     }
178 }
179 
setExpandedMode(bool expand)180 void TerminalHeaderBar::setExpandedMode(bool expand)
181 {
182     if (m_toggleExpandedMode->isChecked() != expand) {
183         m_toggleExpandedMode->setChecked(expand);
184     }
185     if (expand) {
186         m_toggleExpandedMode->setToolTip(i18nc("@info:tooltip", "Restore terminal"));
187     } else {
188         m_toggleExpandedMode->setToolTip(i18nc("@info:tooltip", "Maximize terminal"));
189     }
190 }
191 
paintEvent(QPaintEvent * paintEvent)192 void TerminalHeaderBar::paintEvent(QPaintEvent *paintEvent)
193 {
194     /* Try to get the widget that's 10px above this one.
195      * If the widget is something else than a TerminalWidget, a TabBar or a QSplitter,
196      * draw a 1px line to separate it from the others.
197      */
198 
199     const auto globalPos = parentWidget()->mapToGlobal(pos());
200     auto *widget = qApp->widgetAt(globalPos.x() + 10, globalPos.y() - 10);
201 
202     const bool isTabbar = qobject_cast<QTabBar *>(widget) != nullptr;
203     const bool isTerminalWidget = qobject_cast<TerminalDisplay *>(widget) != nullptr;
204     const bool isSplitter = (qobject_cast<QSplitter *>(widget) != nullptr) || (qobject_cast<QSplitterHandle *>(widget) != nullptr);
205     if ((widget != nullptr) && !isTabbar && !isTerminalWidget && !isSplitter) {
206         QStyleOptionTabBarBase optTabBase;
207         QStylePainter p(this);
208         optTabBase.init(this);
209         optTabBase.shape = QTabBar::Shape::RoundedSouth;
210         optTabBase.documentMode = false;
211         p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
212     }
213 
214     QWidget::paintEvent(paintEvent);
215     if (!m_terminalIsFocused) {
216         auto p = qApp->palette();
217         auto shadowColor = p.color(QPalette::ColorRole::Shadow);
218         shadowColor.setAlphaF(qreal(0.2) * shadowColor.alphaF()); // same as breeze.
219 
220         QPainter painter(this);
221         painter.setPen(Qt::NoPen);
222         painter.setBrush(shadowColor);
223         painter.drawRect(rect());
224     }
225 }
226 
mouseMoveEvent(QMouseEvent * ev)227 void TerminalHeaderBar::mouseMoveEvent(QMouseEvent *ev)
228 {
229     if (m_toggleExpandedMode->isChecked()) {
230         return;
231     }
232     auto point = ev->pos() - m_startDrag;
233     if (point.manhattanLength() > 10) {
234         auto drag = new QDrag(parent());
235         auto mimeData = new QMimeData();
236         QByteArray payload;
237         payload.setNum(qApp->applicationPid());
238         mimeData->setData(QStringLiteral("konsole/terminal_display"), payload);
239         drag->setMimeData(mimeData);
240         drag->exec();
241     }
242 }
243 
mousePressEvent(QMouseEvent * ev)244 void TerminalHeaderBar::mousePressEvent(QMouseEvent *ev)
245 {
246     m_startDrag = ev->pos();
247 }
248 
mouseReleaseEvent(QMouseEvent * ev)249 void TerminalHeaderBar::mouseReleaseEvent(QMouseEvent *ev)
250 {
251     Q_UNUSED(ev)
252 }
253 
minimumSizeHint() const254 QSize TerminalHeaderBar::minimumSizeHint() const
255 {
256     auto height = sizeHint().height();
257     return {height, height};
258 }
259 
getTopLevelSplitter()260 QSplitter *TerminalHeaderBar::getTopLevelSplitter()
261 {
262     QWidget *p = parentWidget();
263     // This is expected.
264     if (qobject_cast<TerminalDisplay *>(p) != nullptr) {
265         p = p->parentWidget();
266     }
267 
268     // this is also expected.
269     auto *innerSplitter = qobject_cast<ViewSplitter *>(p);
270     if (innerSplitter == nullptr) {
271         return nullptr;
272     }
273 
274     return innerSplitter->getToplevelSplitter();
275 }
276 
applyVisibilitySettings()277 void TerminalHeaderBar::applyVisibilitySettings()
278 {
279     auto *settings = KonsoleSettings::self();
280     auto toVisibility = settings->splitViewVisibility();
281     switch (toVisibility) {
282     case KonsoleSettings::AlwaysShowSplitHeader:
283         setVisible(true);
284         break;
285     case KonsoleSettings::ShowSplitHeaderWhenNeeded: {
286         const bool visible = !(getTopLevelSplitter()->findChildren<TerminalDisplay *>().count() == 1);
287         setVisible(visible);
288     } break;
289     case KonsoleSettings::AlwaysHideSplitHeader:
290         setVisible(false);
291     default:
292         break;
293     }
294 }
295 
296 }
297