From 5781010b288f41b2952e0337a7fc5b9663b3a393 Mon Sep 17 00:00:00 2001 From: George Ciesinski Date: Thu, 16 Jan 2020 13:45:38 -0500 Subject: [PATCH 1/6] Created Config directory and file. --- Config/config.ini | 10 ++++++++++ Settings.py | 7 +++++++ app.py | 12 ++++++++++++ glib.py | 3 +-- 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 Config/config.ini create mode 100644 Settings.py diff --git a/Config/config.ini b/Config/config.ini new file mode 100644 index 0000000..60096b1 --- /dev/null +++ b/Config/config.ini @@ -0,0 +1,10 @@ +[DEFAULT] +# Tracks key strokes saved history +ShortcutsUsed = 0 +ShortcutChars = 0 +TextblockChars = 0 + +[DIRECTORIES] +DefaultDirectory = Textblocks/ +LocalDirectory = None +RemoteDirectory = None \ No newline at end of file diff --git a/Settings.py b/Settings.py new file mode 100644 index 0000000..cbee1b9 --- /dev/null +++ b/Settings.py @@ -0,0 +1,7 @@ +import configparser + + +class Setup: + + def __init__(self): + pass \ No newline at end of file diff --git a/app.py b/app.py index ae1327f..850e086 100644 --- a/app.py +++ b/app.py @@ -1,15 +1,27 @@ import glib +import Settings from Logger import Logger from TextController import WordCatcher, KeyboardEmulator if __name__ == "__main__": + """ + Initialize Logger + """ + # Initialize Logger L = Logger() L.log.debug("Program started from App.py.") + """ + Configure Settings + """ + + s = Settings() + + """ Initialize Text Controller """ diff --git a/glib.py b/glib.py index ab2b494..6fbd751 100644 --- a/glib.py +++ b/glib.py @@ -1,6 +1,5 @@ """ -George's Library -Contains non-class methods for this program. +Contains non-class or miscellaneous methods """ import os From f8d858f0882634d1fa1c0ec5943611ee5f561d30 Mon Sep 17 00:00:00 2001 From: George Ciesinski Date: Thu, 16 Jan 2020 14:35:03 -0500 Subject: [PATCH 2/6] Added config creation method to program --- Config/config.ini | 15 ++++++----- Settings.py | 68 +++++++++++++++++++++++++++++++++++++++++++++-- app.py | 5 ++-- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/Config/config.ini b/Config/config.ini index 60096b1..fe7e3cb 100644 --- a/Config/config.ini +++ b/Config/config.ini @@ -1,10 +1,11 @@ [DEFAULT] -# Tracks key strokes saved history -ShortcutsUsed = 0 -ShortcutChars = 0 -TextblockChars = 0 +; tracks key strokes saved history +shortcutsused = 0 +shortcutchars = 0 +textblockchars = 0 [DIRECTORIES] -DefaultDirectory = Textblocks/ -LocalDirectory = None -RemoteDirectory = None \ No newline at end of file +defaultdirectory = Textblocks / +localdirectory = None +remotedirectory = None + diff --git a/Settings.py b/Settings.py index cbee1b9..7d4a101 100644 --- a/Settings.py +++ b/Settings.py @@ -1,7 +1,71 @@ import configparser +from Logger import Logger +from os import path class Setup: - def __init__(self): - pass \ No newline at end of file + def __init__(self, log): + + # Creates instance wide log variable + self.log = log.log + + # Creates instance of ConfigParser + self.config = configparser.ConfigParser(allow_no_value=True) + + # Config Directory + self.config_dir = 'Config/config.ini' + + self.log.debug("Setup initialized successfully.") + + def config_exists(self): + """ + Checks if Config file exists + """ + + if path.exists("Config/config.ini"): + + self.log.debug("Config file found.") + + else: + + self.log.debug("No config file exists. Creating new config file.") + + # Creates new config file + self.create_config() + + def create_config(self): + """ + Creates a new config file + """ + + self.config['DEFAULT'] = {} + self.config.set('DEFAULT', '; Tracks key strokes saved history') + self.config.set('DEFAULT', 'shortcutsused', 0) + self.config.set('DEFAULT', 'shortcutchars', 0) + self.config.set('DEFAULT', 'textblockchars', 0) + self.config['DIRECTORIES'] = { + 'defaultdirectory': 'Textblocks /', + 'localdirectory': 'None', + 'remotedirectory': 'None' + } + with open(self.config_dir, 'w') as configfile: + self.config.write(configfile) + + self.log.debug(f"{self.config_dir} file created successfully.") + + def find_directories(self): + + self.config.read(self.config_dir) + + +if __name__ == "__main__": + + # Initialize Logger + L = Logger() + + L.log.debug("Program started from Settings.py.") + + s = Setup(L) + + s.config_exists() \ No newline at end of file diff --git a/app.py b/app.py index 850e086..bbcf004 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ import glib -import Settings +from Settings import Setup from Logger import Logger from TextController import WordCatcher, KeyboardEmulator @@ -19,8 +19,7 @@ Configure Settings """ - s = Settings() - + s = Setup(L) """ Initialize Text Controller From 9e64797bbf32dfe81def75fc04868d0e4f070ece Mon Sep 17 00:00:00 2001 From: George Ciesinski Date: Thu, 16 Jan 2020 15:32:05 -0500 Subject: [PATCH 3/6] Adding history functionality, not working yet --- Config/config.ini | 4 ++-- Settings.py | 38 +++++++++++++++++++++++++++++++------- TextController.py | 7 +++++++ app.py | 8 +++++--- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/Config/config.ini b/Config/config.ini index fe7e3cb..ea0994d 100644 --- a/Config/config.ini +++ b/Config/config.ini @@ -1,11 +1,11 @@ -[DEFAULT] +[HISTORY] ; tracks key strokes saved history shortcutsused = 0 shortcutchars = 0 textblockchars = 0 [DIRECTORIES] -defaultdirectory = Textblocks / +defaultdirectory = Textblocks/ localdirectory = None remotedirectory = None diff --git a/Settings.py b/Settings.py index 7d4a101..e538760 100644 --- a/Settings.py +++ b/Settings.py @@ -39,13 +39,13 @@ def create_config(self): Creates a new config file """ - self.config['DEFAULT'] = {} - self.config.set('DEFAULT', '; Tracks key strokes saved history') - self.config.set('DEFAULT', 'shortcutsused', 0) - self.config.set('DEFAULT', 'shortcutchars', 0) - self.config.set('DEFAULT', 'textblockchars', 0) + self.config['HISTORY'] = {} + self.config.set('HISTORY', '; Tracks key strokes saved history') + self.config.set('HISTORY', 'shortcutsused', 0) + self.config.set('HISTORY', 'shortcutchars', 0) + self.config.set('HISTORY', 'textblockchars', 0) self.config['DIRECTORIES'] = { - 'defaultdirectory': 'Textblocks /', + 'defaultdirectory': 'Textblocks/', 'localdirectory': 'None', 'remotedirectory': 'None' } @@ -55,8 +55,32 @@ def create_config(self): self.log.debug(f"{self.config_dir} file created successfully.") def find_directories(self): + """ + Finds the directories in the config file + """ + + self.config.read(self.config_dir) + default_directory = self.config['DIRECTORIES']['defaultdirectory'] + + return default_directory + + def update_history(self, shortcut, textblock): self.config.read(self.config_dir) + shortcuts_used = int(self.config['HISTORY']['shortcutsused']) + shortcut_chars = int(self.config['HISTORY']['shortcutchars']) + textblock_chars = int(self.config['HISTORY']['textblockchars']) + + shortcuts_used += 1 + shortcut_chars += len(shortcut) + textblock_chars += len(textblock) + + self.config.set('HISTORY', 'shortcutsused', shortcuts_used) + self.config.set('HISTORY', 'shortcutchars', shortcut_chars) + self.config.set('HISTORY', 'textblockchars', textblock_chars) + + with open(self.config_dir, 'wb') as configfile: + self.config.write(configfile) if __name__ == "__main__": @@ -68,4 +92,4 @@ def find_directories(self): s = Setup(L) - s.config_exists() \ No newline at end of file + s.config_exists() diff --git a/TextController.py b/TextController.py index c11a810..c8dfcff 100644 --- a/TextController.py +++ b/TextController.py @@ -2,6 +2,7 @@ from pynput.keyboard import Controller, Key, Listener import pyperclip from Logger import Logger +from Settings import Setup # Class catches individual words as they are typed @@ -12,6 +13,9 @@ def __init__(self, log, keyboard, shortcut_list, file_dir_list): # Creates instance wide log variable self.log = log.log + # Creates instance wide Setup + self.s = Setup(log) + # Creates instance wide keyboard variable self.keyboard = keyboard @@ -173,6 +177,9 @@ def check_shortcut(self): # Deletes the typed out shortcut self.keyboard.delete_shortcut(self.current_word) + # Update history + self.s.update_history(self.current_word, self.textblock) + # Passes the textbox to the keyboard self.keyboard.paste_block(self.textblock) diff --git a/app.py b/app.py index bbcf004..9d0f22d 100644 --- a/app.py +++ b/app.py @@ -21,13 +21,15 @@ s = Setup(L) + s.config_exists() + + # Gets file_list and file_dir_list + textblock_dir = s.find_directories() + """ Initialize Text Controller """ - # Gets file_list and file_dir_list - textblock_dir = "Textblocks/" - file_list, file_dir_list = glib.list_files(textblock_dir) # Creates shortcut list with the same index From 3160187d22f3c2bd8ab43abbfdd46ec03fa462f5 Mon Sep 17 00:00:00 2001 From: George Ciesinski Date: Fri, 17 Jan 2020 15:06:34 -0500 Subject: [PATCH 4/6] Program updates config history section with statistics. --- Config/config.ini | 4 +++- Settings.py | 54 +++++++++++++++++++++++++++++++++++++++++------ TextController.py | 15 ++++++++----- app.py | 13 ++++++++---- 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/Config/config.ini b/Config/config.ini index ea0994d..eca1ac4 100644 --- a/Config/config.ini +++ b/Config/config.ini @@ -1,5 +1,7 @@ +[TEXTSCRIPT] +version = 1.1.0 + [HISTORY] -; tracks key strokes saved history shortcutsused = 0 shortcutchars = 0 textblockchars = 0 diff --git a/Settings.py b/Settings.py index e538760..f69575c 100644 --- a/Settings.py +++ b/Settings.py @@ -5,12 +5,19 @@ class Setup: - def __init__(self, log): + # TODO: Create check to ensure config file has the right categories and values - # Creates instance wide log variable + def __init__(self, log, text_script_version): + + # Creates instance of current version variable + self.version = text_script_version + + # TODO: Check if version has changed, update config + + # Creates instance wide log object self.log = log.log - # Creates instance of ConfigParser + # Creates instance of ConfigParser object self.config = configparser.ConfigParser(allow_no_value=True) # Config Directory @@ -39,16 +46,22 @@ def create_config(self): Creates a new config file """ + self.config['TEXTSCRIPT'] = { + 'version': self.version + } + self.config['HISTORY'] = {} self.config.set('HISTORY', '; Tracks key strokes saved history') self.config.set('HISTORY', 'shortcutsused', 0) self.config.set('HISTORY', 'shortcutchars', 0) self.config.set('HISTORY', 'textblockchars', 0) + self.config['DIRECTORIES'] = { 'defaultdirectory': 'Textblocks/', 'localdirectory': 'None', 'remotedirectory': 'None' } + with open(self.config_dir, 'w') as configfile: self.config.write(configfile) @@ -64,22 +77,49 @@ def find_directories(self): return default_directory + +class UpdateConfig: + + def __init__(self, log): + + # Creates instance wide log variable + self.log = log.log + + # Creates instance of ConfigParser + self.config = configparser.ConfigParser(allow_no_value=True) + + # Config Directory + self.config_dir = 'Config/config.ini' + + self.log.debug("Setup initialized successfully.") + def update_history(self, shortcut, textblock): + """ + Updates the shortcuts used, total shortcut characters typed, and total textblock characters pasted + """ + # Read config file for the shortcuts used, shortcut characters, and textblock characters self.config.read(self.config_dir) shortcuts_used = int(self.config['HISTORY']['shortcutsused']) shortcut_chars = int(self.config['HISTORY']['shortcutchars']) textblock_chars = int(self.config['HISTORY']['textblockchars']) + # Increase shortcuts used by 1 shortcuts_used += 1 + + # Increase shortcut characters by the length of the current shortcut shortcut_chars += len(shortcut) + + # Increase textblock characters by the length of the current textblock textblock_chars += len(textblock) - self.config.set('HISTORY', 'shortcutsused', shortcuts_used) - self.config.set('HISTORY', 'shortcutchars', shortcut_chars) - self.config.set('HISTORY', 'textblockchars', textblock_chars) + # Update the config categories with the updated data + self.config.set('HISTORY', 'shortcutsused', str(shortcuts_used)) + self.config.set('HISTORY', 'shortcutchars', str(shortcut_chars)) + self.config.set('HISTORY', 'textblockchars', str(textblock_chars)) - with open(self.config_dir, 'wb') as configfile: + # Write to the config file + with open(self.config_dir, 'w') as configfile: self.config.write(configfile) diff --git a/TextController.py b/TextController.py index c8dfcff..da8acfe 100644 --- a/TextController.py +++ b/TextController.py @@ -2,7 +2,7 @@ from pynput.keyboard import Controller, Key, Listener import pyperclip from Logger import Logger -from Settings import Setup +from Settings import UpdateConfig # Class catches individual words as they are typed @@ -10,11 +10,11 @@ class WordCatcher: def __init__(self, log, keyboard, shortcut_list, file_dir_list): - # Creates instance wide log variable + # Creates instance wide log object self.log = log.log - # Creates instance wide Setup - self.s = Setup(log) + # Creates instance wide UpdateConfig object + self.update = UpdateConfig(log) # Creates instance wide keyboard variable self.keyboard = keyboard @@ -178,7 +178,7 @@ def check_shortcut(self): self.keyboard.delete_shortcut(self.current_word) # Update history - self.s.update_history(self.current_word, self.textblock) + self.update.update_history(self.current_word, self.textblock) # Passes the textbox to the keyboard self.keyboard.paste_block(self.textblock) @@ -275,9 +275,14 @@ def paste_block(self, textblock): paste_block copies the textblock into the clipboard and pastes it using pyinput controller. """ + # TODO: Determine why pyperclip can't save clipboard item and paste back into clipboard later + try: pyperclip.copy(textblock) + # TODO: Look up Pyperclip documentation for OSX & Linux implementation + + # TODO: Test pyperclip.paste() self.c.press(Key.ctrl_l) self.c.press('v') self.c.release(Key.ctrl_l) diff --git a/app.py b/app.py index 9d0f22d..c8b2d66 100644 --- a/app.py +++ b/app.py @@ -6,6 +6,9 @@ if __name__ == "__main__": + # Current app version / / Ensure this is correct during updates + text_script_version = "1.1.0" + """ Initialize Logger """ @@ -13,18 +16,20 @@ # Initialize Logger L = Logger() - L.log.debug("Program started from App.py.") + L.log.debug(f"Program started from App.py. Version: {text_script_version}") """ Configure Settings """ - s = Setup(L) + # Initialize setup + setup = Setup(L, text_script_version) - s.config_exists() + # Check if config file exists + setup.config_exists() # Gets file_list and file_dir_list - textblock_dir = s.find_directories() + textblock_dir = setup.find_directories() """ Initialize Text Controller From 3d9cd6131d65c6e6f379c3cc455042187d81f746 Mon Sep 17 00:00:00 2001 From: George Ciesinski Date: Fri, 17 Jan 2020 15:24:40 -0500 Subject: [PATCH 5/6] Updated changelog, created command list, modified app.py. --- CHANGELOG.md | 4 ++++ Config/config.ini | 6 +++--- TextController.py | 7 +++++++ Textblocks/Examples/#example.txt | Bin 0 -> 256 bytes Textblocks/Examples/#help.txt | 13 ------------- app.py | 22 ++++++++++++++++------ 6 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 Textblocks/Examples/#example.txt delete mode 100644 Textblocks/Examples/#help.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 00de265..f17427b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.0] - 2020-01-17 +### Added +- Statistics tracking for shortcuts used, total shortcut characters typed, and total textblock characters pasted + ## [1.0.0] - 2020-01-16 ### Added - Debugging print lines removed diff --git a/Config/config.ini b/Config/config.ini index eca1ac4..bb472df 100644 --- a/Config/config.ini +++ b/Config/config.ini @@ -2,9 +2,9 @@ version = 1.1.0 [HISTORY] -shortcutsused = 0 -shortcutchars = 0 -textblockchars = 0 +shortcutsused = 2 +shortcutchars = 13 +textblockchars = 692 [DIRECTORIES] defaultdirectory = Textblocks/ diff --git a/TextController.py b/TextController.py index da8acfe..2d7dc88 100644 --- a/TextController.py +++ b/TextController.py @@ -19,6 +19,13 @@ def __init__(self, log, keyboard, shortcut_list, file_dir_list): # Creates instance wide keyboard variable self.keyboard = keyboard + # List of Text-Script commands + self.commands = [ + "help", + "#exit", + "reload" + ] + # Creates instance wide shortcut_list & file_dir_list self.shortcut_list = shortcut_list self.file_dir_list = file_dir_list diff --git a/Textblocks/Examples/#example.txt b/Textblocks/Examples/#example.txt new file mode 100644 index 0000000000000000000000000000000000000000..2d86aa100e9f4c1e50cab42e09f9e6f1aba36984 GIT binary patch literal 256 zcmYk0Q4Yc&5JczO#5-^R<7s*UP#Q_K7@($JUVW?@{pe;pv%Hz-W6()SRqivrX7;F6 zUKd?;W_QXOnVzqi*A#HF4w}$L-uAma`;Yfk9h`-Ph8v7lA>G)HDBiLcQWLa{7PT{d sHorIUIPdFZ?HZkXtAV+~{qIs>WXsAb+*Gmyq{Lr@({PLKblqyB7jvpE@Bjb+ literal 0 HcmV?d00001 diff --git a/Textblocks/Examples/#help.txt b/Textblocks/Examples/#help.txt deleted file mode 100644 index 3d2dd58..0000000 --- a/Textblocks/Examples/#help.txt +++ /dev/null @@ -1,13 +0,0 @@ -Help Menu: - -How to make a shortcut: - -1. Navigate to the program folder, and go to the Textblocks folder -2. Either navigate to an existing folder in Textblocks, or create a new one -3. Create a new text file here. The naming convention is #____.txt where ____ is the shortcut you will type -4. Open the text file and put your text block / signature / template in here -5. Click "Save As" and select the same text file, but change encoding to unicode - -Note: Other formats may still work, but this is designed to read unicode text files. - -To exit Text-Script, type: #exit diff --git a/app.py b/app.py index c8b2d66..fe64ceb 100644 --- a/app.py +++ b/app.py @@ -4,6 +4,21 @@ from TextController import WordCatcher, KeyboardEmulator +def shortcut_setup(): + """ + Creates shortcut_list and file_dir_list + """ + + file_list, file_dir_list = glib.list_files(textblock_dir) + + # Creates shortcut list with the same index + shortcut_list = glib.list_shortcuts(file_list) + + glib.print_shortcuts(file_dir_list, shortcut_list) + + return shortcut_list, file_dir_list + + if __name__ == "__main__": # Current app version / / Ensure this is correct during updates @@ -35,12 +50,7 @@ Initialize Text Controller """ - file_list, file_dir_list = glib.list_files(textblock_dir) - - # Creates shortcut list with the same index - shortcut_list = glib.list_shortcuts(file_list) - - glib.print_shortcuts(file_dir_list, shortcut_list) + shortcut_list, file_dir_list = shortcut_setup() # Initializes KeyboardEmulator instance k = KeyboardEmulator(L) From de295829cd8a6ba7915f5739eafd3db75cf1b0d4 Mon Sep 17 00:00:00 2001 From: George Ciesinski Date: Fri, 17 Jan 2020 15:46:05 -0500 Subject: [PATCH 6/6] Updated icon, added new commands, updated changelog and readme. --- CHANGELOG.md | 1 + README.md | 4 +++ TextController.py | 80 ++++++++++++++++++++++++++++++++++++++-------- textscript.ico | Bin 19986 -> 18989 bytes 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f17427b..3298675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.0] - 2020-01-17 ### Added - Statistics tracking for shortcuts used, total shortcut characters typed, and total textblock characters pasted +- Help and Exit commands have been added ## [1.0.0] - 2020-01-16 ### Added diff --git a/README.md b/README.md index 60d8f86..bd12f18 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # text-script An app that runs in the background and replaces text shortcuts with pre-saved blocks of text. + +## Planned future updates +- Save whatever the latest clipboard item is and replace it after shortcut is used. Currently the last textblock used fills the last clipboard spot +- Create #reload command which reloads the shortcut list with all the latest updates. \ No newline at end of file diff --git a/TextController.py b/TextController.py index 2d7dc88..a8c7b70 100644 --- a/TextController.py +++ b/TextController.py @@ -21,9 +21,9 @@ def __init__(self, log, keyboard, shortcut_list, file_dir_list): # List of Text-Script commands self.commands = [ - "help", + "#help", "#exit", - "reload" + "#reload" ] # Creates instance wide shortcut_list & file_dir_list @@ -46,6 +46,7 @@ def __init__(self, log, keyboard, shortcut_list, file_dir_list): self.log.debug("WordCatcher initialized.") + # TODO: Look into self.listener.join and why this is even working. Might need to be changed. # Start self.listener with Listener(on_press=self.word_builder) as self.listener: self.listener.join() @@ -159,20 +160,12 @@ def check_shortcut(self): Checks list of shortcuts for a match. Sets text block if match is found. """ - # Exit program if user typed in #exit - if self.current_word == "#exit": - - exit_text = "Text-Script exited." - - self.keyboard.delete_shortcut(self.current_word) + # If shortcut is in command list, determine which command was used + if self.current_word in self.commands: - self.keyboard.paste_block(exit_text) - - self.log.debug("The user has typed #exit. Exiting program.") - - # Close the program with no error - sys.exit(0) + self.determine_command() + # If shortcut is in shortcut_list, determine which shortcut was used if self.current_word in self.shortcut_list: # Finds index of self.current_word on shortcut list @@ -190,6 +183,65 @@ def check_shortcut(self): # Passes the textbox to the keyboard self.keyboard.paste_block(self.textblock) + def determine_command(self): + + # Exit program if user typed in #exit + if self.current_word == "#exit": + + self.log.debug("The user has typed #exit. Exiting program.") + self.exit_program() + + # Paste help menu if user typed in #help + elif self.current_word == "#help": + + self.log.debug("The user has typed in #help. Pasting help menu.") + self.help_menu() + + elif self.current_word == "#reload": + + self.log.debug("The user has typed in #reload. Reloading shortcut_list and file_dir_list.") + pass + + def exit_program(self): + """ + Exits the program + """ + + exit_text = "Text-Script exited." + + self.keyboard.delete_shortcut(self.current_word) + + self.keyboard.paste_block(exit_text) + + # Close the program with no error + sys.exit(0) + + def help_menu(self): + + help_text = """Help Menu: + +How to make a shortcut: + +1. Navigate to the program folder, and go to the Textblocks folder +2. Either navigate to an existing folder in Textblocks, or create a new one +3. Create a new text file here. The naming convention is #____.txt where ____ is the shortcut you will type +4. Open the text file and put your text block / signature / template in here +5. Click "Save As" and select the same text file, but change encoding to unicode + +Note: Other formats may still work, but this is designed to read unicode text files. + +To exit Text-Script, type: #exit +""" + + self.keyboard.delete_shortcut(self.current_word) + + self.keyboard.paste_block(help_text) + + def update_shortcuts(self): + + # TODO: Create update_shortcuts method + pass + def find_file_directory(self, index): """ Finds the directory of the Textblock file. diff --git a/textscript.ico b/textscript.ico index 4f9f00b9e365e114e9d6985d394abf4f50f200e2..d04d1642827d6d8bbb28f3f77e1026119dfa79df 100644 GIT binary patch literal 18989 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 literal 19986 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