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