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