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