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