Falkon Develop
Cross-platform Qt-based web browser
tabtreemodel.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
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#include "tabtreemodel.h"
19#include "tabmodel.h"
20#include "webtab.h"
21#include "tabwidget.h"
22#include "browserwindow.h"
23
24#include <QTimer>
25#include <QMimeData>
26
28{
29public:
30 explicit TabTreeModelItem(WebTab *tab = nullptr, const QModelIndex &index = QModelIndex());
32
33 void setParent(TabTreeModelItem *item);
34 void addChild(TabTreeModelItem *item, int index = -1);
35
36 WebTab *tab = nullptr;
38 QVector<TabTreeModelItem*> children;
39 QPersistentModelIndex sourceIndex;
40};
41
42TabTreeModelItem::TabTreeModelItem(WebTab *tab, const QModelIndex &index)
43 : tab(tab)
44 , sourceIndex(index)
45{
46}
47
49{
50 for (TabTreeModelItem *child : std::as_const(children)) {
51 delete child;
52 }
53}
54
56{
57 if (parent == item) {
58 return;
59 }
60 if (parent) {
61 parent->children.removeOne(this);
62 }
63 parent = item;
64 if (parent) {
65 parent->children.append(this);
66 }
67}
68
70{
71 item->setParent(nullptr);
72 item->parent = this;
73 if (index < 0 || index > children.size()) {
74 children.append(item);
75 } else {
76 children.insert(index, item);
77 }
78}
79
81 : QAbstractProxyModel(parent)
82 , m_window(window)
83{
84 connect(m_window, &BrowserWindow::aboutToClose, this, &TabTreeModel::syncTopLevelTabs);
85
86 connect(this, &QAbstractProxyModel::sourceModelChanged, this, &TabTreeModel::init);
87}
88
90{
91 delete m_root;
92}
93
94QModelIndex TabTreeModel::tabIndex(WebTab *tab) const
95{
96 TabTreeModelItem *item = m_items.value(tab);
97 if (!item) {
98 return {};
99 }
100 return createIndex(item->parent->children.indexOf(item), 0, item);
101}
102
103WebTab *TabTreeModel::tab(const QModelIndex &index) const
104{
105 TabTreeModelItem *it = item(index);
106 return it ? it->tab : nullptr;
107}
108
109Qt::ItemFlags TabTreeModel::flags(const QModelIndex &index) const
110{
111 TabTreeModelItem *it = item(index);
112 if (!it || !it->tab) {
113 return Qt::ItemIsDropEnabled;
114 }
115 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
116 if (!it->tab->isPinned()) {
117 flags |= Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled;
118 }
119 return flags;
120}
121
122QVariant TabTreeModel::data(const QModelIndex &index, int role) const
123{
124 return sourceModel()->data(mapToSource(index), role);
125}
126
127int TabTreeModel::rowCount(const QModelIndex &parent) const
128{
129 TabTreeModelItem *it = item(parent);
130 if (!it) {
131 return 0;
132 }
133 return it->children.count();
134}
135
136int TabTreeModel::columnCount(const QModelIndex &parent) const
137{
138 if (parent.column() > 0) {
139 return 0;
140 }
141 return 1;
142}
143
144bool TabTreeModel::hasChildren(const QModelIndex &parent) const
145{
146 TabTreeModelItem *it = item(parent);
147 if (!it) {
148 return false;
149 }
150 return !it->children.isEmpty();
151}
152
153QModelIndex TabTreeModel::parent(const QModelIndex &child) const
154{
155 TabTreeModelItem *it = item(child);
156 if (!it) {
157 return {};
158 }
159 return index(it->parent);
160}
161
162QModelIndex TabTreeModel::index(int row, int column, const QModelIndex &parent) const
163{
164 if (!hasIndex(row, column, parent)) {
165 return {};
166 }
167 TabTreeModelItem *parentItem = item(parent);
168 return createIndex(row, column, parentItem->children.at(row));
169}
170
171QModelIndex TabTreeModel::mapFromSource(const QModelIndex &sourceIndex) const
172{
173 return tabIndex(sourceIndex.data(TabModel::WebTabRole).value<WebTab*>());
174}
175
176QModelIndex TabTreeModel::mapToSource(const QModelIndex &proxyIndex) const
177{
178 TabTreeModelItem *it = item(proxyIndex);
179 if (!it) {
180 return {};
181 }
182 return it->sourceIndex;
183}
184
185bool TabTreeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
186{
187 Q_UNUSED(row)
188 Q_UNUSED(parent)
189 if (action != Qt::MoveAction || column > 0 || !m_window) {
190 return false;
191 }
192 const auto *mimeData = qobject_cast<const TabModelMimeData*>(data);
193 if (!mimeData) {
194 return false;
195 }
196 WebTab *tab = mimeData->tab();
197 if (!tab) {
198 return false;
199 }
200 // This would require moving the entire tab tree
201 if (tab->browserWindow() != m_window && !tab->childTabs().isEmpty()) {
202 return false;
203 }
204 return true;
205}
206
207bool TabTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
208{
209 if (!canDropMimeData(data, action, row, column, parent)) {
210 return false;
211 }
212
213 const auto *mimeData = static_cast<const TabModelMimeData*>(data);
214 WebTab *tab = mimeData->tab();
215
216 if (tab->isPinned()) {
217 tab->togglePinned();
218 }
219
220 if (tab->browserWindow() != m_window) {
221 if (tab->browserWindow()) {
224 }
225 }
226
227 TabTreeModelItem *it = m_items.value(tab);
228 TabTreeModelItem *parentItem = item(parent);
229 if (!it || !parentItem) {
230 return false;
231 }
232 if (it->parent == parentItem && row < 0) {
233 return false;
234 }
235 if (!parentItem->tab) {
236 tab->setParentTab(nullptr);
237 if (row < 0) {
238 row = m_root->children.count();
239 }
240 const QModelIndex fromIdx = index(it);
241 const int childPos = row > fromIdx.row() ? row - 1 : row;
242 if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), QModelIndex(), row)) {
243 qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << "root" << row;
244 return true;
245 }
246 m_root->addChild(it, childPos);
247 endMoveRows();
248 } else {
249 parentItem->tab->addChildTab(tab, row);
250 }
251
252 return true;
253}
254
255void TabTreeModel::init()
256{
257 delete m_root;
258 m_items.clear();
259
260 m_root = new TabTreeModelItem;
261
262 for (int i = 0; i < sourceModel()->rowCount(); ++i) {
263 const QModelIndex index = sourceModel()->index(i, 0);
264 auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
265 if (tab && !tab->parentTab()) {
266 auto *item = new TabTreeModelItem(tab, index);
267 m_items[tab] = item;
268 m_root->addChild(createItems(item));
269 }
270 }
271
272 for (TabTreeModelItem *item : std::as_const(m_items)) {
273 connectTab(item->tab);
274 }
275
276 connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &TabTreeModel::sourceDataChanged, Qt::UniqueConnection);
277 connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &TabTreeModel::sourceRowsInserted, Qt::UniqueConnection);
278 connect(sourceModel(), &QAbstractItemModel::rowsAboutToBeRemoved, this, &TabTreeModel::sourceRowsAboutToBeRemoved, Qt::UniqueConnection);
279 connect(sourceModel(), &QAbstractItemModel::modelReset, this, &TabTreeModel::sourceReset, Qt::UniqueConnection);
280}
281
282QModelIndex TabTreeModel::index(TabTreeModelItem *item) const
283{
284 if (!item || item == m_root) {
285 return {};
286 }
287 return createIndex(item->parent->children.indexOf(item), 0, item);
288}
289
290TabTreeModelItem *TabTreeModel::item(const QModelIndex &index) const
291{
292 auto *it = static_cast<TabTreeModelItem*>(index.internalPointer());
293 return it ? it : m_root;
294}
295
296TabTreeModelItem *TabTreeModel::createItems(TabTreeModelItem *root)
297{
298 const auto children = root->tab->childTabs();
299 for (WebTab *child : children) {
300 const QModelIndex index = sourceModel()->index(child->tabIndex(), 0);
301 auto *item = new TabTreeModelItem(child, index);
302 m_items[child] = item;
303 root->addChild(createItems(item));
304 }
305 return root;
306}
307
308void TabTreeModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
309{
310 Q_EMIT dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight), roles);
311}
312
313void TabTreeModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
314{
315 for (int i = start; i <= end; ++i) {
316 insertIndex(sourceModel()->index(i, 0, parent));
317 }
318}
319
320void TabTreeModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
321{
322 for (int i = start; i <= end; ++i) {
323 removeIndex(sourceModel()->index(i, 0, parent));
324 }
325}
326
327void TabTreeModel::sourceReset()
328{
329 beginResetModel();
330 init();
331 endResetModel();
332}
333
334void TabTreeModel::insertIndex(const QModelIndex &sourceIndex)
335{
336 auto *tab = sourceIndex.data(TabModel::WebTabRole).value<WebTab*>();
337 if (!tab) {
338 return;
339 }
340 TabTreeModelItem *parent = m_items.value(tab->parentTab());
341 if (!parent) {
342 parent = m_root;
343 }
344 auto *item = new TabTreeModelItem(tab, sourceIndex);
345
346 const int idx = parent->children.count();
347 beginInsertRows(tabIndex(tab->parentTab()), idx, idx);
348 m_items[tab] = item;
349 parent->addChild(item);
350 endInsertRows();
351
352 connectTab(tab);
353}
354
355void TabTreeModel::removeIndex(const QModelIndex &sourceIndex)
356{
357 auto *tab = sourceIndex.data(TabModel::WebTabRole).value<WebTab*>();
358 if (!tab) {
359 return;
360 }
361 TabTreeModelItem *item = m_items.value(tab);
362 if (!item) {
363 return;
364 }
365
366 const QModelIndex index = mapFromSource(sourceIndex);
367 beginRemoveRows(index.parent(), index.row(), index.row());
368 item->setParent(nullptr);
369 Q_ASSERT(item->children.isEmpty());
370 delete item;
371 endRemoveRows();
372
373 tab->disconnect(this);
374}
375
376void TabTreeModel::connectTab(WebTab *tab)
377{
378 TabTreeModelItem *item = m_items.value(tab);
379 Q_ASSERT(item);
380
381 connect(tab, &WebTab::parentTabChanged, this, [=](WebTab *parent) {
382 // Handle only move to root, everything else is done in childTabAdded
383 if (item->parent == m_root || parent) {
384 return;
385 }
386 int pos = m_root->children.count();
387 // Move it to the same spot as old parent
388 if (item->parent->parent == m_root) {
389 pos = m_root->children.indexOf(item->parent);
390 }
391 const QModelIndex fromIdx = index(item);
392 if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), QModelIndex(), pos)) {
393 qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << "root" << pos;
394 return;
395 }
396 m_root->addChild(item, pos);
397 endMoveRows();
398 });
399
400 connect(tab, &WebTab::childTabAdded, this, [=](WebTab *child, int pos) {
401 TabTreeModelItem *from = m_items.value(child);
402 if (!from) {
403 return;
404 }
405 const QModelIndex fromIdx = index(from);
406 const QModelIndex toIdx = index(item);
407 const int childPos = fromIdx.parent() == toIdx && pos > fromIdx.row() ? pos - 1 : pos;
408 if (!beginMoveRows(fromIdx.parent(), fromIdx.row(), fromIdx.row(), toIdx, pos)) {
409 qWarning() << "Invalid beginMoveRows" << fromIdx.parent() << fromIdx.row() << toIdx << pos;
410 return;
411 }
412 item->addChild(from, childPos);
413 endMoveRows();
414 });
415}
416
417void TabTreeModel::syncTopLevelTabs()
418{
419 // Move all normal top-level tabs to the beginning to preserve order in session
420
421 int index = -1;
422
423 const auto items = m_root->children;
424 for (TabTreeModelItem *item : items) {
425 if (!item->tab->isPinned()) {
426 const int tabIndex = item->tab->tabIndex();
427 if (index < 0 || tabIndex < index) {
428 index = tabIndex;
429 }
430 }
431 }
432
433 if (index < 0) {
434 return;
435 }
436
437 for (TabTreeModelItem *item : items) {
438 if (!item->tab->isPinned()) {
439 item->tab->moveTab(index++);
440 }
441 }
442}
void aboutToClose()
TabWidget * tabWidget() const
@ WebTabRole
Definition: tabmodel.h:54
int rowCount(const QModelIndex &parent) const override
~TabTreeModel() override
int columnCount(const QModelIndex &parent) const override
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
WebTab * tab(const QModelIndex &index) const
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
QVariant data(const QModelIndex &index, int role) const override
QModelIndex tabIndex(WebTab *tab) const
TabTreeModel(BrowserWindow *window, QObject *parent=nullptr)
QModelIndex parent(const QModelIndex &child) const override
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
bool hasChildren(const QModelIndex &parent) const override
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
TabTreeModelItem * parent
TabTreeModelItem(WebTab *tab=nullptr, const QModelIndex &index=QModelIndex())
void addChild(TabTreeModelItem *item, int index=-1)
QPersistentModelIndex sourceIndex
void setParent(TabTreeModelItem *item)
QVector< TabTreeModelItem * > children
int addView(const LoadRequest &req, const Qz::NewTabPositionFlags &openFlags, bool selectLine=false, bool pinned=false)
Definition: tabwidget.cpp:314
void detachTab(WebTab *tab)
Definition: tabwidget.cpp:685
Definition: webtab.h:40
int tabIndex() const
Definition: webtab.cpp:710
void setParentTab(WebTab *tab)
Definition: webtab.cpp:474
void parentTabChanged(WebTab *tab)
QVector< WebTab * > childTabs() const
Definition: webtab.cpp:533
bool isPinned() const
Definition: webtab.cpp:414
BrowserWindow * browserWindow() const
Definition: webtab.cpp:204
void addChildTab(WebTab *tab, int index=-1)
Definition: webtab.cpp:502
void togglePinned()
Definition: webtab.cpp:715
void moveTab(int to)
Definition: webtab.cpp:703
void childTabAdded(WebTab *tab, int index)
WebTab * parentTab() const
Definition: webtab.cpp:469
@ NT_SelectedTab
Definition: qzcommon.h:97
i
Definition: i18n.py:23