Skip to content

Commit fc84191

Browse files
MightyCreakYurii Zolotko
andauthored
Prompt once to reload if more than one file modified externally (bis) (#180)
* Create only one dialog even if multiple files has been changed on disk * fix: the calls to `NumericDialog` weren't working * use `transient_for` instead of `parent` when creating new `Gtk.Dialog` * Changelog: I removed the "thanks to" (just kept the mentions to the users), because it felt weird to put "thanks to myself" 😅 Co-authored-by: Yurii Zolotko <[email protected]>
1 parent cd0a79d commit fc84191

File tree

5 files changed

+121
-77
lines changed

5 files changed

+121
-77
lines changed

CHANGELOG.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- Language: initial support for Rust language (thanks to @alopatindev)
12+
- Language: initial support for Rust language (@alopatindev)
1313

1414
### Changed
1515

16-
- Translation: updated Swedish translation (thanks to @eson57)
16+
- Translation: updated Swedish translation (@eson57)
17+
- Dialog: prompt only once if several files needs to be reloaded (@yuriiz)
18+
19+
### Fixed
20+
21+
- fix: "Go to line..." dialog didn't show up (@MightyCreak)
22+
- Tech debt: use `transient_for` instead of the deprecated `parent` when creating
23+
a `Gtk.Widget` (@MightyCreak)
1724

1825
- Documentation: prefer `pip3` over `pip` to ensure it works everywhere (thanks to @krlmlr)
1926

2027
## 0.7.7 - 2022-10-23
2128

2229
### Changed
2330

24-
- Translation: updated Spanish translation (thanks to @oscfdezdz)
31+
- Translation: updated Spanish translation (@oscfdezdz)
2532
- Translation: updated POT file
2633
- Translation: fixed issue with commented string that still needs translation
2734

2835
## 0.7.6 - 2022-10-23
2936

3037
### Added
3138

32-
- Port to Mac OS (thanks to @hugoholgersson)
39+
- Port to Mac OS (@hugoholgersson)
3340

3441
### Changed
3542

data/io.github.mightycreak.Diffuse.appdata.xml.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
</p>
4949
<p>Added:</p>
5050
<ul>
51-
<li>Port to Mac OS (thanks to @hugoholgersson)</li>
51+
<li>Port to Mac OS (@hugoholgersson)</li>
5252
</ul>
5353
<p>Changed:</p>
5454
<ul>

src/diffuse/dialogs.py

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import os
2121

2222
from gettext import gettext as _
23+
from typing import Optional
2324

2425
from diffuse import constants
2526
from diffuse import utils
@@ -74,23 +75,23 @@ def _current_folder_changed_cb(widget):
7475
FileChooserDialog.last_chosen_folder = widget.get_current_folder()
7576

7677
def __init__(self, title, parent, prefs, action, accept, rev=False):
77-
Gtk.FileChooserDialog.__init__(self, title=title, parent=parent, action=action)
78+
Gtk.FileChooserDialog.__init__(self, title=title, transient_for=parent, action=action)
7879
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
7980
self.add_button(accept, Gtk.ResponseType.OK)
8081
self.prefs = prefs
8182
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0, border_width=5)
8283
label = Gtk.Label(label=_('Encoding: '))
8384
hbox.pack_start(label, False, False, 0)
8485
label.show()
85-
self.encoding = entry = utils.EncodingMenu(
86-
prefs,
87-
action in [Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.SELECT_FOLDER])
88-
hbox.pack_start(entry, False, False, 5)
89-
entry.show()
86+
self._encoding = utils.EncodingMenu(
87+
prefs=prefs,
88+
autodetect=action in [Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.SELECT_FOLDER])
89+
hbox.pack_start(self._encoding, False, False, 5)
90+
self._encoding.show()
9091
if rev:
91-
self.revision = entry = Gtk.Entry()
92-
hbox.pack_end(entry, False, False, 0)
93-
entry.show()
92+
self._revision = Gtk.Entry()
93+
hbox.pack_end(self._revision, False, False, 0)
94+
self._revision.show()
9495
label = Gtk.Label(label=_('Revision: '))
9596
hbox.pack_end(label, False, False, 0)
9697
label.show()
@@ -100,14 +101,14 @@ def __init__(self, title, parent, prefs, action, accept, rev=False):
100101
self.set_current_folder(self.last_chosen_folder)
101102
self.connect('current-folder-changed', self._current_folder_changed_cb)
102103

103-
def set_encoding(self, encoding):
104-
self.encoding.set_text(encoding)
104+
def set_encoding(self, encoding: Optional[str]) -> None:
105+
self._encoding.set_text(encoding)
105106

106-
def get_encoding(self) -> str:
107-
return self.encoding.get_text()
107+
def get_encoding(self) -> Optional[str]:
108+
return self._encoding.get_text()
108109

109110
def get_revision(self) -> str:
110-
return self.revision.get_text()
111+
return self._revision.get_text()
111112

112113
def get_filename(self) -> str:
113114
# convert from UTF-8 string to unicode
@@ -117,7 +118,7 @@ def get_filename(self) -> str:
117118
# dialogue used to search for text
118119
class NumericDialog(Gtk.Dialog):
119120
def __init__(self, parent, title, text, val, lower, upper, step=1, page=0):
120-
Gtk.Dialog.__init__(self, title=title, parent=parent, destroy_with_parent=True)
121+
Gtk.Dialog.__init__(self, title=title, transient_for=parent, destroy_with_parent=True)
121122
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
122123
self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
123124

@@ -128,32 +129,40 @@ def __init__(self, parent, title, text, val, lower, upper, step=1, page=0):
128129
label = Gtk.Label(label=text)
129130
hbox.pack_start(label, False, False, 0)
130131
label.show()
132+
131133
adj = Gtk.Adjustment(
132134
value=val,
133135
lower=lower,
134136
upper=upper,
135137
step_increment=step,
136138
page_increment=page,
137139
page_size=0)
138-
self.button = button = Gtk.SpinButton(adjustment=adj, climb_rate=1.0, digits=0)
139-
button.connect('activate', self.button_cb)
140-
hbox.pack_start(button, True, True, 0)
141-
button.show()
140+
self._button = Gtk.SpinButton(adjustment=adj, climb_rate=1.0, digits=0)
141+
self._button.connect('activate', self._button_cb)
142+
hbox.pack_start(self._button, True, True, 0)
143+
self._button.show()
142144

143145
vbox.pack_start(hbox, True, True, 0)
144146
hbox.show()
145147

146148
self.vbox.pack_start(vbox, False, False, 0)
147149
vbox.show()
148150

149-
def button_cb(self, widget):
151+
def _button_cb(self, widget: Gtk.SpinButton) -> None:
150152
self.response(Gtk.ResponseType.ACCEPT)
151153

154+
def get_value(self) -> int:
155+
return self._button.get_value_as_int()
156+
152157

153158
# dialogue used to search for text
154159
class SearchDialog(Gtk.Dialog):
155160
def __init__(self, parent, pattern=None, history=None):
156-
Gtk.Dialog.__init__(self, title=_('Find...'), parent=parent, destroy_with_parent=True)
161+
Gtk.Dialog.__init__(
162+
self,
163+
title=_('Find...'),
164+
transient_for=parent,
165+
destroy_with_parent=True)
157166
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
158167
self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
159168

@@ -165,11 +174,11 @@ def __init__(self, parent, pattern=None, history=None):
165174
hbox.pack_start(label, False, False, 0)
166175
label.show()
167176
combo = Gtk.ComboBoxText.new_with_entry()
168-
self.entry = combo.get_child()
169-
self.entry.connect('activate', self.entry_cb)
177+
self._entry = combo.get_child()
178+
self._entry.connect('activate', self._entry_cb)
170179

171180
if pattern is not None:
172-
self.entry.set_text(pattern)
181+
self._entry.set_text(pattern)
173182

174183
if history is not None:
175184
completion = Gtk.EntryCompletion()
@@ -179,7 +188,7 @@ def __init__(self, parent, pattern=None, history=None):
179188
for h in history:
180189
liststore.append([h])
181190
combo.append_text(h)
182-
self.entry.set_completion(completion)
191+
self._entry.set_completion(completion)
183192

184193
hbox.pack_start(combo, True, True, 0)
185194
combo.show()
@@ -200,5 +209,8 @@ def __init__(self, parent, pattern=None, history=None):
200209
vbox.show()
201210

202211
# callback used when the Enter key is pressed
203-
def entry_cb(self, widget):
212+
def _entry_cb(self, widget: Gtk.Entry) -> None:
204213
self.response(Gtk.ResponseType.ACCEPT)
214+
215+
def get_search_text(self) -> str:
216+
return self._entry.get_text()

src/diffuse/main.py

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,19 @@ def setEdits(self, has_edits: bool) -> None:
177177
self.has_edits = has_edits
178178
self.updateTitle()
179179

180+
# Has the file on disk changed since last time it was loaded?
181+
def has_file_changed_on_disk(self) -> bool:
182+
if self.info.last_stat is not None:
183+
try:
184+
new_stat = os.stat(self.info.name)
185+
if self.info.last_stat[stat.ST_MTIME] < new_stat[stat.ST_MTIME]:
186+
# update our notion of the most recent modification
187+
self.info.last_stat = new_stat
188+
return True
189+
except OSError:
190+
return False
191+
return False
192+
180193
# pane footer
181194
class PaneFooter(Gtk.Box):
182195
def __init__(self) -> None:
@@ -465,36 +478,6 @@ def reload_file_button_cb(self, widget, f):
465478
def reload_file_cb(self, widget, data):
466479
self.open_file(self.current_pane, True)
467480

468-
# check changes to files on disk when receiving keyboard focus
469-
def focus_in(self, widget, event):
470-
for f, h in enumerate(self.headers):
471-
info = h.info
472-
try:
473-
if info.last_stat is not None:
474-
info = h.info
475-
new_stat = os.stat(info.name)
476-
if info.last_stat[stat.ST_MTIME] < new_stat[stat.ST_MTIME]:
477-
# update our notion of the most recent modification
478-
info.last_stat = new_stat
479-
if info.label is not None:
480-
s = info.label
481-
else:
482-
s = info.name
483-
msg = _(
484-
'The file %s changed on disk. Do you want to reload the file?'
485-
) % (s, )
486-
dialog = utils.MessageDialog(
487-
self.get_toplevel(),
488-
Gtk.MessageType.QUESTION,
489-
msg
490-
)
491-
ok = (dialog.run() == Gtk.ResponseType.OK)
492-
dialog.destroy()
493-
if ok:
494-
self.open_file(f, True)
495-
except OSError:
496-
pass
497-
498481
# save contents of pane 'f' to file
499482
def save_file(self, f: int, save_as: bool = False) -> bool:
500483
h = self.headers[f]
@@ -626,10 +609,10 @@ def go_to_line_cb(self, widget, data):
626609
_('Line Number: '),
627610
val=1,
628611
lower=1,
629-
step=self.panes[self.current_pane].max_line_number + 1
612+
upper=self.panes[self.current_pane].max_line_number + 1
630613
)
631614
okay = (dialog.run() == Gtk.ResponseType.ACCEPT)
632-
i = dialog.button.get_value_as_int()
615+
i = dialog.get_value()
633616
dialog.destroy()
634617
if okay:
635618
self.go_to_line(i)
@@ -958,8 +941,50 @@ def __init__(self, rc_dir):
958941
# notifies all viewers on focus changes so they may check for external
959942
# changes to files
960943
def focus_in_cb(self, widget, event):
944+
changed = []
961945
for i in range(self.notebook.get_n_pages()):
962-
self.notebook.get_nth_page(i).focus_in(widget, event)
946+
page = self.notebook.get_nth_page(i)
947+
for f, h in enumerate(page.headers):
948+
if h.has_file_changed_on_disk():
949+
changed.append((page, f))
950+
951+
if changed:
952+
filenames = []
953+
for (page, f) in changed:
954+
h = page.headers[f]
955+
filename = h.info.label if h.info.label is not None else h.info.name
956+
filenames.append(filename)
957+
958+
primary_text = _("Changes detected")
959+
secondary_text = ""
960+
if len(filenames) == 1:
961+
secondary_text = _(
962+
"The file \"%s\" changed on disk.\n\n"
963+
"Do you want to reload the file?"
964+
) % (filenames[0],)
965+
else:
966+
secondary_text = _(
967+
"The following files changed on disk:\n%s\n\n"
968+
"Do you want to reload these files?"
969+
) % ("\n".join("- " + filename for filename in filenames),)
970+
971+
dialog = Gtk.MessageDialog(
972+
transient_for=self.get_toplevel(),
973+
message_type=Gtk.MessageType.QUESTION,
974+
buttons=Gtk.ButtonsType.YES_NO,
975+
text=primary_text)
976+
dialog.format_secondary_text(secondary_text)
977+
dialog.set_default_response(Gtk.ResponseType.YES)
978+
979+
button = dialog.get_widget_for_response(Gtk.ResponseType.YES)
980+
button.get_style_context().add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION)
981+
982+
response = dialog.run()
983+
dialog.destroy()
984+
985+
if response == Gtk.ResponseType.YES:
986+
for page, f in changed:
987+
page.open_file(f, True)
963988

964989
# record the window's position and size
965990
def configure_cb(self, widget, event):
@@ -1050,7 +1075,7 @@ def confirmCloseViewers(self, viewers: List[FileDiffViewer]) -> bool:
10501075
return True
10511076

10521077
# ask the user which files should be saved
1053-
dialog = Gtk.MessageDialog(parent=self.get_toplevel(),
1078+
dialog = Gtk.MessageDialog(transient_for=self.get_toplevel(),
10541079
destroy_with_parent=True,
10551080
message_type=Gtk.MessageType.WARNING,
10561081
buttons=Gtk.ButtonsType.NONE,
@@ -1487,7 +1512,7 @@ def new_n_way_file_merge_cb(self, widget, data):
14871512
upper=16
14881513
)
14891514
okay = (dialog.run() == Gtk.ResponseType.ACCEPT)
1490-
npanes = dialog.button.get_value_as_int()
1515+
npanes = dialog.get_value()
14911516
dialog.destroy()
14921517
if okay:
14931518
viewer = self.newFileDiffViewer(npanes)
@@ -1528,7 +1553,7 @@ def find(self, force: bool, reverse: bool) -> None:
15281553
dialog.backwards_button.set_active(self.bool_state['search_backwards'])
15291554
keep = (dialog.run() == Gtk.ResponseType.ACCEPT)
15301555
# persist the search options
1531-
pattern = dialog.entry.get_text()
1556+
pattern = dialog.get_search_text()
15321557
match_case = dialog.match_case_button.get_active()
15331558
backwards = dialog.backwards_button.get_active()
15341559
dialog.destroy()

0 commit comments

Comments
 (0)