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