I am new to programing, but here is my attempt, i am learning as i go, i have some flaws, soe incomplete routines, but the program compiled seems to work flawlessly;
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
import json
import os
# still learning be gentle
# Figure out ehy it says it folloed the curser for inserts but appends instead, I am working on that.
class LunarOrbitLogbook:
def __init__(self, root):
self.root = root
self.root.title("Lunar Orbit Logbook, Blog Writer for Gemini Protocol")
self.root.configure(bg='#001f3f')
self.title_var = tk.StringVar()
self.author_var = tk.StringVar()
self.date_var = tk.StringVar()
self.bio_var = tk.StringVar()
self.keywords_var = tk.StringVar()
self.tags_var = tk.StringVar()
self.text_frame = tk.Frame(root, bg='#001f3f')
self.text_frame.grid(row=3, column=0, columnspan=2, sticky=tk.NSEW, padx=5, pady=5)
self.content_text = tk.Text(self.text_frame, wrap=tk.WORD, bg='#001f3f', fg='white', insertbackground='white')
self.content_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.text_frame, command=self.content_text.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.content_text.config(yscrollcommand=self.scrollbar.set)
self.word_count_label = tk.Label(root, text="Word Count: 0", bg='#001f3f', fg='white')
self.word_count_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=5)
self.versions = []
self.create_widgets()
self.bind_shortcuts()
self.content_text.bind("<KeyRelease>", self.update_word_count)
#debating on wheter to rearrange location of keywords and tags.
def create_widgets(self):
tk.Label(self.root, text="Title:", bg='#001f3f', fg='white').grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
tk.Entry(self.root, textvariable=self.title_var, bg='#0074D9', fg='white').grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)
tk.Label(self.root, text="Author:", bg='#001f3f', fg='white').grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
tk.Entry(self.root, textvariable=self.author_var, bg='#0074D9', fg='white').grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)
tk.Label(self.root, text="Date of Publish:", bg='#001f3f', fg='white').grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
tk.Entry(self.root, textvariable=self.date_var, bg='#0074D9', fg='white').grid(row=2, column=1, sticky=tk.EW, padx=5, pady=5)
tk.Label(self.root, text="Short Bio:", bg='#001f3f', fg='white').grid(row=4, column=0, sticky=tk.W, padx=5, pady=5)
tk.Entry(self.root, textvariable=self.bio_var, bg='#0074D9', fg='white').grid(row=4, column=1, sticky=tk.EW, padx=5, pady=5)
tk.Label(self.root, text="Keywords:", bg='#001f3f', fg='white').grid(row=5, column=0, sticky=tk.W, padx=5, pady=5)
tk.Entry(self.root, textvariable=self.keywords_var, bg='#0074D9', fg='white').grid(row=5, column=1, sticky=tk.EW, padx=5, pady=5)
tk.Label(self.root, text="Tags:", bg='#001f3f', fg='white').grid(row=6, column=0, sticky=tk.W, padx=5, pady=5)
tk.Entry(self.root, textvariable=self.tags_var, bg='#0074D9', fg='white').grid(row=6, column=1, sticky=tk.EW, padx=5, pady=5)
menu = tk.Menu(self.root, bg='black', fg='green')
self.root.config(menu=menu)
file_menu = tk.Menu(menu, bg='black', fg='green')
menu.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="New", command=self.new_file, accelerator="Ctrl+N")
file_menu.add_command(label="Copy", command=self.copy_text, accelerator="Ctrl+C")
file_menu.add_command(label="Paste", command=self.paste_text, accelerator="Ctrl+V")
file_menu.add_command(label="Cut", command=self.cut_text, accelerator="Ctrl+X")
file_menu.add_command(label="Save as .GMI", command=self.save_file, accelerator="Ctrl+S")
file_menu.add_command(label="Load .GMI", command=self.load_file, accelerator="Ctrl+O")
file_menu.add_command(label="Save as .GOPHER", command=self.save_gopher_file, accelerator="Ctrl+Shift+S")
file_menu.add_command(label="Load .GOPHER", command=self.load_gopher_file, accelerator="Ctrl+Shift+L")
file_menu.add_command(label="Save as .SPARTAN", command=self.save_gopher_file, accelerator="Ctrl+Shift+S")
file_menu.add_command(label="Load .SPARTAN", command=self.load_gopher_file, accelerator="Ctrl+Shift+L")
file_menu.add_command(label="Export as Markdown", command=self.export_as_markdown)
file_menu.add_command(label="Export as HTML", command=self.export_as_html)
inserts_menu = tk.Menu(menu, bg='black', fg='green')
menu.add_cascade(label="Inserts", menu=inserts_menu)
inserts_menu.add_command(label="Insert Heading 1", command=lambda: self.insert_gemtext("# Heading"))
inserts_menu.add_command(label="Insert Heading 2", command=lambda: self.insert_gemtext("## Sub-heading"))
inserts_menu.add_command(label="Insert Heading 3", command=lambda: self.insert_gemtext("### Sub-subheading"))
inserts_menu.add_command(label="Insert Link", command=self.insert_link)
inserts_menu.add_command(label="Insert Quote", command=self.insert_quote)
inserts_menu.add_command(label="Insert Preformatted Text", command=self.insert_preformatted_text)
inserts_menu.add_command(label="Insert List Item", command=lambda: self.insert_gemtext("* List Item"))
inserts_menu.add_command(label="Insert Input Spartan Only", command=lambda: self.insert_gemtext("= input field"))
inserts_menu.add_command(label="Save Version", command=self.save_version)
inserts_menu.add_command(label="Revert to Last Version", command=self.revert_version)
search_menu = tk.Menu(menu, bg='black', fg='green')
menu.add_cascade(label="Search", menu=search_menu)
search_menu.add_command(label="Search Current Blog", command=self.search_posts)
about_menu = tk.Menu(menu, bg='black', fg='green')
menu.add_cascade(label="About", menu=about_menu)
about_menu.add_command(label="About This Program", command=self.show_about)
self.root.grid_rowconfigure(3, weight=1)
self.root.grid_columnconfigure(1, weight=1)
def show_about(self):
about_message = (
"Lunar Orbit Logbook, Blog Writer for Gemini Protocol\n"
"Program Created by Brian Lynn Bentley\n"
"Ophesian Group copyright 2026\n"
"This application simplifies the creation of Gemlogs, "
"allowing users to write, save, and export their blog posts "
"in various formats."
)
messagebox.showinfo("About", about_message)
def bind_shortcuts(self):
self.root.bind('<Control-n>', lambda event: self.new_file())
self.root.bind('<Control-c>', lambda event: self.copy_text())
self.root.bind('<Control-v>', lambda event: self.paste_text())
self.root.bind('<Control-x>', lambda event: self.cut_text())
self.root.bind('<Control-s>', lambda event: self.save_file())
self.root.bind('<Control-o>', lambda event: self.load_file())
self.root.bind('<Control-S>', lambda event: self.save_gopher_file())
self.root.bind('<Control-L>', lambda event: self.load_gopher_file())
def new_file(self):
self.content_text.delete("1.0", tk.END)
self.title_var.set("")
self.author_var.set("")
self.date_var.set("")
self.bio_var.set("")
self.keywords_var.set("")
self.tags_var.set("")
self.word_count_label.config(text="Word Count: 0")
def copy_text(self):
self.root.clipboard_clear()
text = self.content_text.get(tk.SEL_FIRST, tk.SEL_LAST)
self.root.clipboard_append(text)
def paste_text(self):
try:
text = self.root.clipboard_get()
self.content_text.insert(tk.INSERT, text)
except tk.TclError:
messagebox.showwarning("Paste Error", "No text in clipboard to paste.")
def cut_text(self):
self.copy_text()
self.content_text.delete(tk.SEL_FIRST, tk.SEL_LAST)
def update_word_count(self, event=None):
content = self.content_text.get("1.0", tk.END).strip()
word_count = len(content.split()) if content else 0
self.word_count_label.config(text=f"Word Count: {word_count}")
def insert_gemtext(self, gemtext):
self.content_text.insert(tk.END, f"{gemtext}\n")
def insert_quote(self):
quote = simpledialog.askstring("Insert Quote", "Enter your quote:")
if quote:
self.insert_gemtext(f"> {quote}")
def insert_preformatted_text(self):
preformatted_text = simpledialog.askstring("Insert Preformatted Text", "Enter your preformatted text:")
if preformatted_text:
self.insert_gemtext(f"```\n{preformatted_text}\n```")
def insert_link(self):
url = simpledialog.askstring("Insert Link", "Enter the URL:")
label = simpledialog.askstring("Insert Link", "Enter the link label:")
if url and label:
self.content_text.insert(tk.END, f"=> {url}\t{label}\n")
def save_file(self):
content = self.get_content()
if self.validate_fields():
file_path = filedialog.asksaveasfilename(defaultextension=".gmi", filetypes=[("Gemini Files", "*.gmi")])
if file_path:
try:
with open(file_path, 'w') as file:
file.write(content)
self.save_as_json()
messagebox.showinfo("Success", "File saved successfully!")
except IOError as e:
messagebox.showerror("File Error", f"An error occurred while saving the file: {e}")
def load_file(self):
file_path = filedialog.askopenfilename(filetypes=[("Gemini Files", "*.gmi")])
if file_path and os.path.isfile(file_path):
try:
with open(file_path, 'r') as file:
content = file.read()
self.set_content(content)
messagebox.showinfo("Success", "File loaded successfully!")
except IOError as e:
messagebox.showerror("File Error", f"An error occurred while loading the file: {e}")
def save_gopher_file(self):
content = self.get_content()
if self.validate_fields():
file_path = filedialog.asksaveasfilename(defaultextension=".gopher", filetypes=[("Gopher Files", "*.gopher")])
if file_path:
try:
with open(file_path, 'w') as file:
file.write(content)
messagebox.showinfo("Success", "Gopher file saved successfully!")
except IOError as e:
messagebox.showerror("File Error", f"An error occurred while saving the Gopher file: {e}")
def load_gopher_file(self):
file_path = filedialog.askopenfilename(filetypes=[("Gopher Files", "*.gopher")])
if file_path and os.path.isfile(file_path):
try:
with open(file_path, 'r') as file:
content = file.read()
self.set_content(content)
messagebox.showinfo("Success", "Gopher file loaded successfully!")
except IOError as e:
messagebox.showerror("File Error", f"An error occurred while loading the Gopher file: {e}")
def get_content(self):
return f"## {self.title_var.get()}\n" \
f"Author: {self.author_var.get()}\n" \
f"Date: {self.date_var.get()}\n" \
f"Bio: {self.bio_var.get()}\n" \
f"Keywords: {self.keywords_var.get()}\n" \
f"Tags: {self.tags_var.get()}\n\n" \
f"{self.content_text.get('1.0', tk.END)}"
def set_content(self, content):
lines = content.splitlines()
self.title_var.set(lines[0][3:])
self.author_var.set(lines[1][7:])
self.date_var.set(lines[2][6:])
self.bio_var.set(lines[3][5:])
self.keywords_var.set(lines[4][9:])
self.tags_var.set(lines[5][6:])
self.content_text.delete("1.0", tk.END)
self.content_text.insert(tk.END, "\n".join(lines[7:]))
def export_as_markdown(self):
content = self.get_content()
file_path = filedialog.asksaveasfilename(defaultextension=".md", filetypes=[("Markdown Files", "*.md")])
if file_path:
try:
with open(file_path, 'w') as file:
file.write(content)
messagebox.showinfo("Success", "File exported as Markdown successfully!")
except IOError as e:
messagebox.showerror("File Error", f"An error occurred while exporting the file: {e}")
def export_as_html(self):
content = self.get_content()
html_content = f"<html><body><h1>{self.title_var.get()}</h1><p>Author: {self.author_var.get()}</p><p>Date: {self.date_var.get()}</p><p>Bio: {self.bio_var.get()}</p><p>Keywords: {self.keywords_var.get()}</p><p>Tags: {self.tags_var.get()}</p><div>{self.content_text.get('1.0', tk.END)}</div></body></html>"
file_path = filedialog.asksaveasfilename(defaultextension=".html", filetypes=[("HTML Files", "*.html")])
if file_path:
try:
with open(file_path, 'w') as file:
file.write(html_content)
messagebox.showinfo("Success", "File exported as HTML successfully!")
except IOError as e:
messagebox.showerror("File Error", f"An error occurred while exporting the file: {e}")
def save_as_json(self):
json_data = {
"title": self.title_var.get(),
"author": self.author_var.get(),
"date": self.date_var.get(),
"bio": self.bio_var.get(),
"keywords": self.keywords_var.get().split(','),
"tags": self.tags_var.get().split(','),
"content": self.content_text.get('1.0', tk.END).strip()
}
json_file_path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON Files", "*.json")])
if json_file_path:
try:
with open(json_file_path, 'w') as json_file:
json.dump(json_data, json_file, indent=4)
messagebox.showinfo("Success", "File saved as JSON successfully!")
except IOError as e:
messagebox.showerror("File Error", f"An error occurred while saving the JSON file: {e}")
def save_version(self):
self.versions.append(self.get_content())
messagebox.showinfo("Version Saved", "Current version saved successfully!")
def revert_version(self):
if self.versions:
last_version = self.versions.pop()
self.set_content(last_version)
messagebox.showinfo("Version Reverted", "Reverted to the last saved version.")
else:
messagebox.showwarning("No Versions", "No versions available to revert.")
def search_posts(self):
search_term = simpledialog.askstring("Search Current Document", "Enter keyword to search:")
if search_term:
content = self.content_text.get('1.0', tk.END)
start_index = '1.0'
self.content_text.tag_remove('highlight', '1.0', tk.END)
while True:
start_index = self.content_text.search(search_term, start_index, nocase=True, stopindex=tk.END)
if not start_index:
break
end_index = f"{start_index}+{len(search_term)}c"
self.content_text.tag_add('highlight', start_index, end_index)
start_index = end_index
self.content_text.tag_config('highlight', background='yellow')
if self.content_text.index('highlight.first') != '1.0':
self.content_text.see('highlight.first')
messagebox.showinfo("Search Result", f"Keyword '{search_term}' found in the post.")
else:
messagebox.showinfo("Search Result", f"Keyword '{search_term}' not found.")
def validate_fields(self):
if not self.title_var.get().strip():
messagebox.showwarning("Validation Error", "Title cannot be empty.")
return False
if not self.author_var.get().strip():
messagebox.showwarning("Validation Error", "Author cannot be empty.")
return False
if not self.date_var.get().strip():
messagebox.showwarning("Validation Error", "Date cannot be empty.")
return False
if not self.bio_var.get().strip():
messagebox.showwarning("Validation Error", "Bio cannot be empty.")
return False
return True
if __name__ == "__main__":
root = tk.Tk()
app = LunarOrbitLogbook(root)
root.mainloop()
Comments
Displaying 0 of 0 comments ( View all | Add Comment )