From 5c7a51061f861cdd574fe55a8098c1e616942683 Mon Sep 17 00:00:00 2001 From: not-nef Date: Wed, 30 Aug 2023 19:05:24 +0200 Subject: [PATCH 01/31] initial commit --- .gitignore | 264 +++++++++++++++++++------------------- LICENSE | 42 +++---- docs/CODE_OF_CONDUCT.md | 256 ++++++++++++++++++------------------- docs/CONTRIBUTING.md | 48 +++---- docs/FEATURES.md | 34 ++--- docs/README.md | 26 ++-- requirements.txt | 24 ++-- src/config.py | 51 -------- src/editor.py | 227 +++------------------------------ src/generatesize.py | 21 ---- src/main.py | 110 ++++++---------- src/mdpreview.py | 36 ------ src/pages/about.py | 28 ----- src/pages/filetype.py | 39 ------ src/pages/wanttosave.py | 24 ---- src/settings/UI.py | 271 ---------------------------------------- src/settings/images.py | 19 --- src/tabmanager.py | 168 ------------------------- src/themes/dark.py | 11 -- src/themes/light.py | 11 -- src/update.py | 21 ---- src/utils.py | 10 -- src/vars.py | 30 ----- 23 files changed, 402 insertions(+), 1369 deletions(-) delete mode 100644 src/config.py delete mode 100644 src/generatesize.py delete mode 100644 src/mdpreview.py delete mode 100644 src/pages/about.py delete mode 100644 src/pages/filetype.py delete mode 100644 src/pages/wanttosave.py delete mode 100644 src/settings/UI.py delete mode 100644 src/settings/images.py delete mode 100644 src/tabmanager.py delete mode 100644 src/themes/dark.py delete mode 100644 src/themes/light.py delete mode 100644 src/update.py delete mode 100644 src/utils.py delete mode 100644 src/vars.py 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..82749c0 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) 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. 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 - - Releases - -

- -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 + + Releases + +

+ +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..facdfb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ -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 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/editor.py b/src/editor.py index 0466a05..5f2489d 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,212 +1,15 @@ -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 tkinter.ttk import Notebook +from tkinter import _Cursor, _Relief, _ScreenUnits, _TakeFocusValue, Frame, Misc +from typing import Any +from typing_extensions import Literal + +class Manager(Notebook): + def __init__(self, *args): + super().__init__(*args) + + def newtab(self, name): + self.add(Editor, text=name) + +class Editor(Frame): + def __init__(self, *args): + super().__init__(*args) \ No newline at end of file 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/main.py b/src/main.py index 068651a..dd981a2 100644 --- a/src/main.py +++ b/src/main.py @@ -1,83 +1,53 @@ -ver = "0.9 beta" +from tkinter import Tk, Label +from tkinter.ttk import Button +from sv_ttk import set_theme +from ntkutils import placeappincenter -import os -import tkinter -from tkinter import ttk +from editor import Manager -import ntkutils -import sv_ttk -from tkinterdnd2 import * +onlaunch = "new" +screenheight = 0 +screenwidth = 0 -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 +class StartupWindow(Tk): + def __init__(self): + super().__init__() + self.geometry("250x300") + self.withdraw() + self.title("Futura Notes") + self.resizable(False, False) + set_theme("dark") + placeappincenter(self) + self.update_idletasks() -v.cfg = config.get() + self.title = Label(self, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20) + self.btncreatenew = Button(self, text="Create New File", command=self.openmainwindow).pack(anchor="nw", padx=20) -if u.dark(): - theme = dark.get() -else: - theme = light.get() + self.deiconify() -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 openmainwindow(self): + self.destroy() +class App(Tk): + def __init__(self, onlaunch="new"): + super().__init__() + self.withdraw() -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) + 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.deiconify() -def openfile(path): - preparewindow() - tabmanager.openfile(path=path) + self.manager = Manager(self) + self.manager.pack(fill="both", expand=True) -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() +if __name__ == "__main__": + start = StartupWindow() + start.mainloop() + 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 = "" From 787bb8f959fc0f33176e92a0f38c1b97ee0e84b8 Mon Sep 17 00:00:00 2001 From: not-nef Date: Wed, 30 Aug 2023 19:10:00 +0200 Subject: [PATCH 02/31] what the hell are these variables for --- src/editor.py | 3 +-- src/main.py | 9 +-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/editor.py b/src/editor.py index 5f2489d..699595a 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,7 +1,6 @@ from tkinter.ttk import Notebook -from tkinter import _Cursor, _Relief, _ScreenUnits, _TakeFocusValue, Frame, Misc +from tkinter import Frame from typing import Any -from typing_extensions import Literal class Manager(Notebook): def __init__(self, *args): diff --git a/src/main.py b/src/main.py index dd981a2..2384e04 100644 --- a/src/main.py +++ b/src/main.py @@ -1,14 +1,8 @@ from tkinter import Tk, Label from tkinter.ttk import Button from sv_ttk import set_theme -from ntkutils import placeappincenter - from editor import Manager -onlaunch = "new" -screenheight = 0 -screenwidth = 0 - class StartupWindow(Tk): def __init__(self): super().__init__() @@ -17,7 +11,6 @@ def __init__(self): self.title("Futura Notes") self.resizable(False, False) set_theme("dark") - placeappincenter(self) self.update_idletasks() self.title = Label(self, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20) @@ -29,7 +22,7 @@ def openmainwindow(self): self.destroy() class App(Tk): - def __init__(self, onlaunch="new"): + def __init__(self): super().__init__() self.withdraw() From fada209d76c0a700306590a9401949c4fb9984b3 Mon Sep 17 00:00:00 2001 From: not-nef Date: Wed, 30 Aug 2023 19:11:48 +0200 Subject: [PATCH 03/31] where do these imports keep coming from --- src/editor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor.py b/src/editor.py index 699595a..496c17a 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,6 +1,5 @@ from tkinter.ttk import Notebook from tkinter import Frame -from typing import Any class Manager(Notebook): def __init__(self, *args): From bef441accc779f2189bf6214c792c4fd71d07fbc Mon Sep 17 00:00:00 2001 From: nef Date: Thu, 31 Aug 2023 15:11:18 +0200 Subject: [PATCH 04/31] starting the editor --- src/editor.py | 35 ++++++++++++++++++++++++++++++++--- src/main.py | 8 ++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/editor.py b/src/editor.py index 496c17a..7ad5dd9 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,13 +1,42 @@ -from tkinter.ttk import Notebook +from tkinter.ttk import Notebook, Style from tkinter import Frame +from tkinter.font import Font +from chlorophyll import CodeView +from tklinenums import TkLineNumbers +from pygments.lexers import TextLexer class Manager(Notebook): def __init__(self, *args): super().__init__(*args) def newtab(self, name): - self.add(Editor, text=name) + self.add(Editor(), text=name) class Editor(Frame): def __init__(self, *args): - super().__init__(*args) \ No newline at end of file + super().__init__(*args) + + self.footer = Frame(self, width=self.winfo_width(), height=25) + self.footer.pack(side="bottom", fill="x") + self.footer.pack_propagate(False) + + self.text = CodeView(self, bg="#1c1c1c", lexer=TextLexer) + self.text._set_color_scheme("ayu-dark") + self.text.pack(side="right", fill="both", expand=True) + self.text._hs.grid_remove() + + self.linenumbersstyle = Style() + self.linenumbersstyle.configure("TLineNumbers", background="#1c1c1c", foreground="white") + self.linenumbersfont = Font(family="Courier New bold", name="TkLineNumsFont") + + self.linenumbers = TkLineNumbers(self, self.text, "right") + self.linenumbers.pack(side="left", fill="y") + self.linenumbers.configure(borderwidth=0) + + self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) + self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) + self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) + + self.text["yscrollcommand"] = self.linenumbers.redraw + self.text._line_numbers.destroy() + self.text._line_numbers = self.linenumbers diff --git a/src/main.py b/src/main.py index 2384e04..3f5a4d5 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,6 @@ from tkinter import Tk, Label from tkinter.ttk import Button -from sv_ttk import set_theme +from sv_ttk import SunValleyTtkTheme from editor import Manager class StartupWindow(Tk): @@ -10,7 +10,7 @@ def __init__(self): self.withdraw() self.title("Futura Notes") self.resizable(False, False) - set_theme("dark") + SunValleyTtkTheme.set_theme("dark") self.update_idletasks() self.title = Label(self, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20) @@ -26,6 +26,9 @@ def __init__(self): super().__init__() self.withdraw() + SunValleyTtkTheme.initialized = False + SunValleyTtkTheme.set_theme("dark") + self.h = self.winfo_screenheight() - 200 self.w = self.winfo_screenwidth() - 100 self.x = int((self.winfo_screenwidth() - self.w) / 2) @@ -36,6 +39,7 @@ def __init__(self): self.manager = Manager(self) self.manager.pack(fill="both", expand=True) + self.manager.newtab("Test") From daae2a7975db2dda4044e7a3c76bf2fe23d8b466 Mon Sep 17 00:00:00 2001 From: nef Date: Thu, 31 Aug 2023 15:25:47 +0200 Subject: [PATCH 05/31] menu bar --- src/editor.py | 5 ++++- src/main.py | 11 ++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/editor.py b/src/editor.py index 7ad5dd9..5fb6a1a 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,5 +1,5 @@ from tkinter.ttk import Notebook, Style -from tkinter import Frame +from tkinter import Frame, Label from tkinter.font import Font from chlorophyll import CodeView from tklinenums import TkLineNumbers @@ -20,6 +20,9 @@ def __init__(self, *args): 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, bg="#1c1c1c", lexer=TextLexer) self.text._set_color_scheme("ayu-dark") self.text.pack(side="right", fill="both", expand=True) diff --git a/src/main.py b/src/main.py index 3f5a4d5..3b761e5 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -from tkinter import Tk, Label +from tkinter import Tk, Label, Menu from tkinter.ttk import Button from sv_ttk import SunValleyTtkTheme from editor import Manager @@ -24,7 +24,6 @@ def openmainwindow(self): class App(Tk): def __init__(self): super().__init__() - self.withdraw() SunValleyTtkTheme.initialized = False SunValleyTtkTheme.set_theme("dark") @@ -35,12 +34,18 @@ def __init__(self): self.y = int((self.winfo_screenheight() - self.h - 75) / 2) self.geometry("{}x{}+{}+{}".format(self.w, self.h, self.x, self.y)) - self.deiconify() self.manager = Manager(self) self.manager.pack(fill="both", expand=True) self.manager.newtab("Test") + self.menubar = Menu(self, tearoff=False) + self.config(menu=self.menubar) + + self.filemenu = Menu(self.menubar, tearoff=False, bg="white") + + self.menubar.add_cascade(label="File", menu=self.filemenu) + if __name__ == "__main__": From 8d776734a7251aa40a60e8a66ee042dacaf85071 Mon Sep 17 00:00:00 2001 From: nef Date: Thu, 31 Aug 2023 18:44:17 +0200 Subject: [PATCH 06/31] isort --- src/editor.py | 6 ++++-- src/main.py | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/editor.py b/src/editor.py index 5fb6a1a..22fa89f 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,9 +1,11 @@ -from tkinter.ttk import Notebook, Style from tkinter import Frame, Label from tkinter.font import Font +from tkinter.ttk import Notebook, Style + from chlorophyll import CodeView -from tklinenums import TkLineNumbers from pygments.lexers import TextLexer +from tklinenums import TkLineNumbers + class Manager(Notebook): def __init__(self, *args): diff --git a/src/main.py b/src/main.py index 3b761e5..6539ce7 100644 --- a/src/main.py +++ b/src/main.py @@ -1,8 +1,11 @@ -from tkinter import Tk, Label, Menu +from tkinter import Label, Menu, Tk from tkinter.ttk import Button + from sv_ttk import SunValleyTtkTheme + from editor import Manager + class StartupWindow(Tk): def __init__(self): super().__init__() From 060d03f6a0f3a0f4a04554144501af40473c9944 Mon Sep 17 00:00:00 2001 From: nef Date: Thu, 31 Aug 2023 19:18:08 +0200 Subject: [PATCH 07/31] close icon and click detection --- src/editor.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/editor.py b/src/editor.py index 22fa89f..0133236 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,4 +1,5 @@ -from tkinter import Frame, Label +from pathlib import Path +from tkinter import Frame, Label, PhotoImage from tkinter.font import Font from tkinter.ttk import Notebook, Style @@ -11,8 +12,31 @@ class Manager(Notebook): def __init__(self, *args): super().__init__(*args) + # Remove dotted line :O + self.style = Style() + self.style.configure("TNotebook.Tab", focuscolor=self.style.configure(".")["background"]) + + self.closeimg = PhotoImage(file="assets/close_light.png") + + self.bind("", self.on_click, add=True) + def newtab(self, name): - self.add(Editor(), text=name) + self.add(Editor(), text=name, image=self.closeimg, compound="right") + + def closetab(self, event): + print("close tab") + + def on_click(self, 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 + + # 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) + class Editor(Frame): def __init__(self, *args): From 1278d8a0367ebd92058a3f84461a95253dc0b692 Mon Sep 17 00:00:00 2001 From: nef Date: Thu, 31 Aug 2023 19:23:26 +0200 Subject: [PATCH 08/31] cleaning up --- src/editor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/editor.py b/src/editor.py index 0133236..ce2696f 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,4 +1,3 @@ -from pathlib import Path from tkinter import Frame, Label, PhotoImage from tkinter.font import Font from tkinter.ttk import Notebook, Style @@ -54,8 +53,8 @@ def __init__(self, *args): self.text.pack(side="right", fill="both", expand=True) self.text._hs.grid_remove() - self.linenumbersstyle = Style() - self.linenumbersstyle.configure("TLineNumbers", background="#1c1c1c", foreground="white") + self.style = Style() + self.style.configure("TLineNumbers", background="#1c1c1c", foreground="white") self.linenumbersfont = Font(family="Courier New bold", name="TkLineNumsFont") self.linenumbers = TkLineNumbers(self, self.text, "right") From 9d21fc26e7804cf57a9e9ebc8eea74576b4fe400 Mon Sep 17 00:00:00 2001 From: nef Date: Thu, 31 Aug 2023 20:41:59 +0200 Subject: [PATCH 09/31] closing tabs and working scrollbar --- src/editor.py | 25 ++++++++++++++++++------- src/main.py | 3 +++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/editor.py b/src/editor.py index ce2696f..4cd72aa 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,6 +1,6 @@ from tkinter import Frame, Label, PhotoImage from tkinter.font import Font -from tkinter.ttk import Notebook, Style +from tkinter.ttk import Notebook, Style, Scrollbar from chlorophyll import CodeView from pygments.lexers import TextLexer @@ -23,9 +23,13 @@ def newtab(self, name): self.add(Editor(), text=name, image=self.closeimg, compound="right") def closetab(self, event): - print("close tab") + 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 @@ -48,14 +52,16 @@ def __init__(self, *args): self.filedir = Label(self.footer, text="unsaved") self.filedir.pack(side="left") + self.scrollbar = Scrollbar(self) + self.scrollbar.pack(side="right", fill="y") + self.text = CodeView(self, bg="#1c1c1c", lexer=TextLexer) self.text._set_color_scheme("ayu-dark") self.text.pack(side="right", fill="both", expand=True) + self.text._vs.grid_remove() self.text._hs.grid_remove() - self.style = Style() - self.style.configure("TLineNumbers", background="#1c1c1c", foreground="white") - self.linenumbersfont = Font(family="Courier New bold", name="TkLineNumsFont") + self.scrollbar.configure(command=self.text.yview) self.linenumbers = TkLineNumbers(self, self.text, "right") self.linenumbers.pack(side="left", fill="y") @@ -65,6 +71,11 @@ def __init__(self, *args): self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) - self.text["yscrollcommand"] = self.linenumbers.redraw - self.text._line_numbers.destroy() + self.text._line_numbers.destroy() # mine look cooler self.text._line_numbers = self.linenumbers + + self.text["yscrollcommand"] = self.yscroll + + def yscroll(self, *args): + self.scrollbar.set(*args) + self.linenumbers.redraw(*args) diff --git a/src/main.py b/src/main.py index 6539ce7..e45641f 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,5 @@ from tkinter import Label, Menu, Tk +from tkinter.font import Font from tkinter.ttk import Button from sv_ttk import SunValleyTtkTheme @@ -49,6 +50,8 @@ def __init__(self): self.menubar.add_cascade(label="File", menu=self.filemenu) + self.filemenu.add_command(label="New", command=lambda:self.manager.newtab("Test"), foreground="black") + if __name__ == "__main__": From 679b20eaab58a11208e8d1520735f5703abf0953 Mon Sep 17 00:00:00 2001 From: nef Date: Thu, 31 Aug 2023 20:53:08 +0200 Subject: [PATCH 10/31] credit to akuli --- src/editor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/editor.py b/src/editor.py index 4cd72aa..c44d9cf 100644 --- a/src/editor.py +++ b/src/editor.py @@ -22,6 +22,8 @@ def __init__(self, *args): def newtab(self, name): self.add(Editor(), text=name, image=self.closeimg, compound="right") + # 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 @@ -76,6 +78,7 @@ def __init__(self, *args): self.text["yscrollcommand"] = self.yscroll + # Extra function so that the linenumbers and the scrollbar dont fight over the yscrollcommand def yscroll(self, *args): self.scrollbar.set(*args) self.linenumbers.redraw(*args) From 42ce34b9fc6d9832cbc043dad1d466e83b707078 Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 1 Sep 2023 11:42:19 +0200 Subject: [PATCH 11/31] open files and home tab --- src/editor.py | 34 ++++++++++++++++++++++++++++++---- src/main.py | 30 +++--------------------------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/editor.py b/src/editor.py index c44d9cf..388e1d1 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,6 +1,8 @@ +from os.path import basename from tkinter import Frame, Label, PhotoImage +from tkinter.filedialog import askopenfile from tkinter.font import Font -from tkinter.ttk import Notebook, Style, Scrollbar +from tkinter.ttk import Notebook, Style, Scrollbar, Button from chlorophyll import CodeView from pygments.lexers import TextLexer @@ -17,10 +19,23 @@ def __init__(self, *args): self.closeimg = PhotoImage(file="assets/close_light.png") + 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, name): - self.add(Editor(), text=name, image=self.closeimg, compound="right") + def newtab(self, file=None): + self.add(Editor(file), text="Untitled" if file==None else basename(file.name), image=self.closeimg, compound="right") + self.select(self.index(self.select()) + 1) # Select newly opened tab + + def openfile(self): + self.file = askopenfile() + self.newtab(self.file) + self.file.close() + # The next two functions are heavily inspired by Akuli: # https://github.com/Akuli/porcupine/blob/main/porcupine/plugins/tab_closing.py @@ -44,7 +59,7 @@ def on_click(self, event) -> None: class Editor(Frame): - def __init__(self, *args): + def __init__(self, file, *args): super().__init__(*args) self.footer = Frame(self, width=self.winfo_width(), height=25) @@ -78,7 +93,18 @@ def __init__(self, *args): self.text["yscrollcommand"] = self.yscroll + + if file != None: + self.text.insert("1.0", file.read()) + + # Extra function so that the linenumbers and the scrollbar dont fight over the yscrollcommand def yscroll(self, *args): self.scrollbar.set(*args) self.linenumbers.redraw(*args) + +class Home(Frame): + def __init__(self): + super().__init__() + + diff --git a/src/main.py b/src/main.py index e45641f..b65d52d 100644 --- a/src/main.py +++ b/src/main.py @@ -1,30 +1,9 @@ -from tkinter import Label, Menu, Tk -from tkinter.font import Font -from tkinter.ttk import Button +from tkinter import Menu, Tk from sv_ttk import SunValleyTtkTheme from editor import Manager - -class StartupWindow(Tk): - def __init__(self): - super().__init__() - self.geometry("250x300") - self.withdraw() - self.title("Futura Notes") - self.resizable(False, False) - SunValleyTtkTheme.set_theme("dark") - self.update_idletasks() - - self.title = Label(self, text="Futura Notes", font=("Segoe UI", 20, "bold")).pack(anchor="nw", padx=20, pady=20) - self.btncreatenew = Button(self, text="Create New File", command=self.openmainwindow).pack(anchor="nw", padx=20) - - self.deiconify() - - def openmainwindow(self): - self.destroy() - class App(Tk): def __init__(self): super().__init__() @@ -41,8 +20,6 @@ def __init__(self): self.manager = Manager(self) self.manager.pack(fill="both", expand=True) - self.manager.newtab("Test") - self.menubar = Menu(self, tearoff=False) self.config(menu=self.menubar) @@ -50,12 +27,11 @@ def __init__(self): self.menubar.add_cascade(label="File", menu=self.filemenu) - self.filemenu.add_command(label="New", command=lambda:self.manager.newtab("Test"), foreground="black") + self.filemenu.add_command(label="New", command=lambda:self.manager.newtab(), foreground="black") + self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="black") if __name__ == "__main__": - start = StartupWindow() - start.mainloop() main = App() main.mainloop() From c445a36137e450521781a965e56aa676cf453af3 Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 1 Sep 2023 13:15:20 +0200 Subject: [PATCH 12/31] proper background color --- src/dark.toml | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/editor.py | 10 ++++--- src/main.py | 1 + 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/dark.toml diff --git a/src/dark.toml b/src/dark.toml new file mode 100644 index 0000000..fce2ca3 --- /dev/null +++ b/src/dark.toml @@ -0,0 +1,79 @@ +[editor] +bg = "#1c1c1c" +fg = "#b3b1ad" +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/editor.py b/src/editor.py index 388e1d1..d646a54 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,8 +1,8 @@ from os.path import basename from tkinter import Frame, Label, PhotoImage from tkinter.filedialog import askopenfile -from tkinter.font import Font -from tkinter.ttk import Notebook, Style, Scrollbar, Button +from tkinter.ttk import Button, Notebook, Scrollbar, Style +from toml import load from chlorophyll import CodeView from pygments.lexers import TextLexer @@ -72,12 +72,14 @@ def __init__(self, file, *args): self.scrollbar = Scrollbar(self) self.scrollbar.pack(side="right", fill="y") - self.text = CodeView(self, bg="#1c1c1c", lexer=TextLexer) - self.text._set_color_scheme("ayu-dark") + self.text = CodeView(self, lexer=TextLexer) self.text.pack(side="right", fill="both", expand=True) self.text._vs.grid_remove() self.text._hs.grid_remove() + self.color_scheme = load("src/dark.toml") + self.text._set_color_scheme(self.color_scheme) + self.scrollbar.configure(command=self.text.yview) self.linenumbers = TkLineNumbers(self, self.text, "right") diff --git a/src/main.py b/src/main.py index b65d52d..e74011f 100644 --- a/src/main.py +++ b/src/main.py @@ -4,6 +4,7 @@ from editor import Manager + class App(Tk): def __init__(self): super().__init__() From 4f94b4abd3c03780f0cf6b30e09a9dd277eafb91 Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 1 Sep 2023 13:18:19 +0200 Subject: [PATCH 13/31] file path in footer --- src/editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor.py b/src/editor.py index d646a54..135e63a 100644 --- a/src/editor.py +++ b/src/editor.py @@ -98,6 +98,7 @@ def __init__(self, file, *args): if file != None: self.text.insert("1.0", file.read()) + self.filedir.configure(text=file.name) # Extra function so that the linenumbers and the scrollbar dont fight over the yscrollcommand From 10ad11a26f69f2c944de089fee7eeac2b176e598 Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 1 Sep 2023 13:22:06 +0200 Subject: [PATCH 14/31] apparently the new tab is always inserted at the end --- src/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor.py b/src/editor.py index 135e63a..ae80634 100644 --- a/src/editor.py +++ b/src/editor.py @@ -29,7 +29,7 @@ def __init__(self, *args): def newtab(self, file=None): self.add(Editor(file), text="Untitled" if file==None else basename(file.name), image=self.closeimg, compound="right") - self.select(self.index(self.select()) + 1) # Select newly opened tab + self.select(len(self.tabs()) - 1) # Select newly opened tab def openfile(self): self.file = askopenfile() From de17e35bec8576c962496804060e905a3a2c5f0a Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 2 Sep 2023 12:32:31 +0200 Subject: [PATCH 15/31] linux menu bars are black --- src/editor.py | 3 +++ src/main.py | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/editor.py b/src/editor.py index ae80634..b67bafb 100644 --- a/src/editor.py +++ b/src/editor.py @@ -35,6 +35,9 @@ def openfile(self): self.file = askopenfile() self.newtab(self.file) self.file.close() + + def save(self): + pass # The next two functions are heavily inspired by Akuli: diff --git a/src/main.py b/src/main.py index e74011f..194ae8b 100644 --- a/src/main.py +++ b/src/main.py @@ -3,7 +3,10 @@ from sv_ttk import SunValleyTtkTheme from editor import Manager +from platform import system +if system() == "Linux": LINUX = True +else: LINUX = False class App(Tk): def __init__(self): @@ -28,8 +31,9 @@ def __init__(self): self.menubar.add_cascade(label="File", menu=self.filemenu) - self.filemenu.add_command(label="New", command=lambda:self.manager.newtab(), foreground="black") - self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="black") + self.filemenu.add_command(label="New", command=lambda:self.manager.newtab(), foreground="white" if LINUX else "black") + self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="white" if LINUX else "black") + self.filemenu.add_command(label="Save", command=self.manager.save, foreground="white" if LINUX else "black") From 66cd993927852cf8587faf3d7afb43572e905004 Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 2 Sep 2023 13:05:52 +0200 Subject: [PATCH 16/31] saving files --- LICENSE | 2 +- src/editor.py | 25 +++++++++++++++++++++---- src/main.py | 3 ++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index 82749c0..a42cabb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 not-nef +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 diff --git a/src/editor.py b/src/editor.py index b67bafb..206eba3 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,6 +1,6 @@ -from os.path import basename +from os.path import basename, isfile from tkinter import Frame, Label, PhotoImage -from tkinter.filedialog import askopenfile +from tkinter.filedialog import askopenfile, asksaveasfile from tkinter.ttk import Button, Notebook, Scrollbar, Style from toml import load @@ -37,7 +37,24 @@ def openfile(self): self.file.close() def save(self): - pass + self.editor = self.nametowidget(self.select()) # Retrieves Editor Object of currently opened Tab + 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.nametowidget(self.select()) + self.file3 = asksaveasfile() + 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() + # The next two functions are heavily inspired by Akuli: @@ -98,10 +115,10 @@ def __init__(self, file, *args): self.text["yscrollcommand"] = self.yscroll - if file != None: self.text.insert("1.0", file.read()) self.filedir.configure(text=file.name) + file.close() # Extra function so that the linenumbers and the scrollbar dont fight over the yscrollcommand diff --git a/src/main.py b/src/main.py index 194ae8b..b54b5a2 100644 --- a/src/main.py +++ b/src/main.py @@ -31,9 +31,10 @@ def __init__(self): self.menubar.add_cascade(label="File", menu=self.filemenu) - self.filemenu.add_command(label="New", command=lambda:self.manager.newtab(), foreground="white" if LINUX else "black") + self.filemenu.add_command(label="New", command=self.manager.newtab, foreground="white" if LINUX else "black") self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="white" if LINUX else "black") self.filemenu.add_command(label="Save", command=self.manager.save, foreground="white" if LINUX else "black") + self.filemenu.add_command(label="Save As", command=self.manager.saveas, foreground="white" if LINUX else "black") From c71c9ba7c553f59a87364947901591a569f90205 Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 8 Sep 2023 20:49:38 +0200 Subject: [PATCH 17/31] yes --- src/editor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/editor.py b/src/editor.py index 206eba3..a831542 100644 --- a/src/editor.py +++ b/src/editor.py @@ -29,7 +29,7 @@ def __init__(self, *args): def newtab(self, file=None): self.add(Editor(file), text="Untitled" if file==None else basename(file.name), image=self.closeimg, compound="right") - self.select(len(self.tabs()) - 1) # Select newly opened tab + self.select(self.tabs()[-1]) # Select newly opened tab def openfile(self): self.file = askopenfile() @@ -54,8 +54,7 @@ def saveas(self): self.editor.filedir.configure(text=self.file3.name) self.tab(self.select(), text=basename(self.file3.name)) self.file3.close() - - + # The next two functions are heavily inspired by Akuli: # https://github.com/Akuli/porcupine/blob/main/porcupine/plugins/tab_closing.py From a93e33d118b5b043b1daa9792737af64da23a74b Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 8 Sep 2023 20:55:51 +0200 Subject: [PATCH 18/31] i am overthinking it --- src/editor.py | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/editor.py b/src/editor.py index a831542..08e9074 100644 --- a/src/editor.py +++ b/src/editor.py @@ -88,43 +88,20 @@ def __init__(self, file, *args): self.filedir = Label(self.footer, text="unsaved") self.filedir.pack(side="left") - self.scrollbar = Scrollbar(self) - self.scrollbar.pack(side="right", fill="y") - self.text = CodeView(self, lexer=TextLexer) self.text.pack(side="right", fill="both", expand=True) - self.text._vs.grid_remove() self.text._hs.grid_remove() self.color_scheme = load("src/dark.toml") self.text._set_color_scheme(self.color_scheme) - self.scrollbar.configure(command=self.text.yview) - - self.linenumbers = TkLineNumbers(self, self.text, "right") - self.linenumbers.pack(side="left", fill="y") - self.linenumbers.configure(borderwidth=0) - - self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) - self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) - self.text.bind("", lambda event: self.after_idle(self.linenumbers.redraw), add=True) - - self.text._line_numbers.destroy() # mine look cooler - self.text._line_numbers = self.linenumbers - - self.text["yscrollcommand"] = self.yscroll + self.text._line_numbers.configure(borderwidth=0) if file != None: self.text.insert("1.0", file.read()) self.filedir.configure(text=file.name) file.close() - - # Extra function so that the linenumbers and the scrollbar dont fight over the yscrollcommand - def yscroll(self, *args): - self.scrollbar.set(*args) - self.linenumbers.redraw(*args) - class Home(Frame): def __init__(self): super().__init__() From b2cea03c8ae563e1ea9b8374b2db8febf1b511b6 Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 8 Sep 2023 20:57:12 +0200 Subject: [PATCH 19/31] remove that --- src/editor.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/editor.py b/src/editor.py index 08e9074..57c8297 100644 --- a/src/editor.py +++ b/src/editor.py @@ -102,8 +102,4 @@ def __init__(self, file, *args): self.filedir.configure(text=file.name) file.close() -class Home(Frame): - def __init__(self): - super().__init__() - From 765f310229ea6512fc3dab7d983ba15adeb2bc7a Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 8 Sep 2023 20:57:35 +0200 Subject: [PATCH 20/31] unused imports --- src/editor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/editor.py b/src/editor.py index 57c8297..4851419 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,12 +1,11 @@ from os.path import basename, isfile from tkinter import Frame, Label, PhotoImage from tkinter.filedialog import askopenfile, asksaveasfile -from tkinter.ttk import Button, Notebook, Scrollbar, Style +from tkinter.ttk import Button, Notebook, Style from toml import load from chlorophyll import CodeView from pygments.lexers import TextLexer -from tklinenums import TkLineNumbers class Manager(Notebook): From a0ae799ff9c25bb26e20f5d47a1e743770ba62c2 Mon Sep 17 00:00:00 2001 From: nef Date: Fri, 8 Sep 2023 21:08:53 +0200 Subject: [PATCH 21/31] cool sv themed file browser --- src/editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor.py b/src/editor.py index 4851419..c85a6b5 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,6 +1,6 @@ from os.path import basename, isfile +from tkfilebrowser import askopenfilename, asksaveasfilename from tkinter import Frame, Label, PhotoImage -from tkinter.filedialog import askopenfile, asksaveasfile from tkinter.ttk import Button, Notebook, Style from toml import load @@ -31,7 +31,7 @@ def newtab(self, file=None): self.select(self.tabs()[-1]) # Select newly opened tab def openfile(self): - self.file = askopenfile() + self.file = open(askopenfilename(), "r") self.newtab(self.file) self.file.close() @@ -47,7 +47,7 @@ def save(self): def saveas(self): self.editor = self.nametowidget(self.select()) - self.file3 = asksaveasfile() + 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) From 0c3a1189066357f20e615d6fbdd033ddd79ec227 Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 09:48:46 +0200 Subject: [PATCH 22/31] light theme --- assets/close_dark.png | Bin 402 -> 325 bytes assets/close_light.png | Bin 325 -> 402 bytes src/dark.toml | 2 +- src/editor.py | 12 ++++--- src/light.toml | 79 +++++++++++++++++++++++++++++++++++++++++ src/main.py | 9 ++--- 6 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 src/light.toml diff --git a/assets/close_dark.png b/assets/close_dark.png index bdb4038d63bcabdffa93675fdb3db8c0bd576de1..5296a2843b703847bce2257dea99a9d89f315cd0 100644 GIT binary patch delta 248 zcmVFH&PsV5t5-5ehGA yC<+YLC8AO3Jg7UN@ah#967e@ND7CX$y6z1q-10?8fNux@0000b;z6ozASf#d{HlmVoU6)Qar?OHK&1y)Mo z92|f{AhlgGGA3eTY2g2Z9Z^c~GcYjlNl%aWaPq~mtppG5pEOOMlO!47r`mVKC|-x? znz}}tTvL|i3(^RKC&*F}1sp#@^s0^nUNA*$5udF0?M5`mnSb$9)Z#ZF6njL~bL#A$UUm<#}JYSjigS#XM Yb_5D?fX9olnE(I)07*qoM6N<$f^D&m@&Et; diff --git a/assets/close_light.png b/assets/close_light.png index 5296a2843b703847bce2257dea99a9d89f315cd0..bdb4038d63bcabdffa93675fdb3db8c0bd576de1 100644 GIT binary patch delta 326 zcmV-M0lEIg0+IueReu2|Nklb;z6ozASf#d{HlmVoU6)Qar?OHK&1y)Mo z92|f{AhlgGGA3eTY2g2Z9Z^c~GcYjlNl%aWaPq~mtppG5pEOOMlO!47r`mVKC|-x? znz}}tTvL|i3(^RKC&*F}1sp#@^s0^nUNA*$5udF0?M5`mnSb$9)Z#ZF6njL~bL#A$UUm<#}JYSjigS#XM Yb_5D?fX9olnE(I)07*qoM6N<$f^D&m@&Et; delta 248 zcmVFH&PsV5t5-5ehGA yC<+YLC8AO3Jg7UN@ah#967e@ND7CX$y6z1q-10?8fNux@0000", self.on_click, add=True) def newtab(self, file=None): - self.add(Editor(file), text="Untitled" if file==None else basename(file.name), image=self.closeimg, compound="right") + 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): @@ -77,7 +79,7 @@ def on_click(self, event) -> None: class Editor(Frame): - def __init__(self, file, *args): + def __init__(self, file, theme, *args): super().__init__(*args) self.footer = Frame(self, width=self.winfo_width(), height=25) @@ -91,7 +93,7 @@ def __init__(self, file, *args): self.text.pack(side="right", fill="both", expand=True) self.text._hs.grid_remove() - self.color_scheme = load("src/dark.toml") + self.color_scheme = load("src/{}.toml".format(theme)) self.text._set_color_scheme(self.color_scheme) self.text._line_numbers.configure(borderwidth=0) 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 b54b5a2..7576a4e 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,6 @@ from tkinter import Menu, Tk -from sv_ttk import SunValleyTtkTheme +from sv_ttk import set_theme from editor import Manager from platform import system @@ -8,12 +8,13 @@ if system() == "Linux": LINUX = True else: LINUX = False +theme = "dark" + class App(Tk): def __init__(self): super().__init__() - SunValleyTtkTheme.initialized = False - SunValleyTtkTheme.set_theme("dark") + set_theme(theme) self.h = self.winfo_screenheight() - 200 self.w = self.winfo_screenwidth() - 100 @@ -22,7 +23,7 @@ def __init__(self): self.geometry("{}x{}+{}+{}".format(self.w, self.h, self.x, self.y)) - self.manager = Manager(self) + self.manager = Manager(theme, self) self.manager.pack(fill="both", expand=True) self.menubar = Menu(self, tearoff=False) self.config(menu=self.menubar) From 15756b88a00e0bf35c648ceb29baa1e27a687079 Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 09:59:52 +0200 Subject: [PATCH 23/31] syntax highlighting --- src/editor.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/editor.py b/src/editor.py index d193b0c..1aaf368 100644 --- a/src/editor.py +++ b/src/editor.py @@ -5,7 +5,8 @@ from toml import load from chlorophyll import CodeView -from pygments.lexers import TextLexer +from pygments.lexers import TextLexer, get_lexer_for_filename +from pygments.util import ClassNotFound class Manager(Notebook): @@ -89,7 +90,7 @@ def __init__(self, file, theme, *args): self.filedir = Label(self.footer, text="unsaved") self.filedir.pack(side="left") - self.text = CodeView(self, lexer=TextLexer) + self.text = CodeView(self) self.text.pack(side="right", fill="both", expand=True) self.text._hs.grid_remove() @@ -101,6 +102,15 @@ def __init__(self, file, theme, *args): 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 + From d67edbd8bc9bc661ca31e927fe9cf6ad4264a6fc Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 10:47:46 +0200 Subject: [PATCH 24/31] preview --- requirements.txt | 1 + src/editor.py | 23 ++++++++++++++++++++++- src/main.py | 2 ++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index facdfb2..f40cc47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ tklinenums >= 1.5 requests; sys_platform != 'linux' pygments pyperclip +pillow == 10.0.0 # tkinterweb requires version 10.0.0 diff --git a/src/editor.py b/src/editor.py index 1aaf368..a8527cc 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,7 +1,9 @@ +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 @@ -78,6 +80,9 @@ def on_click(self, event) -> None: 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): @@ -91,7 +96,7 @@ def __init__(self, file, theme, *args): self.filedir.pack(side="left") self.text = CodeView(self) - self.text.pack(side="right", fill="both", expand=True) + self.text.pack(side="left", fill="both", expand=True) self.text._hs.grid_remove() self.color_scheme = load("src/{}.toml".format(theme)) @@ -112,5 +117,21 @@ def get_lexer(self, file): lexer = TextLexer return lexer + + def openpreview(self): + 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() + + def updatepreview(self, *args): + self.after_idle(lambda: self.preview.load_html(markdown(self.text.get("1.0", "end").replace("\n", "
")))) + + diff --git a/src/main.py b/src/main.py index 7576a4e..9d404dc 100644 --- a/src/main.py +++ b/src/main.py @@ -36,6 +36,8 @@ def __init__(self): self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="white" if LINUX else "black") self.filemenu.add_command(label="Save", command=self.manager.save, foreground="white" if LINUX else "black") self.filemenu.add_command(label="Save As", command=self.manager.saveas, foreground="white" if LINUX else "black") + self.filemenu.add_separator() + self.filemenu.add_command(label="Preview", command=self.manager.openpreview, foreground="white" if LINUX else "black") From 3e62c3a22be0c45676110247ec88065aa884a2cb Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 11:00:41 +0200 Subject: [PATCH 25/31] remove that or h tags get messed up --- src/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor.py b/src/editor.py index a8527cc..a1d003e 100644 --- a/src/editor.py +++ b/src/editor.py @@ -130,7 +130,7 @@ def openpreview(self): self.updatepreview() def updatepreview(self, *args): - self.after_idle(lambda: self.preview.load_html(markdown(self.text.get("1.0", "end").replace("\n", "
")))) + self.after_idle(lambda: self.preview.load_html(markdown(self.text.get("1.0", "end")))) From 2eba99f3cb1b78a71472d88536876580c824968c Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 13:04:49 +0200 Subject: [PATCH 26/31] preview indicator --- assets/check_dark.png | Bin 0 -> 4587 bytes assets/check_light.png | Bin 0 -> 4717 bytes src/editor.py | 41 ++++++++++++++++++++++++++--------------- src/main.py | 21 ++++++++++++++------- 4 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 assets/check_dark.png create mode 100644 assets/check_light.png diff --git a/assets/check_dark.png b/assets/check_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b8576f50e0e3eff512e05c17925fa94e29806dd6 GIT binary patch literal 4587 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 C?Nvem literal 0 HcmV?d00001 diff --git a/src/editor.py b/src/editor.py index a1d003e..8c08ea7 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,7 +1,7 @@ from markdown import markdown from os.path import basename, isfile from tkfilebrowser import askopenfilename, asksaveasfilename -from tkinter import Frame, Label, PhotoImage +from tkinter import Frame, Label, PhotoImage, TclError from tkinter.ttk import Button, Notebook, Style from tkinterweb import HtmlFrame from toml import load @@ -17,12 +17,12 @@ def __init__(self, theme, *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.closeimg = PhotoImage(file="assets/close_{}.png".format(theme)) - 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) @@ -41,7 +41,7 @@ def openfile(self): self.file.close() def save(self): - self.editor = self.nametowidget(self.select()) # Retrieves Editor Object of currently opened Tab + self.editor = self.getcurrentchild() self.filetosave = self.editor.filedir.cget("text") if isfile(self.filetosave): self.file2 = open(self.filetosave, "w") @@ -51,13 +51,16 @@ def save(self): self.saveas() def saveas(self): - self.editor = self.nametowidget(self.select()) + 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: @@ -88,6 +91,8 @@ 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) @@ -119,18 +124,24 @@ def get_lexer(self, file): return lexer def openpreview(self): - 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() + 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): - self.after_idle(lambda: self.preview.load_html(markdown(self.text.get("1.0", "end")))) + if self.ispreviewed: + self.after_idle(lambda: self.preview.load_html(markdown(self.text.get("1.0", "end")))) diff --git a/src/main.py b/src/main.py index 9d404dc..1c8c991 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -from tkinter import Menu, Tk +from tkinter import Menu, Tk, PhotoImage from sv_ttk import set_theme @@ -16,6 +16,8 @@ def __init__(self): set_theme(theme) + self.checkimg = PhotoImage(file="assets/check_{}.png".format(theme)) + self.h = self.winfo_screenheight() - 200 self.w = self.winfo_screenwidth() - 100 self.x = int((self.winfo_screenwidth() - self.w) / 2) @@ -28,16 +30,21 @@ def __init__(self): self.menubar = Menu(self, tearoff=False) self.config(menu=self.menubar) - self.filemenu = Menu(self.menubar, tearoff=False, bg="white") + 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, foreground="white" if LINUX else "black") - self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="white" if LINUX else "black") - self.filemenu.add_command(label="Save", command=self.manager.save, foreground="white" if LINUX else "black") - self.filemenu.add_command(label="Save As", command=self.manager.saveas, foreground="white" if LINUX else "black") + self.filemenu.add_command(label="New", command=self.manager.newtab, foreground="white" if LINUX and theme == "dark" else "black") + self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="white" if LINUX and theme == "dark" else "black") + self.filemenu.add_command(label="Save", command=self.manager.save, foreground="white" if LINUX and theme == "dark" else "black") + self.filemenu.add_command(label="Save As", command=self.manager.saveas, foreground="white" if LINUX and theme == "dark" else "black") self.filemenu.add_separator() - self.filemenu.add_command(label="Preview", command=self.manager.openpreview, foreground="white" if LINUX else "black") + self.filemenu.add_command(label="Preview", command=self.openpreview, foreground="white" if LINUX and theme == "dark" else "black", compound="right") + + def openpreview(self): + self.manager.openpreview() + if self.manager.getcurrentchild().ispreviewed: self.filemenu.entryconfigure(6, image=self.checkimg) + else: self.filemenu.entryconfigure(6, image="") From 8fb8be82c671c81044e2890ab04d521813eecdb0 Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 13:11:40 +0200 Subject: [PATCH 27/31] drag and drop --- src/main.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 1c8c991..00dcf41 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,5 @@ -from tkinter import Menu, Tk, PhotoImage +from tkinter import Menu, PhotoImage +from tkinterdnd2 import Tk, DND_FILES from sv_ttk import set_theme @@ -41,11 +42,19 @@ def __init__(self): self.filemenu.add_separator() self.filemenu.add_command(label="Preview", command=self.openpreview, foreground="white" if LINUX and theme == "dark" else "black", compound="right") + 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(6, 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() + if __name__ == "__main__": From 45323787e818d6babb85a4a7771d143b7c9e850e Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 13:21:29 +0200 Subject: [PATCH 28/31] outline for properties window --- src/editor.py | 2 +- src/main.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/editor.py b/src/editor.py index 8c08ea7..2e8a6ff 100644 --- a/src/editor.py +++ b/src/editor.py @@ -1,7 +1,7 @@ from markdown import markdown from os.path import basename, isfile from tkfilebrowser import askopenfilename, asksaveasfilename -from tkinter import Frame, Label, PhotoImage, TclError +from tkinter import Frame, Label, PhotoImage from tkinter.ttk import Button, Notebook, Style from tkinterweb import HtmlFrame from toml import load diff --git a/src/main.py b/src/main.py index 00dcf41..be58035 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -from tkinter import Menu, PhotoImage +from tkinter import Menu, PhotoImage, Toplevel from tkinterdnd2 import Tk, DND_FILES from sv_ttk import set_theme @@ -15,6 +15,7 @@ class App(Tk): def __init__(self): super().__init__() + self.title("Futura Notes") set_theme(theme) self.checkimg = PhotoImage(file="assets/check_{}.png".format(theme)) @@ -41,6 +42,8 @@ def __init__(self): self.filemenu.add_command(label="Save As", command=self.manager.saveas, foreground="white" if LINUX and theme == "dark" else "black") self.filemenu.add_separator() self.filemenu.add_command(label="Preview", command=self.openpreview, foreground="white" if LINUX and theme == "dark" else "black", compound="right") + self.filemenu.add_separator() + self.filemenu.add_command(label="Properties", command=self.openproperties, foreground="white" if LINUX and theme == "dark" else "black") self.drop_target_register(DND_FILES) self.dnd_bind("<>", self.filedrop) @@ -55,6 +58,14 @@ def filedrop(self, event): self.manager.newtab(self.file) self.file.close() + def openproperties(self): + self.properties = Properties(self) + +class Properties(Toplevel): + def __init__(self, *args): + super().__init__(*args) + + self.title("Properties Window") if __name__ == "__main__": From e95923af85c078d85a36cb465d094b9518b40f89 Mon Sep 17 00:00:00 2001 From: nef Date: Sat, 9 Sep 2023 16:31:18 +0200 Subject: [PATCH 29/31] first progress on properties window --- assets/filetypes/css_dark.png | Bin 0 -> 6434 bytes assets/filetypes/css_light.png | Bin 0 -> 7090 bytes assets/filetypes/html_dark.png | Bin 0 -> 6126 bytes assets/filetypes/html_light.png | Bin 0 -> 6622 bytes assets/filetypes/js_dark.png | Bin 0 -> 5783 bytes assets/filetypes/js_light.png | Bin 0 -> 6164 bytes assets/filetypes/md_dark.png | Bin 0 -> 5282 bytes assets/filetypes/md_light.png | Bin 0 -> 5430 bytes assets/filetypes/other_dark.png | Bin 0 -> 4850 bytes assets/filetypes/other_light.png | Bin 0 -> 4929 bytes assets/filetypes/txt_dark.png | Bin 0 -> 5157 bytes assets/filetypes/txt_light.png | Bin 0 -> 5292 bytes src/main.py | 26 ++++++++++++++++++++++---- 13 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 assets/filetypes/css_dark.png create mode 100644 assets/filetypes/css_light.png create mode 100644 assets/filetypes/html_dark.png create mode 100644 assets/filetypes/html_light.png create mode 100644 assets/filetypes/js_dark.png create mode 100644 assets/filetypes/js_light.png create mode 100644 assets/filetypes/md_dark.png create mode 100644 assets/filetypes/md_light.png create mode 100644 assets/filetypes/other_dark.png create mode 100644 assets/filetypes/other_light.png create mode 100644 assets/filetypes/txt_dark.png create mode 100644 assets/filetypes/txt_light.png diff --git a/assets/filetypes/css_dark.png b/assets/filetypes/css_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..11bddcdb8cdc87a3f803935b2d6193f96faea988 GIT binary patch literal 6434 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 literal 0 HcmV?d00001 diff --git a/assets/filetypes/html_light.png b/assets/filetypes/html_light.png new file mode 100644 index 0000000000000000000000000000000000000000..4876a4ad937c11d98a6d11d7279cd2b002b16f17 GIT binary patch literal 6622 zcmeHKdpML^`<}r}&T`19WEyJIHs<6slVRE<$}ll(l$gUi%#N99j0}-M2SsfpsT?bj zX-kHR5Y>*yM|_H}*Vf6dH#*YiH>UiVt-Ue9{weRop5 zR%)mkt3n_U4G(u$AFyW0iZUF$@*nP_fz@>&z=Q4Ku@s^V-hfD`5(Fy4fCc)UtpHVo zD1yH2V9^0>8Aeu&p)h%!4%+iS+u5LP@fo84+E}OpL<6kzz=8v9J+LkZ3unjdSdE}9 z`zXu5?w}=O{!*{>pimJwYb?eZZv*zn;P6BY0sJAbwnSSyqAdJ znjd#Lhj(QhJ?o%a0h^PkFVVDauOpsSO&-unm89)GoR&Qu>=Um2$fSi-Kh=Pa>4*Lz zZKFxUbf~shTg`PD*5{YP*LQm3w;Ddw!N0B*5@8)LqP0x>#}7MRJvY{&?W4LxF&_EC z>V`&|D8=D4=ULv=&Wk6kEH6F1qzezun9dxoy2{I2t*PdSk6__nU7|Bi-TYHvAgwCa z>6p7x&+kb5qNi0CV$-BSe@Neu-m0a*B7pg)L(X4cRhS6=xB!?P=+eXaH+&#VaC-Env zvt7iGLAt4lS$#v{nP#E<`Df3Lg}+?LuT@lcewMX}kX4vNwNe-lo20~s4(S^PM9tiG zYI&xZQL?^-D^76C)lozQ2_gX@9U+R0;6@QeB&3{|2--3?8i|lYgd0f605FhTcme=nV{K!N zL9GyRHsO#?st88`i%s-#b^A;K-jR^uLLr}sM#sd&SjXV4d4e!BmOvn&F*r01hXN6( zs93I$E<$mm%w!a^9IikVQ^4U1IXo^x#z|-JqJ<_e0v3vmS14Qwn1x#SYCV(q6`<{Zu{Mw%%Er^gW zhs8t#5kMpejRHr-{%uIvk^9<1CP5e{k}vNCiv2gFki-5a*5BqPYmqPK`-yd1h^{spc7DdP7SSTAd z&Yr=>**MtQV!xyE;6@4QTqYo+0?Dm8AP+zXSPZNk6NP00c$5vmut5`_>j0|8}&X8B-?ni@r6` zIFW#_kSo<;hdtBHI=sxEN4ax-3pX0=XYt z3(-XY(1kn!jmL{1A-|^Gugw(jII-wLx+`4>fTUj&G3`qt{uzTKVrHi=qoeRx9Cqw~ zLdy;h!V$!STkg(@0>_V)yJk<6FR*d;ZuT~UBR@(Ag#1tt>CD+BMA0_^vzrBS%{DQ^ z>D({?%pRY^^^2eLZ$bfwXX6R>SUVKUo^20?0t*910RfA$!(-TV1_29R333&_vPbdQ z!Wg;$SRMxI2x#fDSkSI| zaEe{%~4yi%Y2o`QR+oYhLX2)q`BqPr1bn`Mu{3H6+yDt9`pOBkCpRYlpUa&i@OTr5NvB_Er$|a9mAx4^IjigI1)(+-Biljm&KpDrM|x= zq_pIn$99JKW5YntX9I`sjpTL}La!9D$VAvGxH+AQL#Rl6afn+~?m;gLqm181?eZ-* zf(_Wkidh)Yv32_V=p*lV?&b`m&)3YO_SSOl|Xwx=VV4Qk}Lwv_@pg$+|Y1S29 zO>5lz>){*grFXQkH>pZ%T$eAep-o&K)j`keF=;aWBTou1Hl4QQQ`w7rp>>$?L9^- zZa|`R@il+Q)Y8+Y=65&pHZU|p%byPRu3MN3S=kDY5aOVLWmURSJ7WQVrzLTQxU5)27 zZ|%LHw@h-A(tRNEUVBYbO4AxtM7P3#mfQXw`u+C4c~sRtwQ?)i`hrgKwLE-CC~5)~ z>VGI}4_Z}}lOJMuai z^@Nfag?h+&DN*@oMn~v4d`lTT;jlk6%cyjc z-!>GfWU}v>QrC^)YdX`g%C*;+AAI-4cgCEwem54Al*Sm5)(xF|YLF1)ci?4KM4Ut32Qdetu%sLW;)+!FDdk$X{wk{tfX{G#hV|oA0XES z&=&_a9)h=2>#7`WaiaU1IK`4&LzOxvc48SfvvvgxZ0@GL9wAx{GH$eY+*7H+R4PtK zJ{PAxf0%1I78lCtJh_#Auia8B8(qw5ThqffU~W|Lg` z$nJtw8grApy%ZkZ^l47J5O5!oeu}y!P~*?;g=RCvikFgioL!3=UN>P5DKZ%rjb|Db zutKKNrTv|mP3w_{+dN@&r3(#pn!=wY$3#?DF|_&Qicl-gqI8Swle>EZtVG>O ztIa;;|BUQ>3+?VwFt6LgI9Z-(frg%}DNY!Npl{#(*lgi=t6XJ%mOg^#=Xc|DRJgfG z@6om^)te4CWB75MF{Y9edtc7@^zNLt3$G^j<5A>~^WSdCXfmrQB3~_1S1ddUb8)@x zLkf7KS|>5Pacyu4Q5$vUIowCmUA%VSfkpP=ac}n3|x@yHlUxwDly$ZBO7mcQ6yRmKQ|7uCcHB18qw_9kIYdF~++p#J&av zNZ~17YfD3oywiKbDGTtIpEqZXH=L5PwBjTNrN_}~G+x-t2Y0wermNPiKF%@9$uIPV zJYr(^Y+U^(OeLXdhY##%zyv&LC1b?&mn)_Vw<)Zf%luSG3v{d0f1H$yv6BcwSlt9+ z!M;@)J(h!y>pj?{y&-I=+kLX;of%^f{|n8xq#t~oD7Pi*7V7al<=>eOYP z#gjEv7^^-iiwpXkRQ4aHv}udK2tQ?TT#+K6;U@b%&D6y;yD#MGDL17G)cC&1 z5Bq{>Xo;s%o|($ABVPO6l>Jq22oP)4DsueL$>kAj;-q@pu7|nyE|ZhQbK2Lm3h=u} z>&l1DbyUNYo2bp*73Eir5c&zYpt@tH0`CqAIAIJaz literal 0 HcmV?d00001 diff --git a/assets/filetypes/js_dark.png b/assets/filetypes/js_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0b292fb8fba86ea4cad24236640ed5c6aa59fac0 GIT binary patch literal 5783 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 literal 0 HcmV?d00001 diff --git a/assets/filetypes/js_light.png b/assets/filetypes/js_light.png new file mode 100644 index 0000000000000000000000000000000000000000..2dcbfc1535229425f8811c5eb311c61db3043a05 GIT binary patch literal 6164 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 LTS-0b;1~T*!7OBq literal 0 HcmV?d00001 diff --git a/assets/filetypes/md_dark.png b/assets/filetypes/md_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8a4d1cb919cf526d432ca4666ed7999fdd5e3fbb GIT binary patch literal 5282 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 B+D!le literal 0 HcmV?d00001 diff --git a/assets/filetypes/other_dark.png b/assets/filetypes/other_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8bd4cb7831ff9c7ba384a37ac8fb7e353b60b141 GIT binary patch literal 4850 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 literal 0 HcmV?d00001 diff --git a/assets/filetypes/txt_dark.png b/assets/filetypes/txt_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..574d756177718751d4f126b1dbc76772faa2dc82 GIT binary patch literal 5157 zcmeHKdsq`!7Eee~3f4NL5|6*}qardEwRq3;CFs_uoBT1XFvtz~4 zA`tu@q7%{sYY%Gz_wd7A^T`0{QyS|#r7w{oY??H)J~-FB?M z-6w?}M%%!=71`;v)wW4gPxO@!z468SkrB1c(%1uD0!e5i zRH@cfWugQ%>RAY8RO2k0-b9`lB|64tLeO=%m7&Hpgh9$2IChfBATTNOMS+r|G{xas zLcZRNuUwy`Lf5ZD#TYYY5k1-_0R(#7iZETGwEb~Z;F%B7SLCu88N*E0ck`qxIbFES)6O1z+#HTkYl@s&^%eV?a z54H_4vAHY`Td!wNo?)@Z=K+%OfPQm^MO9$J*(tcim}^Gy_&nTToi~{RLnr2&a?Lu| zb}*EU>u^1QTEME@N0ub7+{6r00u7-zxn=>eA3<7)oQGmP5*s<<+Ro%a!1M{;N6-)U z?g9gpQYn!c(Ofb-g-psM*Oy>Ml)xloCXDj9c#axEb5J$JS9AD~NPr0-M2PdE#BdIV z3%HZ06b6eGF`zg}1;|+h;1LRJ?!;ed+J<14KQsU}w_=EK4$v4AfW zafCwNL(pp6Yyp`_a=MDAYlJ)&3D^t}79sN#5V-tcFOoPjj#!OmmC>k^GAGLJ#IO=v zCk(M7GQ^4l(uqQ>dQgZLa$pH(EPRqqX~c+}g8zjkFApOc;K7m02@6=iz~vgdQ7iF3 zjy;V%>Im0WVlZ5nLV}=UTd*K`IOd8IaE%S2TEw8iLG>6<*9U&$H&TI%^RXygAcl}A z4g&EJP82vlE(byd9F72oVKoOH6KR6oV$8ATBW8Sw2IvU10_o}c6U7L39aO}lvE*xU z@)Uqz5JwDt{!=ja_=eeJ&3LfmX!d_;672#^Dl%Z+m=6>$Pz%`;#c*6R5by8#89$5P za|s6H$s&)X?-RM6$n{tXJQny#c0G~nu@rbL@RRKNzsW^^_&S9fz<)vc;AP2}Q9l4) zw5F(+EtOI3li&R(4sQpZX(oA^g+lT4BO8_SR(T+BPPHnO@l$)JP4jx*8_OC6@9(rG zg=~q+_V=56y0<=q2e!ntfAj9T`3}tYpYbbvir(A(Oj^L5fIx#*=dsbf>A+=W_CIuV z`ijg|%NELOORbq@$xnSCvj)V^+!Y@m?nv0*xGszRwXWfZwKvQ6Xucj9bv6cxdiVTt z=BJ)L1-(xPq9xo@f#{hI-_hX|MO$G@A>_<-n)VI#z#sFNtc<=5J|V=>in(>QXXHP4 zf6l#eJ{w5+FWfojNV&KuJN(uw zc^kjb+jk^+ovS+Z!=dU<_pq8}mGfeSZfzNk%-)I;@9NSE(87Ys&KGCLzILZfm?GKH zwx*&a-F`4to>7r1ha>zQ?~hcMUO#xet74{S<6c}@-wa%b#)cBN6oqbAhnxn;>H=WD zeV8=9WK2ubX0hrbUe28&KlPS-y^?zWwj$x?(6wW7Dg$~0O zyQe?A@9@Sq4YeW6ZM2rI^8AblSqE)Xx<^+lhZ90viv4j=G`P3oZ76hlS0SOu2(2@` zk+Qo9^D{j?yt<>sIpjR!d$->zyPFdd6;|kVK~ot>^`GCoJ84Z^Nl~hucxlB+k8+f8%I$I2Cv8*35rN-^;Yya3 zfqN^1J*#GPei~#B`ZFbozvlda`(DlW?)mW^zs$Zg%jdc6XX3&(vNt}zOquq2bt}bg z&no;|!iOO-p@}`+J0q{YvpIH7WLoQ5MZSg`(l;mDcsl;&*@JZz`Gxy}`>XqDc{{(u zsoy3ItlxF3`D|=SQSH{q-QDf9&*nN7+Y3jXP4?l#-|xzwKbUfxxWO$xwDRJJ&nXZq_Rna-rk7?qhsX{WF+&VTo!M`Kv} zF4}Up`oR6BDB_c3AI4_C_AABm BU8Vp4 literal 0 HcmV?d00001 diff --git a/assets/filetypes/txt_light.png b/assets/filetypes/txt_light.png new file mode 100644 index 0000000000000000000000000000000000000000..d84b9cb20ec5de8f9d5c1732cbcdc91b05a32bb2 GIT binary patch literal 5292 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 edW=2u`s|~+?T Date: Sat, 9 Sep 2023 22:18:27 +0200 Subject: [PATCH 30/31] properties window --- src/dialogs.py | 190 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.py | 52 +++++++++++++- 2 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 src/dialogs.py 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/main.py b/src/main.py index 4aa26bb..6f15a87 100644 --- a/src/main.py +++ b/src/main.py @@ -1,17 +1,22 @@ +from os import rename from os.path import isfile -from tkinter import Menu, PhotoImage, Toplevel, Label -from tkinter.ttk import Entry +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): @@ -62,15 +67,23 @@ def filedrop(self, event): 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("350x400") + self.geometry("350x175") self.resizable(False, False) self.imagefile = "assets/filetypes/{}_{}.png".format(file.split(".")[-1], theme) @@ -85,6 +98,39 @@ def __init__(self, file, *args): 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() From a4b340aca3174d302969cd78eb723718d359fa0b Mon Sep 17 00:00:00 2001 From: not-nef Date: Sat, 30 Sep 2023 14:04:59 +0200 Subject: [PATCH 31/31] fix some stuff --- src/main.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main.py b/src/main.py index 6f15a87..6b7c554 100644 --- a/src/main.py +++ b/src/main.py @@ -25,7 +25,7 @@ def __init__(self): self.title("Futura Notes") set_theme(theme) - self.checkimg = PhotoImage(file="assets/check_{}.png".format(theme)) + self.checkimg = PhotoImage(file="assets/check_light.png") self.h = self.winfo_screenheight() - 200 self.w = self.winfo_screenwidth() - 100 @@ -43,21 +43,22 @@ def __init__(self): self.menubar.add_cascade(label="File", menu=self.filemenu) - self.filemenu.add_command(label="New", command=self.manager.newtab, foreground="white" if LINUX and theme == "dark" else "black") - self.filemenu.add_command(label="Open", command=self.manager.openfile, foreground="white" if LINUX and theme == "dark" else "black") - self.filemenu.add_command(label="Save", command=self.manager.save, foreground="white" if LINUX and theme == "dark" else "black") - self.filemenu.add_command(label="Save As", command=self.manager.saveas, foreground="white" if LINUX and theme == "dark" else "black") - self.filemenu.add_separator() - self.filemenu.add_command(label="Preview", command=self.openpreview, foreground="white" if LINUX and theme == "dark" else "black", compound="right") - self.filemenu.add_separator() - self.filemenu.add_command(label="Properties", command=self.openproperties, foreground="white" if LINUX and theme == "dark" else "black") + 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(6, image=self.checkimg) + if self.manager.getcurrentchild().ispreviewed: + self.filemenu.entryconfigure(5, image=self.checkimg) else: self.filemenu.entryconfigure(6, image="") def filedrop(self, event):