Merge lp:~artem-anufrij/scratch/dont-force-to-save-unsaved into lp:~elementary-apps/scratch/scratch

Proposed by Artem Anufrij
Status: Merged
Approved by: Robert Roth
Approved revision: 1414
Merged at revision: 1393
Proposed branch: lp:~artem-anufrij/scratch/dont-force-to-save-unsaved
Merge into: lp:~elementary-apps/scratch/scratch
Diff against target: 746 lines (+236/-186)
7 files modified
HACKING (+1/-1)
src/Dialogs/PreferencesDialog.vala (+1/-5)
src/MainWindow.vala (+53/-6)
src/Scratch.vala (+16/-2)
src/Services/Document.vala (+145/-161)
src/Widgets/DocumentView.vala (+13/-3)
src/Widgets/ToolBar.vala (+7/-8)
To merge this branch: bzr merge lp:~artem-anufrij/scratch/dont-force-to-save-unsaved
Reviewer Review Type Date Requested Status
Robert Roth (community) code review Approve
Danielle Foré ux Approve
Review via email: mp+237154@code.launchpad.net

Commit message

Use temporary files for unsaved files to be able to restore them at next startup

Description of the change

BLUEPRINT DESCRIPTION:
+1 for keeping in .local/share/scratch/unsaved/. Because losing work is the worst user experience ever. If we keep files in /tmp they can get deleted by the system (think sudden power outage and user boots again or even someone is keeping /tmp in RAM - other systems and maybe future eOS). And if we keep them in trash user can delete them unknowingly because he expects that only files that he himself moved to trash are there. Also files that are not saved anywhere IMHO should have a *highly visible* warning so that even user that is absolutely new to PCs knows it's not saved. (Perhaps a red exclamation mark on the tab? Or the entire tab tinted slightly red?) Won't it be annoying? No, because how often do you write and keep in editor (for a longer time) something that you don't want to save? ~grzesiek1e5

To post a comment you must log in.
1399. By artem-anufrij

Ready for check (clear 'file != null' block)

1400. By artem-anufrij

Replace 'scratch' to exec_name. Fix code style.

1401. By artem-anufrij

Delete old backup file after 'save as...'

Revision history for this message
Robert Roth (evfool) wrote :

Code looks mostly OK, here are some notes:
* Singleton is not needed here, accessing data_home_folder_unsaved seems to be the only reason you're making the Scratch instance a singleton, but that variable should be part of settings (e.g I want to configure that in dconf - or scratch Preferences if Dan allows - to use /tmp instead of my home dir)
* string message = _("File ") + " \"<b>%s</b>\" ".printf (get_basename ()) + _("was modified by an external application. Do you want to load it again or continue your editing?");
This is not translation-friendly, and there are more like this. Translatable strings should not be concatenated.
It can also be written like: _("File %s was modified by an external application.").printf ("<b>%s</b>".printf (get_basename ())).
I see that there are some of these coming from before your commits, so it's not 100% your code, but they should be fixed too (not necessarily on this branch), but at least the code we touch should get better after each touch.
* Add a description and a commit message to the merge proposal
I haven't tested this yet, this is just a code review, will test later, with the fixes.

review: Needs Fixing (code review)
1402. By artem-anufrij

dev-commit

1403. By artem-anufrij

get_user_cache_dir() for termporary files; changed the text.

1404. By artem-anufrij

always open temporary files on startup

Revision history for this message
Artem Anufrij (artem-anufrij) wrote :

Please check the fixes.
- temporary location: .local/share/scretch-text-editor/unsaved
- text style was fixed.

Revision history for this message
Robert Roth (evfool) wrote :

See my inline comments. I'm still not sure the scratch app instance should be made singleton just to be able to access the path variable, that's a second reason why settings-based path was suggested, as the settings object is available globally.

review: Needs Fixing
1405. By artem-anufrij

code fixing

Revision history for this message
Robert Roth (evfool) wrote :

Ok, looks nice, as per Slack comments the path doesn't have to be configurable, so we're keeping the app instance singleton. Approved.

review: Approve
1406. By artem-anufrij

temporary typer

Revision history for this message
Danielle Foré (danrabbit) wrote :

I think if you close the individual tab (as opposed to the whole app) you should be prompted to either save or delete. I don't think we should encourage a workflow where users can perpetually store a bunch of documents in a hidden folder. Additionally, in the current workflow I'm not sure how I'm supposed to discard a document that I've decided I don't actually want to save.

I'm also thinking that we should override the tab/title name for unsaved documents and just call them "unsaved document" or something instead of referring to them as a file name with a location inside the app.

review: Needs Fixing (ux)
1407. By artem-anufrij

Implemented as agreed.

1408. By artem-anufrij

Implemented as agreed.

1409. By artem-anufrij

Implemented as agreed.

Revision history for this message
Artem Anufrij (artem-anufrij) wrote :

I have changed the functionality as agreed. Please check this out.

Revision history for this message
Robert Roth (evfool) wrote :

Code style is mostly ok, see one minor request inline.

review: Needs Fixing (code style)
1410. By artem-anufrij

Code style has been improved.

Revision history for this message
Robert Roth (evfool) wrote :

Code style fixed, approving, waiting for Dan's approval.

review: Approve (code style)
Revision history for this message
Danielle Foré (danrabbit) wrote :

Maybe I'm missing something, but the feature seems to no longer work at all. Trying to close the app with an unsaved document prompts me to save.

Also the window title still reflects the name and location of the file

1411. By artem-anufrij

Window title fixed

App closing: save 'unsaved' files in ./local/share/... without save dialog
Tab closing: show save dialog for 'unsaved' files

Revision history for this message
Artem Anufrij (artem-anufrij) wrote :

Hey Dan,

sorry I have overlooked the: "...(as opposed to the whole app)...".

Now it feels smooth.

1412. By artem-anufrij

code style

1413. By artem-anufrij

code style

Revision history for this message
Danielle Foré (danrabbit) wrote :

Works as expected now. Thanks Artem! This is great :)

review: Approve (ux)
Revision history for this message
Robert Roth (evfool) wrote :

Looks fine, two more indentation issues to fix before the final approval.

review: Needs Fixing (code review)
1414. By artem-anufrij

code style fixed

Revision history for this message
Artem Anufrij (artem-anufrij) wrote :
Revision history for this message
Robert Roth (evfool) wrote :

Ok, Approving. Thanks for the several fixes and your perseverence, it is something of great value.

review: Approve (code review)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'HACKING'
--- HACKING 2013-08-17 21:53:30 +0000
+++ HACKING 2014-10-18 19:50:44 +0000
@@ -152,4 +152,4 @@
152 bzr branch lp:scratch scratch152 bzr branch lp:scratch scratch
153 bzr branch scratch/ scratch-fix-123456153 bzr branch scratch/ scratch-fix-123456
154 cd scratch-fix-123456154 cd scratch-fix-123456
155 bzr pull lp:scratch155 bzr pull lp:scratch
156\ No newline at end of file156\ No newline at end of file
157157
=== modified file 'src/Dialogs/PreferencesDialog.vala'
--- src/Dialogs/PreferencesDialog.vala 2014-08-07 09:22:40 +0000
+++ src/Dialogs/PreferencesDialog.vala 2014-10-18 19:50:44 +0000
@@ -288,10 +288,6 @@
288 var scheme = scheme_manager.get_scheme (scheme_id);288 var scheme = scheme_manager.get_scheme (scheme_id);
289 style_scheme.append (scheme.id, scheme.name);289 style_scheme.append (scheme.id, scheme.name);
290 }290 }
291
292
293 }291 }
294
295 }292 }
296293} // Namespace
297} // Namespace
298\ No newline at end of file294\ No newline at end of file
299295
=== modified file 'src/MainWindow.vala'
--- src/MainWindow.vala 2014-10-05 20:56:25 +0000
+++ src/MainWindow.vala 2014-10-18 19:50:44 +0000
@@ -86,6 +86,9 @@
86 // Restore session86 // Restore session
87 restore_saved_state_extra ();87 restore_saved_state_extra ();
8888
89 // Crate folder for unsaved documents
90 create_unsaved_documents_directory ();
91
89#if HAVE_ZEITGEIST92#if HAVE_ZEITGEIST
90 // Set up the Data Source Registry for Zeitgeist93 // Set up the Data Source Registry for Zeitgeist
91 registry = new DataSourceRegistry ();94 registry = new DataSourceRegistry ();
@@ -181,7 +184,12 @@
181 path = _("Trash");184 path = _("Trash");
182185
183 path = Uri.unescape_string (path);186 path = Uri.unescape_string (path);
184 this.toolbar.title = doc.file.get_basename () + " (%s)".printf(path);187
188 string toolbar_title = doc.file.get_basename () + " (%s)".printf (path);
189 if (doc.is_file_temporary)
190 toolbar_title = "(%s)".printf (doc.get_basename ());
191
192 this.toolbar.title = toolbar_title;
185 }193 }
186 else {194 else {
187 this.toolbar.title = this.app.app_cmd_name;195 this.toolbar.title = this.app.app_cmd_name;
@@ -241,14 +249,17 @@
241249
242 this.search_revealer.set_reveal_child (false);250 this.search_revealer.set_reveal_child (false);
243 251
252 main_actions.get_action ("OpenTemporaryFiles").visible = this.has_temporary_files ();
244 main_actions.get_action ("SaveFile").visible = !settings.autosave;253 main_actions.get_action ("SaveFile").visible = !settings.autosave;
245 main_actions.get_action ("Templates").visible = plugins.plugin_iface.template_manager.template_available;254 main_actions.get_action ("Templates").visible = plugins.plugin_iface.template_manager.template_available;
246 plugins.plugin_iface.template_manager.notify["template_available"].connect ( () => {255 plugins.plugin_iface.template_manager.notify["template_available"].connect ( () => {
247 main_actions.get_action ("Templates").visible = plugins.plugin_iface.template_manager.template_available;256 main_actions.get_action ("Templates").visible = plugins.plugin_iface.template_manager.template_available;
248 });257 });
249258
250 // Show welcome by default259 if (has_temporary_files ())
251 this.split_view.show_welcome ();260 action_open_temporary_files ();
261 else
262 this.split_view.show_welcome ();
252263
253 // Plugins hook264 // Plugins hook
254 HookFunc hook_func = () => {265 HookFunc hook_func = () => {
@@ -268,7 +279,6 @@
268 hook_func ();279 hook_func ();
269 });280 });
270 hook_func ();281 hook_func ();
271
272 }282 }
273283
274 private void on_plugin_toggled (Gtk.Notebook notebook) {284 private void on_plugin_toggled (Gtk.Notebook notebook) {
@@ -381,13 +391,25 @@
381 return split_view.is_empty ();391 return split_view.is_empty ();
382 }392 }
383393
394 public bool has_temporary_files () {
395 FileEnumerator enumerator = File.new_for_path (app.data_home_folder_unsaved).enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0, null);
396 var fileinfo = enumerator.next_file (null);
397 while (fileinfo != null) {
398 if (!fileinfo.get_name ().has_suffix ("~")) {
399 return true;
400 }
401 fileinfo = enumerator.next_file (null);
402 }
403 return false;
404 }
405
384 // Check if there no unsaved changes406 // Check if there no unsaved changes
385 private bool check_unsaved_changes () {407 private bool check_unsaved_changes () {
386 if (!is_empty ()) {408 if (!is_empty ()) {
387 foreach (var w in this.split_view.views) {409 foreach (var w in this.split_view.views) {
388 var view = w as Scratch.Widgets.DocumentView;410 var view = w as Scratch.Widgets.DocumentView;
389 foreach (var doc in view.docs) {411 foreach (var doc in view.docs) {
390 if (!doc.close ()) {412 if (!doc.close (true)) {
391 view.set_current_document (doc);413 view.set_current_document (doc);
392 return false;414 return false;
393 }415 }
@@ -418,6 +440,16 @@
418 vp.set_position (Scratch.saved_state.vp_size);440 vp.set_position (Scratch.saved_state.vp_size);
419 }441 }
420442
443 private void create_unsaved_documents_directory () {
444 File directory = File.new_for_path (app.data_home_folder_unsaved);
445 if (!directory.query_exists ()) {
446 debug ("create 'unsaved' directory: %s", directory.get_path ());
447 directory.make_directory_with_parents ();
448 return;
449 }
450 debug ("'unsaved' directory already exists.");
451 }
452
421 private void update_saved_state () {453 private void update_saved_state () {
422454
423 // Save window state455 // Save window state
@@ -517,6 +549,21 @@
517 filech.close ();549 filech.close ();
518 }550 }
519551
552 void action_open_temporary_files () {
553 FileEnumerator enumerator = File.new_for_path (app.data_home_folder_unsaved).enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0, null);
554 var fileinfo = enumerator.next_file (null);
555 while (fileinfo != null) {
556 if (!fileinfo.get_name ().has_suffix ("~")) {
557 debug ("open temporary file: %s", fileinfo.get_name ());
558 var file = File.new_for_path (app.data_home_folder_unsaved + fileinfo.get_name ());
559 var doc = new Scratch.Services.Document (this.main_actions, file);
560 this.open_document (doc);
561 }
562 // Next file info
563 fileinfo = enumerator.next_file (null);
564 }
565 }
566
520 void action_save () {567 void action_save () {
521 this.get_current_document ().save ();568 this.get_current_document ().save ();
522 }569 }
@@ -694,7 +741,7 @@
694 /* label, accelerator */ N_("Redo"), "<Control><shift>z",741 /* label, accelerator */ N_("Redo"), "<Control><shift>z",
695 /* tooltip */ N_("Redo the last undone action"),742 /* tooltip */ N_("Redo the last undone action"),
696 action_redo },743 action_redo },
697 { "Revert", Gtk.Stock.REVERT_TO_SAVED,744 { "Revert", Gtk.Stock.REVERT_TO_SAVED,
698 /* label, accelerator */ N_("Revert"), "<Control><shift>o",745 /* label, accelerator */ N_("Revert"), "<Control><shift>o",
699 /* tooltip */ N_("Restore this file"),746 /* tooltip */ N_("Restore this file"),
700 action_revert },747 action_revert },
701748
=== modified file 'src/Scratch.vala'
--- src/Scratch.vala 2014-10-05 21:21:40 +0000
+++ src/Scratch.vala 2014-10-18 19:50:44 +0000
@@ -34,7 +34,9 @@
34 private GLib.List <MainWindow> windows;34 private GLib.List <MainWindow> windows;
3535
36 public string app_cmd_name { get { return _app_cmd_name; } }36 public string app_cmd_name { get { return _app_cmd_name; } }
37 public string data_home_folder_unsaved { get { return _data_home_folder_unsaved; } }
37 private static string _app_cmd_name;38 private static string _app_cmd_name;
39 private static string _data_home_folder_unsaved;
38 private static string _cwd;40 private static string _cwd;
39 private static bool print_version = false;41 private static bool print_version = false;
40 private static bool create_new_tab = false;42 private static bool create_new_tab = false;
@@ -90,6 +92,18 @@
90 services = new ServicesSettings ();92 services = new ServicesSettings ();
91 windows = new GLib.List <MainWindow> ();93 windows = new GLib.List <MainWindow> ();
92 94
95 // Init data home folder for unsaved text files
96 _data_home_folder_unsaved = Environment.get_user_data_dir () + "/" + exec_name + "/unsaved/";
97 }
98
99 public static ScratchApp _instance = null;
100
101 public static ScratchApp instance {
102 get {
103 if (_instance == null)
104 _instance = new ScratchApp ();
105 return _instance;
106 }
93 }107 }
94108
95 protected override int command_line (ApplicationCommandLine command_line) {109 protected override int command_line (ApplicationCommandLine command_line) {
@@ -253,8 +267,8 @@
253 return Posix.EXIT_SUCCESS;267 return Posix.EXIT_SUCCESS;
254 }268 }
255269
256 var app = new ScratchApp ();270 ScratchApp app = ScratchApp.instance;
257 return app.run (args_primary_instance);271 return app.run (args_primary_instance);
258 }272 }
259 }273 }
260}274}
261\ No newline at end of file275\ No newline at end of file
262276
=== modified file 'src/Services/Document.vala'
--- src/Services/Document.vala 2014-08-06 05:45:44 +0000
+++ src/Services/Document.vala 2014-10-18 19:50:44 +0000
@@ -56,15 +56,23 @@
56 private Gtk.InfoBar info_bar = new Gtk.InfoBar ();56 private Gtk.InfoBar info_bar = new Gtk.InfoBar ();
5757
58 // Objects58 // Objects
59 private File? _file = null;59 private File _file = null;
60 public File? file {60 public File file {
61 get { return _file; }61 get { return _file; }
62 set { _file = value; file_changed (); }62 set {
63 _file = value;
64 file_changed ();
65 }
63 }66 }
64 public string original_content;67 public string original_content;
65 public string? last_saved_content = null;68 public string? last_saved_content = null;
66 public bool saved = true;69 public bool saved = true;
67 private bool error_shown = false;70 private bool error_shown = false;
71 public bool is_file_temporary {
72 get {
73 return file.get_path ().has_prefix (ScratchApp.instance.data_home_folder_unsaved);
74 }
75 }
68 76
69 // It is used to load file content on focusing77 // It is used to load file content on focusing
70 private bool loaded = false;78 private bool loaded = false;
@@ -134,43 +142,6 @@
134142
135 public async bool open () {143 public async bool open () {
136 this.source_view.buffer.create_tag ("highlight_search_all", "background", "yellow", null);144 this.source_view.buffer.create_tag ("highlight_search_all", "background", "yellow", null);
137 if (file == null) {
138 message ("New Document opened");
139 this.source_view.focus_in_event.connect (() => {
140 main_actions.get_action ("SaveFile").visible = true;
141 check_file_status ();
142 check_undoable_actions ();
143 return false;
144 });
145 uint timeout_saving = -1;
146 this.source_view.buffer.changed.connect (() => {
147 check_undoable_actions ();
148 // If it wasn't yet saved
149 if (file == null) {
150 this.set_saved_status (false);
151 return;
152 }
153 // Save if autosave is ON
154 if (settings.autosave) {
155 if (timeout_saving >= 0) {
156 Source.remove (timeout_saving);
157 timeout_saving = -1;
158 }
159 timeout_saving = Timeout.add (3000, () => {
160 save ();
161 timeout_saving = -1;
162 return false;
163 });
164 }
165 else if (!settings.autosave || file == null)
166 this.set_saved_status (false);
167 });
168 this.set_saved_status (true);
169 this.has_started_loading = true;
170 this.loaded = true;
171
172 return true;
173 }
174145
175 // If it does not exists, let's create it!146 // If it does not exists, let's create it!
176 if (!exists ()) {147 if (!exists ()) {
@@ -227,14 +198,17 @@
227 return true;198 return true;
228 }199 }
229200
230 public new bool close () {201 public new bool close (bool app_closing = false) {
231202
232 message ("Closing \"%s\"", get_basename ());203 message ("Closing \"%s\"", get_basename ());
233204
234 bool ret_value = true;205 bool ret_value = true;
235 206 if (app_closing && is_file_temporary && !delete_temporary_file ()) {
207 debug ("Save temporary file!");
208 this.save ();
209 }
236 // Check for unsaved changes210 // Check for unsaved changes
237 if (!this.saved) {211 else if (!this.saved || (!app_closing && is_file_temporary && !delete_temporary_file ())) {
238 debug ("There are unsaved changes, showing a Message Dialog!");212 debug ("There are unsaved changes, showing a Message Dialog!");
239213
240 // Create a GtkDialog214 // Create a GtkDialog
@@ -265,42 +239,40 @@
265 ret_value = false;239 ret_value = false;
266 break;240 break;
267 case Gtk.ResponseType.YES:241 case Gtk.ResponseType.YES:
268 this.save ();242 if (this.is_file_temporary)
269 ret_value = true;243 this.save_as ();
244 else
245 this.save ();
270 break;246 break;
271 case Gtk.ResponseType.NO:247 case Gtk.ResponseType.NO:
272 ret_value = true;248 if (this.is_file_temporary)
249 delete_temporary_file (true);
273 break;250 break;
274 }251 }
275 dialog.destroy ();252 dialog.destroy ();
276 }253 }
277254
278 if (file != null && ret_value) {255 if (ret_value) {
279 // Delete backup copy file256 // Delete backup copy file
280 delete_backup ();257 delete_backup ();
258 }
281#if HAVE_ZEITGEIST259#if HAVE_ZEITGEIST
282 // Zeitgeist integration260 // Zeitgeist integration
283 zg_log.close_insert (file.get_uri (), get_mime_type ());261 zg_log.close_insert (file.get_uri (), get_mime_type ());
284#endif262#endif
285 }
286 263
287 return ret_value;264 return ret_value;
288 }265 }
289266
290 public bool save () {267 public bool save () {
291 if (last_saved_content == get_text () && this.file != null)268 if (last_saved_content == get_text ())
292 return false;269 return false;
293270
294 if (!this.loaded)271 if (!this.loaded)
295 return false;272 return false;
296273
297 // Create backup copy file if it does not still exist274 // Create backup copy file
298 if (this.file != null)275 this.create_backup ();
299 create_backup ();
300
301 // Show save as dialog if file is null
302 if (this.file == null)
303 return this.save_as ();
304276
305 // Replace old content with the new one277 // Replace old content with the new one
306 try {278 try {
@@ -308,6 +280,7 @@
308 file.replace_contents (this.source_view.buffer.text.data, null, false, 0, out s);280 file.replace_contents (this.source_view.buffer.text.data, null, false, 0, out s);
309 } catch (Error e) {281 } catch (Error e) {
310 warning ("Cannot save \"%s\": %s", get_basename (), e.message);282 warning ("Cannot save \"%s\": %s", get_basename (), e.message);
283 return false;
311 }284 }
312285
313#if HAVE_ZEITGEIST286#if HAVE_ZEITGEIST
@@ -330,6 +303,9 @@
330 // New file303 // New file
331 var filech = Utils.new_file_chooser_dialog (Gtk.FileChooserAction.SAVE, _("Save File"), null);304 var filech = Utils.new_file_chooser_dialog (Gtk.FileChooserAction.SAVE, _("Save File"), null);
332305
306 string current_file = file.get_path ();
307 bool is_current_file_temporary = this.is_file_temporary;
308
333 if (filech.run () == Gtk.ResponseType.ACCEPT) {309 if (filech.run () == Gtk.ResponseType.ACCEPT) {
334 this.file = File.new_for_uri (filech.get_file ().get_uri ());310 this.file = File.new_for_uri (filech.get_file ().get_uri ());
335 // Update last visited path311 // Update last visited path
@@ -341,10 +317,20 @@
341 return false;317 return false;
342 }318 }
343319
344 // reset the last saved content320 // Reset the last saved content
345 last_saved_content = null;321 last_saved_content = null;
346322
347 save ();323 if (save () && is_current_file_temporary) {
324 try {
325 // Delete temporary file
326 File.new_for_path (current_file).delete ();
327 } catch (Error err) {
328 message ("Temporary file cannot be deleted: %s", current_file);
329 }
330 }
331
332 // Delete backup file
333 delete_backup (current_file + "~");
348334
349 // Change syntax highlight335 // Change syntax highlight
350 this.source_view.change_syntax_highlight_from_file (this.file);336 this.source_view.change_syntax_highlight_from_file (this.file);
@@ -369,19 +355,15 @@
369355
370 // Get mime type for the document356 // Get mime type for the document
371 public string get_mime_type () {357 public string get_mime_type () {
372 if (file == null)358 FileInfo info;
373 return "text/plain";359 string mime_type;
374 else {360 try {
375 FileInfo info;361 info = file.query_info ("standard::*", FileQueryInfoFlags.NONE, null);
376 string mime_type;362 mime_type = ContentType.get_mime_type (info.get_attribute_as_string (FileAttribute.STANDARD_CONTENT_TYPE));
377 try {363 return mime_type;
378 info = file.query_info ("standard::*", FileQueryInfoFlags.NONE, null);364 } catch (Error e) {
379 mime_type = ContentType.get_mime_type (info.get_attribute_as_string (FileAttribute.STANDARD_CONTENT_TYPE));365 warning ("%s", e.message);
380 return mime_type;366 return "undefined";
381 } catch (Error e) {
382 warning ("%s", e.message);
383 return "undefined";
384 }
385 }367 }
386 }368 }
387369
@@ -406,17 +388,15 @@
406388
407 // Get file uri389 // Get file uri
408 public string get_uri () {390 public string get_uri () {
409 if (file == null)
410 return "";
411 return this.file.get_uri ();391 return this.file.get_uri ();
412 } 392 }
413 393
414 // Get file name394 // Get file name
415 public string get_basename () {395 public string get_basename () {
416 if (file != null)396 if (is_file_temporary)
397 return _("New Document");
398 else
417 return file.get_basename ();399 return file.get_basename ();
418 else
419 return _("New Document");
420 }400 }
421401
422 // Set InfoBars message402 // Set InfoBars message
@@ -553,7 +533,7 @@
553 if (this.error_shown)533 if (this.error_shown)
554 return;534 return;
555 this.error_shown = true;535 this.error_shown = true;
556 string message = _("File \"<b>%s</b>\" cannot be read. Maybe it is corrupt\nor you do not have the necessary permissions to read it.").printf (get_basename ());536 string message = _("File \"%s\" cannot be read. Maybe it is corrupt\nor you do not have the necessary permissions to read it.").printf ("<b>%s</b>".printf (get_basename ()));
557 var parent_window = source_view.get_toplevel () as Gtk.Window;537 var parent_window = source_view.get_toplevel () as Gtk.Window;
558 var dialog = new Gtk.MessageDialog.with_markup (parent_window, Gtk.DialogFlags.MODAL,538 var dialog = new Gtk.MessageDialog.with_markup (parent_window, Gtk.DialogFlags.MODAL,
559 Gtk.MessageType.ERROR,539 Gtk.MessageType.ERROR,
@@ -566,84 +546,73 @@
566 546
567 // Check if the file was deleted/changed by an external source547 // Check if the file was deleted/changed by an external source
568 public void check_file_status () {548 public void check_file_status () {
569 if (file != null) {549 // If the file does not exist anymore
570 // If the file does not exist anymore550 if (!exists ()) {
571 if (!exists ()) {551 if (mounted == false) {
572 if (mounted == false) {552 string message = _("The location containing the file \"%s\" was unmounted. Do you want to save somewhere else?").printf ("<b>%s</b>".printf (get_basename ()));
573 string message = _("The location containing the file") + " \"<b>%s</b>\" ".printf (get_basename ()) +553
574 _("was unmounted. Do you want to save somewhere else?");554 set_message (Gtk.MessageType.WARNING, message, _("Save As…"), () => {
575
576 set_message (Gtk.MessageType.WARNING, message, _("Save As…"), () => {
577 this.save_as ();
578 hide_info_bar ();
579 });
580 } else {
581 string message = _("File") + " \"<b>%s</b>\" ".printf (get_basename ()) +
582 _("was deleted. Do you want to save it anyway?");
583
584 set_message (Gtk.MessageType.WARNING, message, _("Save"), () => {
585 this.save ();
586 hide_info_bar ();
587 });
588 }
589 main_actions.get_action ("SaveFile").sensitive = false;
590 this.source_view.editable = false;
591 return;
592 }
593 // If the file can't be written
594 if (!can_write ()) {
595 string message = _("You cannot save changes on file") + " \"<b>%s</b>\". ".printf (get_basename ()) +
596 _("Do you want to save the changes to this file in a different location?");
597
598 set_message (Gtk.MessageType.WARNING, message, _("Save changes elsewhere"), () => {
599 this.save_as ();555 this.save_as ();
600 hide_info_bar ();556 hide_info_bar ();
601 });557 });
602 main_actions.get_action ("SaveFile").sensitive = false;558 } else {
603 this.source_view.editable = !settings.autosave;559 string message = _("File \"%s\" was deleted. Do you want to save it anyway?").printf ("<b>%s</b>".printf (get_basename ()));
604 }560
605 else {561 set_message (Gtk.MessageType.WARNING, message, _("Save"), () => {
606 main_actions.get_action ("SaveFile").sensitive = true;562 this.save ();
607 this.source_view.editable = true;563 hide_info_bar ();
608 }564 });
609 // Detect external changes565 }
610 FileHandler.load_content_from_file.begin (file, (obj, res) => {566 main_actions.get_action ("SaveFile").sensitive = false;
611 var text = FileHandler.load_content_from_file.end (res);567 this.source_view.editable = false;
612 if (text == null) {568 return;
613 show_error_dialog ();569 }
614 return;570 // If the file can't be written
615 }571 if (!can_write ()) {
616 if (!text.validate())572 string message = _("You cannot save changes on file \"%s\". Do you want to save the changes to this file in a different location?").printf ("<b>%s</b>".printf (get_basename ()));
617 text = file_content_to_utf8 (file, text);573
618 // Reload automatically if auto save is ON574 set_message (Gtk.MessageType.WARNING, message, _("Save changes elsewhere"), () => {
619 if (last_saved_content != null && text != last_saved_content) {575 this.save_as ();
620 if (settings.autosave)576 hide_info_bar ();
621 this.source_view.set_text (text, false);
622 else {
623 string message = _("File ") + " \"<b>%s</b>\" ".printf (get_basename ()) +
624 _("was modified by an external application. Do you want to load it again or continue your editing?");
625
626 set_message (Gtk.MessageType.WARNING, message, _("Load"), () => {
627 this.source_view.set_text (text, false);
628 hide_info_bar ();
629 }, _("Continue"), () => {
630 hide_info_bar ();
631 });
632 }
633 }
634 });577 });
578 main_actions.get_action ("SaveFile").sensitive = false;
579 this.source_view.editable = !settings.autosave;
635 }580 }
636 else {581 else {
637 main_actions.get_action ("SaveFile").sensitive = true;582 main_actions.get_action ("SaveFile").sensitive = true;
638 this.source_view.editable = true;583 this.source_view.editable = true;
639 }584 }
585 // Detect external changes
586 FileHandler.load_content_from_file.begin (file, (obj, res) => {
587 var text = FileHandler.load_content_from_file.end (res);
588 if (text == null) {
589 show_error_dialog ();
590 return;
591 }
592 if (!text.validate ())
593 text = file_content_to_utf8 (file, text);
594 // Reload automatically if auto save is ON
595 if (last_saved_content != null && text != last_saved_content) {
596 if (settings.autosave)
597 this.source_view.set_text (text, false);
598 else {
599 string message = _("File \"%s\" was modified by an external application. Do you want to load it again or continue your editing?").printf ("<b>%s</b>".printf (get_basename ()));
600 set_message (Gtk.MessageType.WARNING, message, _("Load"), () => {
601 this.source_view.set_text (text, false);
602 hide_info_bar ();
603 }, _("Continue"), () => {
604 hide_info_bar ();
605 });
606 }
607 }
608 });
640 }609 }
641610
642 // Set Undo/Redo action sensitive property611 // Set Undo/Redo action sensitive property
643 public void check_undoable_actions () {612 public void check_undoable_actions () {
644 main_actions.get_action ("Undo").sensitive = this.source_view.buffer.can_undo;613 main_actions.get_action ("Undo").sensitive = this.source_view.buffer.can_undo;
645 main_actions.get_action ("Redo").sensitive = this.source_view.buffer.can_redo;614 main_actions.get_action ("Redo").sensitive = this.source_view.buffer.can_redo;
646 main_actions.get_action ("Revert").sensitive = (file != null && original_content != source_view.buffer.text);615 main_actions.get_action ("Revert").sensitive = (original_content != source_view.buffer.text);
647 }616 }
648617
649 // Set saved status618 // Set saved status
@@ -676,35 +645,55 @@
676 }645 }
677 }646 }
678647
679 private void delete_backup () {648 private void delete_backup (string? backup_path = null) {
680 var backup = File.new_for_path (this.file.get_path () + "~");649
681 if (!backup.query_exists ())650 string backup_file = "";
651
652 if (backup_path == null)
653 backup_file = file.get_path () + "~";
654 else
655 backup_file = backup_path;
656
657 debug ("Backup file deleting: %s", backup_file);
658
659 var backup = File.new_for_path (backup_file);
660 if (backup == null || !backup.query_exists ()) {
661 debug ("Backup file doesn't exists: %s", backup.get_path ());
682 return;662 return;
663 }
683 try {664 try {
684 backup.delete ();665 backup.delete ();
666 debug ("Backup file deleted: %s", backup_file);
685 } catch (Error e) {667 } catch (Error e) {
686 warning ("Cannot delete backup for file \"%s\": %s", get_basename (), e.message);668 warning ("Cannot delete backup for file \"%s\": %s", get_basename (), e.message);
687 }669 }
688 }670 }
689671
672 private bool delete_temporary_file (bool force = false) {
673 if (!is_file_temporary || (get_text ().length > 0 && !force))
674 return false;
675 try {
676 file.delete ();
677 return true;
678 } catch (Error e) {
679 warning ("Cannot delete temporary file \"%s\": %s", file.get_uri (), e.message);
680 }
681 return false;
682 }
683
690 // Return true if the file is writable684 // Return true if the file is writable
691 public bool can_write () {685 public bool can_write () {
692 FileInfo info;686 FileInfo info;
693687
694 bool writable = false;688 bool writable = false;
695689
696 if (this.file == null)
697 return writable = true;
698
699 try {690 try {
700 info = this.file.query_info (FileAttribute.ACCESS_CAN_WRITE, FileQueryInfoFlags.NONE, null);691 info = this.file.query_info (FileAttribute.ACCESS_CAN_WRITE, FileQueryInfoFlags.NONE, null);
701 writable = info.get_attribute_boolean (FileAttribute.ACCESS_CAN_WRITE);692 writable = info.get_attribute_boolean (FileAttribute.ACCESS_CAN_WRITE);
702 return writable;693 return writable;
703 } catch (Error e) {694 } catch (Error e) {
704 if (this.file != null ) {695 warning ("query_info failed, but filename appears to be correct, allowing as new file");
705 warning ("query_info failed, but filename appears to be correct, allowing as new file");696 writable = true;
706 writable = true;
707 }
708 return writable;697 return writable;
709 }698 }
710 }699 }
@@ -715,11 +704,6 @@
715 }704 }
716705
717 private void file_changed () {706 private void file_changed () {
718 if (file == null) {
719 mounted = true;
720 return;
721 }
722
723 if (mount != null) {707 if (mount != null) {
724 mount.unmounted.disconnect (unmounted_cb);708 mount.unmounted.disconnect (unmounted_cb);
725 mount = null;709 mount = null;
@@ -740,4 +724,4 @@
740 mounted = false;724 mounted = false;
741 }725 }
742 }726 }
743}727}
744\ No newline at end of file728\ No newline at end of file
745729
=== modified file 'src/Widgets/DocumentView.vala'
--- src/Widgets/DocumentView.vala 2014-06-02 16:22:42 +0000
+++ src/Widgets/DocumentView.vala 2014-10-18 19:50:44 +0000
@@ -85,11 +85,21 @@
85 85
86 show_all ();86 show_all ();
87 }87 }
88
89 private string unsaved_file_path_builder () {
90 DateTime timestamp = new DateTime.now_local ();
91 string new_text_file = _("Text file from ") + timestamp.format ("%Y-%m-%d %H:%M:%S");
92
93 return ScratchApp.instance.data_home_folder_unsaved + new_text_file;
94 }
88 95
89 public void new_document () {96 public void new_document () {
90 var doc = new Document (window.main_actions);97 File file = File.new_for_path (unsaved_file_path_builder ());
98 file.create (FileCreateFlags.PRIVATE);
99
100 var doc = new Document (window.main_actions, file);
91 doc.create_page ();101 doc.create_page ();
92 102
93 this.notebook.insert_tab (doc, -1);103 this.notebook.insert_tab (doc, -1);
94 this.notebook.current = doc;104 this.notebook.current = doc;
95 105
@@ -220,4 +230,4 @@
220 }230 }
221 }231 }
222 }232 }
223}233}
224\ No newline at end of file234\ No newline at end of file
225235
=== modified file 'src/Widgets/ToolBar.vala'
--- src/Widgets/ToolBar.vala 2014-08-11 17:35:47 +0000
+++ src/Widgets/ToolBar.vala 2014-10-18 19:50:44 +0000
@@ -39,19 +39,18 @@
39 public AppMenu app_menu;39 public AppMenu app_menu;
4040
41 public Toolbar (Gtk.ActionGroup main_actions) {41 public Toolbar (Gtk.ActionGroup main_actions) {
42
43 // Toolbar properties42 // Toolbar properties
44 // compliant with elementary HIG43 // compliant with elementary HIG
45 get_style_context ().add_class ("primary-toolbar");44 get_style_context ().add_class ("primary-toolbar");
46 45
47 // Create ToolButtons46 // Create ToolButtons
48 open_button = main_actions.get_action ("Open").create_tool_item() as Gtk.ToolButton;47 open_button = main_actions.get_action ("Open").create_tool_item () as Gtk.ToolButton;
49 templates_button = main_actions.get_action ("Templates").create_tool_item() as Gtk.ToolButton;48 templates_button = main_actions.get_action ("Templates").create_tool_item () as Gtk.ToolButton;
50 save_button = main_actions.get_action ("SaveFile").create_tool_item() as Gtk.ToolButton;49 save_button = main_actions.get_action ("SaveFile").create_tool_item () as Gtk.ToolButton;
51 save_as_button = main_actions.get_action ("SaveFileAs").create_tool_item() as Gtk.ToolButton;50 save_as_button = main_actions.get_action ("SaveFileAs").create_tool_item () as Gtk.ToolButton;
52 revert_button = main_actions.get_action ("Revert").create_tool_item() as Gtk.ToolButton;51 revert_button = main_actions.get_action ("Revert").create_tool_item () as Gtk.ToolButton;
53 find_button = main_actions.get_action ("Fetch").create_tool_item() as Gtk.ToolButton;52 find_button = main_actions.get_action ("Fetch").create_tool_item () as Gtk.ToolButton;
54 53
55 // Create Share and AppMenu54 // Create Share and AppMenu
56 share_menu = new Gtk.Menu ();55 share_menu = new Gtk.Menu ();
57 share_app_menu = new Granite.Widgets.ToolButtonWithMenu (new Image.from_icon_name ("document-export", IconSize.MENU), _("Share"), share_menu);56 share_app_menu = new Granite.Widgets.ToolButtonWithMenu (new Image.from_icon_name ("document-export", IconSize.MENU), _("Share"), share_menu);

Subscribers

People subscribed via source and target branches