38 @signal multiProjectClosed() emitted after a multi project was closed |
39 @signal multiProjectClosed() emitted after a multi project was closed |
39 @signal multiProjectPropertiesChanged() emitted after the multi project |
40 @signal multiProjectPropertiesChanged() emitted after the multi project |
40 properties were changed |
41 properties were changed |
41 @signal showMenu(string, QMenu) emitted when a menu is about to be shown. |
42 @signal showMenu(string, QMenu) emitted when a menu is about to be shown. |
42 The name of the menu and a reference to the menu are given. |
43 The name of the menu and a reference to the menu are given. |
43 @signal projectDataChanged(project data dict) emitted after a project entry |
44 @signal projectDataChanged(project metadata) emitted after a project entry |
44 has been changed |
45 has been changed |
45 @signal projectAdded(project data dict) emitted after a project entry |
46 @signal projectAdded(project metadata) emitted after a project entry |
46 has been added |
47 has been added |
47 @signal projectRemoved(project data dict) emitted after a project entry |
48 @signal projectRemoved(project metadata) emitted after a project entry |
48 has been removed |
49 has been removed |
49 @signal projectOpened(filename) emitted after the project has been opened |
50 @signal projectOpened(filename) emitted after the project has been opened |
50 """ |
51 """ |
51 |
52 |
52 dirty = pyqtSignal(bool) |
53 dirty = pyqtSignal(bool) |
53 newMultiProject = pyqtSignal() |
54 newMultiProject = pyqtSignal() |
54 multiProjectOpened = pyqtSignal() |
55 multiProjectOpened = pyqtSignal() |
55 multiProjectClosed = pyqtSignal() |
56 multiProjectClosed = pyqtSignal() |
56 multiProjectPropertiesChanged = pyqtSignal() |
57 multiProjectPropertiesChanged = pyqtSignal() |
57 showMenu = pyqtSignal(str, QMenu) |
58 showMenu = pyqtSignal(str, QMenu) |
58 projectDataChanged = pyqtSignal(dict) |
59 projectDataChanged = pyqtSignal(MultiProjectProjectMeta) |
59 projectAdded = pyqtSignal(dict) |
60 projectAdded = pyqtSignal(MultiProjectProjectMeta) |
60 projectRemoved = pyqtSignal(dict) |
61 projectRemoved = pyqtSignal(MultiProjectProjectMeta) |
61 projectOpened = pyqtSignal(str) |
62 projectOpened = pyqtSignal(str) |
62 |
63 |
63 def __init__(self, project, parent=None, filename=None): |
64 def __init__(self, project, parent=None, filename=None): |
64 """ |
65 """ |
65 Constructor |
66 Constructor |
66 |
67 |
67 @param project reference to the project object (Project.Project) |
68 @param project reference to the project object |
68 @param parent parent widget (usually the ui object) (QWidget) |
69 @type Project.Project |
|
70 @param parent parent widget (usually the ui object) |
|
71 @type QWidget |
69 @param filename optional filename of a multi project file to open |
72 @param filename optional filename of a multi project file to open |
70 (string) |
73 @type str |
71 """ |
74 """ |
72 super().__init__(parent) |
75 super().__init__(parent) |
73 |
76 |
74 self.ui = parent |
77 self.ui = parent |
75 self.projectObject = project |
78 self.projectObject = project |
139 """ |
137 """ |
140 Public method to set the dirty state. |
138 Public method to set the dirty state. |
141 |
139 |
142 It emits the signal dirty(int). |
140 It emits the signal dirty(int). |
143 |
141 |
144 @param b dirty state (boolean) |
142 @param b dirty state |
|
143 @type bool |
145 """ |
144 """ |
146 self.__dirty = b |
145 self.__dirty = b |
147 self.saveAct.setEnabled(b) |
146 self.saveAct.setEnabled(b) |
148 self.dirty.emit(bool(b)) |
147 self.dirty.emit(bool(b)) |
149 |
148 |
150 def isDirty(self): |
149 def isDirty(self): |
151 """ |
150 """ |
152 Public method to return the dirty state. |
151 Public method to return the dirty state. |
153 |
152 |
154 @return dirty state (boolean) |
153 @return dirty state |
|
154 @rtype bool |
155 """ |
155 """ |
156 return self.__dirty |
156 return self.__dirty |
157 |
157 |
158 def isOpen(self): |
158 def isOpen(self): |
159 """ |
159 """ |
160 Public method to return the opened state. |
160 Public method to return the opened state. |
161 |
161 |
162 @return open state (boolean) |
162 @return open state |
|
163 @rtype bool |
163 """ |
164 """ |
164 return self.opened |
165 return self.opened |
165 |
166 |
166 def getMultiProjectPath(self): |
167 def getMultiProjectPath(self): |
167 """ |
168 """ |
168 Public method to get the multi project path. |
169 Public method to get the multi project path. |
169 |
170 |
170 @return multi project path (string) |
171 @return multi project path |
|
172 @rtype str |
171 """ |
173 """ |
172 return self.ppath |
174 return self.ppath |
173 |
175 |
174 def getMultiProjectFile(self): |
176 def getMultiProjectFile(self): |
175 """ |
177 """ |
176 Public method to get the path of the multi project file. |
178 Public method to get the path of the multi project file. |
177 |
179 |
178 @return path of the multi project file (string) |
180 @return path of the multi project file |
|
181 @rtype str |
179 """ |
182 """ |
180 return self.pfile |
183 return self.pfile |
181 |
184 |
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): |
185 def __extractCategories(self): |
201 """ |
186 """ |
202 Private slot to extract the categories used in the project definitions. |
187 Private slot to extract the categories used in the project definitions. |
203 """ |
188 """ |
204 for project in self.__projects.values(): |
189 for project in self.__projects.values(): |
205 if project["category"] and project["category"] not in self.categories: |
190 if project.category and project.category not in self.categories: |
206 self.categories.append(project["category"]) |
191 self.categories.append(project.category) |
207 |
192 |
208 def getCategories(self): |
193 def getCategories(self): |
209 """ |
194 """ |
210 Public method to get the list of defined categories. |
195 Public method to get the list of defined categories. |
211 |
196 |
212 @return list of categories (list of string) |
197 @return list of categories |
|
198 @rtype list of str |
213 """ |
199 """ |
214 return [c for c in self.categories if c] |
200 return [c for c in self.categories if c] |
215 |
201 |
216 def __readMultiProject(self, fn): |
202 def __readMultiProject(self, fn): |
217 """ |
203 """ |
218 Private method to read in a multi project (.emj, .e4m, .e5m) file. |
204 Private method to read in a multi project (.emj, .e4m, .e5m) file. |
219 |
205 |
220 @param fn filename of the multi project file to be read (string) |
206 @param fn filename of the multi project file to be read |
|
207 @type str |
221 @return flag indicating success |
208 @return flag indicating success |
|
209 @rtype bool |
222 """ |
210 """ |
223 if os.path.splitext(fn)[1] == ".emj": |
211 if os.path.splitext(fn)[1] == ".emj": |
224 # new JSON based format |
212 # new JSON based format |
225 with EricOverrideCursor(): |
213 with EricOverrideCursor(): |
226 res = self.__multiProjectFile.readFile(fn) |
214 res = self.__multiProjectFile.readFile(fn) |
312 startdir = Preferences.getMultiProject("Workspace") |
302 startdir = Preferences.getMultiProject("Workspace") |
313 dlg = AddProjectDialog( |
303 dlg = AddProjectDialog( |
314 self.ui, startdir=startdir, categories=self.categories, category=category |
304 self.ui, startdir=startdir, categories=self.categories, category=category |
315 ) |
305 ) |
316 if dlg.exec() == QDialog.DialogCode.Accepted: |
306 if dlg.exec() == QDialog.DialogCode.Accepted: |
317 name, filename, isMain, description, category, uid = dlg.getData() |
307 newProject = dlg.getProjectMetadata() |
318 |
308 |
319 # step 1: check, if project was already added |
309 # step 1: check, if project was already added |
320 for project in self.__projects.values(): |
310 for project in self.__projects.values(): |
321 if project["file"] == filename: |
311 if project.file == newProject.file: |
322 return |
312 return |
323 |
313 |
324 # step 2: check, if main should be changed |
314 # step 2: check, if main should be changed |
325 if isMain: |
315 if newProject.master: |
326 for project in self.__projects.values(): |
316 for project in self.__projects.values(): |
327 if project["master"]: |
317 if project.master: |
328 project["master"] = False |
318 project.master = False |
329 self.projectDataChanged.emit(project) |
319 self.projectDataChanged.emit(project) |
330 self.setDirty(True) |
320 self.setDirty(True) |
331 break |
321 break |
332 |
322 |
333 # step 3: add the project entry |
323 # step 3: add the project entry |
334 project = { |
324 self.__projects[newProject.uid] = newProject |
335 "name": name, |
|
336 "file": filename, |
|
337 "master": isMain, |
|
338 "description": description, |
|
339 "category": category, |
|
340 "uid": uid, |
|
341 } |
|
342 self.__projects[uid] = project |
|
343 if category not in self.categories: |
325 if category not in self.categories: |
344 self.categories.append(category) |
326 self.categories.append(category) |
345 self.projectAdded.emit(project) |
327 self.projectAdded.emit(newProject) |
346 self.setDirty(True) |
328 self.setDirty(True) |
347 |
329 |
348 def copyProject(self, uid): |
330 def copyProject(self, uid): |
349 """ |
331 """ |
350 Public method to copy the project with given UID on disk. |
332 Public method to copy the project with given UID on disk. |
382 ).format(srcProjectDirectory, dstProjectDirectory), |
364 ).format(srcProjectDirectory, dstProjectDirectory), |
383 ) |
365 ) |
384 return |
366 return |
385 |
367 |
386 dstUid = QUuid.createUuid().toString() |
368 dstUid = QUuid.createUuid().toString() |
387 dstProject = { |
369 dstProject = MultiProjectProjectMeta( |
388 "name": self.tr("{0} - Copy").format(srcProject["name"]), |
370 name=self.tr("{0} - Copy").format(srcProject["name"]), |
389 "file": os.path.join( |
371 file=os.path.join( |
390 dstProjectDirectory, os.path.basename(srcProject["file"]) |
372 dstProjectDirectory, os.path.basename(srcProject["file"]) |
391 ), |
373 ), |
392 "master": False, |
374 master=False, |
393 "description": srcProject["description"], |
375 description=srcProject.description, |
394 "category": srcProject["category"], |
376 category=srcProject.category, |
395 "uid": dstUid, |
377 uid=dstUid, |
396 } |
378 ) |
397 self.__projects[dstUid] = dstProject |
379 self.__projects[dstUid] = dstProject |
398 self.projectAdded.emit(dstProject) |
380 self.projectAdded.emit(dstProject) |
399 self.setDirty(True) |
381 self.setDirty(True) |
400 |
382 |
401 def changeProjectProperties(self, pro): |
383 def changeProjectProperties(self, pro): |
402 """ |
384 """ |
403 Public method to change the data of a project entry. |
385 Public method to change the data of a project entry. |
404 |
386 |
405 @param pro dictionary with the project data (string) |
387 @param pro dictionary with the project data |
|
388 @type str |
406 """ |
389 """ |
407 # step 1: check, if main should be changed |
390 # step 1: check, if main should be changed |
408 if pro["master"]: |
391 if pro.master: |
409 for project in self.__projects.values(): |
392 for project in self.__projects.values(): |
410 if project["master"]: |
393 if project.master: |
411 if project["uid"] != pro["uid"]: |
394 if project.uid != pro.uid: |
412 project["master"] = False |
395 project.master = False |
413 self.projectDataChanged.emit(project) |
396 self.projectDataChanged.emit(project) |
414 self.setDirty(True) |
397 self.setDirty(True) |
415 break |
398 break |
416 |
399 |
417 # step 2: change the entry |
400 # step 2: change the entry |
418 project = self.__projects[pro["uid"]] |
401 project = self.__projects[pro.uid] |
419 # project UID is not changeable via interface |
402 # project UID is not changeable via interface |
420 project["file"] = pro["file"] |
403 project.file = pro.file |
421 project["name"] = pro["name"] |
404 project.name = pro.name |
422 project["master"] = pro["master"] |
405 project.master = pro.master |
423 project["description"] = pro["description"] |
406 project.description = pro.description |
424 project["category"] = pro["category"] |
407 project.category = pro.category |
425 if project["category"] not in self.categories: |
408 if project.category not in self.categories: |
426 self.categories.append(project["category"]) |
409 self.categories.append(project.category) |
427 self.projectDataChanged.emit(project) |
410 self.projectDataChanged.emit(project) |
428 self.setDirty(True) |
411 self.setDirty(True) |
429 |
412 |
430 def getProjects(self): |
413 def getProjects(self): |
431 """ |
414 """ |
432 Public method to get all project entries. |
415 Public method to get all project entries. |
433 |
416 |
434 @return list of all project entries (list of dictionaries) |
417 @return list of all project entries |
|
418 @rtype list of MultiProjectProjectMeta |
435 """ |
419 """ |
436 return self.__projects.values() |
420 return self.__projects.values() |
437 |
421 |
438 def getProject(self, uid): |
422 def getProject(self, uid): |
439 """ |
423 """ |
440 Public method to get a reference to a project entry. |
424 Public method to get a reference to a project entry. |
441 |
425 |
442 @param uid UID of the project to get |
426 @param uid UID of the project to get |
443 @type str |
427 @type str |
444 @return dictionary containing the project data |
428 @return project metadata |
445 @rtype dict |
429 @rtype MultiProjectProjectMeta |
446 """ |
430 """ |
447 if uid in self.__projects: |
431 if uid in self.__projects: |
448 return self.__projects[uid] |
432 return self.__projects[uid] |
449 else: |
433 else: |
450 return None |
434 return None |
821 ) |
807 ) |
822 ) |
808 ) |
823 self.propsAct.triggered.connect(self.__showProperties) |
809 self.propsAct.triggered.connect(self.__showProperties) |
824 self.actions.append(self.propsAct) |
810 self.actions.append(self.propsAct) |
825 |
811 |
|
812 self.clearRemovedAct = EricAction( |
|
813 self.tr("Clear Out"), |
|
814 EricPixmapCache.getIcon("clear"), |
|
815 self.tr("Clear Out"), |
|
816 0, |
|
817 0, |
|
818 self, |
|
819 "multi_project_clearout", |
|
820 ) |
|
821 self.clearRemovedAct.setStatusTip( |
|
822 self.tr("Remove all projects marked as removed") |
|
823 ) |
|
824 self.clearRemovedAct.setWhatsThis( |
|
825 self.tr( |
|
826 """<b>Clear Out...</b>""" |
|
827 """<p>This removes all projects marked as removed.</p>""" |
|
828 ) |
|
829 ) |
|
830 self.clearRemovedAct.triggered.connect(self.clearRemovedProjects) |
|
831 self.actions.append(self.clearRemovedAct) |
|
832 |
826 self.closeAct.setEnabled(False) |
833 self.closeAct.setEnabled(False) |
827 self.saveAct.setEnabled(False) |
834 self.saveAct.setEnabled(False) |
828 self.saveasAct.setEnabled(False) |
835 self.saveasAct.setEnabled(False) |
829 self.addProjectAct.setEnabled(False) |
836 self.addProjectAct.setEnabled(False) |
830 self.propsAct.setEnabled(False) |
837 self.propsAct.setEnabled(False) |
|
838 self.clearRemovedAct.setEnabled(False) |
831 |
839 |
832 def initMenu(self): |
840 def initMenu(self): |
833 """ |
841 """ |
834 Public slot to initialize the multi project menu. |
842 Public slot to initialize the multi project menu. |
835 |
843 |
836 @return the menu generated (QMenu) |
844 @return the menu generated |
|
845 @rtype QMenu |
837 """ |
846 """ |
838 menu = QMenu(self.tr("&Multiproject"), self.parent()) |
847 menu = QMenu(self.tr("&Multiproject"), self.parent()) |
839 self.recentMenu = QMenu(self.tr("Open &Recent Multiprojects"), menu) |
848 self.recentMenu = QMenu(self.tr("Open &Recent Multiprojects"), menu) |
840 |
849 |
841 self.__menus = { |
850 self.__menus = { |
858 menu.addAction(self.saveAct) |
867 menu.addAction(self.saveAct) |
859 menu.addAction(self.saveasAct) |
868 menu.addAction(self.saveasAct) |
860 menu.addSeparator() |
869 menu.addSeparator() |
861 menu.addAction(self.addProjectAct) |
870 menu.addAction(self.addProjectAct) |
862 menu.addSeparator() |
871 menu.addSeparator() |
|
872 menu.addAction(self.clearRemovedAct) |
|
873 menu.addSeparator() |
863 menu.addAction(self.propsAct) |
874 menu.addAction(self.propsAct) |
864 |
875 |
865 self.menu = menu |
876 self.menu = menu |
866 return menu |
877 return menu |
867 |
878 |
868 def initToolbar(self, toolbarManager): |
879 def initToolbar(self, toolbarManager): |
869 """ |
880 """ |
870 Public slot to initialize the multi project toolbar. |
881 Public slot to initialize the multi project toolbar. |
871 |
882 |
872 @param toolbarManager reference to a toolbar manager object |
883 @param toolbarManager reference to a toolbar manager object |
873 (EricToolBarManager) |
884 @type EricToolBarManager |
874 @return the toolbar generated (QToolBar) |
885 @return the toolbar generated |
|
886 @rtype QToolBar |
875 """ |
887 """ |
876 tb = QToolBar(self.tr("Multiproject"), self.ui) |
888 tb = QToolBar(self.tr("Multiproject"), self.ui) |
877 tb.setObjectName("MultiProjectToolbar") |
889 tb.setObjectName("MultiProjectToolbar") |
878 tb.setToolTip(self.tr("Multiproject")) |
890 tb.setToolTip(self.tr("Multiproject")) |
879 |
891 |
952 |
966 |
953 def getActions(self): |
967 def getActions(self): |
954 """ |
968 """ |
955 Public method to get a list of all actions. |
969 Public method to get a list of all actions. |
956 |
970 |
957 @return list of all actions (list of EricAction) |
971 @return list of all actions |
|
972 @rtype list of EricAction |
958 """ |
973 """ |
959 return self.actions[:] |
974 return self.actions[:] |
960 |
975 |
961 def addEricActions(self, actions): |
976 def addEricActions(self, actions): |
962 """ |
977 """ |
963 Public method to add actions to the list of actions. |
978 Public method to add actions to the list of actions. |
964 |
979 |
965 @param actions list of actions (list of EricAction) |
980 @param actions list of actions |
|
981 @type list of EricAction |
966 """ |
982 """ |
967 self.actions.extend(actions) |
983 self.actions.extend(actions) |
968 |
984 |
969 def removeEricActions(self, actions): |
985 def removeEricActions(self, actions): |
970 """ |
986 """ |
971 Public method to remove actions from the list of actions. |
987 Public method to remove actions from the list of actions. |
972 |
988 |
973 @param actions list of actions (list of EricAction) |
989 @param actions list of actions |
|
990 @type list of EricAction |
974 """ |
991 """ |
975 for act in actions: |
992 for act in actions: |
976 with contextlib.suppress(ValueError): |
993 with contextlib.suppress(ValueError): |
977 self.actions.remove(act) |
994 self.actions.remove(act) |
978 |
995 |
979 def getMenu(self, menuName): |
996 def getMenu(self, menuName): |
980 """ |
997 """ |
981 Public method to get a reference to the main menu or a submenu. |
998 Public method to get a reference to the main menu or a submenu. |
982 |
999 |
983 @param menuName name of the menu (string) |
1000 @param menuName name of the menu |
984 @return reference to the requested menu (QMenu) or None |
1001 @type str |
|
1002 @return reference to the requested menu |
|
1003 @rtype QMenu or None |
985 """ |
1004 """ |
986 try: |
1005 try: |
987 return self.__menus[menuName] |
1006 return self.__menus[menuName] |
988 except KeyError: |
1007 except KeyError: |
989 return None |
1008 return None |
990 |
1009 |
991 def openProject(self, filename): |
1010 def openProject(self, filename): |
992 """ |
1011 """ |
993 Public slot to open a project. |
1012 Public slot to open a project. |
994 |
1013 |
995 @param filename filename of the project file (string) |
1014 @param filename filename of the project file |
|
1015 @type str |
996 """ |
1016 """ |
997 self.projectObject.openProject(filename) |
1017 self.projectObject.openProject(filename) |
998 self.projectOpened.emit(filename) |
1018 self.projectOpened.emit(filename) |
999 |
1019 |
1000 def __openMainProject(self, reopen=True): |
1020 def __openMainProject(self, reopen=True): |
1001 """ |
1021 """ |
1002 Private slot to open the main project. |
1022 Private slot to open the main project. |
1003 |
1023 |
1004 @param reopen flag indicating, that the main project should be |
1024 @param reopen flag indicating, that the main project should be |
1005 reopened, if it has been opened already (boolean) |
1025 reopened, if it has been opened already |
|
1026 @type bool |
1006 """ |
1027 """ |
1007 for project in self.__projects.values(): |
1028 for project in self.__projects.values(): |
1008 if project["master"] and ( |
1029 if project.master and not project.removed and ( |
1009 reopen |
1030 reopen |
1010 or not self.projectObject.isOpen() |
1031 or not self.projectObject.isOpen() |
1011 or self.projectObject.getProjectFile() != project["file"] |
1032 or self.projectObject.getProjectFile() != project.file |
1012 ): |
1033 ): |
1013 self.openProject(project["file"]) |
1034 self.openProject(project.file) |
1014 return |
1035 return |
1015 |
1036 |
1016 def getMainProjectFile(self): |
1037 def getMainProjectFile(self): |
1017 """ |
1038 """ |
1018 Public method to get the filename of the main project. |
1039 Public method to get the filename of the main project. |
1019 |
1040 |
1020 @return name of the main project file |
1041 @return name of the main project file |
1021 @rtype str |
1042 @rtype str |
1022 """ |
1043 """ |
1023 for project in self.__projects: |
1044 for project in self.__projects: |
1024 if project["master"]: |
1045 if project.master: |
1025 return project["file"] |
1046 return project.file |
1026 |
1047 |
1027 return None |
1048 return None |
1028 |
1049 |
1029 def getDependantProjectFiles(self): |
1050 def getDependantProjectFiles(self): |
1030 """ |
1051 """ |
1031 Public method to get the filenames of the dependent projects. |
1052 Public method to get the filenames of the dependent projects. |
1032 |
1053 |
1033 @return names of the dependent project files (list of strings) |
1054 @return names of the dependent project files |
|
1055 @rtype list of str |
1034 """ |
1056 """ |
1035 files = [] |
1057 files = [] |
1036 for project in self.__projects.values(): |
1058 for project in self.__projects.values(): |
1037 if not project["master"]: |
1059 if not project.master: |
1038 files.append(project["file"]) |
1060 files.append(project.file) |
1039 return files |
1061 return files |
|
1062 |
|
1063 def __checkFilesExist(self): |
|
1064 """ |
|
1065 Private method to check, if the files in a list exist. |
|
1066 |
|
1067 The project files are checked for existance in the |
|
1068 filesystem. Non existant projects are removed from the list and the |
|
1069 dirty state of the multi project is changed accordingly. |
|
1070 """ |
|
1071 for project in self.__projects.values(): |
|
1072 project.removed = not os.path.exists(project.file) |
|
1073 self.clearRemovedAct.setEnabled(True) |
|
1074 |
|
1075 @pyqtSlot() |
|
1076 def clearRemovedProjects(self): |
|
1077 """ |
|
1078 Public slot to clear out all projects marked as removed. |
|
1079 """ |
|
1080 for key in list(self.__projects.keys()): |
|
1081 if self.__projects[key].removed: |
|
1082 self.removeProject(key) |
|
1083 |
|
1084 self.clearRemovedAct.setEnabled(False) |
|
1085 |
|
1086 def hasRemovedProjects(self): |
|
1087 """ |
|
1088 Public method to check for removed projects. |
|
1089 |
|
1090 @return flag indicating the existence of a removed project |
|
1091 @rtype bool |
|
1092 """ |
|
1093 return any(p.removed for p in self.__projects.values()) |