src/eric7/MultiProject/MultiProject.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9187
e53e07c6f482
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the multi project management functionality.
8 """
9
10 import os
11 import shutil
12 import contextlib
13 import pathlib
14
15 from PyQt6.QtCore import (
16 pyqtSignal, pyqtSlot, QFile, QIODevice, QObject, QUuid
17 )
18 from PyQt6.QtWidgets import QMenu, QDialog, QToolBar
19
20 from Globals import recentNameMultiProject
21
22 from EricGui.EricAction import EricAction, createActionGroup
23 from EricWidgets import EricFileDialog, EricMessageBox, EricPathPickerDialog
24 from EricWidgets.EricPathPickerDialog import EricPathPickerModes
25 from EricGui.EricOverrideCursor import EricOverrideCursor
26
27 import UI.PixmapCache
28
29 import Preferences
30 import Utilities
31
32 from .MultiProjectFile import MultiProjectFile
33
34
35 class MultiProject(QObject):
36 """
37 Class implementing the project management functionality.
38
39 @signal dirty(bool) emitted when the dirty state changes
40 @signal newMultiProject() emitted after a new multi project was generated
41 @signal multiProjectOpened() emitted after a multi project file was read
42 @signal multiProjectClosed() emitted after a multi project was closed
43 @signal multiProjectPropertiesChanged() emitted after the multi project
44 properties were changed
45 @signal showMenu(string, QMenu) emitted when a menu is about to be shown.
46 The name of the menu and a reference to the menu are given.
47 @signal projectDataChanged(project data dict) emitted after a project entry
48 has been changed
49 @signal projectAdded(project data dict) emitted after a project entry
50 has been added
51 @signal projectRemoved(project data dict) emitted after a project entry
52 has been removed
53 @signal projectOpened(filename) emitted after the project has been opened
54 """
55 dirty = pyqtSignal(bool)
56 newMultiProject = pyqtSignal()
57 multiProjectOpened = pyqtSignal()
58 multiProjectClosed = pyqtSignal()
59 multiProjectPropertiesChanged = pyqtSignal()
60 showMenu = pyqtSignal(str, QMenu)
61 projectDataChanged = pyqtSignal(dict)
62 projectAdded = pyqtSignal(dict)
63 projectRemoved = pyqtSignal(dict)
64 projectOpened = pyqtSignal(str)
65
66 def __init__(self, project, parent=None, filename=None):
67 """
68 Constructor
69
70 @param project reference to the project object (Project.Project)
71 @param parent parent widget (usually the ui object) (QWidget)
72 @param filename optional filename of a multi project file to open
73 (string)
74 """
75 super().__init__(parent)
76
77 self.ui = parent
78 self.projectObject = project
79
80 self.__initData()
81
82 self.__multiProjectFile = MultiProjectFile(self)
83
84 self.recent = []
85 self.__loadRecent()
86
87 if filename is not None:
88 self.openMultiProject(filename)
89
90 def __initData(self):
91 """
92 Private method to initialize the multi project data part.
93 """
94 self.loaded = False # flag for the loaded status
95 self.__dirty = False # dirty flag
96 self.pfile = "" # name of the multi project file
97 self.ppath = "" # name of the multi project directory
98 self.description = "" # description of the multi project
99 self.name = ""
100 self.opened = False
101 self.__projects = {}
102 # dict of project info keyed by 'uid'; each info entry is a dictionary
103 # 'name' : name of the project
104 # 'file' : project file name
105 # 'master' : flag indicating the master
106 # project
107 # 'description' : description of the project
108 # 'category' : name of the group
109 # 'uid' : unique identifier
110 self.categories = []
111
112 def __loadRecent(self):
113 """
114 Private method to load the recently opened multi project filenames.
115 """
116 self.recent = []
117 Preferences.Prefs.rsettings.sync()
118 rp = Preferences.Prefs.rsettings.value(recentNameMultiProject)
119 if rp is not None:
120 for f in rp:
121 if pathlib.Path(f).exists():
122 self.recent.append(f)
123
124 def __saveRecent(self):
125 """
126 Private method to save the list of recently opened filenames.
127 """
128 Preferences.Prefs.rsettings.setValue(
129 recentNameMultiProject, self.recent)
130 Preferences.Prefs.rsettings.sync()
131
132 def getMostRecent(self):
133 """
134 Public method to get the most recently opened multiproject.
135
136 @return path of the most recently opened multiproject (string)
137 """
138 if len(self.recent):
139 return self.recent[0]
140 else:
141 return None
142
143 def setDirty(self, b):
144 """
145 Public method to set the dirty state.
146
147 It emits the signal dirty(int).
148
149 @param b dirty state (boolean)
150 """
151 self.__dirty = b
152 self.saveAct.setEnabled(b)
153 self.dirty.emit(bool(b))
154
155 def isDirty(self):
156 """
157 Public method to return the dirty state.
158
159 @return dirty state (boolean)
160 """
161 return self.__dirty
162
163 def isOpen(self):
164 """
165 Public method to return the opened state.
166
167 @return open state (boolean)
168 """
169 return self.opened
170
171 def getMultiProjectPath(self):
172 """
173 Public method to get the multi project path.
174
175 @return multi project path (string)
176 """
177 return self.ppath
178
179 def getMultiProjectFile(self):
180 """
181 Public method to get the path of the multi project file.
182
183 @return path of the multi project file (string)
184 """
185 return self.pfile
186
187 def __checkFilesExist(self):
188 """
189 Private method to check, if the files in a list exist.
190
191 The project files are checked for existance in the
192 filesystem. Non existant projects are removed from the list and the
193 dirty state of the multi project is changed accordingly.
194 """
195 removelist = []
196 for key, project in self.__projects.items():
197 if not os.path.exists(project['file']):
198 removelist.append(key)
199
200 if removelist:
201 for key in removelist:
202 del self.__projects[key]
203 self.setDirty(True)
204
205 def __extractCategories(self):
206 """
207 Private slot to extract the categories used in the project definitions.
208 """
209 for project in self.__projects.values():
210 if (
211 project['category'] and
212 project['category'] not in self.categories
213 ):
214 self.categories.append(project['category'])
215
216 def getCategories(self):
217 """
218 Public method to get the list of defined categories.
219
220 @return list of categories (list of string)
221 """
222 return [c for c in self.categories if c]
223
224 def __readMultiProject(self, fn):
225 """
226 Private method to read in a multi project (.emj, .e4m, .e5m) file.
227
228 @param fn filename of the multi project file to be read (string)
229 @return flag indicating success
230 """
231 if os.path.splitext(fn)[1] == ".emj":
232 # new JSON based format
233 with EricOverrideCursor():
234 res = self.__multiProjectFile.readFile(fn)
235 else:
236 # old XML based format
237 f = QFile(fn)
238 if f.open(QIODevice.OpenModeFlag.ReadOnly):
239 with EricOverrideCursor():
240 from EricXML.MultiProjectReader import MultiProjectReader
241 reader = MultiProjectReader(f, self)
242 reader.readXML()
243 f.close()
244 res = not reader.hasError()
245 else:
246 EricMessageBox.critical(
247 self.ui,
248 self.tr("Read Multi Project File"),
249 self.tr(
250 "<p>The multi project file <b>{0}</b> could not be"
251 " read.</p>").format(fn))
252 res = False
253
254 if res:
255 self.pfile = os.path.abspath(fn)
256 self.ppath = os.path.abspath(os.path.dirname(fn))
257
258 self.__extractCategories()
259
260 # insert filename into list of recently opened multi projects
261 self.__syncRecent()
262
263 self.name = os.path.splitext(os.path.basename(fn))[0]
264
265 # check, if the files of the multi project still exist
266 self.__checkFilesExist()
267
268 return res
269
270 def __writeMultiProject(self, fn=None):
271 """
272 Private method to save the multi project infos to a multi project file.
273
274 @param fn optional filename of the multi project file to be written.
275 If fn is None, the filename stored in the multi project object
276 is used. This is the 'save' action. If fn is given, this filename
277 is used instead of the one in the multi project object. This is the
278 'save as' action.
279 @return flag indicating success
280 """
281 if fn is None:
282 fn = self.pfile
283
284 res = self.__multiProjectFile.writeFile(fn)
285 if res:
286 self.pfile = os.path.abspath(fn)
287 self.ppath = os.path.abspath(os.path.dirname(fn))
288 self.name = os.path.splitext(os.path.basename(fn))[0]
289 self.setDirty(False)
290
291 # insert filename into list of recently opened projects
292 self.__syncRecent()
293
294 return res
295
296 def addProject(self, project):
297 """
298 Public method to add a project to the multi-project.
299
300 @param project dictionary containing the project data to be added
301 @type dict
302 """
303 self.__projects[project['uid']] = project
304
305 @pyqtSlot()
306 def addNewProject(self, startdir="", category=""):
307 """
308 Public slot used to add a new project to the multi-project.
309
310 @param startdir start directory for the selection dialog
311 @type str
312 @param category category to be preset
313 @type str
314 """
315 from .AddProjectDialog import AddProjectDialog
316 if not startdir:
317 startdir = self.ppath
318 if not startdir:
319 startdir = Preferences.getMultiProject("Workspace")
320 dlg = AddProjectDialog(self.ui, startdir=startdir,
321 categories=self.categories, category=category)
322 if dlg.exec() == QDialog.DialogCode.Accepted:
323 name, filename, isMaster, description, category, uid = (
324 dlg.getData()
325 )
326
327 # step 1: check, if project was already added
328 for project in self.__projects.values():
329 if project['file'] == filename:
330 return
331
332 # step 2: check, if master should be changed
333 if isMaster:
334 for project in self.__projects.values():
335 if project['master']:
336 project['master'] = False
337 self.projectDataChanged.emit(project)
338 self.setDirty(True)
339 break
340
341 # step 3: add the project entry
342 project = {
343 'name': name,
344 'file': filename,
345 'master': isMaster,
346 'description': description,
347 'category': category,
348 'uid': uid,
349 }
350 self.__projects[uid] = project
351 if category not in self.categories:
352 self.categories.append(category)
353 self.projectAdded.emit(project)
354 self.setDirty(True)
355
356 def copyProject(self, uid):
357 """
358 Public method to copy the project with given UID on disk.
359
360 @param uid UID of the project to copy
361 @type str
362 """
363 if uid in self.__projects:
364 startdir = self.ppath
365 if not startdir:
366 startdir = Preferences.getMultiProject("Workspace")
367 srcProject = self.__projects[uid]
368 srcProjectDirectory = os.path.dirname(srcProject["file"])
369 dstProjectDirectory, ok = EricPathPickerDialog.getPath(
370 self.parent(),
371 self.tr("Copy Project"),
372 self.tr("Enter directory for the new project (must not exist"
373 " already):"),
374 mode=EricPathPickerModes.DIRECTORY_MODE,
375 path=srcProjectDirectory,
376 defaultDirectory=startdir,
377 )
378 if (
379 ok and
380 dstProjectDirectory and
381 not os.path.exists(dstProjectDirectory)
382 ):
383 try:
384 shutil.copytree(srcProjectDirectory, dstProjectDirectory)
385 except shutil.Error:
386 EricMessageBox.critical(
387 self.parent(),
388 self.tr("Copy Project"),
389 self.tr("<p>The source project <b>{0}</b> could not"
390 " be copied to its destination <b>{1}</b>."
391 "</p>").format(srcProjectDirectory,
392 dstProjectDirectory))
393 return
394
395 dstUid = QUuid.createUuid().toString()
396 dstProject = {
397 'name': self.tr("{0} - Copy").format(srcProject["name"]),
398 'file': os.path.join(dstProjectDirectory,
399 os.path.basename(srcProject["file"])),
400 'master': False,
401 'description': srcProject["description"],
402 'category': srcProject["category"],
403 'uid': dstUid,
404 }
405 self.__projects[dstUid] = dstProject
406 self.projectAdded.emit(dstProject)
407 self.setDirty(True)
408
409 def changeProjectProperties(self, pro):
410 """
411 Public method to change the data of a project entry.
412
413 @param pro dictionary with the project data (string)
414 """
415 # step 1: check, if master should be changed
416 if pro['master']:
417 for project in self.__projects.values():
418 if project['master']:
419 if project['uid'] != pro['uid']:
420 project['master'] = False
421 self.projectDataChanged.emit(project)
422 self.setDirty(True)
423 break
424
425 # step 2: change the entry
426 project = self.__projects[pro['uid']]
427 # project UID is not changeable via interface
428 project['file'] = pro['file']
429 project['name'] = pro['name']
430 project['master'] = pro['master']
431 project['description'] = pro['description']
432 project['category'] = pro['category']
433 if project['category'] not in self.categories:
434 self.categories.append(project['category'])
435 self.projectDataChanged.emit(project)
436 self.setDirty(True)
437
438 def getProjects(self):
439 """
440 Public method to get all project entries.
441
442 @return list of all project entries (list of dictionaries)
443 """
444 return self.__projects.values()
445
446 def getProject(self, uid):
447 """
448 Public method to get a reference to a project entry.
449
450 @param uid UID of the project to get
451 @type str
452 @return dictionary containing the project data
453 @rtype dict
454 """
455 if uid in self.__projects:
456 return self.__projects[uid]
457 else:
458 return None
459
460 def removeProject(self, uid):
461 """
462 Public slot to remove a project from the multi project.
463
464 @param uid UID of the project to be removed from the multi
465 project
466 @type str
467 """
468 if uid in self.__projects:
469 project = self.__projects[uid]
470 del self.__projects[uid]
471 self.projectRemoved.emit(project)
472 self.setDirty(True)
473
474 def deleteProject(self, uid):
475 """
476 Public slot to delete project(s) from the multi project and disk.
477
478 @param uid UID of the project to be removed from the multi
479 project
480 @type str
481 """
482 if uid in self.__projects:
483 project = self.__projects[uid]
484 projectPath = os.path.dirname(project["file"])
485 shutil.rmtree(projectPath, True)
486
487 self.removeProject(uid)
488
489 def __newMultiProject(self):
490 """
491 Private slot to build a new multi project.
492
493 This method displays the new multi project dialog and initializes
494 the multi project object with the data entered.
495 """
496 if not self.checkDirty():
497 return
498
499 from .PropertiesDialog import PropertiesDialog
500 dlg = PropertiesDialog(self, True)
501 if dlg.exec() == QDialog.DialogCode.Accepted:
502 self.closeMultiProject()
503 dlg.storeData()
504 self.opened = True
505 self.setDirty(True)
506 self.closeAct.setEnabled(True)
507 self.saveasAct.setEnabled(True)
508 self.addProjectAct.setEnabled(True)
509 self.propsAct.setEnabled(True)
510 self.newMultiProject.emit()
511
512 def __showProperties(self):
513 """
514 Private slot to display the properties dialog.
515 """
516 from .PropertiesDialog import PropertiesDialog
517 dlg = PropertiesDialog(self, False)
518 if dlg.exec() == QDialog.DialogCode.Accepted:
519 dlg.storeData()
520 self.setDirty(True)
521 self.multiProjectPropertiesChanged.emit()
522
523 @pyqtSlot()
524 @pyqtSlot(str)
525 def openMultiProject(self, fn=None, openMaster=True):
526 """
527 Public slot to open a multi project.
528
529 @param fn optional filename of the multi project file to be
530 read
531 @type str
532 @param openMaster flag indicating, that the master project
533 should be opened depending on the configuration
534 @type bool
535 """
536 if not self.checkDirty():
537 return
538
539 if fn is None:
540 fn = EricFileDialog.getOpenFileName(
541 self.parent(),
542 self.tr("Open Multi Project"),
543 Preferences.getMultiProject("Workspace") or
544 Utilities.getHomeDir(),
545 self.tr("Multi Project Files (*.emj);;"
546 "XML Multi Project Files (*.e5m *.e4m)"))
547
548 if fn == "":
549 fn = None
550
551 if fn is not None:
552 self.closeMultiProject()
553 ok = self.__readMultiProject(fn)
554 if ok:
555 self.opened = True
556
557 self.closeAct.setEnabled(True)
558 self.saveasAct.setEnabled(True)
559 self.addProjectAct.setEnabled(True)
560 self.propsAct.setEnabled(True)
561
562 self.multiProjectOpened.emit()
563
564 if openMaster and Preferences.getMultiProject(
565 "OpenMasterAutomatically"):
566 self.__openMasterProject(False)
567
568 def saveMultiProject(self):
569 """
570 Public slot to save the current multi project.
571
572 @return flag indicating success
573 @rtype bool
574 """
575 if self.isDirty():
576 if len(self.pfile) > 0:
577 if self.pfile.endswith((".e4m", ".e5m")):
578 self.pfile = (self.pfile
579 .replace(".e4m", ".emj")
580 .replace(".e5m", ".emj"))
581 self.__syncRecent()
582 ok = self.__writeMultiProject()
583 else:
584 ok = self.saveMultiProjectAs()
585 else:
586 ok = True
587 return ok
588
589 def saveMultiProjectAs(self):
590 """
591 Public slot to save the current multi project to a different file.
592
593 @return flag indicating success
594 @rtype bool
595 """
596 defaultFilter = self.tr("Multi Project Files (*.emj)")
597 defaultPath = (
598 self.ppath
599 if self.ppath else
600 (Preferences.getMultiProject("Workspace") or
601 Utilities.getHomeDir())
602 )
603 fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
604 self.parent(),
605 self.tr("Save Multiproject"),
606 defaultPath,
607 self.tr("Multi Project Files (*.emj)"),
608 defaultFilter,
609 EricFileDialog.DontConfirmOverwrite)
610
611 if fn:
612 fpath = pathlib.Path(fn)
613 if not fpath.suffix:
614 ex = selectedFilter.split("(*")[1].split(")")[0]
615 if ex:
616 fpath = fpath.with_suffix(ex)
617 if fpath.exists():
618 res = EricMessageBox.yesNo(
619 self.parent(),
620 self.tr("Save File"),
621 self.tr("<p>The file <b>{0}</b> already exists."
622 " Overwrite it?</p>").format(fn),
623 icon=EricMessageBox.Warning)
624 if not res:
625 return False
626
627 self.name = fpath.stem
628 self.__writeMultiProject(str(fpath))
629
630 self.multiProjectClosed.emit()
631 self.multiProjectOpened.emit()
632 return True
633 else:
634 return False
635
636 def checkDirty(self):
637 """
638 Public method to check the dirty status and open a message window.
639
640 @return flag indicating whether this operation was successful (boolean)
641 """
642 if self.isDirty():
643 res = EricMessageBox.okToClearData(
644 self.parent(),
645 self.tr("Close Multiproject"),
646 self.tr("The current multiproject has unsaved changes."),
647 self.saveMultiProject)
648 if res:
649 self.setDirty(False)
650 return res
651
652 return True
653
654 def closeMultiProject(self):
655 """
656 Public slot to close the current multi project.
657
658 @return flag indicating success (boolean)
659 """
660 # save the list of recently opened projects
661 self.__saveRecent()
662
663 if not self.isOpen():
664 return True
665
666 if not self.checkDirty():
667 return False
668
669 # now close the current project, if it belongs to the multi project
670 pfile = self.projectObject.getProjectFile()
671 if pfile:
672 for project in self.__projects.values():
673 if project['file'] == pfile:
674 if not self.projectObject.closeProject():
675 return False
676 break
677
678 self.__initData()
679 self.closeAct.setEnabled(False)
680 self.saveasAct.setEnabled(False)
681 self.saveAct.setEnabled(False)
682 self.addProjectAct.setEnabled(False)
683 self.propsAct.setEnabled(False)
684
685 self.multiProjectClosed.emit()
686
687 return True
688
689 def initActions(self):
690 """
691 Public slot to initialize the multi project related actions.
692 """
693 self.actions = []
694
695 self.actGrp1 = createActionGroup(self)
696
697 act = EricAction(
698 self.tr('New multiproject'),
699 UI.PixmapCache.getIcon("multiProjectNew"),
700 self.tr('&New...'), 0, 0,
701 self.actGrp1, 'multi_project_new')
702 act.setStatusTip(self.tr('Generate a new multiproject'))
703 act.setWhatsThis(self.tr(
704 """<b>New...</b>"""
705 """<p>This opens a dialog for entering the info for a"""
706 """ new multiproject.</p>"""
707 ))
708 act.triggered.connect(self.__newMultiProject)
709 self.actions.append(act)
710
711 act = EricAction(
712 self.tr('Open multiproject'),
713 UI.PixmapCache.getIcon("multiProjectOpen"),
714 self.tr('&Open...'), 0, 0,
715 self.actGrp1, 'multi_project_open')
716 act.setStatusTip(self.tr('Open an existing multiproject'))
717 act.setWhatsThis(self.tr(
718 """<b>Open...</b>"""
719 """<p>This opens an existing multiproject.</p>"""
720 ))
721 act.triggered.connect(self.openMultiProject)
722 self.actions.append(act)
723
724 self.closeAct = EricAction(
725 self.tr('Close multiproject'),
726 UI.PixmapCache.getIcon("multiProjectClose"),
727 self.tr('&Close'), 0, 0, self, 'multi_project_close')
728 self.closeAct.setStatusTip(self.tr(
729 'Close the current multiproject'))
730 self.closeAct.setWhatsThis(self.tr(
731 """<b>Close</b>"""
732 """<p>This closes the current multiproject.</p>"""
733 ))
734 self.closeAct.triggered.connect(self.closeMultiProject)
735 self.actions.append(self.closeAct)
736
737 self.saveAct = EricAction(
738 self.tr('Save multiproject'),
739 UI.PixmapCache.getIcon("multiProjectSave"),
740 self.tr('&Save'), 0, 0, self, 'multi_project_save')
741 self.saveAct.setStatusTip(self.tr('Save the current multiproject'))
742 self.saveAct.setWhatsThis(self.tr(
743 """<b>Save</b>"""
744 """<p>This saves the current multiproject.</p>"""
745 ))
746 self.saveAct.triggered.connect(self.saveMultiProject)
747 self.actions.append(self.saveAct)
748
749 self.saveasAct = EricAction(
750 self.tr('Save multiproject as'),
751 UI.PixmapCache.getIcon("multiProjectSaveAs"),
752 self.tr('Save &as...'), 0, 0, self,
753 'multi_project_save_as')
754 self.saveasAct.setStatusTip(self.tr(
755 'Save the current multiproject to a new file'))
756 self.saveasAct.setWhatsThis(self.tr(
757 """<b>Save as</b>"""
758 """<p>This saves the current multiproject to a new file.</p>"""
759 ))
760 self.saveasAct.triggered.connect(self.saveMultiProjectAs)
761 self.actions.append(self.saveasAct)
762
763 self.addProjectAct = EricAction(
764 self.tr('Add project to multiproject'),
765 UI.PixmapCache.getIcon("fileProject"),
766 self.tr('Add &project...'), 0, 0,
767 self, 'multi_project_add_project')
768 self.addProjectAct.setStatusTip(self.tr(
769 'Add a project to the current multiproject'))
770 self.addProjectAct.setWhatsThis(self.tr(
771 """<b>Add project...</b>"""
772 """<p>This opens a dialog for adding a project"""
773 """ to the current multiproject.</p>"""
774 ))
775 self.addProjectAct.triggered.connect(self.addNewProject)
776 self.actions.append(self.addProjectAct)
777
778 self.propsAct = EricAction(
779 self.tr('Multiproject properties'),
780 UI.PixmapCache.getIcon("multiProjectProps"),
781 self.tr('&Properties...'), 0, 0, self,
782 'multi_project_properties')
783 self.propsAct.setStatusTip(self.tr(
784 'Show the multiproject properties'))
785 self.propsAct.setWhatsThis(self.tr(
786 """<b>Properties...</b>"""
787 """<p>This shows a dialog to edit the multiproject"""
788 """ properties.</p>"""
789 ))
790 self.propsAct.triggered.connect(self.__showProperties)
791 self.actions.append(self.propsAct)
792
793 self.closeAct.setEnabled(False)
794 self.saveAct.setEnabled(False)
795 self.saveasAct.setEnabled(False)
796 self.addProjectAct.setEnabled(False)
797 self.propsAct.setEnabled(False)
798
799 def initMenu(self):
800 """
801 Public slot to initialize the multi project menu.
802
803 @return the menu generated (QMenu)
804 """
805 menu = QMenu(self.tr('&Multiproject'), self.parent())
806 self.recentMenu = QMenu(self.tr('Open &Recent Multiprojects'),
807 menu)
808
809 self.__menus = {
810 "Main": menu,
811 "Recent": self.recentMenu,
812 }
813
814 # connect the aboutToShow signals
815 self.recentMenu.aboutToShow.connect(self.__showContextMenuRecent)
816 self.recentMenu.triggered.connect(self.__openRecent)
817 menu.aboutToShow.connect(self.__showMenu)
818
819 # build the main menu
820 menu.setTearOffEnabled(True)
821 menu.addActions(self.actGrp1.actions())
822 self.menuRecentAct = menu.addMenu(self.recentMenu)
823 menu.addSeparator()
824 menu.addAction(self.closeAct)
825 menu.addSeparator()
826 menu.addAction(self.saveAct)
827 menu.addAction(self.saveasAct)
828 menu.addSeparator()
829 menu.addAction(self.addProjectAct)
830 menu.addSeparator()
831 menu.addAction(self.propsAct)
832
833 self.menu = menu
834 return menu
835
836 def initToolbar(self, toolbarManager):
837 """
838 Public slot to initialize the multi project toolbar.
839
840 @param toolbarManager reference to a toolbar manager object
841 (EricToolBarManager)
842 @return the toolbar generated (QToolBar)
843 """
844 tb = QToolBar(self.tr("Multiproject"), self.ui)
845 tb.setIconSize(UI.Config.ToolBarIconSize)
846 tb.setObjectName("MultiProjectToolbar")
847 tb.setToolTip(self.tr('Multiproject'))
848
849 tb.addActions(self.actGrp1.actions())
850 tb.addAction(self.closeAct)
851 tb.addSeparator()
852 tb.addAction(self.saveAct)
853 tb.addAction(self.saveasAct)
854
855 toolbarManager.addToolBar(tb, tb.windowTitle())
856 toolbarManager.addAction(self.addProjectAct, tb.windowTitle())
857 toolbarManager.addAction(self.propsAct, tb.windowTitle())
858
859 return tb
860
861 def __showMenu(self):
862 """
863 Private method to set up the multi project menu.
864 """
865 self.menuRecentAct.setEnabled(len(self.recent) > 0)
866
867 self.showMenu.emit("Main", self.__menus["Main"])
868
869 def __syncRecent(self):
870 """
871 Private method to synchronize the list of recently opened multi
872 projects with the central store.
873 """
874 for recent in self.recent[:]:
875 if Utilities.samepath(self.pfile, recent):
876 self.recent.remove(recent)
877 self.recent.insert(0, self.pfile)
878 maxRecent = Preferences.getProject("RecentNumber")
879 if len(self.recent) > maxRecent:
880 self.recent = self.recent[:maxRecent]
881 self.__saveRecent()
882
883 def __showContextMenuRecent(self):
884 """
885 Private method to set up the recent multi projects menu.
886 """
887 self.__loadRecent()
888
889 self.recentMenu.clear()
890
891 for idx, rp in enumerate(self.recent, start=1):
892 formatStr = '&{0:d}. {1}' if idx < 10 else '{0:d}. {1}'
893 act = self.recentMenu.addAction(
894 formatStr.format(
895 idx,
896 Utilities.compactPath(rp, self.ui.maxMenuFilePathLen)))
897 act.setData(rp)
898 act.setEnabled(pathlib.Path(rp).exists())
899
900 self.recentMenu.addSeparator()
901 self.recentMenu.addAction(self.tr('&Clear'), self.clearRecent)
902
903 def __openRecent(self, act):
904 """
905 Private method to open a multi project from the list of rencently
906 opened multi projects.
907
908 @param act reference to the action that triggered (QAction)
909 """
910 file = act.data()
911 if file:
912 self.openMultiProject(file)
913
914 def clearRecent(self):
915 """
916 Public method to clear the recent multi projects menu.
917 """
918 self.recent = []
919 self.__saveRecent()
920
921 def getActions(self):
922 """
923 Public method to get a list of all actions.
924
925 @return list of all actions (list of EricAction)
926 """
927 return self.actions[:]
928
929 def addEricActions(self, actions):
930 """
931 Public method to add actions to the list of actions.
932
933 @param actions list of actions (list of EricAction)
934 """
935 self.actions.extend(actions)
936
937 def removeEricActions(self, actions):
938 """
939 Public method to remove actions from the list of actions.
940
941 @param actions list of actions (list of EricAction)
942 """
943 for act in actions:
944 with contextlib.suppress(ValueError):
945 self.actions.remove(act)
946
947 def getMenu(self, menuName):
948 """
949 Public method to get a reference to the main menu or a submenu.
950
951 @param menuName name of the menu (string)
952 @return reference to the requested menu (QMenu) or None
953 """
954 try:
955 return self.__menus[menuName]
956 except KeyError:
957 return None
958
959 def openProject(self, filename):
960 """
961 Public slot to open a project.
962
963 @param filename filename of the project file (string)
964 """
965 self.projectObject.openProject(filename)
966 self.projectOpened.emit(filename)
967
968 def __openMasterProject(self, reopen=True):
969 """
970 Private slot to open the master project.
971
972 @param reopen flag indicating, that the master project should be
973 reopened, if it has been opened already (boolean)
974 """
975 for project in self.__projects.values():
976 if (
977 project['master'] and
978 (reopen or
979 not self.projectObject.isOpen() or
980 self.projectObject.getProjectFile() != project['file'])
981 ):
982 self.openProject(project['file'])
983 return
984
985 def getMasterProjectFile(self):
986 """
987 Public method to get the filename of the master project.
988
989 @return name of the master project file (string)
990 """
991 for project in self.__projects:
992 if project['master']:
993 return project['file']
994
995 return None
996
997 def getDependantProjectFiles(self):
998 """
999 Public method to get the filenames of the dependent projects.
1000
1001 @return names of the dependent project files (list of strings)
1002 """
1003 files = []
1004 for project in self.__projects.values():
1005 if not project['master']:
1006 files.append(project['file'])
1007 return files

eric ide

mercurial