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