src/eric7/UI/DiffDialog.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
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.
157 lines1 = f1.readlines() 167 lines1 = f1.readlines()
158 except OSError: 168 except OSError:
159 EricMessageBox.critical( 169 EricMessageBox.critical(
160 self, 170 self,
161 self.tr("Compare Files"), 171 self.tr("Compare Files"),
162 self.tr( 172 self.tr("""<p>The file <b>{0}</b> could not be read.</p>""").format(
163 """<p>The file <b>{0}</b> could not be read.</p>""") 173 self.filename1
164 .format(self.filename1)) 174 ),
175 )
165 return 176 return
166 177
167 self.filename2 = Utilities.toNativeSeparators(self.file2Picker.text()) 178 self.filename2 = Utilities.toNativeSeparators(self.file2Picker.text())
168 try: 179 try:
169 filemtime2 = time.ctime(os.stat(self.filename2).st_mtime) 180 filemtime2 = time.ctime(os.stat(self.filename2).st_mtime)
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

eric ide

mercurial