6 """ |
6 """ |
7 Module implementing a browser with class browsing capabilities. |
7 Module implementing a browser with class browsing capabilities. |
8 """ |
8 """ |
9 |
9 |
10 import os |
10 import os |
|
11 import shutil |
11 |
12 |
12 from PyQt5.QtCore import ( |
13 from PyQt5.QtCore import ( |
13 pyqtSignal, pyqtSlot, Qt, QUrl, QCoreApplication, QItemSelectionModel, |
14 pyqtSignal, pyqtSlot, Qt, QUrl, QCoreApplication, QItemSelectionModel, |
14 QModelIndex, QElapsedTimer |
15 QModelIndex, QElapsedTimer |
15 ) |
16 ) |
16 from PyQt5.QtGui import QDesktopServices |
17 from PyQt5.QtGui import QDesktopServices |
17 from PyQt5.QtWidgets import ( |
18 from PyQt5.QtWidgets import ( |
18 QTreeView, QApplication, QMenu, QAbstractItemView, QAction |
19 QTreeView, QApplication, QMenu, QAbstractItemView, QAction, QInputDialog, |
|
20 QLineEdit, QDialog |
19 ) |
21 ) |
20 |
22 |
21 from E5Gui.E5Application import e5App |
23 from E5Gui.E5Application import e5App |
22 from E5Gui import E5FileDialog, E5MessageBox |
24 from E5Gui import E5FileDialog, E5MessageBox |
23 |
25 |
24 from Project.ProjectBrowserModel import ProjectBrowserSimpleDirectoryItem |
26 from Project.ProjectBrowserModel import ProjectBrowserSimpleDirectoryItem |
25 from .BrowserModel import ( |
27 from .BrowserModel import ( |
26 BrowserModel, BrowserDirectoryItem, BrowserFileItem, BrowserClassItem, |
28 BrowserModel, BrowserDirectoryItem, BrowserFileItem, BrowserClassItem, |
27 BrowserMethodItem, BrowserClassAttributeItem, BrowserImportItem, |
29 BrowserMethodItem, BrowserClassAttributeItem, BrowserImportItem, |
28 BrowserImportsItem, BrowserSysPathItem, BrowserGlobalsItem |
30 BrowserImportsItem, BrowserSysPathItem, BrowserGlobalsItem, |
|
31 BrowserItemDirectory |
29 ) |
32 ) |
30 from .BrowserSortFilterProxyModel import BrowserSortFilterProxyModel |
33 from .BrowserSortFilterProxyModel import BrowserSortFilterProxyModel |
31 |
34 |
32 import UI.PixmapCache |
35 import UI.PixmapCache |
33 import Preferences |
36 import Preferences |
211 self.showHiddenFilesAct.setCheckable(True) |
214 self.showHiddenFilesAct.setCheckable(True) |
212 self.showHiddenFilesAct.toggled.connect(self._showHidden) |
215 self.showHiddenFilesAct.toggled.connect(self._showHidden) |
213 self.showHiddenFilesAct.setChecked( |
216 self.showHiddenFilesAct.setChecked( |
214 Preferences.getUI("BrowsersListHiddenFiles")) |
217 Preferences.getUI("BrowsersListHiddenFiles")) |
215 |
218 |
|
219 self.__newMenu = QMenu(QCoreApplication.translate('Browser', "New"), |
|
220 self) |
|
221 self.__newMenu.addAction( |
|
222 QCoreApplication.translate('Browser', 'Directory'), |
|
223 self._newDirectory) |
|
224 self.__newMenu.addAction( |
|
225 QCoreApplication.translate('Browser', 'File'), self._newFile) |
|
226 |
216 # create the popup menu for source files |
227 # create the popup menu for source files |
217 self.sourceMenu = QMenu(self) |
228 self.sourceMenu = QMenu(self) |
218 self.sourceMenu.addAction( |
229 self.sourceMenu.addAction( |
219 QCoreApplication.translate('Browser', 'Open'), self._openItem) |
230 QCoreApplication.translate('Browser', 'Open'), self._openItem) |
220 self.unittestAct = self.sourceMenu.addAction( |
231 self.unittestAct = self.sourceMenu.addAction( |
232 self.sourceMenu.addAction( |
243 self.sourceMenu.addAction( |
233 QCoreApplication.translate('Browser', 'Copy Path to Clipboard'), |
244 QCoreApplication.translate('Browser', 'Copy Path to Clipboard'), |
234 self._copyToClipboard) |
245 self._copyToClipboard) |
235 self.sourceMenu.addSeparator() |
246 self.sourceMenu.addSeparator() |
236 self.sourceMenu.addAction(self.showHiddenFilesAct) |
247 self.sourceMenu.addAction(self.showHiddenFilesAct) |
|
248 self.sourceMenu.addSeparator() |
|
249 self.sourceMenu.addMenu(self.__newMenu) |
|
250 self.sourceMenu.addAction( |
|
251 QCoreApplication.translate('Browser', 'Delete'), |
|
252 self._deleteFileOrDirectory) |
237 |
253 |
238 # create the popup menu for general use |
254 # create the popup menu for general use |
239 self.menu = QMenu(self) |
255 self.menu = QMenu(self) |
240 self.menu.addAction( |
256 self.menu.addAction( |
241 QCoreApplication.translate('Browser', 'Open'), self._openItem) |
257 QCoreApplication.translate('Browser', 'Open'), self._openItem) |
253 self.menu.addAction( |
269 self.menu.addAction( |
254 QCoreApplication.translate('Browser', 'Copy Path to Clipboard'), |
270 QCoreApplication.translate('Browser', 'Copy Path to Clipboard'), |
255 self._copyToClipboard) |
271 self._copyToClipboard) |
256 self.menu.addSeparator() |
272 self.menu.addSeparator() |
257 self.menu.addAction(self.showHiddenFilesAct) |
273 self.menu.addAction(self.showHiddenFilesAct) |
|
274 self.menu.addSeparator() |
|
275 self.menu.addMenu(self.__newMenu) |
|
276 self.menu.addAction( |
|
277 QCoreApplication.translate('Browser', 'Delete'), |
|
278 self._deleteFileOrDirectory) |
258 |
279 |
259 # create the menu for multiple selected files |
280 # create the menu for multiple selected files |
260 self.multiMenu = QMenu(self) |
281 self.multiMenu = QMenu(self) |
261 self.multiMenu.addAction( |
282 self.multiMenu.addAction( |
262 QCoreApplication.translate('Browser', 'Open'), self._openItem) |
283 QCoreApplication.translate('Browser', 'Open'), self._openItem) |
263 self.multiMenu.addSeparator() |
284 self.multiMenu.addSeparator() |
264 self.multiMenu.addAction(self.showHiddenFilesAct) |
285 self.multiMenu.addAction(self.showHiddenFilesAct) |
|
286 self.multiMenu.addSeparator() |
|
287 self.multiMenu.addAction( |
|
288 QCoreApplication.translate('Browser', 'Delete'), |
|
289 self.__deleteMultiple) |
265 |
290 |
266 # create the directory menu |
291 # create the directory menu |
267 self.dirMenu = QMenu(self) |
292 self.dirMenu = QMenu(self) |
268 self.dirMenu.addAction( |
293 self.dirMenu.addAction( |
269 QCoreApplication.translate('Browser', 'New toplevel directory...'), |
294 QCoreApplication.translate('Browser', 'New toplevel directory...'), |
289 self.dirMenu.addAction( |
314 self.dirMenu.addAction( |
290 QCoreApplication.translate('Browser', 'Copy Path to Clipboard'), |
315 QCoreApplication.translate('Browser', 'Copy Path to Clipboard'), |
291 self._copyToClipboard) |
316 self._copyToClipboard) |
292 self.dirMenu.addSeparator() |
317 self.dirMenu.addSeparator() |
293 self.dirMenu.addAction(self.showHiddenFilesAct) |
318 self.dirMenu.addAction(self.showHiddenFilesAct) |
|
319 self.dirMenu.addSeparator() |
|
320 self.dirMenu.addMenu(self.__newMenu) |
|
321 self.dirMenu.addAction( |
|
322 QCoreApplication.translate('Browser', 'Delete'), |
|
323 self._deleteFileOrDirectory) |
294 |
324 |
295 # create the attribute menu |
325 # create the attribute menu |
296 self.gotoMenu = QMenu(QCoreApplication.translate('Browser', "Goto"), |
326 self.gotoMenu = QMenu(QCoreApplication.translate('Browser', "Goto"), |
297 self) |
327 self) |
298 self.gotoMenu.aboutToShow.connect(self._showGotoMenu) |
328 self.gotoMenu.aboutToShow.connect(self._showGotoMenu) |
817 @return flag indicating a correct type |
847 @return flag indicating a correct type |
818 @rtype bool |
848 @rtype bool |
819 """ |
849 """ |
820 return isinstance( |
850 return isinstance( |
821 item, (BrowserDirectoryItem, BrowserFileItem, BrowserSysPathItem)) |
851 item, (BrowserDirectoryItem, BrowserFileItem, BrowserSysPathItem)) |
|
852 |
|
853 @pyqtSlot() |
|
854 def _newDirectory(self): |
|
855 """ |
|
856 Protected slot to create a new directory. |
|
857 """ |
|
858 index = self.currentIndex() |
|
859 if index.isValid(): |
|
860 dname = self.model().item(index).dirName() |
|
861 newName, ok = QInputDialog.getText( |
|
862 self, |
|
863 self.tr("New Directory"), |
|
864 self.tr("Name for new directory:"), |
|
865 QLineEdit.Normal) |
|
866 if ok and bool(newName): |
|
867 dirpath = os.path.join(dname, newName) |
|
868 if os.path.exists(dirpath): |
|
869 E5MessageBox.warning( |
|
870 self, |
|
871 self.tr("New Directory"), |
|
872 self.tr("A file or directory named <b>{0}</b> exists" |
|
873 " already. Aborting...") |
|
874 .format(newName)) |
|
875 else: |
|
876 try: |
|
877 os.mkdir(dirpath, mode=0o751) |
|
878 except OSError as err: |
|
879 E5MessageBox.critical( |
|
880 self, |
|
881 self.tr("New Directory"), |
|
882 self.tr("<p>The directory <b>{0}</b> could not be" |
|
883 " created.</p><p>Reason: {1}</p>") |
|
884 .format(newName, str(err))) |
|
885 |
|
886 @pyqtSlot() |
|
887 def _newFile(self): |
|
888 """ |
|
889 Protected slot to create a new file. |
|
890 """ |
|
891 index = self.currentIndex() |
|
892 if index.isValid(): |
|
893 dname = self.model().item(index).dirName() |
|
894 fname, ok = QInputDialog.getText( |
|
895 self, |
|
896 self.tr("New File"), |
|
897 self.tr("Name for new file:"), |
|
898 QLineEdit.Normal) |
|
899 if ok and bool(fname): |
|
900 filepath = os.path.join(dname, fname) |
|
901 if os.path.exists(filepath): |
|
902 E5MessageBox.warning( |
|
903 self, |
|
904 self.tr("New File"), |
|
905 self.tr("A file or directory named <b>{0}</b> exists" |
|
906 " already. Aborting...") |
|
907 .format(fname)) |
|
908 else: |
|
909 try: |
|
910 f = open(filepath, "w") |
|
911 f.close() |
|
912 except OSError as err: |
|
913 E5MessageBox.critical( |
|
914 self, |
|
915 self.tr("New File"), |
|
916 self.tr("<p>The file <b>{0}</b> could not be" |
|
917 " created.</p><p>Reason: {1}</p>") |
|
918 .format(fname, str(err))) |
|
919 |
|
920 @pyqtSlot() |
|
921 def _deleteFileOrDirectory(self): |
|
922 """ |
|
923 Protected slot to delete a directory or file. |
|
924 """ |
|
925 index = self.currentIndex() |
|
926 if index.isValid(): |
|
927 itm = self.model().item(index) |
|
928 if itm.type() == BrowserItemDirectory: |
|
929 self.__deleteDirectory(itm.dirName()) |
|
930 else: |
|
931 self.__deleteFile(itm.fileName()) |
|
932 |
|
933 def __deleteFile(self, fn): |
|
934 """ |
|
935 Private method to delete a file. |
|
936 |
|
937 @param fn filename to be deleted |
|
938 @type str |
|
939 """ |
|
940 try: |
|
941 from ThirdParty.Send2Trash.send2trash import send2trash as s2t |
|
942 trashMsg = self.tr("Do you really want to move this file to the" |
|
943 " trash?") |
|
944 except ImportError: |
|
945 s2t = os.remove |
|
946 trashMsg = self.tr("Do you really want to delete this file?") |
|
947 |
|
948 from UI.DeleteFilesConfirmationDialog import ( |
|
949 DeleteFilesConfirmationDialog |
|
950 ) |
|
951 dlg = DeleteFilesConfirmationDialog( |
|
952 self.parent(), |
|
953 self.tr("Delete File"), |
|
954 trashMsg, |
|
955 [fn]) |
|
956 if dlg.exec() == QDialog.Accepted: |
|
957 try: |
|
958 s2t(fn) |
|
959 except OSError as err: |
|
960 E5MessageBox.critical( |
|
961 self.ui, |
|
962 self.tr("Delete File"), |
|
963 self.tr( |
|
964 "<p>The selected file <b>{0}</b> could not be" |
|
965 " deleted.</p><p>Reason: {1}</p>") |
|
966 .format(fn, str(err)) |
|
967 ) |
|
968 |
|
969 def __deleteDirectory(self, dn): |
|
970 """ |
|
971 Private method to delete a directory. |
|
972 |
|
973 @param dn directory name to be removed from the project |
|
974 @type str |
|
975 """ |
|
976 try: |
|
977 from ThirdParty.Send2Trash.send2trash import send2trash |
|
978 s2tAvailable = True |
|
979 trashMsg = self.tr("Do you really want to move this directory to" |
|
980 " the trash?") |
|
981 except ImportError: |
|
982 s2tAvailable = False |
|
983 trashMsg = self.tr("Do you really want to delete this directory?") |
|
984 |
|
985 from UI.DeleteFilesConfirmationDialog import ( |
|
986 DeleteFilesConfirmationDialog |
|
987 ) |
|
988 dlg = DeleteFilesConfirmationDialog( |
|
989 self.parent(), |
|
990 self.tr("Delete Directory"), |
|
991 trashMsg, |
|
992 [dn]) |
|
993 if dlg.exec() == QDialog.Accepted: |
|
994 try: |
|
995 if s2tAvailable: |
|
996 send2trash(dn) |
|
997 else: |
|
998 shutil.rmtree(dn, True) |
|
999 except OSError as err: |
|
1000 E5MessageBox.critical( |
|
1001 self.ui, |
|
1002 self.tr("Delete Directory"), |
|
1003 self.tr( |
|
1004 "<p>The selected directory <b>{0}</b> could not be" |
|
1005 " deleted.</p><p>Reason: {1}</p>") |
|
1006 .format(dn, str(err)) |
|
1007 ) |
|
1008 |
|
1009 @pyqtSlot() |
|
1010 def __deleteMultiple(self): |
|
1011 """ |
|
1012 Private slot to delete multiple directories and files. |
|
1013 |
|
1014 Note: The context menu for multi selection is only shown for file |
|
1015 items. |
|
1016 """ |
|
1017 fileNames = [] |
|
1018 for itm in self.getSelectedItems(): |
|
1019 fileNames.append(itm.fileName()) |
|
1020 |
|
1021 try: |
|
1022 from ThirdParty.Send2Trash.send2trash import send2trash as s2t |
|
1023 trashMsg = self.tr("Do you really want to move these files to the" |
|
1024 " trash?") |
|
1025 except ImportError: |
|
1026 s2t = os.remove |
|
1027 trashMsg = self.tr("Do you really want to delete these files?") |
|
1028 |
|
1029 from UI.DeleteFilesConfirmationDialog import ( |
|
1030 DeleteFilesConfirmationDialog |
|
1031 ) |
|
1032 dlg = DeleteFilesConfirmationDialog( |
|
1033 self.parent(), |
|
1034 self.tr("Delete Files"), |
|
1035 trashMsg, |
|
1036 sorted(fileNames) |
|
1037 ) |
|
1038 if dlg.exec() == QDialog.Accepted: |
|
1039 for fn in fileNames: |
|
1040 try: |
|
1041 s2t(fn) |
|
1042 except OSError as err: |
|
1043 E5MessageBox.critical( |
|
1044 self.ui, |
|
1045 self.tr("Delete File"), |
|
1046 self.tr( |
|
1047 "<p>The selected file <b>{0}</b> could not be" |
|
1048 " deleted.</p><p>Reason: {1}</p>") |
|
1049 .format(fn, str(err)) |
|
1050 ) |