1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2015 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 **************************************************************************/
19
20 #include "TabSwitcherWidget.h"
21 #include "Animation.h"
22 #include "MainWindow.h"
23 #include "Window.h"
24 #include "../core/Application.h"
25 #include "../core/SessionModel.h"
26 #include "../core/ThemesManager.h"
27
28 #include <QtGui/QKeyEvent>
29 #include <QtWidgets/QFrame>
30 #include <QtWidgets/QHBoxLayout>
31 #include <QtWidgets/QHeaderView>
32
33 namespace Otter
34 {
35
TabSwitcherWidget(MainWindow * parent)36 TabSwitcherWidget::TabSwitcherWidget(MainWindow *parent) : QWidget(parent),
37 m_mainWindow(parent),
38 m_model(new QStandardItemModel(this)),
39 m_tabsView(new ItemViewWidget(this)),
40 m_previewLabel(new QLabel(this)),
41 m_spinnerAnimation(nullptr),
42 m_reason(KeyboardReason),
43 m_isIgnoringMinimizedTabs(SettingsManager::getOption(SettingsManager::TabSwitcher_IgnoreMinimizedTabsOption).toBool())
44 {
45 QFrame *frame(new QFrame(this));
46 QHBoxLayout *mainLayout(new QHBoxLayout(this));
47 mainLayout->addWidget(frame, 0, Qt::AlignCenter);
48
49 setLayout(mainLayout);
50 setAutoFillBackground(false);
51
52 QHBoxLayout *frameLayout(new QHBoxLayout(frame));
53 frameLayout->addWidget(m_tabsView, 1);
54 frameLayout->addWidget(m_previewLabel, 0, Qt::AlignCenter);
55
56 m_model->setSortRole(OrderRole);
57
58 frame->setLayout(frameLayout);
59 frame->setAutoFillBackground(true);
60 frame->setMinimumWidth(600);
61 frame->setObjectName(QLatin1String("tabSwitcher"));
62 frame->setStyleSheet(QStringLiteral("#tabSwitcher {background:%1;border:1px solid #B3B3B3;border-radius:4px;}").arg(palette().color(QPalette::Base).name()));
63
64 m_tabsView->setModel(m_model);
65 m_tabsView->setStyleSheet(QLatin1String("border:0;"));
66 m_tabsView->header()->hide();
67 m_tabsView->header()->setStretchLastSection(true);
68 m_tabsView->viewport()->installEventFilter(this);
69
70 m_previewLabel->setFixedSize(260, 170);
71 m_previewLabel->setAlignment(Qt::AlignCenter);
72 m_previewLabel->setStyleSheet(QLatin1String("border:1px solid gray;"));
73
74 connect(m_tabsView, &ItemViewWidget::clicked, this, &TabSwitcherWidget::handleIndexClicked);
75 connect(m_tabsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TabSwitcherWidget::handleCurrentTabChanged);
76 }
77
showEvent(QShowEvent * event)78 void TabSwitcherWidget::showEvent(QShowEvent *event)
79 {
80 setFocus();
81
82 const MainWindowSessionItem *mainWindowItem(SessionsManager::getModel()->getMainWindowItem(m_mainWindow));
83
84 if (mainWindowItem)
85 {
86 const bool useSorting(SettingsManager::getOption(SettingsManager::TabSwitcher_OrderByLastActivityOption).toBool());
87
88 for (int i = 0; i < mainWindowItem->rowCount(); ++i)
89 {
90 const WindowSessionItem *windowItem(static_cast<WindowSessionItem*>(mainWindowItem->child(i, 0)));
91
92 if (windowItem && (!m_isIgnoringMinimizedTabs || (windowItem->getActiveWindow() && windowItem->getActiveWindow()->getWindowState().state != Qt::WindowMinimized)))
93 {
94 m_model->appendRow(createRow(windowItem->getActiveWindow(), (useSorting ? QVariant(windowItem->getActiveWindow()->getLastActivity()) : QVariant(i))));
95 }
96 }
97 }
98
99 m_model->sort(0, (SettingsManager::getOption(SettingsManager::TabSwitcher_OrderByLastActivityOption).toBool() ? Qt::DescendingOrder : Qt::AscendingOrder));
100
101 const Window *activeWindow(m_mainWindow->getActiveWindow());
102 const int contentsHeight(m_model->rowCount() * 22);
103
104 m_tabsView->setCurrentIndex(m_model->index((activeWindow ? findRow(activeWindow->getIdentifier()) : 0), 0));
105 m_tabsView->setMinimumHeight(qMin(contentsHeight, int(height() * 0.9)));
106
107 QWidget::showEvent(event);
108
109 connect(m_mainWindow, &MainWindow::windowAdded, this, &TabSwitcherWidget::handleWindowAdded);
110 connect(m_mainWindow, &MainWindow::windowRemoved, this, &TabSwitcherWidget::handleWindowRemoved);
111 }
112
hideEvent(QHideEvent * event)113 void TabSwitcherWidget::hideEvent(QHideEvent *event)
114 {
115 QWidget::hideEvent(event);
116
117 disconnect(m_mainWindow, &MainWindow::windowAdded, this, &TabSwitcherWidget::handleWindowAdded);
118 disconnect(m_mainWindow, &MainWindow::windowRemoved, this, &TabSwitcherWidget::handleWindowRemoved);
119
120 m_model->clear();
121 }
122
keyPressEvent(QKeyEvent * event)123 void TabSwitcherWidget::keyPressEvent(QKeyEvent *event)
124 {
125 switch (event->key())
126 {
127 case Qt::Key_Tab:
128 case Qt::Key_Down:
129 selectTab(true);
130
131 break;
132 case Qt::Key_Backtab:
133 case Qt::Key_Up:
134 selectTab(false);
135
136 break;
137 case Qt::Key_Enter:
138 case Qt::Key_Return:
139 accept();
140
141 break;
142 case Qt::Key_Escape:
143 hide();
144
145 break;
146 default:
147 break;
148 }
149 }
150
keyReleaseEvent(QKeyEvent * event)151 void TabSwitcherWidget::keyReleaseEvent(QKeyEvent *event)
152 {
153 if (event->key() == Qt::Key_Control && m_reason == KeyboardReason)
154 {
155 accept();
156
157 event->accept();
158 }
159 }
160
show(SwitcherReason reason)161 void TabSwitcherWidget::show(SwitcherReason reason)
162 {
163 m_reason = reason;
164
165 QWidget::show();
166 }
167
accept()168 void TabSwitcherWidget::accept()
169 {
170 m_mainWindow->setActiveWindowByIdentifier(m_tabsView->currentIndex().data(IdentifierRole).toULongLong());
171
172 hide();
173 }
174
selectTab(bool next)175 void TabSwitcherWidget::selectTab(bool next)
176 {
177 const int currentRow(m_tabsView->currentIndex().row());
178
179 m_tabsView->setCurrentIndex(m_model->index((next ? ((currentRow == (m_model->rowCount() - 1)) ? 0 : (currentRow + 1)) : ((currentRow == 0) ? (m_model->rowCount() - 1) : (currentRow - 1))), 0));
180 }
181
handleIndexClicked(const QModelIndex & index)182 void TabSwitcherWidget::handleIndexClicked(const QModelIndex &index)
183 {
184 if (index.isValid())
185 {
186 m_mainWindow->setActiveWindowByIdentifier(index.data(IdentifierRole).toULongLong());
187
188 hide();
189 }
190 }
191
handleCurrentTabChanged(const QModelIndex & index)192 void TabSwitcherWidget::handleCurrentTabChanged(const QModelIndex &index)
193 {
194 const Window *window(m_mainWindow->getWindowByIdentifier(index.data(IdentifierRole).toULongLong()));
195
196 m_previewLabel->setMovie(nullptr);
197 m_previewLabel->setPixmap({});
198
199 if (!window)
200 {
201 return;
202 }
203
204 if (window->getLoadingState() == WebWidget::DeferredLoadingState || window->getLoadingState() == WebWidget::OngoingLoadingState)
205 {
206 if (!m_spinnerAnimation)
207 {
208 const QString path(ThemesManager::getAnimationPath(QLatin1String("spinner")));
209
210 if (path.isEmpty())
211 {
212 m_spinnerAnimation = new SpinnerAnimation(this);
213 }
214 else
215 {
216 m_spinnerAnimation = new GenericAnimation(path, this);
217 }
218
219 connect(m_spinnerAnimation, &Animation::frameChanged, m_previewLabel, [&]()
220 {
221 m_previewLabel->setPixmap(m_spinnerAnimation->getCurrentPixmap());
222 });
223 }
224
225 m_spinnerAnimation->start();
226
227 m_previewLabel->setPixmap(m_spinnerAnimation->getCurrentPixmap());
228 }
229 else
230 {
231 if (m_spinnerAnimation && m_spinnerAnimation->isRunning())
232 {
233 m_spinnerAnimation->stop();
234 }
235
236 m_previewLabel->setPixmap((window->getLoadingState() == WebWidget::CrashedLoadingState) ? ThemesManager::createIcon(QLatin1String("tab-crashed")).pixmap(32, 32) : window->createThumbnail());
237 }
238 }
239
handleWindowAdded(quint64 identifier)240 void TabSwitcherWidget::handleWindowAdded(quint64 identifier)
241 {
242 Window *window(m_mainWindow->getWindowByIdentifier(identifier));
243
244 if (window && (!m_isIgnoringMinimizedTabs || window->getWindowState().state != Qt::WindowMinimized))
245 {
246 m_model->insertRow(0, createRow(window, (SettingsManager::getOption(SettingsManager::TabSwitcher_OrderByLastActivityOption).toBool() ? QVariant(window->getLastActivity()) : QVariant(-1))));
247 }
248 }
249
handleWindowRemoved(quint64 identifier)250 void TabSwitcherWidget::handleWindowRemoved(quint64 identifier)
251 {
252 const int row(findRow(identifier));
253
254 if (row >= 0)
255 {
256 m_model->removeRow(row);
257 }
258 }
259
setTitle(const QString & title)260 void TabSwitcherWidget::setTitle(const QString &title)
261 {
262 const Window *window(qobject_cast<Window*>(sender()));
263
264 if (window)
265 {
266 const int row(findRow(window->getIdentifier()));
267
268 if (row >= 0)
269 {
270 m_model->setData(m_model->index(row, 0), title, Qt::DisplayRole);
271 }
272 }
273 }
274
setIcon(const QIcon & icon)275 void TabSwitcherWidget::setIcon(const QIcon &icon)
276 {
277 const Window *window(qobject_cast<Window*>(sender()));
278
279 if (window)
280 {
281 const int row(findRow(window->getIdentifier()));
282
283 if (row >= 0)
284 {
285 m_model->setData(m_model->index(row, 0), icon, Qt::DecorationRole);
286 }
287 }
288 }
289
setLoadingState(WebWidget::LoadingState state)290 void TabSwitcherWidget::setLoadingState(WebWidget::LoadingState state)
291 {
292 const Window *window(qobject_cast<Window*>(sender()));
293
294 if (window)
295 {
296 const int row(findRow(window->getIdentifier()));
297
298 if (row >= 0)
299 {
300 QColor color(palette().color(QPalette::Text));
301
302 if (state == WebWidget::DeferredLoadingState)
303 {
304 color.setAlpha(150);
305 }
306
307 m_model->setData(m_model->index(row, 0), color, Qt::TextColorRole);
308 }
309 }
310 }
311
createRow(Window * window,const QVariant & index) const312 QStandardItem* TabSwitcherWidget::createRow(Window *window, const QVariant &index) const
313 {
314 QColor color(palette().color(QPalette::Text));
315
316 if (window->getLoadingState() == WebWidget::DeferredLoadingState)
317 {
318 color.setAlpha(150);
319 }
320
321 QStandardItem *item(new QStandardItem(window->getIcon(), window->getTitle()));
322 item->setData(color, Qt::TextColorRole);
323 item->setData(window->getIdentifier(), IdentifierRole);
324 item->setData(index, OrderRole);
325 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
326
327 connect(window, &Window::titleChanged, this, &TabSwitcherWidget::setTitle);
328 connect(window, &Window::iconChanged, this, &TabSwitcherWidget::setIcon);
329 connect(window, &Window::loadingStateChanged, this, &TabSwitcherWidget::setLoadingState);
330
331 return item;
332 }
333
getReason() const334 TabSwitcherWidget::SwitcherReason TabSwitcherWidget::getReason() const
335 {
336 return m_reason;
337 }
338
findRow(quint64 identifier) const339 int TabSwitcherWidget::findRow(quint64 identifier) const
340 {
341 for (int i = 0; i < m_model->rowCount(); ++i)
342 {
343 if (m_model->index(i, 0).data(IdentifierRole).toULongLong() == identifier)
344 {
345 return i;
346 }
347 }
348
349 return -1;
350 }
351
eventFilter(QObject * object,QEvent * event)352 bool TabSwitcherWidget::eventFilter(QObject *object, QEvent *event)
353 {
354 if (object == m_tabsView->viewport() && event->type() == QEvent::MouseButtonPress)
355 {
356 const QMouseEvent *mouseEvent(static_cast<QMouseEvent*>(event));
357
358 if (mouseEvent->button() == Qt::MiddleButton)
359 {
360 const QModelIndex index(m_tabsView->indexAt(mouseEvent->pos()));
361
362 if (index.isValid())
363 {
364 Application::triggerAction(ActionsManager::CloseTabAction, {{QLatin1String("tab"), index.data(IdentifierRole).toULongLong()}}, parentWidget());
365 }
366
367 return true;
368 }
369 }
370
371 return QObject::eventFilter(object, event);
372 }
373
374 }
375