src/eric7/Project/ProjectSourcesBrowser.py

Fri, 25 Oct 2024 17:58:59 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 25 Oct 2024 17:58:59 +0200
branch
eric7
changeset 11006
a671918232f3
parent 10680
306373ccf8fd
child 11019
27cd57e98461
child 11090
f5f5f5803935
permissions
-rw-r--r--

Modified modal dialog usage to always include a valid parent (needed for Wayland).

# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2024 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a class used to display the Sources part of the project.
"""

import contextlib
import os

from PyQt6.QtCore import pyqtSignal
from PyQt6.QtWidgets import QDialog, QInputDialog, QMenu

from eric7 import Utilities
from eric7.CodeFormatting.BlackFormattingAction import BlackFormattingAction
from eric7.CodeFormatting.BlackUtilities import aboutBlack
from eric7.CodeFormatting.IsortFormattingAction import IsortFormattingAction
from eric7.CodeFormatting.IsortUtilities import aboutIsort
from eric7.EricGui import EricPixmapCache
from eric7.EricWidgets import EricMessageBox, EricPathPickerDialog
from eric7.EricWidgets.EricApplication import ericApp
from eric7.EricWidgets.EricPathPickerDialog import EricPathPickerModes
from eric7.Graphics.UMLDialog import UMLDialog, UMLDialogType
from eric7.SystemUtilities import FileSystemUtilities
from eric7.UI.BrowserModel import (
    BrowserClassAttributeItem,
    BrowserClassItem,
    BrowserFileItem,
    BrowserImportItem,
    BrowserMethodItem,
)
from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog

from .FileCategoryRepositoryItem import FileCategoryRepositoryItem
from .ProjectBaseBrowser import ProjectBaseBrowser
from .ProjectBrowserModel import (
    ProjectBrowserFileItem,
    ProjectBrowserSimpleDirectoryItem,
)
from .ProjectBrowserRepositoryItem import ProjectBrowserRepositoryItem


class ProjectSourcesBrowser(ProjectBaseBrowser):
    """
    A class used to display the Sources part of the project.

    @signal showMenu(str, QMenu) emitted when a menu is about to be shown.
        The name of the menu and a reference to the menu are given.
    """

    showMenu = pyqtSignal(str, QMenu)

    def __init__(self, project, projectBrowser, parent=None):
        """
        Constructor

        @param project reference to the project object
        @type Project
        @param projectBrowser reference to the project browser object
        @type ProjectBrowser
        @param parent parent widget of this browser
        @type QWidget
        """
        ProjectBaseBrowser.__init__(self, project, "source", parent)

        self.selectedItemsFilter = [
            ProjectBrowserFileItem,
            ProjectBrowserSimpleDirectoryItem,
        ]

        self.setWindowTitle(self.tr("Sources"))

        self.setWhatsThis(
            self.tr(
                """<b>Project Sources Browser</b>"""
                """<p>This allows to easily see all sources contained in the"""
                """ current project. Several actions can be executed via the"""
                """ context menu.</p>"""
            )
        )

        # Add the file category handled by the browser.
        project.addFileCategory(
            "SOURCES",
            FileCategoryRepositoryItem(
                fileCategoryFilterTemplate=self.tr("Source Files ({0})"),
                fileCategoryUserString=self.tr("Source Files"),
                fileCategoryTyeString=self.tr("Sources"),
                fileCategoryExtensions=["*.py", "*.pyw"],  # Python files as default
            ),
        )

        # Add the project browser type to the browser type repository.
        projectBrowser.addTypedProjectBrowser(
            "sources",
            ProjectBrowserRepositoryItem(
                projectBrowser=self,
                projectBrowserUserString=self.tr("Sources Browser"),
                priority=100,
                fileCategory="SOURCES",
                fileFilter="source",
                getIcon=self.getIcon,
            ),
        )

        # Connect signals of Project.
        project.prepareRepopulateItem.connect(self._prepareRepopulateItem)
        project.completeRepopulateItem.connect(self._completeRepopulateItem)
        project.projectClosed.connect(self._projectClosed)
        project.projectOpened.connect(self._projectOpened)
        project.newProject.connect(self._newProject)
        project.reinitVCS.connect(self._initMenusAndVcs)
        project.projectPropertiesChanged.connect(self._initMenusAndVcs)

        # Connect signals of ProjectBrowser.
        projectBrowser.preferencesChanged.connect(self.handlePreferencesChanged)

        # Connect some of our own signals.
        self.sourceFile[str].connect(projectBrowser.sourceFile[str])
        self.sourceFile[str, int].connect(projectBrowser.sourceFile[str, int])
        self.sourceFile[str, list].connect(projectBrowser.sourceFile[str, list])
        self.sourceFile[str, int, str].connect(projectBrowser.sourceFile[str, int, str])
        self.sourceFile[str, int, int].connect(projectBrowser.sourceFile[str, int, int])
        self.closeSourceWindow.connect(projectBrowser.closeSourceWindow)
        self.testFile.connect(projectBrowser.testFile)

        self.codemetrics = None
        self.codecoverage = None
        self.profiledata = None
        self.classDiagram = None
        self.importsDiagram = None
        self.packageDiagram = None
        self.applicationDiagram = None
        self.loadedDiagram = None

    def getIcon(self):
        """
        Public method to get an icon for the project browser.

        @return icon for the browser
        @rtype QIcon
        """
        if not self.project.isOpen():
            icon = EricPixmapCache.getIcon("projectSources")
        else:
            if self.project.getProjectLanguage() == "Python3":
                if self.project.isMixedLanguageProject():
                    icon = EricPixmapCache.getIcon("projectSourcesPyMixed")
                else:
                    icon = EricPixmapCache.getIcon("projectSourcesPy")
            elif self.project.getProjectLanguage() == "MicroPython":
                icon = EricPixmapCache.getIcon("micropython")
            elif self.project.getProjectLanguage() == "Ruby":
                if self.project.isMixedLanguageProject():
                    icon = EricPixmapCache.getIcon("projectSourcesRbMixed")
                else:
                    icon = EricPixmapCache.getIcon("projectSourcesRb")
            elif self.project.getProjectLanguage() == "JavaScript":
                icon = EricPixmapCache.getIcon("projectSourcesJavaScript")
            else:
                icon = EricPixmapCache.getIcon("projectSources")

        return icon

    def __closeAllWindows(self):
        """
        Private method to close all project related windows.
        """
        self.codemetrics and self.codemetrics.close()
        self.codecoverage and self.codecoverage.close()
        self.profiledata and self.profiledata.close()
        self.classDiagram and self.classDiagram.close()
        self.importsDiagram and self.importsDiagram.close()
        self.packageDiagram and self.packageDiagram.close()
        self.applicationDiagram and self.applicationDiagram.close()
        self.loadedDiagram and self.loadedDiagram.close()

    def _projectClosed(self):
        """
        Protected slot to handle the projectClosed signal.
        """
        self.__closeAllWindows()
        ProjectBaseBrowser._projectClosed(self)

    def _createPopupMenus(self):
        """
        Protected overloaded method to generate the popup menu.
        """
        ProjectBaseBrowser._createPopupMenus(self)
        self.sourceMenuActions = {}

        if self.project.isPythonProject():
            self.__createPythonPopupMenus()
        elif self.project.isRubyProject():
            self.__createRubyPopupMenus()
        elif self.project.isJavaScriptProject():
            self.__createJavaScriptPopupMenus()
        else:
            # assign generic source menu
            self.mainMenu = self.sourceMenu

    def __createPythonPopupMenus(self):
        """
        Private method to generate the popup menus for a Python project.
        """
        self.checksMenu = QMenu(self.tr("Check"))
        self.checksMenu.aboutToShow.connect(self.__showContextMenuCheck)

        self.formattingMenu = QMenu(self.tr("Code Formatting"))
        act = self.formattingMenu.addAction(self.tr("Black"), aboutBlack)
        font = act.font()
        font.setBold(True)
        act.setFont(font)
        self.formattingMenu.addAction(
            self.tr("Format Code"),
            lambda: self.__performFormatWithBlack(BlackFormattingAction.Format),
        )
        self.formattingMenu.addAction(
            self.tr("Check Formatting"),
            lambda: self.__performFormatWithBlack(BlackFormattingAction.Check),
        )
        self.formattingMenu.addAction(
            self.tr("Formatting Diff"),
            lambda: self.__performFormatWithBlack(BlackFormattingAction.Diff),
        )
        self.formattingMenu.addSeparator()
        act = self.formattingMenu.addAction(self.tr("isort"), aboutIsort)
        font = act.font()
        font.setBold(True)
        act.setFont(font)
        self.formattingMenu.addAction(
            self.tr("Sort Imports"),
            lambda: self.__performImportSortingWithIsort(IsortFormattingAction.Sort),
        )
        self.formattingMenu.addAction(
            self.tr("Imports Sorting Diff"),
            lambda: self.__performImportSortingWithIsort(IsortFormattingAction.Diff),
        )
        self.formattingMenu.addSeparator()
        self.formattingMenu.aboutToShow.connect(self.__showContextMenuFormatting)

        self.menuShow = QMenu(self.tr("Show"))
        self.menuShow.addAction(self.tr("Code metrics..."), self.__showCodeMetrics)
        self.coverageMenuAction = self.menuShow.addAction(
            self.tr("Code coverage..."), self.__showCodeCoverage
        )
        self.profileMenuAction = self.menuShow.addAction(
            self.tr("Profile data..."), self.__showProfileData
        )
        self.menuShow.aboutToShow.connect(self.__showContextMenuShow)

        self.graphicsMenu = QMenu(self.tr("Diagrams"))
        self.classDiagramAction = self.graphicsMenu.addAction(
            self.tr("Class Diagram..."), self.__showClassDiagram
        )
        self.graphicsMenu.addAction(
            self.tr("Package Diagram..."), self.__showPackageDiagram
        )
        self.importsDiagramAction = self.graphicsMenu.addAction(
            self.tr("Imports Diagram..."), self.__showImportsDiagram
        )
        self.graphicsMenu.addAction(
            self.tr("Application Diagram..."), self.__showApplicationDiagram
        )
        self.graphicsMenu.addSeparator()
        self.graphicsMenu.addAction(
            EricPixmapCache.getIcon("open"),
            self.tr("Load Diagram..."),
            self.__loadDiagram,
        )
        self.graphicsMenu.aboutToShow.connect(self.__showContextMenuGraphics)

        self.__startMenu = QMenu(self.tr("Start"), self)
        self.__startMenu.addAction(
            EricPixmapCache.getIcon("runScript"),
            self.tr("Run Script..."),
            self.__contextMenuRunScript,
        )
        self.__startMenu.addAction(
            EricPixmapCache.getIcon("debugScript"),
            self.tr("Debug Script..."),
            self.__contextMenuDebugScript,
        )
        self.__startMenu.addAction(
            EricPixmapCache.getIcon("profileScript"),
            self.tr("Profile Script..."),
            self.__contextMenuProfileScript,
        )
        self.__startMenu.addAction(
            EricPixmapCache.getIcon("coverageScript"),
            self.tr("Coverage run of Script..."),
            self.__contextMenuCoverageScript,
        )

        self.testingAction = self.sourceMenu.addAction(
            self.tr("Run tests..."), self.handleTesting
        )
        self.sourceMenu.addSeparator()
        act = self.sourceMenu.addAction(self.tr("Rename file"), self._renameFile)
        self.menuActions.append(act)
        act = self.sourceMenu.addAction(
            self.tr("Remove from project"), self._removeFile
        )
        self.menuActions.append(act)
        act = self.sourceMenu.addAction(self.tr("Delete"), self.__deleteFile)
        self.menuActions.append(act)
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("New package..."), self.__addNewPackage)
        self.sourceMenu.addAction(
            self.tr("New source file..."), self.__addNewSourceFile
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.sourceMenu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addMenu(self.graphicsMenu)
        self.sourceMenu.addMenu(self.checksMenu)
        self.sourceMenuActions["Formatting"] = self.sourceMenu.addMenu(
            self.formattingMenu
        )
        self.sourceMenuActions["Show"] = self.sourceMenu.addMenu(self.menuShow)
        self.sourceMenu.addSeparator()
        self.__startAct = self.sourceMenu.addMenu(self.__startMenu)
        self.sourceMenu.addSeparator()
        self.__sourceMenuFileManagerAct = self.sourceMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.sourceMenu.addAction(
            self.tr("Copy Path to Clipboard"), self._copyToClipboard
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.sourceMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.sourceMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Configure..."), self._configure)

        self.menu.addSeparator()
        self.menu.addAction(self.tr("New package..."), self.__addNewPackage)
        self.menu.addAction(self.tr("New source file..."), self.__addNewSourceFile)
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.menu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.menu.addSeparator()
        self.__menuFileManagerAct = self.menu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs)
        self.menu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Configure..."), self._configure)

        # create the attribute menu
        self.gotoMenu = QMenu(self.tr("Goto"), self)
        self.gotoMenu.aboutToShow.connect(self._showGotoMenu)
        self.gotoMenu.triggered.connect(self._gotoAttribute)

        self.attributeMenu = QMenu(self)
        self.attributeMenu.addMenu(self.gotoMenu)
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(self.tr("New package..."), self.__addNewPackage)
        self.attributeMenu.addAction(
            self.tr("New source file..."), self.__addNewSourceFile
        )
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(
            self.tr("Add source files..."),
            lambda: self.project.addFiles("SOURCES"),
        )
        self.attributeMenu.addAction(
            self.tr("Add source directory..."),
            lambda: self.project.addDirectory("SOURCES"),
        )
        self.attributeMenu.addSeparator()
        self.__attributeMenuFileManagerAct = self.attributeMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.attributeMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.attributeMenu.addAction(
            self.tr("Collapse all files"), self._collapseAllFiles
        )
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(self.tr("Configure..."), self._configure)

        self.backMenu = QMenu(self)
        self.backMenu.addAction(self.tr("New package..."), self.__addNewPackage)
        self.backMenu.addAction(self.tr("New source file..."), self.__addNewSourceFile)
        self.backMenu.addSeparator()
        self.backMenu.addAction(
            self.tr("Add source files..."),
            lambda: self.project.addFiles("SOURCES"),
        )
        self.backMenu.addAction(
            self.tr("Add source directory..."),
            lambda: self.project.addDirectory("SOURCES"),
        )
        self.backMenu.addSeparator()
        self.__backMenuFileManagerAct = self.backMenu.addAction(
            self.tr("Show in File Manager"), self._showProjectInFileManager
        )
        self.backMenu.addSeparator()
        self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.backMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.backMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.backMenu.addSeparator()
        self.backMenu.addAction(self.tr("Configure..."), self._configure)
        self.backMenu.setEnabled(False)

        self.multiMenu.addSeparator()
        act = self.multiMenu.addAction(self.tr("Remove from project"), self._removeFile)
        self.multiMenuActions.append(act)
        act = self.multiMenu.addAction(self.tr("Delete"), self.__deleteFile)
        self.multiMenuActions.append(act)
        self.multiMenu.addSeparator()
        self.multiMenu.addMenu(self.checksMenu)
        self.__multiMenuFormattingAct = self.multiMenu.addMenu(self.formattingMenu)
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.multiMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.multiMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.tr("Configure..."), self._configure)

        self.dirMenu = QMenu(self)
        act = self.dirMenu.addAction(self.tr("Remove from project"), self._removeDir)
        self.dirMenuActions.append(act)
        act = self.dirMenu.addAction(self.tr("Delete"), self._deleteDirectory)
        self.dirMenuActions.append(act)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("New package..."), self.__addNewPackage)
        self.dirMenu.addAction(self.tr("New source file..."), self.__addNewSourceFile)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.dirMenu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.dirMenu.addSeparator()
        act = self.dirMenu.addMenu(self.graphicsMenu)
        self.dirMenu.addMenu(self.checksMenu)
        self.__dirMenuFormattingAct = self.dirMenu.addMenu(self.formattingMenu)
        self.dirMenu.addSeparator()
        self.__dirMenuFileManagerAct = self.dirMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.dirMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.dirMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Configure..."), self._configure)

        self.dirMultiMenu = QMenu(self)
        self.dirMultiMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.dirMultiMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.dirMultiMenu.addAction(
            self.tr("Collapse all files"), self._collapseAllFiles
        )
        self.dirMultiMenu.addSeparator()
        self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure)

        self.sourceMenu.aboutToShow.connect(self.__showContextMenu)
        self.menu.aboutToShow.connect(self.__showContextMenuGeneral)
        self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute)
        self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
        self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
        self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
        self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
        self.mainMenu = self.sourceMenu

    def __createRubyPopupMenus(self):
        """
        Private method to generate the popup menus for a Ruby project.
        """
        self.graphicsMenu = QMenu(self.tr("Diagrams"))
        self.classDiagramAction = self.graphicsMenu.addAction(
            self.tr("Class Diagram..."), self.__showClassDiagram
        )
        self.graphicsMenu.addAction(
            self.tr("Package Diagram..."), self.__showPackageDiagram
        )
        self.graphicsMenu.addAction(
            self.tr("Application Diagram..."), self.__showApplicationDiagram
        )
        self.graphicsMenu.addSeparator()
        self.graphicsMenu.addAction(
            EricPixmapCache.getIcon("fileOpen"),
            self.tr("Load Diagram..."),
            self.__loadDiagram,
        )

        self.sourceMenu.addSeparator()
        act = self.sourceMenu.addAction(self.tr("Rename file"), self._renameFile)
        self.menuActions.append(act)
        act = self.sourceMenu.addAction(
            self.tr("Remove from project"), self._removeFile
        )
        self.menuActions.append(act)
        act = self.sourceMenu.addAction(self.tr("Delete"), self.__deleteFile)
        self.menuActions.append(act)
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.sourceMenu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.sourceMenu.addSeparator()
        act = self.sourceMenu.addMenu(self.graphicsMenu)
        self.sourceMenu.addSeparator()
        self.__sourceMenuFileManagerAct = self.sourceMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.sourceMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.sourceMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Configure..."), self._configure)

        self.menu.addSeparator()
        self.menu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.menu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.menu.addSeparator()
        self.__menuFileManagerAct = self.menu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs)
        self.menu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Configure..."), self._configure)

        # create the attribute menu
        self.gotoMenu = QMenu(self.tr("Goto"), self)
        self.gotoMenu.aboutToShow.connect(self._showGotoMenu)
        self.gotoMenu.triggered.connect(self._gotoAttribute)

        self.attributeMenu = QMenu(self)
        self.attributeMenu.addMenu(self.gotoMenu)
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(
            self.tr("Add source files..."),
            lambda: self.project.addFiles("SOURCES"),
        )
        self.attributeMenu.addAction(
            self.tr("Add source directory..."),
            lambda: self.project.addDirectory("SOURCES"),
        )
        self.attributeMenu.addSeparator()
        self.__attributeMenuFileManagerAct = self.attributeMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.attributeMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.attributeMenu.addAction(
            self.tr("Collapse all files"), self._collapseAllFiles
        )
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(self.tr("Configure..."), self._configure)

        self.backMenu = QMenu(self)
        self.backMenu.addAction(
            self.tr("Add source files..."),
            lambda: self.project.addFiles("SOURCES"),
        )
        self.backMenu.addAction(
            self.tr("Add source directory..."),
            lambda: self.project.addDirectory("SOURCES"),
        )
        self.backMenu.addSeparator()
        self.__backMenuFileManagerAct = self.backMenu.addAction(
            self.tr("Show in File Manager"), self._showProjectInFileManager
        )
        self.backMenu.addSeparator()
        self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.backMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.backMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.backMenu.setEnabled(False)
        self.backMenu.addSeparator()
        self.backMenu.addAction(self.tr("Configure..."), self._configure)

        self.multiMenu.addSeparator()
        act = self.multiMenu.addAction(self.tr("Remove from project"), self._removeFile)
        self.multiMenuActions.append(act)
        act = self.multiMenu.addAction(self.tr("Delete"), self.__deleteFile)
        self.multiMenuActions.append(act)
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.multiMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.multiMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.tr("Configure..."), self._configure)

        self.dirMenu = QMenu(self)
        act = self.dirMenu.addAction(self.tr("Remove from project"), self._removeDir)
        self.dirMenuActions.append(act)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.dirMenu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.dirMenu.addSeparator()
        act = self.dirMenu.addMenu(self.graphicsMenu)
        self.dirMenu.addSeparator()
        self.__dirMenuFileManagerAct = self.dirMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.dirMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.dirMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Configure..."), self._configure)

        self.dirMultiMenu = QMenu(self)
        self.dirMultiMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.dirMultiMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.dirMultiMenu.addAction(
            self.tr("Collapse all files"), self._collapseAllFiles
        )
        self.dirMultiMenu.addSeparator()
        self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure)

        self.sourceMenu.aboutToShow.connect(self.__showContextMenu)
        self.menu.aboutToShow.connect(self.__showContextMenuGeneral)
        self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute)
        self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
        self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
        self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
        self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
        self.mainMenu = self.sourceMenu

    def __createJavaScriptPopupMenus(self):
        """
        Private method to generate the popup menus for a Python project.
        """
        self.checksMenu = QMenu(self.tr("Check"))
        self.checksMenu.aboutToShow.connect(self.__showContextMenuCheck)

        self.sourceMenu.addSeparator()
        act = self.sourceMenu.addAction(self.tr("Rename file"), self._renameFile)
        self.menuActions.append(act)
        act = self.sourceMenu.addAction(
            self.tr("Remove from project"), self._removeFile
        )
        self.menuActions.append(act)
        act = self.sourceMenu.addAction(self.tr("Delete"), self.__deleteFile)
        self.menuActions.append(act)
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.sourceMenu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addMenu(self.checksMenu)
        self.sourceMenu.addSeparator()
        self.__sourceMenuFileManagerAct = self.sourceMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.sourceMenu.addAction(
            self.tr("Copy Path to Clipboard"), self._copyToClipboard
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.sourceMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.sourceMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Configure..."), self._configure)

        self.menu.addSeparator()
        self.menu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.menu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.menu.addSeparator()
        self.__menuFileManagerAct = self.menu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.menu.addAction(self.tr("Collapse all directories"), self._collapseAllDirs)
        self.menu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Configure..."), self._configure)

        # create the attribute menu
        self.gotoMenu = QMenu(self.tr("Goto"), self)
        self.gotoMenu.aboutToShow.connect(self._showGotoMenu)
        self.gotoMenu.triggered.connect(self._gotoAttribute)

        self.attributeMenu = QMenu(self)
        self.attributeMenu.addMenu(self.gotoMenu)
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(
            self.tr("Add source files..."),
            lambda: self.project.addFiles("SOURCES"),
        )
        self.attributeMenu.addAction(
            self.tr("Add source directory..."),
            lambda: self.project.addDirectory("SOURCES"),
        )
        self.attributeMenu.addSeparator()
        self.__attrMenuFileManagerAct = self.attributeMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.attributeMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.attributeMenu.addAction(
            self.tr("Collapse all files"), self._collapseAllFiles
        )
        self.attributeMenu.addSeparator()
        self.attributeMenu.addAction(self.tr("Configure..."), self._configure)

        self.backMenu = QMenu(self)
        self.backMenu.addAction(
            self.tr("Add source files..."),
            lambda: self.project.addFiles("SOURCES"),
        )
        self.backMenu.addAction(
            self.tr("Add source directory..."),
            lambda: self.project.addDirectory("SOURCES"),
        )
        self.backMenu.addSeparator()
        self.__backMenuFileManagerAct = self.backMenu.addAction(
            self.tr("Show in File Manager"), self._showProjectInFileManager
        )
        self.backMenu.addSeparator()
        self.backMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.backMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.backMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.backMenu.addSeparator()
        self.backMenu.addAction(self.tr("Configure..."), self._configure)
        self.backMenu.setEnabled(False)

        self.multiMenu.addSeparator()
        act = self.multiMenu.addAction(self.tr("Remove from project"), self._removeFile)
        self.multiMenuActions.append(act)
        act = self.multiMenu.addAction(self.tr("Delete"), self.__deleteFile)
        self.multiMenuActions.append(act)
        self.multiMenu.addSeparator()
        self.multiMenu.addMenu(self.checksMenu)
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.multiMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.multiMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.tr("Configure..."), self._configure)

        self.dirMenu = QMenu(self)
        act = self.dirMenu.addAction(self.tr("Remove from project"), self._removeDir)
        self.dirMenuActions.append(act)
        act = self.dirMenu.addAction(self.tr("Delete"), self._deleteDirectory)
        self.dirMenuActions.append(act)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Add source files..."), self.__addSourceFiles)
        self.dirMenu.addAction(
            self.tr("Add source directory..."), self.__addSourceDirectory
        )
        self.dirMenu.addSeparator()
        self.dirMenu.addMenu(self.checksMenu)
        self.dirMenu.addSeparator()
        self.__dirMenuFileManagerAct = self.dirMenu.addAction(
            self.tr("Show in File Manager"), self._showInFileManager
        )
        self.dirMenu.addAction(self.tr("Copy Path to Clipboard"), self._copyToClipboard)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Expand all directories"), self._expandAllDirs)
        self.dirMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.dirMenu.addAction(self.tr("Collapse all files"), self._collapseAllFiles)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Configure..."), self._configure)

        self.dirMultiMenu = QMenu(self)
        self.dirMultiMenu.addAction(
            self.tr("Expand all directories"), self._expandAllDirs
        )
        self.dirMultiMenu.addAction(
            self.tr("Collapse all directories"), self._collapseAllDirs
        )
        self.dirMultiMenu.addAction(
            self.tr("Collapse all files"), self._collapseAllFiles
        )
        self.dirMultiMenu.addSeparator()
        self.dirMultiMenu.addAction(self.tr("Configure..."), self._configure)

        self.sourceMenu.aboutToShow.connect(self.__showContextMenu)
        self.menu.aboutToShow.connect(self.__showContextMenuGeneral)
        self.attributeMenu.aboutToShow.connect(self.__showContextMenuAttribute)
        self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti)
        self.dirMenu.aboutToShow.connect(self.__showContextMenuDir)
        self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti)
        self.backMenu.aboutToShow.connect(self.__showContextMenuBack)
        self.mainMenu = self.sourceMenu

    def _contextMenuRequested(self, coord):
        """
        Protected slot to show the context menu.

        @param coord the position of the mouse pointer
        @type QPoint
        """
        if not self.project.isOpen():
            return

        with contextlib.suppress(Exception):  # secok
            categories = self.getSelectedItemsCountCategorized(
                [
                    ProjectBrowserFileItem,
                    BrowserClassItem,
                    BrowserMethodItem,
                    ProjectBrowserSimpleDirectoryItem,
                    BrowserClassAttributeItem,
                    BrowserImportItem,
                ]
            )
            cnt = categories["sum"]
            if cnt <= 1:
                index = self.indexAt(coord)
                if index.isValid():
                    self._selectSingleItem(index)
                    categories = self.getSelectedItemsCountCategorized(
                        [
                            ProjectBrowserFileItem,
                            BrowserClassItem,
                            BrowserMethodItem,
                            ProjectBrowserSimpleDirectoryItem,
                            BrowserClassAttributeItem,
                            BrowserImportItem,
                        ]
                    )
                    cnt = categories["sum"]

            bfcnt = categories[str(ProjectBrowserFileItem)]
            cmcnt = (
                categories[str(BrowserClassItem)]
                + categories[str(BrowserMethodItem)]
                + categories[str(BrowserClassAttributeItem)]
                + categories[str(BrowserImportItem)]
            )
            sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)]
            if cnt > 1 and cnt == bfcnt:
                self.multiMenu.popup(self.mapToGlobal(coord))
            elif cnt > 1 and cnt == sdcnt:
                self.dirMultiMenu.popup(self.mapToGlobal(coord))
            else:
                index = self.indexAt(coord)
                if cnt == 1 and index.isValid():
                    if bfcnt == 1 or cmcnt == 1:
                        itm = self.model().item(index)
                        if isinstance(itm, ProjectBrowserFileItem):
                            fn = itm.fileName()
                            if self.project.isPythonProject():
                                if fn.endswith(".ptl"):
                                    for act in self.sourceMenuActions.values():
                                        act.setEnabled(False)
                                    self.classDiagramAction.setEnabled(True)
                                    self.importsDiagramAction.setEnabled(True)
                                    self.testingAction.setEnabled(False)
                                    self.checksMenu.menuAction().setEnabled(False)
                                elif fn.endswith(".rb"):
                                    # entry for mixed mode programs
                                    for act in self.sourceMenuActions.values():
                                        act.setEnabled(False)
                                    self.classDiagramAction.setEnabled(True)
                                    self.importsDiagramAction.setEnabled(False)
                                    self.testingAction.setEnabled(False)
                                    self.checksMenu.menuAction().setEnabled(False)
                                elif fn.endswith(".js"):
                                    # entry for mixed mode programs
                                    for act in self.sourceMenuActions.values():
                                        act.setEnabled(False)
                                    self.testingAction.setEnabled(False)
                                    self.checksMenu.menuAction().setEnabled(False)
                                    self.graphicsMenu.menuAction().setEnabled(False)
                                else:
                                    # assume the source file is a Python file
                                    for act in self.sourceMenuActions.values():
                                        act.setEnabled(True)
                                    self.classDiagramAction.setEnabled(True)
                                    self.importsDiagramAction.setEnabled(True)
                                    self.testingAction.setEnabled(True)
                                    self.checksMenu.menuAction().setEnabled(True)
                            self.sourceMenu.popup(self.mapToGlobal(coord))
                        elif isinstance(
                            itm,
                            (BrowserClassItem, BrowserMethodItem, BrowserImportItem),
                        ):
                            self.menu.popup(self.mapToGlobal(coord))
                        elif isinstance(itm, BrowserClassAttributeItem):
                            self.attributeMenu.popup(self.mapToGlobal(coord))
                        else:
                            self.backMenu.popup(self.mapToGlobal(coord))
                    elif sdcnt == 1:
                        self.classDiagramAction.setEnabled(False)
                        self.dirMenu.popup(self.mapToGlobal(coord))
                    else:
                        self.backMenu.popup(self.mapToGlobal(coord))
                else:
                    self.backMenu.popup(self.mapToGlobal(coord))

    def __showContextMenu(self):
        """
        Private slot called by the sourceMenu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenu(self, self.sourceMenu)

        itm = self.model().item(self.currentIndex())
        if itm:
            try:
                self.__startAct.setEnabled(itm.isPython3File())
            except AttributeError:
                self.__startAct.setEnabled(False)
        else:
            self.__startAct.setEnabled(False)

        self.sourceMenuActions["Formatting"].setEnabled(
            self.sourceMenuActions["Formatting"].isEnabled()
            and not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )
        self.__sourceMenuFileManagerAct.setEnabled(
            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )

        self.showMenu.emit("Main", self.sourceMenu)

    def __showContextMenuGeneral(self):
        """
        Private slot called by the menu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenu(self, self.menu)

        self.__menuFileManagerAct.setEnabled(
            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )

    def __showContextMenuAttribute(self):
        """
        Private slot called by the attributeMenu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenu(self, self.menu)

        self.__attributeMenuFileManagerAct.setEnabled(
            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )

    def __showContextMenuMulti(self):
        """
        Private slot called by the multiMenu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu)

        self.__multiMenuFormattingAct.setEnabled(
            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )

        self.showMenu.emit("MainMulti", self.multiMenu)

    def __showContextMenuDir(self):
        """
        Private slot called by the dirMenu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu)

        self.__dirMenuFormattingAct.setEnabled(
            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )
        self.__dirMenuFileManagerAct.setEnabled(
            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )

        self.showMenu.emit("MainDir", self.dirMenu)

    def __showContextMenuDirMulti(self):
        """
        Private slot called by the dirMultiMenu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu)

        self.showMenu.emit("MainDirMulti", self.dirMultiMenu)

    def __showContextMenuBack(self):
        """
        Private slot called by the backMenu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenuBack(self, self.backMenu)

        self.__backMenuFileManagerAct.setEnabled(
            not FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        )

        self.showMenu.emit("MainBack", self.backMenu)

    def __showContextMenuShow(self):
        """
        Private slot called before the show menu is shown.
        """
        prEnable = False
        coEnable = False

        # first check if the file belongs to a project and there is
        # a project coverage file
        fn = self.project.getMainScript(True)
        if fn is not None:
            prEnable = self.project.isPy3Project() and bool(
                Utilities.getProfileFileNames(fn)
            )
            coEnable = self.project.isPy3Project() and bool(
                Utilities.getCoverageFileNames(fn)
            )

        # now check the selected item
        itm = self.model().item(self.currentIndex())
        fn = itm.fileName()
        if fn is not None:
            prEnable |= itm.isPython3File() and bool(Utilities.getProfileFileNames(fn))
            coEnable |= itm.isPython3File() and bool(Utilities.getCoverageFileName(fn))

        self.profileMenuAction.setEnabled(prEnable)
        self.coverageMenuAction.setEnabled(coEnable)

        self.showMenu.emit("Show", self.menuShow)

    def _openItem(self):
        """
        Protected slot to handle the open popup menu entry.
        """
        itmList = self.getSelectedItems(
            [
                BrowserFileItem,
                BrowserClassItem,
                BrowserMethodItem,
                BrowserClassAttributeItem,
                BrowserImportItem,
            ]
        )

        for itm in itmList:
            if isinstance(itm, BrowserFileItem):
                if itm.isPython3File():
                    self.sourceFile[str].emit(itm.fileName())
                elif itm.isRubyFile():
                    self.sourceFile[str, int, str].emit(itm.fileName(), -1, "Ruby")
                elif itm.isDFile():
                    self.sourceFile[str, int, str].emit(itm.fileName(), -1, "D")
                else:
                    self.sourceFile[str].emit(itm.fileName())
            elif isinstance(itm, BrowserClassItem):
                self.sourceFile[str, int, int].emit(
                    itm.fileName(), itm.lineno(), itm.colOffset()
                )
            elif isinstance(itm, BrowserMethodItem):
                self.sourceFile[str, int, int].emit(
                    itm.fileName(), itm.lineno(), itm.colOffset()
                )
            elif isinstance(itm, BrowserClassAttributeItem):
                self.sourceFile[str, int, int].emit(
                    itm.fileName(), itm.lineno(), itm.colOffset()
                )
            elif isinstance(itm, BrowserImportItem):
                self.sourceFile[str, list].emit(itm.fileName(), itm.linenos())

    def __addNewPackage(self):
        """
        Private method to add a new package to the project.
        """
        from .NewPythonPackageDialog import NewPythonPackageDialog

        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        remotefsInterface = (
            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
        )
        separator = remotefsInterface.separator() if isRemote else os.sep

        dn = self.currentDirectory(relative=True)
        if dn.startswith(separator):
            dn = dn[1:]
        dlg = NewPythonPackageDialog(dn, parent=self)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            packageName = dlg.getData()
            nameParts = packageName.split(".")
            packagePath = self.project.ppath
            packageFile = ""
            for name in nameParts:
                packagePath = (
                    remotefsInterface.join(packagePath, name)
                    if isRemote
                    else os.path.join(packagePath, name)
                )
                exists = (
                    remotefsInterface.exists(packagePath)
                    if isRemote
                    else os.path.exists(packagePath)
                )
                if not exists:
                    try:
                        if isRemote:
                            remotefsInterface.mkdir(packagePath)
                        else:
                            os.mkdir(packagePath)
                    except OSError as err:
                        EricMessageBox.critical(
                            self,
                            self.tr("Add new Python package"),
                            self.tr(
                                """<p>The package directory <b>{0}</b> could"""
                                """ not be created. Aborting...</p>"""
                                """<p>Reason: {1}</p>"""
                            ).format(packagePath, str(err)),
                        )
                        return
                packageFile = (
                    remotefsInterface.join(packagePath, "__init__.py")
                    if isRemote
                    else os.path.join(packagePath, "__init__.py")
                )
                exists = (
                    remotefsInterface.exists(packageFile)
                    if isRemote
                    else os.path.exists(packageFile)
                )
                if not exists:
                    try:
                        if isRemote:
                            remotefsInterface.writeFile(packageFile, b"")
                        else:
                            with open(packageFile, "w", encoding="utf-8"):
                                pass
                    except OSError as err:
                        EricMessageBox.critical(
                            self,
                            self.tr("Add new Python package"),
                            self.tr(
                                """<p>The package file <b>{0}</b> could"""
                                """ not be created. Aborting...</p>"""
                                """<p>Reason: {1}</p>"""
                            ).format(packageFile, str(err)),
                        )
                        return
                self.project.appendFile(packageFile)
            if packageFile:
                self.sourceFile[str].emit(packageFile)

    def __addNewSourceFile(self):
        """
        Private method to add a new source file to the project.
        """
        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        remotefsInterface = (
            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
        )

        dn = self.currentDirectory()
        filename, ok = EricPathPickerDialog.getStrPath(
            self,
            self.tr("New source file"),
            self.tr("Enter the path of the new source file:"),
            mode=EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE,
            strPath=dn,
            defaultDirectory=dn,
            filters=self.project.getFileCategoryFilters(
                categories=["SOURCES"], withAll=False
            ),
            remote=isRemote,
        )
        if ok:
            exists = (
                remotefsInterface.exists(filename)
                if isRemote
                else os.path.exists(filename)
            )
            if exists:
                EricMessageBox.critical(
                    self,
                    self.tr("New source file"),
                    self.tr(
                        "<p>The file <b>{0}</b> already exists. The action will be"
                        " aborted.</p>"
                    ).format(filename),
                )
                return

            try:
                newline = (
                    None if self.project.useSystemEol() else self.project.getEolString()
                )
                header = "# -*- coding: utf-8 -*-\n# {0}\n".format(
                    self.project.getRelativePath(filename)
                )
                if isRemote:
                    remotefsInterface.writeFile(
                        filename, header.encode("utf-8"), newline=newline
                    )
                else:
                    with open(filename, "w", newline=newline) as f:
                        f.write(header)
            except OSError as err:
                EricMessageBox.critical(
                    self,
                    self.tr("New source file"),
                    self.tr(
                        "<p>The file <b>{0}</b> could not be created. Aborting...</p>"
                        "<p>Reason: {1}</p>"
                    ).format(filename, str(err)),
                )
                return

            self.project.appendFile(filename)
            self.sourceFile[str].emit(filename)

    def __addSourceFiles(self):
        """
        Private method to add a source file to the project.
        """
        self.project.addFiles("SOURCES", self.currentDirectory())

    def __addSourceDirectory(self):
        """
        Private method to add source files of a directory to the project.
        """
        self.project.addDirectory("SOURCES", self.currentDirectory())

    def __deleteFile(self):
        """
        Private method to delete files from the project.
        """
        itmList = self.getSelectedItems()

        files = []
        fullNames = []
        for itm in itmList:
            fn2 = itm.fileName()
            fullNames.append(fn2)
            fn = self.project.getRelativePath(fn2)
            files.append(fn)

        dlg = DeleteFilesConfirmationDialog(
            self.parent(),
            self.tr("Delete files"),
            self.tr("Do you really want to delete these files from the project?"),
            files,
        )

        if dlg.exec() == QDialog.DialogCode.Accepted:
            for fn2, fn in zip(fullNames, files):
                self.closeSourceWindow.emit(fn2)
                self.project.deleteFile(fn)

    ###########################################################################
    ## Methods for the Checks submenu
    ###########################################################################

    def __showContextMenuCheck(self):
        """
        Private slot called before the checks menu is shown.
        """
        self.showMenu.emit("Checks", self.checksMenu)

    ###########################################################################
    ## Methods for the Show submenu
    ###########################################################################

    def __showCodeMetrics(self):
        """
        Private method to handle the code metrics context menu action.
        """
        from eric7.DataViews.CodeMetricsDialog import CodeMetricsDialog

        itm = self.model().item(self.currentIndex())
        fn = itm.fileName()

        self.codemetrics = CodeMetricsDialog()
        self.codemetrics.show()
        self.codemetrics.start(fn)

    def __showCodeCoverage(self):
        """
        Private method to handle the code coverage context menu action.
        """
        from eric7.DataViews.PyCoverageDialog import PyCoverageDialog

        itm = self.model().item(self.currentIndex())
        fn = itm.fileName()
        pfn = self.project.getMainScript(True)

        files = []

        if pfn is not None:
            files.extend(
                [f for f in Utilities.getCoverageFileNames(pfn) if f not in files]
            )

        if fn is not None:
            files.extend(
                [f for f in Utilities.getCoverageFileNames(fn) if f not in files]
            )

        if files:
            if len(files) > 1:
                cfn, ok = QInputDialog.getItem(
                    None,
                    self.tr("Code Coverage"),
                    self.tr("Please select a coverage file"),
                    files,
                    0,
                    False,
                )
                if not ok:
                    return
            else:
                cfn = files[0]
        else:
            return

        self.codecoverage = PyCoverageDialog()
        self.codecoverage.show()
        self.codecoverage.start(cfn, fn)

    def __showProfileData(self):
        """
        Private method to handle the show profile data context menu action.
        """
        from eric7.DataViews.PyProfileDialog import PyProfileDialog

        itm = self.model().item(self.currentIndex())
        fn = itm.fileName()
        pfn = self.project.getMainScript(True)

        files = []

        if pfn is not None:
            files.extend(
                [f for f in Utilities.getProfileFileNames(pfn) if f not in files]
            )

        if fn is not None:
            files.extend(
                [f for f in Utilities.getProfileFileNames(fn) if f not in files]
            )

        if files:
            if len(files) > 1:
                pfn, ok = QInputDialog.getItem(
                    None,
                    self.tr("Profile Data"),
                    self.tr("Please select a profile file"),
                    files,
                    0,
                    False,
                )
                if not ok:
                    return
            else:
                pfn = files[0]
        else:
            return

        self.profiledata = PyProfileDialog()
        self.profiledata.show()
        self.profiledata.start(pfn, fn)

    ###########################################################################
    ## Methods for the Graphics submenu
    ###########################################################################

    def __showContextMenuGraphics(self):
        """
        Private slot called before the checks menu is shown.
        """
        self.showMenu.emit("Graphics", self.graphicsMenu)

    def __showClassDiagram(self):
        """
        Private method to handle the class diagram context menu action.
        """
        itm = self.model().item(self.currentIndex())
        try:
            fn = itm.fileName()
        except AttributeError:
            fn = itm.dirName()
        res = EricMessageBox.yesNo(
            self,
            self.tr("Class Diagram"),
            self.tr("""Include class attributes?"""),
            yesDefault=True,
        )

        self.classDiagram = UMLDialog(
            UMLDialogType.CLASS_DIAGRAM, self.project, fn, self, noAttrs=not res
        )
        self.classDiagram.show()

    def __showImportsDiagram(self):
        """
        Private method to handle the imports diagram context menu action.
        """
        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        remotefsInterface = (
            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
        )

        itm = self.model().item(self.currentIndex())
        try:
            fn = itm.fileName()
        except AttributeError:
            fn = itm.dirName()
        package = (
            fn
            if remotefsInterface.isdir(fn)
            else (
                remotefsInterface.dirname(fn)
                if isRemote
                else fn if os.path.isdir(fn) else os.path.dirname(fn)
            )
        )
        res = EricMessageBox.yesNo(
            self,
            self.tr("Imports Diagram"),
            self.tr("""Include imports from external modules?"""),
        )

        self.importsDiagram = UMLDialog(
            UMLDialogType.IMPORTS_DIAGRAM,
            self.project,
            package,
            self,
            showExternalImports=res,
        )
        self.importsDiagram.show()

    def __showPackageDiagram(self):
        """
        Private method to handle the package diagram context menu action.
        """
        isRemote = FileSystemUtilities.isRemoteFileName(self.project.getProjectPath())
        remotefsInterface = (
            ericApp().getObject("EricServer").getServiceInterface("FileSystem")
        )

        itm = self.model().item(self.currentIndex())
        try:
            fn = itm.fileName()
        except AttributeError:
            fn = itm.dirName()
        package = (
            fn
            if remotefsInterface.isdir(fn)
            else (
                remotefsInterface.dirname(fn)
                if isRemote
                else fn if os.path.isdir(fn) else os.path.dirname(fn)
            )
        )
        res = EricMessageBox.yesNo(
            self,
            self.tr("Package Diagram"),
            self.tr("""Include class attributes?"""),
            yesDefault=True,
        )

        self.packageDiagram = UMLDialog(
            UMLDialogType.PACKAGE_DIAGRAM, self.project, package, self, noAttrs=not res
        )
        self.packageDiagram.show()

    def __showApplicationDiagram(self):
        """
        Private method to handle the application diagram context menu action.
        """
        res = EricMessageBox.yesNo(
            self,
            self.tr("Application Diagram"),
            self.tr("""Include module names?"""),
            yesDefault=True,
        )

        self.applicationDiagram = UMLDialog(
            UMLDialogType.APPLICATION_DIAGRAM, self.project, self, noModules=not res
        )
        self.applicationDiagram.show()

    def __loadDiagram(self):
        """
        Private slot to load a diagram from file.
        """
        from eric7.Graphics.UMLDialog import UMLDialog, UMLDialogType

        self.loadedDiagram = None
        loadedDiagram = UMLDialog(UMLDialogType.NO_DIAGRAM, self.project, parent=self)
        if loadedDiagram.load():
            self.loadedDiagram = loadedDiagram
            self.loadedDiagram.show(fromFile=True)

    ###########################################################################
    ## Methods for the Start submenu
    ###########################################################################

    def __contextMenuRunScript(self):
        """
        Private method to run the editor script.
        """
        fn = self.model().item(self.currentIndex()).fileName()
        ericApp().getObject("DebugUI").doRun(False, script=fn)

    def __contextMenuDebugScript(self):
        """
        Private method to debug the editor script.
        """
        fn = self.model().item(self.currentIndex()).fileName()
        ericApp().getObject("DebugUI").doDebug(False, script=fn)

    def __contextMenuProfileScript(self):
        """
        Private method to profile the editor script.
        """
        fn = self.model().item(self.currentIndex()).fileName()
        ericApp().getObject("DebugUI").doProfile(False, script=fn)

    def __contextMenuCoverageScript(self):
        """
        Private method to run a coverage test of the editor script.
        """
        fn = self.model().item(self.currentIndex()).fileName()
        ericApp().getObject("DebugUI").doCoverage(False, script=fn)

    ###########################################################################
    ## Methods for the Code Formatting submenu
    ###########################################################################

    def __showContextMenuFormatting(self):
        """
        Private slot called before the Code Formatting menu is shown.
        """
        self.showMenu.emit("Formatting", self.formattingMenu)

    def __performFormatWithBlack(self, action):
        """
        Private method to format the selected project sources using the 'Black' tool.

        Following actions are supported.
        <ul>
        <li>BlackFormattingAction.Format - the code reformatting is performed</li>
        <li>BlackFormattingAction.Check - a check is performed, if code formatting
            is necessary</li>
        <li>BlackFormattingAction.Diff - a unified diff of potential code formatting
            changes is generated</li>
        </ul>

        @param action formatting operation to be performed
        @type BlackFormattingAction
        """
        from eric7.CodeFormatting.BlackConfigurationDialog import (
            BlackConfigurationDialog,
        )
        from eric7.CodeFormatting.BlackFormattingDialog import BlackFormattingDialog

        files = [
            itm.fileName()
            for itm in self.getSelectedItems([BrowserFileItem])
            if itm.isPython3File()
        ]
        if not files:
            # called for a directory
            itm = self.model().item(self.currentIndex())
            dirName = itm.dirName()
            files = [
                f
                for f in self.project.getProjectFiles("SOURCES", normalized=True)
                if f.startswith(dirName)
            ]

        vm = ericApp().getObject("ViewManager")
        files = [fn for fn in files if vm.checkFileDirty(fn)]

        if files:
            dlg = BlackConfigurationDialog(withProject=True, parent=self)
            if dlg.exec() == QDialog.DialogCode.Accepted:
                config = dlg.getConfiguration()

                formattingDialog = BlackFormattingDialog(
                    config, files, project=self.project, action=action, parent=self
                )
                formattingDialog.exec()
        else:
            EricMessageBox.information(
                self,
                self.tr("Code Formatting"),
                self.tr("""There are no files left for reformatting."""),
            )

    def __performImportSortingWithIsort(self, action):
        """
        Private method to sort the import statements of the selected project sources
        using the 'isort' tool.

        Following actions are supported.
        <ul>
        <li>IsortFormattingAction.Sort - the import statement sorting is performed</li>
        <li>IsortFormattingAction.Check - a check is performed, if import statement
            resorting is necessary</li>
        <li>IsortFormattingAction.Diff - a unified diff of potential import statement
            changes is generated</li>
        </ul>

        @param action sorting operation to be performed
        @type IsortFormattingAction
        """
        from eric7.CodeFormatting.IsortConfigurationDialog import (
            IsortConfigurationDialog,
        )
        from eric7.CodeFormatting.IsortFormattingDialog import IsortFormattingDialog

        files = [
            itm.fileName()
            for itm in self.getSelectedItems([BrowserFileItem])
            if itm.isPython3File()
        ]
        if not files:
            # called for a directory
            itm = self.model().item(self.currentIndex())
            dirName = itm.dirName()
            files = [
                f
                for f in self.project.getProjectFiles("SOURCES", normalized=True)
                if f.startswith(dirName)
            ]

        vm = ericApp().getObject("ViewManager")
        files = [fn for fn in files if vm.checkFileDirty(fn)]

        if files:
            dlg = IsortConfigurationDialog(withProject=True, parent=self)
            if dlg.exec() == QDialog.DialogCode.Accepted:
                config = dlg.getConfiguration()

                formattingDialog = IsortFormattingDialog(
                    config, files, project=self.project, action=action, parent=self
                )
                formattingDialog.exec()
        else:
            EricMessageBox.information(
                self,
                self.tr("Import Sorting"),
                self.tr("""There are no files left for import statement sorting."""),
            )

eric ide

mercurial