├── 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 |
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 |
--------------------------------------------------------------------------------