1
2#############################################################################
3##
4## Copyright (C) 2013 Riverbank Computing Limited.
5## Copyright (C) 2016 The Qt Company Ltd.
6## Contact: http://www.qt.io/licensing/
7##
8## This file is part of the Qt for Python examples of the Qt Toolkit.
9##
10## $QT_BEGIN_LICENSE:BSD$
11## You may use this file under the terms of the BSD license as follows:
12##
13## "Redistribution and use in source and binary forms, with or without
14## modification, are permitted provided that the following conditions are
15## met:
16##   * Redistributions of source code must retain the above copyright
17##     notice, this list of conditions and the following disclaimer.
18##   * Redistributions in binary form must reproduce the above copyright
19##     notice, this list of conditions and the following disclaimer in
20##     the documentation and/or other materials provided with the
21##     distribution.
22##   * Neither the name of The Qt Company Ltd nor the names of its
23##     contributors may be used to endorse or promote products derived
24##     from this software without specific prior written permission.
25##
26##
27## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
38##
39## $QT_END_LICENSE$
40##
41#############################################################################
42
43"""PySide2 port of the xml/dombookmarks example from Qt v5.x"""
44
45from PySide2 import QtCore, QtGui, QtWidgets, QtXml
46
47
48class MainWindow(QtWidgets.QMainWindow):
49    def __init__(self, parent=None):
50        super(MainWindow, self).__init__(parent)
51
52        self.xbelTree = XbelTree()
53        self.setCentralWidget(self.xbelTree)
54
55        self.createActions()
56        self.createMenus()
57
58        self.statusBar().showMessage("Ready")
59
60        self.setWindowTitle("DOM Bookmarks")
61        self.resize(480, 320)
62
63    def open(self):
64        fileName = QtWidgets.QFileDialog.getOpenFileName(self,
65                "Open Bookmark File", QtCore.QDir.currentPath(),
66                "XBEL Files (*.xbel *.xml)")[0]
67
68        if not fileName:
69            return
70
71        inFile = QtCore.QFile(fileName)
72        if not inFile.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text):
73            QtWidgets.QMessageBox.warning(self, "DOM Bookmarks",
74                    "Cannot read file %s:\n%s." % (fileName, inFile.errorString()))
75            return
76
77        if self.xbelTree.read(inFile):
78            self.statusBar().showMessage("File loaded", 2000)
79
80    def saveAs(self):
81        fileName = QtWidgets.QFileDialog.getSaveFileName(self,
82                "Save Bookmark File", QtCore.QDir.currentPath(),
83                "XBEL Files (*.xbel *.xml)")[0]
84
85        if not fileName:
86            return
87
88        outFile = QtCore.QFile(fileName)
89        if not outFile.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text):
90            QtWidgets.QMessageBox.warning(self, "DOM Bookmarks",
91                    "Cannot write file %s:\n%s." % (fileName, outFile.errorString()))
92            return
93
94        if self.xbelTree.write(outFile):
95            self.statusBar().showMessage("File saved", 2000)
96
97    def about(self):
98       QtWidgets.QMessageBox.about(self, "About DOM Bookmarks",
99            "The <b>DOM Bookmarks</b> example demonstrates how to use Qt's "
100            "DOM classes to read and write XML documents.")
101
102    def createActions(self):
103        self.openAct = QtWidgets.QAction("&Open...", self, shortcut="Ctrl+O",
104                triggered=self.open)
105
106        self.saveAsAct = QtWidgets.QAction("&Save As...", self, shortcut="Ctrl+S",
107                triggered=self.saveAs)
108
109        self.exitAct = QtWidgets.QAction("E&xit", self, shortcut="Ctrl+Q",
110                triggered=self.close)
111
112        self.aboutAct = QtWidgets.QAction("&About", self, triggered=self.about)
113
114        self.aboutQtAct = QtWidgets.QAction("About &Qt", self,
115                triggered=qApp.aboutQt)
116
117    def createMenus(self):
118        self.fileMenu = self.menuBar().addMenu("&File")
119        self.fileMenu.addAction(self.openAct)
120        self.fileMenu.addAction(self.saveAsAct)
121        self.fileMenu.addAction(self.exitAct)
122
123        self.menuBar().addSeparator()
124
125        self.helpMenu = self.menuBar().addMenu("&Help")
126        self.helpMenu.addAction(self.aboutAct)
127        self.helpMenu.addAction(self.aboutQtAct)
128
129
130class XbelTree(QtWidgets.QTreeWidget):
131    def __init__(self, parent=None):
132        super(XbelTree, self).__init__(parent)
133
134        self.header().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
135        self.setHeaderLabels(("Title", "Location"))
136
137        self.domDocument = QtXml.QDomDocument()
138
139        self.domElementForItem = {}
140
141        self.folderIcon = QtGui.QIcon()
142        self.bookmarkIcon = QtGui.QIcon()
143
144        self.folderIcon.addPixmap(self.style().standardPixmap(QtWidgets.QStyle.SP_DirClosedIcon),
145                QtGui.QIcon.Normal, QtGui.QIcon.Off)
146        self.folderIcon.addPixmap(self.style().standardPixmap(QtWidgets.QStyle.SP_DirOpenIcon),
147                QtGui.QIcon.Normal, QtGui.QIcon.On)
148        self.bookmarkIcon.addPixmap(self.style().standardPixmap(QtWidgets.QStyle.SP_FileIcon))
149
150    def read(self, device):
151        ok, errorStr, errorLine, errorColumn = self.domDocument.setContent(device, True)
152        if not ok:
153            QtWidgets.QMessageBox.information(self.window(), "DOM Bookmarks",
154                    "Parse error at line %d, column %d:\n%s" % (errorLine, errorColumn, errorStr))
155            return False
156
157        root = self.domDocument.documentElement()
158        if root.tagName() != 'xbel':
159            QtWidgets.QMessageBox.information(self.window(), "DOM Bookmarks",
160                    "The file is not an XBEL file.")
161            return False
162        elif root.hasAttribute('version') and root.attribute('version') != '1.0':
163            QtWidgets.QMessageBox.information(self.window(), "DOM Bookmarks",
164                    "The file is not an XBEL version 1.0 file.")
165            return False
166
167        self.clear()
168
169        # It might not be connected.
170        try:
171            self.itemChanged.disconnect(self.updateDomElement)
172        except:
173            pass
174
175        child = root.firstChildElement('folder')
176        while not child.isNull():
177            self.parseFolderElement(child)
178            child = child.nextSiblingElement('folder')
179
180        self.itemChanged.connect(self.updateDomElement)
181
182        return True
183
184    def write(self, device):
185        indentSize = 4
186
187        out = QtCore.QTextStream(device)
188        self.domDocument.save(out, indentSize)
189        return True
190
191    def updateDomElement(self, item, column):
192        element = self.domElementForItem.get(id(item))
193        if not element.isNull():
194            if column == 0:
195                oldTitleElement = element.firstChildElement('title')
196                newTitleElement = self.domDocument.createElement('title')
197
198                newTitleText = self.domDocument.createTextNode(item.text(0))
199                newTitleElement.appendChild(newTitleText)
200
201                element.replaceChild(newTitleElement, oldTitleElement)
202            else:
203                if element.tagName() == 'bookmark':
204                    element.setAttribute('href', item.text(1))
205
206    def parseFolderElement(self, element, parentItem=None):
207        item = self.createItem(element, parentItem)
208
209        title = element.firstChildElement('title').text()
210        if not title:
211            title = "Folder"
212
213        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
214        item.setIcon(0, self.folderIcon)
215        item.setText(0, title)
216
217        folded = (element.attribute('folded') != 'no')
218        self.setItemExpanded(item, not folded)
219
220        child = element.firstChildElement()
221        while not child.isNull():
222            if child.tagName() == 'folder':
223                self.parseFolderElement(child, item)
224            elif child.tagName() == 'bookmark':
225                childItem = self.createItem(child, item)
226
227                title = child.firstChildElement('title').text()
228                if not title:
229                    title = "Folder"
230
231                childItem.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
232                childItem.setIcon(0, self.bookmarkIcon)
233                childItem.setText(0, title)
234                childItem.setText(1, child.attribute('href'))
235            elif child.tagName() == 'separator':
236                childItem = self.createItem(child, item)
237                childItem.setFlags(item.flags() & ~(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable))
238                childItem.setText(0, 30 * "\xb7")
239
240            child = child.nextSiblingElement()
241
242    def createItem(self, element, parentItem=None):
243        item = QtWidgets.QTreeWidgetItem()
244
245        if parentItem is not None:
246            item = QtWidgets.QTreeWidgetItem(parentItem)
247        else:
248            item = QtWidgets.QTreeWidgetItem(self)
249
250        self.domElementForItem[id(item)] = element
251        return item
252
253
254if __name__ == '__main__':
255
256    import sys
257
258    app = QtWidgets.QApplication(sys.argv)
259    mainWin = MainWindow()
260    mainWin.show()
261    mainWin.open()
262    sys.exit(app.exec_())
263