eric7/MultiProject/MultiProject.py

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

eric ide

mercurial