1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing a class used to display the Sources part of the project. 8""" 9 10import os 11import contextlib 12 13from PyQt5.QtCore import pyqtSignal 14from PyQt5.QtWidgets import QDialog, QInputDialog, QMenu 15 16from E5Gui import E5MessageBox 17 18from UI.BrowserModel import ( 19 BrowserFileItem, BrowserClassItem, BrowserMethodItem, 20 BrowserClassAttributeItem, BrowserImportItem 21) 22 23from .ProjectBrowserModel import ( 24 ProjectBrowserFileItem, ProjectBrowserSimpleDirectoryItem, 25 ProjectBrowserDirectoryItem, ProjectBrowserSourceType 26) 27from .ProjectBaseBrowser import ProjectBaseBrowser 28 29import Utilities 30import UI.PixmapCache 31 32 33class ProjectSourcesBrowser(ProjectBaseBrowser): 34 """ 35 A class used to display the Sources part of the project. 36 37 @signal showMenu(str, QMenu) emitted when a menu is about to be shown. 38 The name of the menu and a reference to the menu are given. 39 """ 40 showMenu = pyqtSignal(str, QMenu) 41 42 def __init__(self, project, parent=None): 43 """ 44 Constructor 45 46 @param project reference to the project object 47 @param parent parent widget of this browser (QWidget) 48 """ 49 ProjectBaseBrowser.__init__(self, project, ProjectBrowserSourceType, 50 parent) 51 52 self.selectedItemsFilter = [ProjectBrowserFileItem, 53 ProjectBrowserSimpleDirectoryItem] 54 55 self.setWindowTitle(self.tr('Sources')) 56 57 self.setWhatsThis(self.tr( 58 """<b>Project Sources Browser</b>""" 59 """<p>This allows to easily see all sources contained in the""" 60 """ current project. Several actions can be executed via the""" 61 """ context menu.</p>""" 62 )) 63 64 project.prepareRepopulateItem.connect(self._prepareRepopulateItem) 65 project.completeRepopulateItem.connect(self._completeRepopulateItem) 66 67 self.codemetrics = None 68 self.codecoverage = None 69 self.profiledata = None 70 self.classDiagram = None 71 self.importsDiagram = None 72 self.packageDiagram = None 73 self.applicationDiagram = None 74 self.loadedDiagram = None 75 76 def __closeAllWindows(self): 77 """ 78 Private method to close all project related windows. 79 """ 80 self.codemetrics and self.codemetrics.close() 81 self.codecoverage and self.codecoverage.close() 82 self.profiledata and self.profiledata.close() 83 self.classDiagram and self.classDiagram.close() 84 self.importsDiagram and self.importsDiagram.close() 85 self.packageDiagram and self.packageDiagram.close() 86 self.applicationDiagram and self.applicationDiagram.close() 87 self.loadedDiagram and self.loadedDiagram.close() 88 89 def _projectClosed(self): 90 """ 91 Protected slot to handle the projectClosed signal. 92 """ 93 self.__closeAllWindows() 94 ProjectBaseBrowser._projectClosed(self) 95 96 def _createPopupMenus(self): 97 """ 98 Protected overloaded method to generate the popup menu. 99 """ 100 ProjectBaseBrowser._createPopupMenus(self) 101 self.sourceMenuActions = {} 102 103 if self.project.isPythonProject(): 104 self.__createPythonPopupMenus() 105 elif self.project.isRubyProject(): 106 self.__createRubyPopupMenus() 107 elif self.project.isJavaScriptProject(): 108 self.__createJavaScriptPopupMenus() 109 else: 110 # assign generic source menu 111 self.mainMenu = self.sourceMenu 112 113 def __createPythonPopupMenus(self): 114 """ 115 Private method to generate the popup menus for a Python project. 116 """ 117 self.checksMenu = QMenu(self.tr('Check')) 118 self.checksMenu.aboutToShow.connect(self.__showContextMenuCheck) 119 120 self.menuShow = QMenu(self.tr('Show')) 121 self.menuShow.addAction( 122 self.tr('Code metrics...'), self.__showCodeMetrics) 123 self.coverageMenuAction = self.menuShow.addAction( 124 self.tr('Code coverage...'), self.__showCodeCoverage) 125 self.profileMenuAction = self.menuShow.addAction( 126 self.tr('Profile data...'), self.__showProfileData) 127 self.menuShow.aboutToShow.connect(self.__showContextMenuShow) 128 129 self.graphicsMenu = QMenu(self.tr('Diagrams')) 130 self.classDiagramAction = self.graphicsMenu.addAction( 131 self.tr("Class Diagram..."), self.__showClassDiagram) 132 self.graphicsMenu.addAction( 133 self.tr("Package Diagram..."), self.__showPackageDiagram) 134 self.importsDiagramAction = self.graphicsMenu.addAction( 135 self.tr("Imports Diagram..."), self.__showImportsDiagram) 136 self.graphicsMenu.addAction( 137 self.tr("Application Diagram..."), 138 self.__showApplicationDiagram) 139 self.graphicsMenu.addSeparator() 140 self.graphicsMenu.addAction( 141 UI.PixmapCache.getIcon("open"), 142 self.tr("Load Diagram..."), self.__loadDiagram) 143 self.graphicsMenu.aboutToShow.connect(self.__showContextMenuGraphics) 144 145 self.unittestAction = self.sourceMenu.addAction( 146 self.tr('Run unittest...'), self.handleUnittest) 147 self.sourceMenu.addSeparator() 148 act = self.sourceMenu.addAction( 149 self.tr('Rename file'), self._renameFile) 150 self.menuActions.append(act) 151 act = self.sourceMenu.addAction( 152 self.tr('Remove from project'), self._removeFile) 153 self.menuActions.append(act) 154 act = self.sourceMenu.addAction( 155 self.tr('Delete'), self.__deleteFile) 156 self.menuActions.append(act) 157 self.sourceMenu.addSeparator() 158 self.sourceMenu.addAction( 159 self.tr('New package...'), self.__addNewPackage) 160 self.sourceMenu.addAction( 161 self.tr('Add source files...'), self.__addSourceFiles) 162 self.sourceMenu.addAction( 163 self.tr('Add source directory...'), self.__addSourceDirectory) 164 self.sourceMenu.addSeparator() 165 act = self.sourceMenu.addMenu(self.graphicsMenu) 166 self.sourceMenu.addSeparator() 167 self.sourceMenu.addMenu(self.checksMenu) 168 self.sourceMenu.addSeparator() 169 self.sourceMenuActions["Show"] = self.sourceMenu.addMenu(self.menuShow) 170 self.sourceMenu.addSeparator() 171 self.sourceMenu.addAction( 172 self.tr('Copy Path to Clipboard'), self._copyToClipboard) 173 self.sourceMenu.addSeparator() 174 self.sourceMenu.addAction( 175 self.tr('Expand all directories'), self._expandAllDirs) 176 self.sourceMenu.addAction( 177 self.tr('Collapse all directories'), self._collapseAllDirs) 178 self.sourceMenu.addSeparator() 179 self.sourceMenu.addAction(self.tr('Configure...'), self._configure) 180 181 self.menu.addSeparator() 182 self.menu.addAction( 183 self.tr('New package...'), self.__addNewPackage) 184 self.menu.addAction( 185 self.tr('Add source files...'), self.__addSourceFiles) 186 self.menu.addAction( 187 self.tr('Add source directory...'), self.__addSourceDirectory) 188 self.menu.addSeparator() 189 self.menu.addAction( 190 self.tr('Expand all directories'), self._expandAllDirs) 191 self.menu.addAction( 192 self.tr('Collapse all directories'), self._collapseAllDirs) 193 self.menu.addSeparator() 194 self.menu.addAction(self.tr('Configure...'), self._configure) 195 196 # create the attribute menu 197 self.gotoMenu = QMenu(self.tr("Goto"), self) 198 self.gotoMenu.aboutToShow.connect(self._showGotoMenu) 199 self.gotoMenu.triggered.connect(self._gotoAttribute) 200 201 self.attributeMenu = QMenu(self) 202 self.attributeMenu.addMenu(self.gotoMenu) 203 self.attributeMenu.addSeparator() 204 self.attributeMenu.addAction( 205 self.tr('New package...'), self.__addNewPackage) 206 self.attributeMenu.addAction( 207 self.tr('Add source files...'), self.project.addSourceFiles) 208 self.attributeMenu.addAction( 209 self.tr('Add source directory...'), self.project.addSourceDir) 210 self.attributeMenu.addSeparator() 211 self.attributeMenu.addAction( 212 self.tr('Expand all directories'), self._expandAllDirs) 213 self.attributeMenu.addAction( 214 self.tr('Collapse all directories'), self._collapseAllDirs) 215 self.attributeMenu.addSeparator() 216 self.attributeMenu.addAction( 217 self.tr('Configure...'), self._configure) 218 219 self.backMenu = QMenu(self) 220 self.backMenu.addAction( 221 self.tr('New package...'), self.__addNewPackage) 222 self.backMenu.addAction( 223 self.tr('Add source files...'), self.project.addSourceFiles) 224 self.backMenu.addAction( 225 self.tr('Add source directory...'), self.project.addSourceDir) 226 self.backMenu.addSeparator() 227 self.backMenu.addAction( 228 self.tr('Expand all directories'), self._expandAllDirs) 229 self.backMenu.addAction( 230 self.tr('Collapse all directories'), self._collapseAllDirs) 231 self.backMenu.addSeparator() 232 self.backMenu.addAction(self.tr('Configure...'), self._configure) 233 self.backMenu.setEnabled(False) 234 235 self.multiMenu.addSeparator() 236 act = self.multiMenu.addAction( 237 self.tr('Remove from project'), self._removeFile) 238 self.multiMenuActions.append(act) 239 act = self.multiMenu.addAction( 240 self.tr('Delete'), self.__deleteFile) 241 self.multiMenuActions.append(act) 242 self.multiMenu.addSeparator() 243 self.multiMenu.addMenu(self.checksMenu) 244 self.multiMenu.addSeparator() 245 self.multiMenu.addAction( 246 self.tr('Expand all directories'), self._expandAllDirs) 247 self.multiMenu.addAction( 248 self.tr('Collapse all directories'), self._collapseAllDirs) 249 self.multiMenu.addSeparator() 250 self.multiMenu.addAction(self.tr('Configure...'), self._configure) 251 252 self.dirMenu = QMenu(self) 253 act = self.dirMenu.addAction( 254 self.tr('Remove from project'), self._removeDir) 255 self.dirMenuActions.append(act) 256 act = self.dirMenu.addAction( 257 self.tr('Delete'), self._deleteDirectory) 258 self.dirMenuActions.append(act) 259 self.dirMenu.addSeparator() 260 self.dirMenu.addAction( 261 self.tr('New package...'), self.__addNewPackage) 262 self.dirMenu.addAction( 263 self.tr('Add source files...'), self.__addSourceFiles) 264 self.dirMenu.addAction( 265 self.tr('Add source directory...'), self.__addSourceDirectory) 266 self.dirMenu.addSeparator() 267 act = self.dirMenu.addMenu(self.graphicsMenu) 268 self.dirMenu.addSeparator() 269 self.dirMenu.addMenu(self.checksMenu) 270 self.dirMenu.addSeparator() 271 self.dirMenu.addAction( 272 self.tr('Copy Path to Clipboard'), self._copyToClipboard) 273 self.dirMenu.addSeparator() 274 self.dirMenu.addAction( 275 self.tr('Expand all directories'), self._expandAllDirs) 276 self.dirMenu.addAction( 277 self.tr('Collapse all directories'), self._collapseAllDirs) 278 self.dirMenu.addSeparator() 279 self.dirMenu.addAction(self.tr('Configure...'), self._configure) 280 281 self.dirMultiMenu = QMenu(self) 282 self.dirMultiMenu.addAction( 283 self.tr('Expand all directories'), self._expandAllDirs) 284 self.dirMultiMenu.addAction( 285 self.tr('Collapse all directories'), self._collapseAllDirs) 286 self.dirMultiMenu.addSeparator() 287 self.dirMultiMenu.addAction( 288 self.tr('Configure...'), self._configure) 289 290 self.sourceMenu.aboutToShow.connect(self.__showContextMenu) 291 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) 292 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) 293 self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) 294 self.backMenu.aboutToShow.connect(self.__showContextMenuBack) 295 self.mainMenu = self.sourceMenu 296 297 def __createRubyPopupMenus(self): 298 """ 299 Private method to generate the popup menus for a Ruby project. 300 """ 301 self.graphicsMenu = QMenu(self.tr('Diagrams')) 302 self.classDiagramAction = self.graphicsMenu.addAction( 303 self.tr("Class Diagram..."), self.__showClassDiagram) 304 self.graphicsMenu.addAction( 305 self.tr("Package Diagram..."), self.__showPackageDiagram) 306 self.graphicsMenu.addAction( 307 self.tr("Application Diagram..."), 308 self.__showApplicationDiagram) 309 self.graphicsMenu.addSeparator() 310 self.graphicsMenu.addAction( 311 UI.PixmapCache.getIcon("fileOpen"), 312 self.tr("Load Diagram..."), self.__loadDiagram) 313 314 self.sourceMenu.addSeparator() 315 act = self.sourceMenu.addAction( 316 self.tr('Rename file'), self._renameFile) 317 self.menuActions.append(act) 318 act = self.sourceMenu.addAction( 319 self.tr('Remove from project'), self._removeFile) 320 self.menuActions.append(act) 321 act = self.sourceMenu.addAction( 322 self.tr('Delete'), self.__deleteFile) 323 self.menuActions.append(act) 324 self.sourceMenu.addSeparator() 325 self.sourceMenu.addAction( 326 self.tr('Add source files...'), self.__addSourceFiles) 327 self.sourceMenu.addAction( 328 self.tr('Add source directory...'), self.__addSourceDirectory) 329 self.sourceMenu.addSeparator() 330 act = self.sourceMenu.addMenu(self.graphicsMenu) 331 self.sourceMenu.addSeparator() 332 self.sourceMenu.addAction( 333 self.tr('Expand all directories'), self._expandAllDirs) 334 self.sourceMenu.addAction( 335 self.tr('Collapse all directories'), self._collapseAllDirs) 336 self.sourceMenu.addSeparator() 337 self.sourceMenu.addAction(self.tr('Configure...'), self._configure) 338 339 self.menu.addSeparator() 340 self.menu.addAction( 341 self.tr('Add source files...'), self.__addSourceFiles) 342 self.menu.addAction( 343 self.tr('Add source directory...'), self.__addSourceDirectory) 344 self.menu.addSeparator() 345 self.menu.addAction( 346 self.tr('Expand all directories'), self._expandAllDirs) 347 self.menu.addAction( 348 self.tr('Collapse all directories'), self._collapseAllDirs) 349 self.menu.addSeparator() 350 self.menu.addAction(self.tr('Configure...'), self._configure) 351 352 # create the attribute menu 353 self.gotoMenu = QMenu(self.tr("Goto"), self) 354 self.gotoMenu.aboutToShow.connect(self._showGotoMenu) 355 self.gotoMenu.triggered.connect(self._gotoAttribute) 356 357 self.attributeMenu = QMenu(self) 358 self.attributeMenu.addMenu(self.gotoMenu) 359 self.attributeMenu.addSeparator() 360 self.attributeMenu.addAction( 361 self.tr('Add source files...'), self.project.addSourceFiles) 362 self.attributeMenu.addAction( 363 self.tr('Add source directory...'), self.project.addSourceDir) 364 self.attributeMenu.addSeparator() 365 self.attributeMenu.addAction( 366 self.tr('Expand all directories'), self._expandAllDirs) 367 self.attributeMenu.addAction( 368 self.tr('Collapse all directories'), self._collapseAllDirs) 369 self.attributeMenu.addSeparator() 370 self.attributeMenu.addAction( 371 self.tr('Configure...'), self._configure) 372 373 self.backMenu = QMenu(self) 374 self.backMenu.addAction( 375 self.tr('Add source files...'), self.project.addSourceFiles) 376 self.backMenu.addAction( 377 self.tr('Add source directory...'), self.project.addSourceDir) 378 self.backMenu.addSeparator() 379 self.backMenu.addAction( 380 self.tr('Expand all directories'), self._expandAllDirs) 381 self.backMenu.addAction( 382 self.tr('Collapse all directories'), self._collapseAllDirs) 383 self.backMenu.setEnabled(False) 384 self.backMenu.addSeparator() 385 self.backMenu.addAction(self.tr('Configure...'), self._configure) 386 387 self.multiMenu.addSeparator() 388 act = self.multiMenu.addAction( 389 self.tr('Remove from project'), self._removeFile) 390 self.multiMenuActions.append(act) 391 act = self.multiMenu.addAction( 392 self.tr('Delete'), self.__deleteFile) 393 self.multiMenuActions.append(act) 394 self.multiMenu.addSeparator() 395 self.multiMenu.addAction( 396 self.tr('Expand all directories'), self._expandAllDirs) 397 self.multiMenu.addAction( 398 self.tr('Collapse all directories'), self._collapseAllDirs) 399 self.multiMenu.addSeparator() 400 self.multiMenu.addAction(self.tr('Configure...'), self._configure) 401 402 self.dirMenu = QMenu(self) 403 act = self.dirMenu.addAction( 404 self.tr('Remove from project'), self._removeDir) 405 self.dirMenuActions.append(act) 406 self.dirMenu.addSeparator() 407 self.dirMenu.addAction( 408 self.tr('Add source files...'), self.__addSourceFiles) 409 self.dirMenu.addAction( 410 self.tr('Add source directory...'), self.__addSourceDirectory) 411 self.dirMenu.addSeparator() 412 act = self.dirMenu.addMenu(self.graphicsMenu) 413 self.dirMenu.addSeparator() 414 self.dirMenu.addAction( 415 self.tr('Expand all directories'), self._expandAllDirs) 416 self.dirMenu.addAction( 417 self.tr('Collapse all directories'), self._collapseAllDirs) 418 self.dirMenu.addSeparator() 419 self.dirMenu.addAction(self.tr('Configure...'), self._configure) 420 421 self.dirMultiMenu = QMenu(self) 422 self.dirMultiMenu.addAction( 423 self.tr('Expand all directories'), self._expandAllDirs) 424 self.dirMultiMenu.addAction( 425 self.tr('Collapse all directories'), self._collapseAllDirs) 426 self.dirMultiMenu.addSeparator() 427 self.dirMultiMenu.addAction( 428 self.tr('Configure...'), self._configure) 429 430 self.sourceMenu.aboutToShow.connect(self.__showContextMenu) 431 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) 432 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) 433 self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) 434 self.backMenu.aboutToShow.connect(self.__showContextMenuBack) 435 self.mainMenu = self.sourceMenu 436 437 def __createJavaScriptPopupMenus(self): 438 """ 439 Private method to generate the popup menus for a Python project. 440 """ 441 self.checksMenu = QMenu(self.tr('Check')) 442 self.checksMenu.aboutToShow.connect(self.__showContextMenuCheck) 443 444 self.sourceMenu.addSeparator() 445 act = self.sourceMenu.addAction( 446 self.tr('Rename file'), self._renameFile) 447 self.menuActions.append(act) 448 act = self.sourceMenu.addAction( 449 self.tr('Remove from project'), self._removeFile) 450 self.menuActions.append(act) 451 act = self.sourceMenu.addAction( 452 self.tr('Delete'), self.__deleteFile) 453 self.menuActions.append(act) 454 self.sourceMenu.addSeparator() 455 self.sourceMenu.addAction( 456 self.tr('Add source files...'), self.__addSourceFiles) 457 self.sourceMenu.addAction( 458 self.tr('Add source directory...'), self.__addSourceDirectory) 459 self.sourceMenu.addSeparator() 460 self.sourceMenu.addMenu(self.checksMenu) 461 self.sourceMenu.addSeparator() 462 self.sourceMenu.addAction( 463 self.tr('Copy Path to Clipboard'), self._copyToClipboard) 464 self.sourceMenu.addSeparator() 465 self.sourceMenu.addAction( 466 self.tr('Expand all directories'), self._expandAllDirs) 467 self.sourceMenu.addAction( 468 self.tr('Collapse all directories'), self._collapseAllDirs) 469 self.sourceMenu.addSeparator() 470 self.sourceMenu.addAction(self.tr('Configure...'), self._configure) 471 472 self.menu.addSeparator() 473 self.menu.addAction( 474 self.tr('Add source files...'), self.__addSourceFiles) 475 self.menu.addAction( 476 self.tr('Add source directory...'), self.__addSourceDirectory) 477 self.menu.addSeparator() 478 self.menu.addAction( 479 self.tr('Expand all directories'), self._expandAllDirs) 480 self.menu.addAction( 481 self.tr('Collapse all directories'), self._collapseAllDirs) 482 self.menu.addSeparator() 483 self.menu.addAction(self.tr('Configure...'), self._configure) 484 485 # create the attribute menu 486 self.gotoMenu = QMenu(self.tr("Goto"), self) 487 self.gotoMenu.aboutToShow.connect(self._showGotoMenu) 488 self.gotoMenu.triggered.connect(self._gotoAttribute) 489 490 self.attributeMenu = QMenu(self) 491 self.attributeMenu.addMenu(self.gotoMenu) 492 self.attributeMenu.addSeparator() 493 self.attributeMenu.addAction( 494 self.tr('Add source files...'), self.project.addSourceFiles) 495 self.attributeMenu.addAction( 496 self.tr('Add source directory...'), self.project.addSourceDir) 497 self.attributeMenu.addSeparator() 498 self.attributeMenu.addAction( 499 self.tr('Expand all directories'), self._expandAllDirs) 500 self.attributeMenu.addAction( 501 self.tr('Collapse all directories'), self._collapseAllDirs) 502 self.attributeMenu.addSeparator() 503 self.attributeMenu.addAction( 504 self.tr('Configure...'), self._configure) 505 506 self.backMenu = QMenu(self) 507 self.backMenu.addAction( 508 self.tr('Add source files...'), self.project.addSourceFiles) 509 self.backMenu.addAction( 510 self.tr('Add source directory...'), self.project.addSourceDir) 511 self.backMenu.addSeparator() 512 self.backMenu.addAction( 513 self.tr('Expand all directories'), self._expandAllDirs) 514 self.backMenu.addAction( 515 self.tr('Collapse all directories'), self._collapseAllDirs) 516 self.backMenu.addSeparator() 517 self.backMenu.addAction(self.tr('Configure...'), self._configure) 518 self.backMenu.setEnabled(False) 519 520 self.multiMenu.addSeparator() 521 act = self.multiMenu.addAction( 522 self.tr('Remove from project'), self._removeFile) 523 self.multiMenuActions.append(act) 524 act = self.multiMenu.addAction( 525 self.tr('Delete'), self.__deleteFile) 526 self.multiMenuActions.append(act) 527 self.multiMenu.addSeparator() 528 self.multiMenu.addMenu(self.checksMenu) 529 self.multiMenu.addSeparator() 530 self.multiMenu.addAction( 531 self.tr('Expand all directories'), self._expandAllDirs) 532 self.multiMenu.addAction( 533 self.tr('Collapse all directories'), self._collapseAllDirs) 534 self.multiMenu.addSeparator() 535 self.multiMenu.addAction(self.tr('Configure...'), self._configure) 536 537 self.dirMenu = QMenu(self) 538 act = self.dirMenu.addAction( 539 self.tr('Remove from project'), self._removeDir) 540 self.dirMenuActions.append(act) 541 act = self.dirMenu.addAction( 542 self.tr('Delete'), self._deleteDirectory) 543 self.dirMenuActions.append(act) 544 self.dirMenu.addSeparator() 545 self.dirMenu.addAction( 546 self.tr('Add source files...'), self.__addSourceFiles) 547 self.dirMenu.addAction( 548 self.tr('Add source directory...'), self.__addSourceDirectory) 549 self.dirMenu.addSeparator() 550 self.dirMenu.addMenu(self.checksMenu) 551 self.dirMenu.addSeparator() 552 self.dirMenu.addAction( 553 self.tr('Copy Path to Clipboard'), self._copyToClipboard) 554 self.dirMenu.addSeparator() 555 self.dirMenu.addAction( 556 self.tr('Expand all directories'), self._expandAllDirs) 557 self.dirMenu.addAction( 558 self.tr('Collapse all directories'), self._collapseAllDirs) 559 self.dirMenu.addSeparator() 560 self.dirMenu.addAction(self.tr('Configure...'), self._configure) 561 562 self.dirMultiMenu = QMenu(self) 563 self.dirMultiMenu.addAction( 564 self.tr('Expand all directories'), self._expandAllDirs) 565 self.dirMultiMenu.addAction( 566 self.tr('Collapse all directories'), self._collapseAllDirs) 567 self.dirMultiMenu.addSeparator() 568 self.dirMultiMenu.addAction( 569 self.tr('Configure...'), self._configure) 570 571 self.sourceMenu.aboutToShow.connect(self.__showContextMenu) 572 self.multiMenu.aboutToShow.connect(self.__showContextMenuMulti) 573 self.dirMenu.aboutToShow.connect(self.__showContextMenuDir) 574 self.dirMultiMenu.aboutToShow.connect(self.__showContextMenuDirMulti) 575 self.backMenu.aboutToShow.connect(self.__showContextMenuBack) 576 self.mainMenu = self.sourceMenu 577 578 def _contextMenuRequested(self, coord): 579 """ 580 Protected slot to show the context menu. 581 582 @param coord the position of the mouse pointer (QPoint) 583 """ 584 if not self.project.isOpen(): 585 return 586 587 with contextlib.suppress(Exception): 588 categories = self.getSelectedItemsCountCategorized( 589 [ProjectBrowserFileItem, BrowserClassItem, 590 BrowserMethodItem, ProjectBrowserSimpleDirectoryItem, 591 BrowserClassAttributeItem, BrowserImportItem]) 592 cnt = categories["sum"] 593 if cnt <= 1: 594 index = self.indexAt(coord) 595 if index.isValid(): 596 self._selectSingleItem(index) 597 categories = self.getSelectedItemsCountCategorized( 598 [ProjectBrowserFileItem, BrowserClassItem, 599 BrowserMethodItem, ProjectBrowserSimpleDirectoryItem, 600 BrowserClassAttributeItem, BrowserImportItem]) 601 cnt = categories["sum"] 602 603 bfcnt = categories[str(ProjectBrowserFileItem)] 604 cmcnt = ( 605 categories[str(BrowserClassItem)] + 606 categories[str(BrowserMethodItem)] + 607 categories[str(BrowserClassAttributeItem)] + 608 categories[str(BrowserImportItem)] 609 ) 610 sdcnt = categories[str(ProjectBrowserSimpleDirectoryItem)] 611 if cnt > 1 and cnt == bfcnt: 612 self.multiMenu.popup(self.mapToGlobal(coord)) 613 elif cnt > 1 and cnt == sdcnt: 614 self.dirMultiMenu.popup(self.mapToGlobal(coord)) 615 else: 616 index = self.indexAt(coord) 617 if cnt == 1 and index.isValid(): 618 if bfcnt == 1 or cmcnt == 1: 619 itm = self.model().item(index) 620 if isinstance(itm, ProjectBrowserFileItem): 621 fn = itm.fileName() 622 if self.project.isPythonProject(): 623 if fn.endswith('.ptl'): 624 for act in self.sourceMenuActions.values(): 625 act.setEnabled(False) 626 self.classDiagramAction.setEnabled(True) 627 self.importsDiagramAction.setEnabled(True) 628 self.unittestAction.setEnabled(False) 629 self.checksMenu.menuAction().setEnabled( 630 False) 631 elif fn.endswith('.rb'): 632 # entry for mixed mode programs 633 for act in self.sourceMenuActions.values(): 634 act.setEnabled(False) 635 self.classDiagramAction.setEnabled(True) 636 self.importsDiagramAction.setEnabled(False) 637 self.unittestAction.setEnabled(False) 638 self.checksMenu.menuAction().setEnabled( 639 False) 640 elif fn.endswith('.js'): 641 # entry for mixed mode programs 642 for act in self.sourceMenuActions.values(): 643 act.setEnabled(False) 644 self.unittestAction.setEnabled(False) 645 self.checksMenu.menuAction().setEnabled( 646 False) 647 self.graphicsMenu.menuAction().setEnabled( 648 False) 649 else: 650 # assume the source file is a Python file 651 for act in self.sourceMenuActions.values(): 652 act.setEnabled(True) 653 self.classDiagramAction.setEnabled(True) 654 self.importsDiagramAction.setEnabled(True) 655 self.unittestAction.setEnabled(True) 656 self.checksMenu.menuAction().setEnabled( 657 True) 658 self.sourceMenu.popup(self.mapToGlobal(coord)) 659 elif isinstance( 660 itm, 661 (BrowserClassItem, BrowserMethodItem, 662 BrowserImportItem) 663 ): 664 self.menu.popup(self.mapToGlobal(coord)) 665 elif isinstance(itm, BrowserClassAttributeItem): 666 self.attributeMenu.popup(self.mapToGlobal(coord)) 667 else: 668 self.backMenu.popup(self.mapToGlobal(coord)) 669 elif sdcnt == 1: 670 self.classDiagramAction.setEnabled(False) 671 self.dirMenu.popup(self.mapToGlobal(coord)) 672 else: 673 self.backMenu.popup(self.mapToGlobal(coord)) 674 else: 675 self.backMenu.popup(self.mapToGlobal(coord)) 676 677 def __showContextMenu(self): 678 """ 679 Private slot called by the sourceMenu aboutToShow signal. 680 """ 681 ProjectBaseBrowser._showContextMenu(self, self.sourceMenu) 682 683 self.showMenu.emit("Main", self.sourceMenu) 684 685 def __showContextMenuMulti(self): 686 """ 687 Private slot called by the multiMenu aboutToShow signal. 688 """ 689 ProjectBaseBrowser._showContextMenuMulti(self, self.multiMenu) 690 691 self.showMenu.emit("MainMulti", self.multiMenu) 692 693 def __showContextMenuDir(self): 694 """ 695 Private slot called by the dirMenu aboutToShow signal. 696 """ 697 ProjectBaseBrowser._showContextMenuDir(self, self.dirMenu) 698 699 self.showMenu.emit("MainDir", self.dirMenu) 700 701 def __showContextMenuDirMulti(self): 702 """ 703 Private slot called by the dirMultiMenu aboutToShow signal. 704 """ 705 ProjectBaseBrowser._showContextMenuDirMulti(self, self.dirMultiMenu) 706 707 self.showMenu.emit("MainDirMulti", self.dirMultiMenu) 708 709 def __showContextMenuBack(self): 710 """ 711 Private slot called by the backMenu aboutToShow signal. 712 """ 713 ProjectBaseBrowser._showContextMenuBack(self, self.backMenu) 714 715 self.showMenu.emit("MainBack", self.backMenu) 716 717 def __showContextMenuShow(self): 718 """ 719 Private slot called before the show menu is shown. 720 """ 721 prEnable = False 722 coEnable = False 723 724 # first check if the file belongs to a project and there is 725 # a project coverage file 726 fn = self.project.getMainScript(True) 727 if fn is not None: 728 tfn = Utilities.getTestFileName(fn) 729 basename = os.path.splitext(fn)[0] 730 tbasename = os.path.splitext(tfn)[0] 731 prEnable = ( 732 prEnable or 733 os.path.isfile("{0}.profile".format(basename)) or 734 os.path.isfile("{0}.profile".format(tbasename)) 735 ) 736 coEnable = ( 737 (coEnable or 738 os.path.isfile("{0}.coverage".format(basename)) or 739 os.path.isfile("{0}.coverage".format(tbasename))) and 740 self.project.isPy3Project() 741 ) 742 743 # now check the selected item 744 itm = self.model().item(self.currentIndex()) 745 fn = itm.fileName() 746 if fn is not None: 747 basename = os.path.splitext(fn)[0] 748 prEnable = ( 749 prEnable or 750 os.path.isfile("{0}.profile".format(basename)) 751 ) 752 coEnable = ( 753 (coEnable or 754 os.path.isfile("{0}.coverage".format(basename))) and 755 itm.isPython3File() 756 ) 757 758 self.profileMenuAction.setEnabled(prEnable) 759 self.coverageMenuAction.setEnabled(coEnable) 760 761 self.showMenu.emit("Show", self.menuShow) 762 763 def _openItem(self): 764 """ 765 Protected slot to handle the open popup menu entry. 766 """ 767 itmList = self.getSelectedItems( 768 [BrowserFileItem, BrowserClassItem, BrowserMethodItem, 769 BrowserClassAttributeItem, BrowserImportItem]) 770 771 for itm in itmList: 772 if isinstance(itm, BrowserFileItem): 773 if itm.isPython3File(): 774 self.sourceFile[str].emit(itm.fileName()) 775 elif itm.isRubyFile(): 776 self.sourceFile[str, int, str].emit( 777 itm.fileName(), -1, "Ruby") 778 elif itm.isDFile(): 779 self.sourceFile[str, int, str].emit( 780 itm.fileName(), -1, "D") 781 else: 782 self.sourceFile[str].emit(itm.fileName()) 783 elif isinstance(itm, BrowserClassItem): 784 self.sourceFile[str, int].emit( 785 itm.fileName(), itm.classObject().lineno) 786 elif isinstance(itm, BrowserMethodItem): 787 self.sourceFile[str, int].emit( 788 itm.fileName(), itm.functionObject().lineno) 789 elif isinstance(itm, BrowserClassAttributeItem): 790 self.sourceFile[str, int].emit( 791 itm.fileName(), itm.attributeObject().lineno) 792 elif isinstance(itm, BrowserImportItem): 793 self.sourceFile[str, list].emit( 794 itm.fileName(), itm.linenos()) 795 796 def __addNewPackage(self): 797 """ 798 Private method to add a new package to the project. 799 """ 800 itm = self.model().item(self.currentIndex()) 801 if isinstance( 802 itm, 803 (ProjectBrowserFileItem, BrowserClassItem, BrowserMethodItem) 804 ): 805 dn = os.path.dirname(itm.fileName()) 806 elif isinstance( 807 itm, 808 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) 809 ): 810 dn = itm.dirName() 811 else: 812 dn = "" 813 814 dn = self.project.getRelativePath(dn) 815 if dn.startswith(os.sep): 816 dn = dn[1:] 817 from .NewPythonPackageDialog import NewPythonPackageDialog 818 dlg = NewPythonPackageDialog(dn, self) 819 if dlg.exec() == QDialog.DialogCode.Accepted: 820 packageName = dlg.getData() 821 nameParts = packageName.split(".") 822 packagePath = self.project.ppath 823 packageFile = "" 824 for name in nameParts: 825 packagePath = os.path.join(packagePath, name) 826 if not os.path.exists(packagePath): 827 try: 828 os.mkdir(packagePath) 829 except OSError as err: 830 E5MessageBox.critical( 831 self, 832 self.tr("Add new Python package"), 833 self.tr( 834 """<p>The package directory <b>{0}</b> could""" 835 """ not be created. Aborting...</p>""" 836 """<p>Reason: {1}</p>""") 837 .format(packagePath, str(err))) 838 return 839 packageFile = os.path.join(packagePath, "__init__.py") 840 if not os.path.exists(packageFile): 841 try: 842 with open(packageFile, "w", encoding="utf-8"): 843 pass 844 except OSError as err: 845 E5MessageBox.critical( 846 self, 847 self.tr("Add new Python package"), 848 self.tr( 849 """<p>The package file <b>{0}</b> could""" 850 """ not be created. Aborting...</p>""" 851 """<p>Reason: {1}</p>""") 852 .format(packageFile, str(err))) 853 return 854 self.project.appendFile(packageFile) 855 if packageFile: 856 self.sourceFile[str].emit(packageFile) 857 858 def __addSourceFiles(self): 859 """ 860 Private method to add a source file to the project. 861 """ 862 itm = self.model().item(self.currentIndex()) 863 if isinstance( 864 itm, 865 (ProjectBrowserFileItem, BrowserClassItem, BrowserMethodItem) 866 ): 867 dn = os.path.dirname(itm.fileName()) 868 elif isinstance( 869 itm, 870 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) 871 ): 872 dn = itm.dirName() 873 else: 874 dn = None 875 self.project.addFiles('source', dn) 876 877 def __addSourceDirectory(self): 878 """ 879 Private method to add source files of a directory to the project. 880 """ 881 itm = self.model().item(self.currentIndex()) 882 if isinstance( 883 itm, 884 (ProjectBrowserFileItem, BrowserClassItem, BrowserMethodItem) 885 ): 886 dn = os.path.dirname(itm.fileName()) 887 elif isinstance( 888 itm, 889 (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem) 890 ): 891 dn = itm.dirName() 892 else: 893 dn = None 894 self.project.addDirectory('source', dn) 895 896 def __deleteFile(self): 897 """ 898 Private method to delete files from the project. 899 """ 900 itmList = self.getSelectedItems() 901 902 files = [] 903 fullNames = [] 904 for itm in itmList: 905 fn2 = itm.fileName() 906 fullNames.append(fn2) 907 fn = self.project.getRelativePath(fn2) 908 files.append(fn) 909 910 from UI.DeleteFilesConfirmationDialog import ( 911 DeleteFilesConfirmationDialog 912 ) 913 dlg = DeleteFilesConfirmationDialog( 914 self.parent(), 915 self.tr("Delete files"), 916 self.tr( 917 "Do you really want to delete these files from the project?"), 918 files) 919 920 if dlg.exec() == QDialog.DialogCode.Accepted: 921 for fn2, fn in zip(fullNames, files): 922 self.closeSourceWindow.emit(fn2) 923 self.project.deleteFile(fn) 924 925 ########################################################################### 926 ## Methods for the Checks submenu 927 ########################################################################### 928 929 def __showContextMenuCheck(self): 930 """ 931 Private slot called before the checks menu is shown. 932 """ 933 self.showMenu.emit("Checks", self.checksMenu) 934 935 ########################################################################### 936 ## Methods for the Show submenu 937 ########################################################################### 938 939 def __showCodeMetrics(self): 940 """ 941 Private method to handle the code metrics context menu action. 942 """ 943 itm = self.model().item(self.currentIndex()) 944 fn = itm.fileName() 945 946 from DataViews.CodeMetricsDialog import CodeMetricsDialog 947 self.codemetrics = CodeMetricsDialog() 948 self.codemetrics.show() 949 self.codemetrics.start(fn) 950 951 def __showCodeCoverage(self): 952 """ 953 Private method to handle the code coverage context menu action. 954 """ 955 itm = self.model().item(self.currentIndex()) 956 fn = itm.fileName() 957 pfn = self.project.getMainScript(True) 958 959 files = [] 960 961 if pfn is not None: 962 tpfn = Utilities.getTestFileName(pfn) 963 basename = os.path.splitext(pfn)[0] 964 tbasename = os.path.splitext(tpfn)[0] 965 966 f = "{0}.coverage".format(basename) 967 tf = "{0}.coverage".format(tbasename) 968 if os.path.isfile(f): 969 files.append(f) 970 if os.path.isfile(tf): 971 files.append(tf) 972 973 if fn is not None: 974 tfn = Utilities.getTestFileName(fn) 975 basename = os.path.splitext(fn)[0] 976 tbasename = os.path.splitext(tfn)[0] 977 978 f = "{0}.coverage".format(basename) 979 tf = "{0}.coverage".format(tbasename) 980 if os.path.isfile(f) and f not in files: 981 files.append(f) 982 if os.path.isfile(tf) and tf not in files: 983 files.append(tf) 984 985 if files: 986 if len(files) > 1: 987 pfn, ok = QInputDialog.getItem( 988 None, 989 self.tr("Code Coverage"), 990 self.tr("Please select a coverage file"), 991 files, 992 0, False) 993 if not ok: 994 return 995 else: 996 pfn = files[0] 997 else: 998 return 999 1000 from DataViews.PyCoverageDialog import PyCoverageDialog 1001 self.codecoverage = PyCoverageDialog() 1002 self.codecoverage.show() 1003 self.codecoverage.start(pfn, fn) 1004 1005 def __showProfileData(self): 1006 """ 1007 Private method to handle the show profile data context menu action. 1008 """ 1009 itm = self.model().item(self.currentIndex()) 1010 fn = itm.fileName() 1011 pfn = self.project.getMainScript(True) 1012 1013 files = [] 1014 1015 if pfn is not None: 1016 tpfn = Utilities.getTestFileName(pfn) 1017 basename = os.path.splitext(pfn)[0] 1018 tbasename = os.path.splitext(tpfn)[0] 1019 1020 f = "{0}.profile".format(basename) 1021 tf = "{0}.profile".format(tbasename) 1022 if os.path.isfile(f): 1023 files.append(f) 1024 if os.path.isfile(tf): 1025 files.append(tf) 1026 1027 if fn is not None: 1028 tfn = Utilities.getTestFileName(fn) 1029 basename = os.path.splitext(fn)[0] 1030 tbasename = os.path.splitext(tfn)[0] 1031 1032 f = "{0}.profile".format(basename) 1033 tf = "{0}.profile".format(tbasename) 1034 if os.path.isfile(f) and f not in files: 1035 files.append(f) 1036 if os.path.isfile(tf) and tf not in files: 1037 files.append(tf) 1038 1039 if files: 1040 if len(files) > 1: 1041 pfn, ok = QInputDialog.getItem( 1042 None, 1043 self.tr("Profile Data"), 1044 self.tr("Please select a profile file"), 1045 files, 1046 0, False) 1047 if not ok: 1048 return 1049 else: 1050 pfn = files[0] 1051 else: 1052 return 1053 1054 from DataViews.PyProfileDialog import PyProfileDialog 1055 self.profiledata = PyProfileDialog() 1056 self.profiledata.show() 1057 self.profiledata.start(pfn, fn) 1058 1059 ########################################################################### 1060 ## Methods for the Graphics submenu 1061 ########################################################################### 1062 1063 def __showContextMenuGraphics(self): 1064 """ 1065 Private slot called before the checks menu is shown. 1066 """ 1067 self.showMenu.emit("Graphics", self.graphicsMenu) 1068 1069 def __showClassDiagram(self): 1070 """ 1071 Private method to handle the class diagram context menu action. 1072 """ 1073 itm = self.model().item(self.currentIndex()) 1074 try: 1075 fn = itm.fileName() 1076 except AttributeError: 1077 fn = itm.dirName() 1078 res = E5MessageBox.yesNo( 1079 self, 1080 self.tr("Class Diagram"), 1081 self.tr("""Include class attributes?"""), 1082 yesDefault=True) 1083 1084 from Graphics.UMLDialog import UMLDialog, UMLDialogType 1085 self.classDiagram = UMLDialog( 1086 UMLDialogType.CLASS_DIAGRAM, self.project, fn, 1087 self, noAttrs=not res) 1088 self.classDiagram.show() 1089 1090 def __showImportsDiagram(self): 1091 """ 1092 Private method to handle the imports diagram context menu action. 1093 """ 1094 itm = self.model().item(self.currentIndex()) 1095 try: 1096 fn = itm.fileName() 1097 except AttributeError: 1098 fn = itm.dirName() 1099 package = fn if os.path.isdir(fn) else os.path.dirname(fn) 1100 res = E5MessageBox.yesNo( 1101 self, 1102 self.tr("Imports Diagram"), 1103 self.tr("""Include imports from external modules?""")) 1104 1105 from Graphics.UMLDialog import UMLDialog, UMLDialogType 1106 self.importsDiagram = UMLDialog( 1107 UMLDialogType.IMPORTS_DIAGRAM, self.project, package, 1108 self, showExternalImports=res) 1109 self.importsDiagram.show() 1110 1111 def __showPackageDiagram(self): 1112 """ 1113 Private method to handle the package diagram context menu action. 1114 """ 1115 itm = self.model().item(self.currentIndex()) 1116 try: 1117 fn = itm.fileName() 1118 except AttributeError: 1119 fn = itm.dirName() 1120 package = fn if os.path.isdir(fn) else os.path.dirname(fn) 1121 res = E5MessageBox.yesNo( 1122 self, 1123 self.tr("Package Diagram"), 1124 self.tr("""Include class attributes?"""), 1125 yesDefault=True) 1126 1127 from Graphics.UMLDialog import UMLDialog, UMLDialogType 1128 self.packageDiagram = UMLDialog( 1129 UMLDialogType.PACKAGE_DIAGRAM, self.project, package, 1130 self, noAttrs=not res) 1131 self.packageDiagram.show() 1132 1133 def __showApplicationDiagram(self): 1134 """ 1135 Private method to handle the application diagram context menu action. 1136 """ 1137 res = E5MessageBox.yesNo( 1138 self, 1139 self.tr("Application Diagram"), 1140 self.tr("""Include module names?"""), 1141 yesDefault=True) 1142 1143 from Graphics.UMLDialog import UMLDialog, UMLDialogType 1144 self.applicationDiagram = UMLDialog( 1145 UMLDialogType.APPLICATION_DIAGRAM, self.project, 1146 self, noModules=not res) 1147 self.applicationDiagram.show() 1148 1149 def __loadDiagram(self): 1150 """ 1151 Private slot to load a diagram from file. 1152 """ 1153 from Graphics.UMLDialog import UMLDialog, UMLDialogType 1154 self.loadedDiagram = None 1155 loadedDiagram = UMLDialog( 1156 UMLDialogType.NO_DIAGRAM, self.project, parent=self) 1157 if loadedDiagram.load(): 1158 self.loadedDiagram = loadedDiagram 1159 self.loadedDiagram.show(fromFile=True) 1160