eric6/MultiProject/MultiProject.py

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

eric ide

mercurial