1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2011 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing a dialog to define guards for patches.
8"""
9
10from PyQt5.QtCore import pyqtSlot, Qt, QCoreApplication
11from PyQt5.QtWidgets import (
12    QDialog, QDialogButtonBox, QAbstractButton, QListWidgetItem
13)
14
15from E5Gui import E5MessageBox
16
17from .Ui_HgQueuesDefineGuardsDialog import Ui_HgQueuesDefineGuardsDialog
18
19import UI.PixmapCache
20
21
22class HgQueuesDefineGuardsDialog(QDialog, Ui_HgQueuesDefineGuardsDialog):
23    """
24    Class implementing a dialog to define guards for patches.
25    """
26    def __init__(self, vcs, extension, patchesList, parent=None):
27        """
28        Constructor
29
30        @param vcs reference to the vcs object
31        @param extension reference to the extension module (Queues)
32        @param patchesList list of patches (list of strings)
33        @param parent reference to the parent widget (QWidget)
34        """
35        super().__init__(parent)
36        self.setupUi(self)
37        self.setWindowFlags(Qt.WindowType.Window)
38
39        self.vcs = vcs
40        self.extension = extension
41        self.__hgClient = vcs.getClient()
42
43        self.__patches = patchesList[:]
44        self.patchSelector.addItems([""] + self.__patches)
45
46        self.plusButton.setIcon(UI.PixmapCache.getIcon("plus"))
47        self.minusButton.setIcon(UI.PixmapCache.getIcon("minus"))
48
49        self.__dirtyList = False
50        self.__currentPatch = ""
51
52        self.show()
53        QCoreApplication.processEvents()
54
55    def closeEvent(self, e):
56        """
57        Protected slot implementing a close event handler.
58
59        @param e close event (QCloseEvent)
60        """
61        if self.__hgClient.isExecuting():
62            self.__hgClient.cancel()
63
64        if self.__dirtyList:
65            res = E5MessageBox.question(
66                self,
67                self.tr("Unsaved Changes"),
68                self.tr("""The guards list has been changed."""
69                        """ Shall the changes be applied?"""),
70                E5MessageBox.StandardButtons(
71                    E5MessageBox.Apply |
72                    E5MessageBox.Discard),
73                E5MessageBox.Apply)
74            if res == E5MessageBox.Apply:
75                self.__applyGuards()
76            else:
77                self.__dirtyList = False
78
79        e.accept()
80
81    def start(self):
82        """
83        Public slot to start the list command.
84        """
85        self.on_patchSelector_activated(0)
86
87    @pyqtSlot(int)
88    def on_patchSelector_activated(self, index):
89        """
90        Private slot to get the list of guards defined for the given patch
91        name.
92
93        @param index index of the selected entry
94        @type int
95        """
96        patch = self.patchSelector.itemText(index)
97        if self.__dirtyList:
98            res = E5MessageBox.question(
99                self,
100                self.tr("Unsaved Changes"),
101                self.tr("""The guards list has been changed."""
102                        """ Shall the changes be applied?"""),
103                E5MessageBox.StandardButtons(
104                    E5MessageBox.Apply |
105                    E5MessageBox.Discard),
106                E5MessageBox.Apply)
107            if res == E5MessageBox.Apply:
108                self.__applyGuards()
109            else:
110                self.__dirtyList = False
111
112        self.guardsList.clear()
113        self.patchNameLabel.setText("")
114
115        self.guardCombo.clear()
116        guardsList = self.extension.getGuardsList()
117        self.guardCombo.addItems(guardsList)
118        self.guardCombo.setEditText("")
119
120        args = self.vcs.initCommand("qguard")
121        if patch:
122            args.append(patch)
123
124        output = self.__hgClient.runcommand(args)[0]
125
126        if output:
127            patchName, guards = output.split(":", 1)
128            self.patchNameLabel.setText(patchName)
129            guardsList = guards.strip().split()
130            for guard in guardsList:
131                if guard.startswith("+"):
132                    icon = UI.PixmapCache.getIcon("plus")
133                    guard = guard[1:]
134                    sign = "+"
135                elif guard.startswith("-"):
136                    icon = UI.PixmapCache.getIcon("minus")
137                    guard = guard[1:]
138                    sign = "-"
139                else:
140                    continue
141                itm = QListWidgetItem(icon, guard, self.guardsList)
142                itm.setData(Qt.ItemDataRole.UserRole, sign)
143
144        self.on_guardsList_itemSelectionChanged()
145
146    @pyqtSlot()
147    def on_guardsList_itemSelectionChanged(self):
148        """
149        Private slot to handle changes of the selection of guards.
150        """
151        self.removeButton.setEnabled(
152            len(self.guardsList.selectedItems()) > 0)
153
154    def __getGuard(self, guard):
155        """
156        Private method to get a reference to a named guard.
157
158        @param guard name of the guard (string)
159        @return reference to the guard item (QListWidgetItem)
160        """
161        items = self.guardsList.findItems(
162            guard, Qt.MatchFlag.MatchCaseSensitive)
163        if items:
164            return items[0]
165        else:
166            return None
167
168    @pyqtSlot(str)
169    def on_guardCombo_editTextChanged(self, txt):
170        """
171        Private slot to handle changes of the text of the guard combo.
172
173        @param txt contents of the guard combo line edit (string)
174        """
175        self.addButton.setEnabled(txt != "")
176
177    @pyqtSlot()
178    def on_addButton_clicked(self):
179        """
180        Private slot to add a guard definition to the list or change it.
181        """
182        guard = self.guardCombo.currentText()
183        if self.plusButton.isChecked():
184            sign = "+"
185            icon = UI.PixmapCache.getIcon("plus")
186        else:
187            sign = "-"
188            icon = UI.PixmapCache.getIcon("minus")
189
190        guardItem = self.__getGuard(guard)
191        if guardItem:
192            # guard already exists, remove it first
193            row = self.guardsList.row(guardItem)
194            itm = self.guardsList.takeItem(row)
195            del itm
196
197        itm = QListWidgetItem(icon, guard, self.guardsList)
198        itm.setData(Qt.ItemDataRole.UserRole, sign)
199        self.guardsList.sortItems()
200
201        self.__dirtyList = True
202
203    @pyqtSlot()
204    def on_removeButton_clicked(self):
205        """
206        Private slot to remove guard definitions from the list.
207        """
208        res = E5MessageBox.yesNo(
209            self,
210            self.tr("Remove Guards"),
211            self.tr(
212                """Do you really want to remove the selected guards?"""))
213        if res:
214            for guardItem in self.guardsList.selectedItems():
215                row = self.guardsList.row(guardItem)
216                itm = self.guardsList.takeItem(row)        # __IGNORE_WARNING__
217                del itm
218
219        self.__dirtyList = True
220
221    @pyqtSlot(QAbstractButton)
222    def on_buttonBox_clicked(self, button):
223        """
224        Private slot called by a button of the button box clicked.
225
226        @param button button that was clicked (QAbstractButton)
227        """
228        if button == self.buttonBox.button(
229            QDialogButtonBox.StandardButton.Apply
230        ):
231            self.__applyGuards()
232        elif button == self.buttonBox.button(
233            QDialogButtonBox.StandardButton.Close
234        ):
235            self.close()
236
237    @pyqtSlot()
238    def __applyGuards(self):
239        """
240        Private slot to apply the defined guards to the current patch.
241        """
242        if self.__dirtyList:
243            guardsList = []
244            for row in range(self.guardsList.count()):
245                itm = self.guardsList.item(row)
246                guard = itm.data(Qt.ItemDataRole.UserRole) + itm.text()
247                guardsList.append(guard)
248
249            args = self.vcs.initCommand("qguard")
250            args.append(self.patchNameLabel.text())
251            if guardsList:
252                args.append("--")
253                args.extend(guardsList)
254            else:
255                args.append("--none")
256
257            error = self.__hgClient.runcommand(args)[1]
258
259            if error:
260                E5MessageBox.warning(
261                    self,
262                    self.tr("Apply Guard Definitions"),
263                    self.tr("""<p>The defined guards could not be"""
264                            """ applied.</p><p>Reason: {0}</p>""")
265                    .format(error))
266            else:
267                self.__dirtyList = False
268                index = self.patchSelector.findText(self.patchNameLabel.text())
269                if index == -1:
270                    index = 0
271                self.on_patchSelector_activated(index)
272