1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 * Copyright (C) 2014 Piotr Wójcik <chocimier@tlen.pl>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **************************************************************************/
20 
21 #include "TabBarWidget.h"
22 #include "Action.h"
23 #include "Animation.h"
24 #include "ContentsWidget.h"
25 #include "MainWindow.h"
26 #include "PreviewWidget.h"
27 #include "Style.h"
28 #include "ToolBarWidget.h"
29 #include "Window.h"
30 #include "../core/Application.h"
31 #include "../core/GesturesManager.h"
32 #include "../core/InputInterpreter.h"
33 #include "../core/SettingsManager.h"
34 #include "../core/ThemesManager.h"
35 
36 #include <QtCore/QMimeData>
37 #include <QtCore/QtMath>
38 #include <QtGui/QContextMenuEvent>
39 #include <QtGui/QDrag>
40 #include <QtGui/QStatusTipEvent>
41 #include <QtWidgets/QApplication>
42 #include <QtWidgets/QCheckBox>
43 #include <QtWidgets/QDesktopWidget>
44 #include <QtWidgets/QLabel>
45 #include <QtWidgets/QMenu>
46 #include <QtWidgets/QMessageBox>
47 #include <QtWidgets/QStyleOption>
48 #include <QtWidgets/QStylePainter>
49 #include <QtWidgets/QToolTip>
50 
51 namespace Otter
52 {
53 
54 QIcon TabHandleWidget::m_lockedIcon;
55 Animation* TabHandleWidget::m_spinnerAnimation(nullptr);
56 bool TabBarWidget::m_areThumbnailsEnabled(true);
57 bool TabBarWidget::m_isLayoutReversed(false);
58 bool TabBarWidget::m_isCloseButtonEnabled(true);
59 bool TabBarWidget::m_isUrlIconEnabled(true);
60 
TabHandleWidget(Window * window,TabBarWidget * parent)61 TabHandleWidget::TabHandleWidget(Window *window, TabBarWidget *parent) : QWidget(parent),
62 	m_window(window),
63 	m_tabBarWidget(parent),
64 	m_dragTimer(0),
65 	m_isActiveWindow(false),
66 	m_isCloseButtonUnderMouse(false),
67 	m_wasCloseButtonPressed(false)
68 {
69 	handleLoadingStateChanged(window->getLoadingState());
70 	setAcceptDrops(true);
71 	setMouseTracking(true);
72 
73 	connect(window, &Window::needsAttention, this, &TabHandleWidget::markAsNeedingAttention);
74 	connect(window, &Window::titleChanged, this, &TabHandleWidget::updateTitle);
75 	connect(window, &Window::iconChanged, this, static_cast<void(TabHandleWidget::*)()>(&TabHandleWidget::update));
76 	connect(window, &Window::loadingStateChanged, this, &TabHandleWidget::handleLoadingStateChanged);
77 	connect(parent, &TabBarWidget::currentChanged, this, &TabHandleWidget::updateGeometries);
78 	connect(parent, &TabBarWidget::tabsAmountChanged, this, &TabHandleWidget::updateGeometries);
79 	connect(parent, &TabBarWidget::needsGeometriesUpdate, this, &TabHandleWidget::updateGeometries);
80 }
81 
timerEvent(QTimerEvent * event)82 void TabHandleWidget::timerEvent(QTimerEvent *event)
83 {
84 	if (event->timerId() == m_dragTimer)
85 	{
86 		killTimer(m_dragTimer);
87 
88 		m_dragTimer = 0;
89 
90 		if (rect().contains(mapFromGlobal(QCursor::pos())))
91 		{
92 			MainWindow *mainWindow(MainWindow::findMainWindow(this));
93 
94 			if (mainWindow)
95 			{
96 				mainWindow->setActiveWindowByIdentifier(m_window->getIdentifier());
97 			}
98 		}
99 	}
100 }
101 
paintEvent(QPaintEvent * event)102 void TabHandleWidget::paintEvent(QPaintEvent *event)
103 {
104 	Q_UNUSED(event)
105 
106 	if (!m_window)
107 	{
108 		return;
109 	}
110 
111 	QPainter painter(this);
112 
113 	if (m_closeButtonRectangle.isValid())
114 	{
115 		if (m_window->isPinned())
116 		{
117 			if (m_lockedIcon.isNull())
118 			{
119 				m_lockedIcon = ThemesManager::createIcon(QLatin1String("object-locked"));
120 			}
121 
122 			m_lockedIcon.paint(&painter, m_closeButtonRectangle);
123 		}
124 		else
125 		{
126 			QStyleOption option;
127 			option.init(this);
128 			option.rect = m_closeButtonRectangle;
129 			option.state = (QStyle::State_Enabled | QStyle::State_AutoRaise);
130 
131 			if (m_isCloseButtonUnderMouse)
132 			{
133 				option.state |= (QGuiApplication::mouseButtons().testFlag(Qt::LeftButton) ? QStyle::State_Sunken : QStyle::State_Raised);
134 			}
135 
136 			if (m_tabBarWidget->getWindow(m_tabBarWidget->currentIndex()) == m_window)
137 			{
138 				option.state |= QStyle::State_Selected;
139 			}
140 
141 			style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &option, &painter, this);
142 		}
143 	}
144 
145 	if (m_urlIconRectangle.isValid())
146 	{
147 		if (m_window->getLoadingState() == WebWidget::OngoingLoadingState && m_spinnerAnimation)
148 		{
149 			m_spinnerAnimation->paint(&painter, m_urlIconRectangle);
150 		}
151 		else
152 		{
153 			m_window->getIcon().paint(&painter, m_urlIconRectangle);
154 		}
155 	}
156 
157 	if (m_thumbnailRectangle.isValid())
158 	{
159 		const QPixmap thumbnail(m_window->createThumbnail());
160 
161 		if (thumbnail.isNull())
162 		{
163 			painter.fillRect(m_thumbnailRectangle, Qt::white);
164 
165 			if (m_thumbnailRectangle.height() >= 16 && m_thumbnailRectangle.width() >= 16)
166 			{
167 				if (m_window->getLoadingState() == WebWidget::OngoingLoadingState && m_spinnerAnimation)
168 				{
169 					m_spinnerAnimation->paint(&painter, QRect((m_thumbnailRectangle.left() + ((m_thumbnailRectangle.width() - 16) / 2)), (m_thumbnailRectangle.top() + ((m_thumbnailRectangle.height() - 16) / 2)), 16, 16));
170 				}
171 				else
172 				{
173 					m_window->getIcon().paint(&painter, m_thumbnailRectangle);
174 				}
175 			}
176 		}
177 		else
178 		{
179 			QRect sourceRectangle(m_thumbnailRectangle);
180 			sourceRectangle.moveTo(0, 0);
181 
182 			painter.drawPixmap(m_thumbnailRectangle, thumbnail, sourceRectangle);
183 		}
184 	}
185 
186 	if (!m_title.isEmpty())
187 	{
188 		QStyleOptionTab option;
189 		option.initFrom(this);
190 		option.documentMode = true;
191 		option.rect = m_titleRectangle;
192 		option.text = m_title;
193 
194 		if (m_isActiveWindow)
195 		{
196 			option.state |= QStyle::State_Selected;
197 		}
198 
199 		painter.save();
200 
201 		if (m_window->getLoadingState() == WebWidget::DeferredLoadingState)
202 		{
203 			painter.setOpacity(0.75);
204 		}
205 
206 		style()->drawControl(QStyle::CE_TabBarTabLabel, &option, &painter);
207 
208 		painter.restore();
209 	}
210 }
211 
moveEvent(QMoveEvent * event)212 void TabHandleWidget::moveEvent(QMoveEvent *event)
213 {
214 	QWidget::moveEvent(event);
215 
216 	if (underMouse())
217 	{
218 		m_isCloseButtonUnderMouse = m_closeButtonRectangle.contains(mapFromGlobal(QCursor::pos()));
219 	}
220 }
221 
resizeEvent(QResizeEvent * event)222 void TabHandleWidget::resizeEvent(QResizeEvent *event)
223 {
224 	QWidget::resizeEvent(event);
225 
226 	updateGeometries();
227 }
228 
leaveEvent(QEvent * event)229 void TabHandleWidget::leaveEvent(QEvent *event)
230 {
231 	QWidget::leaveEvent(event);
232 
233 	m_isCloseButtonUnderMouse = false;
234 
235 	update();
236 }
237 
mousePressEvent(QMouseEvent * event)238 void TabHandleWidget::mousePressEvent(QMouseEvent *event)
239 {
240 	m_wasCloseButtonPressed = m_closeButtonRectangle.contains(event->pos());
241 
242 	QWidget::mousePressEvent(event);
243 
244 	update();
245 }
246 
mouseMoveEvent(QMouseEvent * event)247 void TabHandleWidget::mouseMoveEvent(QMouseEvent *event)
248 {
249 	const bool wasCloseButtonUnderMouse(m_isCloseButtonUnderMouse);
250 
251 	m_isCloseButtonUnderMouse = m_closeButtonRectangle.contains(event->pos());
252 
253 	if (m_window && !m_window->isPinned())
254 	{
255 		if (wasCloseButtonUnderMouse && !m_isCloseButtonUnderMouse)
256 		{
257 			m_tabBarWidget->showPreview(-1, SettingsManager::getOption(SettingsManager::TabBar_PreviewsAnimationDurationOption).toInt());
258 
259 			QToolTip::hideText();
260 
261 			setToolTip(QString());
262 		}
263 		else if (!wasCloseButtonUnderMouse && m_isCloseButtonUnderMouse)
264 		{
265 			m_tabBarWidget->hidePreview();
266 
267 			const QKeySequence shortcut(ActionsManager::getActionShortcut(ActionsManager::CloseTabAction));
268 
269 			setToolTip(tr("Close Tab") + (shortcut.isEmpty() ? QString() : QLatin1String(" (") + shortcut.toString(QKeySequence::NativeText) + QLatin1Char(')')));
270 		}
271 	}
272 
273 	QWidget::mouseMoveEvent(event);
274 
275 	update();
276 }
277 
mouseReleaseEvent(QMouseEvent * event)278 void TabHandleWidget::mouseReleaseEvent(QMouseEvent *event)
279 {
280 	if (m_window && !m_window->isPinned() && event->button() == Qt::LeftButton && m_wasCloseButtonPressed && m_closeButtonRectangle.contains(event->pos()))
281 	{
282 		m_window->requestClose();
283 
284 		event->accept();
285 	}
286 
287 	QWidget::mouseReleaseEvent(event);
288 }
289 
dragEnterEvent(QDragEnterEvent * event)290 void TabHandleWidget::dragEnterEvent(QDragEnterEvent *event)
291 {
292 	if (m_dragTimer == 0 && event->mimeData()->property("x-window-identifier").isNull() && m_tabBarWidget->getWindow(m_tabBarWidget->currentIndex()) != m_window)
293 	{
294 		m_dragTimer = startTimer(500);
295 	}
296 }
297 
markAsNeedingAttention()298 void TabHandleWidget::markAsNeedingAttention()
299 {
300 	if (!m_isActiveWindow)
301 	{
302 		QFont font(parentWidget()->font());
303 		font.setBold(true);
304 
305 		setFont(font);
306 		updateTitle();
307 	}
308 }
309 
handleLoadingStateChanged(WebWidget::LoadingState state)310 void TabHandleWidget::handleLoadingStateChanged(WebWidget::LoadingState state)
311 {
312 	if (state == WebWidget::OngoingLoadingState)
313 	{
314 		if (!m_spinnerAnimation)
315 		{
316 			const QString path(ThemesManager::getAnimationPath(QLatin1String("spinner")));
317 
318 			if (path.isEmpty())
319 			{
320 				m_spinnerAnimation = new SpinnerAnimation(QCoreApplication::instance());
321 			}
322 			else
323 			{
324 				m_spinnerAnimation = new GenericAnimation(path, QCoreApplication::instance());
325 			}
326 
327 			m_spinnerAnimation->start();
328 		}
329 
330 		connect(m_spinnerAnimation, &Animation::frameChanged, this, static_cast<void(TabHandleWidget::*)()>(&TabHandleWidget::update));
331 	}
332 	else if (m_spinnerAnimation)
333 	{
334 		for (int i = 0; i < m_tabBarWidget->count(); ++i)
335 		{
336 			const Window *window(m_tabBarWidget->getWindow(i));
337 
338 			if (window && window->getLoadingState() == WebWidget::OngoingLoadingState)
339 			{
340 				return;
341 			}
342 		}
343 
344 		m_spinnerAnimation->deleteLater();
345 		m_spinnerAnimation = nullptr;
346 
347 		update();
348 	}
349 }
350 
updateGeometries()351 void TabHandleWidget::updateGeometries()
352 {
353 	if (!m_window)
354 	{
355 		return;
356 	}
357 
358 	QStyleOption option;
359 	option.initFrom(this);
360 
361 	QRect controlsRectangle(style()->subElementRect(QStyle::SE_TabBarTabLeftButton, &option, m_tabBarWidget));
362 
363 	m_closeButtonRectangle = {};
364 	m_urlIconRectangle = {};
365 	m_thumbnailRectangle = {};
366 	m_labelRectangle = {};
367 	m_titleRectangle = {};
368 
369 	if (TabBarWidget::areThumbnailsEnabled())
370 	{
371 		const int controlsHeight(qRound(qMax(16.0, QFontMetrics(font()).height() * 1.5)));
372 
373 		if (controlsRectangle.height() > (controlsHeight * 2))
374 		{
375 			m_thumbnailRectangle = controlsRectangle;
376 			m_thumbnailRectangle.setHeight(controlsRectangle.height() - controlsHeight);
377 			m_thumbnailRectangle.setTop(style()->pixelMetric(QStyle::PM_TabBarTabVSpace) / 2);
378 
379 			controlsRectangle.setTop(m_thumbnailRectangle.bottom());
380 		}
381 	}
382 
383 	const int controlsWidth(controlsRectangle.width());
384 	const bool isActive(m_tabBarWidget->getWindow(m_tabBarWidget->currentIndex()) == m_window);
385 	const bool isCloseButtonEnabled(TabBarWidget::isCloseButtonEnabled());
386 	const bool isUrlIconEnabled(TabBarWidget::isUrlIconEnabled());
387 
388 	if (controlsWidth <= 18 && (isCloseButtonEnabled || isUrlIconEnabled))
389 	{
390 		if (isUrlIconEnabled)
391 		{
392 			if (isActive && isCloseButtonEnabled && !m_window->isPinned())
393 			{
394 				const int buttonWidth((controlsRectangle.width() / 2) - 2);
395 
396 				m_closeButtonRectangle = controlsRectangle;
397 				m_urlIconRectangle = controlsRectangle;
398 
399 				if (TabBarWidget::isLayoutReversed())
400 				{
401 					m_closeButtonRectangle.setWidth(buttonWidth);
402 
403 					m_urlIconRectangle.setLeft(m_urlIconRectangle.right() - buttonWidth);
404 				}
405 				else
406 				{
407 					m_urlIconRectangle.setWidth(buttonWidth);
408 
409 					m_closeButtonRectangle.setLeft(m_closeButtonRectangle.right() - buttonWidth);
410 				}
411 			}
412 			else
413 			{
414 				m_urlIconRectangle = controlsRectangle;
415 			}
416 		}
417 		else
418 		{
419 			m_closeButtonRectangle = controlsRectangle;
420 		}
421 	}
422 	else if (controlsWidth <= 34 && isActive && (isCloseButtonEnabled && !m_window->isPinned()) && isUrlIconEnabled)
423 	{
424 		if (isUrlIconEnabled)
425 		{
426 			const int buttonWidth((controlsRectangle.width() / 2) - 2);
427 
428 			m_closeButtonRectangle = controlsRectangle;
429 			m_urlIconRectangle = controlsRectangle;
430 
431 			if (TabBarWidget::isLayoutReversed())
432 			{
433 				m_closeButtonRectangle.setWidth(buttonWidth);
434 
435 				m_urlIconRectangle.setLeft(m_urlIconRectangle.right() - buttonWidth);
436 			}
437 			else
438 			{
439 				m_urlIconRectangle.setWidth(buttonWidth);
440 
441 				m_closeButtonRectangle.setLeft(m_closeButtonRectangle.right() - buttonWidth);
442 			}
443 		}
444 	}
445 	else
446 	{
447 		m_labelRectangle = controlsRectangle;
448 
449 		if (isUrlIconEnabled)
450 		{
451 			m_urlIconRectangle = controlsRectangle;
452 
453 			if (TabBarWidget::isLayoutReversed())
454 			{
455 				m_urlIconRectangle.setLeft(controlsRectangle.right() - 16);
456 
457 				m_labelRectangle.setRight(controlsRectangle.right() - 20);
458 			}
459 			else
460 			{
461 				m_urlIconRectangle.setWidth(16);
462 
463 				m_labelRectangle.setLeft(m_urlIconRectangle.right() + 4);
464 			}
465 		}
466 
467 		if (isCloseButtonEnabled && (isActive || controlsWidth >= 70))
468 		{
469 			m_closeButtonRectangle = m_labelRectangle;
470 
471 			if (TabBarWidget::isLayoutReversed())
472 			{
473 				m_closeButtonRectangle.setWidth(16);
474 			}
475 			else
476 			{
477 				m_closeButtonRectangle.setLeft(m_labelRectangle.right() - 16);
478 			}
479 
480 			if (controlsWidth <= 40)
481 			{
482 				m_labelRectangle = {};
483 			}
484 			else
485 			{
486 				if (TabBarWidget::isLayoutReversed())
487 				{
488 					m_labelRectangle.setLeft(m_labelRectangle.left() + 20);
489 				}
490 				else
491 				{
492 					m_labelRectangle.setRight(m_closeButtonRectangle.left() - 4);
493 				}
494 			}
495 		}
496 	}
497 
498 	if (m_closeButtonRectangle.isValid() && m_closeButtonRectangle.height() > m_closeButtonRectangle.width())
499 	{
500 		m_closeButtonRectangle.setTop(controlsRectangle.top() + ((m_closeButtonRectangle.height() - m_closeButtonRectangle.width()) / 2));
501 		m_closeButtonRectangle.setHeight(m_closeButtonRectangle.width());
502 	}
503 
504 	if (m_urlIconRectangle.isValid() && m_urlIconRectangle.height() > m_urlIconRectangle.width())
505 	{
506 		m_urlIconRectangle.setTop(controlsRectangle.top() + ((m_urlIconRectangle.height() - m_urlIconRectangle.width()) / 2));
507 		m_urlIconRectangle.setHeight(m_urlIconRectangle.width());
508 	}
509 
510 	m_isCloseButtonUnderMouse = (underMouse() && m_closeButtonRectangle.contains(mapFromGlobal(QCursor::pos())));
511 
512 	updateTitle();
513 }
514 
updateTitle()515 void TabHandleWidget::updateTitle()
516 {
517 	QString title(m_window->getTitle());
518 
519 	if (!m_labelRectangle.isValid() || m_labelRectangle.width() < 5)
520 	{
521 		title.clear();
522 	}
523 	else
524 	{
525 		int length(fontMetrics().width(title));
526 
527 		if (length > m_labelRectangle.width())
528 		{
529 			title = fontMetrics().elidedText(title, Qt::ElideRight, m_labelRectangle.width());
530 			length = fontMetrics().width(title);
531 		}
532 
533 		m_titleRectangle = m_labelRectangle;
534 
535 		if (length < m_labelRectangle.width() && Application::getStyle()->getExtraStyleHint(Style::CanAlignTabBarLabelHint) > 0)
536 		{
537 			if (isLeftToRight())
538 			{
539 				m_titleRectangle.setWidth(length);
540 			}
541 			else
542 			{
543 				m_titleRectangle.setLeft(m_titleRectangle.right() - length);
544 			}
545 		}
546 
547 		title.replace(QLatin1Char('&'), QLatin1String("&&"));
548 	}
549 
550 	if (title != m_title)
551 	{
552 		m_title = title;
553 
554 		update();
555 	}
556 }
557 
setIsActiveWindow(bool isActive)558 void TabHandleWidget::setIsActiveWindow(bool isActive)
559 {
560 	if (isActive != m_isActiveWindow)
561 	{
562 		m_isActiveWindow = isActive;
563 
564 		if (isActive)
565 		{
566 			setFont(parentWidget()->font());
567 			updateTitle();
568 		}
569 		else
570 		{
571 			update();
572 		}
573 	}
574 }
575 
getWindow() const576 Window* TabHandleWidget::getWindow() const
577 {
578 	return m_window;
579 }
580 
TabBarWidget(QWidget * parent)581 TabBarWidget::TabBarWidget(QWidget *parent) : QTabBar(parent),
582 	m_previewWidget(nullptr),
583 	m_activeTabHandleWidget(nullptr),
584 	m_movableTabWidget(nullptr),
585 	m_tabWidth(0),
586 	m_clickedTab(-1),
587 	m_hoveredTab(-1),
588 	m_pinnedTabsAmount(0),
589 	m_previewTimer(0),
590 	m_arePreviewsEnabled(SettingsManager::getOption(SettingsManager::TabBar_EnablePreviewsOption).toBool()),
591 	m_isDraggingTab(false),
592 	m_isDetachingTab(false),
593 	m_isIgnoringTabDrag(false),
594 	m_needsUpdateOnLeave(false)
595 {
596 	m_areThumbnailsEnabled = SettingsManager::getOption(SettingsManager::TabBar_EnableThumbnailsOption).toBool();
597 	m_isCloseButtonEnabled = SettingsManager::getOption(SettingsManager::TabBar_ShowCloseButtonOption).toBool();
598 	m_isUrlIconEnabled = SettingsManager::getOption(SettingsManager::TabBar_ShowUrlIconOption).toBool();
599 
600 	setAcceptDrops(true);
601 	setExpanding(false);
602 	setMovable(true);
603 	setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab);
604 	setElideMode(Qt::ElideRight);
605 	setMouseTracking(true);
606 	setDocumentMode(true);
607 	setMaximumSize(0, 0);
608 	setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
609 	updateStyle();
610 	handleOptionChanged(SettingsManager::TabBar_MaximumTabHeightOption, SettingsManager::getOption(SettingsManager::TabBar_MaximumTabHeightOption));
611 	handleOptionChanged(SettingsManager::TabBar_MinimumTabHeightOption, SettingsManager::getOption(SettingsManager::TabBar_MinimumTabHeightOption));
612 	handleOptionChanged(SettingsManager::TabBar_MaximumTabWidthOption, SettingsManager::getOption(SettingsManager::TabBar_MaximumTabWidthOption));
613 	handleOptionChanged(SettingsManager::TabBar_MinimumTabWidthOption, SettingsManager::getOption(SettingsManager::TabBar_MinimumTabWidthOption));
614 
615 	const ToolBarWidget *toolBar(qobject_cast<ToolBarWidget*>(parent));
616 
617 	if (toolBar)
618 	{
619 		setArea(toolBar->getArea());
620 
621 		connect(toolBar, &ToolBarWidget::areaChanged, this, &TabBarWidget::setArea);
622 	}
623 
624 	connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &TabBarWidget::handleOptionChanged);
625 	connect(ThemesManager::getInstance(), &ThemesManager::widgetStyleChanged, this, &TabBarWidget::updateStyle);
626 	connect(this, &TabBarWidget::currentChanged, this, &TabBarWidget::handleCurrentChanged);
627 }
628 
changeEvent(QEvent * event)629 void TabBarWidget::changeEvent(QEvent *event)
630 {
631 	QTabBar::changeEvent(event);
632 
633 	switch (event->type())
634 	{
635 		case QEvent::ApplicationLayoutDirectionChange:
636 		case QEvent::LayoutDirectionChange:
637 			updateStyle();
638 
639 			break;
640 		case QEvent::FontChange:
641 			handleOptionChanged(SettingsManager::TabBar_MinimumTabHeightOption, SettingsManager::getOption(SettingsManager::TabBar_MinimumTabHeightOption));
642 
643 			break;
644 		default:
645 			break;
646 	}
647 }
648 
childEvent(QChildEvent * event)649 void TabBarWidget::childEvent(QChildEvent *event)
650 {
651 	QTabBar::childEvent(event);
652 
653 	if (m_isDraggingTab && !m_isIgnoringTabDrag && !m_movableTabWidget && event->added())
654 	{
655 		QWidget *widget(qobject_cast<QWidget*>(event->child()));
656 
657 		if (widget)
658 		{
659 			m_movableTabWidget = widget;
660 		}
661 	}
662 }
663 
timerEvent(QTimerEvent * event)664 void TabBarWidget::timerEvent(QTimerEvent *event)
665 {
666 	if (event->timerId() == m_previewTimer)
667 	{
668 		killTimer(m_previewTimer);
669 
670 		m_previewTimer = 0;
671 
672 		showPreview(tabAt(mapFromGlobal(QCursor::pos())));
673 	}
674 }
675 
paintEvent(QPaintEvent * event)676 void TabBarWidget::paintEvent(QPaintEvent *event)
677 {
678 	Q_UNUSED(event)
679 
680 	QStylePainter painter(this);
681 	const int selectedIndex(currentIndex());
682 
683 	for (int i = 0; i < count(); ++i)
684 	{
685 		if (i == selectedIndex)
686 		{
687 			continue;
688 		}
689 
690 		const QStyleOptionTab tabOption(createStyleOptionTab(i));
691 
692 		if (rect().intersects(tabOption.rect))
693 		{
694 			painter.drawControl(QStyle::CE_TabBarTab, tabOption);
695 		}
696 	}
697 
698 	if (selectedIndex >= 0)
699 	{
700 		const QStyleOptionTab tabOption(createStyleOptionTab(selectedIndex));
701 
702 		if (m_isDraggingTab && !m_isIgnoringTabDrag && m_movableTabWidget)
703 		{
704 			const int tabOverlap(style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this));
705 
706 			m_movableTabWidget->setGeometry(tabOption.rect.adjusted(-tabOverlap, 0, tabOverlap, 0));
707 		}
708 		else
709 		{
710 			painter.drawControl(QStyle::CE_TabBarTab, tabOption);
711 		}
712 	}
713 
714 	if (!m_dragMovePosition.isNull())
715 	{
716 		const int dropIndex(getDropIndex());
717 
718 		if (dropIndex >= 0)
719 		{
720 			int lineOffset(0);
721 
722 			if (count() == 0)
723 			{
724 				lineOffset = 0;
725 			}
726 			else if (dropIndex >= count())
727 			{
728 				lineOffset = tabRect(count() - 1).right();
729 			}
730 			else
731 			{
732 				lineOffset = tabRect(dropIndex).left();
733 			}
734 
735 			Application::getStyle()->drawDropZone(((shape() == QTabBar::RoundedNorth || shape() == QTabBar::RoundedSouth) ? QLine(lineOffset, 0, lineOffset, height()) : QLine(0, lineOffset, width(), lineOffset)), &painter);
736 		}
737 	}
738 }
739 
enterEvent(QEvent * event)740 void TabBarWidget::enterEvent(QEvent *event)
741 {
742 	QTabBar::enterEvent(event);
743 
744 	showPreview(-1, SettingsManager::getOption(SettingsManager::TabBar_PreviewsAnimationDurationOption).toInt());
745 }
746 
leaveEvent(QEvent * event)747 void TabBarWidget::leaveEvent(QEvent *event)
748 {
749 	QTabBar::leaveEvent(event);
750 
751 	hidePreview();
752 
753 	m_tabWidth = 0;
754 	m_hoveredTab = -1;
755 
756 	if (m_needsUpdateOnLeave)
757 	{
758 		updateSize();
759 
760 		m_needsUpdateOnLeave = false;
761 	}
762 
763 	QStatusTipEvent statusTipEvent((QString()));
764 
765 	QApplication::sendEvent(this, &statusTipEvent);
766 }
767 
contextMenuEvent(QContextMenuEvent * event)768 void TabBarWidget::contextMenuEvent(QContextMenuEvent *event)
769 {
770 	if (event->reason() == QContextMenuEvent::Mouse)
771 	{
772 		event->accept();
773 
774 		return;
775 	}
776 
777 	m_clickedTab = tabAt(event->pos());
778 
779 	hidePreview();
780 
781 	MainWindow *mainWindow(MainWindow::findMainWindow(this));
782 	ActionExecutor::Object mainWindowExecutor(mainWindow, mainWindow);
783 	ActionExecutor::Object windowExecutor;
784 	QMenu menu(this);
785 	menu.addAction(new Action(ActionsManager::NewTabAction, {}, mainWindowExecutor, &menu));
786 	menu.addAction(new Action(ActionsManager::NewTabPrivateAction, {}, mainWindowExecutor, &menu));
787 
788 	if (m_clickedTab >= 0)
789 	{
790 		Window *window(getWindow(m_clickedTab));
791 
792 		if (window)
793 		{
794 			windowExecutor = ActionExecutor::Object(window, window);
795 
796 			menu.addAction(new Action(ActionsManager::CloneTabAction, {}, windowExecutor, &menu));
797 			menu.addAction(new Action(ActionsManager::PinTabAction, {}, windowExecutor, &menu));
798 			menu.addAction(new Action(ActionsManager::MuteTabMediaAction, {}, windowExecutor, &menu));
799 			menu.addSeparator();
800 			menu.addAction(new Action(ActionsManager::DetachTabAction, {}, windowExecutor, &menu));
801 			menu.addSeparator();
802 			menu.addAction(new Action(ActionsManager::CloseTabAction, {}, windowExecutor, &menu));
803 			menu.addAction(new Action(ActionsManager::CloseOtherTabsAction, {{QLatin1String("tab"), window->getIdentifier()}}, mainWindowExecutor, &menu));
804 			menu.addAction(new Action(ActionsManager::ClosePrivateTabsAction, {}, mainWindowExecutor, &menu));
805 		}
806 	}
807 
808 	menu.addSeparator();
809 
810 	QMenu *arrangeMenu(menu.addMenu(tr("Arrange")));
811 	arrangeMenu->addAction(new Action(ActionsManager::RestoreTabAction, {}, windowExecutor, arrangeMenu));
812 	arrangeMenu->addAction(new Action(ActionsManager::MinimizeTabAction, {}, windowExecutor, arrangeMenu));
813 	arrangeMenu->addAction(new Action(ActionsManager::MaximizeTabAction, {}, windowExecutor, arrangeMenu));
814 	arrangeMenu->addSeparator();
815 	arrangeMenu->addAction(new Action(ActionsManager::RestoreAllAction, {}, mainWindowExecutor, arrangeMenu));
816 	arrangeMenu->addAction(new Action(ActionsManager::MaximizeAllAction, {}, mainWindowExecutor, arrangeMenu));
817 	arrangeMenu->addAction(new Action(ActionsManager::MinimizeAllAction, {}, mainWindowExecutor, arrangeMenu));
818 	arrangeMenu->addSeparator();
819 	arrangeMenu->addAction(new Action(ActionsManager::CascadeAllAction, {}, mainWindowExecutor, arrangeMenu));
820 	arrangeMenu->addAction(new Action(ActionsManager::TileAllAction, {}, mainWindowExecutor, arrangeMenu));
821 
822 	QAction *cycleAction(new QAction(tr("Switch Tabs Using the Mouse Wheel"), this));
823 	cycleAction->setCheckable(true);
824 	cycleAction->setChecked(!SettingsManager::getOption(SettingsManager::TabBar_RequireModifierToSwitchTabOnScrollOption).toBool());
825 
826 	QAction *thumbnailsAction(new QAction(tr("Show Thumbnails in Tabs"), this));
827 	thumbnailsAction->setCheckable(true);
828 	thumbnailsAction->setChecked(SettingsManager::getOption(SettingsManager::TabBar_EnableThumbnailsOption).toBool());
829 
830 	connect(cycleAction, &QAction::toggled, [&](bool isEnabled)
831 	{
832 		SettingsManager::setOption(SettingsManager::TabBar_RequireModifierToSwitchTabOnScrollOption, !isEnabled);
833 	});
834 	connect(thumbnailsAction, &QAction::toggled, [&](bool areEnabled)
835 	{
836 		SettingsManager::setOption(SettingsManager::TabBar_EnableThumbnailsOption, areEnabled);
837 	});
838 
839 	if (qobject_cast<ToolBarWidget*>(parentWidget()))
840 	{
841 		menu.addMenu(ToolBarWidget::createCustomizationMenu(ToolBarsManager::TabBar, {cycleAction, thumbnailsAction}, &menu));
842 	}
843 	else
844 	{
845 		QMenu *customizationMenu(menu.addMenu(tr("Customize")));
846 		customizationMenu->addAction(cycleAction);
847 		customizationMenu->addAction(thumbnailsAction);
848 		customizationMenu->addSeparator();
849 		customizationMenu->addAction(new Action(ActionsManager::LockToolBarsAction, {}, mainWindowExecutor, &menu));
850 	}
851 
852 	menu.exec(event->globalPos());
853 
854 	m_clickedTab = -1;
855 
856 	if (underMouse())
857 	{
858 		m_previewTimer = startTimer(SettingsManager::getOption(SettingsManager::TabBar_PreviewsAnimationDurationOption).toInt());
859 	}
860 }
861 
mousePressEvent(QMouseEvent * event)862 void TabBarWidget::mousePressEvent(QMouseEvent *event)
863 {
864 	QTabBar::mousePressEvent(event);
865 
866 	if (event->button() == Qt::LeftButton)
867 	{
868 		const Window *window(getWindow(tabAt(event->pos())));
869 
870 		m_isIgnoringTabDrag = (count() == 1);
871 
872 		if (window)
873 		{
874 			m_dragStartPosition = event->pos();
875 			m_draggedWindow = window->getIdentifier();
876 		}
877 	}
878 
879 	hidePreview();
880 }
881 
mouseMoveEvent(QMouseEvent * event)882 void TabBarWidget::mouseMoveEvent(QMouseEvent *event)
883 {
884 	tabHovered(tabAt(event->pos()));
885 
886 	if (!m_isDraggingTab && !m_dragStartPosition.isNull())
887 	{
888 		m_isDraggingTab = ((event->pos() - m_dragStartPosition).manhattanLength() > QApplication::startDragDistance());
889 	}
890 
891 	if (m_isDraggingTab && !rect().adjusted(-10, -10, 10, 10).contains(event->pos()))
892 	{
893 		m_isDraggingTab = false;
894 
895 		QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers());
896 
897 		QApplication::sendEvent(this, &mouseEvent);
898 
899 		m_isDetachingTab = true;
900 
901 		updateSize();
902 
903 		const MainWindow *mainWindow(MainWindow::findMainWindow(this));
904 
905 		if (mainWindow)
906 		{
907 			const Window *window(mainWindow->getWindowByIdentifier(m_draggedWindow));
908 
909 			if (window)
910 			{
911 				QMimeData *mimeData(new QMimeData());
912 				mimeData->setText(window->getUrl().toString());
913 				mimeData->setUrls({window->getUrl()});
914 				mimeData->setProperty("x-url-title", window->getTitle());
915 				mimeData->setProperty("x-window-identifier", window->getIdentifier());
916 
917 				const QPixmap thumbnail(window->createThumbnail());
918 				QDrag *drag(new QDrag(this));
919 				drag->setMimeData(mimeData);
920 				drag->setPixmap(thumbnail.isNull() ? window->getIcon().pixmap(16, 16) : thumbnail);
921 				drag->exec(Qt::CopyAction | Qt::MoveAction);
922 
923 				m_isDetachingTab = false;
924 
925 				if (!drag->target())
926 				{
927 					Application::triggerAction(ActionsManager::DetachTabAction, {{QLatin1String("tab"), window->getIdentifier()}}, parentWidget());
928 				}
929 			}
930 		}
931 
932 		return;
933 	}
934 
935 	if (!m_isIgnoringTabDrag && !m_isDetachingTab)
936 	{
937 		QTabBar::mouseMoveEvent(event);
938 	}
939 }
940 
mouseReleaseEvent(QMouseEvent * event)941 void TabBarWidget::mouseReleaseEvent(QMouseEvent *event)
942 {
943 	QTabBar::mouseReleaseEvent(event);
944 
945 	if (event->button() == Qt::LeftButton)
946 	{
947 		if (m_isDetachingTab)
948 		{
949 			Application::triggerAction(ActionsManager::DetachTabAction, {{QLatin1String("tab"), m_draggedWindow}}, parentWidget());
950 
951 			m_isDetachingTab = false;
952 		}
953 
954 		m_dragStartPosition = {};
955 		m_isDraggingTab = false;
956 	}
957 }
958 
wheelEvent(QWheelEvent * event)959 void TabBarWidget::wheelEvent(QWheelEvent *event)
960 {
961 	QWidget::wheelEvent(event);
962 
963 	if (event->modifiers().testFlag(Qt::ControlModifier) || !SettingsManager::getOption(SettingsManager::TabBar_RequireModifierToSwitchTabOnScrollOption).toBool())
964 	{
965 		Application::triggerAction(((event->delta() > 0) ? ActionsManager::ActivateTabOnLeftAction : ActionsManager::ActivateTabOnRightAction), {}, parentWidget());
966 	}
967 }
968 
dragEnterEvent(QDragEnterEvent * event)969 void TabBarWidget::dragEnterEvent(QDragEnterEvent *event)
970 {
971 	if (event->mimeData()->hasText() || event->mimeData()->hasUrls() || (event->source() && !event->mimeData()->property("x-window-identifier").isNull()))
972 	{
973 		event->accept();
974 
975 		m_dragMovePosition = event->pos();
976 
977 		update();
978 	}
979 }
980 
dragMoveEvent(QDragMoveEvent * event)981 void TabBarWidget::dragMoveEvent(QDragMoveEvent *event)
982 {
983 	m_dragMovePosition = event->pos();
984 
985 	update();
986 }
987 
dragLeaveEvent(QDragLeaveEvent * event)988 void TabBarWidget::dragLeaveEvent(QDragLeaveEvent *event)
989 {
990 	Q_UNUSED(event)
991 
992 	m_dragMovePosition = {};
993 
994 	update();
995 }
996 
dropEvent(QDropEvent * event)997 void TabBarWidget::dropEvent(QDropEvent *event)
998 {
999 	const int dropIndex(getDropIndex());
1000 
1001 	if (event->source() && !event->mimeData()->property("x-window-identifier").isNull())
1002 	{
1003 		event->setDropAction(Qt::MoveAction);
1004 		event->accept();
1005 
1006 		int previousIndex(-1);
1007 		const quint64 windowIdentifier(event->mimeData()->property("x-window-identifier").toULongLong());
1008 
1009 		if (event->source() == this)
1010 		{
1011 			for (int i = 0; i < count(); ++i)
1012 			{
1013 				const Window *window(getWindow(i));
1014 
1015 				if (window && window->getIdentifier() == windowIdentifier)
1016 				{
1017 					previousIndex = i;
1018 
1019 					break;
1020 				}
1021 			}
1022 		}
1023 
1024 		if (previousIndex < 0)
1025 		{
1026 			MainWindow *mainWindow(MainWindow::findMainWindow(this));
1027 
1028 			if (mainWindow)
1029 			{
1030 				const QVector<MainWindow*> mainWindows(Application::getWindows());
1031 
1032 				for (int i = 0; i < mainWindows.count(); ++i)
1033 				{
1034 					if (mainWindows.at(i))
1035 					{
1036 						Window *window(mainWindows.at(i)->getWindowByIdentifier(windowIdentifier));
1037 
1038 						if (window)
1039 						{
1040 							mainWindows.at(i)->moveWindow(window, mainWindow, {{QLatin1String("index"), dropIndex}});
1041 
1042 							break;
1043 						}
1044 					}
1045 				}
1046 			}
1047 		}
1048 		else if (previousIndex != dropIndex && (previousIndex + 1) != dropIndex)
1049 		{
1050 			moveTab(previousIndex, (dropIndex - ((dropIndex > previousIndex) ? 1 : 0)));
1051 		}
1052 	}
1053 	else if (event->mimeData()->hasText() || event->mimeData()->hasUrls())
1054 	{
1055 		MainWindow *mainWindow(MainWindow::findMainWindow(this));
1056 		bool canOpen(mainWindow != nullptr);
1057 
1058 		if (canOpen)
1059 		{
1060 			const QVector<QUrl> urls(Utils::extractUrls(event->mimeData()));
1061 
1062 			if (urls.isEmpty())
1063 			{
1064 				const InputInterpreter::InterpreterResult result(InputInterpreter::interpret(event->mimeData()->text(), (InputInterpreter::NoBookmarkKeywordsFlag | InputInterpreter::NoSearchKeywordsFlag)));
1065 
1066 				if (result.isValid())
1067 				{
1068 					switch (result.type)
1069 					{
1070 						case InputInterpreter::InterpreterResult::UrlType:
1071 							mainWindow->triggerAction(ActionsManager::OpenUrlAction, {{QLatin1String("url"), result.url}, {QLatin1String("index"), dropIndex}});
1072 
1073 							break;
1074 						case InputInterpreter::InterpreterResult::SearchType:
1075 							mainWindow->search(result.searchQuery, result.searchEngine, SessionsManager::NewTabOpen);
1076 
1077 							break;
1078 						default:
1079 							break;
1080 					}
1081 				}
1082 			}
1083 			else
1084 			{
1085 				if (urls.count() > 1 && SettingsManager::getOption(SettingsManager::Choices_WarnOpenMultipleDroppedUrlsOption).toBool())
1086 				{
1087 					QMessageBox messageBox;
1088 					messageBox.setWindowTitle(tr("Question"));
1089 					messageBox.setText(tr("You are about to open %n URL(s).", "", urls.count()));
1090 					messageBox.setInformativeText(tr("Do you want to continue?"));
1091 					messageBox.setIcon(QMessageBox::Question);
1092 					messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1093 					messageBox.setDefaultButton(QMessageBox::Yes);
1094 					messageBox.setCheckBox(new QCheckBox(tr("Do not show this message again")));
1095 
1096 					if (messageBox.exec() == QMessageBox::Cancel)
1097 					{
1098 						canOpen = false;
1099 					}
1100 
1101 					SettingsManager::setOption(SettingsManager::Choices_WarnOpenMultipleDroppedUrlsOption, !messageBox.checkBox()->isChecked());
1102 				}
1103 
1104 				if (canOpen)
1105 				{
1106 					for (int i = 0; i < urls.count(); ++i)
1107 					{
1108 						mainWindow->triggerAction(ActionsManager::OpenUrlAction, {{QLatin1String("url"), urls.at(i)}, {QLatin1String("index"), (dropIndex + i)}});
1109 					}
1110 				}
1111 			}
1112 		}
1113 
1114 		if (canOpen)
1115 		{
1116 			event->setDropAction(Qt::CopyAction);
1117 			event->accept();
1118 		}
1119 		else
1120 		{
1121 			event->ignore();
1122 		}
1123 	}
1124 	else
1125 	{
1126 		event->ignore();
1127 	}
1128 
1129 	m_dragMovePosition = {};
1130 
1131 	update();
1132 }
1133 
tabLayoutChange()1134 void TabBarWidget::tabLayoutChange()
1135 {
1136 	QTabBar::tabLayoutChange();
1137 
1138 	for (int i = 0; i < count(); ++i)
1139 	{
1140 		QWidget *tabHandleWidget(tabButton(i, QTabBar::LeftSide));
1141 
1142 		if (tabHandleWidget)
1143 		{
1144 			QStyleOptionTab tabOption;
1145 
1146 			initStyleOption(&tabOption, i);
1147 
1148 			tabHandleWidget->resize(style()->subElementRect(QStyle::SE_TabBarTabLeftButton, &tabOption, this).size());
1149 		}
1150 	}
1151 
1152 	tabHovered(tabAt(mapFromGlobal(QCursor::pos())));
1153 }
1154 
tabInserted(int index)1155 void TabBarWidget::tabInserted(int index)
1156 {
1157 	setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
1158 
1159 	QTabBar::tabInserted(index);
1160 
1161 	emit tabsAmountChanged(count());
1162 }
1163 
tabRemoved(int index)1164 void TabBarWidget::tabRemoved(int index)
1165 {
1166 	QTabBar::tabRemoved(index);
1167 
1168 	if (count() == 0)
1169 	{
1170 		setMaximumSize(0, 0);
1171 	}
1172 	else if (underMouse())
1173 	{
1174 		m_needsUpdateOnLeave = true;
1175 	}
1176 
1177 	emit tabsAmountChanged(count());
1178 }
1179 
tabHovered(int index)1180 void TabBarWidget::tabHovered(int index)
1181 {
1182 	if (index == m_hoveredTab)
1183 	{
1184 		return;
1185 	}
1186 
1187 	m_hoveredTab = index;
1188 
1189 	if (m_previewWidget && !m_previewWidget->isVisible() && m_previewTimer == 0)
1190 	{
1191 		m_previewWidget->show();
1192 	}
1193 
1194 	if (m_previewWidget && m_previewWidget->isVisible())
1195 	{
1196 		showPreview(index);
1197 	}
1198 
1199 	if (!m_isDraggingTab)
1200 	{
1201 		const Window *window(getWindow(index));
1202 
1203 		if (window)
1204 		{
1205 			QStatusTipEvent statusTipEvent(window->getUrl().toDisplayString());
1206 
1207 			QApplication::sendEvent(this, &statusTipEvent);
1208 		}
1209 	}
1210 }
1211 
addTab(int index,Window * window)1212 void TabBarWidget::addTab(int index, Window *window)
1213 {
1214 	const int selectedIndex(currentIndex());
1215 
1216 	blockSignals(true);
1217 	insertTab(index, {});
1218 	blockSignals(false);
1219 	setTabButton(index, QTabBar::LeftSide, new TabHandleWidget(window, this));
1220 	setTabButton(index, QTabBar::RightSide, nullptr);
1221 
1222 	if (selectedIndex != currentIndex() || count() == 1)
1223 	{
1224 		emit currentChanged(currentIndex());
1225 	}
1226 
1227 	connect(window, &Window::isPinnedChanged, this, &TabBarWidget::updatePinnedTabsAmount);
1228 
1229 	if (window->isPinned())
1230 	{
1231 		updatePinnedTabsAmount();
1232 	}
1233 }
1234 
removeTab(int index)1235 void TabBarWidget::removeTab(int index)
1236 {
1237 	if (underMouse())
1238 	{
1239 		m_tabWidth = tabSizeHint(count() - 1).width();
1240 	}
1241 
1242 	Window *window(getWindow(index));
1243 
1244 	if (window)
1245 	{
1246 		window->deleteLater();
1247 	}
1248 
1249 	QTabBar::removeTab(index);
1250 
1251 	if (window && window->isPinned())
1252 	{
1253 		updatePinnedTabsAmount();
1254 		updateSize();
1255 	}
1256 
1257 	if (underMouse() && tabAt(mapFromGlobal(QCursor::pos())) < 0)
1258 	{
1259 		m_tabWidth = 0;
1260 
1261 		updateSize();
1262 	}
1263 }
1264 
showPreview(int index,int delay)1265 void TabBarWidget::showPreview(int index, int delay)
1266 {
1267 	if (delay > 0)
1268 	{
1269 		if (m_previewTimer == 0)
1270 		{
1271 			m_previewTimer = startTimer(delay);
1272 		}
1273 
1274 		return;
1275 	}
1276 
1277 	if (!m_arePreviewsEnabled || !isActiveWindow())
1278 	{
1279 		hidePreview();
1280 
1281 		return;
1282 	}
1283 
1284 	Window *window(getWindow(index));
1285 
1286 	if (window && m_clickedTab < 0)
1287 	{
1288 		if (!m_previewWidget)
1289 		{
1290 			m_previewWidget = new PreviewWidget(this);
1291 		}
1292 
1293 		QPoint position;
1294 		// Note that screen rectangle, tab rectangle and preview rectangle could have
1295 		// negative values on multiple monitors systems. All calculations must be done in context
1296 		// of a current screen rectangle. Because top left point of current screen could
1297 		// have coordinates (-1366, 250) instead of (0, 0).
1298 		///TODO: Calculate screen rectangle based on current mouse pointer position
1299 		const QRect screen(QApplication::desktop()->screenGeometry(this));
1300 		QRect rectangle(tabRect(index));
1301 		rectangle.moveTo(mapToGlobal(rectangle.topLeft()));
1302 
1303 		const bool isActive(index == currentIndex());
1304 
1305 		m_previewWidget->setPreview(window->getTitle(), ((isActive || m_areThumbnailsEnabled) ? QPixmap() : window->createThumbnail()), isActive);
1306 
1307 		switch (shape())
1308 		{
1309 			case QTabBar::RoundedEast:
1310 				position = QPoint((rectangle.left() - m_previewWidget->width()), qMax(screen.top(), ((rectangle.bottom() - (rectangle.height() / 2)) - (m_previewWidget->height() / 2))));
1311 
1312 				break;
1313 			case QTabBar::RoundedWest:
1314 				position = QPoint(rectangle.right(), qMax(screen.top(), ((rectangle.bottom() - (rectangle.height() / 2)) - (m_previewWidget->height() / 2))));
1315 
1316 				break;
1317 			case QTabBar::RoundedSouth:
1318 				position = QPoint(qMax(screen.left(), ((rectangle.right() - (rectangle.width() / 2)) - (m_previewWidget->width() / 2))), (rectangle.top() - m_previewWidget->height()));
1319 
1320 				break;
1321 			default:
1322 				position = QPoint(qMax(screen.left(), ((rectangle.right() - (rectangle.width() / 2)) - (m_previewWidget->width() / 2))), rectangle.bottom());
1323 
1324 				break;
1325 		}
1326 
1327 		if ((position.x() + m_previewWidget->width()) > screen.right())
1328 		{
1329 			position.setX(screen.right() - m_previewWidget->width());
1330 		}
1331 
1332 		if ((position.y() + m_previewWidget->height()) > screen.bottom())
1333 		{
1334 			position.setY(screen.bottom() - m_previewWidget->height());
1335 		}
1336 
1337 		if (m_previewWidget->isVisible())
1338 		{
1339 			m_previewWidget->setPosition(position);
1340 		}
1341 		else
1342 		{
1343 			m_previewWidget->move(position);
1344 			m_previewWidget->show();
1345 		}
1346 	}
1347 	else if (m_previewWidget)
1348 	{
1349 		m_previewWidget->hide();
1350 	}
1351 }
1352 
hidePreview()1353 void TabBarWidget::hidePreview()
1354 {
1355 	if (m_previewWidget)
1356 	{
1357 		m_previewWidget->hide();
1358 	}
1359 
1360 	if (m_previewTimer != 0)
1361 	{
1362 		killTimer(m_previewTimer);
1363 
1364 		m_previewTimer = 0;
1365 	}
1366 }
1367 
handleOptionChanged(int identifier,const QVariant & value)1368 void TabBarWidget::handleOptionChanged(int identifier, const QVariant &value)
1369 {
1370 	switch (identifier)
1371 	{
1372 		case SettingsManager::Interface_WidgetStyleOption:
1373 			updateStyle();
1374 
1375 			break;
1376 		case SettingsManager::TabBar_EnablePreviewsOption:
1377 			m_arePreviewsEnabled = value.toBool();
1378 
1379 			emit needsGeometriesUpdate();
1380 
1381 			break;
1382 		case SettingsManager::TabBar_EnableThumbnailsOption:
1383 			if (value.toBool() != m_areThumbnailsEnabled)
1384 			{
1385 				m_areThumbnailsEnabled = value.toBool();
1386 
1387 				if (!m_areThumbnailsEnabled)
1388 				{
1389 					ToolBarWidget *toolBar(qobject_cast<ToolBarWidget*>(parentWidget()));
1390 
1391 					if (toolBar)
1392 					{
1393 						toolBar->resetGeometry();
1394 					}
1395 				}
1396 
1397 				updateSize();
1398 
1399 				emit needsGeometriesUpdate();
1400 			}
1401 
1402 			break;
1403 		case SettingsManager::TabBar_MaximumTabHeightOption:
1404 			{
1405 				const int oldValue(m_maximumTabSize.height());
1406 
1407 				m_maximumTabSize.setHeight(value.toInt());
1408 
1409 				if (m_maximumTabSize.height() < 0)
1410 				{
1411 					m_maximumTabSize.setHeight(QWIDGETSIZE_MAX);
1412 				}
1413 
1414 				if (m_maximumTabSize.height() != oldValue)
1415 				{
1416 					updateSize();
1417 				}
1418 			}
1419 
1420 			break;
1421 		case SettingsManager::TabBar_MaximumTabWidthOption:
1422 			{
1423 				const int oldValue(m_maximumTabSize.width());
1424 
1425 				m_maximumTabSize.setWidth(value.toInt());
1426 
1427 				if (m_maximumTabSize.width() < 0)
1428 				{
1429 					m_maximumTabSize.setWidth(250);
1430 				}
1431 
1432 				if (m_maximumTabSize.width() != oldValue)
1433 				{
1434 					updateSize();
1435 				}
1436 			}
1437 
1438 			break;
1439 		case SettingsManager::TabBar_MinimumTabHeightOption:
1440 			{
1441 				const int oldValue(m_minimumTabSize.height());
1442 
1443 				m_minimumTabSize.setHeight(value.toInt());
1444 
1445 				if (m_minimumTabSize.height() < 0)
1446 				{
1447 					m_minimumTabSize.setHeight(qRound(QFontMetrics(font()).lineSpacing() * 1.25) + style()->pixelMetric(QStyle::PM_TabBarTabVSpace));
1448 				}
1449 
1450 				if (m_minimumTabSize.height() != oldValue)
1451 				{
1452 					updateSize();
1453 				}
1454 			}
1455 
1456 			break;
1457 		case SettingsManager::TabBar_MinimumTabWidthOption:
1458 			{
1459 				const int oldValue(m_minimumTabSize.width());
1460 
1461 				m_minimumTabSize.setWidth(value.toInt());
1462 
1463 				if (m_minimumTabSize.width() < 0)
1464 				{
1465 					m_minimumTabSize.setWidth(16 + style()->pixelMetric(QStyle::PM_TabBarTabHSpace));
1466 				}
1467 
1468 				if (m_minimumTabSize.width() != oldValue)
1469 				{
1470 					updateSize();
1471 				}
1472 			}
1473 
1474 			break;
1475 		case SettingsManager::TabBar_ShowCloseButtonOption:
1476 			if (value.toBool() != m_isCloseButtonEnabled)
1477 			{
1478 				m_isCloseButtonEnabled = value.toBool();
1479 
1480 				emit needsGeometriesUpdate();
1481 			}
1482 
1483 			break;
1484 		case SettingsManager::TabBar_ShowUrlIconOption:
1485 			if (value.toBool() != m_isUrlIconEnabled)
1486 			{
1487 				m_isUrlIconEnabled = value.toBool();
1488 
1489 				emit needsGeometriesUpdate();
1490 			}
1491 
1492 			break;
1493 		default:
1494 			break;
1495 	}
1496 }
1497 
handleCurrentChanged(int index)1498 void TabBarWidget::handleCurrentChanged(int index)
1499 {
1500 	MainWindow *mainWindow(MainWindow::findMainWindow(this));
1501 
1502 	if (mainWindow)
1503 	{
1504 		mainWindow->setActiveWindowByIndex(index);
1505 	}
1506 
1507 	if (m_previewWidget && m_previewWidget->isVisible())
1508 	{
1509 		showPreview(tabAt(mapFromGlobal(QCursor::pos())));
1510 	}
1511 
1512 	TabHandleWidget *tabHandleWidget(qobject_cast<TabHandleWidget*>(tabButton(index, QTabBar::LeftSide)));
1513 
1514 	if (tabHandleWidget)
1515 	{
1516 		tabHandleWidget->setIsActiveWindow(true);
1517 	}
1518 
1519 	if (m_activeTabHandleWidget && tabHandleWidget != m_activeTabHandleWidget)
1520 	{
1521 		m_activeTabHandleWidget->setIsActiveWindow(false);
1522 	}
1523 
1524 	m_activeTabHandleWidget = tabHandleWidget;
1525 }
1526 
updatePinnedTabsAmount()1527 void TabBarWidget::updatePinnedTabsAmount()
1528 {
1529 	int amount(0);
1530 
1531 	for (int i = 0; i < count(); ++i)
1532 	{
1533 		const Window *window(getWindow(i));
1534 
1535 		if (window && window->isPinned())
1536 		{
1537 			++amount;
1538 		}
1539 	}
1540 
1541 	if (amount != m_pinnedTabsAmount)
1542 	{
1543 		m_pinnedTabsAmount = amount;
1544 
1545 		updateSize();
1546 	}
1547 }
1548 
updateSize()1549 void TabBarWidget::updateSize()
1550 {
1551 	updateGeometry();
1552 	adjustSize();
1553 }
1554 
updateStyle()1555 void TabBarWidget::updateStyle()
1556 {
1557 	m_isLayoutReversed = (static_cast<QTabBar::ButtonPosition>(style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition)) == QTabBar::LeftSide);
1558 
1559 	if (isRightToLeft())
1560 	{
1561 		m_isLayoutReversed = !m_isLayoutReversed;
1562 	}
1563 
1564 	handleOptionChanged(SettingsManager::TabBar_MinimumTabHeightOption, SettingsManager::getOption(SettingsManager::TabBar_MinimumTabHeightOption));
1565 	handleOptionChanged(SettingsManager::TabBar_MinimumTabWidthOption, SettingsManager::getOption(SettingsManager::TabBar_MinimumTabWidthOption));
1566 
1567 	emit needsGeometriesUpdate();
1568 }
1569 
setArea(Qt::ToolBarArea area)1570 void TabBarWidget::setArea(Qt::ToolBarArea area)
1571 {
1572 	switch (area)
1573 	{
1574 		case Qt::LeftToolBarArea:
1575 			setShape(QTabBar::RoundedWest);
1576 
1577 			break;
1578 		case Qt::RightToolBarArea:
1579 			setShape(QTabBar::RoundedEast);
1580 
1581 			break;
1582 		case Qt::BottomToolBarArea:
1583 			setShape(QTabBar::RoundedSouth);
1584 
1585 			break;
1586 		default:
1587 			setShape(QTabBar::RoundedNorth);
1588 
1589 			break;
1590 	}
1591 
1592 	setSizePolicy(QSizePolicy::Preferred, ((area != Qt::LeftToolBarArea && area != Qt::RightToolBarArea) ? QSizePolicy::Maximum : QSizePolicy::Preferred));
1593 }
1594 
getWindow(int index) const1595 Window* TabBarWidget::getWindow(int index) const
1596 {
1597 	if (index >= 0 && index < count())
1598 	{
1599 		const TabHandleWidget *widget(qobject_cast<TabHandleWidget*>(tabButton(index, QTabBar::LeftSide)));
1600 
1601 		if (widget)
1602 		{
1603 			return widget->getWindow();
1604 		}
1605 	}
1606 
1607 	return nullptr;
1608 }
1609 
createStyleOptionTab(int index) const1610 QStyleOptionTab TabBarWidget::createStyleOptionTab(int index) const
1611 {
1612 	QStyleOptionTab tabOption;
1613 
1614 	initStyleOption(&tabOption, index);
1615 
1616 	const QWidget *widget(tabButton(index, QTabBar::LeftSide));
1617 
1618 	if (widget)
1619 	{
1620 		const QPoint position(widget->mapToParent(widget->rect().topLeft()));
1621 
1622 		if (shape() == QTabBar::RoundedNorth || shape() == QTabBar::RoundedSouth)
1623 		{
1624 			tabOption.rect.moveTo(position.x(), tabOption.rect.y());
1625 		}
1626 		else
1627 		{
1628 			tabOption.rect.moveTo(tabOption.rect.x(), position.y());
1629 		}
1630 	}
1631 
1632 	return tabOption;
1633 }
1634 
tabSizeHint(int index) const1635 QSize TabBarWidget::tabSizeHint(int index) const
1636 {
1637 	if (shape() == QTabBar::RoundedNorth || shape() == QTabBar::RoundedSouth)
1638 	{
1639 		const Window *window(getWindow(index));
1640 		const int tabHeight(qBound(m_minimumTabSize.height(), qMax((m_areThumbnailsEnabled ? 200 : 0), (parentWidget() ? parentWidget()->height() : height())), m_maximumTabSize.height()));
1641 
1642 		if (window && window->isPinned())
1643 		{
1644 			return {m_minimumTabSize.width(), tabHeight};
1645 		}
1646 
1647 		if (m_tabWidth > 0)
1648 		{
1649 			return {m_tabWidth, tabHeight};
1650 		}
1651 
1652 		return {qBound(m_minimumTabSize.width(), qFloor((rect().width() - (m_pinnedTabsAmount * m_minimumTabSize.width())) / qMax(1, (count() - m_pinnedTabsAmount))), m_maximumTabSize.width()), tabHeight};
1653 	}
1654 
1655 	return {m_maximumTabSize.width(), (m_areThumbnailsEnabled ? 200 : m_minimumTabSize.height())};
1656 }
1657 
minimumSizeHint() const1658 QSize TabBarWidget::minimumSizeHint() const
1659 {
1660 	return {0, 0};
1661 }
1662 
sizeHint() const1663 QSize TabBarWidget::sizeHint() const
1664 {
1665 	if (shape() == QTabBar::RoundedNorth || shape() == QTabBar::RoundedSouth)
1666 	{
1667 		int size(0);
1668 
1669 		for (int i = 0; i < count(); ++i)
1670 		{
1671 			const Window *window(getWindow(i));
1672 
1673 			size += ((window && window->isPinned()) ? m_minimumTabSize.width() : m_maximumTabSize.width());
1674 		}
1675 
1676 		if (parentWidget() && size > parentWidget()->width())
1677 		{
1678 			size = parentWidget()->width();
1679 		}
1680 
1681 		return {size, tabSizeHint(0).height()};
1682 	}
1683 
1684 	return {QTabBar::sizeHint().width(), (tabSizeHint(0).height() * count())};
1685 }
1686 
getDropIndex() const1687 int TabBarWidget::getDropIndex() const
1688 {
1689 	if (m_dragMovePosition.isNull())
1690 	{
1691 		return ((count() > 0) ? (count() + 1) : 0);
1692 	}
1693 
1694 	int index(tabAt(m_dragMovePosition));
1695 	const bool isHorizontal((shape() == QTabBar::RoundedNorth || shape() == QTabBar::RoundedSouth));
1696 
1697 	if (index >= 0)
1698 	{
1699 		const QPoint tabCenter(tabRect(index).center());
1700 
1701 		if ((isHorizontal && m_dragMovePosition.x() > tabCenter.x()) || (!isHorizontal && m_dragMovePosition.y() > tabCenter.y()))
1702 		{
1703 			++index;
1704 		}
1705 	}
1706 	else
1707 	{
1708 		index = (((isHorizontal && m_dragMovePosition.x() < rect().left()) || (!isHorizontal && m_dragMovePosition.y() < rect().top())) ? count() : 0);
1709 	}
1710 
1711 	return index;
1712 }
1713 
areThumbnailsEnabled()1714 bool TabBarWidget::areThumbnailsEnabled()
1715 {
1716 	return m_areThumbnailsEnabled;
1717 }
1718 
isLayoutReversed()1719 bool TabBarWidget::isLayoutReversed()
1720 {
1721 	return m_isLayoutReversed;
1722 }
1723 
isCloseButtonEnabled()1724 bool TabBarWidget::isCloseButtonEnabled()
1725 {
1726 	return m_isCloseButtonEnabled;
1727 }
1728 
isUrlIconEnabled()1729 bool TabBarWidget::isUrlIconEnabled()
1730 {
1731 	return m_isUrlIconEnabled;
1732 }
1733 
event(QEvent * event)1734 bool TabBarWidget::event(QEvent *event)
1735 {
1736 	switch (event->type())
1737 	{
1738 		case QEvent::LayoutDirectionChange:
1739 			emit needsGeometriesUpdate();
1740 
1741 			break;
1742 		case QEvent::MouseButtonPress:
1743 		case QEvent::MouseButtonDblClick:
1744 		case QEvent::Wheel:
1745 			{
1746 				QVariantMap parameters;
1747 				int tab(-1);
1748 
1749 				if (event->type() == QEvent::Wheel)
1750 				{
1751 					tab = tabAt(static_cast<QWheelEvent*>(event)->pos());
1752 				}
1753 				else
1754 				{
1755 					tab = tabAt(static_cast<QMouseEvent*>(event)->pos());
1756 				}
1757 
1758 				if (tab >= 0)
1759 				{
1760 					const Window *window(getWindow(tab));
1761 
1762 					if (window)
1763 					{
1764 						parameters[QLatin1String("tab")] = window->getIdentifier();
1765 					}
1766 				}
1767 
1768 				QVector<GesturesManager::GesturesContext> contexts;
1769 
1770 				if (tab < 0)
1771 				{
1772 					contexts.append(GesturesManager::NoTabHandleContext);
1773 				}
1774 				else if (tab == currentIndex())
1775 				{
1776 					contexts.append(GesturesManager::ActiveTabHandleContext);
1777 					contexts.append(GesturesManager::TabHandleContext);
1778 				}
1779 				else
1780 				{
1781 					contexts.append(GesturesManager::TabHandleContext);
1782 				}
1783 
1784 				if (qobject_cast<ToolBarWidget*>(parentWidget()))
1785 				{
1786 					contexts.append(GesturesManager::ToolBarContext);
1787 				}
1788 
1789 				contexts.append(GesturesManager::GenericContext);
1790 
1791 				GesturesManager::startGesture(this, event, contexts, parameters);
1792 			}
1793 
1794 			break;
1795 		case QEvent::ParentChange:
1796 			{
1797 				const ToolBarWidget *toolBar(qobject_cast<ToolBarWidget*>(parent()));
1798 
1799 				if (toolBar)
1800 				{
1801 					setArea(toolBar->getArea());
1802 
1803 					connect(toolBar, &ToolBarWidget::areaChanged, this, &TabBarWidget::setArea);
1804 				}
1805 			}
1806 
1807 			break;
1808 		default:
1809 			break;
1810 	}
1811 
1812 	return QTabBar::event(event);
1813 }
1814 
1815 }
1816