--- a/eric6/UI/Browser.py Thu Feb 18 17:43:32 2021 +0100 +++ b/eric6/UI/Browser.py Thu Feb 18 17:44:53 2021 +0100 @@ -8,6 +8,7 @@ """ import os +import shutil from PyQt5.QtCore import ( pyqtSignal, pyqtSlot, Qt, QUrl, QCoreApplication, QItemSelectionModel, @@ -15,7 +16,8 @@ ) from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import ( - QTreeView, QApplication, QMenu, QAbstractItemView, QAction + QTreeView, QApplication, QMenu, QAbstractItemView, QAction, QInputDialog, + QLineEdit, QDialog ) from E5Gui.E5Application import e5App @@ -25,7 +27,8 @@ from .BrowserModel import ( BrowserModel, BrowserDirectoryItem, BrowserFileItem, BrowserClassItem, BrowserMethodItem, BrowserClassAttributeItem, BrowserImportItem, - BrowserImportsItem, BrowserSysPathItem, BrowserGlobalsItem + BrowserImportsItem, BrowserSysPathItem, BrowserGlobalsItem, + BrowserItemDirectory ) from .BrowserSortFilterProxyModel import BrowserSortFilterProxyModel @@ -213,6 +216,14 @@ self.showHiddenFilesAct.setChecked( Preferences.getUI("BrowsersListHiddenFiles")) + self.__newMenu = QMenu(QCoreApplication.translate('Browser', "New"), + self) + self.__newMenu.addAction( + QCoreApplication.translate('Browser', 'Directory'), + self._newDirectory) + self.__newMenu.addAction( + QCoreApplication.translate('Browser', 'File'), self._newFile) + # create the popup menu for source files self.sourceMenu = QMenu(self) self.sourceMenu.addAction( @@ -234,6 +245,11 @@ self._copyToClipboard) self.sourceMenu.addSeparator() self.sourceMenu.addAction(self.showHiddenFilesAct) + self.sourceMenu.addSeparator() + self.sourceMenu.addMenu(self.__newMenu) + self.sourceMenu.addAction( + QCoreApplication.translate('Browser', 'Delete'), + self._deleteFileOrDirectory) # create the popup menu for general use self.menu = QMenu(self) @@ -255,6 +271,11 @@ self._copyToClipboard) self.menu.addSeparator() self.menu.addAction(self.showHiddenFilesAct) + self.menu.addSeparator() + self.menu.addMenu(self.__newMenu) + self.menu.addAction( + QCoreApplication.translate('Browser', 'Delete'), + self._deleteFileOrDirectory) # create the menu for multiple selected files self.multiMenu = QMenu(self) @@ -262,6 +283,10 @@ QCoreApplication.translate('Browser', 'Open'), self._openItem) self.multiMenu.addSeparator() self.multiMenu.addAction(self.showHiddenFilesAct) + self.multiMenu.addSeparator() + self.multiMenu.addAction( + QCoreApplication.translate('Browser', 'Delete'), + self.__deleteMultiple) # create the directory menu self.dirMenu = QMenu(self) @@ -291,6 +316,11 @@ self._copyToClipboard) self.dirMenu.addSeparator() self.dirMenu.addAction(self.showHiddenFilesAct) + self.dirMenu.addSeparator() + self.dirMenu.addMenu(self.__newMenu) + self.dirMenu.addAction( + QCoreApplication.translate('Browser', 'Delete'), + self._deleteFileOrDirectory) # create the attribute menu self.gotoMenu = QMenu(QCoreApplication.translate('Browser', "Goto"), @@ -698,7 +728,7 @@ Public method to get the selected items. @param filterList list of classes to check against - @return list of selected items (list of BroweserItem) + @return list of selected items (list of BrowserItem) """ selectedItems = [] indexes = self.selectedIndexes() @@ -819,3 +849,202 @@ """ return isinstance( item, (BrowserDirectoryItem, BrowserFileItem, BrowserSysPathItem)) + + @pyqtSlot() + def _newDirectory(self): + """ + Protected slot to create a new directory. + """ + index = self.currentIndex() + if index.isValid(): + dname = self.model().item(index).dirName() + newName, ok = QInputDialog.getText( + self, + self.tr("New Directory"), + self.tr("Name for new directory:"), + QLineEdit.Normal) + if ok and bool(newName): + dirpath = os.path.join(dname, newName) + if os.path.exists(dirpath): + E5MessageBox.warning( + self, + self.tr("New Directory"), + self.tr("A file or directory named <b>{0}</b> exists" + " already. Aborting...") + .format(newName)) + else: + try: + os.mkdir(dirpath, mode=0o751) + except OSError as err: + E5MessageBox.critical( + self, + self.tr("New Directory"), + self.tr("<p>The directory <b>{0}</b> could not be" + " created.</p><p>Reason: {1}</p>") + .format(newName, str(err))) + + @pyqtSlot() + def _newFile(self): + """ + Protected slot to create a new file. + """ + index = self.currentIndex() + if index.isValid(): + dname = self.model().item(index).dirName() + fname, ok = QInputDialog.getText( + self, + self.tr("New File"), + self.tr("Name for new file:"), + QLineEdit.Normal) + if ok and bool(fname): + filepath = os.path.join(dname, fname) + if os.path.exists(filepath): + E5MessageBox.warning( + self, + self.tr("New File"), + self.tr("A file or directory named <b>{0}</b> exists" + " already. Aborting...") + .format(fname)) + else: + try: + f = open(filepath, "w") + f.close() + except OSError as err: + E5MessageBox.critical( + self, + self.tr("New File"), + self.tr("<p>The file <b>{0}</b> could not be" + " created.</p><p>Reason: {1}</p>") + .format(fname, str(err))) + + @pyqtSlot() + def _deleteFileOrDirectory(self): + """ + Protected slot to delete a directory or file. + """ + index = self.currentIndex() + if index.isValid(): + itm = self.model().item(index) + if itm.type() == BrowserItemDirectory: + self.__deleteDirectory(itm.dirName()) + else: + self.__deleteFile(itm.fileName()) + + def __deleteFile(self, fn): + """ + Private method to delete a file. + + @param fn filename to be deleted + @type str + """ + try: + from ThirdParty.Send2Trash.send2trash import send2trash as s2t + trashMsg = self.tr("Do you really want to move this file to the" + " trash?") + except ImportError: + s2t = os.remove + trashMsg = self.tr("Do you really want to delete this file?") + + from UI.DeleteFilesConfirmationDialog import ( + DeleteFilesConfirmationDialog + ) + dlg = DeleteFilesConfirmationDialog( + self.parent(), + self.tr("Delete File"), + trashMsg, + [fn]) + if dlg.exec() == QDialog.Accepted: + try: + s2t(fn) + except OSError as err: + E5MessageBox.critical( + self.ui, + self.tr("Delete File"), + self.tr( + "<p>The selected file <b>{0}</b> could not be" + " deleted.</p><p>Reason: {1}</p>") + .format(fn, str(err)) + ) + + def __deleteDirectory(self, dn): + """ + Private method to delete a directory. + + @param dn directory name to be removed from the project + @type str + """ + try: + from ThirdParty.Send2Trash.send2trash import send2trash + s2tAvailable = True + trashMsg = self.tr("Do you really want to move this directory to" + " the trash?") + except ImportError: + s2tAvailable = False + trashMsg = self.tr("Do you really want to delete this directory?") + + from UI.DeleteFilesConfirmationDialog import ( + DeleteFilesConfirmationDialog + ) + dlg = DeleteFilesConfirmationDialog( + self.parent(), + self.tr("Delete Directory"), + trashMsg, + [dn]) + if dlg.exec() == QDialog.Accepted: + try: + if s2tAvailable: + send2trash(dn) + else: + shutil.rmtree(dn, True) + except OSError as err: + E5MessageBox.critical( + self.ui, + self.tr("Delete Directory"), + self.tr( + "<p>The selected directory <b>{0}</b> could not be" + " deleted.</p><p>Reason: {1}</p>") + .format(dn, str(err)) + ) + + @pyqtSlot() + def __deleteMultiple(self): + """ + Private slot to delete multiple directories and files. + + Note: The context menu for multi selection is only shown for file + items. + """ + fileNames = [] + for itm in self.getSelectedItems(): + fileNames.append(itm.fileName()) + + try: + from ThirdParty.Send2Trash.send2trash import send2trash as s2t + trashMsg = self.tr("Do you really want to move these files to the" + " trash?") + except ImportError: + s2t = os.remove + trashMsg = self.tr("Do you really want to delete these files?") + + from UI.DeleteFilesConfirmationDialog import ( + DeleteFilesConfirmationDialog + ) + dlg = DeleteFilesConfirmationDialog( + self.parent(), + self.tr("Delete Files"), + trashMsg, + sorted(fileNames) + ) + if dlg.exec() == QDialog.Accepted: + for fn in fileNames: + try: + s2t(fn) + except OSError as err: + E5MessageBox.critical( + self.ui, + self.tr("Delete File"), + self.tr( + "<p>The selected file <b>{0}</b> could not be" + " deleted.</p><p>Reason: {1}</p>") + .format(fn, str(err)) + )