src/eric7/MultiProject/MultiProjectBrowser.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2008 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the multi project browser.
8 """
9
10 import os
11 import glob
12
13 from PyQt6.QtCore import Qt
14 from PyQt6.QtWidgets import QTreeWidget, QTreeWidgetItem, QDialog, QMenu
15
16 from EricWidgets.EricApplication import ericApp
17 from EricWidgets import EricMessageBox
18
19 import UI.PixmapCache
20
21
22 class MultiProjectBrowser(QTreeWidget):
23 """
24 Class implementing the multi project browser.
25 """
26 ProjectFileNameRole = Qt.ItemDataRole.UserRole
27 ProjectUidRole = Qt.ItemDataRole.UserRole + 1
28
29 def __init__(self, multiProject, project, parent=None):
30 """
31 Constructor
32
33 @param multiProject reference to the multi project object
34 @type MultiProject
35 @param project reference to the project object
36 @type Project
37 @param parent parent widget
38 @type QWidget
39 """
40 super().__init__(parent)
41 self.multiProject = multiProject
42 self.project = project
43
44 self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
45 self.setAlternatingRowColors(True)
46 self.setHeaderHidden(True)
47 self.setItemsExpandable(False)
48 self.setRootIsDecorated(False)
49 self.setSortingEnabled(True)
50
51 self.__openingProject = False
52
53 self.multiProject.newMultiProject.connect(
54 self.__newMultiProject)
55 self.multiProject.multiProjectOpened.connect(
56 self.__multiProjectOpened)
57 self.multiProject.multiProjectClosed.connect(
58 self.__multiProjectClosed)
59 self.multiProject.projectDataChanged.connect(
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)
67 self.project.projectClosed.connect(self.__projectClosed)
68
69 self.__createPopupMenu()
70 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
71 self.customContextMenuRequested.connect(self.__contextMenuRequested)
72 self.itemActivated.connect(self.__openItem)
73
74 self.setEnabled(False)
75
76 ###########################################################################
77 ## Slot handling methods below
78 ###########################################################################
79
80 def __newMultiProject(self):
81 """
82 Private slot to handle the creation of a new multi project.
83 """
84 self.clear()
85 self.setEnabled(True)
86
87 def __multiProjectOpened(self):
88 """
89 Private slot to handle the opening of a multi project.
90 """
91 for project in self.multiProject.getProjects():
92 self.__addProject(project)
93
94 self.sortItems(0, Qt.SortOrder.AscendingOrder)
95
96 self.setEnabled(True)
97
98 def __multiProjectClosed(self):
99 """
100 Private slot to handle the closing of a multi project.
101 """
102 self.clear()
103 self.setEnabled(False)
104
105 def __projectAdded(self, project):
106 """
107 Private slot to handle the addition of a project to the multi project.
108
109 @param project reference to the project data dictionary
110 """
111 self.__addProject(project)
112 self.sortItems(0, Qt.SortOrder.AscendingOrder)
113
114 def __projectRemoved(self, project):
115 """
116 Private slot to handle the removal of a project from the multi project.
117
118 @param project reference to the project data dictionary
119 """
120 itm = self.__findProjectItem(project)
121 if itm:
122 parent = itm.parent()
123 parent.removeChild(itm)
124 del itm
125 if parent.childCount() == 0:
126 top = self.takeTopLevelItem(self.indexOfTopLevelItem(parent))
127 # __IGNORE_WARNING__
128 del top
129
130 def __projectDataChanged(self, project):
131 """
132 Private slot to handle the change of a project of the multi project.
133
134 @param project reference to the project data dictionary
135 """
136 itm = self.__findProjectItem(project)
137 if itm:
138 parent = itm.parent()
139 if parent.text(0) != project["category"]:
140 self.__projectRemoved(project)
141 self.__addProject(project)
142 else:
143 self.__setItemData(itm, project)
144
145 self.sortItems(0, Qt.SortOrder.AscendingOrder)
146
147 def __projectOpened(self):
148 """
149 Private slot to handle the opening of a project.
150 """
151 projectfile = self.project.getProjectFile()
152 project = {
153 'name': "",
154 'file': projectfile,
155 'master': False,
156 'description': "",
157 'category': "",
158 'uid': "",
159 }
160 itm = self.__findProjectItem(project)
161 if itm:
162 font = itm.font(0)
163 font.setBold(True)
164 itm.setFont(0, font)
165
166 def __projectClosed(self):
167 """
168 Private slot to handle the closing of a project.
169 """
170 for topIndex in range(self.topLevelItemCount()):
171 topItem = self.topLevelItem(topIndex)
172 for childIndex in range(topItem.childCount()):
173 childItem = topItem.child(childIndex)
174 font = childItem.font(0)
175 font.setBold(False)
176 childItem.setFont(0, font)
177
178 def __contextMenuRequested(self, coord):
179 """
180 Private slot to show the context menu.
181
182 @param coord the position of the mouse pointer (QPoint)
183 """
184 itm = self.itemAt(coord)
185 if itm is None or itm.parent() is None:
186 self.__backMenu.popup(self.mapToGlobal(coord))
187 else:
188 self.__menu.popup(self.mapToGlobal(coord))
189
190 def __openItem(self, itm=None):
191 """
192 Private slot to open a project.
193
194 @param itm reference to the project item to be opened (QTreeWidgetItem)
195 """
196 if itm is None:
197 itm = self.currentItem()
198 if itm is None or itm.parent() is None:
199 return
200
201 if not self.__openingProject:
202 filename = itm.data(0, MultiProjectBrowser.ProjectFileNameRole)
203 if filename:
204 self.__openingProject = True
205 self.multiProject.openProject(filename)
206 self.__openingProject = False
207
208 ###########################################################################
209 ## Private methods below
210 ###########################################################################
211
212 def __findCategoryItem(self, category):
213 """
214 Private method to find the item for a category.
215
216 @param category category to search for (string)
217 @return reference to the category item or None, if there is
218 no such item (QTreeWidgetItem or None)
219 """
220 if category == "":
221 category = self.tr("Not categorized")
222 for index in range(self.topLevelItemCount()):
223 itm = self.topLevelItem(index)
224 if itm.text(0) == category:
225 return itm
226
227 return None
228
229 def __addProject(self, project):
230 """
231 Private method to add a project to the list.
232
233 @param project reference to the project data dictionary
234 """
235 parent = self.__findCategoryItem(project['category'])
236 if parent is None:
237 if project['category']:
238 parent = QTreeWidgetItem(self, [project['category']])
239 else:
240 parent = QTreeWidgetItem(self, [self.tr("Not categorized")])
241 parent.setExpanded(True)
242 itm = QTreeWidgetItem(parent)
243 self.__setItemData(itm, project)
244
245 def __setItemData(self, itm, project):
246 """
247 Private method to set the data of a project item.
248
249 @param itm reference to the item to be set (QTreeWidgetItem)
250 @param project reference to the project data dictionary
251 """
252 itm.setText(0, project['name'])
253 if project['master']:
254 itm.setIcon(0, UI.PixmapCache.getIcon("masterProject"))
255 else:
256 itm.setIcon(0, UI.PixmapCache.getIcon("empty"))
257 itm.setToolTip(0, project['file'])
258 itm.setData(0, MultiProjectBrowser.ProjectFileNameRole,
259 project['file'])
260 itm.setData(0, MultiProjectBrowser.ProjectUidRole, project['uid'])
261
262 def __findProjectItem(self, project):
263 """
264 Private method to search a specific project item.
265
266 @param project reference to the project data dictionary
267 @return reference to the item (QTreeWidgetItem) or None
268 """
269 if project["uid"]:
270 compareData = project["uid"]
271 compareRole = MultiProjectBrowser.ProjectUidRole
272 else:
273 compareData = project["file"]
274 compareRole = MultiProjectBrowser.ProjectFileNameRole
275
276 for topIndex in range(self.topLevelItemCount()):
277 topItm = self.topLevelItem(topIndex)
278 for childIndex in range(topItm.childCount()):
279 itm = topItm.child(childIndex)
280 data = itm.data(0, compareRole)
281 if data == compareData:
282 return itm
283
284 return None
285
286 def __removeProject(self):
287 """
288 Private method to handle the Remove context menu entry.
289 """
290 itm = self.currentItem()
291 if itm is not None and itm.parent() is not None:
292 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole)
293 if uid:
294 self.multiProject.removeProject(uid)
295
296 def __deleteProject(self):
297 """
298 Private method to handle the Delete context menu entry.
299 """
300 itm = self.currentItem()
301 if itm is not None and itm.parent() is not None:
302 projectFile = itm.data(0, MultiProjectBrowser.ProjectFileNameRole)
303 projectPath = os.path.dirname(projectFile)
304
305 if self.project.getProjectPath() == projectPath:
306 EricMessageBox.warning(
307 self,
308 self.tr("Delete Project"),
309 self.tr("""The current project cannot be deleted."""
310 """ Please close it first."""))
311 else:
312 projectFiles = glob.glob(os.path.join(projectPath, "*.epj"))
313 projectFiles += glob.glob(os.path.join(projectPath, "*.e4p"))
314 if not projectFiles:
315 # Oops, that should not happen; play it save
316 res = False
317 elif len(projectFiles) == 1:
318 res = EricMessageBox.yesNo(
319 self,
320 self.tr("Delete Project"),
321 self.tr("""<p>Shall the project <b>{0}</b> (Path:"""
322 """ {1}) really be deleted?</p>""").format(
323 itm.text(0), projectPath))
324 else:
325 res = EricMessageBox.yesNo(
326 self,
327 self.tr("Delete Project"),
328 self.tr("""<p>Shall the project <b>{0}</b> (Path:"""
329 """ {1}) really be deleted?</p>"""
330 """<p><b>Warning:</b> It contains <b>{2}</b>"""
331 """ sub-projects.</p>""").format(
332 itm.text(0), projectPath, len(projectFiles)))
333 if res:
334 for subprojectFile in projectFiles:
335 # remove all sub-projects before deleting the directory
336 if subprojectFile != projectFile:
337 projectData = {
338 'name': "",
339 'file': subprojectFile,
340 'master': False,
341 'description': "",
342 'category': "",
343 'uid': "",
344 }
345 pitm = self.__findProjectItem(projectData)
346 if pitm:
347 uid = pitm.data(
348 0, MultiProjectBrowser.ProjectUidRole)
349 if uid:
350 self.multiProject.removeProject(uid)
351
352 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole)
353 if uid:
354 self.multiProject.deleteProject(uid)
355
356 def __showProjectProperties(self):
357 """
358 Private method to show the data of a project entry.
359 """
360 itm = self.currentItem()
361 if itm is not None and itm.parent() is not None:
362 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole)
363 if uid:
364 project = self.multiProject.getProject(uid)
365 if project is not None:
366 from .AddProjectDialog import AddProjectDialog
367 dlg = AddProjectDialog(
368 self, project=project,
369 categories=self.multiProject.getCategories())
370 if dlg.exec() == QDialog.DialogCode.Accepted:
371 (name, filename, isMaster, description, category,
372 uid) = dlg.getData()
373 project = {
374 'name': name,
375 'file': filename,
376 'master': isMaster,
377 'description': description,
378 'category': category,
379 'uid': uid,
380 }
381 self.multiProject.changeProjectProperties(project)
382
383 def __addNewProject(self):
384 """
385 Private method to add a new project entry.
386 """
387 itm = self.currentItem()
388 if itm is not None:
389 if itm.parent() is None:
390 # current item is a category item
391 category = itm.text(0)
392 else:
393 category = itm.parent().text(0)
394 else:
395 category = ""
396 self.multiProject.addNewProject(category=category)
397
398 def __copyProject(self):
399 """
400 Private method to copy the selected project on disk.
401 """
402 itm = self.currentItem()
403 if itm and itm.parent():
404 # it is a project item and not a category
405 uid = itm.data(0, MultiProjectBrowser.ProjectUidRole)
406 if uid:
407 self.multiProject.copyProject(uid)
408
409 def __createPopupMenu(self):
410 """
411 Private method to create the popup menu.
412 """
413 self.__menu = QMenu(self)
414 self.__menu.addAction(self.tr("Open"), self.__openItem)
415 self.__menu.addAction(self.tr("Remove from Multi Project"),
416 self.__removeProject)
417 self.__menu.addAction(self.tr("Delete from Disk"),
418 self.__deleteProject)
419 self.__menu.addAction(self.tr("Properties"),
420 self.__showProjectProperties)
421 self.__menu.addSeparator()
422 self.__menu.addAction(self.tr("Add Project..."),
423 self.__addNewProject)
424 self.__menu.addAction(self.tr("Copy Project..."),
425 self.__copyProject)
426 self.__menu.addSeparator()
427 self.__menu.addAction(self.tr("Configure..."), self.__configure)
428
429 self.__backMenu = QMenu(self)
430 self.__backMenu.addAction(self.tr("Add Project..."),
431 self.__addNewProject)
432 self.__backMenu.addSeparator()
433 self.__backMenu.addAction(self.tr("Configure..."),
434 self.__configure)
435
436 def __configure(self):
437 """
438 Private method to open the configuration dialog.
439 """
440 ericApp().getObject("UserInterface").showPreferences(
441 "multiProjectPage")

eric ide

mercurial