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 ¶meters, 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 ¶meters, 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 ¶meters) 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