1 /* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-only
5 */
6
7 // Own
8 #include "bookmark_module.h"
9 #include "bookmark_item.h"
10
11 // Qt
12 #include <QList>
13 #include <QClipboard>
14 #include <QCursor>
15 #include <QGridLayout>
16 #include <QLabel>
17 #include <QMenu>
18 #include <QIcon>
19 #include <QAction>
20
21 // KDE
22 #include <k3bookmarkdrag.h>
23 #include <kactioncollection.h>
24 #include <kapplication.h>
25 #include <kbookmark.h>
26 #include <kbookmarkmanager.h>
27 #include <kiconloader.h>
28 #include <klineedit.h>
29 #include <kmessagebox.h>
30 #include <kstandardaction.h>
31 #include <kparts/part.h>
32 #include <kinputdialog.h>
33
34 KBookmarkManager *s_bookmarkManager = 0;
35
KonqSidebarBookmarkModule(KonqSidebarTree * parentTree)36 KonqSidebarBookmarkModule::KonqSidebarBookmarkModule(KonqSidebarTree *parentTree)
37 : QObject(0L), KonqSidebarTreeModule(parentTree),
38 m_topLevelItem(0L), m_ignoreOpenChange(true)
39 {
40 if (!s_bookmarkManager) {
41 s_bookmarkManager = KBookmarkManager::userBookmarksManager();
42 }
43
44 // formats handled by K3BookmarkDrag:
45 QStringList formats;
46 formats << "text/uri-list" << "application/x-xbel" << "text/plain";
47 tree()->setDropFormats(formats);
48
49 connect(tree(), SIGNAL(moved(Q3ListViewItem*,Q3ListViewItem*,Q3ListViewItem*)),
50 this, SLOT(slotMoved(Q3ListViewItem*,Q3ListViewItem*,Q3ListViewItem*)));
51 connect(tree(), SIGNAL(dropped(K3ListView*,QDropEvent*,Q3ListViewItem*,Q3ListViewItem*)),
52 this, SLOT(slotDropped(K3ListView*,QDropEvent*,Q3ListViewItem*,Q3ListViewItem*)));
53
54 connect(tree(), SIGNAL(expanded(Q3ListViewItem*)),
55 this, SLOT(slotOpenChange(Q3ListViewItem*)));
56 connect(tree(), SIGNAL(collapsed(Q3ListViewItem*)),
57 this, SLOT(slotOpenChange(Q3ListViewItem*)));
58
59 m_collection = new KActionCollection(this);
60 QAction *action = m_collection->addAction("create_folder");
61 action->setIcon(QIcon::fromTheme("folder-new"));
62 action->setText(i18n("&Create New Folder"));
63 connect(action, SIGNAL(triggered(bool)), SLOT(slotCreateFolder()));
64 action = m_collection->addAction("delete_folder");
65 action->setIcon(QIcon::fromTheme("edit-delete"));
66 action->setText(i18n("Delete Folder"));
67 connect(action, SIGNAL(triggered(bool)), SLOT(slotDelete()));
68 action = m_collection->addAction("delete_bookmark");
69 action->setIcon(QIcon::fromTheme("edit-delete"));
70 action->setText(i18n("Delete Bookmark"));
71 connect(action, SIGNAL(triggered(bool)), SLOT(slotDelete()));
72 action = m_collection->addAction("item_properties");
73 action->setIcon(QIcon::fromTheme("document-properties"));
74 action->setText(i18n("Properties"));
75 connect(action, SIGNAL(triggered(bool)), SLOT(slotProperties()));
76 action = m_collection->addAction("open_window");
77 action->setIcon(QIcon::fromTheme("window-new"));
78 action->setText(i18n("Open in New Window"));
79 connect(action, SIGNAL(triggered(bool)), SLOT(slotOpenNewWindow()));
80 action = m_collection->addAction("open_tab");
81 action->setIcon(QIcon::fromTheme("tab-new"));
82 action->setText(i18n("Open in New Tab"));
83 connect(action, SIGNAL(triggered(bool)), SLOT(slotOpenTab()));
84 action = m_collection->addAction("folder_open_tabs");
85 action->setIcon(QIcon::fromTheme("tab-new"));
86 action->setText(i18n("Open Folder in Tabs"));
87 connect(action, SIGNAL(triggered(bool)), SLOT(slotOpenTab()));
88 action = m_collection->addAction("copy_location");
89 action->setIcon(QIcon::fromTheme("edit-copy"));
90 action->setText(i18n("Copy Link Address"));
91 connect(action, SIGNAL(triggered(bool)), SLOT(slotCopyLocation()));
92
93 m_collection->addAction("edit_bookmarks",
94 KStandardAction::editBookmarks(s_bookmarkManager,
95 SLOT(slotEditBookmarks()), this));
96
97 connect(s_bookmarkManager, SIGNAL(changed(QString,QString)),
98 SLOT(slotBookmarksChanged(QString)));
99 }
100
~KonqSidebarBookmarkModule()101 KonqSidebarBookmarkModule::~KonqSidebarBookmarkModule()
102 {
103 }
104
addTopLevelItem(KonqSidebarTreeTopLevelItem * item)105 void KonqSidebarBookmarkModule::addTopLevelItem(KonqSidebarTreeTopLevelItem *item)
106 {
107 m_ignoreOpenChange = true;
108
109 m_topLevelItem = item;
110 fillListView();
111
112 m_ignoreOpenChange = false;
113 }
114
handleTopLevelContextMenu(KonqSidebarTreeTopLevelItem *,const QPoint &)115 bool KonqSidebarBookmarkModule::handleTopLevelContextMenu(KonqSidebarTreeTopLevelItem *, const QPoint &)
116 {
117 QMenu *menu = new QMenu;
118
119 menu->addAction(m_collection->action("folder_open_tabs"));
120 menu->addSeparator();
121 menu->addAction(m_collection->action("create_folder"));
122
123 menu->addSeparator();
124 menu->addAction(m_collection->action("edit_bookmarks"));
125
126 menu->exec(QCursor::pos());
127 delete menu;
128
129 return true;
130 }
131
showPopupMenu()132 void KonqSidebarBookmarkModule::showPopupMenu()
133 {
134 KonqSidebarBookmarkItem *bi = dynamic_cast<KonqSidebarBookmarkItem *>(tree()->selectedItem());
135 if (!bi) {
136 return;
137 }
138
139 QMenu *menu = new QMenu;
140
141 if (bi->bookmark().isGroup()) {
142 menu->addAction(m_collection->action("folder_open_tabs"));
143 menu->addSeparator();
144 menu->addAction(m_collection->action("create_folder"));
145 menu->addAction(m_collection->action("item_properties"));
146 menu->addSeparator();
147 menu->addAction(m_collection->action("delete_folder"));
148 } else {
149 menu->addAction(m_collection->action("open_window"));
150 menu->addAction(m_collection->action("open_tab"));
151 menu->addAction(m_collection->action("copy_location"));
152 menu->addSeparator();
153 menu->addAction(m_collection->action("create_folder"));
154 menu->addAction(m_collection->action("item_properties"));
155 menu->addSeparator();
156 menu->addAction(m_collection->action("delete_bookmark"));
157 }
158
159 menu->exec(QCursor::pos());
160 delete menu;
161 }
162
slotMoved(Q3ListViewItem * i,Q3ListViewItem *,Q3ListViewItem * after)163 void KonqSidebarBookmarkModule::slotMoved(Q3ListViewItem *i, Q3ListViewItem *, Q3ListViewItem *after)
164 {
165 KonqSidebarBookmarkItem *item = dynamic_cast<KonqSidebarBookmarkItem *>(i);
166 if (!item) {
167 return;
168 }
169 KBookmark bookmark = item->bookmark();
170
171 KBookmark afterBookmark;
172 KonqSidebarBookmarkItem *afterItem = dynamic_cast<KonqSidebarBookmarkItem *>(after);
173 if (afterItem) {
174 afterBookmark = afterItem->bookmark();
175 }
176
177 KBookmarkGroup oldParentGroup = bookmark.parentGroup();
178 KBookmarkGroup parentGroup;
179 // try to get the parent group (assume that the QListViewItem has been reparented by K3ListView)...
180 // if anything goes wrong, use the root.
181 if (item->parent()) {
182 bool error = false;
183
184 KonqSidebarBookmarkItem *parent = dynamic_cast<KonqSidebarBookmarkItem *>((item->parent()));
185 if (!parent) {
186 error = true;
187 } else {
188 if (parent->bookmark().isGroup()) {
189 parentGroup = parent->bookmark().toGroup();
190 } else {
191 error = true;
192 }
193 }
194
195 if (error) {
196 parentGroup = s_bookmarkManager->root();
197 }
198 } else {
199 // No parent! This means the user dropped it before the top level item
200 // And K3ListView has moved the item there, we need to correct it
201 tree()->moveItem(item, m_topLevelItem, 0L);
202 parentGroup = s_bookmarkManager->root();
203 }
204
205 // remove the old reference.
206 oldParentGroup.deleteBookmark(bookmark);
207
208 // insert the new item.
209 parentGroup.moveBookmark(bookmark, afterBookmark);
210
211 // inform others about the changed groups. quite expensive, so do
212 // our best to update them in only one emitChanged call.
213 QString oldAddress = oldParentGroup.address();
214 QString newAddress = parentGroup.address();
215 if (oldAddress == newAddress) {
216 s_bookmarkManager->emitChanged(parentGroup);
217 } else {
218 int i = 0;
219 while (true) {
220 QChar c1 = oldAddress[i];
221 QChar c2 = newAddress[i];
222 if (c1 == QChar::null) {
223 // oldParentGroup is probably parent of parentGroup.
224 s_bookmarkManager->emitChanged(oldParentGroup);
225 break;
226 } else if (c2 == QChar::null) {
227 // parentGroup is probably parent of oldParentGroup.
228 s_bookmarkManager->emitChanged(parentGroup);
229 break;
230 } else {
231 if (c1 == c2) {
232 // step to the next character.
233 ++i;
234 } else {
235 // ugh... need to update both groups separately.
236 s_bookmarkManager->emitChanged(oldParentGroup);
237 s_bookmarkManager->emitChanged(parentGroup);
238 break;
239 }
240 }
241 }
242 }
243 }
244
slotDropped(K3ListView *,QDropEvent * e,Q3ListViewItem * parent,Q3ListViewItem * after)245 void KonqSidebarBookmarkModule::slotDropped(K3ListView *, QDropEvent *e, Q3ListViewItem *parent, Q3ListViewItem *after)
246 {
247 if (!KBookmark::List::canDecode(e->mimeData())) {
248 return;
249 }
250
251 KBookmark afterBookmark;
252 KonqSidebarBookmarkItem *afterItem = dynamic_cast<KonqSidebarBookmarkItem *>(after);
253 if (afterItem) {
254 afterBookmark = afterItem->bookmark();
255 }
256
257 KBookmarkGroup parentGroup;
258 // try to get the parent group...
259 if (after) {
260 parentGroup = afterBookmark.parentGroup();
261 } else if (parent) {
262 if (KonqSidebarBookmarkItem *p = dynamic_cast<KonqSidebarBookmarkItem *>(parent)) {
263 if (!p) {
264 return;
265 }
266 KBookmark bm = p->bookmark();
267 if (bm.isGroup()) {
268 parentGroup = bm.toGroup();
269 } else {
270 return;
271 }
272 } else if (parent == m_topLevelItem) {
273 parentGroup = s_bookmarkManager->root();
274 }
275 } else {
276 // it's most probably the root...
277 parentGroup = s_bookmarkManager->root();
278 }
279
280 QDomDocument parentDocument;
281 const KBookmark::List bookmarks = KBookmark::List::fromMimeData(e->mimeData(), parentDocument);
282
283 // copy
284 KBookmark::List::const_iterator it = bookmarks.constBegin();
285 for (; it != bookmarks.constEnd(); ++it) {
286 // insert new item.
287 parentGroup.moveBookmark(*it, afterBookmark);
288 }
289
290 s_bookmarkManager->emitChanged(parentGroup);
291 }
292
slotCreateFolder()293 void KonqSidebarBookmarkModule::slotCreateFolder()
294 {
295 KonqSidebarBookmarkItem *bi = dynamic_cast<KonqSidebarBookmarkItem *>(tree()->selectedItem());
296 KBookmarkGroup parentGroup;
297 if (bi) {
298 if (bi->bookmark().isGroup()) {
299 parentGroup = bi->bookmark().toGroup();
300 } else {
301 parentGroup = bi->bookmark().parentGroup();
302 }
303 } else if (tree()->selectedItem() == m_topLevelItem) {
304 parentGroup = s_bookmarkManager->root();
305 } else {
306 return;
307 }
308
309 bool ok;
310 const QString str = KInputDialog::getText(i18nc("@title:window", "Create New Bookmark Folder"),
311 i18n("New folder:"), QString(), &ok, tree());
312 if (!ok) {
313 return;
314 }
315
316 KBookmark bookmark = parentGroup.createNewFolder(str);
317 if (bi && !(bi->bookmark().isGroup())) {
318 parentGroup.moveBookmark(bookmark, bi->bookmark());
319 }
320 s_bookmarkManager->emitChanged(parentGroup);
321 }
322
slotDelete()323 void KonqSidebarBookmarkModule::slotDelete()
324 {
325 KonqSidebarBookmarkItem *bi = dynamic_cast<KonqSidebarBookmarkItem *>(tree()->selectedItem());
326 if (!bi) {
327 return;
328 }
329
330 KBookmark bookmark = bi->bookmark();
331 bool folder = bookmark.isGroup();
332
333 if (KMessageBox::warningYesNo(
334 tree(),
335 folder ? i18n("Are you sure you wish to remove the bookmark folder\n\"%1\"?", bookmark.text())
336 : i18n("Are you sure you wish to remove the bookmark\n\"%1\"?", bookmark.text()),
337 folder ? i18nc("@title:window", "Bookmark Folder Deletion")
338 : i18nc("@title:window", "Bookmark Deletion"),
339 KStandardGuiItem::del(), KStandardGuiItem::cancel())
340 != KMessageBox::Yes
341 ) {
342 return;
343 }
344
345 KBookmarkGroup parentBookmark = bookmark.parentGroup();
346 parentBookmark.deleteBookmark(bookmark);
347
348 s_bookmarkManager->emitChanged(parentBookmark);
349 }
350
makeTextNodeMod(const KBookmark & bk,const QString & m_nodename,const QString & m_newText)351 void makeTextNodeMod(const KBookmark &bk, const QString &m_nodename, const QString &m_newText)
352 {
353 QDomNode subnode = bk.internalElement().namedItem(m_nodename);
354 if (subnode.isNull()) {
355 subnode = bk.internalElement().ownerDocument().createElement(m_nodename);
356 bk.internalElement().appendChild(subnode);
357 }
358
359 if (subnode.firstChild().isNull()) {
360 QDomText domtext = subnode.ownerDocument().createTextNode("");
361 subnode.appendChild(domtext);
362 }
363
364 QDomText domtext = subnode.firstChild().toText();
365
366 QString m_oldText = domtext.data();
367 domtext.setData(m_newText);
368 }
369
slotProperties(KonqSidebarBookmarkItem * bi)370 void KonqSidebarBookmarkModule::slotProperties(KonqSidebarBookmarkItem *bi)
371 {
372 if (!bi) {
373 bi = dynamic_cast<KonqSidebarBookmarkItem *>(tree()->selectedItem());
374 if (!bi) {
375 return;
376 }
377 }
378
379 KBookmark bookmark = bi->bookmark();
380
381 QString folder = bookmark.isGroup() ? QString() : bookmark.url().pathOrUrl();
382 BookmarkEditDialog dlg(bookmark.fullText(), folder, 0, 0,
383 i18nc("@title:window", "Bookmark Properties"));
384 if (dlg.exec() != KDialog::Accepted) {
385 return;
386 }
387
388 makeTextNodeMod(bookmark, "title", dlg.finalTitle());
389 if (!dlg.finalUrl().isNull()) {
390 QUrl u(dlg.finalUrl());
391 bookmark.internalElement().setAttribute("href", u.url());
392 }
393
394 KBookmarkGroup parentBookmark = bookmark.parentGroup();
395 s_bookmarkManager->emitChanged(parentBookmark);
396 }
397
slotOpenNewWindow()398 void KonqSidebarBookmarkModule::slotOpenNewWindow()
399 {
400 KonqSidebarBookmarkItem *bi = dynamic_cast<KonqSidebarBookmarkItem *>(tree()->selectedItem());
401 if (!bi) {
402 return;
403 }
404
405 KParts::OpenUrlArguments args;
406 args.setActionRequestedByUser(true);
407 KParts::BrowserArguments browserArgs;
408 browserArgs.setForcesNewWindow(true);
409 emit tree()->createNewWindow(bi->bookmark().url(), args, browserArgs);
410 }
411
slotOpenTab()412 void KonqSidebarBookmarkModule::slotOpenTab()
413 {
414 KonqSidebarBookmarkItem *bi = dynamic_cast<KonqSidebarBookmarkItem *>(tree()->selectedItem());
415 KBookmark bookmark;
416 if (bi) {
417 bookmark = bi->bookmark();
418 } else if (tree()->selectedItem() == m_topLevelItem) {
419 bookmark = s_bookmarkManager->root();
420 } else {
421 return;
422 }
423
424 KParts::OpenUrlArguments args;
425 args.setActionRequestedByUser(true);
426 KParts::BrowserArguments browserArguments;
427 browserArguments.setNewTab(true);
428 if (bookmark.isGroup()) {
429 KBookmarkGroup group = bookmark.toGroup();
430 bookmark = group.first();
431 while (!bookmark.isNull()) {
432 if (!bookmark.isGroup() && !bookmark.isSeparator()) {
433 emit tree()->createNewWindow(bookmark.url(),
434 args,
435 browserArguments);
436 }
437 bookmark = group.next(bookmark);
438 }
439 } else {
440 emit tree()->createNewWindow(bookmark.url(),
441 args,
442 browserArguments);
443 }
444 }
445
slotCopyLocation()446 void KonqSidebarBookmarkModule::slotCopyLocation()
447 {
448 KonqSidebarBookmarkItem *bi = dynamic_cast<KonqSidebarBookmarkItem *>(tree()->selectedItem());
449 if (!bi) {
450 return;
451 }
452
453 KBookmark bookmark = bi->bookmark();
454
455 if (!bookmark.isGroup()) {
456 qApp->clipboard()->setData(K3BookmarkDrag::newDrag(bookmark, 0),
457 QClipboard::Selection);
458 qApp->clipboard()->setData(K3BookmarkDrag::newDrag(bookmark, 0),
459 QClipboard::Clipboard);
460 }
461 }
462
slotOpenChange(Q3ListViewItem * i)463 void KonqSidebarBookmarkModule::slotOpenChange(Q3ListViewItem *i)
464 {
465 if (m_ignoreOpenChange) {
466 return;
467 }
468
469 KonqSidebarBookmarkItem *bi = dynamic_cast<KonqSidebarBookmarkItem *>(i);
470 if (!bi) {
471 return;
472 }
473
474 KBookmark bookmark = bi->bookmark();
475
476 bool open = bi->isOpen();
477
478 if (!open) {
479 m_folderOpenState.remove(bookmark.address()); // no need to store closed folders...
480 } else {
481 m_folderOpenState[bookmark.address()] = open;
482 }
483 }
484
slotBookmarksChanged(const QString & groupAddress)485 void KonqSidebarBookmarkModule::slotBookmarksChanged(const QString &groupAddress)
486 {
487 m_ignoreOpenChange = true;
488
489 // update the right part of the tree
490 KBookmarkGroup group = s_bookmarkManager->findByAddress(groupAddress).toGroup();
491 KonqSidebarBookmarkItem *item = findByAddress(groupAddress);
492 Q_ASSERT(!group.isNull());
493 Q_ASSERT(item);
494 if (!group.isNull() && item) {
495 // Delete all children of item
496 Q3ListViewItem *child = item->firstChild();
497 while (child) {
498 Q3ListViewItem *next = child->nextSibling();
499 delete child;
500 child = next;
501 }
502 fillGroup(item, group);
503 }
504
505 m_ignoreOpenChange = false;
506 }
507
fillListView()508 void KonqSidebarBookmarkModule::fillListView()
509 {
510 m_ignoreOpenChange = true;
511
512 KBookmarkGroup root = s_bookmarkManager->root();
513 fillGroup(m_topLevelItem, root);
514
515 m_ignoreOpenChange = false;
516 }
517
fillGroup(KonqSidebarTreeItem * parentItem,const KBookmarkGroup & group)518 void KonqSidebarBookmarkModule::fillGroup(KonqSidebarTreeItem *parentItem, const KBookmarkGroup &group)
519 {
520 int n = 0;
521 for (KBookmark bk = group.first(); !bk.isNull(); bk = group.next(bk), ++n) {
522 KonqSidebarBookmarkItem *item = new KonqSidebarBookmarkItem(parentItem, m_topLevelItem, bk, n);
523 if (bk.isGroup()) {
524 KBookmarkGroup grp = bk.toGroup();
525 fillGroup(item, grp);
526
527 QString address(grp.address());
528 if (m_folderOpenState.contains(address)) {
529 item->setOpen(m_folderOpenState[address]);
530 } else {
531 item->setOpen(false);
532 }
533 } else if (bk.isSeparator()) {
534 item->setVisible(false); // ### this probably causes bug #78257
535 } else {
536 item->setExpandable(false);
537 }
538 }
539 }
540
541 // Borrowed from KEditBookmarks
findByAddress(const QString & address) const542 KonqSidebarBookmarkItem *KonqSidebarBookmarkModule::findByAddress(const QString &address) const
543 {
544 Q3ListViewItem *item = m_topLevelItem;
545 // The address is something like /5/10/2
546 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
547 const QStringList addresses = address.split('/', QString::SkipEmptyParts);
548 #else
549 const QStringList addresses = address.split('/', Qt::SkipEmptyParts);
550 #endif
551 for (QStringList::const_iterator it = addresses.constBegin(); it != addresses.constEnd(); ++it) {
552 uint number = (*it).toUInt();
553 item = item->firstChild();
554 for (uint i = 0; i < number; ++i) {
555 item = item->nextSibling();
556 }
557 }
558 Q_ASSERT(item);
559 return static_cast<KonqSidebarBookmarkItem *>(item);
560 }
561
562 // Borrowed&modified from KBookmarkMenu...
BookmarkEditDialog(const QString & title,const QString & url,QWidget * parent,const char * name,const QString & caption)563 BookmarkEditDialog::BookmarkEditDialog(const QString &title, const QString &url,
564 QWidget *parent, const char *name, const QString &caption)
565 : KDialog(parent),
566 m_title(0), m_location(0)
567 {
568 setObjectName(name);
569 setModal(true);
570 setCaption(caption);
571 setButtons(Ok | Cancel);
572
573 setButtonText(Ok, i18n("&Update"));
574
575 QWidget *main = new QWidget(this);
576 setMainWidget(main);
577
578 bool folder = url.isNull();
579 QGridLayout *grid = new QGridLayout(main);
580
581 QLabel *nameLabel = new QLabel(i18n("Name:"), main);
582 nameLabel->setObjectName(QLatin1String("title label"));
583 grid->addWidget(nameLabel, 0, 0);
584 m_title = new KLineEdit(main);
585 m_title->setText(title);
586 nameLabel->setBuddy(m_title);
587 grid->addWidget(m_title, 0, 1);
588 if (!folder) {
589 QLabel *locationLabel = new QLabel(i18n("Location:"), main);
590 locationLabel->setObjectName(QLatin1String("location label"));
591 grid->addWidget(locationLabel, 1, 0);
592 m_location = new KLineEdit(main);
593 m_location->setText(url);
594 locationLabel->setBuddy(m_location);
595 grid->addWidget(m_location, 1, 1);
596 }
597 main->setMinimumSize(300, 0);
598 }
599
finalUrl() const600 QString BookmarkEditDialog::finalUrl() const
601 {
602 if (m_location != 0) {
603 return m_location->text();
604 } else {
605 return QString();
606 }
607 }
608
finalTitle() const609 QString BookmarkEditDialog::finalTitle() const
610 {
611 return m_title->text();
612 }
613
614 extern "C"
615 {
create_konq_sidebartree_bookmarks(KonqSidebarTree * par,const bool)616 KDE_EXPORT KonqSidebarTreeModule *create_konq_sidebartree_bookmarks(KonqSidebarTree *par, const bool)
617 {
618 return new KonqSidebarBookmarkModule(par);
619 }
620 }
621
622