Skip to content

Commit

Permalink
feat: Ask user to save unsaved changes on tab/app closed #289 from vy…
Browse files Browse the repository at this point in the history
…shnav-vinod/feat/ask-save-before-close (nightly)

feat: Ask user to save the file before they close the tab #280
  • Loading branch information
tomlin7 authored Apr 13, 2024
2 parents 36ae81a + 30a251c commit cff8f22
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 5 deletions.
1 change: 1 addition & 0 deletions biscuit/core/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def close_dir(self, *_) -> None:
self.base.close_active_directory()

def quit(self, *_) -> None:
self.base.on_close_app()
self.base.destroy()

def toggle_maximize(self, *_) -> None:
Expand Down
28 changes: 27 additions & 1 deletion biscuit/core/components/editors/texteditor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import tkinter as tk
from hashlib import md5
from tkinter.font import Font

from biscuit.core.utils import Scrollbar
Expand All @@ -24,7 +25,8 @@ def __init__(self, master, path=None, exists=True, language=None, minimalist=Fal
self.editable = True
self.run_command_value = None
self.unsupported = False

self.content_hash = ''

if not self.standalone:
self.__buttons__ = [('sync', self.base.editorsmanager.reopen_active_editor),]

Expand Down Expand Up @@ -81,6 +83,29 @@ def __init__(self, master, path=None, exists=True, language=None, minimalist=Fal
if self.base.settings.config.auto_save_enabled:
self.auto_save()

def file_loaded(self):
self.recalculate_content_hash()
self.event_generate("<<FileLoaded>>", when="tail")

def recalculate_content_hash(self):
""" Recalculate the hash of the editor content """

self.content_hash = self.calculate_content_hash()

def calculate_content_hash(self):
""" Calculate the hash of the editor content """

if self.exists and self.editable:
text = self.text.get_all_text()
return md5(text.encode()).hexdigest()

@property
def unsaved_changes(self):
""" Check if the editor content has changed """

if self.editable:
return self.content_hash != self.calculate_content_hash()

def run_file(self, dedicated=False, external=False):
if not self.run_command_value:
self.base.notifications.show("No programs are configured to run this file.")
Expand Down Expand Up @@ -137,6 +162,7 @@ def set_fontsize(self, size):

def save(self, path=None):
if self.editable:
self.recalculate_content_hash()
self.text.save_file(path)

def auto_save(self):
Expand Down
4 changes: 2 additions & 2 deletions biscuit/core/components/editors/texteditor/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Text(BaseText):

def __init__(self, master: TextEditor, path: str=None, exists: bool=True, minimalist: bool=False, standalone: bool=False, language: str=None, *args, **kwargs) -> None:
super().__init__(master, *args, **kwargs)
self.master = master
self.master: TextEditor = master
self.path = path
self.filename = os.path.basename(path) if path else None
self.encoding = 'utf-8'
Expand Down Expand Up @@ -901,7 +901,7 @@ def process_queue(self, eol: str=None):
# If the queue is empty, schedule the next check after a short delay
self.master.after(100, self.process_queue)

self.master.event_generate("<<FileLoaded>>", when="tail")
self.master.file_loaded()

def custom_get(self, start, end):
content = self.get(start, end)
Expand Down
5 changes: 5 additions & 0 deletions biscuit/core/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def setup_tk(self) -> None:

self.setup_floating_widgets()
self.setup_root()

self.protocol("WM_DELETE_WINDOW", self.on_close_app)

def setup_root(self):
# the very parent of all GUI parts
Expand Down Expand Up @@ -137,6 +139,9 @@ def on_gui_update(self, *_) -> None:

def register_onfocus(self, fn) -> None:
self.onfocus_callbacks.append(fn)

def on_close_app(self) -> None:
self.editorsmanager.delete_all_editors()

def on_focus(self, *_) -> None:
for fn in self.onfocus_callbacks:
Expand Down
12 changes: 10 additions & 2 deletions biscuit/core/layout/base/content/editors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,23 @@ def generate_actionsets(self) -> None:

def add_default_editors(self) -> None:
"Adds all default editors"

self.add_editors(self.default_editors)

def add_welcome(self) -> None:
"Shows welcome tab"

self.add_editor(Welcome(self))

def add_editors(self, editors: list[Editor]) -> None:
"Append <Editor>s to list. Create tabs for them."

for editor in editors:
self.add_editor(editor)

def add_editor(self, editor: Union[Editor,BaseEditor]) -> Editor | BaseEditor:
"Appends a editor to list. Create a tab."

self.active_editors.append(editor)
if editor.content:
editor.content.create_buttons(self.editorsbar.container)
Expand All @@ -97,9 +101,12 @@ def add_editor(self, editor: Union[Editor,BaseEditor]) -> Editor | BaseEditor:

def delete_all_editors(self) -> None:
"Permanently delete all editors."
for editor in self.active_editors:
editor.destroy()

for tab in self.tabs.tabs:
if e := tab.editor:
self.tabs.save_unsaved_changes(e)
e.destroy()

self.editorsbar.clear()
self.tabs.clear_all_tabs()
self.active_editors.clear()
Expand All @@ -108,6 +115,7 @@ def delete_all_editors(self) -> None:

def reopen_active_editor(self) -> None:
"Reopen the active editor"

if self.active_editor and self.active_editor.exists:
self.delete_editor(self.active_editor)
self.update()
Expand Down
14 changes: 14 additions & 0 deletions biscuit/core/layout/base/content/editors/tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from . import Editorsbar
from biscuit.core.components import Editor

import os
import tkinter as tk
from tkinter.messagebox import askyesno

from biscuit.core.utils import Frame

Expand All @@ -30,8 +32,20 @@ def add_tab(self, editor: Editor) -> None:

def close_active_tab(self) -> None:
self.close_tab(self.active_tab)

def save_unsaved_changes(self, e) -> None:
if e.content and e.content.editable and e.content.unsaved_changes:
if askyesno(f"Unsaved changes", f"Do you want to save the changes you made to {e.filename}"):
if e.exists:
e.save()
else:
self.base.commands.save_as()
print(f"Saved changes to {e.path}.")

def close_tab(self, tab: Tab) -> None:
if e := tab.editor:
self.save_unsaved_changes(e)

try:
i = self.tabs.index(tab)
except ValueError:
Expand Down

0 comments on commit cff8f22

Please sign in to comment.