1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include <QtWidgets>
52
53#include "xbeltree.h"
54
55enum { DomElementRole = Qt::UserRole + 1 };
56
57Q_DECLARE_METATYPE(QDomElement)
58
59static inline QString titleElement() { return QStringLiteral("title"); }
60static inline QString folderElement() { return QStringLiteral("folder"); }
61static inline QString bookmarkElement() { return QStringLiteral("bookmark"); }
62
63static inline QString versionAttribute() { return QStringLiteral("version"); }
64static inline QString hrefAttribute() { return QStringLiteral("href"); }
65static inline QString foldedAttribute() { return QStringLiteral("folded"); }
66
67XbelTree::XbelTree(QWidget *parent)
68 : QTreeWidget(parent)
69{
70 QStringList labels;
71 labels << tr("Title") << tr("Location");
72
73 header()->setSectionResizeMode(QHeaderView::Stretch);
74 setHeaderLabels(labels);
75
76 folderIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirClosedIcon),
77 QIcon::Normal, QIcon::Off);
78 folderIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirOpenIcon),
79 QIcon::Normal, QIcon::On);
80 bookmarkIcon.addPixmap(style()->standardPixmap(QStyle::SP_FileIcon));
81}
82
83#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
84void XbelTree::contextMenuEvent(QContextMenuEvent *event)
85{
86 const QTreeWidgetItem *item = itemAt(event->pos());
87 if (!item)
88 return;
89 const QString url = item->text(1);
90 QMenu contextMenu;
91 QAction *copyAction = contextMenu.addAction(tr("Copy Link to Clipboard"));
92 QAction *openAction = contextMenu.addAction(tr("Open"));
93 QAction *action = contextMenu.exec(event->globalPos());
94 if (action == copyAction)
95 QGuiApplication::clipboard()->setText(url);
96 else if (action == openAction)
97 QDesktopServices::openUrl(QUrl(url));
98}
99#endif // !QT_NO_CONTEXTMENU && !QT_NO_CLIPBOARD
100
101bool XbelTree::read(QIODevice *device)
102{
103 QString errorStr;
104 int errorLine;
105 int errorColumn;
106
107 if (!domDocument.setContent(device, true, &errorStr, &errorLine,
108 &errorColumn)) {
109 QMessageBox::information(window(), tr("DOM Bookmarks"),
110 tr("Parse error at line %1, column %2:\n%3")
111 .arg(errorLine)
112 .arg(errorColumn)
113 .arg(errorStr));
114 return false;
115 }
116
117 QDomElement root = domDocument.documentElement();
118 if (root.tagName() != "xbel") {
119 QMessageBox::information(window(), tr("DOM Bookmarks"),
120 tr("The file is not an XBEL file."));
121 return false;
122 } else if (root.hasAttribute(versionAttribute())
123 && root.attribute(versionAttribute()) != QLatin1String("1.0")) {
124 QMessageBox::information(window(), tr("DOM Bookmarks"),
125 tr("The file is not an XBEL version 1.0 "
126 "file."));
127 return false;
128 }
129
130 clear();
131
132 disconnect(this, &QTreeWidget::itemChanged, this, &XbelTree::updateDomElement);
133
134 QDomElement child = root.firstChildElement(folderElement());
135 while (!child.isNull()) {
136 parseFolderElement(child);
137 child = child.nextSiblingElement(folderElement());
138 }
139
140 connect(this, &QTreeWidget::itemChanged, this, &XbelTree::updateDomElement);
141
142 return true;
143}
144
145bool XbelTree::write(QIODevice *device) const
146{
147 const int IndentSize = 4;
148
149 QTextStream out(device);
150 domDocument.save(out, IndentSize);
151 return true;
152}
153
154void XbelTree::updateDomElement(const QTreeWidgetItem *item, int column)
155{
156 QDomElement element = qvariant_cast<QDomElement>(item->data(0, DomElementRole));
157 if (!element.isNull()) {
158 if (column == 0) {
159 QDomElement oldTitleElement = element.firstChildElement(titleElement());
160 QDomElement newTitleElement = domDocument.createElement(titleElement());
161
162 QDomText newTitleText = domDocument.createTextNode(item->text(0));
163 newTitleElement.appendChild(newTitleText);
164
165 element.replaceChild(newTitleElement, oldTitleElement);
166 } else {
167 if (element.tagName() == bookmarkElement())
168 element.setAttribute(hrefAttribute(), item->text(1));
169 }
170 }
171}
172
173void XbelTree::parseFolderElement(const QDomElement &element,
174 QTreeWidgetItem *parentItem)
175{
176 QTreeWidgetItem *item = createItem(element, parentItem);
177
178 QString title = element.firstChildElement(titleElement()).text();
179 if (title.isEmpty())
180 title = QObject::tr("Folder");
181
182 item->setFlags(item->flags() | Qt::ItemIsEditable);
183 item->setIcon(0, folderIcon);
184 item->setText(0, title);
185
186 bool folded = (element.attribute(foldedAttribute()) != QLatin1String("no"));
187 item->setExpanded(!folded);
188
189 QDomElement child = element.firstChildElement();
190 while (!child.isNull()) {
191 if (child.tagName() == folderElement()) {
192 parseFolderElement(child, item);
193 } else if (child.tagName() == bookmarkElement()) {
194 QTreeWidgetItem *childItem = createItem(child, item);
195
196 QString title = child.firstChildElement(titleElement()).text();
197 if (title.isEmpty())
198 title = QObject::tr("Folder");
199
200 childItem->setFlags(item->flags() | Qt::ItemIsEditable);
201 childItem->setIcon(0, bookmarkIcon);
202 childItem->setText(0, title);
203 childItem->setText(1, child.attribute(hrefAttribute()));
204 } else if (child.tagName() == QLatin1String("separator")) {
205 QTreeWidgetItem *childItem = createItem(child, item);
206 childItem->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEditable));
207 childItem->setText(0, QString(30, u'\xB7'));
208 }
209 child = child.nextSiblingElement();
210 }
211}
212
213QTreeWidgetItem *XbelTree::createItem(const QDomElement &element,
214 QTreeWidgetItem *parentItem)
215{
216 QTreeWidgetItem *item;
217 if (parentItem) {
218 item = new QTreeWidgetItem(parentItem);
219 } else {
220 item = new QTreeWidgetItem(this);
221 }
222 item->setData(0, DomElementRole, QVariant::fromValue(element));
223 return item;
224}
225