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
55 enum { DomElementRole = Qt::UserRole + 1 };
56
Q_DECLARE_METATYPE(QDomElement)57 Q_DECLARE_METATYPE(QDomElement)
58
59 static inline QString titleElement() { return QStringLiteral("title"); }
folderElement()60 static inline QString folderElement() { return QStringLiteral("folder"); }
bookmarkElement()61 static inline QString bookmarkElement() { return QStringLiteral("bookmark"); }
62
versionAttribute()63 static inline QString versionAttribute() { return QStringLiteral("version"); }
hrefAttribute()64 static inline QString hrefAttribute() { return QStringLiteral("href"); }
foldedAttribute()65 static inline QString foldedAttribute() { return QStringLiteral("folded"); }
66
XbelTree(QWidget * parent)67 XbelTree::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)
contextMenuEvent(QContextMenuEvent * event)84 void 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
read(QIODevice * device)101 bool 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
write(QIODevice * device) const145 bool 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
updateDomElement(const QTreeWidgetItem * item,int column)154 void 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
parseFolderElement(const QDomElement & element,QTreeWidgetItem * parentItem)173 void 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, 0xB7));
208 }
209 child = child.nextSiblingElement();
210 }
211 }
212
createItem(const QDomElement & element,QTreeWidgetItem * parentItem)213 QTreeWidgetItem *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