eric6/MultiProject/MultiProjectBrowser.py

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

eric ide

mercurial