ExtensionProtobuf/ProjectProtocolsBrowser.py

Mon, 11 Dec 2023 11:01:10 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 11 Dec 2023 11:01:10 +0100
changeset 34
41070d80eb1f
parent 27
5e9a61e7d7d0
child 40
c187d961ee3a
permissions
-rw-r--r--

- Added context menu entries to show the directory path of an item in an external file manager.
- Added an entry to the background context menu to show the project directory in an external file manager.

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

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

"""
Module implementing the a class used to display the protocols (protobuf) part
of the project.
"""

import contextlib
import functools
import glob
import os

from PyQt6.QtCore import QProcess, QThread, pyqtSignal
from PyQt6.QtWidgets import QApplication, QDialog, QMenu

from eric7 import Preferences
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.EricWidgets.EricProgressDialog import EricProgressDialog
from eric7.Project.FileCategoryRepositoryItem import FileCategoryRepositoryItem
from eric7.Project.ProjectBaseBrowser import ProjectBaseBrowser
from eric7.Project.ProjectBrowserModel import (
    ProjectBrowserDirectoryItem,
    ProjectBrowserFileItem,
    ProjectBrowserSimpleDirectoryItem,
)
from eric7.Project.ProjectBrowserRepositoryItem import ProjectBrowserRepositoryItem
from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
from eric7.UI.BrowserModel import (
    BrowserClassAttributeItem,
    BrowserClassItem,
    BrowserFileItem,
    BrowserMethodItem,
)
from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
from eric7.UI.NotificationWidget import NotificationTypes


class ProjectProtocolsBrowser(ProjectBaseBrowser):
    """
    A class used to display the protocols (protobuf) part of the project.

    @signal appendStdout(str) emitted after something was received from
        a QProcess on stdout
    @signal appendStderr(str) emitted after something was received from
        a QProcess on stderr
    @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.
    """

    appendStdout = pyqtSignal(str)
    appendStderr = pyqtSignal(str)
    showMenu = pyqtSignal(str, QMenu)

    FileFilter = "proto"

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

        @param plugin reference to the plugin object
        @type ProtobufExtensionPlugin
        @param parent parent widget of this browser
        @type QWidget
        """
        project = ericApp().getObject("Project")
        projectBrowser = ericApp().getObject("ProjectBrowser")

        ProjectBaseBrowser.__init__(self, project, self.FileFilter, parent)

        self.selectedItemsFilter = [
            ProjectBrowserFileItem,
            ProjectBrowserSimpleDirectoryItem,
        ]

        self.setWindowTitle(self.tr("Protocols (protobuf)"))

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

        self.__plugin = plugin

        # Add the file category handled by the browser.
        project.addFileCategory(
            "PROTOCOLS",
            FileCategoryRepositoryItem(
                fileCategoryFilterTemplate=self.tr("Protobuf Files ({0})"),
                fileCategoryUserString=self.tr("Protobuf Files"),
                fileCategoryTyeString=self.tr("Protobuf Files"),
                fileCategoryExtensions=["*.proto"],
            ),
        )

        # Add the project browser type to the browser type repository.
        projectBrowser.addTypedProjectBrowser(
            "protocols",
            ProjectBrowserRepositoryItem(
                projectBrowser=self,
                projectBrowserUserString=self.tr("Protocols (protobuf) Browser"),
                priority=50,
                fileCategory="PROTOCOLS",
                fileFilter=self.FileFilter,
                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.appendStderr.connect(projectBrowser.appendStderr)
        self.appendStdout.connect(projectBrowser.appendStdout)
        self.closeSourceWindow.connect(projectBrowser.closeSourceWindow)
        self.sourceFile[str].connect(projectBrowser.sourceFile[str])
        self.sourceFile[str, int].connect(projectBrowser.sourceFile[str, int])

    def deactivate(self):
        """
        Public method to deactivate the browser.
        """
        project = ericApp().getObject("Project")
        projectBrowser = ericApp().getObject("ProjectBrowser")

        # Disconnect some of our own signals.
        self.appendStderr.disconnect(projectBrowser.appendStderr)
        self.appendStdout.disconnect(projectBrowser.appendStdout)
        self.closeSourceWindow.disconnect(projectBrowser.closeSourceWindow)
        self.sourceFile[str].disconnect(projectBrowser.sourceFile[str])
        self.sourceFile[str, int].disconnect(projectBrowser.sourceFile[str, int])

        # Disconnect signals of ProjectBrowser.
        projectBrowser.preferencesChanged.disconnect(self.handlePreferencesChanged)

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

        # Remove the project browser type from the browser type repository.
        projectBrowser.removeTypedProjectBrowser("protocols")

        # Remove the file category handled by the browser.
        project.removeFileCategory("PROTOCOLS")

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

        @return icon for the browser
        @rtype QIcon
        """
        return EricPixmapCache.getIcon(
            os.path.join(os.path.dirname(__file__), "icons", "protobuf")
        )

    def _createPopupMenus(self):
        """
        Protected overloaded method to generate the popup menu.
        """
        self.menuActions = []
        self.multiMenuActions = []
        self.dirMenuActions = []
        self.dirMultiMenuActions = []

        self.sourceMenu = QMenu(self)
        self.sourceMenu.addAction(self.tr("Compile protocol"), self.__compileProtocol)
        self.sourceMenu.addAction(
            self.tr("Compile all protocols"), self.__compileAllProtocols
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(
            self.tr("Compile protocol as gRPC"),
            functools.partial(self.__compileProtocol, grpc=True),
        )
        self.sourceMenu.addAction(
            self.tr("Compile all protocols as gRPC"),
            functools.partial(self.__compileAllProtocols, grpc=True),
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Open"), self._openItem)
        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 protocol file..."), self.__addNewProtocolFile
        )
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(self.tr("Add protocols..."), self.__addProtocolFiles)
        self.sourceMenu.addAction(
            self.tr("Add protocols directory..."), self.__addProtocolsDirectory
        )
        self.sourceMenu.addSeparator()
        with contextlib.suppress(AttributeError):
            # eric7 > 23.12
            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.sourceMenu.addAction(
            self.tr("Configure Protobuf..."), self.__configureProtobuf
        )

        self.menu = QMenu(self)
        self.menu.addAction(self.tr("Compile protocol"), self.__compileProtocol)
        self.menu.addAction(
            self.tr("Compile all protocols"), self.__compileAllProtocols
        )
        self.menu.addSeparator()
        self.menu.addAction(
            self.tr("Compile protocol as gRPC"),
            functools.partial(self.__compileProtocol, grpc=True),
        )
        self.menu.addAction(
            self.tr("Compile all protocols as gRPC"),
            functools.partial(self.__compileAllProtocols, grpc=True),
        )
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Open"), self._openItem)
        self.menu.addSeparator()
        self.menu.addAction(self.tr("New protocol file..."), self.__addNewProtocolFile)
        self.menu.addSeparator()
        self.menu.addAction(self.tr("Add protocols..."), self.__addProtocolFiles)
        self.menu.addAction(
            self.tr("Add protocols directory..."), self.__addProtocolsDirectory
        )
        self.menu.addSeparator()
        with contextlib.suppress(AttributeError):
            # eric7 > 23.12
            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)
        self.menu.addAction(self.tr("Configure Protobuf..."), self.__configureProtobuf)

        self.backMenu = QMenu(self)
        self.backMenu.addAction(
            self.tr("Compile all protocols"), self.__compileAllProtocols
        )
        self.backMenu.addSeparator()
        self.backMenu.addAction(
            self.tr("Compile all protocols as gRPC"),
            functools.partial(self.__compileAllProtocols, grpc=True),
        )
        self.backMenu.addSeparator()
        self.backMenu.addAction(
            self.tr("New protocol file..."), self.__addNewProtocolFile
        )
        self.backMenu.addSeparator()
        self.backMenu.addAction(
            self.tr("Add protocols..."), lambda: self.project.addFiles("PROTOCOLS")
        )
        self.backMenu.addAction(
            self.tr("Add protocols directory..."),
            lambda: self.project.addDirectory("PROTOCOLS"),
        )
        self.backMenu.addSeparator()
        with contextlib.suppress(AttributeError):
            # eric7 > 23.12
            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.addAction(
            self.tr("Configure Protobuf..."), self.__configureProtobuf
        )
        self.backMenu.setEnabled(False)

        # create the menu for multiple selected files
        self.multiMenu = QMenu(self)
        self.multiMenu.addAction(
            self.tr("Compile protocols"), self.__compileSelectedProtocols
        )
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(
            self.tr("Compile protocols as gRPC"),
            functools.partial(self.__compileSelectedProtocols, grpc=True),
        )
        self.multiMenu.addSeparator()
        self.multiMenu.addAction(self.tr("Open"), self._openItem)
        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.multiMenu.addAction(
            self.tr("Configure Protobuf..."), self.__configureProtobuf
        )

        self.dirMenu = QMenu(self)
        self.dirMenu.addAction(
            self.tr("Compile all protocols"), self.__compileAllProtocols
        )
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(
            self.tr("Compile all protocols as gRPC"),
            functools.partial(self.__compileAllProtocols, grpc=True),
        )
        act = self.dirMenu.addAction(self.tr("Remove from project"), self._removeFile)
        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 protocol file..."), self.__addNewProtocolFile
        )
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(self.tr("Add protocols..."), self.__addProtocolFiles)
        self.dirMenu.addAction(
            self.tr("Add protocols directory..."), self.__addProtocolsDirectory
        )
        self.dirMenu.addSeparator()
        with contextlib.suppress(AttributeError):
            # eric7 > 23.12
            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.dirMenu.addAction(
            self.tr("Configure Protobuf..."), self.__configureProtobuf
        )

        self.dirMultiMenu = QMenu(self)
        self.dirMultiMenu.addAction(
            self.tr("Compile all protocols"), self.__compileAllProtocols
        )
        self.dirMultiMenu.addSeparator()
        self.dirMultiMenu.addAction(
            self.tr("Compile all protocols as gRPC"),
            functools.partial(self.__compileAllProtocols, grpc=True),
        )
        self.dirMultiMenu.addAction(
            self.tr("Add protocols..."), lambda: self.project.addFiles("PROTOCOLS")
        )
        self.dirMultiMenu.addAction(
            self.tr("Add protocols directory..."),
            lambda: self.project.addDirectory("PROTOCOLS"),
        )
        self.dirMultiMenu.addSeparator()
        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.dirMultiMenu.addAction(
            self.tr("Configure Protobuf..."), self.__configureProtobuf
        )

        self.sourceMenu.aboutToShow.connect(self.__showContextMenu)
        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 (QPoint)
        """
        if not self.project.isOpen():
            return

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

            bfcnt = categories[str(ProjectBrowserFileItem)]
            cmcnt = (
                categories[str(BrowserClassItem)] + categories[str(BrowserMethodItem)]
            )
            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):
                            self.sourceMenu.popup(self.mapToGlobal(coord))
                        elif isinstance(itm, (BrowserClassItem, BrowserMethodItem)):
                            self.menu.popup(self.mapToGlobal(coord))
                        else:
                            self.backMenu.popup(self.mapToGlobal(coord))
                    elif sdcnt == 1:
                        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 menu aboutToShow signal.
        """
        ProjectBaseBrowser._showContextMenu(self, self.menu)

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

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

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

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

        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.showMenu.emit("MainBack", self.backMenu)

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

        for itm in itmList:
            if isinstance(itm, BrowserFileItem):
                self.sourceFile[str].emit(itm.fileName())
            elif isinstance(itm, BrowserClassItem):
                self.sourceFile[str, int].emit(itm.fileName(), itm.classObject().lineno)
            elif isinstance(itm, BrowserMethodItem):
                self.sourceFile[str, int].emit(
                    itm.fileName(), itm.functionObject().lineno
                )
            elif isinstance(itm, BrowserClassAttributeItem):
                self.sourceFile[str, int].emit(
                    itm.fileName(), itm.attributeObject().lineno
                )

    def __addNewProtocolFile(self):
        """
        Private method to add a new interface file to the project.
        """
        itm = self.model().item(self.currentIndex())
        if isinstance(
            itm, (ProjectBrowserFileItem, BrowserClassItem, BrowserMethodItem)
        ):
            dn = os.path.dirname(itm.fileName())
        elif isinstance(
            itm, (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
        ):
            dn = itm.dirName()
        else:
            dn = ""

        filename, ok = EricPathPickerDialog.getStrPath(
            self,
            self.tr("New protocol file"),
            self.tr("Enter the path of the new protocol file:"),
            mode=EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE,
            strPath=dn,
            defaultDirectory=dn,
            filters=self.project.getFileCategoryFilters(
                categories=["PROTOCOLS"], withAll=False
            ),
        )
        if ok:
            if not os.path.splitext(filename)[1]:
                filename += ".proto"

            if os.path.exists(filename):
                EricMessageBox.critical(
                    self,
                    self.tr("New protocol file"),
                    self.tr(
                        "<p>The file <b>{0}</b> already exists. The action will be"
                        " aborted.</p>"
                    ).format(filename),
                )
                return

            try:
                with open(filename, "w") as f:
                    f.write("// {0}\n".format(self.project.getRelativePath(filename)))
                    f.write('syntax = "proto2";\n')
            except OSError as err:
                EricMessageBox.critical(
                    self,
                    self.tr("New protocol 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 __addProtocolFiles(self):
        """
        Private method to add protocol files to the project.
        """
        itm = self.model().item(self.currentIndex())
        if isinstance(
            itm, (ProjectBrowserFileItem, BrowserClassItem, BrowserMethodItem)
        ):
            dn = os.path.dirname(itm.fileName())
        elif isinstance(
            itm, (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
        ):
            dn = itm.dirName()
        else:
            dn = None
        self.project.addFiles("PROTOCOLS", dn)

    def __addProtocolsDirectory(self):
        """
        Private method to add protocol files of a directory to the project.
        """
        itm = self.model().item(self.currentIndex())
        if isinstance(
            itm, (ProjectBrowserFileItem, BrowserClassItem, BrowserMethodItem)
        ):
            dn = os.path.dirname(itm.fileName())
        elif isinstance(
            itm, (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem)
        ):
            dn = itm.dirName()
        else:
            dn = None
        self.project.addDirectory("PROTOCOLS", dn)

    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 Protocols"),
            self.tr(
                "Do you really want to delete these protocol 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 to handle the various compile commands
    ###########################################################################

    def __getCompilerCommand(self, grpc):
        """
        Private method to get the compiler command.

        @param grpc flag indicating to get a gRPC command
        @type bool
        @return tuple giving the executable and its parameter list
        @rtype tuple of (str, list of str)
        """
        exe = None
        exeArgs = []

        if grpc:
            env = self.__plugin.getPreferences("grpcPythonEnv")
            exe = ericApp().getObject("VirtualEnvManager").getVirtualenvInterpreter(env)
            if not exe:
                exe = PythonUtilities.getPythonExecutable()
            exeArgs = ["-m", "grpc_tools.protoc"]
        else:
            exe = self.__plugin.getPreferences("protoc")
            if exe == "":
                exe = "protoc.exe" if OSUtilities.isWindowsPlatform() else "protoc"
            if not FileSystemUtilities.isinpath(exe):
                exe = None

        return exe, exeArgs

    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal of the
        protoc process.
        """
        if self.compileProc is None:
            return

        ioEncoding = Preferences.getSystem("IOEncoding")

        self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardOutput)
        while self.compileProc and self.compileProc.canReadLine():
            s = "protoc: "
            output = str(self.compileProc.readLine(), ioEncoding, "replace")
            s += output
            self.appendStdout.emit(s)

    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal of the
        protoc process.
        """
        if self.compileProc is None:
            return

        ioEncoding = Preferences.getSystem("IOEncoding")

        self.compileProc.setReadChannel(QProcess.ProcessChannel.StandardError)
        while self.compileProc and self.compileProc.canReadLine():
            s = "protoc: "
            error = str(self.compileProc.readLine(), ioEncoding, "replace")
            s += error
            self.appendStderr.emit(s)

    def __compileProtoDone(self, exitCode, exitStatus, grpc):
        """
        Private slot to handle the finished signal of the protoc process.

        @param exitCode exit code of the process
        @type int
        @param exitStatus exit status of the process
        @type QProcess.ExitStatus
        @param grpc flag indicating to compile as gRPC files
        @type bool
        """
        self.__compileRunning = False
        ui = ericApp().getObject("UserInterface")
        if exitStatus == QProcess.ExitStatus.NormalExit and exitCode == 0:
            path = os.path.dirname(self.__protoFile)
            fileList = glob.glob(os.path.join(path, "*_pb2.py"))
            if grpc:
                fileList += glob.glob(os.path.join(path, "*_pb2_grpc.py"))
            for file in fileList:
                self.project.appendFile(file)
            if grpc:
                icon = EricPixmapCache.getPixmap("gRPC48")
            else:
                icon = EricPixmapCache.getPixmap("protobuf48")
            ui.showNotification(
                icon,
                self.tr("Protocol Compilation"),
                self.tr("The compilation of the protocol file was successful."),
            )
        else:
            if grpc:
                icon = EricPixmapCache.getPixmap(
                    os.path.join(os.path.dirname(__file__), "icons", "gRPC48")
                )
            else:
                icon = EricPixmapCache.getPixmap(
                    os.path.join(os.path.dirname(__file__), "icons", "protobuf48")
                )
            ui.showNotification(
                icon,
                self.tr("Protocol Compilation"),
                self.tr("The compilation of the protocol file failed."),
                kind=NotificationTypes.CRITICAL,
                timeout=0,
            )
        self.compileProc = None

    def __compileProto(self, fn, noDialog=False, progress=None, grpc=False):
        """
        Private method to compile a .proto file to Python.

        @param fn filename of the .proto file to be compiled
        @type str
        @param noDialog flag indicating silent operations
        @type bool
        @param progress reference to the progress dialog
        @type EricProgressDialog
        @param grpc flag indicating to compile as gRPC files
        @type bool
        @return reference to the compile process
        @rtype QProcess
        """
        exe, exeArgs = self.__getCompilerCommand(grpc)
        if exe:
            self.compileProc = QProcess()
            args = []

            fn = os.path.join(self.project.ppath, fn)
            self.__protoFile = fn

            srcPath = os.path.dirname(fn)
            args.append("--proto_path={0}".format(srcPath))
            args.append("--python_out={0}".format(srcPath))
            if grpc:
                args.append("--grpc_python_out={0}".format(srcPath))
            args.append(fn)

            self.compileProc.finished.connect(
                lambda c, s: self.__compileProtoDone(c, s, grpc)
            )
            self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
            self.compileProc.readyReadStandardError.connect(self.__readStderr)

            self.noDialog = noDialog
            self.compileProc.start(exe, exeArgs + args)
            procStarted = self.compileProc.waitForStarted(5000)
            if procStarted:
                self.__compileRunning = True
                return self.compileProc
            else:
                self.__compileRunning = False
                if progress is not None:
                    progress.cancel()
                EricMessageBox.critical(
                    self,
                    self.tr("Process Generation Error"),
                    self.tr(
                        "<p>Could not start {0}.<br>"
                        "Ensure that it is in the search path.</p>"
                    ).format(exe),
                )
                return None
        else:
            EricMessageBox.critical(
                self,
                self.tr("Compiler Invalid"),
                self.tr("The configured compiler is invalid."),
            )
            return None

    def __compileProtocol(self, grpc=False):
        """
        Private method to compile a protocol to Python.

        @param grpc flag indicating to compile as gRPC files
        @type bool
        """
        if self.__getCompilerCommand(grpc)[0] is not None:
            itm = self.model().item(self.currentIndex())
            fn2 = itm.fileName()
            fn = self.project.getRelativePath(fn2)
            self.__compileProto(fn, grpc=grpc)

    def __compileAllProtocols(self, grpc=False):
        """
        Private method to compile all protocols to Python.

        @param grpc flag indicating to compile as gRPC files
        @type bool
        """
        if self.__getCompilerCommand(grpc)[0] is not None:
            numProtos = len(self.project.getProjectData(dataKey="PROTOCOLS"))
            progress = EricProgressDialog(
                self.tr("Compiling Protocols..."),
                self.tr("Abort"),
                0,
                numProtos,
                self.tr("%v/%m Protocols"),
                self,
            )
            progress.setModal(True)
            progress.setMinimumDuration(0)
            progress.setWindowTitle(self.tr("Protocols"))

            for prog, fn in enumerate(self.project.getProjectData(dataKey="PROTOCOLS")):
                progress.setValue(prog)
                if progress.wasCanceled():
                    break
                proc = self.__compileProto(fn, True, progress, grpc=grpc)
                if proc is not None:
                    while proc.state() == QProcess.ProcessState.Running:
                        QThread.msleep(100)
                        QApplication.processEvents()
                else:
                    break
            progress.setValue(numProtos)

    def __compileSelectedProtocols(self, grpc=False):
        """
        Private method to compile selected protocols to Python.

        @param grpc flag indicating to compile as gRPC files
        @type bool
        """
        if self.__getCompilerCommand(grpc)[0] is not None:
            items = self.getSelectedItems()

            files = [self.project.getRelativePath(itm.fileName()) for itm in items]
            numProtos = len(files)
            progress = EricProgressDialog(
                self.tr("Compiling Protocols..."),
                self.tr("Abort"),
                0,
                numProtos,
                self.tr("%v/%m Protocols"),
                self,
            )
            progress.setModal(True)
            progress.setMinimumDuration(0)
            progress.setWindowTitle(self.tr("Protocols"))

            for prog, fn in enumerate(files):
                progress.setValue(prog)
                if progress.wasCanceled():
                    break
                proc = self.__compileProto(fn, True, progress, grpc=grpc)
                if proc is not None:
                    while proc.state() == QProcess.ProcessState.Running:
                        QThread.msleep(100)
                        QApplication.processEvents()
                else:
                    break
            progress.setValue(numProtos)

    def __configureProtobuf(self):
        """
        Private method to open the configuration dialog.
        """
        ericApp().getObject("UserInterface").showPreferences("protobufPage")

eric ide

mercurial