src/eric7/MultiProject/MultiProject.py

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

eric ide

mercurial