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 *
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 "BookmarksContentsWidget.h"
21 #include "../../../core/Application.h"
22 #include "../../../core/SessionsManager.h"
23 #include "../../../core/SettingsManager.h"
24 #include "../../../core/ThemesManager.h"
25 #include "../../../ui/Action.h"
26 #include "../../../ui/BookmarkPropertiesDialog.h"
27 #include "../../../ui/MainWindow.h"
28 #include "../../../ui/ProxyModel.h"
29 #include "../../../ui/Window.h"
30 
31 #include "ui_BookmarksContentsWidget.h"
32 
33 #include <QtGui/QClipboard>
34 #include <QtGui/QMouseEvent>
35 #include <QtWidgets/QDesktopWidget>
36 #include <QtWidgets/QMenu>
37 #include <QtWidgets/QToolTip>
38 
39 namespace Otter
40 {
41 
BookmarksContentsWidget(const QVariantMap & parameters,Window * window,QWidget * parent)42 BookmarksContentsWidget::BookmarksContentsWidget(const QVariantMap &parameters, Window *window, QWidget *parent) : ContentsWidget(parameters, window, parent),
43 	m_model(nullptr),
44 	m_ui(new Ui::BookmarksContentsWidget)
45 {
46 	m_ui->setupUi(this);
47 	m_ui->filterLineEditWidget->setClearOnEscape(true);
48 
49 	QMenu *addMenu(new QMenu(m_ui->addButton));
50 	addMenu->addAction(ThemesManager::createIcon(QLatin1String("inode-directory")), tr("Add Folder…"), this, &BookmarksContentsWidget::addFolder);
51 	addMenu->addAction(tr("Add Bookmark…"), this, &BookmarksContentsWidget::addBookmark);
52 	addMenu->addAction(tr("Add Separator"), this, &BookmarksContentsWidget::addSeparator);
53 
54 	m_model = new ProxyModel(BookmarksManager::getModel(), QVector<QPair<QString, int> >({{tr("Title"), BookmarksModel::TitleRole}, {tr("Address"), BookmarksModel::UrlRole}, {tr("Description"), BookmarksModel::DescriptionRole}, {tr("Keyword"), BookmarksModel::KeywordRole}, {tr("Added"), BookmarksModel::TimeAddedRole}, {tr("Modified"), BookmarksModel::TimeModifiedRole}, {tr("Visited"), BookmarksModel::TimeVisitedRole}, {tr("Visits"), BookmarksModel::VisitsRole}}), this);
55 	m_model->setHeaderData(0, Qt::Horizontal, 300, HeaderViewWidget::WidthRole);
56 	m_model->setHeaderData(1, Qt::Horizontal, 300, HeaderViewWidget::WidthRole);
57 	m_model->setHeaderData(3, Qt::Horizontal, 150, HeaderViewWidget::WidthRole);
58 	m_model->setHeaderData(7, Qt::Horizontal, 150, HeaderViewWidget::WidthRole);
59 
60 	m_ui->addButton->setMenu(addMenu);
61 	m_ui->bookmarksViewWidget->setViewMode(ItemViewWidget::TreeView);
62 	m_ui->bookmarksViewWidget->setModel(m_model);
63 	m_ui->bookmarksViewWidget->setExpanded(m_ui->bookmarksViewWidget->model()->index(0, 0), true);
64 	m_ui->bookmarksViewWidget->installEventFilter(this);
65 	m_ui->bookmarksViewWidget->viewport()->installEventFilter(this);
66 	m_ui->bookmarksViewWidget->viewport()->setMouseTracking(true);
67 
68 	if (isSidebarPanel())
69 	{
70 		m_ui->detailsWidget->hide();
71 	}
72 
73 	connect(BookmarksManager::getModel(), &BookmarksModel::modelReset, this, &BookmarksContentsWidget::updateActions);
74 	connect(m_ui->propertiesButton, &QPushButton::clicked, this, &BookmarksContentsWidget::bookmarkProperties);
75 	connect(m_ui->deleteButton, &QPushButton::clicked, this, &BookmarksContentsWidget::removeBookmark);
76 	connect(m_ui->addButton, &QPushButton::clicked, this, &BookmarksContentsWidget::addBookmark);
77 	connect(m_ui->filterLineEditWidget, &LineEditWidget::textChanged, m_ui->bookmarksViewWidget, &ItemViewWidget::setFilterString);
78 	connect(m_ui->bookmarksViewWidget, &ItemViewWidget::doubleClicked, this, &BookmarksContentsWidget::openBookmark);
79 	connect(m_ui->bookmarksViewWidget, &ItemViewWidget::customContextMenuRequested, this, &BookmarksContentsWidget::showContextMenu);
80 	connect(m_ui->bookmarksViewWidget, &ItemViewWidget::needsActionsUpdate, this, &BookmarksContentsWidget::updateActions);
81 }
82 
~BookmarksContentsWidget()83 BookmarksContentsWidget::~BookmarksContentsWidget()
84 {
85 	delete m_ui;
86 }
87 
changeEvent(QEvent * event)88 void BookmarksContentsWidget::changeEvent(QEvent *event)
89 {
90 	ContentsWidget::changeEvent(event);
91 
92 	if (event->type() == QEvent::LanguageChange)
93 	{
94 		m_ui->retranslateUi(this);
95 	}
96 }
97 
addBookmark()98 void BookmarksContentsWidget::addBookmark()
99 {
100 	const BookmarkLocation location(getBookmarkCreationLocation());
101 	BookmarkPropertiesDialog dialog({}, {}, {}, location.folder, location.row, true, this);
102 	dialog.exec();
103 }
104 
addFolder()105 void BookmarksContentsWidget::addFolder()
106 {
107 	const BookmarkLocation location(getBookmarkCreationLocation());
108 	BookmarkPropertiesDialog dialog({}, {}, {}, location.folder, location.row, false, this);
109 	dialog.exec();
110 }
111 
addSeparator()112 void BookmarksContentsWidget::addSeparator()
113 {
114 	const BookmarkLocation location(getBookmarkCreationLocation());
115 
116 	BookmarksManager::addBookmark(BookmarksModel::SeparatorBookmark, {}, location.folder, location.row);
117 }
118 
removeBookmark()119 void BookmarksContentsWidget::removeBookmark()
120 {
121 	BookmarksManager::getModel()->trashBookmark(getBookmark(m_ui->bookmarksViewWidget->currentIndex()));
122 }
123 
openBookmark()124 void BookmarksContentsWidget::openBookmark()
125 {
126 	const BookmarksModel::Bookmark *bookmark(getBookmark(m_ui->bookmarksViewWidget->currentIndex()));
127 
128 	if (bookmark)
129 	{
130 		const QAction *action(qobject_cast<QAction*>(sender()));
131 
132 		Application::triggerAction(ActionsManager::OpenBookmarkAction, {{QLatin1String("bookmark"), bookmark->getIdentifier()}, {QLatin1String("hints"), QVariant((action ? static_cast<SessionsManager::OpenHints>(action->data().toInt()) : SessionsManager::DefaultOpen))}}, parentWidget());
133 	}
134 }
135 
bookmarkProperties()136 void BookmarksContentsWidget::bookmarkProperties()
137 {
138 	BookmarksModel::Bookmark *bookmark(getBookmark(m_ui->bookmarksViewWidget->currentIndex()));
139 
140 	if (bookmark)
141 	{
142 		BookmarkPropertiesDialog dialog(bookmark, this);
143 		dialog.exec();
144 
145 		updateActions();
146 	}
147 }
148 
showContextMenu(const QPoint & position)149 void BookmarksContentsWidget::showContextMenu(const QPoint &position)
150 {
151 	const QModelIndex index(m_ui->bookmarksViewWidget->indexAt(position));
152 	const BookmarksModel::BookmarkType type(static_cast<BookmarksModel::BookmarkType>(index.data(BookmarksModel::TypeRole).toInt()));
153 	QMenu menu(this);
154 
155 	switch (type)
156 	{
157 		case BookmarksModel::TrashBookmark:
158 			 menu.addAction(ThemesManager::createIcon(QLatin1String("trash-empty")), tr("Empty Trash"), BookmarksManager::getModel(), &BookmarksModel::emptyTrash)->setEnabled(BookmarksManager::getModel()->getTrashItem()->rowCount() > 0);
159 
160 			break;
161 		case BookmarksModel::UnknownBookmark:
162 			menu.addAction(ThemesManager::createIcon(QLatin1String("inode-directory")), tr("Add Folder…"), this, &BookmarksContentsWidget::addFolder);
163 			menu.addAction(tr("Add Bookmark…"), this, &BookmarksContentsWidget::addBookmark);
164 			menu.addAction(tr("Add Separator"), this, &BookmarksContentsWidget::addSeparator);
165 
166 			break;
167 		default:
168 			{
169 				const bool isInTrash(index.data(BookmarksModel::IsTrashedRole).toBool());
170 
171 				menu.addAction(ThemesManager::createIcon(QLatin1String("document-open")), QCoreApplication::translate("actions", "Open"), this, &BookmarksContentsWidget::openBookmark);
172 				menu.addAction(QCoreApplication::translate("actions", "Open in New Tab"), this, &BookmarksContentsWidget::openBookmark)->setData(SessionsManager::NewTabOpen);
173 				menu.addAction(QCoreApplication::translate("actions", "Open in New Background Tab"), this, &BookmarksContentsWidget::openBookmark)->setData(static_cast<int>(SessionsManager::NewTabOpen | SessionsManager::BackgroundOpen));
174 				menu.addSeparator();
175 				menu.addAction(QCoreApplication::translate("actions", "Open in New Window"), this, &BookmarksContentsWidget::openBookmark)->setData(SessionsManager::NewWindowOpen);
176 				menu.addAction(QCoreApplication::translate("actions", "Open in New Background Window"), this, &BookmarksContentsWidget::openBookmark)->setData(static_cast<int>(SessionsManager::NewWindowOpen | SessionsManager::BackgroundOpen));
177 
178 				if (type == BookmarksModel::SeparatorBookmark || (type == BookmarksModel::FolderBookmark && index.child(0, 0).data(BookmarksModel::TypeRole).toInt() == 0))
179 				{
180 					for (int i = 0; i < menu.actions().count(); ++i)
181 					{
182 						menu.actions().at(i)->setEnabled(false);
183 					}
184 				}
185 
186 				MainWindow *mainWindow(MainWindow::findMainWindow(this));
187 				ActionExecutor::Object executor(this, this);
188 				QVariantMap parameters;
189 
190 				if (type == BookmarksModel::FolderBookmark)
191 				{
192 					parameters[QLatin1String("folder")] = index.data(BookmarksModel::IdentifierRole);
193 				}
194 
195 				menu.addSeparator();
196 				menu.addAction(new Action(ActionsManager::BookmarkAllOpenPagesAction, parameters, ActionExecutor::Object(mainWindow, mainWindow), &menu));
197 				menu.addSeparator();
198 				menu.addAction(new Action(ActionsManager::CopyLinkToClipboardAction, {}, executor, &menu));
199 
200 				if (!isInTrash)
201 				{
202 					menu.addSeparator();
203 
204 					QMenu *addMenu(menu.addMenu(tr("Add Bookmark")));
205 					addMenu->addAction(ThemesManager::createIcon(QLatin1String("inode-directory")), tr("Add Folder…"), this, &BookmarksContentsWidget::addFolder);
206 					addMenu->addAction(tr("Add Bookmark…"), this, &BookmarksContentsWidget::addBookmark);
207 					addMenu->addAction(tr("Add Separator"), this, &BookmarksContentsWidget::addSeparator);
208 				}
209 
210 				if (type != BookmarksModel::RootBookmark)
211 				{
212 					menu.addSeparator();
213 
214 					if (isInTrash)
215 					{
216 						menu.addAction(tr("Restore Bookmark"), &menu, [&]()
217 						{
218 							BookmarksManager::getModel()->restoreBookmark(getBookmark(m_ui->bookmarksViewWidget->currentIndex()));
219 						});
220 					}
221 					else
222 					{
223 						menu.addAction(new Action(ActionsManager::DeleteAction, {}, executor, &menu));
224 					}
225 
226 					if (type != BookmarksModel::SeparatorBookmark)
227 					{
228 						menu.addSeparator();
229 						menu.addAction(tr("Properties…"), this, &BookmarksContentsWidget::bookmarkProperties);
230 					}
231 				}
232 			}
233 
234 			break;
235 	}
236 
237 	menu.exec(m_ui->bookmarksViewWidget->mapToGlobal(position));
238 }
239 
triggerAction(int identifier,const QVariantMap & parameters,ActionsManager::TriggerType trigger)240 void BookmarksContentsWidget::triggerAction(int identifier, const QVariantMap &parameters, ActionsManager::TriggerType trigger)
241 {
242 	switch (identifier)
243 	{
244 		case ActionsManager::DeleteAction:
245 			removeBookmark();
246 
247 			break;
248 		case ActionsManager::CopyLinkToClipboardAction:
249 			if (static_cast<BookmarksModel::BookmarkType>(m_ui->bookmarksViewWidget->currentIndex().data(BookmarksModel::TypeRole).toInt()) == BookmarksModel::UrlBookmark)
250 			{
251 				QGuiApplication::clipboard()->setText(m_ui->bookmarksViewWidget->currentIndex().data(BookmarksModel::UrlRole).toString());
252 			}
253 
254 			break;
255 		case ActionsManager::FindAction:
256 		case ActionsManager::QuickFindAction:
257 			m_ui->filterLineEditWidget->setFocus();
258 
259 			break;
260 		case ActionsManager::ActivateContentAction:
261 			m_ui->bookmarksViewWidget->setFocus();
262 
263 			break;
264 		default:
265 			ContentsWidget::triggerAction(identifier, parameters, trigger);
266 
267 			break;
268 	}
269 }
270 
updateActions()271 void BookmarksContentsWidget::updateActions()
272 {
273 	const bool hasSelecion(!m_ui->bookmarksViewWidget->selectionModel()->selectedIndexes().isEmpty());
274 	const QModelIndex index(hasSelecion ? m_ui->bookmarksViewWidget->selectionModel()->currentIndex().sibling(m_ui->bookmarksViewWidget->selectionModel()->currentIndex().row(), 0) : QModelIndex());
275 	const BookmarksModel::BookmarkType type(static_cast<BookmarksModel::BookmarkType>(index.data(BookmarksModel::TypeRole).toInt()));
276 
277 	m_ui->addressLabelWidget->setText(index.data(BookmarksModel::UrlRole).toString());
278 	m_ui->titleLabelWidget->setText(index.data(BookmarksModel::TitleRole).toString());
279 	m_ui->descriptionLabelWidget->setText(index.data(BookmarksModel::DescriptionRole).toString());
280 	m_ui->keywordLabelWidget->setText(index.data(BookmarksModel::KeywordRole).toString());
281 	m_ui->propertiesButton->setEnabled((hasSelecion && (type == BookmarksModel::FolderBookmark || type == BookmarksModel::UrlBookmark)));
282 	m_ui->deleteButton->setEnabled(hasSelecion && type != BookmarksModel::RootBookmark && type != BookmarksModel::TrashBookmark);
283 
284 	emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::EditingCategory, ActionsManager::ActionDefinition::LinkCategory});
285 }
286 
print(QPrinter * printer)287 void BookmarksContentsWidget::print(QPrinter *printer)
288 {
289 	m_ui->bookmarksViewWidget->render(printer);
290 }
291 
getBookmark(const QModelIndex & index) const292 BookmarksModel::Bookmark* BookmarksContentsWidget::getBookmark(const QModelIndex &index) const
293 {
294 	const QModelIndex mappedIndex(m_model->mapToSource(index));
295 
296 	return BookmarksManager::getModel()->getBookmark(mappedIndex.sibling(mappedIndex.row(), 0));
297 }
298 
getBookmarkCreationLocation()299 BookmarksContentsWidget::BookmarkLocation BookmarksContentsWidget::getBookmarkCreationLocation()
300 {
301 	const QModelIndex index(m_model->mapToSource(m_ui->bookmarksViewWidget->currentIndex()));
302 	BookmarksModel::Bookmark *item(BookmarksManager::getModel()->getBookmark(index));
303 	BookmarkLocation location;
304 
305 	if (!item || item == BookmarksManager::getModel()->getRootItem() || item == BookmarksManager::getModel()->getTrashItem())
306 	{
307 		location.folder = BookmarksManager::getModel()->getRootItem();
308 
309 		return location;
310 	}
311 
312 	const BookmarksModel::BookmarkType type(item->getType());
313 
314 	location.folder = ((type == BookmarksModel::RootBookmark || type == BookmarksModel::FolderBookmark) ? item : static_cast<BookmarksModel::Bookmark*>(item->parent()));
315 	location.row = ((location.folder && location.folder->index() == index) ? -1 : (index.row() + 1));
316 
317 	return location;
318 }
319 
getTitle() const320 QString BookmarksContentsWidget::getTitle() const
321 {
322 	return tr("Bookmarks");
323 }
324 
getType() const325 QLatin1String BookmarksContentsWidget::getType() const
326 {
327 	return QLatin1String("bookmarks");
328 }
329 
getUrl() const330 QUrl BookmarksContentsWidget::getUrl() const
331 {
332 	return QUrl(QLatin1String("about:bookmarks"));
333 }
334 
getIcon() const335 QIcon BookmarksContentsWidget::getIcon() const
336 {
337 	return ThemesManager::createIcon(QLatin1String("bookmarks"), false);
338 }
339 
getActionState(int identifier,const QVariantMap & parameters) const340 ActionsManager::ActionDefinition::State BookmarksContentsWidget::getActionState(int identifier, const QVariantMap &parameters) const
341 {
342 	ActionsManager::ActionDefinition::State state(ActionsManager::getActionDefinition(identifier).getDefaultState());
343 
344 	switch (identifier)
345 	{
346 		case ActionsManager::CopyLinkToClipboardAction:
347 			state.isEnabled = (static_cast<BookmarksModel::BookmarkType>(m_ui->bookmarksViewWidget->currentIndex().data(BookmarksModel::TypeRole).toInt()) == BookmarksModel::UrlBookmark);
348 
349 			return state;
350 		case ActionsManager::DeleteAction:
351 			state.text = QCoreApplication::translate("actions", "Remove Bookmark");
352 			state.isEnabled = m_ui->deleteButton->isEnabled();
353 
354 			return state;
355 		default:
356 			break;
357 	}
358 
359 	return ContentsWidget::getActionState(identifier, parameters);
360 }
361 
eventFilter(QObject * object,QEvent * event)362 bool BookmarksContentsWidget::eventFilter(QObject *object, QEvent *event)
363 {
364 	if (object == m_ui->bookmarksViewWidget && event->type() == QEvent::KeyPress)
365 	{
366 		switch (static_cast<QKeyEvent*>(event)->key())
367 		{
368 			case Qt::Key_Delete:
369 				removeBookmark();
370 
371 				return true;
372 			case Qt::Key_Enter:
373 			case Qt::Key_Return:
374 				openBookmark();
375 
376 				return true;
377 			default:
378 				break;
379 		}
380 	}
381 	else if (object == m_ui->bookmarksViewWidget->viewport() && event->type() == QEvent::MouseButtonRelease)
382 	{
383 		const QMouseEvent *mouseEvent(static_cast<QMouseEvent*>(event));
384 
385 		if ((mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() != Qt::NoModifier) || mouseEvent->button() == Qt::MiddleButton)
386 		{
387 			const BookmarksModel::Bookmark *bookmark(BookmarksManager::getModel()->getBookmark(m_ui->bookmarksViewWidget->indexAt(mouseEvent->pos())));
388 
389 			if (bookmark)
390 			{
391 				Application::triggerAction(ActionsManager::OpenBookmarkAction, {{QLatin1String("bookmark"), bookmark->getIdentifier()}, {QLatin1String("hints"), QVariant(SessionsManager::calculateOpenHints(SessionsManager::NewTabOpen, mouseEvent->button(), mouseEvent->modifiers()))}}, parentWidget());
392 
393 				return true;
394 			}
395 		}
396 	}
397 	else if (object == m_ui->bookmarksViewWidget->viewport() && event->type() == QEvent::ToolTip)
398 	{
399 		const QHelpEvent *helpEvent(static_cast<QHelpEvent*>(event));
400 		const QModelIndex index(m_ui->bookmarksViewWidget->indexAt(helpEvent->pos()));
401 		const BookmarksModel::Bookmark *bookmark(BookmarksManager::getModel()->getBookmark(index));
402 
403 		if (bookmark)
404 		{
405 			QToolTip::showText(helpEvent->globalPos(), QFontMetrics(QToolTip::font()).elidedText(bookmark->toolTip(), Qt::ElideRight, (QApplication::desktop()->screenGeometry(m_ui->bookmarksViewWidget).width() / 2)), m_ui->bookmarksViewWidget, m_ui->bookmarksViewWidget->visualRect(index));
406 		}
407 
408 		return true;
409 	}
410 
411 	return ContentsWidget::eventFilter(object, event);
412 }
413 
414 }
415