1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2005 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing the base class of the VCS project browser helper.
8"""
9
10import os
11
12from PyQt5.QtCore import QObject, QCoreApplication
13from PyQt5.QtWidgets import QDialog
14
15from E5Gui.E5Application import e5App
16
17from UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
18
19from Project.ProjectBrowserModel import (
20    ProjectBrowserSimpleDirectoryItem, ProjectBrowserFileItem,
21    ProjectBrowserDirectoryItem
22)
23
24import Preferences
25
26
27class VcsProjectBrowserHelper(QObject):
28    """
29    Class implementing the base class of the VCS project browser helper.
30    """
31    def __init__(self, vcsObject, browserObject, projectObject,
32                 isTranslationsBrowser, parent=None, name=None):
33        """
34        Constructor
35
36        @param vcsObject reference to the vcs object
37        @param browserObject reference to the project browser object
38        @param projectObject reference to the project object
39        @param isTranslationsBrowser flag indicating, the helper is requested
40            for the translations browser (this needs some special treatment)
41        @param parent parent widget (QWidget)
42        @param name name of this object (string)
43        """
44        super().__init__(parent)
45        if name:
46            self.setObjectName(name)
47
48        self.vcs = vcsObject
49        self.browser = browserObject
50        self.isTranslationsBrowser = isTranslationsBrowser
51        self.project = projectObject
52
53    def addVCSMenus(self, mainMenu, multiMenu, backMenu, dirMenu,
54                    dirMultiMenu):
55        """
56        Public method to add the VCS entries to the various project browser
57        menus.
58
59        @param mainMenu reference to the main menu (QPopupMenu)
60        @param multiMenu reference to the multiple selection menu (QPopupMenu)
61        @param backMenu reference to the background menu (QPopupMenu)
62        @param dirMenu reference to the directory menu (QPopupMenu)
63        @param dirMultiMenu reference to the multiple selection directory
64            menu (QPopupMenu)
65        """
66        self._addVCSMenu(mainMenu)
67        self._addVCSMenuMulti(multiMenu)
68        self._addVCSMenuBack(backMenu)
69        self._addVCSMenuDir(dirMenu)
70        self._addVCSMenuDirMulti(dirMultiMenu)
71
72    def showContextMenu(self, menu, standardItems):
73        """
74        Public slot called before the context menu is shown.
75
76        It enables/disables the VCS menu entries depending on the overall
77        VCS status and the file status.
78
79        @param menu reference to the menu to be shown
80        @param standardItems array of standard items that need
81            activation/deactivation depending on the overall VCS status
82        @exception RuntimeError to indicate that this method must be
83            implemented by a subclass
84        """
85        raise RuntimeError('Not implemented')
86
87    def showContextMenuMulti(self, menu, standardItems):
88        """
89        Public slot called before the context menu (multiple selections) is
90        shown.
91
92        It enables/disables the VCS menu entries depending on the overall
93        VCS status and the files status.
94
95        @param menu reference to the menu to be shown
96        @param standardItems array of standard items that need
97            activation/deactivation depending on the overall VCS status
98        @exception RuntimeError to indicate that this method must be
99            implemented by a subclass
100        """
101        raise RuntimeError('Not implemented')
102
103    def showContextMenuDir(self, menu, standardItems):
104        """
105        Public slot called before the context menu is shown.
106
107        It enables/disables the VCS menu entries depending on the overall
108        VCS status and the directory status.
109
110        @param menu reference to the menu to be shown
111        @param standardItems array of standard items that
112            need activation/deactivation depending on the overall VCS status
113        @exception RuntimeError to indicate that this method must be
114            implemented by a subclass
115        """
116        raise RuntimeError('Not implemented')
117
118    def showContextMenuDirMulti(self, menu, standardItems):
119        """
120        Public slot called before the context menu is shown.
121
122        It enables/disables the VCS menu entries depending on the overall
123        VCS status and the directory status.
124
125        @param menu reference to the menu to be shown
126        @param standardItems array of standard items that need
127            activation/deactivation depending on the overall VCS status
128        @exception RuntimeError to indicate that this method must be
129            implemented by a subclass
130        """
131        raise RuntimeError('Not implemented')
132
133    ###########################################################################
134    ## General menu handling methods below
135    ###########################################################################
136
137    def _VCSUpdate(self):
138        """
139        Protected slot called by the context menu to update a file from the
140        VCS repository.
141        """
142        if self.isTranslationsBrowser:
143            names = [itm.dirName()
144                     for itm in self.browser.getSelectedItems(
145                         [ProjectBrowserSimpleDirectoryItem])]
146            if not names:
147                names = [itm.fileName()
148                         for itm in self.browser.getSelectedItems(
149                             [ProjectBrowserFileItem])]
150        else:
151            names = []
152            for itm in self.browser.getSelectedItems():
153                try:
154                    name = itm.fileName()
155                except AttributeError:
156                    name = itm.dirName()
157                names.append(name)
158        self.vcs.vcsUpdate(names)
159
160    def _VCSCommit(self):
161        """
162        Protected slot called by the context menu to commit the changes to the
163        VCS repository.
164        """
165        if self.isTranslationsBrowser:
166            names = [itm.dirName()
167                     for itm in self.browser.getSelectedItems(
168                         [ProjectBrowserSimpleDirectoryItem])]
169            if not names:
170                names = [itm.fileName()
171                         for itm in self.browser.getSelectedItems(
172                             [ProjectBrowserFileItem])]
173        else:
174            names = []
175            for itm in self.browser.getSelectedItems():
176                try:
177                    name = itm.fileName()
178                except AttributeError:
179                    name = itm.dirName()
180                names.append(name)
181        if Preferences.getVCS("AutoSaveFiles"):
182            vm = e5App().getObject("ViewManager")
183            for name in names:
184                vm.saveEditor(name)
185        self.vcs.vcsCommit(names, '')
186
187    def _VCSAdd(self):
188        """
189        Protected slot called by the context menu to add the selected file to
190        the VCS repository.
191        """
192        if self.isTranslationsBrowser:
193            items = self.browser.getSelectedItems(
194                [ProjectBrowserSimpleDirectoryItem])
195            if items:
196                names = [itm.dirName() for itm in items]
197                qnames = []
198            else:
199                items = self.browser.getSelectedItems([ProjectBrowserFileItem])
200                names = []
201                qnames = []
202                for itm in items:
203                    name = itm.fileName()
204                    if name.endswith('.qm'):
205                        qnames.append(name)
206                    else:
207                        names.append(name)
208        else:
209            names = []
210            for itm in self.browser.getSelectedItems():
211                try:
212                    name = itm.fileName()
213                except AttributeError:
214                    name = itm.dirName()
215                names.append(name)
216            qnames = []
217
218        if not len(names + qnames):
219            return
220
221        if len(names + qnames) == 1:
222            if names:
223                self.vcs.vcsAdd(names[0], os.path.isdir(names[0]))
224            else:
225                if self.vcs.canDetectBinaries:
226                    self.vcs.vcsAdd(qnames)
227                else:
228                    self.vcs.vcsAddBinary(qnames)
229        else:
230            if self.vcs.canDetectBinaries:
231                self.vcs.vcsAdd(names + qnames)
232            else:
233                self.vcs.vcsAdd(names)
234                if len(qnames):
235                    self.vcs.vcsAddBinary(qnames)
236        for fn in names + qnames:
237            self._updateVCSStatus(fn)
238
239    def _VCSAddTree(self):
240        """
241        Protected slot called by the context menu.
242
243        It is used to add the selected
244        directory tree to the VCS repository.
245        """
246        names = []
247        for itm in self.browser.getSelectedItems():
248            try:
249                name = itm.fileName()
250            except AttributeError:
251                name = itm.dirName()
252            names.append(name)
253        self.vcs.vcsAddTree(names)
254        for fn in names:
255            self._updateVCSStatus(fn)
256
257    def _VCSRemove(self):
258        """
259        Protected slot called by the context menu to remove the selected file
260        from the VCS repository.
261        """
262        if self.isTranslationsBrowser:
263            items = self.browser.getSelectedItems(
264                [ProjectBrowserSimpleDirectoryItem])
265            if items:
266                return      # not supported
267
268            isRemoveDirs = False
269            items = self.browser.getSelectedItems([ProjectBrowserFileItem])
270            names = [itm.fileName() for itm in items]
271
272            dlg = DeleteFilesConfirmationDialog(
273                self.parent(),
274                QCoreApplication.translate(
275                    "VcsProjectBrowserHelper",
276                    "Remove from repository (and disk)"),
277                QCoreApplication.translate(
278                    "VcsProjectBrowserHelper",
279                    "Do you really want to remove these translation files from"
280                    " the repository (and disk)?"),
281                names)
282        else:
283            items = self.browser.getSelectedItems()
284            isRemoveDirs = (
285                len(items) == self.browser.getSelectedItemsCount(
286                    [ProjectBrowserSimpleDirectoryItem,
287                     ProjectBrowserDirectoryItem])
288            )
289            if isRemoveDirs:
290                names = [itm.dirName() for itm in items]
291            else:
292                names = [itm.fileName() for itm in items]
293            files = [self.browser.project.getRelativePath(name)
294                     for name in names]
295
296            dlg = DeleteFilesConfirmationDialog(
297                self.parent(),
298                QCoreApplication.translate(
299                    "VcsProjectBrowserHelper",
300                    "Remove from repository (and disk)"),
301                QCoreApplication.translate(
302                    "VcsProjectBrowserHelper",
303                    "Do you really want to remove these files/directories"
304                    " from the repository (and disk)?"),
305                files)
306
307        if dlg.exec() == QDialog.DialogCode.Accepted:
308            status = self.vcs.vcsRemove(names)
309            if status:
310                if isRemoveDirs:
311                    self.browser._removeDir()
312                    # remove directories from Project
313                else:
314                    self.browser._removeFile()  # remove file(s) from project
315
316    def _VCSLog(self):
317        """
318        Protected slot called by the context menu to show the VCS log of a
319        file/directory.
320        """
321        # kept for backward compatibility for plug-ins
322        self._VCSLogBrowser()
323
324    def _VCSLogBrowser(self):
325        """
326        Protected slot called by the context menu to show the log browser for a
327        file.
328        """
329        itm = self.browser.currentItem()
330        try:
331            fn = itm.fileName()
332            isFile = True
333        except AttributeError:
334            fn = itm.dirName()
335            isFile = False
336        self.vcs.vcsLogBrowser(fn, isFile=isFile)
337
338    def _VCSDiff(self):
339        """
340        Protected slot called by the context menu to show the difference of a
341        file/directory to the repository.
342        """
343        names = []
344        for itm in self.browser.getSelectedItems():
345            try:
346                name = itm.fileName()
347            except AttributeError:
348                name = itm.dirName()
349            names.append(name)
350        self.vcs.vcsDiff(names)
351
352    def _VCSStatus(self):
353        """
354        Protected slot called by the context menu to show the status of a file.
355        """
356        if self.isTranslationsBrowser:
357            items = self.browser.getSelectedItems(
358                [ProjectBrowserSimpleDirectoryItem])
359            if items:
360                names = [itm.dirName() for itm in items]
361            else:
362                items = self.browser.getSelectedItems([ProjectBrowserFileItem])
363                names = [itm.fileName() for itm in items]
364        else:
365            names = []
366            for itm in self.browser.getSelectedItems():
367                try:
368                    name = itm.fileName()
369                except AttributeError:
370                    name = itm.dirName()
371                names.append(name)
372        self.vcs.vcsStatus(names)
373
374    def _VCSRevert(self):
375        """
376        Protected slot called by the context menu to revert changes made to a
377        file.
378        """
379        names = []
380        for itm in self.browser.getSelectedItems():
381            try:
382                name = itm.fileName()
383            except AttributeError:
384                name = itm.dirName()
385            names.append(name)
386        self.vcs.vcsRevert(names)
387
388    def _VCSMerge(self):
389        """
390        Protected slot called by the context menu to merge changes into to a
391        file.
392        """
393        itm = self.browser.currentItem()
394        try:
395            name = itm.fileName()
396        except AttributeError:
397            name = itm.dirName()
398        self.vcs.vcsMerge(name)
399
400    def _VCSInfoDisplay(self):
401        """
402        Protected slot called to show some vcs information.
403        """
404        from .RepositoryInfoDialog import VcsRepositoryInfoDialog
405        info = self.vcs.vcsRepositoryInfos(self.project.ppath)
406        dlg = VcsRepositoryInfoDialog(None, info)
407        dlg.exec()
408
409    def _updateVCSStatus(self, name):
410        """
411        Protected method to update the VCS status of an item.
412
413        @param name filename or directoryname of the item to be updated
414            (string)
415        """
416        self.project.getModel().updateVCSStatus(name)
417