1 /* This file is part of the KDE project
2     SPDX-FileCopyrightText: 2009 Pino Toscano <pino@kde.org>
3     SPDX-FileCopyrightText: 2009 Daivd Faure <faure@kde.org>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "konqhistoryview.h"
9 #include "konqhistory.h"
10 #include "konq_historyprovider.h"
11 #include "konqhistorymodel.h"
12 #include "konqhistoryproxymodel.h"
13 #include "konqhistorysettings.h"
14 
15 #include <KDialogJobUiDelegate>
16 #include <QAction>
17 #include <QApplication>
18 #include <QClipboard>
19 #include <QIcon>
20 #include <QLineEdit>
21 #include <QMenu>
22 #include <QMimeData>
23 #include <QTimer>
24 #include <QTreeView>
25 #include <QVBoxLayout>
26 
27 #include <kactioncollection.h>
28 #include "konqdebug.h"
29 #include <KLocalizedString>
30 #include <kmessagebox.h>
31 
32 #include <KIO/CommandLauncherJob>
33 
KonqHistoryView(QWidget * parent)34 KonqHistoryView::KonqHistoryView(QWidget *parent)
35     : QWidget(parent)
36     , m_searchTimer(nullptr)
37 {
38     m_treeView = new QTreeView(this);
39     m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
40     m_treeView->setHeaderHidden(true);
41 
42     m_historyProxyModel = new KonqHistoryProxyModel(KonqHistorySettings::self(), m_treeView);
43     connect(m_treeView, &QTreeView::customContextMenuRequested, this, &KonqHistoryView::slotContextMenu);
44     m_historyModel = new KonqHistoryModel(m_historyProxyModel);
45     m_treeView->setModel(m_historyProxyModel);
46     m_historyProxyModel->setSourceModel(m_historyModel);
47     m_historyProxyModel->sort(0);
48 
49     m_collection = new KActionCollection(this);
50     m_collection->addAssociatedWidget(m_treeView); // make shortcuts work
51     QAction *action = m_collection->addAction(QStringLiteral("open_new"));
52     action->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
53     action->setText(i18n("Open in New &Window"));
54     connect(action, &QAction::triggered, this, &KonqHistoryView::slotNewWindow);
55 
56     action = m_collection->addAction(QStringLiteral("open_tab"));
57     action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
58     action->setText(i18n("Open in New Tab"));
59     connect(action, &QAction::triggered, this, &KonqHistoryView::slotNewTab);
60 
61     action = m_collection->addAction(QStringLiteral("open_current_tab"));
62     action->setIcon(QIcon::fromTheme(QStringLiteral("window")));
63     action->setText(i18n("Open in Current Tab"));
64     connect(action, &QAction::triggered, this, &KonqHistoryView::slotCurrentTab);
65 
66     action = m_collection->addAction(QStringLiteral("copylinklocation"));
67     action->setText(i18n("&Copy Link Address"));
68     connect(action, &QAction::triggered, this, &KonqHistoryView::slotCopyLinkLocation);
69 
70     action = m_collection->addAction(QStringLiteral("remove"));
71     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
72     action->setText(i18n("&Remove Entry"));
73     m_collection->setDefaultShortcut(action, QKeySequence(Qt::Key_Delete)); // #135966
74     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
75     connect(action, &QAction::triggered, this, &KonqHistoryView::slotRemoveEntry);
76 
77     action = m_collection->addAction(QStringLiteral("clear"));
78     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
79     action->setText(i18n("C&lear History"));
80     connect(action, &QAction::triggered, this, &KonqHistoryView::slotClearHistory);
81 
82     action = m_collection->addAction(QStringLiteral("preferences"));
83     action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
84     action->setText(i18n("&Preferences..."));
85     connect(action, &QAction::triggered, this, &KonqHistoryView::slotPreferences);
86 
87     QActionGroup *sortGroup = new QActionGroup(this);
88     sortGroup->setExclusive(true);
89 
90     action = m_collection->addAction(QStringLiteral("byName"));
91     action->setText(i18n("By &Name"));
92     action->setCheckable(true);
93     action->setData(QVariant::fromValue(0));
94     sortGroup->addAction(action);
95 
96     action = m_collection->addAction(QStringLiteral("byDate"));
97     action->setText(i18n("By &Date"));
98     action->setCheckable(true);
99     action->setData(QVariant::fromValue(1));
100     sortGroup->addAction(action);
101 
102     KonqHistorySettings *settings = KonqHistorySettings::self();
103     sortGroup->actions().at(settings->m_sortsByName ? 0 : 1)->setChecked(true);
104     connect(sortGroup, &QActionGroup::triggered, this, &KonqHistoryView::slotSortChange);
105 
106     m_searchLineEdit = new QLineEdit(this);
107     m_searchLineEdit->setPlaceholderText(i18n("Search in history"));
108     m_searchLineEdit->setClearButtonEnabled(true);
109 
110     connect(m_searchLineEdit, &QLineEdit::textChanged, this, &KonqHistoryView::slotFilterTextChanged);
111 
112     QVBoxLayout *mainLayout = new QVBoxLayout(this);
113     mainLayout->setContentsMargins(0, 0, 0, 0);
114     mainLayout->addWidget(m_searchLineEdit);
115     mainLayout->addWidget(m_treeView);
116 }
117 
slotContextMenu(const QPoint & pos)118 void KonqHistoryView::slotContextMenu(const QPoint &pos)
119 {
120     const QModelIndex index = m_treeView->indexAt(pos);
121     if (!index.isValid()) {
122         return;
123     }
124 
125     const int nodeType = index.data(KonqHistory::TypeRole).toInt();
126 
127     QMenu *menu = new QMenu(this);
128 
129     if (nodeType == KonqHistory::HistoryType) {
130         menu->addAction(m_collection->action(QStringLiteral("open_new")));
131         menu->addAction(m_collection->action(QStringLiteral("open_tab")));
132         menu->addAction(m_collection->action(QStringLiteral("open_current_tab")));
133         menu->addAction(m_collection->action(QStringLiteral("copylinklocation")));
134         menu->addSeparator();
135     }
136 
137     menu->addAction(m_collection->action(QStringLiteral("remove")));
138     menu->addAction(m_collection->action(QStringLiteral("clear")));
139     menu->addSeparator();
140     QMenu *sortMenu = menu->addMenu(i18nc("@action:inmenu Parent of 'By Name' and 'By Date'", "Sort"));
141     sortMenu->addAction(m_collection->action(QStringLiteral("byName")));
142     sortMenu->addAction(m_collection->action(QStringLiteral("byDate")));
143     menu->addSeparator();
144     menu->addAction(m_collection->action(QStringLiteral("preferences")));
145 
146     menu->exec(m_treeView->viewport()->mapToGlobal(pos));
147 
148     delete menu;
149 }
150 
slotRemoveEntry()151 void KonqHistoryView::slotRemoveEntry()
152 {
153     const QModelIndex index = m_treeView->currentIndex();
154     if (!index.isValid()) {
155         return;
156     }
157 
158     // TODO undo/redo support
159     m_historyModel->deleteItem(m_historyProxyModel->mapToSource(index));
160 }
161 
slotClearHistory()162 void KonqHistoryView::slotClearHistory()
163 {
164     KGuiItem guiitem = KStandardGuiItem::clear();
165     guiitem.setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
166 
167     if (KMessageBox::warningContinueCancel(this,
168                                            i18n("Do you really want to clear the entire history?"),
169                                            i18nc("@title:window", "Clear History?"), guiitem)
170             == KMessageBox::Continue) {
171         KonqHistoryProvider::self()->emitClear();
172     }
173 }
174 
slotPreferences()175 void KonqHistoryView::slotPreferences()
176 {
177     // Run the history sidebar settings.
178     KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5 kcmhistory"));
179     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
180     job->start();
181 }
182 
slotSortChange(QAction * action)183 void KonqHistoryView::slotSortChange(QAction *action)
184 {
185     if (!action) {
186         return;
187     }
188 
189     const int which = action->data().toInt();
190     KonqHistorySettings *settings = KonqHistorySettings::self();
191     settings->m_sortsByName = (which == 0);
192     settings->applySettings();
193 }
194 
slotFilterTextChanged(const QString & text)195 void KonqHistoryView::slotFilterTextChanged(const QString &text)
196 {
197     Q_UNUSED(text);
198     if (!m_searchTimer) {
199         m_searchTimer = new QTimer(this);
200         m_searchTimer->setSingleShot(true);
201         connect(m_searchTimer, &QTimer::timeout, this, &KonqHistoryView::slotTimerTimeout);
202     }
203     m_searchTimer->start(600);
204 }
205 
slotTimerTimeout()206 void KonqHistoryView::slotTimerTimeout()
207 {
208     m_historyProxyModel->setFilterFixedString(m_searchLineEdit->text());
209 }
210 
treeView() const211 QTreeView *KonqHistoryView::treeView() const
212 {
213     return m_treeView;
214 }
215 
lineEdit() const216 QLineEdit *KonqHistoryView::lineEdit() const
217 {
218     return m_searchLineEdit;
219 }
220 
slotNewWindow()221 void KonqHistoryView::slotNewWindow()
222 {
223     const QUrl url = urlForIndex(m_treeView->currentIndex());
224     if (url.isValid()) {
225         emit openUrlInNewWindow(url);
226     }
227 }
228 
slotNewTab()229 void KonqHistoryView::slotNewTab()
230 {
231     const QUrl url = urlForIndex(m_treeView->currentIndex());
232     if (url.isValid()) {
233         emit openUrlInNewTab(url);
234     }
235 }
236 
slotCurrentTab()237 void KonqHistoryView::slotCurrentTab()
238 {
239     const QUrl url = urlForIndex(m_treeView->currentIndex());
240     if (url.isValid()) {
241         emit openUrlInCurrentTab(url);
242     }
243 }
244 
urlForIndex(const QModelIndex & index) const245 QUrl KonqHistoryView::urlForIndex(const QModelIndex &index) const
246 {
247     if (!index.isValid() || (index.data(KonqHistory::TypeRole).toInt() != KonqHistory::HistoryType)) {
248         return QUrl();
249     }
250 
251     return index.data(KonqHistory::UrlRole).toUrl();
252 }
253 
254 // Code taken from KHTMLPopupGUIClient::slotCopyLinkLocation
slotCopyLinkLocation()255 void KonqHistoryView::slotCopyLinkLocation()
256 {
257     QUrl safeURL = urlForIndex(m_treeView->currentIndex()).adjusted(QUrl::RemovePassword);
258 
259     // Set it in both the mouse selection and in the clipboard
260     QMimeData *mimeData = new QMimeData;
261     mimeData->setUrls(QList<QUrl>() << safeURL);
262     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
263     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Selection);
264 }
265