├── assets ├── logo.ico ├── logo.png ├── brush_dark.png ├── close_dark.png ├── warn_dark.png ├── warn_light.png ├── brush_light.png ├── close_light.png ├── github_dark.png ├── github_light.png ├── update_dark.png ├── update_light.png ├── keyboard_dark.png ├── keyboard_light.png ├── settings_dark.png └── settings_light.png ├── src ├── utils.py ├── themes │ ├── dark.py │ └── light.py ├── update.py ├── vars.py ├── generatesize.py ├── pages │ ├── wanttosave.py │ ├── filetype.py │ └── about.py ├── mdpreview.py ├── settings │ ├── images.py │ └── UI.py ├── config.py ├── main.py ├── tabmanager.py └── editor.py ├── requirements.txt ├── compile.bat ├── docs ├── FEATURES.md ├── CONTRIBUTING.md ├── README.md └── CODE_OF_CONDUCT.md ├── LICENSE └── .gitignore /assets/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/logo.ico -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/brush_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/brush_dark.png -------------------------------------------------------------------------------- /assets/close_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/close_dark.png -------------------------------------------------------------------------------- /assets/warn_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/warn_dark.png -------------------------------------------------------------------------------- /assets/warn_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/warn_light.png -------------------------------------------------------------------------------- /assets/brush_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/brush_light.png -------------------------------------------------------------------------------- /assets/close_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/close_light.png -------------------------------------------------------------------------------- /assets/github_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/github_dark.png -------------------------------------------------------------------------------- /assets/github_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/github_light.png -------------------------------------------------------------------------------- /assets/update_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/update_dark.png -------------------------------------------------------------------------------- /assets/update_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/update_light.png -------------------------------------------------------------------------------- /assets/keyboard_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/keyboard_dark.png -------------------------------------------------------------------------------- /assets/keyboard_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/keyboard_light.png -------------------------------------------------------------------------------- /assets/settings_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/settings_dark.png -------------------------------------------------------------------------------- /assets/settings_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Futura-Py/Notes/HEAD/assets/settings_light.png -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | import darkdetect 2 | 3 | import vars as v 4 | 5 | 6 | def dark(): 7 | if v.cfg["theme"] == "Dark" or (v.cfg["theme"] == "System" and darkdetect.isDark()): 8 | return True 9 | else: 10 | return False -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | sv-ttk >= 2.4 2 | ntkutils 3 | darkdetect 4 | pywin32; sys_platform == 'win32' 5 | chlorophyll 6 | markdown 7 | tkinterweb 8 | tkinterdnd2 9 | tklinenums >= 1.5 10 | requests; sys_platform != 'linux' 11 | pygments 12 | pyperclip 13 | -------------------------------------------------------------------------------- /compile.bat: -------------------------------------------------------------------------------- 1 | rmdir /Q /S dist 2 | pyinstaller ./src/main.py --onefile --windowed --collect-data sv_ttk --collect-all tkinterweb --collect-all tkinterdnd2 --icon "./assets/logo.ico" 3 | rmdir /Q /S build 4 | rmdir /Q /S __pycache__ 5 | del /Q /S main.spec 6 | pause -------------------------------------------------------------------------------- /src/themes/dark.py: -------------------------------------------------------------------------------- 1 | theme = { 2 | "primary": "#1c1c1c", 3 | "secondary": "#202020", 4 | "opposite_secondary": "#f3f3f3", 5 | "color_scheme": "ayu-dark", 6 | "closeimg": "assets/close_light.png", 7 | } 8 | 9 | 10 | def get(): 11 | return theme 12 | -------------------------------------------------------------------------------- /src/themes/light.py: -------------------------------------------------------------------------------- 1 | theme = { 2 | "primary": "#fafafa", 3 | "secondary": "#f3f3f3", 4 | "opposite_secondary": "#202020", 5 | "color_scheme": "ayu-light", 6 | "closeimg": "assets/close_dark.png", 7 | } 8 | 9 | 10 | def get(): 11 | return theme 12 | -------------------------------------------------------------------------------- /docs/FEATURES.md: -------------------------------------------------------------------------------- 1 | # v0.2 2 | 3 | ## File 4 | - Edit 5 | - Save / Save As 6 | - Open 7 | - Create New 8 | - Change Extension 9 | 10 | ## Hotkeys (DISABLED) 11 | - `ctrl+s` to save 12 | - `ctrl+o` to open 13 | 14 | ## UI 15 | - Window is made slightly smaller than, and placed in the middle of, the screen at startup 16 | - Display save directory of opened file at bottom of screen 17 | - Menu bar with dropdown menus 18 | -------------------------------------------------------------------------------- /src/update.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | import vars as v 4 | 5 | 6 | def install(): 7 | pass 8 | 9 | 10 | def check(): 11 | api_response = requests.get("https://api.github.com/repos/futura-py/notes/releases") 12 | 13 | try: 14 | latest_tag = next(iter(api_response.json()))["tag_name"] 15 | 16 | if float(str(latest_tag).removeprefix("v")) > float(v.ver.split(" ")[0]): 17 | return True 18 | else: 19 | return False 20 | except: 21 | return "rate limit" 22 | -------------------------------------------------------------------------------- /src/vars.py: -------------------------------------------------------------------------------- 1 | tabselected = 0 2 | 3 | cfg = [] 4 | 5 | root = "" 6 | textwidget = "" 7 | filedir = "" 8 | closeimg = "" 9 | normal = "" 10 | selected = "" 11 | normal_hover = "" 12 | selected_hover = "" 13 | tabbar = "" 14 | footer = "" 15 | theme = "" 16 | ver = "" 17 | 18 | brush_light = "" 19 | brush_dark = "" 20 | keyboard_light = "" 21 | keyboard_dark = "" 22 | warn_light = "" 23 | warn_dark = "" 24 | logo = "" 25 | github_dark = "" 26 | github_light = "" 27 | update_dark = "" 28 | update_light = "" 29 | settings_light = "" 30 | settings_dark = "" 31 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to txt2 2 | 3 | Start off by creating a fork of the repository on github. 4 | Then clone this fork with git: 5 | 6 | ```git clone https://github.com/your_username/txt2``` 7 | 8 | And checkout to a new branch: 9 | 10 | ```git checkout -b my_new_feature``` 11 | 12 | Now open the generated txt2 folder with a code editor of your choice and make your edits. 13 | Then stage your changes: 14 | 15 | ```git add .``` 16 | 17 | And commit and push: 18 | 19 | ``` 20 | git commit -m "My New Feature" 21 | git push 22 | ``` 23 | 24 | Now create a pull request on the txt2 repo. 25 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Futura Notes 4 | 5 | Releases 6 | 7 |

8 | 9 | > The Main Branch is unusable. There is a rewrite branch, but that was paused because of performance difficulties 10 | 11 | A text editor in python! 12 | 13 | Modern and native look thanks to rdbendes [Sun Valley ttk theme](https://github.com/rdbende/Sun-Valley-ttk-theme) 14 | 15 | You can find more information in the [wiki](https://github.com/not-nef/onyx/wiki) 16 | -------------------------------------------------------------------------------- /src/generatesize.py: -------------------------------------------------------------------------------- 1 | import platform 2 | 3 | system = platform.system() 4 | if system == "Windows": 5 | from win32api import GetMonitorInfo, MonitorFromPoint 6 | 7 | def get(): 8 | monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0))) 9 | work_area = monitor_info.get("Work") 10 | return "{}x{}".format(work_area[2] - 40, work_area[3] - 80) 11 | 12 | else: 13 | import tkinter 14 | 15 | root = tkinter.Tk() 16 | root.withdraw() 17 | WIDTH, HEIGHT = root.winfo_screenwidth(), root.winfo_screenheight() 18 | root.destroy() 19 | 20 | def get(): 21 | return "{}x{}".format(WIDTH, HEIGHT - 100) 22 | -------------------------------------------------------------------------------- /src/pages/wanttosave.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | from tkinter import ttk 3 | 4 | import tabmanager as t 5 | import vars as v 6 | 7 | 8 | def save(e=""): 9 | t.save() 10 | v.root.destroy() 11 | 12 | 13 | def build(): 14 | w = tkinter.Toplevel() 15 | w.geometry("300x100") 16 | w.title("Save before exiting?") 17 | w.focus_set() 18 | 19 | lbl = tkinter.Label(w, font=("Segoe UI", 10, "bold"), text="Do you want to save before exiting?").pack(pady=10) 20 | 21 | btnno = ttk.Button(w, text="No", command=v.root.destroy, width=10).place(x=25, y=50) 22 | btnyes = ttk.Button(w, text="Yes", command=save, width=10, style="Accent.TButton").place(x=170, y=50) 23 | 24 | w.bind("", save) -------------------------------------------------------------------------------- /src/mdpreview.py: -------------------------------------------------------------------------------- 1 | import markdown 2 | from tkinterweb.htmlwidgets import HtmlFrame 3 | 4 | import vars as v 5 | 6 | 7 | def update(): 8 | html = markdown.markdown(v.textwidget.get("1.0", "end")) 9 | display.load_html(html) 10 | 11 | 12 | def reload(e): 13 | v.root.after(2, update) 14 | 15 | 16 | def build(): 17 | global display, binding 18 | 19 | display = HtmlFrame(v.root, messages_enabled=False) 20 | display.place( 21 | x=v.root.winfo_width() / 2, 22 | y=50, 23 | width=v.root.winfo_width() / 2, 24 | height=v.root.winfo_height() - 75, 25 | ) 26 | display.on_link_click(reload) # This line blocks clicking on links 27 | 28 | v.textwidget.bind("", reload, add="+") 29 | v.textwidget.bind("", reload, add="+") 30 | reload("") 31 | 32 | 33 | def close(): 34 | v.textwidget.unbind("") 35 | v.textwidget.unbind("") 36 | display.destroy() 37 | -------------------------------------------------------------------------------- /src/pages/filetype.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tkinter 3 | from tkinter import ttk 4 | 5 | import ntkutils 6 | 7 | from generatesize import system 8 | 9 | 10 | def changetype(filename): 11 | global filetype 12 | 13 | def change(): 14 | global new_path 15 | if entry.get().startswith("."): 16 | new_path = ( 17 | filename.removesuffix("." + filename.split(".")[-1]) + entry.get() 18 | ) 19 | os.rename(filename, new_path) 20 | filetype.destroy() 21 | else: 22 | print("not an extension") 23 | 24 | filetype = tkinter.Toplevel() 25 | if system != "Darwin": 26 | ntkutils.dark_title_bar(filetype) 27 | filetype.title("Futura Notes - Change file type") 28 | lbl = tkinter.Label(filetype, text="Change file extension:", font=("", 20)).pack( 29 | pady=5 30 | ) 31 | entry = ttk.Entry(filetype) 32 | entry.pack(pady=5) 33 | btn = ttk.Button(filetype, text="Apply", command=change).pack(pady=5) 34 | 35 | 36 | def get(path): 37 | changetype(path) 38 | filetype.wait_window() 39 | return new_path 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 not-nef 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/settings/images.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | 3 | import vars as v 4 | 5 | 6 | def setimages(): 7 | v.brush_light = tkinter.PhotoImage(master=v.root, file="./assets/brush_light.png") 8 | v.brush_dark = tkinter.PhotoImage(master=v.root, file="./assets/brush_dark.png") 9 | v.keyboard_light = tkinter.PhotoImage(master=v.root, file="./assets/keyboard_light.png") 10 | v.keyboard_dark = tkinter.PhotoImage(master=v.root, file="./assets/keyboard_dark.png") 11 | v.warn_light = tkinter.PhotoImage(master=v.root, file="./assets/warn_light.png") 12 | v.warn_dark = tkinter.PhotoImage(master=v.root, file="./assets/warn_dark.png") 13 | v.logo = tkinter.PhotoImage(master=v.root, file="./assets/logo.png") 14 | v.github_dark = tkinter.PhotoImage(master=v.root, file="./assets/github_dark.png") 15 | v.github_light = tkinter.PhotoImage(master=v.root, file="./assets/github_light.png") 16 | v.update_dark = tkinter.PhotoImage(master=v.root, file="./assets/update_dark.png") 17 | v.update_light = tkinter.PhotoImage(master=v.root, file="./assets/update_light.png") 18 | v.settings_light = tkinter.PhotoImage(master=v.root, file="./assets/settings_light.png") 19 | v.settings_dark = tkinter.PhotoImage(master=v.root, file="./assets/settings_dark.png") 20 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import ntkutils.cfgtools as cfgtools 4 | 5 | from generatesize import system 6 | 7 | default_win = { 8 | "theme": "Dark", 9 | "font": "Helvetica", 10 | "font-size": 11, 11 | "mica": False, 12 | "hkey-open": "Control-o", 13 | "hkey-save": "Control-s", 14 | "linenumbers": True, 15 | "syntax-highlighting": False, 16 | "onclose": "Ask", 17 | } 18 | 19 | default_mac = { 20 | "theme": "Dark", 21 | "font": "Helvetica", 22 | "font-size": 11, 23 | "mica": False, 24 | "hkey-open": "Command-o", 25 | "hkey-save": "Command-s", 26 | "linenumbers": True, 27 | "syntax-highlighting": False, 28 | } 29 | 30 | # Update Config if settings are missing 31 | try: 32 | config_file = open("cfg.json") 33 | cfg = json.load(config_file) 34 | config_file.close() 35 | 36 | if len(cfg) != len(default_win): 37 | temp_cfg = default_win.copy() 38 | for i in cfg: 39 | temp_cfg.pop(i) 40 | for i in temp_cfg: 41 | cfg[i] = temp_cfg[i] 42 | cfgtools.SaveCFG(cfg) 43 | except FileNotFoundError: 44 | pass 45 | 46 | 47 | def get(): 48 | if system != "Darwin": 49 | return cfgtools.init(default_win) 50 | else: 51 | return cfgtools.init(default_mac) 52 | -------------------------------------------------------------------------------- /src/pages/about.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | import webbrowser 3 | from tkinter import messagebox, ttk 4 | 5 | import update 6 | import utils as u 7 | import vars as v 8 | 9 | 10 | def checkforupdates(): 11 | response = update.check() 12 | 13 | if response == True: webbrowser.open("https://github.com/futura-py/notes/releases") 14 | elif response == False: messagebox.showinfo(title="Update", message="You are on the newest version of Futura Notes!") 15 | else: messagebox.showinfo(title="Rate Limit", message="You have managed to exceed the github api rate limit of 60 requests per hour. idk how that can be achieved by accident. try again in an hour i guess.",) 16 | 17 | 18 | def build(): 19 | root = tkinter.Toplevel() 20 | root.title("About Futura Notes") 21 | root.geometry("650x200") 22 | root.resizable(False, False) 23 | 24 | name = tkinter.Label(root, text="Futura Notes", font=("Segoe UI", 40, "bold")).place(x=30, y=15) 25 | version = tkinter.Label(root, text="Version {}".format(v.ver.split(" ")[0]), font=("Segoe UI", 20, "")).place(x=370, y=42) 26 | versiontype = tkinter.Label(root, text="Beta" if v.ver.endswith("beta") else "Stable", font=("Segoe UI", 20, ""), fg="orange" if v.ver.endswith("beta") else "green").place(x=510, y=42) 27 | github = ttk.Button(root, text=" Github Repo", image=v.github_light if u.dark() else v.github_dark, compound="left", command=lambda: webbrowser.open("https://github.com/futura-py/notes")).place(x=30, y=105) 28 | updatebtn = ttk.Button(root, text=" Check for Updates", image=v.update_light if u.dark() else v.update_dark, compound="left", command=checkforupdates).place(x=170, y=105) 29 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | ver = "0.9 beta" 2 | 3 | import os 4 | import tkinter 5 | from tkinter import ttk 6 | 7 | import ntkutils 8 | import sv_ttk 9 | from tkinterdnd2 import * 10 | 11 | import config 12 | import editor 13 | import generatesize as size 14 | import settings.UI as settings 15 | import tabmanager 16 | import utils as u 17 | import vars as v 18 | from themes import dark, light 19 | 20 | v.cfg = config.get() 21 | 22 | if u.dark(): 23 | theme = dark.get() 24 | else: 25 | theme = light.get() 26 | 27 | root = TkinterDnD.Tk() 28 | root.geometry("200x350") 29 | root.withdraw() 30 | ntkutils.windowsetup(root, title="Futura Notes", resizeable=False) 31 | sv_ttk.set_theme(v.cfg["theme"].lower()) 32 | root.update_idletasks() 33 | ntkutils.placeappincenter(root) 34 | root.update_idletasks() 35 | 36 | 37 | def preparewindow(): 38 | root.title("Futura Notes - Untitled *") 39 | ntkutils.clearwin(root) 40 | root.geometry(size.get()) 41 | root.update() 42 | ntkutils.placeappincenter(root) 43 | root.resizable(True, True) 44 | editor.build(theme, root, ver) 45 | 46 | 47 | def openfile(path): 48 | preparewindow() 49 | tabmanager.openfile(path=path) 50 | 51 | def settingss(): 52 | preparewindow() 53 | settings.build() 54 | 55 | 56 | title = tkinter.Label(root, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20) 57 | btncreatenew = ttk.Button(root, text="Create New File", command=preparewindow).pack(anchor="nw", padx=20) 58 | btnopenfile = ttk.Button(root, text="Open File", command=lambda: openfile(path="")).pack(anchor="nw", pady=10, padx=20) 59 | btnopendir = ttk.Button(root, text="Open Directory", state="disabled").pack(anchor="nw", padx=20) 60 | btnopenlast = ttk.Button(root, text="Open last file", command=lambda: openfile(path=content)) 61 | btnopenlast.pack(anchor="nw", padx=20, pady=20) 62 | 63 | if os.path.isfile("lastfile.txt"): 64 | file = open("lastfile.txt", "r") 65 | content = file.read() 66 | file.close() 67 | 68 | if not os.path.isfile(content): 69 | btnopenlast.configure(state="disabled") 70 | else: 71 | btnopenlast.configure(state="disabled") 72 | 73 | root.update_idletasks() 74 | root.deiconify() 75 | root.mainloop() 76 | 77 | # Save path of last opened file 78 | content = tabmanager.tabs[v.tabselected][2] 79 | 80 | if content != "unsaved": 81 | file = open("lastfile.txt", "w+") 82 | file.write(content) 83 | file.close() 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | cfg.json 132 | lastfile.txt 133 | .vscode -------------------------------------------------------------------------------- /src/tabmanager.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | from tkinter import filedialog 3 | 4 | import pygments.lexers 5 | from pygments.lexers import get_lexer_for_filename 6 | 7 | import pages.filetype as f 8 | import vars as v 9 | 10 | tabs = [["Untitled", "", "unsaved", "*"]] 11 | 12 | # Item 0: Name 13 | # Item 1: Content 14 | # Item 2: Storage Path 15 | # Item 3: Save Status ("*" or "") 16 | 17 | 18 | def updatetab(file): 19 | tabs[v.tabselected][0] = file.name.split("/")[-1] 20 | tabs[v.tabselected][2] = file.name 21 | tabs[v.tabselected][3] = "" 22 | 23 | 24 | def updatetitle(): 25 | v.root.title("Futura Notes - {} {}".format(tabs[v.tabselected][0], tabs[v.tabselected][3])) 26 | 27 | 28 | def redrawlinenums(): 29 | if v.cfg["linenumbers"]: 30 | v.textwidget.linenums.redraw() 31 | 32 | 33 | def new(): 34 | tabs[v.tabselected][1] = v.textwidget.get("1.0", "end") # Save edits 35 | v.textwidget.delete("1.0", "end") 36 | tabs.append(["Untitled", "", "unsaved", "*"]) 37 | v.filedir.configure(text="unsaved") 38 | v.tabselected += 1 39 | 40 | v.tabbar.add(tkinter.Frame(), text=tabs[v.tabselected][0], image=v.closeimg, compound="right") 41 | v.tabbar.select(v.tabselected) 42 | 43 | updatetitle() 44 | if v.cfg["syntax-highlighting"]: 45 | v.textwidget._set_lexer(pygments.lexers.TextLexer) 46 | 47 | 48 | def save(e="", saveas=False): 49 | if tabs[v.tabselected][2] == "unsaved" or saveas: 50 | file = filedialog.asksaveasfile() 51 | if file == None: 52 | return 53 | else: 54 | file = open(tabs[v.tabselected][2], "w") 55 | 56 | if file != None: 57 | file.write(v.textwidget.get("1.0", "end")) 58 | 59 | updatetab(file) 60 | v.filedir.configure(text=file.name) 61 | 62 | file.close() 63 | 64 | updatetitle() 65 | setlexer() 66 | 67 | 68 | def openfile(e="", path=""): 69 | if path == "": 70 | file = filedialog.askopenfile() 71 | content = file.read() 72 | else: 73 | file = open(path, "r") 74 | content = file.read() 75 | 76 | isopen = False 77 | 78 | for i in tabs: 79 | if i[2] == file.name: isopen=True 80 | 81 | if not isopen: 82 | if v.textwidget.get("1.0", "end").replace("\n", "") != "": 83 | new() 84 | 85 | updatetab(file) 86 | 87 | file.close() 88 | 89 | v.tabbar.tab(v.tabselected, text=tabs[v.tabselected][0], image=v.closeimg, compound="right") 90 | v.textwidget.insert("1.0", content) 91 | v.filedir.configure(text=tabs[v.tabselected][2]) 92 | 93 | updatetitle() 94 | setlexer() 95 | redrawlinenums() 96 | 97 | 98 | def opentab(event, tabdeleted=False): 99 | if not tabdeleted: 100 | tabs[v.tabselected][1] = v.textwidget.get("1.0", "end") 101 | 102 | v.tabselected = v.tabbar.index(v.tabbar.select()) 103 | 104 | v.textwidget.delete("1.0", "end") 105 | v.textwidget.insert("1.0", tabs[v.tabselected][1]) 106 | v.textwidget.delete("end-1c", "end") 107 | 108 | v.filedir.configure(text=tabs[v.tabselected][2]) 109 | 110 | updatetitle() 111 | setlexer() 112 | redrawlinenums() 113 | 114 | 115 | def setlexer(): 116 | if v.cfg["syntax-highlighting"]: 117 | try: 118 | lexer = get_lexer_for_filename(tabs[v.tabselected][0]) 119 | except pygments.util.ClassNotFound: 120 | lexer = pygments.lexers.TextLexer 121 | lexer = "pygments.lexers." + str(lexer).split(".")[-1].removesuffix(">").removesuffix("'") 122 | v.textwidget._set_lexer(eval(lexer)) 123 | 124 | 125 | # The following two functions contain code copied from https://github.com/Akuli/porcupine 126 | 127 | 128 | def closetab(event): 129 | before = v.tabbar.index(f"@{event.x},{event.y}") 130 | after = before + 1 131 | 132 | if v.tabbar.index(v.tabbar.tabs()[before:after][0]) < v.tabselected: 133 | v.tabselected -= 1 134 | 135 | tabs.pop(v.tabbar.index(v.tabbar.tabs()[before:after][0])) 136 | v.tabbar.forget(v.tabbar.tabs()[before:after][0]) 137 | opentab(event, True) 138 | 139 | 140 | def click(event) -> None: 141 | if event.widget.identify(event.x, event.y) == "label": 142 | # find the right edge of the top label (including close button) 143 | right = event.x 144 | while event.widget.identify(right, event.y) == "label": 145 | right += 1 146 | 147 | if event.x >= right - v.closeimg.width(): 148 | if event.widget.index("end") != 1: 149 | closetab(event) 150 | else: 151 | v.root.destroy() 152 | else: 153 | opentab(event) 154 | else: 155 | opentab(event) 156 | 157 | 158 | def changetype(): 159 | if tabs[v.tabselected][2] == "unsaved": 160 | save() 161 | else: 162 | result = f.get(tabs[v.tabselected][2]) 163 | tabs[v.tabselected][2] = result 164 | tabs[v.tabselected][0] = result.split("/")[-1] 165 | v.filedir.configure(text=result) 166 | 167 | updatetitle() 168 | setlexer() 169 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | GitHub Issues. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/editor.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | from pathlib import Path 3 | from tkinter import ttk 4 | from tkinter.font import Font 5 | 6 | import chlorophyll 7 | import darkdetect 8 | import ntkutils 9 | import pygments 10 | import pyperclip 11 | from tkinterdnd2 import * 12 | from tklinenums import TkLineNumbers 13 | 14 | import mdpreview as md 15 | import pages.about as about 16 | import pages.wanttosave as w 17 | import settings.UI as settingsui 18 | import tabmanager 19 | import vars as v 20 | from settings.images import setimages 21 | 22 | 23 | def build(theme, root, ver): 24 | closeimg = tkinter.PhotoImage(file=Path(theme["closeimg"])) 25 | 26 | def closepreview(): 27 | md.close() 28 | textwidget.bind("", refreshtitle) 29 | if v.cfg["linenumbers"]: 30 | textwidget.bind( 31 | f"", lambda event: root.after_idle(linenums.redraw), add=True 32 | ) 33 | 34 | notebook = ttk.Notebook(root) 35 | notebook.pack(fill="both", expand=True) 36 | 37 | footer = tkinter.Frame(root, width=root.winfo_width(), height=25) 38 | footer.update_idletasks() 39 | footer.pack(side="bottom", fill="x") 40 | footer.pack_propagate(False) 41 | 42 | scrollbar = ttk.Scrollbar(root) 43 | scrollbar.pack(side="right", fill="y") 44 | 45 | if v.cfg["syntax-highlighting"]: 46 | textwidget = chlorophyll.CodeView( 47 | root, 48 | height=800, 49 | bg=theme["primary"], 50 | lexer=pygments.lexers.TextLexer, 51 | font=(v.cfg["font"], int(v.cfg["font-size"])), 52 | ) 53 | textwidget._set_color_scheme(theme["color_scheme"]) 54 | textwidget.pack(side="right", fill="both", expand=True) 55 | textwidget._hs.grid_remove() 56 | textwidget._vs.grid_remove() 57 | else: 58 | textwidget = tkinter.Text( 59 | root, 60 | width=100, 61 | borderwidth=0, 62 | height=root.winfo_height() - 125, 63 | font=(v.cfg["font"], int(v.cfg["font-size"])), 64 | ) 65 | textwidget.pack(side="right", fill="both", expand=True) 66 | 67 | textwidget.update() 68 | 69 | scrollbar.configure(command=textwidget.yview) 70 | textwidget["yscrollcommand"] = scrollbar.set 71 | 72 | if v.cfg["linenumbers"]: 73 | style = ttk.Style() 74 | style.configure( 75 | "TLineNumbers", 76 | background=theme["primary"], 77 | foreground=theme["opposite_secondary"], 78 | ) 79 | 80 | font = Font( 81 | family="Courier New bold", size=v.cfg["font-size"], name="TkLineNumsFont" 82 | ) 83 | 84 | linenums = TkLineNumbers(root, textwidget, font, "right") 85 | linenums.pack(side="left", fill="y") 86 | linenums.configure(borderwidth=0) 87 | linenums.reload(font) 88 | 89 | textwidget.bind( 90 | "", lambda event: root.after_idle(linenums.redraw), add=True 91 | ) 92 | textwidget.bind( 93 | f"", lambda event: root.after_idle(linenums.redraw), add=True 94 | ) 95 | textwidget.bind( 96 | f"", lambda event: root.after_idle(linenums.redraw), add=True 97 | ) 98 | 99 | def onscroll(first, last): 100 | scrollbar.set(first, last) 101 | linenums.redraw() 102 | 103 | textwidget["yscrollcommand"] = onscroll 104 | 105 | textwidget.linenums = linenums 106 | 107 | filedir = tkinter.Label(footer, text="unsaved") 108 | filedir.pack(side="left") 109 | 110 | menubar = tkinter.Menu(root) 111 | root.config(menu=menubar) 112 | 113 | filemenu = tkinter.Menu(menubar, tearoff=False, bg="white") 114 | settingsmenu = tkinter.Menu(menubar, tearoff=False, bg="white") 115 | 116 | menubar.add_cascade(label="File", menu=filemenu) 117 | menubar.add_cascade(label="Settings", menu=settingsmenu) 118 | 119 | filemenu.add_command(label="Save ({})".format(v.cfg["hkey-save"]), command=tabmanager.save, foreground="black" ) 120 | filemenu.add_command(label="Save As", command=lambda: tabmanager.save(saveas=True), foreground="black") 121 | filemenu.add_command(label="Open ({})".format(v.cfg["hkey-open"]), command=tabmanager.openfile, foreground="black") 122 | filemenu.add_command(label="New", command=tabmanager.new, foreground="black") 123 | filemenu.add_separator() 124 | filemenu.add_command(label="Change file extension", command=tabmanager.changetype, foreground="black") 125 | filemenu.add_separator() 126 | filemenu.add_command(label="Preview Markdown", command=md.build, foreground="black") 127 | filemenu.add_command(label="Close Preview", command=closepreview, foreground="black") 128 | 129 | settingsmenu.add_command(label="Open Settings", command=settingsui.build, foreground="black") 130 | settingsmenu.add_command(label="About", command=about.build, foreground="black") 131 | 132 | if v.cfg["mica"]: 133 | if v.cfg["theme"] == "Dark" or (v.cfg["theme"] == "System" and darkdetect.isDark()): 134 | notebook.configure(bg="#1c1c1c") 135 | ntkutils.blur_window_background(root, dark=True) 136 | textwidget.text.configure(bg="#1b1c1b") 137 | try: 138 | textwidget.numberLines.configure(bg="#1b1c1b") 139 | except: 140 | pass 141 | else: 142 | ntkutils.blur_window_background(root) 143 | textwidget.text.configure(bg="#fafbfa") 144 | 145 | def refreshtitle(e): 146 | if not root.wm_title().endswith("*"): 147 | root.title(root.wm_title() + "*") 148 | tabmanager.tabs[v.tabselected][3] = "*" 149 | 150 | textwidget.bind("", refreshtitle) 151 | 152 | root.event_add("<>", "<{}>".format(v.cfg["hkey-open"])) 153 | root.event_add("<>", "<{}>".format(v.cfg["hkey-save"])) 154 | 155 | root.bind("<>", tabmanager.openfile) 156 | root.bind("<>", tabmanager.save) 157 | 158 | def filedrop(event): 159 | tabmanager.openfile(path=event.data) 160 | 161 | root.drop_target_register(DND_FILES) 162 | root.dnd_bind("<>", filedrop) 163 | 164 | def cut(): 165 | pyperclip.copy(textwidget.selection_get()) 166 | textwidget.delete("sel.first", "sel.last") 167 | 168 | def copy(): 169 | pyperclip.copy(textwidget.selection_get()) 170 | 171 | def paste(): 172 | textwidget.insert("insert", pyperclip.paste()) 173 | 174 | def popup(event): 175 | try: 176 | context.tk_popup(event.x_root, event.y_root, 0) 177 | finally: 178 | context.grab_release() 179 | 180 | context = tkinter.Menu(root, tearoff=False, bg="white") 181 | context.add_command(label="Cut", command=cut, foreground="black") 182 | context.add_command(label="Copy", command=copy, foreground="black") 183 | context.add_command(label="Paste", command=paste, foreground="black") 184 | root.bind("", popup) 185 | 186 | def on_closing(): 187 | if v.cfg["onclose"] == "Do Nothing": 188 | root.destroy() 189 | elif v.cfg["onclose"] == "Save": 190 | tabmanager.save() 191 | root.destroy() 192 | else: 193 | w.build() 194 | 195 | 196 | root.protocol("WM_DELETE_WINDOW", on_closing) 197 | 198 | # Set global variables 199 | v.root = root 200 | v.textwidget = textwidget 201 | v.filedir = filedir 202 | v.tabbar = notebook 203 | v.footer = footer 204 | v.closeimg = closeimg 205 | v.theme = theme 206 | v.ver = ver 207 | 208 | setimages() 209 | 210 | notebook.add(tkinter.Frame(), text=tabmanager.tabs[0][0], image=closeimg, compound="right") 211 | # Bind Left mouse button to write content of selected tab into the text widget 212 | notebook.bind("", tabmanager.click, add="+") 213 | -------------------------------------------------------------------------------- /src/settings/UI.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | from tkinter import font, ttk 3 | 4 | import ntkutils 5 | import sv_ttk 6 | 7 | import config 8 | import utils as u 9 | import vars as v 10 | from generatesize import system 11 | 12 | options = [ 13 | "Theme", 14 | "Font", 15 | "Font Size", 16 | "Display Line Numbers", 17 | "Syntax Highlighting", 18 | "Hotkeys", 19 | "Mica Blur", 20 | ] 21 | 22 | options2 = { 23 | "Theme": "appearance", 24 | "Font": "appearance", 25 | "Font Size": "appearance", 26 | "Display Line Numbers": "general", 27 | "Syntax Highlighting": "general", 28 | "Hotkeys": "hotkeys", 29 | "Mica Blur": "experimental", 30 | "On Close": "general", 31 | } 32 | 33 | def general(): 34 | global page, btnnumbers, btnhighlight, boxonclose 35 | 36 | savechanges() 37 | clearstates() 38 | 39 | btngeneral.configure(style="Accent.TButton") 40 | if u.dark(): btngeneral.configure(image=v.settings_dark) 41 | else: btngeneral.configure(image=v.settings_light) 42 | 43 | ntkutils.clearwin(frameright) 44 | 45 | page = "general" 46 | 47 | lblonclose = tkinter.Label(frameright, text="On Close:").place(x=10, y=15) 48 | boxonclose = ttk.Combobox(frameright, values=["Do Nothing", "Save", "Ask"], state="readonly", width=25) 49 | boxonclose.set(cfg["onclose"]) 50 | boxonclose.pack(padx=10, pady=10, anchor="e") 51 | 52 | lblnumbers = tkinter.Label(frameright, text="Display line numbers:").place(x=10, y=60) 53 | btnnumbers = ttk.Checkbutton(frameright, style="Switch.TCheckbutton") 54 | btnnumbers.pack(padx=10, pady=10, anchor="e") 55 | btnnumbers.state(["!alternate"]) 56 | if cfg["linenumbers"]: btnnumbers.state(["!alternate", "selected"]) 57 | 58 | lblhighlight = tkinter.Label(frameright, text="Syntax Highlighting:").place(x=10, y=115) 59 | btnhighlight = ttk.Checkbutton(frameright, style="Switch.TCheckbutton") 60 | btnhighlight.pack(padx=10, pady=15, anchor="e") 61 | btnhighlight.state(["!alternate"]) 62 | if cfg["syntax-highlighting"]: btnhighlight.state(["!alternate", "selected"]) 63 | 64 | def appearance(): 65 | global boxtheme, boxfont, boxsize, page 66 | 67 | savechanges() 68 | clearstates() 69 | 70 | btnappearence.configure(style="Accent.TButton", image=eval("v.brush_" + sv_ttk.get_theme())) 71 | 72 | ntkutils.clearwin(frameright) 73 | 74 | page = "appearance" 75 | 76 | lbltheme = tkinter.Label(frameright, text="Theme:").place(x=10, y=15) 77 | boxtheme = ttk.Combobox(frameright, values=["Dark", "Light", "System"], state="readonly", width=25) 78 | boxtheme.set(cfg["theme"]) 79 | boxtheme.pack(padx=10, pady=10, anchor="e") 80 | 81 | lblfont = tkinter.Label(frameright, text="Font:").place(x=10, y=60) 82 | boxfont = ttk.Combobox(frameright, state="readonly", values=fonts, width=15) 83 | boxfont.set(cfg["font"]) 84 | boxfont.pack(padx=70, pady=10, anchor="e") 85 | boxsize = ttk.Entry(frameright, width=5) 86 | boxsize.insert(0, cfg["font-size"]) 87 | boxsize.place(x=265, y=58) 88 | 89 | 90 | def experimental(): 91 | global page, btnmica, btnhotkeys 92 | 93 | savechanges() 94 | clearstates() 95 | 96 | btnexperimental.configure(style="Accent.TButton", image=eval("v.warn_" + sv_ttk.get_theme())) 97 | 98 | ntkutils.clearwin(frameright) 99 | 100 | page = "experimental" 101 | 102 | lblmica = tkinter.Label(frameright, text="Mica Blur:").place(x=10, y=12) 103 | btnmica = ttk.Checkbutton(frameright, style="Switch.TCheckbutton") 104 | btnmica.pack(padx=10, pady=10, anchor="e") 105 | btnmica.state(["!alternate"]) 106 | if cfg["mica"]: btnmica.state(["!alternate", "selected"]) 107 | 108 | 109 | def hotkeys(): 110 | global page, entryopen, entrysave 111 | 112 | savechanges() 113 | clearstates() 114 | 115 | btnhotkeys.configure(style="Accent.TButton", image=eval("v.keyboard_" + sv_ttk.get_theme())) 116 | 117 | ntkutils.clearwin(frameright) 118 | 119 | page = "hotkeys" 120 | 121 | lblopen = tkinter.Label(frameright, text="Open:").place(x=10, y=12) 122 | entryopen = ttk.Entry(frameright, width=25) 123 | entryopen.insert(0, cfg["hkey-open"]) 124 | entryopen.pack(padx=10, pady=10, anchor="e") 125 | 126 | lblsave = tkinter.Label(frameright, text="Save:").place(x=10, y=67) 127 | entrysave = ttk.Entry(frameright, width=25) 128 | entrysave.insert(0, cfg["hkey-save"]) 129 | entrysave.pack(padx=10, pady=10, anchor="e") 130 | 131 | 132 | def savechanges(): 133 | if page == "general": 134 | cfg["linenumbers"] = btnnumbers.instate(["selected"]) 135 | cfg["syntax-highlighting"] = btnhighlight.instate(["selected"]) 136 | cfg["onclose"] = boxonclose.get() 137 | 138 | if u.dark(): btngeneral.configure(image=v.settings_light) 139 | else: btngeneral.configure(image=v.settings_dark) 140 | elif page == "appearance": 141 | cfg["theme"] = boxtheme.get() 142 | cfg["font"] = boxfont.get() 143 | cfg["font-size"] = boxsize.get() 144 | 145 | if u.dark(): btnappearence.configure(image=v.brush_light) 146 | else: btnappearence.configure(image=v.brush_dark) 147 | elif page == "experimental": 148 | cfg["mica"] = btnmica.instate(["selected"]) 149 | 150 | if u.dark(): btnexperimental.configure(image=v.warn_light) 151 | else: btnexperimental.configure(image=v.warn_dark) 152 | elif page == "hotkeys": 153 | cfg["hkey-open"] = entryopen.get() 154 | cfg["hkey-save"] = entrysave.get() 155 | 156 | if u.dark(): btnhotkeys.configure(image=v.keyboard_light) 157 | else: btnhotkeys.configure(image=v.keyboard_dark) 158 | 159 | 160 | def apply(): 161 | global page, save 162 | 163 | savechanges() 164 | 165 | ntkutils.cfgtools.SaveCFG(cfg) 166 | settings.destroy() 167 | 168 | 169 | def build(): 170 | global frameright, frameleft, btnappearence, btnexperimental, btnhotkeys, settings, page, cfg, btngeneral 171 | 172 | cfg = config.get() 173 | page = "" 174 | 175 | settings = tkinter.Toplevel() 176 | ntkutils.windowsetup(settings, "Futura Notes - Settings", "assets/logo.png", False, "500x400") 177 | if system != "Darwin" and u.dark(): 178 | ntkutils.dark_title_bar(settings) 179 | 180 | frameleft = tkinter.Frame(settings, width=175, bg=v.theme["secondary"]) 181 | frameleft.pack(side=tkinter.LEFT, fill="y") 182 | frameleft.pack_propagate(False) 183 | 184 | frameright = tkinter.Frame(settings, width=325) 185 | frameright.pack(side=tkinter.LEFT, fill="both") 186 | frameright.pack_propagate(False) 187 | 188 | def update(data): 189 | menu.delete(0, "end") 190 | for value in data: 191 | menu.insert("end", value) 192 | menu.configure(height=len(data)) 193 | 194 | def check(e): 195 | v = search.get() 196 | if v == "": 197 | data = options 198 | else: 199 | data = [] 200 | for item in options: 201 | if v.lower() in item.lower(): 202 | data.append(item) 203 | update(data) 204 | 205 | def showlist(e): 206 | search.delete(0, "end") 207 | menu.bind("<>", onselect) 208 | menu.place(x=search.winfo_x(), y=search.winfo_y() + search.winfo_height()) 209 | check("") 210 | 211 | def removelist(e): 212 | menu.unbind("<>") 213 | menu.place(x=1000, y=1000) 214 | 215 | search = ttk.Entry(frameleft, width=23) 216 | search.pack(pady=10) 217 | search.bind("", check) 218 | search.update() 219 | search.bind("", showlist) 220 | search.bind("", removelist) 221 | search.insert(0, "Search for a setting...") 222 | 223 | def onselect(evt): 224 | w = evt.widget 225 | index = int(w.curselection()[0]) 226 | value = w.get(index) 227 | func = eval(options2[value]) 228 | func() 229 | 230 | btngeneral = ttk.Button(frameleft, text="General", width=14, command=general, image=v.settings_dark, compound="left") 231 | btngeneral.pack(pady=10) 232 | 233 | btnappearence = ttk.Button(frameleft, text="Appearence", width=14, command=appearance, image=v.brush_dark, compound="left") 234 | btnappearence.pack() 235 | 236 | btnhotkeys = ttk.Button(frameleft, text="Hotkeys", width=14, command=hotkeys, image=v.keyboard_dark, compound="left") 237 | btnhotkeys.pack(pady=10) 238 | 239 | btnexperimental = ttk.Button(frameleft, text="Unstable Features", width=14, command=experimental, image=v.warn_dark, compound="left") 240 | btnexperimental.pack() 241 | 242 | btnapply = ttk.Button(frameleft, text="Apply", style="Accent.TButton", width=18, command=apply) 243 | btnapply.pack(side="bottom", pady=10) 244 | 245 | lblrestart = tkinter.Label(frameleft, text="Restart required!", wraplength=170, fg="grey", bg=v.theme["secondary"]).pack(side="bottom") 246 | 247 | menu = tkinter.Listbox(frameleft, width=23) 248 | menu.bind("<>", onselect) 249 | 250 | if u.dark(): 251 | btngeneral.configure(image=v.settings_light) 252 | btnappearence.configure(image=v.brush_light) 253 | btnhotkeys.configure(image=v.keyboard_light) 254 | btnexperimental.configure(image=v.warn_light) 255 | 256 | getfonts() 257 | general() 258 | 259 | 260 | def clearstates(): 261 | for i in frameleft.pack_slaves(): 262 | try: 263 | i.configure(style="TButton") 264 | except: 265 | pass 266 | 267 | 268 | def getfonts(): 269 | global fonts 270 | fonts = list(font.families()) 271 | fonts.sort() 272 | --------------------------------------------------------------------------------