Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use dialog for external changes #1309

Merged
merged 46 commits into from
Jul 9, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
0987e69
Ignore external changes after "Continue"
Mar 23, 2023
3cc29cf
Reload or Overwrite options
Mar 24, 2023
d3fdc67
Merge branch 'master' into fix-external-edit
Mar 24, 2023
9db86e1
Merge branch 'master' into fix-external-edit
Mar 27, 2023
34cb3f2
Merge branch 'master' into fix-external-edit
Mar 28, 2023
d6d16f4
Add AskSaveDialog.vala
Apr 11, 2023
e9f3e3a
Introduce "locked" document state
Apr 11, 2023
3cedc1f
Lock document during loading; unlock after if writable
Apr 11, 2023
88c2bbf
Stop some functions if locked
Apr 11, 2023
bad0f37
Modify save and save as functions for locked
Apr 11, 2023
3c821f2
Make some funcs private
Apr 11, 2023
0d35093
Modify check file status function for locked
Apr 11, 2023
d3e300e
Rewrite ask save location for dialog and locked
Apr 11, 2023
eef32ab
Compile dialog
Apr 11, 2023
453ccc4
Minor improvements
Apr 11, 2023
2411124
Start unlocked after loading
Apr 12, 2023
3826760
Handle closing changed locked documents
Apr 12, 2023
ee5cbdb
Update saved status when locked status changes
Apr 12, 2023
a2c3a6d
Amend tab tooltip when locked
Apr 12, 2023
984f97b
Merge branch 'master' into fix-external-edit
zeebok Apr 15, 2023
4658618
Merge branch 'fix-external-edit' into ask-savelocation-dialog
Apr 16, 2023
fb3e173
Rework external changes with dialog
Apr 16, 2023
9e2bc94
Merge branch 'master' into ask-savelocation-dialog
Apr 16, 2023
31c6c66
Merge branch 'ask-savelocation-dialog' into asksave-externalchange-di…
Apr 16, 2023
56aa99f
Connect focus-in handler with focus-out handler
Apr 17, 2023
e8bc76f
Merge branch 'master' into asksave-externalchange-dialog
May 25, 2023
d984c2d
Merge branch 'master' into asksave-externalchange-dialog
May 31, 2023
4b87600
Fix focus in and overwrite
May 31, 2023
b4ae35b
Inline AskExternalChanges dialog
May 31, 2023
4e3bc10
Merge branch 'master' into asksave-externalchange-dialog
Jun 3, 2023
070314a
Do not throw dialog if there are no unsaved internal edits
Jun 3, 2023
7e17654
Remove unused class
Jun 3, 2023
9534e3b
Move some code back to original position
Jun 3, 2023
3b141f9
Update last_save_content after loading external changes
Jun 3, 2023
694bd94
Check for external changes even when internal changes
Jun 3, 2023
22710cc
Only save deleted file if there are unsaved changes
Jun 6, 2023
206a752
Merge branch 'master' into asksave-externalchange-dialog
Jun 16, 2023
8432737
Merge branch 'master' into asksave-externalchange-dialog
zeebok Jul 3, 2023
1a51238
Update src/Services/Document.vala
Jul 3, 2023
50db488
Merge branch 'master' into asksave-externalchange-dialog
zeebok Jul 6, 2023
7c214f6
Use copy paste to autoload changed text
Jul 6, 2023
a80ad16
Flag as not loaded while replacing text
Jul 6, 2023
77251d4
Merge branch 'master' into asksave-externalchange-dialog
zeebok Jul 7, 2023
04e428d
No undo on autoload
Jul 7, 2023
f5008b0
Lose unused infobar
Jul 7, 2023
7b6187a
Merge branch 'master' into asksave-externalchange-dialog
zeebok Jul 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ namespace Scratch {
if (doc.is_file_temporary == true) {
action_save_as ();
} else {
doc.save_with_hold.begin (true);
doc.save_request ();
}
}
}
Expand Down
194 changes: 144 additions & 50 deletions src/Services/Document.vala
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ namespace Scratch.Services {

// Locked documents can be edited but cannot be (auto)saved to the current file.
// Locked documents can be saved to a different file (when they will be unlocked)
// Create as locked so focus events ingb=nored. Unlock when content is loaded
jeremypw marked this conversation as resolved.
Show resolved Hide resolved
private bool _locked = true;
public bool locked {
get {
Expand All @@ -115,8 +116,8 @@ namespace Scratch.Services {
public Gtk.Stack main_stack;
public Scratch.Widgets.SourceView source_view;
private Scratch.Services.SymbolOutline? outline = null;
public string original_content;
private string last_save_content;
public string original_content = "";
private string last_save_content = "";
public bool saved = true;
private bool completion_shown = false;

Expand Down Expand Up @@ -208,6 +209,17 @@ namespace Scratch.Services {

this.source_view.buffer.create_tag ("highlight_search_all", "background", "yellow", null);

// Focus in event for SourceView
// Check if file changed externally or permissions changed
this.source_view.focus_in_event.connect (() => {
if (!locked && !is_file_temporary) {
check_file_status ();
check_undoable_actions ();
}

return false;
});

// Focus out event for SourceView
this.source_view.focus_out_event.connect (() => {
if (!locked && Scratch.settings.get_boolean ("autosave")) {
Expand Down Expand Up @@ -237,9 +249,7 @@ namespace Scratch.Services {
completion_shown = false;
});

// /* Create as loaded and unlocked - could be new document */
loaded = file == null;
locked = false;
ellipsize_mode = Pango.EllipsizeMode.MIDDLE;
}

Expand All @@ -261,8 +271,10 @@ namespace Scratch.Services {
Source.remove (timeout_saving);
timeout_saving = 0;
}

timeout_saving = Timeout.add (1000, () => {
save.begin ();
check_file_status ();
save.begin (); // Not forced
timeout_saving = 0;
return false;
});
Expand Down Expand Up @@ -371,16 +383,6 @@ namespace Scratch.Services {
}
}

// Focus in event for SourceView
this.source_view.focus_in_event.connect (() => {
if (!working && !locked) {
check_file_status ();
check_undoable_actions ();
}

return false;
});

// Change syntax highlight
this.source_view.change_syntax_highlight_from_file (this.file);

Expand Down Expand Up @@ -474,6 +476,13 @@ namespace Scratch.Services {
return ret_value;
}

// Handle save action (only use for user interaction)
public void save_request () {
check_undoable_actions ();
check_file_status (); // Need to check for external changes before forcing save
save_with_hold.begin (true);
}

private bool is_saving = false;
public async bool save_with_hold (bool force = false, bool saving_as = false) {
// Prevent reentry which could result in mismatched holds on Application
Expand Down Expand Up @@ -571,6 +580,7 @@ namespace Scratch.Services {

this.set_saved_status (true);
last_save_content = source_view.buffer.text;
hide_info_bar ();

debug ("File “%s” saved successfully", get_basename ());

Expand Down Expand Up @@ -619,6 +629,7 @@ namespace Scratch.Services {
// Should not set "modified" state of the buffer to true - this is automatic
is_saved = yield save (true, true);
if (is_saved) {
source_view.buffer.set_modified (false);
if (is_current_file_temporary) {
try {
// Delete temporary file
Expand Down Expand Up @@ -861,23 +872,37 @@ namespace Scratch.Services {
private void check_file_status () {
// If the file does not exist anymore
if (!exists ()) {
locked = true;
string details;
if (mounted == false) {
details = _("The location containing the file “%s” was unmounted.");
if (source_view.buffer.get_modified ()) {
locked = true;
string details;
if (mounted == false) {
details = _("The location containing the file “%s” was unmounted and there are unsaved changes.");
} else {
details = _("File “%s” was deleted and there are unsaved changes.");
}

ask_save_location (details.printf ("<b>%s</b>".printf (get_basename ())));
} else {
details = _("File “%s” was deleted.");
var close_tab_action = Utils.action_from_group (MainWindow.ACTION_CLOSE_TAB, actions);
close_tab_action.set_enabled (true);
this.saved = true; //Do not try to save
close_tab_action.activate (new Variant ("s", file.get_path ()));
}

ask_save_location (details.printf ("<b>%s</b>".printf (get_basename ())));
} else if (loaded) { // Check external changes after loading
} else if (loaded && !is_saving) { // Check external changes after loading
if (!locked && !can_write () && source_view.buffer.get_modified ()) {
// The file has become unwritable while changes are pending
// The file has become unwritable while changes are pending
locked = true;
var details = _("File “%s” does not have write permission.");
ask_save_location (details.printf ("<b>%s</b>".printf (get_basename ())));
} else {
// Check for external changes (can load even if locked or unwritable)
// Detect external changes by comparing file content with buffer content.
// Only done when no unsaved internal changes else difference from saved
// file are to be expected.

//TODO Check required behaviour on continue
// If user selects to continue regardless then no further
// check made for this document
// External changes will be overwritten on next (auto) save
var new_buffer = new Gtk.SourceBuffer (null);
var source_file_loader = new Gtk.SourceFileLoader (
new_buffer,
Expand All @@ -896,36 +921,34 @@ namespace Scratch.Services {
return;
}

if (source_view.buffer.text == new_buffer.text) {
if (last_save_content == new_buffer.text) {
return;
}

if (!source_view.buffer.get_modified ()) {
//FIXME Should block editing until responded?
if (Scratch.settings.get_boolean ("autosave")) {
source_view.set_text (new_buffer.text, false);
} else {
string message = _(
"File “%s” was modified by an external application."
).printf ("<b>%s</b>".printf (get_uri ()));

set_message (
Gtk.MessageType.WARNING,
message,
_("Reload"), () => {
this.source_view.set_text (
new_buffer.text, false
);
hide_info_bar ();
},
_("Continue"), () => {
hide_info_bar ();
})
;
}
if (last_save_content == source_view.buffer.text) {
// There are no unsaved internal edits so just load the external changes
//TODO Indicate to the user external changes loaded?
source_view.buffer.text = new_buffer.text;
last_save_content = source_view.buffer.text;
// We know the content and file are now in sync so set unmodified
source_view.buffer.set_modified (false);
return;
}

var primary_text = _("File “%s” was modified by an external application").printf (file.get_uri ());
string secondary_text;

if (source_view.buffer.get_modified ()) {
secondary_text = _(
"There are also unsaved changes. Reloading the document will overwrite the unsaved changes."
);
} else {
//TODO Handle conflicting changes (dialog?)
secondary_text = _(
"The document changed externally since you last saved it."
);
}

ask_external_changes (primary_text, secondary_text, new_buffer.text);
}
);
}
Expand Down Expand Up @@ -981,6 +1004,77 @@ namespace Scratch.Services {
dialog.present ();
}

private void ask_external_changes (
string primary_text,
string secondary_text,
string external_content
) {
locked = true;

var app_instance = (Gtk.Application) GLib.Application.get_default ();
var dialog = new Granite.MessageDialog (
primary_text,
secondary_text,
new ThemedIcon ("dialog-warning"),
Gtk.ButtonsType.NONE
) {
transient_for = app_instance.active_window

};

dialog.add_button (_("Continue"), Gtk.ResponseType.REJECT);

var reload_button = (Gtk.Button) (dialog.add_button (_("Reload"), 0));
reload_button.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);

var overwrite_button = (Gtk.Button) (dialog.add_button (_("Overwrite"), 1));
overwrite_button.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);

var saveas_button = (Gtk.Button) (dialog.add_button (_("Save Document elsewhere"), Gtk.ResponseType.ACCEPT));
saveas_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);

dialog.response.connect ((id) => {
dialog.destroy ();
Idle.add (() => {
switch (id) {
case Gtk.ResponseType.ACCEPT: // Save as
save_as_with_hold.begin ((obj, res) => {
if (save_as_with_hold.end (res)) {
locked = false;
}
});
break;
case Gtk.ResponseType.REJECT: // Ignore
// Document remains locked while conflicts exist
// The user must resolve some other way. To overwrite
// external changes use "Save As" with same name
break;
case 0: // Reload
source_view.buffer.text = external_content;
source_view.buffer.set_modified (false);
last_save_content = source_view.buffer.text;
set_saved_status (true);
locked = false;
break;
case 1: // Overwrite
// Force save, unlock to allow saving to same location
locked = false;
save_with_hold.begin (true, false, (obj, res) => {
if (!save_with_hold.end (res)) {
locked = true;
}
});
break;
default:
assert_not_reached ();
}

return Source.REMOVE;
});
});

dialog.present ();
}
// Set Undo/Redo action sensitive property
public void check_undoable_actions () {
var source_buffer = (Gtk.SourceBuffer) source_view.buffer;
Expand Down
8 changes: 2 additions & 6 deletions src/Widgets/DocumentView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,8 @@ public class Scratch.Widgets.DocumentView : Granite.Widgets.DynamicNotebook {
file.create (FileCreateFlags.PRIVATE);

var doc = new Services.Document (window.actions, file);

insert_document (doc, -1);
current_document = doc;

doc.focus ();
save_opened_files ();
// Must open document in order to unlock it.
open_document (doc);
} catch (Error e) {
critical (e.message);
}
Expand Down