21 |
21 |
22 class MultiProjectBrowser(QTreeWidget): |
22 class MultiProjectBrowser(QTreeWidget): |
23 """ |
23 """ |
24 Class implementing the multi project browser. |
24 Class implementing the multi project browser. |
25 """ |
25 """ |
|
26 |
26 ProjectFileNameRole = Qt.ItemDataRole.UserRole |
27 ProjectFileNameRole = Qt.ItemDataRole.UserRole |
27 ProjectUidRole = Qt.ItemDataRole.UserRole + 1 |
28 ProjectUidRole = Qt.ItemDataRole.UserRole + 1 |
28 |
29 |
29 def __init__(self, multiProject, project, parent=None): |
30 def __init__(self, multiProject, project, parent=None): |
30 """ |
31 """ |
31 Constructor |
32 Constructor |
32 |
33 |
33 @param multiProject reference to the multi project object |
34 @param multiProject reference to the multi project object |
34 @type MultiProject |
35 @type MultiProject |
35 @param project reference to the project object |
36 @param project reference to the project object |
36 @type Project |
37 @type Project |
37 @param parent parent widget |
38 @param parent parent widget |
38 @type QWidget |
39 @type QWidget |
39 """ |
40 """ |
40 super().__init__(parent) |
41 super().__init__(parent) |
41 self.multiProject = multiProject |
42 self.multiProject = multiProject |
42 self.project = project |
43 self.project = project |
43 |
44 |
44 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) |
45 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) |
45 self.setAlternatingRowColors(True) |
46 self.setAlternatingRowColors(True) |
46 self.setHeaderHidden(True) |
47 self.setHeaderHidden(True) |
47 self.setItemsExpandable(False) |
48 self.setItemsExpandable(False) |
48 self.setRootIsDecorated(False) |
49 self.setRootIsDecorated(False) |
49 self.setSortingEnabled(True) |
50 self.setSortingEnabled(True) |
50 |
51 |
51 self.__openingProject = False |
52 self.__openingProject = False |
52 |
53 |
53 self.multiProject.newMultiProject.connect( |
54 self.multiProject.newMultiProject.connect(self.__newMultiProject) |
54 self.__newMultiProject) |
55 self.multiProject.multiProjectOpened.connect(self.__multiProjectOpened) |
55 self.multiProject.multiProjectOpened.connect( |
56 self.multiProject.multiProjectClosed.connect(self.__multiProjectClosed) |
56 self.__multiProjectOpened) |
57 self.multiProject.projectDataChanged.connect(self.__projectDataChanged) |
57 self.multiProject.multiProjectClosed.connect( |
58 self.multiProject.projectAdded.connect(self.__projectAdded) |
58 self.__multiProjectClosed) |
59 self.multiProject.projectRemoved.connect(self.__projectRemoved) |
59 self.multiProject.projectDataChanged.connect( |
60 |
60 self.__projectDataChanged) |
|
61 self.multiProject.projectAdded.connect( |
|
62 self.__projectAdded) |
|
63 self.multiProject.projectRemoved.connect( |
|
64 self.__projectRemoved) |
|
65 |
|
66 self.project.projectOpened.connect(self.__projectOpened) |
61 self.project.projectOpened.connect(self.__projectOpened) |
67 self.project.projectClosed.connect(self.__projectClosed) |
62 self.project.projectClosed.connect(self.__projectClosed) |
68 |
63 |
69 self.__createPopupMenu() |
64 self.__createPopupMenu() |
70 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
65 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
71 self.customContextMenuRequested.connect(self.__contextMenuRequested) |
66 self.customContextMenuRequested.connect(self.__contextMenuRequested) |
72 self.itemActivated.connect(self.__openItem) |
67 self.itemActivated.connect(self.__openItem) |
73 |
68 |
74 self.setEnabled(False) |
69 self.setEnabled(False) |
75 |
70 |
76 ########################################################################### |
71 ########################################################################### |
77 ## Slot handling methods below |
72 ## Slot handling methods below |
78 ########################################################################### |
73 ########################################################################### |
79 |
74 |
80 def __newMultiProject(self): |
75 def __newMultiProject(self): |
81 """ |
76 """ |
82 Private slot to handle the creation of a new multi project. |
77 Private slot to handle the creation of a new multi project. |
83 """ |
78 """ |
84 self.clear() |
79 self.clear() |
85 self.setEnabled(True) |
80 self.setEnabled(True) |
86 |
81 |
87 def __multiProjectOpened(self): |
82 def __multiProjectOpened(self): |
88 """ |
83 """ |
89 Private slot to handle the opening of a multi project. |
84 Private slot to handle the opening of a multi project. |
90 """ |
85 """ |
91 for project in self.multiProject.getProjects(): |
86 for project in self.multiProject.getProjects(): |
92 self.__addProject(project) |
87 self.__addProject(project) |
93 |
88 |
94 self.sortItems(0, Qt.SortOrder.AscendingOrder) |
89 self.sortItems(0, Qt.SortOrder.AscendingOrder) |
95 |
90 |
96 self.setEnabled(True) |
91 self.setEnabled(True) |
97 |
92 |
98 def __multiProjectClosed(self): |
93 def __multiProjectClosed(self): |
99 """ |
94 """ |
100 Private slot to handle the closing of a multi project. |
95 Private slot to handle the closing of a multi project. |
101 """ |
96 """ |
102 self.clear() |
97 self.clear() |
103 self.setEnabled(False) |
98 self.setEnabled(False) |
104 |
99 |
105 def __projectAdded(self, project): |
100 def __projectAdded(self, project): |
106 """ |
101 """ |
107 Private slot to handle the addition of a project to the multi project. |
102 Private slot to handle the addition of a project to the multi project. |
108 |
103 |
109 @param project reference to the project data dictionary |
104 @param project reference to the project data dictionary |
110 """ |
105 """ |
111 self.__addProject(project) |
106 self.__addProject(project) |
112 self.sortItems(0, Qt.SortOrder.AscendingOrder) |
107 self.sortItems(0, Qt.SortOrder.AscendingOrder) |
113 |
108 |
114 def __projectRemoved(self, project): |
109 def __projectRemoved(self, project): |
115 """ |
110 """ |
116 Private slot to handle the removal of a project from the multi project. |
111 Private slot to handle the removal of a project from the multi project. |
117 |
112 |
118 @param project reference to the project data dictionary |
113 @param project reference to the project data dictionary |
119 """ |
114 """ |
120 itm = self.__findProjectItem(project) |
115 itm = self.__findProjectItem(project) |
121 if itm: |
116 if itm: |
122 parent = itm.parent() |
117 parent = itm.parent() |
172 for childIndex in range(topItem.childCount()): |
167 for childIndex in range(topItem.childCount()): |
173 childItem = topItem.child(childIndex) |
168 childItem = topItem.child(childIndex) |
174 font = childItem.font(0) |
169 font = childItem.font(0) |
175 font.setBold(False) |
170 font.setBold(False) |
176 childItem.setFont(0, font) |
171 childItem.setFont(0, font) |
177 |
172 |
178 def __contextMenuRequested(self, coord): |
173 def __contextMenuRequested(self, coord): |
179 """ |
174 """ |
180 Private slot to show the context menu. |
175 Private slot to show the context menu. |
181 |
176 |
182 @param coord the position of the mouse pointer (QPoint) |
177 @param coord the position of the mouse pointer (QPoint) |
183 """ |
178 """ |
184 itm = self.itemAt(coord) |
179 itm = self.itemAt(coord) |
185 if itm is None or itm.parent() is None: |
180 if itm is None or itm.parent() is None: |
186 self.__backMenu.popup(self.mapToGlobal(coord)) |
181 self.__backMenu.popup(self.mapToGlobal(coord)) |
187 else: |
182 else: |
188 self.__menu.popup(self.mapToGlobal(coord)) |
183 self.__menu.popup(self.mapToGlobal(coord)) |
189 |
184 |
190 def __openItem(self, itm=None): |
185 def __openItem(self, itm=None): |
191 """ |
186 """ |
192 Private slot to open a project. |
187 Private slot to open a project. |
193 |
188 |
194 @param itm reference to the project item to be opened (QTreeWidgetItem) |
189 @param itm reference to the project item to be opened (QTreeWidgetItem) |
195 """ |
190 """ |
196 if itm is None: |
191 if itm is None: |
197 itm = self.currentItem() |
192 itm = self.currentItem() |
198 if itm is None or itm.parent() is None: |
193 if itm is None or itm.parent() is None: |
199 return |
194 return |
200 |
195 |
201 if not self.__openingProject: |
196 if not self.__openingProject: |
202 filename = itm.data(0, MultiProjectBrowser.ProjectFileNameRole) |
197 filename = itm.data(0, MultiProjectBrowser.ProjectFileNameRole) |
203 if filename: |
198 if filename: |
204 self.__openingProject = True |
199 self.__openingProject = True |
205 self.multiProject.openProject(filename) |
200 self.multiProject.openProject(filename) |
206 self.__openingProject = False |
201 self.__openingProject = False |
207 |
202 |
208 ########################################################################### |
203 ########################################################################### |
209 ## Private methods below |
204 ## Private methods below |
210 ########################################################################### |
205 ########################################################################### |
211 |
206 |
212 def __findCategoryItem(self, category): |
207 def __findCategoryItem(self, category): |
213 """ |
208 """ |
214 Private method to find the item for a category. |
209 Private method to find the item for a category. |
215 |
210 |
216 @param category category to search for (string) |
211 @param category category to search for (string) |
217 @return reference to the category item or None, if there is |
212 @return reference to the category item or None, if there is |
218 no such item (QTreeWidgetItem or None) |
213 no such item (QTreeWidgetItem or None) |
219 """ |
214 """ |
220 if category == "": |
215 if category == "": |
221 category = self.tr("Not categorized") |
216 category = self.tr("Not categorized") |
222 for index in range(self.topLevelItemCount()): |
217 for index in range(self.topLevelItemCount()): |
223 itm = self.topLevelItem(index) |
218 itm = self.topLevelItem(index) |
224 if itm.text(0) == category: |
219 if itm.text(0) == category: |
225 return itm |
220 return itm |
226 |
221 |
227 return None |
222 return None |
228 |
223 |
229 def __addProject(self, project): |
224 def __addProject(self, project): |
230 """ |
225 """ |
231 Private method to add a project to the list. |
226 Private method to add a project to the list. |
232 |
227 |
233 @param project reference to the project data dictionary |
228 @param project reference to the project data dictionary |
234 """ |
229 """ |
235 parent = self.__findCategoryItem(project['category']) |
230 parent = self.__findCategoryItem(project["category"]) |
236 if parent is None: |
231 if parent is None: |
237 if project['category']: |
232 if project["category"]: |
238 parent = QTreeWidgetItem(self, [project['category']]) |
233 parent = QTreeWidgetItem(self, [project["category"]]) |
239 else: |
234 else: |
240 parent = QTreeWidgetItem(self, [self.tr("Not categorized")]) |
235 parent = QTreeWidgetItem(self, [self.tr("Not categorized")]) |
241 parent.setExpanded(True) |
236 parent.setExpanded(True) |
242 itm = QTreeWidgetItem(parent) |
237 itm = QTreeWidgetItem(parent) |
243 self.__setItemData(itm, project) |
238 self.__setItemData(itm, project) |
244 |
239 |
245 def __setItemData(self, itm, project): |
240 def __setItemData(self, itm, project): |
246 """ |
241 """ |
247 Private method to set the data of a project item. |
242 Private method to set the data of a project item. |
248 |
243 |
249 @param itm reference to the item to be set (QTreeWidgetItem) |
244 @param itm reference to the item to be set (QTreeWidgetItem) |
250 @param project reference to the project data dictionary |
245 @param project reference to the project data dictionary |
251 """ |
246 """ |
252 itm.setText(0, project['name']) |
247 itm.setText(0, project["name"]) |
253 if project['master']: |
248 if project["master"]: |
254 itm.setIcon(0, UI.PixmapCache.getIcon("masterProject")) |
249 itm.setIcon(0, UI.PixmapCache.getIcon("masterProject")) |
255 else: |
250 else: |
256 itm.setIcon(0, UI.PixmapCache.getIcon("empty")) |
251 itm.setIcon(0, UI.PixmapCache.getIcon("empty")) |
257 itm.setToolTip(0, project['file']) |
252 itm.setToolTip(0, project["file"]) |
258 itm.setData(0, MultiProjectBrowser.ProjectFileNameRole, |
253 itm.setData(0, MultiProjectBrowser.ProjectFileNameRole, project["file"]) |
259 project['file']) |
254 itm.setData(0, MultiProjectBrowser.ProjectUidRole, project["uid"]) |
260 itm.setData(0, MultiProjectBrowser.ProjectUidRole, project['uid']) |
255 |
261 |
|
262 def __findProjectItem(self, project): |
256 def __findProjectItem(self, project): |
263 """ |
257 """ |
264 Private method to search a specific project item. |
258 Private method to search a specific project item. |
265 |
259 |
266 @param project reference to the project data dictionary |
260 @param project reference to the project data dictionary |
267 @return reference to the item (QTreeWidgetItem) or None |
261 @return reference to the item (QTreeWidgetItem) or None |
268 """ |
262 """ |
269 if project["uid"]: |
263 if project["uid"]: |
270 compareData = project["uid"] |
264 compareData = project["uid"] |
271 compareRole = MultiProjectBrowser.ProjectUidRole |
265 compareRole = MultiProjectBrowser.ProjectUidRole |
272 else: |
266 else: |
273 compareData = project["file"] |
267 compareData = project["file"] |
274 compareRole = MultiProjectBrowser.ProjectFileNameRole |
268 compareRole = MultiProjectBrowser.ProjectFileNameRole |
275 |
269 |
276 for topIndex in range(self.topLevelItemCount()): |
270 for topIndex in range(self.topLevelItemCount()): |
277 topItm = self.topLevelItem(topIndex) |
271 topItm = self.topLevelItem(topIndex) |
278 for childIndex in range(topItm.childCount()): |
272 for childIndex in range(topItm.childCount()): |
279 itm = topItm.child(childIndex) |
273 itm = topItm.child(childIndex) |
280 data = itm.data(0, compareRole) |
274 data = itm.data(0, compareRole) |
281 if data == compareData: |
275 if data == compareData: |
282 return itm |
276 return itm |
283 |
277 |
284 return None |
278 return None |
285 |
279 |
286 def __removeProject(self): |
280 def __removeProject(self): |
287 """ |
281 """ |
288 Private method to handle the Remove context menu entry. |
282 Private method to handle the Remove context menu entry. |
289 """ |
283 """ |
290 itm = self.currentItem() |
284 itm = self.currentItem() |
291 if itm is not None and itm.parent() is not None: |
285 if itm is not None and itm.parent() is not None: |
292 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole) |
286 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole) |
293 if uid: |
287 if uid: |
294 self.multiProject.removeProject(uid) |
288 self.multiProject.removeProject(uid) |
295 |
289 |
296 def __deleteProject(self): |
290 def __deleteProject(self): |
297 """ |
291 """ |
298 Private method to handle the Delete context menu entry. |
292 Private method to handle the Delete context menu entry. |
299 """ |
293 """ |
300 itm = self.currentItem() |
294 itm = self.currentItem() |
301 if itm is not None and itm.parent() is not None: |
295 if itm is not None and itm.parent() is not None: |
302 projectFile = itm.data(0, MultiProjectBrowser.ProjectFileNameRole) |
296 projectFile = itm.data(0, MultiProjectBrowser.ProjectFileNameRole) |
303 projectPath = os.path.dirname(projectFile) |
297 projectPath = os.path.dirname(projectFile) |
304 |
298 |
305 if self.project.getProjectPath() == projectPath: |
299 if self.project.getProjectPath() == projectPath: |
306 EricMessageBox.warning( |
300 EricMessageBox.warning( |
307 self, |
301 self, |
308 self.tr("Delete Project"), |
302 self.tr("Delete Project"), |
309 self.tr("""The current project cannot be deleted.""" |
303 self.tr( |
310 """ Please close it first.""")) |
304 """The current project cannot be deleted.""" |
|
305 """ Please close it first.""" |
|
306 ), |
|
307 ) |
311 else: |
308 else: |
312 projectFiles = glob.glob(os.path.join(projectPath, "*.epj")) |
309 projectFiles = glob.glob(os.path.join(projectPath, "*.epj")) |
313 projectFiles += glob.glob(os.path.join(projectPath, "*.e4p")) |
310 projectFiles += glob.glob(os.path.join(projectPath, "*.e4p")) |
314 if not projectFiles: |
311 if not projectFiles: |
315 # Oops, that should not happen; play it save |
312 # Oops, that should not happen; play it save |
316 res = False |
313 res = False |
317 elif len(projectFiles) == 1: |
314 elif len(projectFiles) == 1: |
318 res = EricMessageBox.yesNo( |
315 res = EricMessageBox.yesNo( |
319 self, |
316 self, |
320 self.tr("Delete Project"), |
317 self.tr("Delete Project"), |
321 self.tr("""<p>Shall the project <b>{0}</b> (Path:""" |
318 self.tr( |
322 """ {1}) really be deleted?</p>""").format( |
319 """<p>Shall the project <b>{0}</b> (Path:""" |
323 itm.text(0), projectPath)) |
320 """ {1}) really be deleted?</p>""" |
|
321 ).format(itm.text(0), projectPath), |
|
322 ) |
324 else: |
323 else: |
325 res = EricMessageBox.yesNo( |
324 res = EricMessageBox.yesNo( |
326 self, |
325 self, |
327 self.tr("Delete Project"), |
326 self.tr("Delete Project"), |
328 self.tr("""<p>Shall the project <b>{0}</b> (Path:""" |
327 self.tr( |
329 """ {1}) really be deleted?</p>""" |
328 """<p>Shall the project <b>{0}</b> (Path:""" |
330 """<p><b>Warning:</b> It contains <b>{2}</b>""" |
329 """ {1}) really be deleted?</p>""" |
331 """ sub-projects.</p>""").format( |
330 """<p><b>Warning:</b> It contains <b>{2}</b>""" |
332 itm.text(0), projectPath, len(projectFiles))) |
331 """ sub-projects.</p>""" |
|
332 ).format(itm.text(0), projectPath, len(projectFiles)), |
|
333 ) |
333 if res: |
334 if res: |
334 for subprojectFile in projectFiles: |
335 for subprojectFile in projectFiles: |
335 # remove all sub-projects before deleting the directory |
336 # remove all sub-projects before deleting the directory |
336 if subprojectFile != projectFile: |
337 if subprojectFile != projectFile: |
337 projectData = { |
338 projectData = { |
338 'name': "", |
339 "name": "", |
339 'file': subprojectFile, |
340 "file": subprojectFile, |
340 'master': False, |
341 "master": False, |
341 'description': "", |
342 "description": "", |
342 'category': "", |
343 "category": "", |
343 'uid': "", |
344 "uid": "", |
344 } |
345 } |
345 pitm = self.__findProjectItem(projectData) |
346 pitm = self.__findProjectItem(projectData) |
346 if pitm: |
347 if pitm: |
347 uid = pitm.data( |
348 uid = pitm.data(0, MultiProjectBrowser.ProjectUidRole) |
348 0, MultiProjectBrowser.ProjectUidRole) |
|
349 if uid: |
349 if uid: |
350 self.multiProject.removeProject(uid) |
350 self.multiProject.removeProject(uid) |
351 |
351 |
352 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole) |
352 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole) |
353 if uid: |
353 if uid: |
354 self.multiProject.deleteProject(uid) |
354 self.multiProject.deleteProject(uid) |
355 |
355 |
356 def __showProjectProperties(self): |
356 def __showProjectProperties(self): |
357 """ |
357 """ |
358 Private method to show the data of a project entry. |
358 Private method to show the data of a project entry. |
359 """ |
359 """ |
360 itm = self.currentItem() |
360 itm = self.currentItem() |
392 else: |
401 else: |
393 category = itm.parent().text(0) |
402 category = itm.parent().text(0) |
394 else: |
403 else: |
395 category = "" |
404 category = "" |
396 self.multiProject.addNewProject(category=category) |
405 self.multiProject.addNewProject(category=category) |
397 |
406 |
398 def __copyProject(self): |
407 def __copyProject(self): |
399 """ |
408 """ |
400 Private method to copy the selected project on disk. |
409 Private method to copy the selected project on disk. |
401 """ |
410 """ |
402 itm = self.currentItem() |
411 itm = self.currentItem() |
403 if itm and itm.parent(): |
412 if itm and itm.parent(): |
404 # it is a project item and not a category |
413 # it is a project item and not a category |
405 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole) |
414 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole) |
406 if uid: |
415 if uid: |
407 self.multiProject.copyProject(uid) |
416 self.multiProject.copyProject(uid) |
408 |
417 |
409 def __createPopupMenu(self): |
418 def __createPopupMenu(self): |
410 """ |
419 """ |
411 Private method to create the popup menu. |
420 Private method to create the popup menu. |
412 """ |
421 """ |
413 self.__menu = QMenu(self) |
422 self.__menu = QMenu(self) |
414 self.__menu.addAction(self.tr("Open"), self.__openItem) |
423 self.__menu.addAction(self.tr("Open"), self.__openItem) |
415 self.__menu.addAction(self.tr("Remove from Multi Project"), |
424 self.__menu.addAction( |
416 self.__removeProject) |
425 self.tr("Remove from Multi Project"), self.__removeProject |
417 self.__menu.addAction(self.tr("Delete from Disk"), |
426 ) |
418 self.__deleteProject) |
427 self.__menu.addAction(self.tr("Delete from Disk"), self.__deleteProject) |
419 self.__menu.addAction(self.tr("Properties"), |
428 self.__menu.addAction(self.tr("Properties"), self.__showProjectProperties) |
420 self.__showProjectProperties) |
|
421 self.__menu.addSeparator() |
429 self.__menu.addSeparator() |
422 self.__menu.addAction(self.tr("Add Project..."), |
430 self.__menu.addAction(self.tr("Add Project..."), self.__addNewProject) |
423 self.__addNewProject) |
431 self.__menu.addAction(self.tr("Copy Project..."), self.__copyProject) |
424 self.__menu.addAction(self.tr("Copy Project..."), |
|
425 self.__copyProject) |
|
426 self.__menu.addSeparator() |
432 self.__menu.addSeparator() |
427 self.__menu.addAction(self.tr("Configure..."), self.__configure) |
433 self.__menu.addAction(self.tr("Configure..."), self.__configure) |
428 |
434 |
429 self.__backMenu = QMenu(self) |
435 self.__backMenu = QMenu(self) |
430 self.__backMenu.addAction(self.tr("Add Project..."), |
436 self.__backMenu.addAction(self.tr("Add Project..."), self.__addNewProject) |
431 self.__addNewProject) |
|
432 self.__backMenu.addSeparator() |
437 self.__backMenu.addSeparator() |
433 self.__backMenu.addAction(self.tr("Configure..."), |
438 self.__backMenu.addAction(self.tr("Configure..."), self.__configure) |
434 self.__configure) |
439 |
435 |
|
436 def __configure(self): |
440 def __configure(self): |
437 """ |
441 """ |
438 Private method to open the configuration dialog. |
442 Private method to open the configuration dialog. |
439 """ |
443 """ |
440 ericApp().getObject("UserInterface").showPreferences( |
444 ericApp().getObject("UserInterface").showPreferences("multiProjectPage") |
441 "multiProjectPage") |
|