30 |
30 |
31 class DiffDialog(QWidget, Ui_DiffDialog): |
31 class DiffDialog(QWidget, Ui_DiffDialog): |
32 """ |
32 """ |
33 Class implementing a dialog to compare two files. |
33 Class implementing a dialog to compare two files. |
34 """ |
34 """ |
|
35 |
35 def __init__(self, parent=None): |
36 def __init__(self, parent=None): |
36 """ |
37 """ |
37 Constructor |
38 Constructor |
38 |
39 |
39 @param parent reference to the parent widget (QWidget) |
40 @param parent reference to the parent widget (QWidget) |
40 """ |
41 """ |
41 super().__init__(parent) |
42 super().__init__(parent) |
42 self.setupUi(self) |
43 self.setupUi(self) |
43 |
44 |
44 self.file1Picker.setMode(EricPathPickerModes.OPEN_FILE_MODE) |
45 self.file1Picker.setMode(EricPathPickerModes.OPEN_FILE_MODE) |
45 self.file2Picker.setMode(EricPathPickerModes.OPEN_FILE_MODE) |
46 self.file2Picker.setMode(EricPathPickerModes.OPEN_FILE_MODE) |
46 |
47 |
47 self.diffButton = self.buttonBox.addButton( |
48 self.diffButton = self.buttonBox.addButton( |
48 self.tr("Compare"), QDialogButtonBox.ButtonRole.ActionRole) |
49 self.tr("Compare"), QDialogButtonBox.ButtonRole.ActionRole |
|
50 ) |
49 self.diffButton.setToolTip( |
51 self.diffButton.setToolTip( |
50 self.tr("Press to perform the comparison of the two files")) |
52 self.tr("Press to perform the comparison of the two files") |
|
53 ) |
51 self.saveButton = self.buttonBox.addButton( |
54 self.saveButton = self.buttonBox.addButton( |
52 self.tr("Save"), QDialogButtonBox.ButtonRole.ActionRole) |
55 self.tr("Save"), QDialogButtonBox.ButtonRole.ActionRole |
53 self.saveButton.setToolTip( |
56 ) |
54 self.tr("Save the output to a patch file")) |
57 self.saveButton.setToolTip(self.tr("Save the output to a patch file")) |
55 self.diffButton.setEnabled(False) |
58 self.diffButton.setEnabled(False) |
56 self.saveButton.setEnabled(False) |
59 self.saveButton.setEnabled(False) |
57 self.diffButton.setDefault(True) |
60 self.diffButton.setDefault(True) |
58 |
61 |
59 self.searchWidget.attachTextEdit(self.contents) |
62 self.searchWidget.attachTextEdit(self.contents) |
60 |
63 |
61 self.filename1 = '' |
64 self.filename1 = "" |
62 self.filename2 = '' |
65 self.filename2 = "" |
63 |
66 |
64 self.updateInterval = 20 # update every 20 lines |
67 self.updateInterval = 20 # update every 20 lines |
65 |
68 |
66 font = Preferences.getEditorOtherFonts("MonospacedFont") |
69 font = Preferences.getEditorOtherFonts("MonospacedFont") |
67 self.contents.document().setDefaultFont(font) |
70 self.contents.document().setDefaultFont(font) |
68 |
71 |
69 self.highlighter = DiffHighlighter(self.contents.document()) |
72 self.highlighter = DiffHighlighter(self.contents.document()) |
70 |
73 |
71 # connect some of our widgets explicitly |
74 # connect some of our widgets explicitly |
72 self.file1Picker.textChanged.connect(self.__fileChanged) |
75 self.file1Picker.textChanged.connect(self.__fileChanged) |
73 self.file2Picker.textChanged.connect(self.__fileChanged) |
76 self.file2Picker.textChanged.connect(self.__fileChanged) |
74 |
77 |
75 def show(self, filename=None): |
78 def show(self, filename=None): |
76 """ |
79 """ |
77 Public slot to show the dialog. |
80 Public slot to show the dialog. |
78 |
81 |
79 @param filename name of a file to use as the first file (string) |
82 @param filename name of a file to use as the first file (string) |
80 """ |
83 """ |
81 if filename: |
84 if filename: |
82 self.file1Picker.setText(filename) |
85 self.file1Picker.setText(filename) |
83 super().show() |
86 super().show() |
84 |
87 |
85 def on_buttonBox_clicked(self, button): |
88 def on_buttonBox_clicked(self, button): |
86 """ |
89 """ |
87 Private slot called by a button of the button box clicked. |
90 Private slot called by a button of the button box clicked. |
88 |
91 |
89 @param button button that was clicked (QAbstractButton) |
92 @param button button that was clicked (QAbstractButton) |
90 """ |
93 """ |
91 if button == self.diffButton: |
94 if button == self.diffButton: |
92 self.on_diffButton_clicked() |
95 self.on_diffButton_clicked() |
93 elif button == self.saveButton: |
96 elif button == self.saveButton: |
94 self.on_saveButton_clicked() |
97 self.on_saveButton_clicked() |
95 |
98 |
96 @pyqtSlot() |
99 @pyqtSlot() |
97 def on_saveButton_clicked(self): |
100 def on_saveButton_clicked(self): |
98 """ |
101 """ |
99 Private slot to handle the Save button press. |
102 Private slot to handle the Save button press. |
100 |
103 |
101 It saves the diff shown in the dialog to a file in the local |
104 It saves the diff shown in the dialog to a file in the local |
102 filesystem. |
105 filesystem. |
103 """ |
106 """ |
104 dname, fname = Utilities.splitPath(self.filename2) |
107 dname, fname = Utilities.splitPath(self.filename2) |
105 fname = "{0}.diff".format(self.filename2) if fname != '.' else dname |
108 fname = "{0}.diff".format(self.filename2) if fname != "." else dname |
106 |
109 |
107 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
110 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
108 self, |
111 self, |
109 self.tr("Save Diff"), |
112 self.tr("Save Diff"), |
110 fname, |
113 fname, |
111 self.tr("Patch Files (*.diff)"), |
114 self.tr("Patch Files (*.diff)"), |
112 None, |
115 None, |
113 EricFileDialog.DontConfirmOverwrite) |
116 EricFileDialog.DontConfirmOverwrite, |
114 |
117 ) |
|
118 |
115 if not fname: |
119 if not fname: |
116 return |
120 return |
117 |
121 |
118 fpath = pathlib.Path(fname) |
122 fpath = pathlib.Path(fname) |
119 if not fpath.suffix: |
123 if not fpath.suffix: |
120 ex = selectedFilter.split("(*")[1].split(")")[0] |
124 ex = selectedFilter.split("(*")[1].split(")")[0] |
121 if ex: |
125 if ex: |
122 fpath = fpath.with_suffix(ex) |
126 fpath = fpath.with_suffix(ex) |
123 if fpath.exists(): |
127 if fpath.exists(): |
124 res = EricMessageBox.yesNo( |
128 res = EricMessageBox.yesNo( |
125 self, |
129 self, |
126 self.tr("Save Diff"), |
130 self.tr("Save Diff"), |
127 self.tr("<p>The patch file <b>{0}</b> already exists." |
131 self.tr( |
128 " Overwrite it?</p>").format(fpath), |
132 "<p>The patch file <b>{0}</b> already exists." " Overwrite it?</p>" |
129 icon=EricMessageBox.Warning) |
133 ).format(fpath), |
|
134 icon=EricMessageBox.Warning, |
|
135 ) |
130 if not res: |
136 if not res: |
131 return |
137 return |
132 |
138 |
133 txt = self.contents.toPlainText() |
139 txt = self.contents.toPlainText() |
134 try: |
140 try: |
135 with fpath.open("w", encoding="utf-8") as f, \ |
141 with fpath.open("w", encoding="utf-8") as f, contextlib.suppress( |
136 contextlib.suppress(UnicodeError): |
142 UnicodeError |
|
143 ): |
137 f.write(txt) |
144 f.write(txt) |
138 except OSError as why: |
145 except OSError as why: |
139 EricMessageBox.critical( |
146 EricMessageBox.critical( |
140 self, self.tr('Save Diff'), |
147 self, |
|
148 self.tr("Save Diff"), |
141 self.tr( |
149 self.tr( |
142 '<p>The patch file <b>{0}</b> could not be saved.<br />' |
150 "<p>The patch file <b>{0}</b> could not be saved.<br />" |
143 'Reason: {1}</p>').format(fpath, str(why))) |
151 "Reason: {1}</p>" |
|
152 ).format(fpath, str(why)), |
|
153 ) |
144 |
154 |
145 @pyqtSlot() |
155 @pyqtSlot() |
146 def on_diffButton_clicked(self): |
156 def on_diffButton_clicked(self): |
147 """ |
157 """ |
148 Private slot to handle the Compare button press. |
158 Private slot to handle the Compare button press. |
174 lines2 = f2.readlines() |
185 lines2 = f2.readlines() |
175 except OSError: |
186 except OSError: |
176 EricMessageBox.critical( |
187 EricMessageBox.critical( |
177 self, |
188 self, |
178 self.tr("Compare Files"), |
189 self.tr("Compare Files"), |
179 self.tr( |
190 self.tr("""<p>The file <b>{0}</b> could not be read.</p>""").format( |
180 """<p>The file <b>{0}</b> could not be read.</p>""") |
191 self.filename2 |
181 .format(self.filename2)) |
192 ), |
|
193 ) |
182 return |
194 return |
183 |
195 |
184 self.contents.clear() |
196 self.contents.clear() |
185 self.highlighter.regenerateRules() |
197 self.highlighter.regenerateRules() |
186 self.saveButton.setEnabled(False) |
198 self.saveButton.setEnabled(False) |
187 |
199 |
188 if self.unifiedRadioButton.isChecked(): |
200 if self.unifiedRadioButton.isChecked(): |
189 self.__generateUnifiedDiff( |
201 self.__generateUnifiedDiff( |
190 lines1, lines2, self.filename1, self.filename2, |
202 lines1, lines2, self.filename1, self.filename2, filemtime1, filemtime2 |
191 filemtime1, filemtime2) |
203 ) |
192 else: |
204 else: |
193 self.__generateContextDiff( |
205 self.__generateContextDiff( |
194 lines1, lines2, self.filename1, self.filename2, |
206 lines1, lines2, self.filename1, self.filename2, filemtime1, filemtime2 |
195 filemtime1, filemtime2) |
207 ) |
196 |
208 |
197 tc = self.contents.textCursor() |
209 tc = self.contents.textCursor() |
198 tc.movePosition(QTextCursor.MoveOperation.Start) |
210 tc.movePosition(QTextCursor.MoveOperation.Start) |
199 self.contents.setTextCursor(tc) |
211 self.contents.setTextCursor(tc) |
200 self.contents.ensureCursorVisible() |
212 self.contents.ensureCursorVisible() |
201 |
213 |
202 self.saveButton.setEnabled(True) |
214 self.saveButton.setEnabled(True) |
203 |
215 |
204 def __appendText(self, txt): |
216 def __appendText(self, txt): |
205 """ |
217 """ |
206 Private method to append text to the end of the contents pane. |
218 Private method to append text to the end of the contents pane. |
207 |
219 |
208 @param txt text to insert (string) |
220 @param txt text to insert (string) |
209 """ |
221 """ |
210 tc = self.contents.textCursor() |
222 tc = self.contents.textCursor() |
211 tc.movePosition(QTextCursor.MoveOperation.End) |
223 tc.movePosition(QTextCursor.MoveOperation.End) |
212 self.contents.setTextCursor(tc) |
224 self.contents.setTextCursor(tc) |
213 self.contents.insertPlainText(txt) |
225 self.contents.insertPlainText(txt) |
214 |
226 |
215 def __generateUnifiedDiff(self, a, b, fromfile, tofile, |
227 def __generateUnifiedDiff(self, a, b, fromfile, tofile, fromfiledate, tofiledate): |
216 fromfiledate, tofiledate): |
|
217 """ |
228 """ |
218 Private slot to generate a unified diff output. |
229 Private slot to generate a unified diff output. |
219 |
230 |
220 @param a first sequence of lines (list of strings) |
231 @param a first sequence of lines (list of strings) |
221 @param b second sequence of lines (list of strings) |
232 @param b second sequence of lines (list of strings) |
222 @param fromfile filename of the first file (string) |
233 @param fromfile filename of the first file (string) |
223 @param tofile filename of the second file (string) |
234 @param tofile filename of the second file (string) |
224 @param fromfiledate modification time of the first file (string) |
235 @param fromfiledate modification time of the first file (string) |
228 unified_diff(a, b, fromfile, tofile, fromfiledate, tofiledate) |
239 unified_diff(a, b, fromfile, tofile, fromfiledate, tofiledate) |
229 ): |
240 ): |
230 self.__appendText(line) |
241 self.__appendText(line) |
231 if not (paras % self.updateInterval): |
242 if not (paras % self.updateInterval): |
232 QApplication.processEvents() |
243 QApplication.processEvents() |
233 |
244 |
234 if self.contents.toPlainText().strip() == "": |
245 if self.contents.toPlainText().strip() == "": |
235 self.__appendText(self.tr('There is no difference.')) |
246 self.__appendText(self.tr("There is no difference.")) |
236 |
247 |
237 def __generateContextDiff(self, a, b, fromfile, tofile, |
248 def __generateContextDiff(self, a, b, fromfile, tofile, fromfiledate, tofiledate): |
238 fromfiledate, tofiledate): |
|
239 """ |
249 """ |
240 Private slot to generate a context diff output. |
250 Private slot to generate a context diff output. |
241 |
251 |
242 @param a first sequence of lines (list of strings) |
252 @param a first sequence of lines (list of strings) |
243 @param b second sequence of lines (list of strings) |
253 @param b second sequence of lines (list of strings) |
244 @param fromfile filename of the first file (string) |
254 @param fromfile filename of the first file (string) |
245 @param tofile filename of the second file (string) |
255 @param tofile filename of the second file (string) |
246 @param fromfiledate modification time of the first file (string) |
256 @param fromfiledate modification time of the first file (string) |
250 context_diff(a, b, fromfile, tofile, fromfiledate, tofiledate) |
260 context_diff(a, b, fromfile, tofile, fromfiledate, tofiledate) |
251 ): |
261 ): |
252 self.__appendText(line) |
262 self.__appendText(line) |
253 if not (paras % self.updateInterval): |
263 if not (paras % self.updateInterval): |
254 QApplication.processEvents() |
264 QApplication.processEvents() |
255 |
265 |
256 if self.contents.toPlainText().strip() == "": |
266 if self.contents.toPlainText().strip() == "": |
257 self.__appendText(self.tr('There is no difference.')) |
267 self.__appendText(self.tr("There is no difference.")) |
258 |
268 |
259 def __fileChanged(self): |
269 def __fileChanged(self): |
260 """ |
270 """ |
261 Private slot to enable/disable the Compare button. |
271 Private slot to enable/disable the Compare button. |
262 """ |
272 """ |
263 if ( |
273 if not self.file1Picker.text() or not self.file2Picker.text(): |
264 not self.file1Picker.text() or |
|
265 not self.file2Picker.text() |
|
266 ): |
|
267 self.diffButton.setEnabled(False) |
274 self.diffButton.setEnabled(False) |
268 else: |
275 else: |
269 self.diffButton.setEnabled(True) |
276 self.diffButton.setEnabled(True) |
270 |
277 |
271 |
278 |
272 class DiffWindow(EricMainWindow): |
279 class DiffWindow(EricMainWindow): |
273 """ |
280 """ |
274 Main window class for the standalone dialog. |
281 Main window class for the standalone dialog. |
275 """ |
282 """ |
|
283 |
276 def __init__(self, parent=None): |
284 def __init__(self, parent=None): |
277 """ |
285 """ |
278 Constructor |
286 Constructor |
279 |
287 |
280 @param parent reference to the parent widget (QWidget) |
288 @param parent reference to the parent widget (QWidget) |
281 """ |
289 """ |
282 super().__init__(parent) |
290 super().__init__(parent) |
283 |
291 |
284 self.setStyle(Preferences.getUI("Style"), |
292 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) |
285 Preferences.getUI("StyleSheet")) |
293 |
286 |
|
287 self.cw = DiffDialog(self) |
294 self.cw = DiffDialog(self) |
288 self.cw.installEventFilter(self) |
295 self.cw.installEventFilter(self) |
289 size = self.cw.size() |
296 size = self.cw.size() |
290 self.setCentralWidget(self.cw) |
297 self.setCentralWidget(self.cw) |
291 self.resize(size) |
298 self.resize(size) |
292 |
299 |
293 def eventFilter(self, obj, event): |
300 def eventFilter(self, obj, event): |
294 """ |
301 """ |
295 Public method to filter events. |
302 Public method to filter events. |
296 |
303 |
297 @param obj reference to the object the event is meant for (QObject) |
304 @param obj reference to the object the event is meant for (QObject) |
298 @param event reference to the event object (QEvent) |
305 @param event reference to the event object (QEvent) |
299 @return flag indicating, whether the event was handled (boolean) |
306 @return flag indicating, whether the event was handled (boolean) |
300 """ |
307 """ |
301 if event.type() == QEvent.Type.Close: |
308 if event.type() == QEvent.Type.Close: |
302 QApplication.exit() |
309 QApplication.exit() |
303 return True |
310 return True |
304 |
311 |
305 return False |
312 return False |