26 class HgDiffDialog(QWidget, Ui_HgDiffDialog): |
26 class HgDiffDialog(QWidget, Ui_HgDiffDialog): |
27 """ |
27 """ |
28 Class implementing a dialog to show the output of the hg diff command |
28 Class implementing a dialog to show the output of the hg diff command |
29 process. |
29 process. |
30 """ |
30 """ |
|
31 |
31 def __init__(self, vcs, parent=None): |
32 def __init__(self, vcs, parent=None): |
32 """ |
33 """ |
33 Constructor |
34 Constructor |
34 |
35 |
35 @param vcs reference to the vcs object |
36 @param vcs reference to the vcs object |
36 @param parent parent widget (QWidget) |
37 @param parent parent widget (QWidget) |
37 """ |
38 """ |
38 super().__init__(parent) |
39 super().__init__(parent) |
39 self.setupUi(self) |
40 self.setupUi(self) |
40 |
41 |
41 self.refreshButton = self.buttonBox.addButton( |
42 self.refreshButton = self.buttonBox.addButton( |
42 self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole) |
43 self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole |
43 self.refreshButton.setToolTip( |
44 ) |
44 self.tr("Press to refresh the display")) |
45 self.refreshButton.setToolTip(self.tr("Press to refresh the display")) |
45 self.refreshButton.setEnabled(False) |
46 self.refreshButton.setEnabled(False) |
46 self.buttonBox.button( |
47 self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled(False) |
47 QDialogButtonBox.StandardButton.Save).setEnabled(False) |
48 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) |
48 self.buttonBox.button( |
49 |
49 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
50 |
|
51 self.searchWidget.attachTextEdit(self.contents) |
50 self.searchWidget.attachTextEdit(self.contents) |
52 |
51 |
53 self.vcs = vcs |
52 self.vcs = vcs |
54 |
53 |
55 font = Preferences.getEditorOtherFonts("MonospacedFont") |
54 font = Preferences.getEditorOtherFonts("MonospacedFont") |
56 self.contents.document().setDefaultFont(font) |
55 self.contents.document().setDefaultFont(font) |
57 |
56 |
58 self.highlighter = HgDiffHighlighter(self.contents.document()) |
57 self.highlighter = HgDiffHighlighter(self.contents.document()) |
59 |
58 |
60 self.__diffGenerator = HgDiffGenerator(vcs, self) |
59 self.__diffGenerator = HgDiffGenerator(vcs, self) |
61 self.__diffGenerator.finished.connect(self.__generatorFinished) |
60 self.__diffGenerator.finished.connect(self.__generatorFinished) |
62 |
61 |
63 def closeEvent(self, e): |
62 def closeEvent(self, e): |
64 """ |
63 """ |
65 Protected slot implementing a close event handler. |
64 Protected slot implementing a close event handler. |
66 |
65 |
67 @param e close event (QCloseEvent) |
66 @param e close event (QCloseEvent) |
68 """ |
67 """ |
69 self.__diffGenerator.stopProcess() |
68 self.__diffGenerator.stopProcess() |
70 e.accept() |
69 e.accept() |
71 |
70 |
72 def start(self, fn, versions=None, bundle=None, qdiff=False, |
71 def start(self, fn, versions=None, bundle=None, qdiff=False, refreshable=False): |
73 refreshable=False): |
|
74 """ |
72 """ |
75 Public slot to start the hg diff command. |
73 Public slot to start the hg diff command. |
76 |
74 |
77 @param fn filename to be diffed (string) |
75 @param fn filename to be diffed (string) |
78 @param versions list of versions to be diffed (list of up to |
76 @param versions list of versions to be diffed (list of up to |
79 2 strings or None) |
77 2 strings or None) |
80 @param bundle name of a bundle file (string) |
78 @param bundle name of a bundle file (string) |
81 @param qdiff flag indicating qdiff command shall be used (boolean) |
79 @param qdiff flag indicating qdiff command shall be used (boolean) |
82 @param refreshable flag indicating a refreshable diff (boolean) |
80 @param refreshable flag indicating a refreshable diff (boolean) |
83 """ |
81 """ |
84 self.refreshButton.setVisible(refreshable) |
82 self.refreshButton.setVisible(refreshable) |
85 |
83 |
86 self.errorGroup.hide() |
84 self.errorGroup.hide() |
87 self.filename = fn |
85 self.filename = fn |
88 |
86 |
89 self.contents.clear() |
87 self.contents.clear() |
90 self.filesCombo.clear() |
88 self.filesCombo.clear() |
91 self.highlighter.regenerateRules() |
89 self.highlighter.regenerateRules() |
92 |
90 |
93 if qdiff: |
91 if qdiff: |
94 self.setWindowTitle(self.tr("Patch Contents")) |
92 self.setWindowTitle(self.tr("Patch Contents")) |
95 |
93 |
96 self.raise_() |
94 self.raise_() |
97 self.activateWindow() |
95 self.activateWindow() |
98 |
96 |
99 procStarted = self.__diffGenerator.start( |
97 procStarted = self.__diffGenerator.start( |
100 fn, versions=versions, bundle=bundle, qdiff=qdiff) |
98 fn, versions=versions, bundle=bundle, qdiff=qdiff |
|
99 ) |
101 if not procStarted: |
100 if not procStarted: |
102 EricMessageBox.critical( |
101 EricMessageBox.critical( |
103 self, |
102 self, |
104 self.tr('Process Generation Error'), |
103 self.tr("Process Generation Error"), |
105 self.tr( |
104 self.tr( |
106 'The process {0} could not be started. ' |
105 "The process {0} could not be started. " |
107 'Ensure, that it is in the search path.' |
106 "Ensure, that it is in the search path." |
108 ).format('hg')) |
107 ).format("hg"), |
109 |
108 ) |
|
109 |
110 def __generatorFinished(self): |
110 def __generatorFinished(self): |
111 """ |
111 """ |
112 Private slot connected to the finished signal. |
112 Private slot connected to the finished signal. |
113 """ |
113 """ |
114 self.refreshButton.setEnabled(True) |
114 self.refreshButton.setEnabled(True) |
115 |
115 |
116 diff, errors, fileSeparators = self.__diffGenerator.getResult() |
116 diff, errors, fileSeparators = self.__diffGenerator.getResult() |
117 |
117 |
118 if diff: |
118 if diff: |
119 self.contents.setPlainText("".join(diff)) |
119 self.contents.setPlainText("".join(diff)) |
120 else: |
120 else: |
121 self.contents.setPlainText( |
121 self.contents.setPlainText(self.tr("There is no difference.")) |
122 self.tr('There is no difference.')) |
122 |
123 |
|
124 if errors: |
123 if errors: |
125 self.errorGroup.show() |
124 self.errorGroup.show() |
126 self.errors.setPlainText("".join(errors)) |
125 self.errors.setPlainText("".join(errors)) |
127 self.errors.ensureCursorVisible() |
126 self.errors.ensureCursorVisible() |
128 |
127 |
129 self.buttonBox.button( |
128 self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled( |
130 QDialogButtonBox.StandardButton.Save).setEnabled(bool(diff)) |
129 bool(diff) |
131 self.buttonBox.button( |
130 ) |
132 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
131 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) |
133 self.buttonBox.button( |
132 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) |
134 QDialogButtonBox.StandardButton.Close).setDefault(True) |
133 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setFocus( |
135 self.buttonBox.button( |
134 Qt.FocusReason.OtherFocusReason |
136 QDialogButtonBox.StandardButton.Close).setFocus( |
135 ) |
137 Qt.FocusReason.OtherFocusReason) |
136 |
138 |
|
139 tc = self.contents.textCursor() |
137 tc = self.contents.textCursor() |
140 tc.movePosition(QTextCursor.MoveOperation.Start) |
138 tc.movePosition(QTextCursor.MoveOperation.Start) |
141 self.contents.setTextCursor(tc) |
139 self.contents.setTextCursor(tc) |
142 self.contents.ensureCursorVisible() |
140 self.contents.ensureCursorVisible() |
143 |
141 |
144 self.filesCombo.addItem(self.tr("<Start>"), 0) |
142 self.filesCombo.addItem(self.tr("<Start>"), 0) |
145 self.filesCombo.addItem(self.tr("<End>"), -1) |
143 self.filesCombo.addItem(self.tr("<End>"), -1) |
146 for oldFile, newFile, pos in sorted(fileSeparators): |
144 for oldFile, newFile, pos in sorted(fileSeparators): |
147 if not oldFile: |
145 if not oldFile: |
148 self.filesCombo.addItem(newFile, pos) |
146 self.filesCombo.addItem(newFile, pos) |
149 elif oldFile != newFile: |
147 elif oldFile != newFile: |
150 self.filesCombo.addItem( |
148 self.filesCombo.addItem("{0}\n{1}".format(oldFile, newFile), pos) |
151 "{0}\n{1}".format(oldFile, newFile), pos) |
|
152 else: |
149 else: |
153 self.filesCombo.addItem(oldFile, pos) |
150 self.filesCombo.addItem(oldFile, pos) |
154 |
151 |
155 def on_buttonBox_clicked(self, button): |
152 def on_buttonBox_clicked(self, button): |
156 """ |
153 """ |
157 Private slot called by a button of the button box clicked. |
154 Private slot called by a button of the button box clicked. |
158 |
155 |
159 @param button button that was clicked (QAbstractButton) |
156 @param button button that was clicked (QAbstractButton) |
160 """ |
157 """ |
161 if button == self.buttonBox.button( |
158 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Save): |
162 QDialogButtonBox.StandardButton.Save |
|
163 ): |
|
164 self.on_saveButton_clicked() |
159 self.on_saveButton_clicked() |
165 elif button == self.refreshButton: |
160 elif button == self.refreshButton: |
166 self.on_refreshButton_clicked() |
161 self.on_refreshButton_clicked() |
167 |
162 |
168 @pyqtSlot(int) |
163 @pyqtSlot(int) |
169 def on_filesCombo_activated(self, index): |
164 def on_filesCombo_activated(self, index): |
170 """ |
165 """ |
171 Private slot to handle the selection of a file. |
166 Private slot to handle the selection of a file. |
172 |
167 |
173 @param index activated row (integer) |
168 @param index activated row (integer) |
174 """ |
169 """ |
175 para = self.filesCombo.itemData(index) |
170 para = self.filesCombo.itemData(index) |
176 |
171 |
177 if para == 0: |
172 if para == 0: |
178 tc = self.contents.textCursor() |
173 tc = self.contents.textCursor() |
179 tc.movePosition(QTextCursor.MoveOperation.Start) |
174 tc.movePosition(QTextCursor.MoveOperation.Start) |
180 self.contents.setTextCursor(tc) |
175 self.contents.setTextCursor(tc) |
181 self.contents.ensureCursorVisible() |
176 self.contents.ensureCursorVisible() |
188 # step 1: move cursor to end |
183 # step 1: move cursor to end |
189 tc = self.contents.textCursor() |
184 tc = self.contents.textCursor() |
190 tc.movePosition(QTextCursor.MoveOperation.End) |
185 tc.movePosition(QTextCursor.MoveOperation.End) |
191 self.contents.setTextCursor(tc) |
186 self.contents.setTextCursor(tc) |
192 self.contents.ensureCursorVisible() |
187 self.contents.ensureCursorVisible() |
193 |
188 |
194 # step 2: move cursor to desired line |
189 # step 2: move cursor to desired line |
195 tc = self.contents.textCursor() |
190 tc = self.contents.textCursor() |
196 delta = tc.blockNumber() - para |
191 delta = tc.blockNumber() - para |
197 tc.movePosition(QTextCursor.MoveOperation.PreviousBlock, |
192 tc.movePosition( |
198 QTextCursor.MoveMode.MoveAnchor, |
193 QTextCursor.MoveOperation.PreviousBlock, |
199 delta) |
194 QTextCursor.MoveMode.MoveAnchor, |
200 self.contents.setTextCursor(tc) |
195 delta, |
201 self.contents.ensureCursorVisible() |
196 ) |
202 |
197 self.contents.setTextCursor(tc) |
|
198 self.contents.ensureCursorVisible() |
|
199 |
203 @pyqtSlot() |
200 @pyqtSlot() |
204 def on_saveButton_clicked(self): |
201 def on_saveButton_clicked(self): |
205 """ |
202 """ |
206 Private slot to handle the Save button press. |
203 Private slot to handle the Save button press. |
207 |
204 |
208 It saves the diff shown in the dialog to a file in the local |
205 It saves the diff shown in the dialog to a file in the local |
209 filesystem. |
206 filesystem. |
210 """ |
207 """ |
211 if isinstance(self.filename, list): |
208 if isinstance(self.filename, list): |
212 if len(self.filename) > 1: |
209 if len(self.filename) > 1: |
213 fname = self.vcs.splitPathList(self.filename)[0] |
210 fname = self.vcs.splitPathList(self.filename)[0] |
214 else: |
211 else: |
215 dname, fname = self.vcs.splitPath(self.filename[0]) |
212 dname, fname = self.vcs.splitPath(self.filename[0]) |
216 if fname != '.': |
213 if fname != ".": |
217 fname = "{0}.diff".format(self.filename[0]) |
214 fname = "{0}.diff".format(self.filename[0]) |
218 else: |
215 else: |
219 fname = dname |
216 fname = dname |
220 else: |
217 else: |
221 fname = self.vcs.splitPath(self.filename)[0] |
218 fname = self.vcs.splitPath(self.filename)[0] |
222 |
219 |
223 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
220 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
224 self, |
221 self, |
225 self.tr("Save Diff"), |
222 self.tr("Save Diff"), |
226 fname, |
223 fname, |
227 self.tr("Patch Files (*.diff)"), |
224 self.tr("Patch Files (*.diff)"), |
228 None, |
225 None, |
229 EricFileDialog.DontConfirmOverwrite) |
226 EricFileDialog.DontConfirmOverwrite, |
230 |
227 ) |
|
228 |
231 if not fname: |
229 if not fname: |
232 return # user aborted |
230 return # user aborted |
233 |
231 |
234 fpath = pathlib.Path(fname) |
232 fpath = pathlib.Path(fname) |
235 if not fpath.suffix: |
233 if not fpath.suffix: |
236 ex = selectedFilter.split("(*")[1].split(")")[0] |
234 ex = selectedFilter.split("(*")[1].split(")")[0] |
237 if ex: |
235 if ex: |
238 fpath = fpath.with_suffix(ex) |
236 fpath = fpath.with_suffix(ex) |
239 if fpath.exists(): |
237 if fpath.exists(): |
240 res = EricMessageBox.yesNo( |
238 res = EricMessageBox.yesNo( |
241 self, |
239 self, |
242 self.tr("Save Diff"), |
240 self.tr("Save Diff"), |
243 self.tr("<p>The patch file <b>{0}</b> already exists." |
241 self.tr( |
244 " Overwrite it?</p>").format(fpath), |
242 "<p>The patch file <b>{0}</b> already exists." " Overwrite it?</p>" |
245 icon=EricMessageBox.Warning) |
243 ).format(fpath), |
|
244 icon=EricMessageBox.Warning, |
|
245 ) |
246 if not res: |
246 if not res: |
247 return |
247 return |
248 |
248 |
249 eol = ericApp().getObject("Project").getEolString() |
249 eol = ericApp().getObject("Project").getEolString() |
250 try: |
250 try: |
251 with fpath.open("w", encoding="utf-8", newline="") as f: |
251 with fpath.open("w", encoding="utf-8", newline="") as f: |
252 f.write(eol.join(self.contents.toPlainText().splitlines())) |
252 f.write(eol.join(self.contents.toPlainText().splitlines())) |
253 except OSError as why: |
253 except OSError as why: |
254 EricMessageBox.critical( |
254 EricMessageBox.critical( |
255 self, self.tr('Save Diff'), |
255 self, |
|
256 self.tr("Save Diff"), |
256 self.tr( |
257 self.tr( |
257 '<p>The patch file <b>{0}</b> could not be saved.' |
258 "<p>The patch file <b>{0}</b> could not be saved." |
258 '<br>Reason: {1}</p>') |
259 "<br>Reason: {1}</p>" |
259 .format(fpath, str(why))) |
260 ).format(fpath, str(why)), |
260 |
261 ) |
|
262 |
261 @pyqtSlot() |
263 @pyqtSlot() |
262 def on_refreshButton_clicked(self): |
264 def on_refreshButton_clicked(self): |
263 """ |
265 """ |
264 Private slot to refresh the display. |
266 Private slot to refresh the display. |
265 """ |
267 """ |
266 self.buttonBox.button( |
268 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) |
267 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
269 |
268 |
270 self.buttonBox.button(QDialogButtonBox.StandardButton.Save).setEnabled(False) |
269 self.buttonBox.button( |
|
270 QDialogButtonBox.StandardButton.Save).setEnabled(False) |
|
271 self.refreshButton.setEnabled(False) |
271 self.refreshButton.setEnabled(False) |
272 |
272 |
273 self.start(self.filename, refreshable=True) |
273 self.start(self.filename, refreshable=True) |