diff --git a/.gitignore b/.gitignore
index 441043a..863f3b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,133 +1,133 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-.python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-cfg.json
-lastfile.txt
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+cfg.json
+lastfile.txt
.vscode
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 23439fd..a42cabb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2022 not-nef
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+MIT License
+
+Copyright (c) 2023 not-nef
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/assets/check_dark.png b/assets/check_dark.png
new file mode 100644
index 0000000..b8576f5
Binary files /dev/null and b/assets/check_dark.png differ
diff --git a/assets/check_light.png b/assets/check_light.png
new file mode 100644
index 0000000..e249cdf
Binary files /dev/null and b/assets/check_light.png differ
diff --git a/assets/close_dark.png b/assets/close_dark.png
index bdb4038..5296a28 100644
Binary files a/assets/close_dark.png and b/assets/close_dark.png differ
diff --git a/assets/close_light.png b/assets/close_light.png
index 5296a28..bdb4038 100644
Binary files a/assets/close_light.png and b/assets/close_light.png differ
diff --git a/assets/filetypes/css_dark.png b/assets/filetypes/css_dark.png
new file mode 100644
index 0000000..11bddcd
Binary files /dev/null and b/assets/filetypes/css_dark.png differ
diff --git a/assets/filetypes/css_light.png b/assets/filetypes/css_light.png
new file mode 100644
index 0000000..f49d29e
Binary files /dev/null and b/assets/filetypes/css_light.png differ
diff --git a/assets/filetypes/html_dark.png b/assets/filetypes/html_dark.png
new file mode 100644
index 0000000..19db55b
Binary files /dev/null and b/assets/filetypes/html_dark.png differ
diff --git a/assets/filetypes/html_light.png b/assets/filetypes/html_light.png
new file mode 100644
index 0000000..4876a4a
Binary files /dev/null and b/assets/filetypes/html_light.png differ
diff --git a/assets/filetypes/js_dark.png b/assets/filetypes/js_dark.png
new file mode 100644
index 0000000..0b292fb
Binary files /dev/null and b/assets/filetypes/js_dark.png differ
diff --git a/assets/filetypes/js_light.png b/assets/filetypes/js_light.png
new file mode 100644
index 0000000..2dcbfc1
Binary files /dev/null and b/assets/filetypes/js_light.png differ
diff --git a/assets/filetypes/md_dark.png b/assets/filetypes/md_dark.png
new file mode 100644
index 0000000..8a4d1cb
Binary files /dev/null and b/assets/filetypes/md_dark.png differ
diff --git a/assets/filetypes/md_light.png b/assets/filetypes/md_light.png
new file mode 100644
index 0000000..2b892b0
Binary files /dev/null and b/assets/filetypes/md_light.png differ
diff --git a/assets/filetypes/other_dark.png b/assets/filetypes/other_dark.png
new file mode 100644
index 0000000..8bd4cb7
Binary files /dev/null and b/assets/filetypes/other_dark.png differ
diff --git a/assets/filetypes/other_light.png b/assets/filetypes/other_light.png
new file mode 100644
index 0000000..dbe43ab
Binary files /dev/null and b/assets/filetypes/other_light.png differ
diff --git a/assets/filetypes/txt_dark.png b/assets/filetypes/txt_dark.png
new file mode 100644
index 0000000..574d756
Binary files /dev/null and b/assets/filetypes/txt_dark.png differ
diff --git a/assets/filetypes/txt_light.png b/assets/filetypes/txt_light.png
new file mode 100644
index 0000000..d84b9cb
Binary files /dev/null and b/assets/filetypes/txt_light.png differ
diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md
index 3f8e7d4..9daa0b6 100644
--- a/docs/CODE_OF_CONDUCT.md
+++ b/docs/CODE_OF_CONDUCT.md
@@ -1,128 +1,128 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
-
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, religion, or sexual identity
-and orientation.
-
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
-
-## Our Standards
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
- and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the
- overall community
-
-Examples of unacceptable behavior include:
-
-* The use of sexualized language or imagery, and sexual attention or
- advances of any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email
- address, without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Enforcement Responsibilities
-
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
-
-## Scope
-
-This Code of Conduct applies within all community spaces, and also applies when
-an individual is officially representing the community in public spaces.
-Examples of representing our community include using an official e-mail address,
-posting via an official social media account, or acting as an appointed
-representative at an online or offline event.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-GitHub Issues.
-All complaints will be reviewed and investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
-
-## Enforcement Guidelines
-
-Community leaders will follow these Community Impact Guidelines in determining
-the consequences for any action they deem in violation of this Code of Conduct:
-
-### 1. Correction
-
-**Community Impact**: Use of inappropriate language or other behavior deemed
-unprofessional or unwelcome in the community.
-
-**Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
-behavior was inappropriate. A public apology may be requested.
-
-### 2. Warning
-
-**Community Impact**: A violation through a single incident or series
-of actions.
-
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction with
-those enforcing the Code of Conduct, for a specified period of time. This
-includes avoiding interactions in community spaces as well as external channels
-like social media. Violating these terms may lead to a temporary or
-permanent ban.
-
-### 3. Temporary Ban
-
-**Community Impact**: A serious violation of community standards, including
-sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No public or
-private interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, is allowed during this period.
-Violating these terms may lead to a permanent ban.
-
-### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
-
-**Consequence**: A permanent ban from any sort of public interaction within
-the community.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.0, available at
-https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
-
-Community Impact Guidelines were inspired by [Mozilla's code of conduct
-enforcement ladder](https://github.com/mozilla/diversity).
-
-[homepage]: https://www.contributor-covenant.org
-
-For answers to common questions about this code of conduct, see the FAQ at
-https://www.contributor-covenant.org/faq. Translations are available at
-https://www.contributor-covenant.org/translations.
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+GitHub Issues.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 6af5b20..7f2bf72 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -1,24 +1,24 @@
-## Contributing to txt2
-
-Start off by creating a fork of the repository on github.
-Then clone this fork with git:
-
-```git clone https://github.com/your_username/txt2```
-
-And checkout to a new branch:
-
-```git checkout -b my_new_feature```
-
-Now open the generated txt2 folder with a code editor of your choice and make your edits.
-Then stage your changes:
-
-```git add .```
-
-And commit and push:
-
-```
-git commit -m "My New Feature"
-git push
-```
-
-Now create a pull request on the txt2 repo.
+## Contributing to txt2
+
+Start off by creating a fork of the repository on github.
+Then clone this fork with git:
+
+```git clone https://github.com/your_username/txt2```
+
+And checkout to a new branch:
+
+```git checkout -b my_new_feature```
+
+Now open the generated txt2 folder with a code editor of your choice and make your edits.
+Then stage your changes:
+
+```git add .```
+
+And commit and push:
+
+```
+git commit -m "My New Feature"
+git push
+```
+
+Now create a pull request on the txt2 repo.
diff --git a/docs/FEATURES.md b/docs/FEATURES.md
index 6365979..c0c1df3 100644
--- a/docs/FEATURES.md
+++ b/docs/FEATURES.md
@@ -1,17 +1,17 @@
-# v0.2
-
-## File
-- Edit
-- Save / Save As
-- Open
-- Create New
-- Change Extension
-
-## Hotkeys (DISABLED)
-- `ctrl+s` to save
-- `ctrl+o` to open
-
-## UI
-- Window is made slightly smaller than, and placed in the middle of, the screen at startup
-- Display save directory of opened file at bottom of screen
-- Menu bar with dropdown menus
+# v0.2
+
+## File
+- Edit
+- Save / Save As
+- Open
+- Create New
+- Change Extension
+
+## Hotkeys (DISABLED)
+- `ctrl+s` to save
+- `ctrl+o` to open
+
+## UI
+- Window is made slightly smaller than, and placed in the middle of, the screen at startup
+- Display save directory of opened file at bottom of screen
+- Menu bar with dropdown menus
diff --git a/docs/README.md b/docs/README.md
index 61fdbf5..9cbe5a2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,13 +1,13 @@
-
-
-# Futura Notes
-
-
-
-
-
-A text editor in python!
-
-Modern and native look thanks to rdbendes [Sun Valley ttk theme](https://github.com/rdbende/Sun-Valley-ttk-theme)
-
-You can find more information in the [wiki](https://github.com/not-nef/onyx/wiki)
+
+
+# Futura Notes
+
+
+
+
+
+A text editor in python!
+
+Modern and native look thanks to rdbendes [Sun Valley ttk theme](https://github.com/rdbende/Sun-Valley-ttk-theme)
+
+You can find more information in the [wiki](https://github.com/not-nef/onyx/wiki)
diff --git a/requirements.txt b/requirements.txt
index d07c363..f40cc47 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,13 @@
-sv-ttk >= 2.4
-ntkutils
-darkdetect
-pywin32; sys_platform == 'win32'
-chlorophyll
-markdown
-tkinterweb
-tkinterdnd2
-tklinenums >= 1.5
-requests; sys_platform != 'linux'
-pygments
-pyperclip
+sv-ttk >= 2.4
+ntkutils
+darkdetect
+pywin32; sys_platform == 'win32'
+chlorophyll
+markdown
+tkinterweb
+tkinterdnd2
+tklinenums >= 1.5
+requests; sys_platform != 'linux'
+pygments
+pyperclip
+pillow == 10.0.0 # tkinterweb requires version 10.0.0
diff --git a/src/config.py b/src/config.py
deleted file mode 100644
index 04208fd..0000000
--- a/src/config.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import json
-
-import ntkutils.cfgtools as cfgtools
-
-from generatesize import system
-
-default_win = {
- "theme": "Dark",
- "font": "Helvetica",
- "font-size": 11,
- "mica": False,
- "hkey-open": "Control-o",
- "hkey-save": "Control-s",
- "linenumbers": True,
- "syntax-highlighting": False,
- "onclose": "Ask",
-}
-
-default_mac = {
- "theme": "Dark",
- "font": "Helvetica",
- "font-size": 11,
- "mica": False,
- "hkey-open": "Command-o",
- "hkey-save": "Command-s",
- "linenumbers": True,
- "syntax-highlighting": False,
-}
-
-# Update Config if settings are missing
-try:
- config_file = open("cfg.json")
- cfg = json.load(config_file)
- config_file.close()
-
- if len(cfg) != len(default_win):
- temp_cfg = default_win.copy()
- for i in cfg:
- temp_cfg.pop(i)
- for i in temp_cfg:
- cfg[i] = temp_cfg[i]
- cfgtools.SaveCFG(cfg)
-except FileNotFoundError:
- pass
-
-
-def get():
- if system != "Darwin":
- return cfgtools.init(default_win)
- else:
- return cfgtools.init(default_mac)
diff --git a/src/dark.toml b/src/dark.toml
new file mode 100644
index 0000000..6f4800a
--- /dev/null
+++ b/src/dark.toml
@@ -0,0 +1,79 @@
+[editor]
+bg = "#1c1c1c"
+fg = "#ffffff"
+select_bg = "#1b2733"
+inactive_select_bg = "#1b2733"
+caret = "#b3b1ad"
+caret_width = 1
+border_width = 0
+focus_border_width = 0
+
+[general]
+comment = "#626a73"
+error = "#ff3333"
+escape = "#b3b1ad"
+keyword = "#ff7700"
+name = "#ff8f40"
+string = "#95e6cb"
+punctuation = "#b3b1ad"
+
+[keyword]
+constant = "#ff7700"
+declaration = "#ff7700"
+namespace = "#ff7700"
+pseudo = "#ff7700"
+reserved = "#ff7700"
+type = "#ff7700"
+
+[name]
+attr = "#ff8f40"
+builtin = "#e6b450"
+builtin_pseudo = "#e6b450"
+class = "#ff8f40"
+class_variable = "#ff8f40"
+constant = "#ffee99"
+decorator = "#e6b673"
+entity = "#ff8f40"
+exception = "#ff8f40"
+function = "#ffb454"
+global_variable = "#ff8f40"
+instance_variable = "#ff8f40"
+label = "#ff8f40"
+magic_function = "#ff8f40"
+magic_variable = "#ff8f40"
+namespace = "#b3b1ad"
+tag = "#ff8f40"
+variable = "#ff8f40"
+
+[operator]
+symbol = "#f29668"
+word = "#f29668"
+
+[string]
+affix = "#c2d94c"
+char = "#95e6cb"
+delimeter = "#c2d94c"
+doc = "#c2d94c"
+double = "#c2d94c"
+escape = "#c2d94c"
+heredoc = "#c2d94c"
+interpol = "#c2d94c"
+regex = "#95e6cb"
+single = "#c2d94c"
+symbol = "#c2d94c"
+
+[number]
+binary = "#e6b450"
+float = "#e6b450"
+hex = "#e6b450"
+integer = "#e6b450"
+long = "#e6b450"
+octal = "#e6b450"
+
+[comment]
+hashbang = "#626a73"
+multiline = "#626a73"
+preproc = "#ff7700"
+preprocfile = "#c2d94c"
+single = "#626a73"
+special = "#626a73"
\ No newline at end of file
diff --git a/src/dialogs.py b/src/dialogs.py
new file mode 100644
index 0000000..5ff3266
--- /dev/null
+++ b/src/dialogs.py
@@ -0,0 +1,190 @@
+# Property of rdbende licensed under the MIT License
+# https://github.com/rdbende/Sun-Valley-messageboxes/blob/main/dialogs.py
+
+import sys
+import tkinter as tk
+from tkinter import ttk
+from functools import partial
+
+
+def popup(parent, title, details, icon, *, buttons):
+ dialog = tk.Toplevel()
+
+ result = None
+
+ big_frame = ttk.Frame(dialog)
+ big_frame.pack(fill="both", expand=True)
+ big_frame.columnconfigure(0, weight=1)
+ big_frame.rowconfigure(0, weight=1)
+
+ info_frame = ttk.Frame(big_frame, padding=(10, 12), style="Dialog_info.TFrame")
+ info_frame.grid(row=0, column=0, sticky="nsew")
+ info_frame.columnconfigure(1, weight=1)
+ info_frame.rowconfigure(1, weight=1)
+
+ try:
+ color = big_frame.tk.call("set", "themeColors::dialogInfoBg")
+ except tk.TclError:
+ color = big_frame.tk.call("ttk::style", "lookup", "TFrame", "-background")
+
+ icon_label = ttk.Label(info_frame, image=icon, anchor="nw", background=color)
+ if icon is not None:
+ icon_label.grid(
+ row=0, column=0, sticky="nsew", padx=(12, 0), pady=10, rowspan=2
+ )
+
+ title_label = ttk.Label(
+ info_frame, text=title, anchor="nw", font=("", 14, "bold"), background=color
+ )
+ title_label.grid(row=0, column=1, sticky="nsew", padx=(12, 17), pady=(10, 8))
+
+ detail_label = ttk.Label(info_frame, text=details, anchor="nw", background=color)
+ detail_label.grid(row=1, column=1, sticky="nsew", padx=(12, 17), pady=(5, 10))
+
+ button_frame = ttk.Frame(
+ big_frame, padding=(22, 22, 12, 22), style="Dialog_buttons.TFrame"
+ )
+ button_frame.grid(row=2, column=0, sticky="nsew")
+
+ def on_button(value):
+ nonlocal result
+ result = value
+ dialog.destroy()
+
+ for index, button_value in enumerate(buttons):
+ style = None
+ state = None
+ default = False
+ sticky = "nes" if len(buttons) == 1 else "nsew"
+
+ if len(button_value) > 2:
+ if button_value[2] == "accent":
+ style = "Accent.TButton"
+ default = True
+ elif button_value[2] == "disabled":
+ state = "disabled"
+ elif button_value[2] == "default":
+ default = True
+
+ button = ttk.Button(
+ button_frame,
+ text=button_value[0],
+ width=18,
+ command=partial(on_button, button_value[1]),
+ style=style,
+ state=state,
+ )
+ if default:
+ button.bind("
", button["command"])
+ button.focus()
+
+ button.grid(row=0, column=index, sticky=sticky, padx=(0, 10))
+
+ button_frame.columnconfigure(index, weight=1)
+
+ if sys.platform == "win32":
+ transparent_color = big_frame.tk.call("ttk::style", "lookup", "TFrame", "-background")
+ dialog.wm_attributes("-transparentcolor", transparent_color)
+
+ # dialog.overrideredirect(True)
+ dialog.update_idletasks()
+
+ dialog_width = dialog.winfo_width()
+ dialog_height = dialog.winfo_height()
+
+ if parent is None:
+ parent_width = dialog.winfo_screenwidth()
+ parent_height = dialog.winfo_screenheight()
+ parent_x = 0
+ parent_y = 0
+ else:
+ parent_width = parent.winfo_width()
+ parent_height = parent.winfo_height()
+ parent_x = parent.winfo_x()
+ parent_y = parent.winfo_y()
+
+ x_coord = int(parent_width / 2 + parent_x - dialog_width / 2)
+ y_coord = int(parent_height / 2 + parent_y - dialog_height / 2)
+
+ dialog.geometry("+{}+{}".format(x_coord, y_coord))
+ dialog.minsize(320, dialog_height)
+
+ dialog.transient(parent)
+ dialog.wm_attributes("-type", "dialog")
+ dialog.grab_set()
+
+ dialog.wait_window()
+ return result
+
+
+def show_message(title="Title", details="Description", *, parent=None, icon=None):
+ return popup(
+ parent,
+ title,
+ details,
+ icon,
+ buttons=[("Ok", None, "default")],
+ )
+
+
+def ask_ok_cancel(title="Title", details="Description", *, parent=None, icon=None):
+ return popup(
+ parent,
+ title,
+ details,
+ icon,
+ buttons=[("Ok", True, "accent"), ("Cancel", None)],
+ )
+
+
+def ask_yes_no(title="Title", details="Description", *, parent=None, icon=None):
+ return popup(
+ parent,
+ title,
+ details,
+ icon,
+ buttons=[("Yes", True, "accent"), ("No", False)],
+ )
+
+
+def ask_yes_no_cancel(title="Title", details="Description", *, parent=None, icon=None):
+ return popup(
+ parent,
+ title,
+ details,
+ icon,
+ buttons=[("Yes", True, "accent"), ("No", False), ("Cancel", None)],
+ )
+
+
+def ask_retry_cancel(title="Title", details="Description", *, parent=None, icon=None):
+ return popup(
+ parent,
+ title,
+ details,
+ icon,
+ buttons=[("Retry", True, "accent"), ("Cancel", None)],
+ )
+
+
+def ask_allow_block(title="Title", details="Description", *, parent=None, icon=None):
+ return popup(
+ parent,
+ title,
+ details,
+ icon,
+ buttons=[("Allow", True, "accent"), ("Block", False)],
+ )
+
+
+if __name__ == "__main__":
+ window = tk.Tk()
+
+ window.tk.call("source", "sun-valley.tcl")
+ window.tk.call("set_theme", "dark")
+
+ window.geometry("600x600")
+
+ show_message("No WiFi connection", "Check your connection and try again.")
+
+ window.mainloop()
\ No newline at end of file
diff --git a/src/editor.py b/src/editor.py
index 0466a05..2e8a6ff 100644
--- a/src/editor.py
+++ b/src/editor.py
@@ -1,212 +1,148 @@
-import tkinter
-from pathlib import Path
-from tkinter import ttk
-from tkinter.font import Font
-
-import chlorophyll
-import darkdetect
-import ntkutils
-import pygments
-import pyperclip
-from tkinterdnd2 import *
-from tklinenums import TkLineNumbers
-
-import mdpreview as md
-import pages.about as about
-import pages.wanttosave as w
-import settings.UI as settingsui
-import tabmanager
-import vars as v
-from settings.images import setimages
-
-
-def build(theme, root, ver):
- closeimg = tkinter.PhotoImage(file=Path(theme["closeimg"]))
-
- def closepreview():
- md.close()
- textwidget.bind("", refreshtitle)
- if v.cfg["linenumbers"]:
- textwidget.bind(
- f"", lambda event: root.after_idle(linenums.redraw), add=True
- )
-
- notebook = ttk.Notebook(root)
- notebook.pack(fill="both", expand=True)
-
- footer = tkinter.Frame(root, width=root.winfo_width(), height=25)
- footer.update_idletasks()
- footer.pack(side="bottom", fill="x")
- footer.pack_propagate(False)
-
- scrollbar = ttk.Scrollbar(root)
- scrollbar.pack(side="right", fill="y")
-
- if v.cfg["syntax-highlighting"]:
- textwidget = chlorophyll.CodeView(
- root,
- height=800,
- bg=theme["primary"],
- lexer=pygments.lexers.TextLexer,
- font=(v.cfg["font"], int(v.cfg["font-size"])),
- )
- textwidget._set_color_scheme(theme["color_scheme"])
- textwidget.pack(side="right", fill="both", expand=True)
- textwidget._hs.grid_remove()
- textwidget._vs.grid_remove()
- else:
- textwidget = tkinter.Text(
- root,
- width=100,
- borderwidth=0,
- height=root.winfo_height() - 125,
- font=(v.cfg["font"], int(v.cfg["font-size"])),
- )
- textwidget.pack(side="right", fill="both", expand=True)
-
- textwidget.update()
-
- scrollbar.configure(command=textwidget.yview)
- textwidget["yscrollcommand"] = scrollbar.set
-
- if v.cfg["linenumbers"]:
- style = ttk.Style()
- style.configure(
- "TLineNumbers",
- background=theme["primary"],
- foreground=theme["opposite_secondary"],
- )
-
- font = Font(
- family="Courier New bold", size=v.cfg["font-size"], name="TkLineNumsFont"
- )
-
- linenums = TkLineNumbers(root, textwidget, font, "right")
- linenums.pack(side="left", fill="y")
- linenums.configure(borderwidth=0)
- linenums.reload(font)
-
- textwidget.bind(
- "", lambda event: root.after_idle(linenums.redraw), add=True
- )
- textwidget.bind(
- f"", lambda event: root.after_idle(linenums.redraw), add=True
- )
- textwidget.bind(
- f"", lambda event: root.after_idle(linenums.redraw), add=True
- )
-
- def onscroll(first, last):
- scrollbar.set(first, last)
- linenums.redraw()
-
- textwidget["yscrollcommand"] = onscroll
-
- textwidget.linenums = linenums
-
- filedir = tkinter.Label(footer, text="unsaved")
- filedir.pack(side="left")
-
- menubar = tkinter.Menu(root)
- root.config(menu=menubar)
-
- filemenu = tkinter.Menu(menubar, tearoff=False, bg="white")
- settingsmenu = tkinter.Menu(menubar, tearoff=False, bg="white")
-
- menubar.add_cascade(label="File", menu=filemenu)
- menubar.add_cascade(label="Settings", menu=settingsmenu)
-
- filemenu.add_command(label="Save ({})".format(v.cfg["hkey-save"]), command=tabmanager.save, foreground="black" )
- filemenu.add_command(label="Save As", command=lambda: tabmanager.save(saveas=True), foreground="black")
- filemenu.add_command(label="Open ({})".format(v.cfg["hkey-open"]), command=tabmanager.openfile, foreground="black")
- filemenu.add_command(label="New", command=tabmanager.new, foreground="black")
- filemenu.add_separator()
- filemenu.add_command(label="Change file extension", command=tabmanager.changetype, foreground="black")
- filemenu.add_separator()
- filemenu.add_command(label="Preview Markdown", command=md.build, foreground="black")
- filemenu.add_command(label="Close Preview", command=closepreview, foreground="black")
-
- settingsmenu.add_command(label="Open Settings", command=settingsui.build, foreground="black")
- settingsmenu.add_command(label="About", command=about.build, foreground="black")
-
- if v.cfg["mica"]:
- if v.cfg["theme"] == "Dark" or (v.cfg["theme"] == "System" and darkdetect.isDark()):
- notebook.configure(bg="#1c1c1c")
- ntkutils.blur_window_background(root, dark=True)
- textwidget.text.configure(bg="#1b1c1b")
- try:
- textwidget.numberLines.configure(bg="#1b1c1b")
- except:
- pass
- else:
- ntkutils.blur_window_background(root)
- textwidget.text.configure(bg="#fafbfa")
-
- def refreshtitle(e):
- if not root.wm_title().endswith("*"):
- root.title(root.wm_title() + "*")
- tabmanager.tabs[v.tabselected][3] = "*"
-
- textwidget.bind("", refreshtitle)
-
- root.event_add("<>", "<{}>".format(v.cfg["hkey-open"]))
- root.event_add("<>", "<{}>".format(v.cfg["hkey-save"]))
-
- root.bind("<>", tabmanager.openfile)
- root.bind("<>", tabmanager.save)
-
- def filedrop(event):
- tabmanager.openfile(path=event.data)
-
- root.drop_target_register(DND_FILES)
- root.dnd_bind("<>", filedrop)
-
- def cut():
- pyperclip.copy(textwidget.selection_get())
- textwidget.delete("sel.first", "sel.last")
-
- def copy():
- pyperclip.copy(textwidget.selection_get())
-
- def paste():
- textwidget.insert("insert", pyperclip.paste())
-
- def popup(event):
- try:
- context.tk_popup(event.x_root, event.y_root, 0)
- finally:
- context.grab_release()
-
- context = tkinter.Menu(root, tearoff=False, bg="white")
- context.add_command(label="Cut", command=cut, foreground="black")
- context.add_command(label="Copy", command=copy, foreground="black")
- context.add_command(label="Paste", command=paste, foreground="black")
- root.bind("", popup)
-
- def on_closing():
- if v.cfg["onclose"] == "Do Nothing":
- root.destroy()
- elif v.cfg["onclose"] == "Save":
- tabmanager.save()
- root.destroy()
- else:
- w.build()
-
-
- root.protocol("WM_DELETE_WINDOW", on_closing)
-
- # Set global variables
- v.root = root
- v.textwidget = textwidget
- v.filedir = filedir
- v.tabbar = notebook
- v.footer = footer
- v.closeimg = closeimg
- v.theme = theme
- v.ver = ver
-
- setimages()
-
- notebook.add(tkinter.Frame(), text=tabmanager.tabs[0][0], image=closeimg, compound="right")
- # Bind Left mouse button to write content of selected tab into the text widget
- notebook.bind("", tabmanager.click, add="+")
+from markdown import markdown
+from os.path import basename, isfile
+from tkfilebrowser import askopenfilename, asksaveasfilename
+from tkinter import Frame, Label, PhotoImage
+from tkinter.ttk import Button, Notebook, Style
+from tkinterweb import HtmlFrame
+from toml import load
+
+from chlorophyll import CodeView
+from pygments.lexers import TextLexer, get_lexer_for_filename
+from pygments.util import ClassNotFound
+
+
+class Manager(Notebook):
+ def __init__(self, theme, *args):
+ super().__init__(*args)
+
+ self.theme = theme
+
+ self.closeimg = PhotoImage(file="assets/close_{}.png".format(theme))
+
+ # Remove dotted line :O
+ self.style = Style()
+ self.style.configure("TNotebook.Tab", focuscolor=self.style.configure(".")["background"])
+
+ self.home = Frame(self)
+ self.title = Label(self.home, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20)
+ self.btncreatenew = Button(self.home, text="Create New File", command=self.newtab).pack(anchor="nw", padx=20)
+ self.btnopen = Button(self.home, text="Open File", command=self.openfile).pack(anchor="nw", padx=20, pady=20)
+ self.add(self.home, text="Home", image=self.closeimg, compound="right")
+
+ self.bind("", self.on_click, add=True)
+
+ def newtab(self, file=None):
+ self.add(Editor(file, self.theme), text="Untitled" if file==None else basename(file.name), image=self.closeimg, compound="right")
+ self.select(self.tabs()[-1]) # Select newly opened tab
+
+ def openfile(self):
+ self.file = open(askopenfilename(), "r")
+ self.newtab(self.file)
+ self.file.close()
+
+ def save(self):
+ self.editor = self.getcurrentchild()
+ self.filetosave = self.editor.filedir.cget("text")
+ if isfile(self.filetosave):
+ self.file2 = open(self.filetosave, "w")
+ self.file2.write(self.editor.text.get("1.0", "end"))
+ self.file2.close()
+ else:
+ self.saveas()
+
+ def saveas(self):
+ self.editor = self.getcurrentchild()
+ self.file3 = open(asksaveasfilename(), "w")
+ if self.file3 != None:
+ self.file3.write(self.editor.text.get("1.0", "end"))
+ self.editor.filedir.configure(text=self.file3.name)
+ self.tab(self.select(), text=basename(self.file3.name))
+ self.file3.close()
+
+ def getcurrentchild(self):
+ return self.nametowidget(self.select())
+
+
+ # The next two functions are heavily inspired by Akuli:
+ # https://github.com/Akuli/porcupine/blob/main/porcupine/plugins/tab_closing.py
+ def closetab(self, event):
+ self.before = self.index(f"@{event.x},{event.y}")
+ self.after = self.before + 1
+ self.forget(self.tabs()[self.before:self.after][0])
+ if len(self.tabs()) == 0: self.master.destroy()
+
+ def on_click(self, event) -> None:
+ self.update_idletasks()
+ if event.widget.identify(event.x, event.y) == "label":
+ # find the right edge of the top label (including close button)
+ right = event.x
+ while event.widget.identify(right, event.y) == "label":
+ right += 1
+
+ # hopefully the image is on the right edge of the label and there's no padding :O
+ if event.x >= right - self.closeimg.width():
+ self.closetab(event)
+
+ def openpreview(self):
+ self.nametowidget(self.select()).openpreview()
+
+
+class Editor(Frame):
+ def __init__(self, file, theme, *args):
+ super().__init__(*args)
+
+ self.ispreviewed = False
+
+ self.footer = Frame(self, width=self.winfo_width(), height=25)
+ self.footer.pack(side="bottom", fill="x")
+ self.footer.pack_propagate(False)
+
+ self.filedir = Label(self.footer, text="unsaved")
+ self.filedir.pack(side="left")
+
+ self.text = CodeView(self)
+ self.text.pack(side="left", fill="both", expand=True)
+ self.text._hs.grid_remove()
+
+ self.color_scheme = load("src/{}.toml".format(theme))
+ self.text._set_color_scheme(self.color_scheme)
+
+ self.text._line_numbers.configure(borderwidth=0)
+
+ if file != None:
+ self.text.insert("1.0", file.read())
+ self.filedir.configure(text=file.name)
+ self.text._set_lexer(self.get_lexer(file))
+ file.close()
+
+ def get_lexer(self, file):
+ try:
+ lexer = get_lexer_for_filename(file.name)
+ except ClassNotFound:
+ lexer = TextLexer
+
+ return lexer
+
+ def openpreview(self):
+ if not self.ispreviewed:
+ self.ispreviewed = True
+ self.update()
+ self.preview = HtmlFrame(self, messages_enabled = False, width = int(self.winfo_width() / 2), vertical_scrollbar=False)
+ self.preview.pack_propagate = False
+ self.preview.pack(side="right", fill = "both", expand=True)
+ self.preview.on_link_click(self.updatepreview)
+
+ self.text.bind("", self.updatepreview, add=True)
+ self.text.bind("", self.updatepreview, add=True)
+ self.updatepreview()
+ else:
+ self.ispreviewed = False
+ self.preview.destroy()
+
+ def updatepreview(self, *args):
+ if self.ispreviewed:
+ self.after_idle(lambda: self.preview.load_html(markdown(self.text.get("1.0", "end"))))
+
+
+
+
diff --git a/src/generatesize.py b/src/generatesize.py
deleted file mode 100644
index 5136cd9..0000000
--- a/src/generatesize.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import platform
-
-system = platform.system()
-if system == "Windows":
- from win32api import GetMonitorInfo, MonitorFromPoint
-
- def get():
- monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0)))
- work_area = monitor_info.get("Work")
- return "{}x{}".format(work_area[2] - 40, work_area[3] - 80)
-
-else:
- import tkinter
-
- root = tkinter.Tk()
- root.withdraw()
- WIDTH, HEIGHT = root.winfo_screenwidth(), root.winfo_screenheight()
- root.destroy()
-
- def get():
- return "{}x{}".format(WIDTH, HEIGHT - 100)
diff --git a/src/light.toml b/src/light.toml
new file mode 100644
index 0000000..2df0ac8
--- /dev/null
+++ b/src/light.toml
@@ -0,0 +1,79 @@
+[editor]
+bg = "#fafafa"
+fg = "#000000"
+select_bg = "#1b2733"
+inactive_select_bg = "#1b2733"
+caret = "#b3b1ad"
+caret_width = 1
+border_width = 0
+focus_border_width = 0
+
+[general]
+comment = "#626a73"
+error = "#ff3333"
+escape = "#b3b1ad"
+keyword = "#ff7700"
+name = "#ff8f40"
+string = "#95e6cb"
+punctuation = "#b3b1ad"
+
+[keyword]
+constant = "#ff7700"
+declaration = "#ff7700"
+namespace = "#ff7700"
+pseudo = "#ff7700"
+reserved = "#ff7700"
+type = "#ff7700"
+
+[name]
+attr = "#ff8f40"
+builtin = "#e6b450"
+builtin_pseudo = "#e6b450"
+class = "#ff8f40"
+class_variable = "#ff8f40"
+constant = "#ffee99"
+decorator = "#e6b673"
+entity = "#ff8f40"
+exception = "#ff8f40"
+function = "#ffb454"
+global_variable = "#ff8f40"
+instance_variable = "#ff8f40"
+label = "#ff8f40"
+magic_function = "#ff8f40"
+magic_variable = "#ff8f40"
+namespace = "#b3b1ad"
+tag = "#ff8f40"
+variable = "#ff8f40"
+
+[operator]
+symbol = "#f29668"
+word = "#f29668"
+
+[string]
+affix = "#c2d94c"
+char = "#95e6cb"
+delimeter = "#c2d94c"
+doc = "#c2d94c"
+double = "#c2d94c"
+escape = "#c2d94c"
+heredoc = "#c2d94c"
+interpol = "#c2d94c"
+regex = "#95e6cb"
+single = "#c2d94c"
+symbol = "#c2d94c"
+
+[number]
+binary = "#e6b450"
+float = "#e6b450"
+hex = "#e6b450"
+integer = "#e6b450"
+long = "#e6b450"
+octal = "#e6b450"
+
+[comment]
+hashbang = "#626a73"
+multiline = "#626a73"
+preproc = "#ff7700"
+preprocfile = "#c2d94c"
+single = "#626a73"
+special = "#626a73"
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index 068651a..6b7c554 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,83 +1,138 @@
-ver = "0.9 beta"
-
-import os
-import tkinter
-from tkinter import ttk
-
-import ntkutils
-import sv_ttk
-from tkinterdnd2 import *
-
-import config
-import editor
-import generatesize as size
-import settings.UI as settings
-import tabmanager
-import utils as u
-import vars as v
-from themes import dark, light
-
-v.cfg = config.get()
-
-if u.dark():
- theme = dark.get()
-else:
- theme = light.get()
-
-root = TkinterDnD.Tk()
-root.geometry("200x350")
-root.withdraw()
-ntkutils.windowsetup(root, title="Futura Notes", resizeable=False)
-sv_ttk.set_theme(v.cfg["theme"].lower())
-root.update_idletasks()
-ntkutils.placeappincenter(root)
-root.update_idletasks()
-
-
-def preparewindow():
- root.title("Futura Notes - Untitled *")
- ntkutils.clearwin(root)
- root.geometry(size.get())
- root.update()
- ntkutils.placeappincenter(root)
- root.resizable(True, True)
- editor.build(theme, root, ver)
-
-
-def openfile(path):
- preparewindow()
- tabmanager.openfile(path=path)
-
-def settingss():
- preparewindow()
- settings.build()
-
-
-title = tkinter.Label(root, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20)
-btncreatenew = ttk.Button(root, text="Create New File", command=preparewindow).pack(anchor="nw", padx=20)
-btnopenfile = ttk.Button(root, text="Open File", command=lambda: openfile(path="")).pack(anchor="nw", pady=10, padx=20)
-btnopendir = ttk.Button(root, text="Open Directory", state="disabled").pack(anchor="nw", padx=20)
-btnopenlast = ttk.Button(root, text="Open last file", command=lambda: openfile(path=content))
-btnopenlast.pack(anchor="nw", padx=20, pady=20)
-
-if os.path.isfile("lastfile.txt"):
- file = open("lastfile.txt", "r")
- content = file.read()
- file.close()
-
- if not os.path.isfile(content):
- btnopenlast.configure(state="disabled")
-else:
- btnopenlast.configure(state="disabled")
-
-root.update_idletasks()
-root.deiconify()
-root.mainloop()
-
-# Save path of last opened file
-content = tabmanager.tabs[v.tabselected][2]
-
-if content != "unsaved":
- file = open("lastfile.txt", "w+")
- file.write(content)
- file.close()
+from os import rename
+from os.path import isfile
+from tkinter import Menu, PhotoImage, Toplevel, Label, Frame
+from tkinter.ttk import Entry, Button
+from tkinterdnd2 import Tk, DND_FILES
+
+from sv_ttk import set_theme
+
+from dialogs import show_message
+from editor import Manager
+from platform import system
+
+BLOCKEDCHARS = "\\/:*?\"<>|"
+
+if system() == "Linux": LINUX = True
+else: LINUX = False
+
+theme = "dark"
+newfile = ""
+
+class App(Tk):
+ def __init__(self):
+ super().__init__()
+
+ self.title("Futura Notes")
+ set_theme(theme)
+
+ self.checkimg = PhotoImage(file="assets/check_light.png")
+
+ self.h = self.winfo_screenheight() - 200
+ self.w = self.winfo_screenwidth() - 100
+ self.x = int((self.winfo_screenwidth() - self.w) / 2)
+ self.y = int((self.winfo_screenheight() - self.h - 75) / 2)
+
+ self.geometry("{}x{}+{}+{}".format(self.w, self.h, self.x, self.y))
+
+ self.manager = Manager(theme, self)
+ self.manager.pack(fill="both", expand=True)
+ self.menubar = Menu(self, tearoff=False)
+ self.config(menu=self.menubar)
+
+ self.filemenu = Menu(self.menubar, tearoff=False)
+
+ self.menubar.add_cascade(label="File", menu=self.filemenu)
+
+ self.filemenu.add_command(label="New", command=self.manager.newtab, background="white", foreground="black")
+ self.filemenu.add_command(label="Open", command=self.manager.openfile, background="white", foreground="black")
+ self.filemenu.add_command(label="Save", command=self.manager.save, background="white", foreground="black")
+ self.filemenu.add_command(label="Save As", command=self.manager.saveas, background="white", foreground="black")
+ self.filemenu.add_separator(background="white")
+ self.filemenu.add_command(label="Preview", command=self.openpreview, background="white", foreground="black", compound="right")
+ self.filemenu.add_separator(background="white")
+ self.filemenu.add_command(label="Properties", command=self.openproperties, background="white", foreground="black")
+
+ self.drop_target_register(DND_FILES)
+ self.dnd_bind("<>", self.filedrop)
+
+ def openpreview(self):
+ self.manager.openpreview()
+ if self.manager.getcurrentchild().ispreviewed:
+ self.filemenu.entryconfigure(5, image=self.checkimg)
+ else: self.filemenu.entryconfigure(6, image="")
+
+ def filedrop(self, event):
+ self.file = open(event.data.replace("{", "").replace("}", ""), "r")
+ self.manager.newtab(self.file)
+ self.file.close()
+
+ def openproperties(self):
+ self.filetoopen = self.manager.getcurrentchild().filedir.cget("text")
+
+ if isfile(self.filetoopen):
+ self.properties = Properties(self.filetoopen, self)
+ self.wait_window(self.properties)
+
+ if isfile(newfile):
+ self.manager.forget(self.manager.select())
+ self.manager.newtab(open(newfile, "r"))
+
+class Properties(Toplevel):
+ def __init__(self, file, *args):
+ super().__init__(*args)
+
+ self.file = file
+
+ self.title("File Properties")
+ self.geometry("350x175")
+ self.resizable(False, False)
+
+ self.imagefile = "assets/filetypes/{}_{}.png".format(file.split(".")[-1], theme)
+ if isfile(self.imagefile): self.image = PhotoImage(file=self.imagefile)
+ else: self.image = PhotoImage(file="assets/filetypes/other_{}.png".format(theme))
+ self.imagelabel = Label(self, image=self.image).place(x=5, y=5)
+
+ self.filename = Entry(self, width=25)
+ self.filename.insert(0, file.split("/")[-1])
+ self.filename.pack(anchor="ne", padx=15, pady=20)
+
+ self.filepath = Label(self, text="/".join(file.split("/")[:-1]), font=("Segoe UI", 10), width=27, anchor="w")
+ self.filepath.pack(anchor="ne", padx=15)
+
+ self.btnframe = Frame(self, width=320, height=40)
+ self.btnframe.pack_propagate(False)
+
+ self.cancelbtn = Button(self.btnframe, text="Cancel", command=self.cancel, width=14).pack(side="left")
+ self.applybtn = Button(self.btnframe, text="Apply", command=self.apply, width=14).pack(side="right")
+
+ self.btnframe.pack(anchor="nw", padx=15, pady=15)
+
+ def cancel(self):
+ global newfile
+
+ newfile = ""
+
+ self.destroy()
+
+ def apply(self):
+ global newfile # im sorry
+
+ for i in BLOCKEDCHARS:
+ if i in self.filename.get():
+ show_message(title="Invalid File Name", details="\nA File Name cannot contain one of the following characters:\n\n{}".format(BLOCKEDCHARS))
+ return
+
+ newfile = self.file.split("/")
+ newfile[-1] = self.filename.get()
+ newfile = "/".join(newfile)
+
+ rename(self.file, newfile)
+
+ self.destroy()
+
+
+
+
+if __name__ == "__main__":
+ main = App()
+ main.mainloop()
diff --git a/src/mdpreview.py b/src/mdpreview.py
deleted file mode 100644
index 1a36dec..0000000
--- a/src/mdpreview.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import markdown
-from tkinterweb.htmlwidgets import HtmlFrame
-
-import vars as v
-
-
-def update():
- html = markdown.markdown(v.textwidget.get("1.0", "end"))
- display.load_html(html)
-
-
-def reload(e):
- v.root.after(2, update)
-
-
-def build():
- global display, binding
-
- display = HtmlFrame(v.root, messages_enabled=False)
- display.place(
- x=v.root.winfo_width() / 2,
- y=50,
- width=v.root.winfo_width() / 2,
- height=v.root.winfo_height() - 75,
- )
- display.on_link_click(reload) # This line blocks clicking on links
-
- v.textwidget.bind("", reload, add="+")
- v.textwidget.bind("", reload, add="+")
- reload("")
-
-
-def close():
- v.textwidget.unbind("")
- v.textwidget.unbind("")
- display.destroy()
diff --git a/src/pages/about.py b/src/pages/about.py
deleted file mode 100644
index 867d548..0000000
--- a/src/pages/about.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import tkinter
-import webbrowser
-from tkinter import messagebox, ttk
-
-import update
-import utils as u
-import vars as v
-
-
-def checkforupdates():
- response = update.check()
-
- if response == True: webbrowser.open("https://github.com/futura-py/notes/releases")
- elif response == False: messagebox.showinfo(title="Update", message="You are on the newest version of Futura Notes!")
- 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.",)
-
-
-def build():
- root = tkinter.Toplevel()
- root.title("About Futura Notes")
- root.geometry("650x200")
- root.resizable(False, False)
-
- name = tkinter.Label(root, text="Futura Notes", font=("Segoe UI", 40, "bold")).place(x=30, y=15)
- version = tkinter.Label(root, text="Version {}".format(v.ver.split(" ")[0]), font=("Segoe UI", 20, "")).place(x=370, y=42)
- 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)
- 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)
- 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)
diff --git a/src/pages/filetype.py b/src/pages/filetype.py
deleted file mode 100644
index 91e8809..0000000
--- a/src/pages/filetype.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import os
-import tkinter
-from tkinter import ttk
-
-import ntkutils
-
-from generatesize import system
-
-
-def changetype(filename):
- global filetype
-
- def change():
- global new_path
- if entry.get().startswith("."):
- new_path = (
- filename.removesuffix("." + filename.split(".")[-1]) + entry.get()
- )
- os.rename(filename, new_path)
- filetype.destroy()
- else:
- print("not an extension")
-
- filetype = tkinter.Toplevel()
- if system != "Darwin":
- ntkutils.dark_title_bar(filetype)
- filetype.title("Futura Notes - Change file type")
- lbl = tkinter.Label(filetype, text="Change file extension:", font=("", 20)).pack(
- pady=5
- )
- entry = ttk.Entry(filetype)
- entry.pack(pady=5)
- btn = ttk.Button(filetype, text="Apply", command=change).pack(pady=5)
-
-
-def get(path):
- changetype(path)
- filetype.wait_window()
- return new_path
diff --git a/src/pages/wanttosave.py b/src/pages/wanttosave.py
deleted file mode 100644
index c9ec0ed..0000000
--- a/src/pages/wanttosave.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import tkinter
-from tkinter import ttk
-
-import tabmanager as t
-import vars as v
-
-
-def save(e=""):
- t.save()
- v.root.destroy()
-
-
-def build():
- w = tkinter.Toplevel()
- w.geometry("300x100")
- w.title("Save before exiting?")
- w.focus_set()
-
- lbl = tkinter.Label(w, font=("Segoe UI", 10, "bold"), text="Do you want to save before exiting?").pack(pady=10)
-
- btnno = ttk.Button(w, text="No", command=v.root.destroy, width=10).place(x=25, y=50)
- btnyes = ttk.Button(w, text="Yes", command=save, width=10, style="Accent.TButton").place(x=170, y=50)
-
- w.bind("", save)
\ No newline at end of file
diff --git a/src/settings/UI.py b/src/settings/UI.py
deleted file mode 100644
index 1a603b4..0000000
--- a/src/settings/UI.py
+++ /dev/null
@@ -1,271 +0,0 @@
-import tkinter
-from tkinter import font, ttk
-
-import ntkutils
-import sv_ttk
-
-import config
-import utils as u
-import vars as v
-from generatesize import system
-
-options = [
- "Theme",
- "Font",
- "Font Size",
- "Display Line Numbers",
- "Syntax Highlighting",
- "Hotkeys",
- "Mica Blur",
-]
-
-options2 = {
- "Theme": "appearance",
- "Font": "appearance",
- "Font Size": "appearance",
- "Display Line Numbers": "general",
- "Syntax Highlighting": "general",
- "Hotkeys": "hotkeys",
- "Mica Blur": "experimental",
- "On Close": "general",
-}
-
-def general():
- global page, btnnumbers, btnhighlight, boxonclose
-
- savechanges()
- clearstates()
-
- btngeneral.configure(style="Accent.TButton")
- if u.dark(): btngeneral.configure(image=v.settings_dark)
- else: btngeneral.configure(image=v.settings_light)
-
- ntkutils.clearwin(frameright)
-
- page = "general"
-
- lblonclose = tkinter.Label(frameright, text="On Close:").place(x=10, y=15)
- boxonclose = ttk.Combobox(frameright, values=["Do Nothing", "Save", "Ask"], state="readonly", width=25)
- boxonclose.set(cfg["onclose"])
- boxonclose.pack(padx=10, pady=10, anchor="e")
-
- lblnumbers = tkinter.Label(frameright, text="Display line numbers:").place(x=10, y=60)
- btnnumbers = ttk.Checkbutton(frameright, style="Switch.TCheckbutton")
- btnnumbers.pack(padx=10, pady=10, anchor="e")
- btnnumbers.state(["!alternate"])
- if cfg["linenumbers"]: btnnumbers.state(["!alternate", "selected"])
-
- lblhighlight = tkinter.Label(frameright, text="Syntax Highlighting:").place(x=10, y=115)
- btnhighlight = ttk.Checkbutton(frameright, style="Switch.TCheckbutton")
- btnhighlight.pack(padx=10, pady=15, anchor="e")
- btnhighlight.state(["!alternate"])
- if cfg["syntax-highlighting"]: btnhighlight.state(["!alternate", "selected"])
-
-def appearance():
- global boxtheme, boxfont, boxsize, page
-
- savechanges()
- clearstates()
-
- btnappearence.configure(style="Accent.TButton", image=eval("v.brush_" + sv_ttk.get_theme()))
-
- ntkutils.clearwin(frameright)
-
- page = "appearance"
-
- lbltheme = tkinter.Label(frameright, text="Theme:").place(x=10, y=15)
- boxtheme = ttk.Combobox(frameright, values=["Dark", "Light", "System"], state="readonly", width=25)
- boxtheme.set(cfg["theme"])
- boxtheme.pack(padx=10, pady=10, anchor="e")
-
- lblfont = tkinter.Label(frameright, text="Font:").place(x=10, y=60)
- boxfont = ttk.Combobox(frameright, state="readonly", values=fonts, width=15)
- boxfont.set(cfg["font"])
- boxfont.pack(padx=70, pady=10, anchor="e")
- boxsize = ttk.Entry(frameright, width=5)
- boxsize.insert(0, cfg["font-size"])
- boxsize.place(x=265, y=58)
-
-
-def experimental():
- global page, btnmica, btnhotkeys
-
- savechanges()
- clearstates()
-
- btnexperimental.configure(style="Accent.TButton", image=eval("v.warn_" + sv_ttk.get_theme()))
-
- ntkutils.clearwin(frameright)
-
- page = "experimental"
-
- lblmica = tkinter.Label(frameright, text="Mica Blur:").place(x=10, y=12)
- btnmica = ttk.Checkbutton(frameright, style="Switch.TCheckbutton")
- btnmica.pack(padx=10, pady=10, anchor="e")
- btnmica.state(["!alternate"])
- if cfg["mica"]: btnmica.state(["!alternate", "selected"])
-
-
-def hotkeys():
- global page, entryopen, entrysave
-
- savechanges()
- clearstates()
-
- btnhotkeys.configure(style="Accent.TButton", image=eval("v.keyboard_" + sv_ttk.get_theme()))
-
- ntkutils.clearwin(frameright)
-
- page = "hotkeys"
-
- lblopen = tkinter.Label(frameright, text="Open:").place(x=10, y=12)
- entryopen = ttk.Entry(frameright, width=25)
- entryopen.insert(0, cfg["hkey-open"])
- entryopen.pack(padx=10, pady=10, anchor="e")
-
- lblsave = tkinter.Label(frameright, text="Save:").place(x=10, y=67)
- entrysave = ttk.Entry(frameright, width=25)
- entrysave.insert(0, cfg["hkey-save"])
- entrysave.pack(padx=10, pady=10, anchor="e")
-
-
-def savechanges():
- if page == "general":
- cfg["linenumbers"] = btnnumbers.instate(["selected"])
- cfg["syntax-highlighting"] = btnhighlight.instate(["selected"])
- cfg["onclose"] = boxonclose.get()
-
- if u.dark(): btngeneral.configure(image=v.settings_light)
- else: btngeneral.configure(image=v.settings_dark)
- elif page == "appearance":
- cfg["theme"] = boxtheme.get()
- cfg["font"] = boxfont.get()
- cfg["font-size"] = boxsize.get()
-
- if u.dark(): btnappearence.configure(image=v.brush_light)
- else: btnappearence.configure(image=v.brush_dark)
- elif page == "experimental":
- cfg["mica"] = btnmica.instate(["selected"])
-
- if u.dark(): btnexperimental.configure(image=v.warn_light)
- else: btnexperimental.configure(image=v.warn_dark)
- elif page == "hotkeys":
- cfg["hkey-open"] = entryopen.get()
- cfg["hkey-save"] = entrysave.get()
-
- if u.dark(): btnhotkeys.configure(image=v.keyboard_light)
- else: btnhotkeys.configure(image=v.keyboard_dark)
-
-
-def apply():
- global page, save
-
- savechanges()
-
- ntkutils.cfgtools.SaveCFG(cfg)
- settings.destroy()
-
-
-def build():
- global frameright, frameleft, btnappearence, btnexperimental, btnhotkeys, settings, page, cfg, btngeneral
-
- cfg = config.get()
- page = ""
-
- settings = tkinter.Toplevel()
- ntkutils.windowsetup(settings, "Futura Notes - Settings", "assets/logo.png", False, "500x400")
- if system != "Darwin" and u.dark():
- ntkutils.dark_title_bar(settings)
-
- frameleft = tkinter.Frame(settings, width=175, bg=v.theme["secondary"])
- frameleft.pack(side=tkinter.LEFT, fill="y")
- frameleft.pack_propagate(False)
-
- frameright = tkinter.Frame(settings, width=325)
- frameright.pack(side=tkinter.LEFT, fill="both")
- frameright.pack_propagate(False)
-
- def update(data):
- menu.delete(0, "end")
- for value in data:
- menu.insert("end", value)
- menu.configure(height=len(data))
-
- def check(e):
- v = search.get()
- if v == "":
- data = options
- else:
- data = []
- for item in options:
- if v.lower() in item.lower():
- data.append(item)
- update(data)
-
- def showlist(e):
- search.delete(0, "end")
- menu.bind("<>", onselect)
- menu.place(x=search.winfo_x(), y=search.winfo_y() + search.winfo_height())
- check("")
-
- def removelist(e):
- menu.unbind("<>")
- menu.place(x=1000, y=1000)
-
- search = ttk.Entry(frameleft, width=23)
- search.pack(pady=10)
- search.bind("", check)
- search.update()
- search.bind("", showlist)
- search.bind("", removelist)
- search.insert(0, "Search for a setting...")
-
- def onselect(evt):
- w = evt.widget
- index = int(w.curselection()[0])
- value = w.get(index)
- func = eval(options2[value])
- func()
-
- btngeneral = ttk.Button(frameleft, text="General", width=14, command=general, image=v.settings_dark, compound="left")
- btngeneral.pack(pady=10)
-
- btnappearence = ttk.Button(frameleft, text="Appearence", width=14, command=appearance, image=v.brush_dark, compound="left")
- btnappearence.pack()
-
- btnhotkeys = ttk.Button(frameleft, text="Hotkeys", width=14, command=hotkeys, image=v.keyboard_dark, compound="left")
- btnhotkeys.pack(pady=10)
-
- btnexperimental = ttk.Button(frameleft, text="Unstable Features", width=14, command=experimental, image=v.warn_dark, compound="left")
- btnexperimental.pack()
-
- btnapply = ttk.Button(frameleft, text="Apply", style="Accent.TButton", width=18, command=apply)
- btnapply.pack(side="bottom", pady=10)
-
- lblrestart = tkinter.Label(frameleft, text="Restart required!", wraplength=170, fg="grey", bg=v.theme["secondary"]).pack(side="bottom")
-
- menu = tkinter.Listbox(frameleft, width=23)
- menu.bind("<>", onselect)
-
- if u.dark():
- btngeneral.configure(image=v.settings_light)
- btnappearence.configure(image=v.brush_light)
- btnhotkeys.configure(image=v.keyboard_light)
- btnexperimental.configure(image=v.warn_light)
-
- getfonts()
- general()
-
-
-def clearstates():
- for i in frameleft.pack_slaves():
- try:
- i.configure(style="TButton")
- except:
- pass
-
-
-def getfonts():
- global fonts
- fonts = list(font.families())
- fonts.sort()
diff --git a/src/settings/images.py b/src/settings/images.py
deleted file mode 100644
index 547ec0b..0000000
--- a/src/settings/images.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import tkinter
-
-import vars as v
-
-
-def setimages():
- v.brush_light = tkinter.PhotoImage(master=v.root, file="./assets/brush_light.png")
- v.brush_dark = tkinter.PhotoImage(master=v.root, file="./assets/brush_dark.png")
- v.keyboard_light = tkinter.PhotoImage(master=v.root, file="./assets/keyboard_light.png")
- v.keyboard_dark = tkinter.PhotoImage(master=v.root, file="./assets/keyboard_dark.png")
- v.warn_light = tkinter.PhotoImage(master=v.root, file="./assets/warn_light.png")
- v.warn_dark = tkinter.PhotoImage(master=v.root, file="./assets/warn_dark.png")
- v.logo = tkinter.PhotoImage(master=v.root, file="./assets/logo.png")
- v.github_dark = tkinter.PhotoImage(master=v.root, file="./assets/github_dark.png")
- v.github_light = tkinter.PhotoImage(master=v.root, file="./assets/github_light.png")
- v.update_dark = tkinter.PhotoImage(master=v.root, file="./assets/update_dark.png")
- v.update_light = tkinter.PhotoImage(master=v.root, file="./assets/update_light.png")
- v.settings_light = tkinter.PhotoImage(master=v.root, file="./assets/settings_light.png")
- v.settings_dark = tkinter.PhotoImage(master=v.root, file="./assets/settings_dark.png")
diff --git a/src/tabmanager.py b/src/tabmanager.py
deleted file mode 100644
index 3c0acf2..0000000
--- a/src/tabmanager.py
+++ /dev/null
@@ -1,168 +0,0 @@
-import tkinter
-from tkinter import filedialog
-
-import pygments.lexers
-from pygments.lexers import get_lexer_for_filename
-
-import pages.filetype as f
-import vars as v
-
-tabs = [["Untitled", "", "unsaved", "*"]]
-
-# Item 0: Name
-# Item 1: Content
-# Item 2: Storage Path
-# Item 3: Save Status ("*" or "")
-
-
-def updatetab(file):
- tabs[v.tabselected][0] = file.name.split("/")[-1]
- tabs[v.tabselected][2] = file.name
- tabs[v.tabselected][3] = ""
-
-
-def updatetitle():
- v.root.title("Futura Notes - {} {}".format(tabs[v.tabselected][0], tabs[v.tabselected][3]))
-
-
-def redrawlinenums():
- if v.cfg["linenumbers"]:
- v.textwidget.linenums.redraw()
-
-
-def new():
- tabs[v.tabselected][1] = v.textwidget.get("1.0", "end") # Save edits
- v.textwidget.delete("1.0", "end")
- tabs.append(["Untitled", "", "unsaved", "*"])
- v.filedir.configure(text="unsaved")
- v.tabselected += 1
-
- v.tabbar.add(tkinter.Frame(), text=tabs[v.tabselected][0], image=v.closeimg, compound="right")
- v.tabbar.select(v.tabselected)
-
- updatetitle()
- if v.cfg["syntax-highlighting"]:
- v.textwidget._set_lexer(pygments.lexers.TextLexer)
-
-
-def save(e="", saveas=False):
- if tabs[v.tabselected][2] == "unsaved" or saveas:
- file = filedialog.asksaveasfile()
- if file == None:
- return
- else:
- file = open(tabs[v.tabselected][2], "w")
-
- if file != None:
- file.write(v.textwidget.get("1.0", "end"))
-
- updatetab(file)
- v.filedir.configure(text=file.name)
-
- file.close()
-
- updatetitle()
- setlexer()
-
-
-def openfile(e="", path=""):
- if path == "":
- file = filedialog.askopenfile()
- content = file.read()
- else:
- file = open(path, "r")
- content = file.read()
-
- isopen = False
-
- for i in tabs:
- if i[2] == file.name: isopen=True
-
- if not isopen:
- if v.textwidget.get("1.0", "end").replace("\n", "") != "":
- new()
-
- updatetab(file)
-
- file.close()
-
- v.tabbar.tab(v.tabselected, text=tabs[v.tabselected][0], image=v.closeimg, compound="right")
- v.textwidget.insert("1.0", content)
- v.filedir.configure(text=tabs[v.tabselected][2])
-
- updatetitle()
- setlexer()
- redrawlinenums()
-
-
-def opentab(event, tabdeleted=False):
- if not tabdeleted:
- tabs[v.tabselected][1] = v.textwidget.get("1.0", "end")
-
- v.tabselected = v.tabbar.index(v.tabbar.select())
-
- v.textwidget.delete("1.0", "end")
- v.textwidget.insert("1.0", tabs[v.tabselected][1])
- v.textwidget.delete("end-1c", "end")
-
- v.filedir.configure(text=tabs[v.tabselected][2])
-
- updatetitle()
- setlexer()
- redrawlinenums()
-
-
-def setlexer():
- if v.cfg["syntax-highlighting"]:
- try:
- lexer = get_lexer_for_filename(tabs[v.tabselected][0])
- except pygments.util.ClassNotFound:
- lexer = pygments.lexers.TextLexer
- lexer = "pygments.lexers." + str(lexer).split(".")[-1].removesuffix(">").removesuffix("'")
- v.textwidget._set_lexer(eval(lexer))
-
-
-# The following two functions contain code copied from https://github.com/Akuli/porcupine
-
-
-def closetab(event):
- before = v.tabbar.index(f"@{event.x},{event.y}")
- after = before + 1
-
- if v.tabbar.index(v.tabbar.tabs()[before:after][0]) < v.tabselected:
- v.tabselected -= 1
-
- tabs.pop(v.tabbar.index(v.tabbar.tabs()[before:after][0]))
- v.tabbar.forget(v.tabbar.tabs()[before:after][0])
- opentab(event, True)
-
-
-def click(event) -> None:
- if event.widget.identify(event.x, event.y) == "label":
- # find the right edge of the top label (including close button)
- right = event.x
- while event.widget.identify(right, event.y) == "label":
- right += 1
-
- if event.x >= right - v.closeimg.width():
- if event.widget.index("end") != 1:
- closetab(event)
- else:
- v.root.destroy()
- else:
- opentab(event)
- else:
- opentab(event)
-
-
-def changetype():
- if tabs[v.tabselected][2] == "unsaved":
- save()
- else:
- result = f.get(tabs[v.tabselected][2])
- tabs[v.tabselected][2] = result
- tabs[v.tabselected][0] = result.split("/")[-1]
- v.filedir.configure(text=result)
-
- updatetitle()
- setlexer()
diff --git a/src/themes/dark.py b/src/themes/dark.py
deleted file mode 100644
index 9cf0409..0000000
--- a/src/themes/dark.py
+++ /dev/null
@@ -1,11 +0,0 @@
-theme = {
- "primary": "#1c1c1c",
- "secondary": "#202020",
- "opposite_secondary": "#f3f3f3",
- "color_scheme": "ayu-dark",
- "closeimg": "assets/close_light.png",
-}
-
-
-def get():
- return theme
diff --git a/src/themes/light.py b/src/themes/light.py
deleted file mode 100644
index a8fea9e..0000000
--- a/src/themes/light.py
+++ /dev/null
@@ -1,11 +0,0 @@
-theme = {
- "primary": "#fafafa",
- "secondary": "#f3f3f3",
- "opposite_secondary": "#202020",
- "color_scheme": "ayu-light",
- "closeimg": "assets/close_dark.png",
-}
-
-
-def get():
- return theme
diff --git a/src/update.py b/src/update.py
deleted file mode 100644
index 801a167..0000000
--- a/src/update.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import requests
-
-import vars as v
-
-
-def install():
- pass
-
-
-def check():
- api_response = requests.get("https://api.github.com/repos/futura-py/notes/releases")
-
- try:
- latest_tag = next(iter(api_response.json()))["tag_name"]
-
- if float(str(latest_tag).removeprefix("v")) > float(v.ver.split(" ")[0]):
- return True
- else:
- return False
- except:
- return "rate limit"
diff --git a/src/utils.py b/src/utils.py
deleted file mode 100644
index 3a88d8f..0000000
--- a/src/utils.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import darkdetect
-
-import vars as v
-
-
-def dark():
- if v.cfg["theme"] == "Dark" or (v.cfg["theme"] == "System" and darkdetect.isDark()):
- return True
- else:
- return False
\ No newline at end of file
diff --git a/src/vars.py b/src/vars.py
deleted file mode 100644
index 4d28283..0000000
--- a/src/vars.py
+++ /dev/null
@@ -1,30 +0,0 @@
-tabselected = 0
-
-cfg = []
-
-root = ""
-textwidget = ""
-filedir = ""
-closeimg = ""
-normal = ""
-selected = ""
-normal_hover = ""
-selected_hover = ""
-tabbar = ""
-footer = ""
-theme = ""
-ver = ""
-
-brush_light = ""
-brush_dark = ""
-keyboard_light = ""
-keyboard_dark = ""
-warn_light = ""
-warn_dark = ""
-logo = ""
-github_dark = ""
-github_light = ""
-update_dark = ""
-update_light = ""
-settings_light = ""
-settings_dark = ""