|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the subversion repository browser dialog. |
|
8 """ |
|
9 |
|
10 import pysvn |
|
11 |
|
12 from PyQt5.QtCore import Qt, pyqtSlot |
|
13 from PyQt5.QtWidgets import ( |
|
14 QHeaderView, QDialog, QApplication, QDialogButtonBox, QTreeWidgetItem |
|
15 ) |
|
16 |
|
17 from E5Gui import E5MessageBox |
|
18 from E5Gui.E5OverrideCursor import E5OverrideCursor |
|
19 |
|
20 from E5Utilities.E5MutexLocker import E5MutexLocker |
|
21 |
|
22 from .SvnUtilities import formatTime |
|
23 from .SvnDialogMixin import SvnDialogMixin |
|
24 |
|
25 from .Ui_SvnRepoBrowserDialog import Ui_SvnRepoBrowserDialog |
|
26 |
|
27 import UI.PixmapCache |
|
28 |
|
29 |
|
30 class SvnRepoBrowserDialog(QDialog, SvnDialogMixin, Ui_SvnRepoBrowserDialog): |
|
31 """ |
|
32 Class implementing the subversion repository browser dialog. |
|
33 """ |
|
34 def __init__(self, vcs, mode="browse", parent=None): |
|
35 """ |
|
36 Constructor |
|
37 |
|
38 @param vcs reference to the vcs object |
|
39 @param mode mode of the dialog (string, "browse" or "select") |
|
40 @param parent parent widget (QWidget) |
|
41 """ |
|
42 super().__init__(parent) |
|
43 self.setupUi(self) |
|
44 SvnDialogMixin.__init__(self) |
|
45 self.setWindowFlags(Qt.WindowType.Window) |
|
46 |
|
47 self.repoTree.headerItem().setText(self.repoTree.columnCount(), "") |
|
48 self.repoTree.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder) |
|
49 |
|
50 self.vcs = vcs |
|
51 self.mode = mode |
|
52 |
|
53 if self.mode == "select": |
|
54 self.buttonBox.button( |
|
55 QDialogButtonBox.StandardButton.Ok).setEnabled(False) |
|
56 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).hide() |
|
57 else: |
|
58 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).hide() |
|
59 self.buttonBox.button( |
|
60 QDialogButtonBox.StandardButton.Cancel).hide() |
|
61 |
|
62 self.__dirIcon = UI.PixmapCache.getIcon("dirClosed") |
|
63 self.__fileIcon = UI.PixmapCache.getIcon("fileMisc") |
|
64 |
|
65 self.__urlRole = Qt.ItemDataRole.UserRole |
|
66 self.__ignoreExpand = False |
|
67 |
|
68 self.client = self.vcs.getClient() |
|
69 self.client.callback_cancel = self._clientCancelCallback |
|
70 self.client.callback_get_login = self._clientLoginCallback |
|
71 self.client.callback_ssl_server_trust_prompt = ( |
|
72 self._clientSslServerTrustPromptCallback |
|
73 ) |
|
74 |
|
75 self.show() |
|
76 QApplication.processEvents() |
|
77 |
|
78 def __resort(self): |
|
79 """ |
|
80 Private method to resort the tree. |
|
81 """ |
|
82 self.repoTree.sortItems( |
|
83 self.repoTree.sortColumn(), |
|
84 self.repoTree.header().sortIndicatorOrder()) |
|
85 |
|
86 def __resizeColumns(self): |
|
87 """ |
|
88 Private method to resize the tree columns. |
|
89 """ |
|
90 self.repoTree.header().resizeSections( |
|
91 QHeaderView.ResizeMode.ResizeToContents) |
|
92 self.repoTree.header().setStretchLastSection(True) |
|
93 |
|
94 def __generateItem(self, parent, repopath, revision, author, size, date, |
|
95 nodekind, url): |
|
96 """ |
|
97 Private method to generate a tree item in the repository tree. |
|
98 |
|
99 @param parent parent of the item to be created (QTreeWidget or |
|
100 QTreeWidgetItem) |
|
101 @param repopath path of the item (string) |
|
102 @param revision revision info (string or pysvn.opt_revision_kind) |
|
103 @param author author info (string) |
|
104 @param size size info (integer) |
|
105 @param date date info (integer) |
|
106 @param nodekind node kind info (pysvn.node_kind) |
|
107 @param url url of the entry (string) |
|
108 @return reference to the generated item (QTreeWidgetItem) |
|
109 """ |
|
110 path = url if repopath == "/" else url.split("/")[-1] |
|
111 |
|
112 rev = revision.number if revision else "" |
|
113 dt = formatTime(date) if date else "" |
|
114 if author is None: |
|
115 author = "" |
|
116 |
|
117 itm = QTreeWidgetItem(parent) |
|
118 itm.setData(0, Qt.ItemDataRole.DisplayRole, path) |
|
119 itm.setData(1, Qt.ItemDataRole.DisplayRole, rev) |
|
120 itm.setData(2, Qt.ItemDataRole.DisplayRole, author) |
|
121 itm.setData(3, Qt.ItemDataRole.DisplayRole, size) |
|
122 itm.setData(4, Qt.ItemDataRole.DisplayRole, dt) |
|
123 |
|
124 if nodekind == pysvn.node_kind.dir: |
|
125 itm.setIcon(0, self.__dirIcon) |
|
126 itm.setChildIndicatorPolicy( |
|
127 QTreeWidgetItem.ChildIndicatorPolicy.ShowIndicator) |
|
128 elif nodekind == pysvn.node_kind.file: |
|
129 itm.setIcon(0, self.__fileIcon) |
|
130 |
|
131 itm.setData(0, self.__urlRole, url) |
|
132 |
|
133 itm.setTextAlignment(0, Qt.AlignmentFlag.AlignLeft) |
|
134 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignRight) |
|
135 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignLeft) |
|
136 itm.setTextAlignment(3, Qt.AlignmentFlag.AlignRight) |
|
137 itm.setTextAlignment(4, Qt.AlignmentFlag.AlignLeft) |
|
138 |
|
139 return itm |
|
140 |
|
141 def __listRepo(self, url, parent=None): |
|
142 """ |
|
143 Private method to perform the svn list command. |
|
144 |
|
145 @param url the repository URL to browser (string) |
|
146 @param parent reference to the item, the data should be appended to |
|
147 (QTreeWidget or QTreeWidgetItem) |
|
148 """ |
|
149 if parent is None: |
|
150 parent = self.repoTree |
|
151 |
|
152 with E5OverrideCursor(): |
|
153 try: |
|
154 with E5MutexLocker(self.vcs.vcsExecutionMutex): |
|
155 entries = self.client.list(url, recurse=False) |
|
156 firstTime = parent == self.repoTree |
|
157 for dirent, _lock in entries: |
|
158 if ( |
|
159 (firstTime and dirent["path"] != url) or |
|
160 (parent != self.repoTree and dirent["path"] == url) |
|
161 ): |
|
162 continue |
|
163 if firstTime: |
|
164 if dirent["repos_path"] != "/": |
|
165 repoUrl = dirent["path"].replace( |
|
166 dirent["repos_path"], "") |
|
167 else: |
|
168 repoUrl = dirent["path"] |
|
169 if repoUrl != url: |
|
170 self.__ignoreExpand = True |
|
171 itm = self.__generateItem( |
|
172 parent, "/", "", "", 0, "", |
|
173 pysvn.node_kind.dir, repoUrl) |
|
174 itm.setExpanded(True) |
|
175 parent = itm |
|
176 urlPart = repoUrl |
|
177 for element in ( |
|
178 dirent["repos_path"].split("/")[:-1] |
|
179 ): |
|
180 if element: |
|
181 urlPart = "{0}/{1}".format(urlPart, |
|
182 element) |
|
183 itm = self.__generateItem( |
|
184 parent, element, "", "", 0, "", |
|
185 pysvn.node_kind.dir, urlPart) |
|
186 itm.setExpanded(True) |
|
187 parent = itm |
|
188 self.__ignoreExpand = False |
|
189 itm = self.__generateItem( |
|
190 parent, dirent["repos_path"], |
|
191 dirent["created_rev"], dirent["last_author"], |
|
192 dirent["size"], dirent["time"], dirent["kind"], |
|
193 dirent["path"]) |
|
194 self.__resort() |
|
195 self.__resizeColumns() |
|
196 except pysvn.ClientError as e: |
|
197 self.__showError(e.args[0]) |
|
198 except AttributeError: |
|
199 self.__showError( |
|
200 self.tr("The installed version of PySvn should be " |
|
201 "1.4.0 or better.")) |
|
202 |
|
203 def __normalizeUrl(self, url): |
|
204 """ |
|
205 Private method to normalite the url. |
|
206 |
|
207 @param url the url to normalize (string) |
|
208 @return normalized URL (string) |
|
209 """ |
|
210 if url.endswith("/"): |
|
211 return url[:-1] |
|
212 return url |
|
213 |
|
214 def start(self, url): |
|
215 """ |
|
216 Public slot to start the svn info command. |
|
217 |
|
218 @param url the repository URL to browser (string) |
|
219 """ |
|
220 self.repoTree.clear() |
|
221 |
|
222 self.url = "" |
|
223 |
|
224 url = self.__normalizeUrl(url) |
|
225 if self.urlCombo.findText(url) == -1: |
|
226 self.urlCombo.addItem(url) |
|
227 |
|
228 @pyqtSlot(int) |
|
229 def on_urlCombo_currentIndexChanged(self, index): |
|
230 """ |
|
231 Private slot called, when a new repository URL is entered or selected. |
|
232 |
|
233 @param index of the current item |
|
234 @type int |
|
235 """ |
|
236 text = self.urlCombo.itemText(index) |
|
237 url = self.__normalizeUrl(text) |
|
238 if url != self.url: |
|
239 self.url = url |
|
240 self.repoTree.clear() |
|
241 self.__listRepo(url) |
|
242 |
|
243 @pyqtSlot(QTreeWidgetItem) |
|
244 def on_repoTree_itemExpanded(self, item): |
|
245 """ |
|
246 Private slot called when an item is expanded. |
|
247 |
|
248 @param item reference to the item to be expanded (QTreeWidgetItem) |
|
249 """ |
|
250 if not self.__ignoreExpand: |
|
251 url = item.data(0, self.__urlRole) |
|
252 self.__listRepo(url, item) |
|
253 |
|
254 @pyqtSlot(QTreeWidgetItem) |
|
255 def on_repoTree_itemCollapsed(self, item): |
|
256 """ |
|
257 Private slot called when an item is collapsed. |
|
258 |
|
259 @param item reference to the item to be collapsed (QTreeWidgetItem) |
|
260 """ |
|
261 for child in item.takeChildren(): |
|
262 del child |
|
263 |
|
264 @pyqtSlot() |
|
265 def on_repoTree_itemSelectionChanged(self): |
|
266 """ |
|
267 Private slot called when the selection changes. |
|
268 """ |
|
269 if self.mode == "select": |
|
270 self.buttonBox.button( |
|
271 QDialogButtonBox.StandardButton.Ok).setEnabled(True) |
|
272 |
|
273 def __showError(self, msg): |
|
274 """ |
|
275 Private slot to show an error message. |
|
276 |
|
277 @param msg error message to show (string) |
|
278 """ |
|
279 E5MessageBox.critical( |
|
280 self, |
|
281 self.tr("Subversion Error"), |
|
282 msg) |
|
283 |
|
284 def accept(self): |
|
285 """ |
|
286 Public slot called when the dialog is accepted. |
|
287 """ |
|
288 if self.focusWidget() == self.urlCombo: |
|
289 return |
|
290 |
|
291 super().accept() |
|
292 |
|
293 def getSelectedUrl(self): |
|
294 """ |
|
295 Public method to retrieve the selected repository URL. |
|
296 |
|
297 @return the selected repository URL (string) |
|
298 """ |
|
299 items = self.repoTree.selectedItems() |
|
300 if len(items) == 1: |
|
301 return items[0].data(0, self.__urlRole) |
|
302 else: |
|
303 return "" |