diff --git a/Encryptext (DEBUG).exe b/Encryptext (DEBUG).exe deleted file mode 100644 index 618a64b..0000000 Binary files a/Encryptext (DEBUG).exe and /dev/null differ diff --git a/Encryptext.pyw b/Encryptext.pyw index 3eb1f9e..797f5aa 100644 --- a/Encryptext.pyw +++ b/Encryptext.pyw @@ -7,9 +7,12 @@ import tkinter as tk from tkinter import filedialog from tkinter import messagebox from tkinter import colorchooser -import traceback -import webbrowser -from cryptography.fernet import Fernet +from traceback import format_exc, print_exc # For the error messages when in exe form +from webbrowser import open_new # For opening the help page +from cryptography.fernet import Fernet # For encryption features +# For Markdown preview features +import tkinterweb +from markdown import markdown # Used for getting files when using one-file mode .exe format def getTrueFilename(filename): @@ -19,13 +22,29 @@ def getTrueFilename(filename): base = abspath(".") return join(base, filename) - """ Window Settings """ # Create the window root = tk.Tk("Encryptext") +def previewWindowCreation(hidden=False, add_frame=True): + global md_preview_window, frame + + md_preview_window = tk.Tk("Preview") + if hidden: + md_preview_window.withdraw() + md_preview_window.title("Preview") + md_preview_window.geometry("800x500") + md_preview_window.iconbitmap(getTrueFilename("app_icon.ico")) + + if add_frame: + frame = tkinterweb.HtmlFrame(md_preview_window, messages_enabled=False) + frame.load_html(markdown(textbox.get("1.0", tk.END))) + frame.pack(fill="both", expand=True) + md_preview_window.bind_all("", updatePreview) + md_preview_window.bind_all("", closePreview) + # Rename the window root.title("Encryptext") # Resize the window (manually resizable too) @@ -39,6 +58,7 @@ Variables debug = False save_location = "" +file_extension = "" font_size = 11 font_type = "Arial" @@ -51,18 +71,19 @@ format_separator = ''# FORMAT SEPARATOR HERE# FORMAT STRING HERE format_string = ''# FORMAT STRING HERE supported_file_types = [ - ("Encryptext", "*.etx"), - ("Plain Text", "*.txt"), - ("Python", "*.py"), - ("HTML", "*.html"), - ("CSS", "*.css"), - ("JavaScript", "*.js"), - ("JSON", "*.json"), - ("Markdown", "*.md"), - ("Comma-Separated Values", "*.csv"), + ("Accepted Files", "*.etx *.md *.txt *.py *.html *.css *.js *.json *.csv *.ini *.log"), + ("Encrypted File", "*.etx"), + ("Markdown File", "*.md"), + ("Plain Text File", "*.txt"), + ("Python File", "*.py"), + ("HTML File", "*.html"), + ("CSS File", "*.css"), + ("JavaScript File", "*.js"), + ("JSON File", "*.json"), + ("Comma-Separated Values File", "*.csv"), ("Windows Initialization File", "*.ini"), ("Log File", "*.log"), - ("All Files", "*.*"), + ("All Files", "*.*") ] # ENCRYPTION KEY HERE encrypt_key = b''# ENCRYPTION KEY HERE @@ -84,7 +105,6 @@ tag_no = 0 Functions """ - def updateTags(): global tags @@ -108,14 +128,23 @@ def quitApp(Event=None): if len(textbox.get("1.0", tk.END)) != 1: quit_confirm = messagebox.askyesno("Quit", "Quit Encryptext?\n\nAny unsaved changes will be lost.") if quit_confirm: - root.destroy() + try: + md_preview_window.destroy() + pref_window.destroy() + finally: + root.destroy() + sys.exit() else: - root.destroy() - + try: + md_preview_window.destroy() + pref_window.destroy() + finally: + root.destroy() + sys.exit() def openFile(Event=None, current=False): # Make save_location global to change it for the whole program - global save_location, tags, history, current_version, tag_no + global save_location, tags, history, current_version, tag_no, file_extension # Reset tag number tag_no = 0 @@ -240,8 +269,8 @@ def openFile(Event=None, current=False): tags.append(format) except Exception as e: if debug: - messagebox.showerror("Error Opening File", traceback.format_exc()) - traceback.print_exc() + messagebox.showerror("Error Opening File", format_exc()) + print_exc() else: messagebox.showerror("Error Opening File", f"Access denied.\nPlease use the Encryptext program that you used to write this file to open it correctly.") else: @@ -250,7 +279,13 @@ def openFile(Event=None, current=False): # Close the file file.close() - + if file_extension == "md": + global md_preview_window + try: + md_preview_window.deiconify() + updatePreview() + except: + previewWindowCreation() def newFile(Event=None): global save_location, history, current_version @@ -267,8 +302,7 @@ def newFile(Event=None): current_version = 1 title.set("Untitled") - else: - pass + else: pass else: save_location = "" textbox.config(state=tk.NORMAL) @@ -279,7 +313,6 @@ def newFile(Event=None): title.set("Untitled") - def saveFile(Event=None): # If it's a new file if save_location == "": @@ -306,7 +339,6 @@ def saveFile(Event=None): file.write(text) file.close() - def saveFileAs(Event=None): global save_location @@ -338,7 +370,6 @@ def saveFileAs(Event=None): # Open the new file openFile(current=True) - def undo(Event=None): global history @@ -366,7 +397,6 @@ def undo(Event=None): textbox.delete("1.0", tk.END) textbox.insert(tk.END, history[current_version]) - def redo(Event=None): global history global current_version @@ -397,41 +427,49 @@ def redo(Event=None): textbox.delete("1.0", tk.END) textbox.insert(tk.END, history[current_version]) +def updatePreview(Event=None): + try: + # Update the preview + frame.load_html(markdown(textbox.get("1.0", tk.END))) + except: + # Only update it if it's markdown or none + if file_extension == "md": + # If the preview window was opened manually + previewWindowCreation() def trackChanges(Event=None): - global history - global current_version + if Event.keysym in ["space", "Return"]: + global history, current_version - # Check if the first version is empty - if history[0] != "": - if current_version < max_history: - # Add a new version - history.insert(0, "") - # Update the current version - current_version += 1 + # Check if the first version is empty + if history[0] != "": + if current_version < max_history: + # Add a new version + history.insert(0, "") + # Update the current version + current_version += 1 - # Shift every version down one - for i in range(0, len(history) - 1): - history[i] = history[i + 1] + # Shift every version down one + for i in range(0, len(history) - 1): + history[i] = history[i + 1] - # Update the current version - history[current_version] = textbox.get("1.0", tk.END) - # Remove the last newline character - history[current_version] = history[current_version][:-1] + # Update the current version + history[current_version] = textbox.get("1.0", tk.END) + # Remove the last newline character + history[current_version] = history[current_version][:-1] + # Update the preview + updatePreview() def cut(Event=None): textbox.event_generate("<>") - def copy(Event=None): textbox.event_generate("<>") - def paste(Event=None): textbox.event_generate("<>") - def viewFile(Event=None): # Open the file openFile() @@ -439,32 +477,47 @@ def viewFile(Event=None): # Set the textbox to be read-only textbox.config(state=tk.DISABLED) - def viewingMode(Event=None): # Set the textbox to be read-only textbox.config(state=tk.DISABLED) - def editingMode(Event=None): # Set the textbox to be writable textbox.config(state=tk.NORMAL) +def openPreview(Event=None): + try: + md_preview_window.deiconify() + except: + previewWindowCreation() + +def closePreview(Event=None): + try: + md_preview_window.withdraw() + except: pass def select_all(Event=None): textbox.event_generate("<>") - def deselect_all(Event=None): textbox.event_generate("<>") +def openPreferences(): + global pref_window, frame + + pref_window = tk.Tk("Preferences") + pref_window.title("Preferences") + pref_window.geometry("500x600") + pref_window.iconbitmap(getTrueFilename("app_icon.ico")) + pref_window.protocol("WM_DELETE_WINDOW", pref_window.destroy) + + pref_window.mainloop() def about_menu(Event=None): messagebox.showinfo("About Encryptext", "Encryptext can do what Notepad does, and more. You can edit, format, and encrypt files securely, while also editing regular files with ease.\n\n Free for everyone. Forever. ❤") - def documentation(Event=None): - webbrowser.open_new("https://github.com/WhenLifeHandsYouLemons/Encryptext") - + open_new("https://github.com/WhenLifeHandsYouLemons/Encryptext") def bold_text_style(Event=None): global tag_no @@ -479,7 +532,6 @@ def bold_text_style(Event=None): tag_no += 1 - def italic_text_style(Event=None): global tag_no # Get the position of current selection @@ -493,7 +545,6 @@ def italic_text_style(Event=None): tag_no += 1 - def normal_text_style(Event=None): global tag_no # Get the position of current selection @@ -507,7 +558,6 @@ def normal_text_style(Event=None): tag_no += 1 - def text_colour_change(Event=None): global tag_no colour_code = colorchooser.askcolor(title="Choose a colour") @@ -525,7 +575,6 @@ def text_colour_change(Event=None): tag_no += 1 - def increase_font(Event=None): global tag_no global font_size @@ -545,7 +594,6 @@ def increase_font(Event=None): tag_no += 1 - def decrease_font(Event=None): global tag_no global font_size @@ -565,6 +613,28 @@ def decrease_font(Event=None): tag_no += 1 +""" +Window Items +""" +title = tk.StringVar() +title.set("Untitled") +title_of_file = tk.Label(textvariable=title, font=("Arial", 18, "bold"), anchor="center") +title_of_file.pack(side=tk.TOP, fill=tk.X) + +textbox = tk.Text(root, state=tk.NORMAL, font=(font_type, font_size, "normal")) + +scroll_bar_vertical = tk.Scrollbar(root, orient=tk.VERTICAL) +scroll_bar_vertical.pack(side=tk.RIGHT, fill=tk.Y) +scroll_bar_vertical.config(command=textbox.yview) + +textbox.config(yscrollcommand=scroll_bar_vertical.set) +textbox.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) + +# To make it more seamless +# The preview window was the focused one before +root.focus_force() + +previewWindowCreation(hidden=True) """ Menu Bar @@ -609,6 +679,7 @@ filemenu.add_separator() filemenu.add_command(label="Exit", accelerator="Ctrl+W", command=quitApp) root.bind_all("", quitApp) +# md_preview_window.bind_all("", closePreview) # Edit menu items editmenu.add_command(label="Undo", accelerator="Ctrl+Z", command=undo) @@ -634,7 +705,20 @@ root.bind_all("", deselect_all) editmenu.add_separator() -editmenu.add_command(label="Edit Preferences") +editmenu.add_command(label="Open Markdown Preview", accelerator="Alt+P", command=openPreview) +root.bind_all("", openPreview) + +editmenu.add_command(label="Close Markdown Preview", accelerator="Alt+Shift+P", command=closePreview) +root.bind_all("", closePreview) +md_preview_window.bind_all("", closePreview) + +editmenu.add_command(label="Update Markdown Preview", accelerator="Ctrl+E", command=updatePreview) +root.bind_all("", updatePreview) +md_preview_window.bind_all("", updatePreview) + +editmenu.add_separator() + +editmenu.add_command(label="Edit Preferences", command=openPreferences) # Format menu items textfontmenu.add_command(label="Arial") @@ -675,42 +759,16 @@ menubar.add_cascade(label="Help", menu=helpmenu) # Display the menu bar root.config(menu=menubar) -root.bind_all("<,>", trackChanges) -root.bind_all("<.>", trackChanges) -root.bind_all("", trackChanges) -root.bind_all("<'>", trackChanges) -root.bind_all('<">', trackChanges) -root.bind_all("", trackChanges) -root.bind_all("<(>", trackChanges) -root.bind_all("<)>", trackChanges) -root.bind_all("<[>", trackChanges) -root.bind_all("<]>", trackChanges) -root.bind_all("<{>", trackChanges) -root.bind_all("<}>", trackChanges) -root.bind_all("", trackChanges) - -""" -Window Items -""" -title = tk.StringVar() -title.set("Untitled") -title_of_file = tk.Label(textvariable=title, font=("Arial", 18, "bold"), anchor="center") -title_of_file.pack(side=tk.TOP, fill=tk.X) - -textbox = tk.Text(root, state=tk.NORMAL, font=(font_type, font_size, "normal")) - -scroll_bar_vertical = tk.Scrollbar(root, orient=tk.VERTICAL) -scroll_bar_vertical.pack(side=tk.RIGHT, fill=tk.Y) -scroll_bar_vertical.config(command=textbox.yview) - -textbox.config(yscrollcommand=scroll_bar_vertical.set) -textbox.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) +# Track document changes and update markdown preview +root.bind('', trackChanges) """ Window Display """ # When closing the app, run the quit_app function root.protocol("WM_DELETE_WINDOW", quitApp) +md_preview_window.protocol("WM_DELETE_WINDOW", md_preview_window.destroy) -# Run the window (display it) +# Display the window +md_preview_window.mainloop() root.mainloop() diff --git a/encryptext_installer_v1.4.0_64bit.exe b/encryptext_installer_v1.5.0_64bit.exe similarity index 97% rename from encryptext_installer_v1.4.0_64bit.exe rename to encryptext_installer_v1.5.0_64bit.exe index 79a9584..f6897e1 100644 Binary files a/encryptext_installer_v1.4.0_64bit.exe and b/encryptext_installer_v1.5.0_64bit.exe differ diff --git a/installer_creator_linux.py b/installer_creator_linux.py new file mode 100644 index 0000000..6d34b06 --- /dev/null +++ b/installer_creator_linux.py @@ -0,0 +1,24 @@ +from os import system +import PyInstaller.__main__ + +# Creates an executable file +PyInstaller.__main__.run([ + 'installer_linux.py', + '--onefile', + '--clean', + '--log-level', + 'FATAL', + '--icon', + 'installer_icon.ico', + '--add-data=app_icon.ico:.', + '--add-data=Encryptext.pyw:.' +]) +# Moves the exe out of the dist folder +system("mv dist/installer_linux encryptext_installer_v0.0.0_linux") + +# Removes the "build" folder +system("rm -rf build") +# Removes the "installer.spec" file +system("rm installer_linux.spec") +# Removes the "dist" folder +system("rm -rf dist") diff --git a/installer_creator.py b/installer_creator_windows.py similarity index 76% rename from installer_creator.py rename to installer_creator_windows.py index ba0ca3c..da09db0 100644 --- a/installer_creator.py +++ b/installer_creator_windows.py @@ -3,7 +3,7 @@ # Creates an executable file PyInstaller.__main__.run([ - 'installer.py', + 'installer_windows.py', '--onefile', '--clean', '--log-level', @@ -16,11 +16,11 @@ 'Encryptext.pyw;.' ]) # Moves the exe out of the dist folder -system("move dist\\installer.exe installer.exe") +system("move dist\\installer_windows.exe encryptext_installer_v0.0.0_64bit.exe") # Removes the "build" folder system("rmdir /s /q build") # Removes the "installer.spec" file -system("del installer.spec") +system("del installer_windows.spec") # Removes the "dist" folder system("rmdir /s /q dist") diff --git a/installer_linux.py b/installer_linux.py new file mode 100644 index 0000000..6dbbb1e --- /dev/null +++ b/installer_linux.py @@ -0,0 +1,165 @@ +from os import devnull, system +from os.path import abspath, join +from sys import _MEIPASS +from time import sleep +from subprocess import check_call +from cryptography.fernet import Fernet as F +from random import choice, randint +from string import ascii_letters, digits +import threading as t + +print("\nStarting installer...") +print("Please wait...") + +# Used for getting files when using one-file mode +def getTrueFilename(filename): + try: + base = _MEIPASS + except Exception: + base = abspath(".") + return join(base, filename) + +# Open the Encryptext.py file and read it into a variable +file = open(getTrueFilename("Encryptext.pyw"), "r", encoding="utf8") +file = file.read() + +# Find where the encryption key is stored in the file +file = file.split("# ENCRYPTION KEY HERE") + +# Create a key and remove the b'' from the string +key = str(F.generate_key()).split("'")[1] + +# Add the key to the file +key_line = file[1] +key_line = key_line.split("'") +key_line[1] = key +key_line = "'".join(key_line) +file[1] = key_line + +text = "".join(file) + +print("Encryption key created!") + +possible_characters = ascii_letters + digits + +# Find where the format item separator string is stored in the file +file = text.split("# FORMAT ITEM SEPARATOR HERE") + +# Create a format item separator string +format_item_separator = "".join([choice(possible_characters) for i in range(randint(15, 45))]) +# Add the format item separator string to the file +key_line = file[1] +key_line = key_line.split("'") +key_line[1] = format_item_separator +key_line = "'".join(key_line) +file[1] = key_line + +text = "".join(file) + +# Find where the format separator string is stored in the file +file = text.split("# FORMAT SEPARATOR HERE") + +# Create a format separator string +format_separator = "".join([choice(possible_characters) for i in range(randint(15, 45))]) + +# Add the format separator string to the file +key_line = file[1] +key_line = key_line.split("'") +key_line[1] = format_separator +key_line = "'".join(key_line) +file[1] = key_line + +text = "".join(file) + +# Find where the format string is stored in the file +file = text.split("# FORMAT STRING HERE") + +# Create a format string +format_string = "".join([choice(possible_characters) for i in range(randint(15, 45))]) + +# Add the format string to the file +key_line = file[1] +key_line = key_line.split("'") +key_line[1] = format_string +key_line = "'".join(key_line) +file[1] = key_line + +text = "".join(file) + +# Write the file back to the Encryptext.py file +file = open(getTrueFilename("Encryptext-User.pyw"), "w", encoding="utf8") +file.write(text) +file.close() + +print("Format strings created!") +print("Creating custom program...\n\n") + +# Creates an executable file +def appCreation(): + file_path = getTrueFilename("Encryptext-User.pyw") + icon_path = getTrueFilename("app_icon.ico") + # https://stackoverflow.com/a/72523249 + # https://stackoverflow.com/a/13790741 + # https://stackoverflow.com/a/8529412 + check_call(["pyinstaller", + "--onefile", + "--clean", + "--windowed", + "--log-level", + "FATAL", + "--icon", + icon_path, + f"--add-data={icon_path}:.", + "--name", + "Encryptext", + file_path + ], + # shell=True, + stdout=open(devnull, 'wb'), + stderr=open(devnull, 'wb')) + +# https://stackoverflow.com/a/34325723 +# Print iterations progress +def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '█', printEnd = "\r"): + percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) + filledLength = int(length * iteration // total) + bar = fill * filledLength + '-' * (length - filledLength) + print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd) + # Print New Line on Complete + if iteration == total: + print() + +# Start thread to create app +app_thread = t.Thread(target=appCreation) +app_thread.start() + +# Show a progress bar while app is compiling +l = 100 +i = 0 +printProgressBar(i, l, prefix='Progress:', suffix='Complete', length=50) +while i < l-25: + sleep(0.5) + i += 1 + printProgressBar(i, l, prefix='Progress:', suffix='Complete', length=50, printEnd='') + +app_thread.join() + +while i < l: + sleep(0.05) + i += 1 + printProgressBar(i, l, prefix='Progress:', suffix='Complete', length=50, printEnd='') + +# Moves the exe out of the dist folder +system("mv dist/Encryptext Encryptext") + +print("\r\n\nCreated program!") +print("Cleaning up...") + +# Removes the "dist" folder +system("rm -rf dist") +# Removes the "build" folder +system("rm -rf build") +# Removes the "Encryptext-User.spec" file +system("rm Encryptext.spec") + +input("\nCompleted! Press enter to finish setup...") diff --git a/installer.py b/installer_windows.py similarity index 100% rename from installer.py rename to installer_windows.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dbc5bde --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +tkinter +traceback +webbrowser +cryptography +tkinterweb +markdown