From 3c6bf3df728f3dcbd1e390198730f51987c2fe0c Mon Sep 17 00:00:00 2001 From: Vyshnav Vinod Date: Fri, 12 Apr 2024 19:04:12 +0530 Subject: [PATCH 1/3] feat: #280 Remove unnecessary comments --- biscuit/core/layout/base/content/editors/tab.py | 14 ++++++++++++++ .../core/layout/base/content/editors/tabs.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/biscuit/core/layout/base/content/editors/tab.py b/biscuit/core/layout/base/content/editors/tab.py index 4b01a000..fb70d51b 100644 --- a/biscuit/core/layout/base/content/editors/tab.py +++ b/biscuit/core/layout/base/content/editors/tab.py @@ -8,6 +8,7 @@ import os import tkinter as tk +from hashlib import md5 from biscuit.core.utils import Frame, Icon, IconButton @@ -37,6 +38,8 @@ def __init__(self, master: Tabs, editor: Editor, *args, **kwargs) -> None: self.bind("", self.on_hover) self.bind("", self.off_hover) + self.content_hash = "" + def close(self, *_) -> None: self.master.close_tab(self) @@ -75,3 +78,14 @@ def select(self, *_) -> None: self.apply_color(self.hbg) self.closebtn.config(activeforeground=self.hfg) self.selected = True + self.content_hash = self.calculate_content_hash() + + def calculate_content_hash(self): + """ Calculate the hash of the editor content """ + + if self.editor.content: # Welcome editor returns a None value + + # Cannot get contents when using self.editor.content.get(..), + # so had to directly call get() + editor_contents = self.editor.content.text.get(index1="1.0", index2="end-1c") + return md5(editor_contents.encode()).hexdigest() diff --git a/biscuit/core/layout/base/content/editors/tabs.py b/biscuit/core/layout/base/content/editors/tabs.py index 84e6b43e..cfaf7e82 100644 --- a/biscuit/core/layout/base/content/editors/tabs.py +++ b/biscuit/core/layout/base/content/editors/tabs.py @@ -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 @@ -32,6 +34,13 @@ def close_active_tab(self) -> None: self.close_tab(self.active_tab) def close_tab(self, tab: Tab) -> None: + + if self.content_has_changed(): + if askyesno("Save File", f"You have unsaved changes. Do you want to save {tab.editor.filename}"): + filepath = os.path.join(self.base.active_directory, tab.editor.filename) + tab.editor.save(filepath) + print(f"Saved changes to {filepath}.") + try: i = self.tabs.index(tab) except ValueError: @@ -87,3 +96,11 @@ def switch_tabs(self, path) -> None: if tab.editor.path == path: tab.select() return tab.editor + + def content_has_changed(self): + current_file_hash = self.active_tab.calculate_content_hash() + if current_file_hash == self.active_tab.content_hash: + # No changes has been made in the editor + return False + else: + return True \ No newline at end of file From c9dd044c174a5c1ca1735d8c67abd614d60c7f0d Mon Sep 17 00:00:00 2001 From: Billy Date: Sun, 14 Apr 2024 00:52:35 +0530 Subject: [PATCH 2/3] feat: Move hash calculation code to `components/TextEditor` --- .../components/editors/texteditor/__init__.py | 28 ++++++++++++++++++- .../components/editors/texteditor/text.py | 4 +-- .../core/layout/base/content/editors/tab.py | 14 ---------- .../core/layout/base/content/editors/tabs.py | 23 ++++++--------- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/biscuit/core/components/editors/texteditor/__init__.py b/biscuit/core/components/editors/texteditor/__init__.py index a8e961d8..d6554dfa 100644 --- a/biscuit/core/components/editors/texteditor/__init__.py +++ b/biscuit/core/components/editors/texteditor/__init__.py @@ -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 @@ -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),] @@ -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("<>", 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.") @@ -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): diff --git a/biscuit/core/components/editors/texteditor/text.py b/biscuit/core/components/editors/texteditor/text.py index 6c8b846d..bc82e5c8 100644 --- a/biscuit/core/components/editors/texteditor/text.py +++ b/biscuit/core/components/editors/texteditor/text.py @@ -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' @@ -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("<>", when="tail") + self.master.file_loaded() def custom_get(self, start, end): content = self.get(start, end) diff --git a/biscuit/core/layout/base/content/editors/tab.py b/biscuit/core/layout/base/content/editors/tab.py index fb70d51b..4b01a000 100644 --- a/biscuit/core/layout/base/content/editors/tab.py +++ b/biscuit/core/layout/base/content/editors/tab.py @@ -8,7 +8,6 @@ import os import tkinter as tk -from hashlib import md5 from biscuit.core.utils import Frame, Icon, IconButton @@ -38,8 +37,6 @@ def __init__(self, master: Tabs, editor: Editor, *args, **kwargs) -> None: self.bind("", self.on_hover) self.bind("", self.off_hover) - self.content_hash = "" - def close(self, *_) -> None: self.master.close_tab(self) @@ -78,14 +75,3 @@ def select(self, *_) -> None: self.apply_color(self.hbg) self.closebtn.config(activeforeground=self.hfg) self.selected = True - self.content_hash = self.calculate_content_hash() - - def calculate_content_hash(self): - """ Calculate the hash of the editor content """ - - if self.editor.content: # Welcome editor returns a None value - - # Cannot get contents when using self.editor.content.get(..), - # so had to directly call get() - editor_contents = self.editor.content.text.get(index1="1.0", index2="end-1c") - return md5(editor_contents.encode()).hexdigest() diff --git a/biscuit/core/layout/base/content/editors/tabs.py b/biscuit/core/layout/base/content/editors/tabs.py index cfaf7e82..f7e19b8c 100644 --- a/biscuit/core/layout/base/content/editors/tabs.py +++ b/biscuit/core/layout/base/content/editors/tabs.py @@ -34,12 +34,15 @@ def close_active_tab(self) -> None: self.close_tab(self.active_tab) def close_tab(self, tab: Tab) -> None: - - if self.content_has_changed(): - if askyesno("Save File", f"You have unsaved changes. Do you want to save {tab.editor.filename}"): - filepath = os.path.join(self.base.active_directory, tab.editor.filename) - tab.editor.save(filepath) - print(f"Saved changes to {filepath}.") + if e := tab.editor: + # checking if its a text editor + 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 {tab.editor.filename}"): + if e.exists: + e.save() + else: + self.base.commands.save_as() + print(f"Saved changes to {e.path}.") try: i = self.tabs.index(tab) @@ -96,11 +99,3 @@ def switch_tabs(self, path) -> None: if tab.editor.path == path: tab.select() return tab.editor - - def content_has_changed(self): - current_file_hash = self.active_tab.calculate_content_hash() - if current_file_hash == self.active_tab.content_hash: - # No changes has been made in the editor - return False - else: - return True \ No newline at end of file From 30a251c422a574430a0ac6a42b2ed430fa920641 Mon Sep 17 00:00:00 2001 From: Billy Date: Sun, 14 Apr 2024 01:10:21 +0530 Subject: [PATCH 3/3] feat: Check for unsaved changes on app destroyed event --- biscuit/core/commands.py | 1 + biscuit/core/gui.py | 5 +++++ .../layout/base/content/editors/__init__.py | 12 ++++++++++-- .../core/layout/base/content/editors/tabs.py | 18 ++++++++++-------- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/biscuit/core/commands.py b/biscuit/core/commands.py index edd3c047..d30ab00d 100644 --- a/biscuit/core/commands.py +++ b/biscuit/core/commands.py @@ -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: diff --git a/biscuit/core/gui.py b/biscuit/core/gui.py index d5c6db28..0ae4cb95 100644 --- a/biscuit/core/gui.py +++ b/biscuit/core/gui.py @@ -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 @@ -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: diff --git a/biscuit/core/layout/base/content/editors/__init__.py b/biscuit/core/layout/base/content/editors/__init__.py index f9705697..9ebef994 100644 --- a/biscuit/core/layout/base/content/editors/__init__.py +++ b/biscuit/core/layout/base/content/editors/__init__.py @@ -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 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) @@ -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() @@ -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() diff --git a/biscuit/core/layout/base/content/editors/tabs.py b/biscuit/core/layout/base/content/editors/tabs.py index f7e19b8c..c659e5c1 100644 --- a/biscuit/core/layout/base/content/editors/tabs.py +++ b/biscuit/core/layout/base/content/editors/tabs.py @@ -32,17 +32,19 @@ 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: - # checking if its a text editor - 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 {tab.editor.filename}"): - if e.exists: - e.save() - else: - self.base.commands.save_as() - print(f"Saved changes to {e.path}.") + self.save_unsaved_changes(e) try: i = self.tabs.index(tab)