1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2015 - 2020 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 "StartPageModel.h"
21 #include "../../../core/AddonsManager.h"
22 #include "../../../core/BookmarksManager.h"
23 #include "../../../core/SessionsManager.h"
24 #include "../../../core/SettingsManager.h"
25 #include "../../../core/WebBackend.h"
26
27 #include <QtCore/QDir>
28 #include <QtCore/QFile>
29 #include <QtCore/QMimeData>
30 #include <QtCore/QTimer>
31 #include <QtGui/QPainter>
32
33 namespace Otter
34 {
35
StartPageModel(QObject * parent)36 StartPageModel::StartPageModel(QObject *parent) : QStandardItemModel(parent),
37 m_bookmark(nullptr)
38 {
39 handleOptionChanged(SettingsManager::Backends_WebOption);
40 reloadModel();
41
42 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkAdded, this, &StartPageModel::handleBookmarkModified);
43 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkModified, this, &StartPageModel::handleBookmarkModified);
44 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkRestored, this, &StartPageModel::handleBookmarkModified);
45 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkMoved, this, &StartPageModel::handleBookmarkMoved);
46 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkTrashed, this, &StartPageModel::handleBookmarkMoved);
47 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkRemoved, this, &StartPageModel::handleBookmarkRemoved);
48 connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &StartPageModel::handleOptionChanged);
49 }
50
reloadModel()51 void StartPageModel::reloadModel()
52 {
53 m_bookmark = BookmarksManager::getModel()->getBookmarkByPath(SettingsManager::getOption(SettingsManager::StartPage_BookmarksFolderOption).toString());
54
55 clear();
56
57 if (m_bookmark)
58 {
59 for (int i = 0; i < m_bookmark->rowCount(); ++i)
60 {
61 const BookmarksModel::Bookmark *bookmark(static_cast<BookmarksModel::Bookmark*>(m_bookmark->child(i)));
62
63 if (bookmark)
64 {
65 const BookmarksModel::BookmarkType type(bookmark->getType());
66
67 if (type != BookmarksModel::UrlBookmark && type != BookmarksModel::FolderBookmark)
68 {
69 continue;
70 }
71
72 const quint64 identifier(bookmark->getIdentifier());
73 const QUrl url(bookmark->getUrl());
74 QStandardItem *item(bookmark->clone());
75 item->setData(identifier, BookmarksModel::IdentifierRole);
76 item->setData(bookmark->getTitle(), Qt::ToolTipRole);
77 item->setFlags(item->flags() | Qt::ItemNeverHasChildren);
78
79 if (type == BookmarksModel::FolderBookmark && bookmark->rowCount() == 0)
80 {
81 item->setData(true, IsEmptyRole);
82 }
83
84 if (url.isValid() && SettingsManager::getOption(SettingsManager::StartPage_TileBackgroundModeOption) == QLatin1String("thumbnail") && !QFile::exists(getThumbnailPath(identifier)))
85 {
86 ThumbnailRequestInformation thumbnailRequestInformation;
87 thumbnailRequestInformation.bookmarkIdentifier = identifier;
88
89 m_reloads[url] = thumbnailRequestInformation;
90
91 AddonsManager::getWebBackend()->requestThumbnail(url, QSize(SettingsManager::getOption(SettingsManager::StartPage_TileWidthOption).toInt(), SettingsManager::getOption(SettingsManager::StartPage_TileHeightOption).toInt()));
92 }
93
94 appendRow(item);
95 }
96 }
97 }
98
99 if (SettingsManager::getOption(SettingsManager::StartPage_ShowAddTileOption).toBool())
100 {
101 QStandardItem *item(new QStandardItem());
102 item->setData(tr("Add Tile…"), Qt::ToolTipRole);
103 item->setData(tr("Add Tile…"), Qt::StatusTipRole);
104 item->setData(QLatin1String("add"), Qt::AccessibleDescriptionRole);
105 item->setDragEnabled(false);
106 item->setDropEnabled(false);
107 item->setFlags(item->flags() | Qt::ItemNeverHasChildren);
108
109 appendRow(item);
110 }
111
112 emit modelModified();
113 }
114
addTile(const QUrl & url)115 void StartPageModel::addTile(const QUrl &url)
116 {
117 if (!m_bookmark)
118 {
119 disconnect(BookmarksManager::getModel(), &BookmarksModel::bookmarkAdded, this, &StartPageModel::handleBookmarkModified);
120 disconnect(BookmarksManager::getModel(), &BookmarksModel::bookmarkModified, this, &StartPageModel::handleBookmarkModified);
121
122 const QStringList directories(SettingsManager::getOption(SettingsManager::StartPage_BookmarksFolderOption).toString().split(QLatin1Char('/'), QString::SkipEmptyParts));
123
124 m_bookmark = BookmarksManager::getModel()->getRootItem();
125
126 for (int i = 0; i < directories.count(); ++i)
127 {
128 bool hasMatch(false);
129
130 for (int j = 0; j < m_bookmark->rowCount(); ++j)
131 {
132 BookmarksModel::Bookmark *childBookmark(static_cast<BookmarksModel::Bookmark*>(m_bookmark->child(j)));
133
134 if (childBookmark && childBookmark->getRawData(Qt::DisplayRole).toString() == directories.at(i))
135 {
136 m_bookmark = childBookmark;
137
138 hasMatch = true;
139
140 break;
141 }
142 }
143
144 if (!hasMatch)
145 {
146 m_bookmark = BookmarksManager::getModel()->addBookmark(BookmarksModel::FolderBookmark, {{BookmarksModel::TitleRole, directories.at(i)}}, m_bookmark);
147 }
148 }
149
150 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkAdded, this, &StartPageModel::handleBookmarkModified);
151 connect(BookmarksManager::getModel(), &BookmarksModel::bookmarkModified, this, &StartPageModel::handleBookmarkModified);
152 }
153
154 const BookmarksModel::Bookmark *bookmark(BookmarksManager::getModel()->addBookmark(BookmarksModel::UrlBookmark, {{BookmarksModel::UrlRole, url}}, m_bookmark));
155
156 if (bookmark)
157 {
158 reloadTile(bookmark->index(), true);
159 }
160 }
161
reloadTile(const QModelIndex & index,bool needsTitleUpdate)162 void StartPageModel::reloadTile(const QModelIndex &index, bool needsTitleUpdate)
163 {
164 const QUrl url(index.data(BookmarksModel::UrlRole).toUrl());
165
166 if (!url.isValid())
167 {
168 return;
169 }
170
171 QSize size;
172 ThumbnailRequestInformation thumbnailRequestInformation;
173 thumbnailRequestInformation.bookmarkIdentifier = index.data(BookmarksModel::IdentifierRole).toULongLong();
174 thumbnailRequestInformation.needsTitleUpdate = needsTitleUpdate;
175
176 if (!SessionsManager::isReadOnly() && SettingsManager::getOption(SettingsManager::StartPage_TileBackgroundModeOption) == QLatin1String("thumbnail"))
177 {
178 size = QSize(SettingsManager::getOption(SettingsManager::StartPage_TileWidthOption).toInt(), SettingsManager::getOption(SettingsManager::StartPage_TileHeightOption).toInt());
179 }
180 else if (!needsTitleUpdate)
181 {
182 return;
183 }
184
185 if (url.scheme() == QLatin1String("about"))
186 {
187 const AddonsManager::SpecialPageInformation information(AddonsManager::getSpecialPage(url.path()));
188
189 if (SessionsManager::isReadOnly())
190 {
191 handleThumbnailCreated(url, {}, information.getTitle());
192 }
193 else
194 {
195 QPixmap thumbnail(size);
196 thumbnail.fill(Qt::white);
197
198 QPainter painter(&thumbnail);
199
200 information.icon.paint(&painter, QRect(QPoint(0, 0), size));
201
202 m_reloads[index.data(BookmarksModel::UrlRole).toUrl()] = thumbnailRequestInformation;
203
204 handleThumbnailCreated(url, thumbnail, information.getTitle());
205 }
206 }
207 else if (AddonsManager::getWebBackend()->requestThumbnail(url, size))
208 {
209 m_reloads[index.data(BookmarksModel::UrlRole).toUrl()] = thumbnailRequestInformation;
210 }
211 }
212
handleOptionChanged(int identifier)213 void StartPageModel::handleOptionChanged(int identifier)
214 {
215 switch (identifier)
216 {
217 case SettingsManager::Backends_WebOption:
218 connect(AddonsManager::getWebBackend(), &WebBackend::thumbnailAvailable, this, &StartPageModel::handleThumbnailCreated);
219
220 break;
221 case SettingsManager::StartPage_BookmarksFolderOption:
222 case SettingsManager::StartPage_ShowAddTileOption:
223 reloadModel();
224
225 break;
226 default:
227 break;
228 }
229 }
230
handleDragEnded()231 void StartPageModel::handleDragEnded()
232 {
233 for (int i = 0; i < rowCount(); ++i)
234 {
235 const QModelIndex index(this->index(i, 0));
236
237 if (index.data(IsDraggedRole).toBool())
238 {
239 setData(index, {}, IsDraggedRole);
240
241 emit isReloadingTileChanged(index);
242 }
243 }
244
245 emit modelModified();
246 }
247
handleBookmarkModified(BookmarksModel::Bookmark * bookmark)248 void StartPageModel::handleBookmarkModified(BookmarksModel::Bookmark *bookmark)
249 {
250 if (!m_bookmark)
251 {
252 m_bookmark = BookmarksManager::getModel()->getBookmarkByPath(SettingsManager::getOption(SettingsManager::StartPage_BookmarksFolderOption).toString());
253 }
254
255 if (m_bookmark && (bookmark == m_bookmark || m_bookmark->isAncestorOf(bookmark)))
256 {
257 reloadModel();
258 }
259 }
260
handleBookmarkMoved(BookmarksModel::Bookmark * bookmark,BookmarksModel::Bookmark * previousParent)261 void StartPageModel::handleBookmarkMoved(BookmarksModel::Bookmark *bookmark, BookmarksModel::Bookmark *previousParent)
262 {
263 if (!m_bookmark)
264 {
265 m_bookmark = BookmarksManager::getModel()->getBookmarkByPath(SettingsManager::getOption(SettingsManager::StartPage_BookmarksFolderOption).toString());
266 }
267
268 if (m_bookmark)
269 {
270 if (bookmark->parent() != m_bookmark)
271 {
272 const QString path(getThumbnailPath(bookmark->getIdentifier()));
273
274 if (QFile::exists(path))
275 {
276 QFile::remove(path);
277 }
278 }
279
280 if (bookmark == m_bookmark || previousParent == m_bookmark || m_bookmark->isAncestorOf(bookmark) || m_bookmark->isAncestorOf(previousParent))
281 {
282 reloadModel();
283 }
284 }
285 }
286
handleBookmarkRemoved(BookmarksModel::Bookmark * bookmark,BookmarksModel::Bookmark * previousParent)287 void StartPageModel::handleBookmarkRemoved(BookmarksModel::Bookmark *bookmark, BookmarksModel::Bookmark *previousParent)
288 {
289 if (m_bookmark && (bookmark == m_bookmark || previousParent == m_bookmark || m_bookmark->isAncestorOf(previousParent)))
290 {
291 const QString path(getThumbnailPath(bookmark->getIdentifier()));
292
293 if (QFile::exists(path))
294 {
295 QFile::remove(path);
296 }
297
298 QTimer::singleShot(100, this, &StartPageModel::reloadModel);
299 }
300 }
301
handleThumbnailCreated(const QUrl & url,const QPixmap & thumbnail,const QString & title)302 void StartPageModel::handleThumbnailCreated(const QUrl &url, const QPixmap &thumbnail, const QString &title)
303 {
304 if (!m_reloads.contains(url))
305 {
306 return;
307 }
308
309 const ThumbnailRequestInformation information(m_reloads[url]);
310 BookmarksModel::Bookmark *bookmark(BookmarksManager::getModel()->getBookmark(information.bookmarkIdentifier));
311
312 m_reloads.remove(url);
313
314 if (!SessionsManager::isReadOnly() && !thumbnail.isNull() && bookmark)
315 {
316 QDir().mkpath(SessionsManager::getWritableDataPath(QLatin1String("thumbnails/")));
317
318 thumbnail.save(getThumbnailPath(information.bookmarkIdentifier), "png");
319 }
320
321 if (bookmark)
322 {
323 if (information.needsTitleUpdate)
324 {
325 bookmark->setData(title, BookmarksModel::TitleRole);
326 }
327
328 emit isReloadingTileChanged(index(bookmark->index().row(), bookmark->index().column()));
329 }
330 }
331
mimeData(const QModelIndexList & indexes) const332 QMimeData* StartPageModel::mimeData(const QModelIndexList &indexes) const
333 {
334 QMimeData *mimeData(new QMimeData());
335 QStringList texts;
336 texts.reserve(indexes.count());
337
338 QList<QUrl> urls;
339 urls.reserve(indexes.count());
340
341 if (indexes.count() == 1)
342 {
343 mimeData->setProperty("x-item-index", indexes.at(0));
344
345 itemFromIndex(indexes.at(0))->setData(true, IsDraggedRole);
346 }
347
348 for (int i = 0; i < indexes.count(); ++i)
349 {
350 if (indexes.at(i).isValid() && static_cast<BookmarksModel::BookmarkType>(indexes.at(i).data(BookmarksModel::TypeRole).toInt()) == BookmarksModel::UrlBookmark)
351 {
352 texts.append(indexes.at(i).data(BookmarksModel::UrlRole).toString());
353 urls.append(indexes.at(i).data(BookmarksModel::UrlRole).toUrl());
354 }
355 }
356
357 mimeData->setText(texts.join(QLatin1String(", ")));
358 mimeData->setUrls(urls);
359
360 connect(mimeData, &QMimeData::destroyed, this, &StartPageModel::handleDragEnded);
361
362 return mimeData;
363 }
364
getBookmark(const QModelIndex & index)365 BookmarksModel::Bookmark* StartPageModel::getBookmark(const QModelIndex &index)
366 {
367 const QVariant data(index.data(BookmarksModel::IdentifierRole));
368
369 return (data.isValid() ? BookmarksManager::getModel()->getBookmark(data.toULongLong()) : nullptr);
370 }
371
getThumbnailPath(quint64 identifier)372 QString StartPageModel::getThumbnailPath(quint64 identifier)
373 {
374 return SessionsManager::getWritableDataPath(QLatin1String("thumbnails/")) + QString::number(identifier) + QLatin1String(".png");
375 }
376
data(const QModelIndex & index,int role) const377 QVariant StartPageModel::data(const QModelIndex &index, int role) const
378 {
379 if (role == IsReloadingRole)
380 {
381 return m_reloads.contains(index.data(BookmarksModel::UrlRole).toUrl());
382 }
383
384 return QStandardItemModel::data(index, role);
385 }
386
mimeTypes() const387 QStringList StartPageModel::mimeTypes() const
388 {
389 return {QLatin1String("text/uri-list")};
390 }
391
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)392 bool StartPageModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
393 {
394 Q_UNUSED(action)
395 Q_UNUSED(column)
396
397 const BookmarksModel::BookmarkType type(static_cast<BookmarksModel::BookmarkType>(parent.data(BookmarksModel::TypeRole).toInt()));
398 const QModelIndex index(data->property("x-item-index").toModelIndex());
399
400 if (m_bookmark)
401 {
402 if (index.isValid())
403 {
404 if (type == BookmarksModel::FolderBookmark || type == BookmarksModel::RootBookmark || type == BookmarksModel::TrashBookmark)
405 {
406 return BookmarksManager::getModel()->moveBookmark(BookmarksManager::getModel()->getBookmark(index), BookmarksManager::getModel()->getBookmark(parent), row);
407 }
408
409 return BookmarksManager::getModel()->moveBookmark(BookmarksManager::getModel()->getBookmark(index), m_bookmark, (parent.row() + ((index.row() < parent.row()) ? 1 : 0)));
410 }
411 else if (data->hasUrls())
412 {
413 const QVector<QUrl> urls(Utils::extractUrls(data));
414
415 for (int i = 0; i < urls.count(); ++i)
416 {
417 addTile(urls.at(i));
418 }
419 }
420 }
421
422 return false;
423 }
424
event(QEvent * event)425 bool StartPageModel::event(QEvent *event)
426 {
427 if (event->type() == QEvent::LanguageChange)
428 {
429 reloadModel();
430 }
431
432 return QStandardItemModel::event(event);
433 }
434
435 }
436